Skip to content

Commit

Permalink
Merge pull request #128 from stellarwp/fix/ip-address-push-pull
Browse files Browse the repository at this point in the history
Better management of on-up callbacks
  • Loading branch information
borkweb authored Sep 7, 2022
2 parents e12b17f + 3012123 commit 3682725
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 81 deletions.
3 changes: 3 additions & 0 deletions slic.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require_once __DIR__ . '/src/scaffold.php';
require_once __DIR__ . '/src/slic.php';
require_once __DIR__ . '/src/docker.php';
require_once __DIR__ . '/src/notify.php';
require_once __DIR__ . '/src/plugins.php';
require_once __DIR__ . '/src/themes.php';
require_once __DIR__ . '/src/scripts.php';
Expand All @@ -16,6 +17,8 @@
require_once __DIR__ . '/src/codeception.php';
require_once __DIR__ . '/src/commands.php';

require_once __DIR__ . '/src/classes/Callback_Stack.php';

use function StellarWP\Slic\args;
use function StellarWP\Slic\cli_header;
use function StellarWP\Slic\colorize;
Expand Down
45 changes: 45 additions & 0 deletions src/classes/Callback_Stack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace StellarWP\Slic;

class Callback_Stack {
/**
* A set of callbacks accumulated in the stack so far, by
* priority.
*
* @var array<string,callable>
*/
private $callbacks = [];

public function call(): void {
foreach ( $this->callbacks as $callback_id => $callback ) {
$callback();

// Remove the callback from the stack after it has been called.
$this->remove( $callback_id );
}
}

/**
* Add a callback to the stack, with a specified priority.
*
* @param string $callback_id The callback ID.
* @param callable $callback The callback to add.
*
* @return void The callback is added to the stack.
*/
public function add( string $callback_id, callable $callback ): void {
$this->callbacks[ $callback_id ] = $callback;
}

/**
* Remove a callback from the stack.
*
* @param string $callback_id The callback ID.
*
* @return void The callback is removed from the stack.
*/
private function remove( $callback_id ) {
unset( $this->callbacks[ $callback_id ] );
}
}
10 changes: 10 additions & 0 deletions src/notify.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
/**
* Provides functions for container notifications.
*/

namespace StellarWP\Slic;

