Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to modify docker-compose configuration #722

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions docs/extra-containers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
order: 100
---
# Extra Containers

Local Server provides a series of Docker containers for the base Altis behaviour. Modules and other packages may add additional
containers (typically called "sidecar" containers) or alter the container behaviour through the extension system.


## How Local Server works

Local Server internally uses Docker Compose to create and manage containers for the Altis environment. When a user runs the
`composer server start` command, Local Server dynamically provisions a `docker-compose.yml` file based on the user's preferences.
(This file is only regenerated when starting Local Server to avoid conflicts or surprising behaviour for users.)

Other commands such as `composer server logs` are passthrough-style commands, which primarily wrap `docker-compose` commands, and
which work with Docker Compose server names.

Domain routing is internally handled using [Traefik Proxy](https://doc.traefik.io/traefik/) and each service can register
user-accessible routes using the [label configuration system](https://doc.traefik.io/traefik/providers/docker/).

Extensions register a class which has the ability to filter the `docker-compose.yml` data before it is written out to a file,
allowing the extensions to register additional services and containers, or modify the configuration in other ways.


## Writing an extension

Any Composer package (including the root package, i.e. the project) can specify an extension class, which is dynamically loaded by
Local Server and which receives the `docker-compose.yml` data to filter.

Packages should implement the [`Altis\Local_Server\Composer\Compose_Extension`
interface](https://github.com/humanmade/altis-local-server/blob/master/inc/composer/class-compose-extension.php) with the relevant
methods.

**Note:** This class will be loaded in the Composer context, not in WordPress, and neither hooks nor the full codebase will be
loaded. Notably, functions like `Altis\get_config()` will not return default values as a result.

For example, to add a sidecar service called `foo`:

```php
namespace MyModule;

use Altis\Local_Server\Composer\{Compose_Extension, Docker_Compose_Generator};

class Local_Server_Extension implements Compose_Extension {
protected Docker_Compose_Generator $generator;

public function set_config( Docker_Compose_Generator $generator, array $args ) : void {
$this->generator = $generator;
}

public function filter_compose( array $config ) : array {
$config['services']['foo'] = [
'container_name' => "{$this->generator->project_name}-foo",
'image' => 'hello-world',
];

return $config;
}
```

This class should then be specified in the package's `composer.json` as `extra.altis.local-server.compose-extension`:

```json
{
"extra": {
"altis": {
"local-server": {
"compose-extension": "Altis\\Enhanced_Search\\Local_Server_Extension"
}
}
}
}
```
47 changes: 46 additions & 1 deletion inc/composer/class-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
* @package altis/local-server
*/
class Command extends BaseCommand {
/**
* Package-specific configuration.
*/

Check failure on line 34 in inc/composer/class-command.php

View check run for this annotation

HM Linter / hmlinter

inc/composer/class-command.php#L34

Missing @var tag in member variable comment
Raw output
{
  "line": 34,
  "column": 6,
  "severity": "error",
  "message": "Missing @var tag in member variable comment",
  "source": "Squiz.Commenting.VariableComment.MissingVar"
}
protected array $package_config;

Check failure on line 35 in inc/composer/class-command.php

View check run for this annotation

HM Linter / hmlinter

inc/composer/class-command.php#L35

PHP syntax error: syntax error, unexpected 'array' (T_ARRAY), expecting function (T_FUNCTION) or const (T_CONST)
Raw output
{
  "line": 35,
  "column": 1,
  "severity": "error",
  "message": "PHP syntax error: syntax error, unexpected 'array' (T_ARRAY), expecting function (T_FUNCTION) or const (T_CONST)",
  "source": "Generic.PHP.Syntax.PHPSyntax"
}

/**
* Command configuration.
Expand Down Expand Up @@ -116,6 +120,40 @@
);
}

/**
* Get all extra configs added by all the packages.
*
* @return array Extra configurations added by packages (e.g. handlers).
*/
protected function get_package_configs() : array {
// Fetch all the packages.
$repo = $this->getApplication()->getComposer()->getLocker()->getLockedRepository( true );
$packages = [];
foreach ( $repo->getPackages() as $package ) {
$packages[ $package->getName() ] = $package;
}

// Check which are publishing containers.
$config = [];
foreach ( $packages as $name => $package ) {
$extra = $package->getExtra();
if ( empty( $extra['altis'] ) || empty( $extra['altis']['local-server'] ) ) {
continue;
}

$config[ $name ] = $extra['altis']['local-server'];
}

// If we have any packages, ensure we have all the autoloaders loaded.
// (By default, Composer only loads composer-plugin packages and their
// dependencies.)

Check failure on line 149 in inc/composer/class-command.php

View check run for this annotation

HM Linter / hmlinter

inc/composer/class-command.php#L149

Inline comments must end in full-stops, exclamation marks, or question marks
Raw output
{
  "line": 149,
  "column": 9,
  "severity": "error",
  "message": "Inline comments must end in full-stops, exclamation marks, or question marks",
  "source": "Squiz.Commenting.InlineComment.InvalidEndChar"
}
if ( ! empty( $config ) ) {
require $this->getApplication()->getComposer()->getConfig()->get( 'vendor-dir' ) . '/autoload.php';
}

return $config;
}

/**
* Execute the given command.
*
Expand Down Expand Up @@ -996,7 +1034,14 @@
* @return void
*/
protected function generate_docker_compose( array $args = [] ) : void {
$docker_compose = new Docker_Compose_Generator( getcwd(), $this->get_project_subdomain(), $this->get_project_tld(), $this->get_project_url(), $args );
$docker_compose = new Docker_Compose_Generator(
getcwd(),
$this->get_project_subdomain(),
$this->get_project_tld(),
$this->get_project_url(),
$args,
$this->get_package_configs()
);
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
file_put_contents(
getcwd() . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'docker-compose.yml',
Expand Down
31 changes: 31 additions & 0 deletions inc/composer/class-compose-extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

Check failure on line 1 in inc/composer/class-compose-extension.php

View check run for this annotation

HM Linter / hmlinter

inc/composer/class-compose-extension.php#L1

Missing file doc comment
Raw output
{
  "line": 1,
  "column": 1,
  "severity": "error",
  "message": "Missing file doc comment",
  "source": "Squiz.Commenting.FileComment.Missing"
}

namespace Altis\Local_Server\Composer;

/**
* Local Server docker-compose extension.
*
* This interface allows modules to alter the Local Server configuration for
* Docker Compose, such as adding additional sidecar containers.
*/
interface Compose_Extension {
/**
* Configure the extension.
*
* @param Docker_Compose_Generator $generator The root generator.
* @param array $args An optional array of arguments to modify the behaviour of the generator.
*/
public function set_config( Docker_Compose_Generator $generator, array $args ) : void;

/**
* Filter the docker-compose.yml config.
*
* This method is supplied with the full configuration for docker-compose
* before it is saved to a file. Handlers can filter this value and return
* an updated config, such as adding additional services.
*
* @param array $config Full docker-compose.yml configuration.
* @return array Altered docker-compose.yml configuration.
*/
public function filter_compose( array $config ) : array;
}
35 changes: 26 additions & 9 deletions inc/composer/class-docker-compose-generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,56 +20,61 @@
*
* @var string
*/
protected $project_name;
public $project_name;

/**
* The S3 bucket name.
*
* @var string
*/
protected $bucket_name;
public $bucket_name;

/**
* The Altis project root directory.
*
* @var string
*/
protected $root_dir;
public $root_dir;

/**
* The docker-compose.yml directory.
*
* @var string
*/
protected $config_dir;
public $config_dir;

/**
* The primary top level domain for the server.
*
* @var string
*/
protected $tld;
public $tld;

/**
* The primary domain name for the project.
*
* @var string
*/
protected $hostname;
public $hostname;

/**
* The client facing domain name for the project.
*
* @var string
*/
protected $url;
public $url;

/**
* An array of data passed to
*
* @var array
*/
protected $args;
public $args;

/**
* Extra configuration from packages.
*/

Check failure on line 76 in inc/composer/class-docker-compose-generator.php

View check run for this annotation

HM Linter / hmlinter

inc/composer/class-docker-compose-generator.php#L76

Missing @var tag in member variable comment
Raw output
{
  "line": 76,
  "column": 6,
  "severity": "error",
  "message": "Missing @var tag in member variable comment",
  "source": "Squiz.Commenting.VariableComment.MissingVar"
}
protected $extra;

/**
* Create and configure the generator.
Expand All @@ -79,8 +84,9 @@
* @param string $tld The primary top level domain for the server.
* @param string $url The client facing URL.
* @param array $args An optional array of arguments to modify the behaviour of the generator.
* @param array $extra Extra configuration from packages.
*/
public function __construct( string $root_dir, string $project_name, string $tld, string $url, array $args = [] ) {
public function __construct( string $root_dir, string $project_name, string $tld, string $url, array $args = [], array $extra = [] ) {
$this->project_name = $project_name;
$this->bucket_name = "s3-{$this->project_name}";
$this->config_dir = dirname( __DIR__, 2 ) . '/docker';
Expand All @@ -89,6 +95,7 @@
$this->hostname = $this->tld ? $this->project_name . '.' . $this->tld : $this->project_name;
$this->url = $url;
$this->args = $args;
$this->extra = $extra;
}

/**
Expand Down Expand Up @@ -923,6 +930,16 @@
}
}

// Initialize plugins and run them.
if ( ! empty( $this->extra ) ) {
foreach ( $this->extra as $package_spec ) {
/** @var Compose_Extension */

Check failure on line 936 in inc/composer/class-docker-compose-generator.php

View check run for this annotation

HM Linter / hmlinter

inc/composer/class-docker-compose-generator.php#L936

Missing short description in doc comment
Raw output
{
  "line": 936,
  "column": 17,
  "severity": "error",
  "message": "Missing short description in doc comment",
  "source": "Generic.Commenting.DocComment.MissingShort"
}
$handler = new $package_spec['compose-extension']();
$handler->set_config( $this, $this->args );
$config = $handler->filter_compose( $config );
}
}

return $config;
}

Expand Down