function service_wordpress_notify() {
echo colorize( PHP_EOL . "Your WordPress site is reachable at: <yellow>http://localhost:" . getenv( 'WORDPRESS_HTTP_PORT' ) . "</yellow>" . PHP_EOL );
}
163 changes: 92 additions & 71 deletions src/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function stack_schema() {
require_once __DIR__ . '/../includes/Spyc/Spyc.php';

$schemas = [];
$stack = slic_stack_array( true );
$stack = slic_stack_array( true );
foreach ( $stack as $file ) {
if ( ! is_readable( $file ) ) {
echo magenta( "File $file cannot be found or is not readable." );
Expand Down Expand Up @@ -65,6 +65,7 @@ function get_services() {
$services = services_schema();
$services = array_keys( $services );
sort( $services );

return $services;
}

Expand All @@ -79,7 +80,7 @@ function get_services() {
* @return array<string> A list of the service dependencies; empty if the service has no
* dependencies.
*/
function service_dependencies( $service ) {
function service_dependencies( string $service ) {
$services_schema = services_schema();
$service_links = isset( $services_schema[ $service ]['links'] ) ? $services_schema[ $service ]['links'] : [];
$service_dependencies = isset( $services_schema[ $service ]['depends_on'] ) ? $services_schema[ $service ]['depends_on'] : [];
Expand All @@ -96,7 +97,7 @@ function service_dependencies( $service ) {
*
* @return false Whether the specified service requires at least one of the dependencies or not.
*/
function service_requires( $service, ...$dependencies ) {
function service_requires( string $service, ...$dependencies ) {
if ( empty( $dependencies ) ) {
return false;
}
Expand All @@ -109,26 +110,37 @@ function service_requires( $service, ...$dependencies ) {
*
* @param string $service The service to check.
*
* @return Closure A closure that should be called after the service is
* up and running, to finish setting it up.
* @return void The service readiness is ensured; if required
* by the service, a callback that should be called
* after the service is ready is registered in the
* Services callback stack.
*/
function ensure_service_ready( $service ) {
$noop = static function(){};
function ensure_service_ready( string $service ): void {
$propagate_wordpress_address = static function () {
// If wordpress isn't running, there's no IP address to propagate.
if ( ! service_running( 'wordpress' ) ) {
return;
}

propagate_ip_address_of_to(
[ 'wordpress' ],
[ 'wordpress', 'slic', 'chrome' ],
[ 'wordpress' => 'wordpress.test' ]
);
};

switch ( $service ) {
case 'wordpress':
ensure_wordpress_ready();
service_up_notify( 'wordpress' );

return static function () {
propagate_ip_address_of_to(
[ 'wordpress' ],
[ 'wordpress', 'slic', 'chrome' ],
[ 'wordpress' => 'wordpress.test' ]
);
};
services_callback_stack()->add( 'propagate_wp_address', $propagate_wordpress_address );
services_callback_stack()->add( 'wordpress_notify', '\StellarWP\Slic\service_wordpress_notify' );
break;
case 'slic':
case 'chrome':
services_callback_stack()->add( 'propagate_wp_address', $propagate_wordpress_address );
break;
default:
return $noop;
break;
}
}

Expand All @@ -138,12 +150,25 @@ function ensure_service_ready( $service ) {
*
* @param string $service The service to ensure the dependencies for.
*
* @return Closure A closure that should be called after the service is
* up and running to finish setting it up in respect to
* its dependencies.
* @return void
*/
function ensure_service_dependencies( string $service ): void {
ensure_services_running_no_callbacks( service_dependencies( $service ) );
}

/**
* Ensures a list of services is running and returns
* a Closure that should be called to finish setting them up.
*
* @param array<string> $services A list of services to ensure
* are running.
*
* @return void On-up callbacks will be registered on the global
* callback stack.
*/
function ensure_service_dependencies( $service ) {
return ensure_services_running( service_dependencies( $service ) );
function ensure_services_running( array $services ): void {
ensure_services_running_no_callbacks( $services );
services_callback_stack()->call();
}

/**
Expand All @@ -153,10 +178,9 @@ function ensure_service_dependencies( $service ) {
* @param array<string> $services A list of services to ensure
* are running.
*
* @return Closure A closure that should be called after all
* services are up to complete a service setup.
* @return void
*/
function ensure_services_running( array $services ) {
function ensure_services_running_no_callbacks( array $services ): void {
// Impose an order to make sure dependencies are optimized.
$order = [ 'db', 'redis', 'chrome', 'slic', 'wordpress' ];
usort( $services, static function ( $a, $b ) use ( $order ) {
Expand All @@ -165,18 +189,9 @@ function ensure_services_running( array $services ) {

return $a_index <=> $b_index;
} );
$on_up = [];
foreach ( $services as $service ) {
$service_on_up = [];
ensure_service_running( $service, [], $service_on_up );
$on_up = array_merge( $on_up, $service_on_up );
ensure_service_running_no_callbacks( $service );
}

return static function () use ( $on_up ) {
foreach ( $on_up as $then ) {
$then();
}
};
}

/**
Expand All @@ -186,7 +201,7 @@ function ensure_services_running( array $services ) {
*
* @return bool Whether a service is running or not.
*/
function service_running( $service ) {
function service_running( string $service ) {
$ps = slic_process()( [ 'ps', '--services', '--filter', '"status=running"' ] );
$ps_status = $ps( 'status' );

Expand Down Expand Up @@ -220,8 +235,8 @@ function quietly_tear_down_stack() {
*
* @return string|null The service container ID if found, `null` otherwise.
*/
function get_service_id( $service ) {
$root = root();
function get_service_id( string $service ) {
$root = root();
$command = "docker ps -f label=com.docker.compose.project.working_dir='$root' " .
"-f label=com.docker.compose.service=$service --format '{{.ID}}'";
debug( "Executing command: $command" . PHP_EOL );
Expand All @@ -238,7 +253,7 @@ function get_service_id( $service ) {
* @return string|null The service IP address if the service is up and the IP address
* could be found, `null` otherwise.
*/
function get_service_ip_address( $service_id ) {
function get_service_ip_address( string $service_id ) {
$command = "docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $service_id";
debug( "Executing command: $command" . PHP_EOL );
exec( $command, $output, $status );
Expand All @@ -263,7 +278,7 @@ function get_service_ip_address( $service_id ) {
* @return true To indicate the operation was completed correctly, the function
* will `exit` with an error, otherwise.
*/
function add_hosts_to_service( $service_id, array $hosts ) {
function add_hosts_to_service( string $service_id, array $hosts ) {
// Read the current contents of the `/etc/hosts` file.
$read_command = "docker exec -u '0:0' $service_id bash -c 'cat /etc/hosts'";
debug( "Executing command: $read_command" . PHP_EOL );
Expand Down Expand Up @@ -306,10 +321,10 @@ function add_hosts_to_service( $service_id, array $hosts ) {
* Updates the `/etc/hosts` file of a list of services to include the IP address of another set of
* services.
*
* @param array<string> $of_services The list of services whose IP address should be propagated.
* @param array<string> $to_services The list of services whose `/etc/hosts` file should be updated
* to include the IP addresses to hastname mappings of the previous
* list.
* @param array<string> $of_services The list of services whose IP address should be propagated.
* @param array<string> $to_services The list of services whose `/etc/hosts` file should be updated
* to include the IP addresses to hastname mappings of the previous
* list.
* @param array<string,string> $hostname_map A map from service names to the hostnames they should be
* mapped to.
*
Expand Down Expand Up @@ -352,34 +367,48 @@ function propagate_ip_address_of_to( array $of_services, array $to_services, arr
* Ensures a service is running by ensuring all its pre-conditions and services
* it depends on.
*
* @param string $service The name of the service to ensure running, e.g., `wordpress`.
* @param string $service The name of the service to ensure running, e.g., `wordpress`.
* @param array<string> $dependencies The list of services that should be running.
* @param array<callable> $on_up If provided, the services and dependencies "on up" callbacks
* will not be immediately executed, but instead will be added to
* this list.
*
* @return int The exit status of the command that will ensure the service is running;
* following UNIX convention, a `0` indicates a success, any other value indicates a
* failure.
*/
function ensure_service_running( $service, array $dependencies = null, array &$on_up = null ) {
function ensure_service_running( string $service, array $dependencies = [] ): int {
$status = ensure_service_running_no_callbacks( $service, $dependencies );

services_callback_stack()->call();

return $status;
}

/**
* Ensures a service is running by ensuring all its pre-conditions and services
* it depends on.
*
* @param string $service The name of the service to ensure running, e.g., `wordpress`.
* @param array<string> $dependencies The list of services that should be running.
*
* @return int The exit status of the command that will ensure the service is running;
* following UNIX convention, a `0` indicates a success, any other value indicates a
* failure.
*/
function ensure_service_running_no_callbacks( string $service, array $dependencies = [] ): int {
if ( empty( $dependencies ) && service_running( $service ) ) {
service_up_notify( $service );
return 0;
}

if ( empty( $dependencies ) ) {
$dependencies_on_up = ensure_service_dependencies( $service );
ensure_service_dependencies( $service );
} else {
$dependencies_on_up = ensure_services_running( $dependencies );
ensure_services_running_no_callbacks( $dependencies );
}

if ( service_running( $service ) ) {
service_up_notify( $service );
return 0;
}

$own_on_up = ensure_service_ready( $service );
ensure_service_ready( $service );

$up_status = slic_realtime()( [ 'up', '-d', $service ] );
service_running( $service );
Expand All @@ -388,28 +417,20 @@ function ensure_service_running( $service, array $dependencies = null, array &$o
return $up_status;
}

if ( is_array( $on_up ) ) {
array_push( $on_up, $dependencies_on_up, $own_on_up );
} else {
$dependencies_on_up();
$own_on_up();
}

service_up_notify( $service );

return 0;
}

/**
* Notifies about the up status of a service.
* Returns the singleton instance of the Services callback stack.
*
* @param string $service
* @return Callback_Stack The singleton instance of the Services callback stack.
*/
function service_up_notify( string $service ) : void {
switch ( $service ) {
case 'wordpress':
echo colorize( PHP_EOL . "Your WordPress site is reachable at: <yellow>http://localhost:" . getenv( 'WORDPRESS_HTTP_PORT' ) . "</yellow>" . PHP_EOL );
default:
return;
function services_callback_stack(): Callback_Stack {
static $callback_stack;

if ( ! $callback_stack instanceof Callback_Stack ) {
$callback_stack = new Callback_Stack();
}

return $callback_stack;
}
6 changes: 5 additions & 1 deletion src/slic.php
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,8 @@ function restart_all_services() {
foreach ( $services as $service ) {
restart_service( $service );
}

services_callback_stack()->call();
}

/**
Expand All @@ -424,8 +426,10 @@ function restart_all_services() {
function start_all_services() {
$services = get_services();
foreach ( $services as $service ) {
ensure_service_running( $service );
ensure_service_running_no_callbacks( $service );
}

services_callback_stack()->call();
}

/**
Expand Down
Loading

0 comments on commit 3682725

Please sign in to comment.