diff --git a/changelog.md b/changelog.md index 99a9e68..61b4600 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.0] - 2023-03-30 + +* Change - Leverage Docker Compose syntax to manage network aliases and service dependencies. Clean up a remove code. +* Add the `dc` command to run Docker Compose commands in the stack. +* Support the `SLIC_DOCKER_COMPOSE_BIN` environment variable to allow for the use a different Docker Compose binary, the `docker compose` one is used by default. + ## [1.2.4] - 2023-03-21 * Fix - [issue 142](https://github.com/stellarwp/slic/issues/142) ensure the hosts file in the docker container contains a tab character before trying to explode it. diff --git a/slic-stack.site.yml b/slic-stack.site.yml index 7f59602..ff7bae6 100644 --- a/slic-stack.site.yml +++ b/slic-stack.site.yml @@ -1,4 +1,4 @@ -# docker-compose configuration file used to run cross-activation tests. +# docker compose configuration file used to run cross-activation tests. version: "3" @@ -8,13 +8,13 @@ services: volumes: # Paths are relative to the directory that contains this file, NOT the current working directory. # Share the WordPress core installation files in the `_wordpress` directory. - - ${SLIC_WP_DIR}:/var/www/html:cached + - ${SLIC_WP_DIR}:/var/www/html cli: volumes: # Paths are relative to the directory that contains this file, NOT the current working directory. # Share the WordPress core installation files in the `_wordpress` directory. - - ${SLIC_WP_DIR}:/var/www/html:cached + - ${SLIC_WP_DIR}:/var/www/html codeception: environment: @@ -22,18 +22,18 @@ services: CODECEPTION_PROJECT_DIR: /var/www/html/${SLIC_CURRENT_PROJECT_RELATIVE_PATH} volumes: # Set the current site as project. - - ${SLIC_HERE_DIR}/${SLIC_CURRENT_PROJECT_RELATIVE_PATH}:/project:cached + - ${SLIC_HERE_DIR}/${SLIC_CURRENT_PROJECT_RELATIVE_PATH}:/project # Paths are relative to the directory that contains this file, NOT the current working directory. # Share the WordPress core installation files in the `_wordpress` directory. - - ${SLIC_WP_DIR}:/var/www/html:cached + - ${SLIC_WP_DIR}:/var/www/html composer: volumes: # Set the current target as project. - - ${SLIC_HERE_DIR}/${SLIC_CURRENT_PROJECT_RELATIVE_PATH}:/project:cached - - ${COMPOSER_CACHE_DIR}:${COMPOSER_CACHE_DIR}:cached + - ${SLIC_HERE_DIR}/${SLIC_CURRENT_PROJECT_RELATIVE_PATH}:/project + - ${COMPOSER_CACHE_DIR}:${COMPOSER_CACHE_DIR} npm: volumes: # Set the current plugin as project. - - ${SLIC_HERE_DIR}/${SLIC_CURRENT_PROJECT_RELATIVE_PATH}:/project:cached + - ${SLIC_HERE_DIR}/${SLIC_CURRENT_PROJECT_RELATIVE_PATH}:/project diff --git a/slic-stack.yml b/slic-stack.yml index 92eb16b..2a406c1 100644 --- a/slic-stack.yml +++ b/slic-stack.yml @@ -1,6 +1,6 @@ -# docker-compose configuration file used to run cross-activation tests. +# docker compose configuration file used to run cross-activation tests. -version: "3" +version: "3.9" networks: slic: @@ -11,7 +11,7 @@ volumes: services: db: - image: mariadb:10.7.3 + image: mariadb:10.7.8 networks: - slic ports: @@ -19,25 +19,45 @@ services: environment: MYSQL_DATABASE: test MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root} + healthcheck: # The `test` db should exist. + test: mysqlshow -u root -p${MYSQL_ROOT_PASSWORD:-root} test + start_period: 5s + interval: 1s + timeout: 3s + retries: 30 tmpfs: - /var/lib/mysql redis: - image: redis + image: redis:7.0.10 networks: - slic: + - slic ports: - "${SLIC_REDIS_LOCALHOST_PORT:-8379}:6379" + healthcheck: # It should reply PONG to PING + test: redis-cli ping | grep PONG + start_period: 2s + interval: 1s + timeout: 3s + retries: 30 tmpfs: - /data wordpress: image: ghcr.io/stellarwp/slic-wordpress-php${SLIC_PHP_VERSION}:${SLIC_VERSION} networks: - - slic + slic: + aliases: # Allow reaching the site at `http://wordpress.test` and other domains that might be used in testing. + - wordpress.test + - www.wordpress.test + - test1.wordpress.test + - test2.wordpress.test + - test3.wordpress.test depends_on: - - db - - redis + db: + condition: service_healthy + redis: + condition: service_healthy # Run the container as the host user and group. # Apache will run as the same user and permission issues with WordPress generated files should not arise. user: "${SLIC_UID:-}:${SLIC_GID:-}" @@ -69,6 +89,8 @@ services: define( 'TRIBE_NO_FREEMIUS', true ); define( 'WP_DEBUG_DISPLAY', true ); define( 'WP_DEBUG_LOG', true ); + define( 'DISABLE_WP_CRON', true ); + define( 'WP_HTTP_BLOCK_EXTERNAL', true ); # Configure this to debug the tests with XDebug. # Map the `_wordpress` directory to `/var/www/html' directory in your IDE of choice. # Map the `_plugins` directory to `/plugins` directory in your IDE of choice. @@ -76,29 +98,49 @@ services: # The `remote_host` is set to `host.docker.internal` that will resolve to the host machine IP address, from # within the container, on macOS and Windows. # On Linux set the host machine IP address before calling the stack: - # XDH=$(ip route | grep docker0 | awk '{print $9}') docker-compose ... + # XDH=$(ip route | grep docker0 | awk '{print $9}') docker compose ... XDEBUG_CONFIG: "idekey=${XDK:-slic} remote_enable=${XDE:-1} remote_host=${XDH:-host.docker.internal} remote_port=${XDP:-9001} client_host=${XDH:-host.docker.internal} client_port=${XDP:-9001}" # Whether to disable the XDebug extension in the Codeception container completely or not. XDEBUG_DISABLE: "${XDEBUG_DISABLE:-0}" volumes: # Paths are relative to the directory that contains this file, NOT the current working directory. - - ${SLIC_WP_DIR}:/var/www/html:cached - - ${SLIC_PLUGINS_DIR}:/var/www/html/wp-content/plugins:cached - - ${SLIC_THEMES_DIR}:/var/www/html/wp-content/themes:cached - - ${COMPOSER_CACHE_DIR:-./.cache}:/composer-cache:cached + - ${SLIC_WP_DIR}:/var/www/html + - ${SLIC_PLUGINS_DIR}:/var/www/html/wp-content/plugins + - ${SLIC_THEMES_DIR}:/var/www/html/wp-content/themes + - ${COMPOSER_CACHE_DIR:-./.cache}:/composer-cache + healthcheck: # Apache service should be running correctly. + test: service apache2 status + start_period: 5s + interval: 1s + timeout: 3s + retries: 30 chrome: - image: ${SLIC_CHROME_CONTAINER:-selenium/standalone-chrome:3.141.59-oxygen} + image: ${SLIC_CHROME_CONTAINER:-selenium/standalone-chrome:3.141.59} networks: - slic - extra_hosts: - - "wordpress.test:172.${SLIC_TEST_SUBNET:-28}.1.1" + depends_on: + wordpress: + condition: service_healthy + healthcheck: # It should reply with a 200 status code to a request to the status endpoint. + test: curl -f http://localhost:4444/wd/hub/status + start_period: 5s + interval: 1s + timeout: 3s + retries: 30 slic: image: ghcr.io/stellarwp/slic-php${SLIC_PHP_VERSION}:${SLIC_VERSION} networks: - slic user: "${SLIC_UID:-}:${SLIC_GID:-}" + depends_on: + db: + condition: service_healthy + chrome: + condition: service_healthy + wordpress: + condition: service_healthy environment: COMPOSER_AUTH: "${COMPOSER_AUTH:-}" COMPOSER_CACHE_DIR: "/composer-cache" @@ -115,7 +157,7 @@ services: # The `remote_host` is set to `host.docker.internal` that will resolve to the host machine IP address, from # within the container, on macOS and Windows. # On Linux set the host machine IP address before calling the stack: - # XDH=$(ip route | grep docker0 | awk '{print $9}') docker-compose ... + # XDH=$(ip route | grep docker0 | awk '{print $9}') docker compose ... XDEBUG_CONFIG: "idekey=${XDK:-slic} remote_enable=${XDE:-1} remote_host=${XDH:-host.docker.internal} remote_port=${XDP:-9001} client_host=${XDH:-host.docker.internal} client_port=${XDP:-9001}" # Move to the target directory before running the command from the plugins directory. CODECEPTION_PROJECT_DIR: /var/www/html/wp-content/plugins/${SLIC_CURRENT_PROJECT:-test}/${SLIC_CURRENT_PROJECT_SUBDIR:-} @@ -137,10 +179,10 @@ services: volumes: # Paths are relative to the directory that contains this file, NOT the current working directory. # Share the WordPress core installation files in the `_wordpress` directory. - - ${SLIC_WP_DIR}:/var/www/html:cached + - ${SLIC_WP_DIR}:/var/www/html # Share the plugins in the `/var/www/hmtl/wp-content/plugins` directory. - - ${SLIC_PLUGINS_DIR}:/var/www/html/wp-content/plugins:cached - - ${SLIC_THEMES_DIR}:/var/www/html/wp-content/themes:cached + - ${SLIC_PLUGINS_DIR}:/var/www/html/wp-content/plugins + - ${SLIC_THEMES_DIR}:/var/www/html/wp-content/themes # In some plugins we use function-mocker and set it up to cache in `/tmp/function-mocker`. # To avoid a long re-caching on each run, let's cache in a docker volume, caching on the host # filesystem would be a worse cure than the disease. @@ -148,32 +190,4 @@ services: - function-mocker-cache:/cache - ${COMPOSER_CACHE_DIR:-./.cache}:/composer-cache # Scripts volume - - ${SLIC_SCRIPTS}:/slic-scripts:cached - -# adminer: -# image: adminer -# networks: -# - slic -# environment: -# ADMINER_DEFAULT_SERVER: db -# ports: -# - "9080:8080" -# -# -# redis-cli: -# image: redis -# networks: -# slic: -# depends_on: -# - redis -# entrypoint: ["redis-cli","-h redis","-p 6379"] -# command: ["--version"] -# -# mailcatcher: -# image: dockage/mailcatcher -# networks: -# slic: -# ports: -# # Expose MailCatcher ports on localhost. -# - "1025:1025" -# - "1080:1080" + - ${SLIC_SCRIPTS}:/slic-scripts diff --git a/slic.md b/slic.md index 8050580..4456e89 100644 --- a/slic.md +++ b/slic.md @@ -7,7 +7,7 @@ The entrypoint to anything you will need to do will be the `slic` binary, locate ## Requirements -The stack runs based on [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/): you will need both installed and available to be able to use the local testing environment. +The stack runs based on [Docker](https://www.docker.com/) and [docker compose](https://docs.docker.com/compose/): you will need both installed and available to be able to use the local testing environment. If your Docker installation requires root access, please follow [this guide](https://docs.docker.com/install/linux/linux-postinstall/) to make sure root access is not required to run `docker` commands. You should be able to run the following command without issues and without requiring root access: @@ -16,7 +16,7 @@ You should be able to run the following command without issues and without requi docker run hello-world ``` -If this is not the case, please take the time to read Docker and docker-compose documentation and fix the issues you encounter. +If this is not the case, please take the time to read Docker and docker compose documentation and fix the issues you encounter. ## Where to get help @@ -178,7 +178,7 @@ slic cli db export /plugins/the-events/calendar/tests/_data/dump.sql ## How the stack works, an overview The stack services are defined by the `slic-stack.yml` file. -This is a YAML format docker-compose configuration file the `slic` binary will use to run the `docker-compose` command. +This is a YAML format docker compose configuration file the `slic` binary will use to run the `docker compose` command. The main services defined there are: * `wordpress` - this uses the `wordpress:latest` image, the official Docker image for WordPress. When running the container will fill the `_wordpress` directory with the contents of the WordPress installation that is currently serving the container. Furthermore the WordPress container is configured to look for plugins in the `/plugins` directory, that directory is a shared volume that you can find in the `dev/test/plugins` directory. diff --git a/slic.php b/slic.php index acec560..295ff43 100644 --- a/slic.php +++ b/slic.php @@ -1,5 +1,7 @@ slic at https://github.com/stellarwp/slic @@ -92,6 +96,7 @@ config Prints the stack configuration as interpolated from the environment. debug Activates or deactivates {$cli_name} debug output or returns the current debug status. down Tears down the stack; alias of `stop`. + dc Runs a docker compose command in the stack. exec Runs a bash command in the stack. group Create or remove group of targets for the current plugins directory. host-ip Returns the IP Address of the host machine from the container perspective. @@ -114,12 +119,13 @@ $is_help = args( [ 'help' ], $args( '...' ), 0 )( 'help', false ) === 'help'; -$run_settings_file = root( '/.env.slic.run' ); $original_subcommand = $args( 'subcommand' ); $subcommand = $args( 'subcommand', 'help' ); -$cli_name = basename( $argv[0] ); +// Both these variables will be used by commands. +$run_settings_file = root( '/.env.slic.run' ); +$cli_name = basename( $argv[0] ); if ( 'help' !== $subcommand ) { maybe_prompt_for_repo_update(); @@ -132,82 +138,45 @@ } } -if ( ! in_array( $subcommand, [ 'help', 'update'] ) ) { +if ( ! in_array( $subcommand, [ 'help', 'update' ] ) ) { + maybe_prompt_for_stack_update(); +} + +if ( empty( $subcommand ) || $subcommand === 'help' ) { + echo $help_message . PHP_EOL; + if ( $original_subcommand ) { + echo PHP_EOL . $help_advanced_message; + } else { + echo colorize( PHP_EOL . "There are a lot more commands. Use slic help to see them all!" . PHP_EOL ); + } + maybe_prompt_for_repo_update(); maybe_prompt_for_stack_update(); + echo PHP_EOL; + exit( 0 ); } -// A map from the user-facing alias to the command that will be actually called. +/* + * Resolve command aliases. + * A map from the user-facing alias to the command that will be actually called. + */ $aliases = [ 'wp' => 'cli', ]; +if ( isset( $aliases[ $subcommand ] ) ) { + $subcommand = $aliases[ $subcommand ]; +} -switch ( $subcommand ) { - default: - case 'help': - echo $help_message . PHP_EOL; - if ( $original_subcommand ) { - echo PHP_EOL . $help_advanced_message; - } else { - echo colorize( PHP_EOL . "There are a lot more commands. Use slic help to see them all!" . PHP_EOL ); - } - maybe_prompt_for_repo_update(); - maybe_prompt_for_stack_update(); - break; - case 'cc': - case 'restart': - case 'run': - case 'serve': - case 'shell': - case 'site-cli': - case 'ssh': - case 'up': - // ensure_wordpress_files(); - // ensure_wordpress_configured(); - // Do not break, let the command be loaded then. - case 'airplane-mode': - case 'cache': - case 'cli': - case 'wp': // Alias of the `cli` command. - // ensure_wordpress_installed(); - // Do not break, let the command be loaded then. - case 'build-prompt': - case 'build-stack': - case 'build-subdir': - case 'composer': - case 'composer-cache': - case 'config': - case 'debug': - case 'down': - case 'exec': - case 'here': - case 'host-ip': - case 'info': - case 'init': - case 'interactive': - case 'logs': - case 'mysql': - case 'npm': - case 'npm_lts': - case 'phpcbf': - case 'phpcs': - case 'ps': - case 'php-version': - case 'reset': - case 'start': - case 'stop': - case 'target': - case 'update': - case 'upgrade': - case 'use': - case 'using': - case 'xdebug': - if ( isset( $aliases[ $subcommand ] ) ) { - $subcommand = $aliases[ $subcommand ]; - } - include_once __DIR__ . '/src/commands/' . $subcommand . '.php'; - break; +$subcommand_file = __DIR__ . '/src/commands/' . $subcommand . '.php'; +if ( file_exists( $subcommand_file ) ) { + include_once $subcommand_file; +} else { + echo colorize( "Unknown command: {$subcommand}" . PHP_EOL . PHP_EOL ); + echo $help_message . PHP_EOL; + maybe_prompt_for_repo_update(); + maybe_prompt_for_stack_update(); + echo PHP_EOL; + exit( 1 ); } // Add a break line at the end of each command to avoid dirty terminal issues. echo PHP_EOL; - diff --git a/src/cache.php b/src/cache.php new file mode 100644 index 0000000..7773403 --- /dev/null +++ b/src/cache.php @@ -0,0 +1,50 @@ +get( $key, $found ); + + if ( $found ) { + return $value; + } + + return $default; +} + +/** + * Sets a value in the cache. + * + * @param string $key + * @param $value + * + * @return void + */ +function slic_cache_set( string $key, $value ) { + global $slic_cache; + + $slic_cache->set( $key, $value ); +} + +/** + * Flushes the cache. + * + * @return void + */ +function slic_cache_flush(): void { + global $slic_cache; + $slic_cache->flush(); +} diff --git a/src/classes/Cache.php b/src/classes/Cache.php new file mode 100644 index 0000000..3329b0f --- /dev/null +++ b/src/classes/Cache.php @@ -0,0 +1,63 @@ + + */ + private array $cache = []; + + /** + * Gets a value from the cache. + * + * @param string $key + * @param bool $found + * + * @return mixed|null + */ + public function get( string $key, ?bool &$found ) { + if ( isset( $this->cache[ $key ] ) ) { + $found = true; + + return $this->cache[ $key ]; + } + + $found = false; + + return null; + } + + /** + * Sets a value in the cache. + * + * @param string $key + * @param $value + * + * @return void + */ + public function set( string $key, $value ): void { + $this->cache[ $key ] = $value; + } + + /** + * Flushes the cache. + * + * @return void + */ + public function flush() { + $this->cache = []; + } +} diff --git a/src/classes/Callback_Stack.php b/src/classes/Callback_Stack.php deleted file mode 100644 index 9deac2e..0000000 --- a/src/classes/Callback_Stack.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ - 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 ] ); - } -} diff --git a/src/codeception.php b/src/codeception.php deleted file mode 100644 index e43a16d..0000000 --- a/src/codeception.php +++ /dev/null @@ -1,39 +0,0 @@ - A list of service dependencies required to run - * a Codeception command. - */ -function codeception_dependencies( array $codeception_args = [] ) { - if ( empty( $codeception_args ) ) { - return []; - } - - $dependencies = []; - - if ( count( array_intersect( [ - 'run', - ], $codeception_args ) ) ) { - $dependencies[] = 'wordpress'; - $dependencies[] = 'db'; - } - - return array_values( array_unique( array_filter( $dependencies ) ) ); -} diff --git a/src/commands.php b/src/commands.php index cb96542..d4160d5 100644 --- a/src/commands.php +++ b/src/commands.php @@ -20,5 +20,7 @@ function command_stop() : int { echo colorize( PHP_EOL . "โŒ Some containers failed to stop. Use slic ps to see what is still running." . PHP_EOL ); } + slic_cache_flush(); + return $status; -} \ No newline at end of file +} diff --git a/src/commands/airplane-mode.php b/src/commands/airplane-mode.php index 33e8a2b..8960e2b 100644 --- a/src/commands/airplane-mode.php +++ b/src/commands/airplane-mode.php @@ -40,7 +40,6 @@ $activate = $toggle === 'on'; setup_id(); -ensure_service_running( 'slic' ); ensure_wordpress_ready(); $ensure_airplane_mode_plugin = static function () { diff --git a/src/commands/cache.php b/src/commands/cache.php index 8432ecd..ad46d20 100644 --- a/src/commands/cache.php +++ b/src/commands/cache.php @@ -42,7 +42,6 @@ $toggle = $cache_args( 'toggle', 'status' ); setup_id(); -ensure_services_running( [ 'wordpress', 'slic' ] ); ensure_wordpress_ready(); // Ensure the plugin is installed. diff --git a/src/commands/cc.php b/src/commands/cc.php index 58eb88b..193139c 100644 --- a/src/commands/cc.php +++ b/src/commands/cc.php @@ -38,7 +38,7 @@ setup_id(); $codeception_args = $args( '...' ); -ensure_service_running( 'slic', codeception_dependencies( $codeception_args ) ); +ensure_service_running( 'slic' ); $codeception_config = ''; if ( file_exists( get_project_local_path() . '/codeception.slic.yml' ) ) { diff --git a/src/commands/cli.php b/src/commands/cli.php index 305c7ac..e65b71a 100644 --- a/src/commands/cli.php +++ b/src/commands/cli.php @@ -34,7 +34,6 @@ } setup_id(); -ensure_services_running( [ 'slic', 'db' ] ); ensure_wordpress_ready(); // Runs a wp-cli command in the stack, using the `cli` service. diff --git a/src/commands/dc.php b/src/commands/dc.php new file mode 100644 index 0000000..4b11bcb --- /dev/null +++ b/src/commands/dc.php @@ -0,0 +1,39 @@ +{$cli_name} dc [...] + + EXAMPLES: + + {$cli_name} dc ps + List the containers in the stack. + + {$cli_name} dc up redis + Starts the Redis container. + + HELP; + + echo colorize( $help ); + + return; +} + +$dc_args = $args( '...' ); +exit( slic_realtime()( $dc_args ) ); diff --git a/src/commands/run.php b/src/commands/run.php index a39e642..76ae191 100644 --- a/src/commands/run.php +++ b/src/commands/run.php @@ -47,7 +47,7 @@ echo light_cyan( "Using {$using}" . PHP_EOL ); $codeception_args = array_merge( [ 'run' ], $args( '...' ) ); -ensure_service_running( 'slic', codeception_dependencies( $codeception_args ) ); +ensure_service_running( 'slic' ); setup_id(); diff --git a/src/commands/site-cli.php b/src/commands/site-cli.php index 5295afd..14f78aa 100644 --- a/src/commands/site-cli.php +++ b/src/commands/site-cli.php @@ -30,8 +30,6 @@ } setup_id(); - -ensure_services_running( [ 'wordpress', 'slic' ] ); ensure_wordpress_ready(); $command = $args( '...' ); @@ -75,7 +73,7 @@ array_unshift( $command, 'wp', '--allow-root' ); /* - * Due to how docker-compose works, the default `CMD` for the `wordpress:cli` image will be overridden as a + * Due to how docker compose works, the default `CMD` for the `wordpress:cli` image will be overridden as a * consequence of overriding the `entrypoint` configuration parameter of the service. * We cannot, thus, pass the user command directly, we use an env var, `SLIC_SITE_CLI_COMMAND`, to embed the * command we're running into the entrypoint call arguments. diff --git a/src/database.php b/src/database.php index 508b932..b722671 100644 --- a/src/database.php +++ b/src/database.php @@ -21,7 +21,7 @@ function ensure_db_service_running() { } // Start the service is not already started. - $status = slic_passive()( [ 'up', '--detach', 'db' ] ); + $status = slic_passive()( [ 'up', '--wait', 'db' ] ); if ( $status !== 0 ) { echo magenta( 'Failed to start or restart the database service; check the output for errors.' ); @@ -135,33 +135,3 @@ function get_db_user() { function get_db_password() { return 'password'; } - -/** - * Starts the stack database service, if required. - * - * @return bool Always `true` to indicate the database service was correctly started. - */ -function ensure_db_service_ready() { - setup_slic_env( root() ); - ensure_db_service_running(); - - $attempts = 0; - while ( $attempts ++ < 30 ) { - debug( "Waiting for database to be ready ..." . PHP_EOL ); - - try { - $mysqli = get_localhost_db_handle(); - if ( $mysqli instanceof mysqli ) { - debug( "Database ready." . PHP_EOL ); - - return true; - } - } catch ( Exception $e ) { - // No-op, just wait. - } - sleep( 1 ); - } - - echo magenta( "Database never became available." . PHP_EOL ); - exit( 1 ); -} diff --git a/src/docker.php b/src/docker.php index 3dc25b6..579c590 100644 --- a/src/docker.php +++ b/src/docker.php @@ -1,6 +1,6 @@ $options A list of options to initialize the wrapper. * - * @return \Closure A closure to actually call docker-compose with more arguments. + * @return \Closure A closure to actually call docker compose with more arguments. */ function docker_compose( array $options = [] ) { setup_id(); @@ -49,8 +49,10 @@ function docker_compose( array $options = [] ) { $host_ip = host_ip( 'Linux' ); } - return static function ( array $command = [] ) use ( $options, $host_ip, $is_ci ) { - $command = 'docker-compose ' . implode( ' ', $options ) . ' ' . implode( ' ', $command ); + $dc_bin = docker_compose_bin(); + + return static function ( array $command = [] ) use ( $dc_bin, $options, $host_ip, $is_ci ) { + $command = $dc_bin . ' ' . implode( ' ', $options ) . ' ' . implode( ' ', $command ); if ( ! empty( $host_ip ) ) { // Set the host IP address on Linux machines. @@ -122,7 +124,7 @@ function wordpress_url() { * @param string $postfix A postfix to use for the stack file, it will be inserted between the file base name and * the `.yml` file extension. * - * @return string The path to the docker-compose stack file to run, depending on the run context. + * @return string The path to the docker compose stack file to run, depending on the run context. */ function stack( $postfix = '' ) { $root_dir = dirname( __DIR__ ); @@ -143,13 +145,13 @@ function stack( $postfix = '' ) { } /** - * Builds a collection of docker-compose yaml files for spinning up a stack. + * Builds a collection of docker compose yaml files for spinning up a stack. * * Typically, this would be slic-stack.yml for plugin-only setups, but if running in site mode, it adds slic-stack.site.yml. * * @param bool $filenames_only Return only the files part of the stack, without including option flags. * - * @return string[] Array of docker-compose arguments indicating the files that should be used to initialize the stack. + * @return string[] Array of docker compose arguments indicating the files that should be used to initialize the stack. */ function slic_stack_array( $filenames_only = false ) { $file_prefix = $filenames_only ? '' : '-f'; @@ -166,7 +168,7 @@ function slic_stack_array( $filenames_only = false ) { } /** - * Executes a docker-compose command in real time, printing the output as produced by the command. + * Executes a docker compose command in real time, printing the output as produced by the command. * * @param array $options A list of options to initialize the wrapper. * @param bool $is_realtime Whether the command should be run in real time (true) or passively (false). @@ -204,7 +206,7 @@ function docker_compose_process( array $options = [], $is_realtime = true ) { $command = array_unique( array_merge( [ $subcommand ], $var, $command ) ); } - $command = 'docker-compose ' . implode( ' ', $options ) . ' ' . implode( ' ', $command ); + $command = docker_compose_bin() . ' ' . implode( ' ', $options ) . ' ' . implode( ' ', $command ); if ( ! empty( $host_ip ) ) { // Set the host IP address on Linux machines. @@ -222,7 +224,7 @@ function docker_compose_process( array $options = [], $is_realtime = true ) { } /** - * Executes a docker-compose command in passive mode, printing the output as produced by the command. + * Executes a docker compose command in passive mode, printing the output as produced by the command. * * This approach is used for commands that can be run in a parallel or forked process without interactivity. * @@ -235,7 +237,7 @@ function docker_compose_passive( array $options = [] ) { } /** - * Executes a docker-compose command in real time, printing the output as produced by the command. + * Executes a docker compose command in real time, printing the output as produced by the command. * * @param array $options A list of options to initialize the wrapper. * @@ -244,3 +246,18 @@ function docker_compose_passive( array $options = [] ) { function docker_compose_realtime( array $options = [] ) { return docker_compose_process( $options, true ); } + +/** + * Returns the path to the docker compose binary. + * + * Newer versions of Docker include the `docker compose` command instead of a separate `docker-compose`. + * Most CIs have the newer version of Docker that includes the `docker compose` command, but will also include the + * outdated `docker-compose` command for back-compatibility. + * Unless the `SLIC_DOCKER_COMPOSE_BIN` environment variable is set, we'll use the newer `docker compose` command. + * + * @return string + */ +function docker_compose_bin(): string { + return (string) getenv( 'SLIC_DOCKER_COMPOSE_BIN' ) ?: 'docker compose'; +} + diff --git a/src/services.php b/src/services.php index 8906279..d91dc4e 100644 --- a/src/services.php +++ b/src/services.php @@ -5,14 +5,13 @@ namespace StellarWP\Slic; -use Closure; use Exception; /** - * Returns the `docker-compose` schema parsed from the `slic` files loaded in the current + * Returns the `docker compose` schema parsed from the `slic` files loaded in the current * request. * - * @return array The loaded `docker-compose` format files, merged in array format. + * @return array The loaded `docker compose` format files, merged in array format. */ function stack_schema() { static $schema; @@ -45,10 +44,10 @@ function stack_schema() { } /** - * Returns the `service` section of the `docker-compose` format stack files loaded in the request + * Returns the `service` section of the `docker compose` format stack files loaded in the request * for `slic`. * - * @return array The `services` section of the loaded `docker-compose` format files. + * @return array The `services` section of the loaded `docker compose` format files. */ function services_schema() { $stack_schema = stack_schema(); @@ -69,93 +68,6 @@ function get_services() { return $services; } -/** - * Returns a list of dependencies for service. - * - * Dependencies are read from the stack docker-compose format file in the `links` and - * `depends_on` sections. - * - * @param string $service The name of the service to get the dependencies for. - * - * @return array A list of the service dependencies; empty if the service has no - * dependencies. - */ -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'] : []; - - return array_merge( $service_links, $service_dependencies ); -} - -/** - * Checks whether a service requires at least one of the listed services in the context of the - * container stack either by means of `links` or `depends_on` specification. - * - * @param string $service The service to check dependencies for. - * @param string ...$dependencies A list of dependencies to check. - * - * @return false Whether the specified service requires at least one of the dependencies or not. - */ -function service_requires( string $service, ...$dependencies ) { - if ( empty( $dependencies ) ) { - return false; - } - - return (bool) count( array_intersect( service_dependencies( $service ), $dependencies ) ); -} - -/** - * Ensures a service is ready to run. - * - * @param string $service The service to check. - * - * @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( 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(); - 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: - break; - } -} - -/** - * Ensures a service dependencies are all correctly set up, will - * exit if not possible. - * - * @param string $service The service to ensure the dependencies for. - * - * @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. @@ -167,30 +79,8 @@ function ensure_service_dependencies( string $service ): void { * callback stack. */ function ensure_services_running( array $services ): void { - ensure_services_running_no_callbacks( $services ); - services_callback_stack()->call(); -} - -/** - * Ensures a list of services is running and returns - * a Closure that should be called to finish setting them up. - * - * @param array $services A list of services to ensure - * are running. - * - * @return void - */ -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 ) { - $a_index = array_search( $a, $order, true ); - $b_index = array_search( $b, $order, true ); - - return $a_index <=> $b_index; - } ); foreach ( $services as $service ) { - ensure_service_running_no_callbacks( $service ); + ensure_service_running( $service ); } } @@ -202,15 +92,18 @@ function ensure_services_running_no_callbacks( array $services ): void { * @return bool Whether a service is running or not. */ function service_running( string $service ) { - $ps = slic_process()( [ 'ps', '--services', '--filter', '"status=running"' ] ); - $ps_status = $ps( 'status' ); + $running_services = slic_cache_get( 'running_services' ); - if ( $ps_status !== 0 ) { - return false; + if ( $running_services === null ) { + $ps = slic_process()( [ 'ps', '--services', '--filter', '"status=running"' ] ); + $ps_status = $ps( 'status' ); + if ( $ps_status !== 0 ) { + return false; + } + $running_services = explode( "\n", $ps( 'string_output' ) ); + slic_cache_set( 'running_services', $running_services ); } - $running_services = explode( "\n", $ps( 'string_output' ) ); - return in_array( $service, $running_services, true ); } @@ -245,196 +138,20 @@ function get_service_id( string $service ) { return $status === 0 ? reset( $output ) : null; } -/** - * Returns the IP address of a stack service, if it's up. - * - * @param string $service_id The service container ID, e.g. `15148db6f216`. - * - * @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( string $service_id ) { - $command = "docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $service_id"; - debug( "Executing command: $command" . PHP_EOL ); - exec( $command, $output, $status ); - - return $status === 0 ? reset( $output ) : null; -} - -/** - * Updates a running container /etc/hosts file to add further mappings. - * - * Updating a running container `/etc/hosts` file can only be done as the `root` user (UID and GID 0). - * The `/etc/hosts` file is `rw` (read and write) for the `root` user, and `r` only for all other users. - * Since the `x` (execute) mode is missing for any user, we cannot use `sed` to update in-place as - * that requires "swapping" the updated file and that requires execute privileges. - * The following code reads the current file contents as `root`, updates them, and replaces the file - * completely using as `root`. - * - * @param string $service_id The service docker container ID. - * @param array $hosts A map from IP addresses to the hostnames that will be added - * to the container `/etc/hosts` file. - * - * @return true To indicate the operation was completed correctly, the function - * will `exit` with an error, otherwise. - */ -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 ); - exec( $read_command, $output, $status ); - - if ( $status !== 0 ) { - echo magenta( "Could not get service $service_id /etc/hosts file contents." ); - exit( 1 ); - } - - // If a line is already present in the file, do not re-add it. - $new_lines = array_filter( $output, static function ( $line ) use ( $hosts ) { - if ( strpos( $line, "\t" ) === false ) { - return true; - } - - list( $ip, $hostname ) = explode( "\t", $line, 2 ); - - return ! in_array( $hostname, $hosts, true ); - } ); - - // The format conventionally used in the `/etc/hosts` file is `\t`. - $hosts_string = ''; - foreach ( $hosts as $ip_address => $hostname ) { - $hosts_string .= "$ip_address\t$hostname\n"; - } - - $new_lines = implode( "\n", $new_lines ) . "\n" . $hosts_string; - - // Write the new lines replacing the `/etc/hosts` file contents. - $write_command = "docker exec -u '0:0' $service_id bash -c 'echo -e \"$new_lines\\\n\" > /etc/hosts'"; - debug( "Executing command: $write_command" . PHP_EOL ); - exec( $write_command, $output, $status ); - - if ( $status !== 0 ) { - echo magenta( "Could not update service $service_id /etc/hosts file contents." ); - exit( 1 ); - } - - return true; -} - -/** - * Updates the `/etc/hosts` file of a list of services to include the IP address of another set of - * services. - * - * @param array $of_services The list of services whose IP address should be propagated. - * @param array $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 $hostname_map A map from service names to the hostnames they should be - * mapped to. - * - * @return void The method does not return any value and will have the side effect of updating the - * `/etc/hosts` file of services. - */ -function propagate_ip_address_of_to( array $of_services, array $to_services, array $hostname_map = [] ) { - if ( empty( $of_services ) || empty( $to_services ) ) { - return; - } - - // There might be intersection between source and destination services. - $all_services = array_unique( array_merge( $of_services, $to_services ) ); - $services_ids = array_combine( - $all_services, - array_map( 'StellarWP\Slic\get_service_id', $all_services ) - ); - - $of_services_ids = array_intersect_key( $services_ids, array_combine( $of_services, array_fill( 0, count( $of_services ), true ) ) ); - $to_services_ids = array_intersect_key( $services_ids, array_combine( $to_services, array_fill( 0, count( $to_services ), true ) ) ); - - $ip_addresses = array_combine( - $of_services, - array_map( 'StellarWP\Slic\get_service_ip_address', $of_services_ids ) - ); - - $hosts = array_merge( ...array_map( static function ( $service ) use ( $ip_addresses, $hostname_map ) { - $value = $hostname_map[ $service ] ?? $service; - $key = $ip_addresses[ $service ]; - - return [ $key => $value ]; - }, $of_services ) ); - - array_walk( $to_services_ids, static function ( $service_id ) use ( $hosts ) { - $service_id && add_hosts_to_service( $service_id, $hosts ); - }, $to_services_ids ); -} - -/** - * 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 $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( 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 $dependencies The list of services that should be running. + * @param string $service The name of the service to ensure running, e.g., `wordpress`. * * @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 ) ) { - return 0; - } - - if ( empty( $dependencies ) ) { - ensure_service_dependencies( $service ); - } else { - ensure_services_running_no_callbacks( $dependencies ); - } - +function ensure_service_running( string $service ): int { if ( service_running( $service ) ) { return 0; } - - ensure_service_ready( $service ); - - $up_status = slic_realtime()( [ 'up', '-d', $service ] ); - service_running( $service ); - - if ( $up_status !== 0 ) { - return $up_status; - } - - return 0; -} - -/** - * Returns the singleton instance of the Services callback stack. - * - * @return Callback_Stack The singleton instance of the Services callback stack. - */ -function services_callback_stack(): Callback_Stack { - static $callback_stack; - - if ( ! $callback_stack instanceof Callback_Stack ) { - $callback_stack = new Callback_Stack(); - } - - return $callback_stack; + // Wait for the service to be up|healthy, detached mode is implied. + return slic_realtime()( [ 'up', '--wait', $service ] ); } diff --git a/src/slic.php b/src/slic.php index f6845a0..81ee284 100644 --- a/src/slic.php +++ b/src/slic.php @@ -365,7 +365,7 @@ function php_services() { /** * Restart the stack PHP services. * - * @param bool $hard Whether to restart the PHP services using the `docker-compose restart` command or by using a + * @param bool $hard Whether to restart the PHP services using the `docker compose restart` command or by using a * tear-down and up again cycle. */ function restart_php_services( $hard = false ) { @@ -379,7 +379,7 @@ function restart_php_services( $hard = false ) { * * @param string $service The name of the service to restart, e.g. `wordpress`. * @param string|null $pretty_name The pretty name to use for the service, or `null` to use the service name. - * @param bool $hard Whether to restart the service using the `docker-compose restart` command or to use full tear-down + * @param bool $hard Whether to restart the service using the `docker compose restart` command or to use full tear-down * and up again cycle. */ function restart_service( $service, $pretty_name = null, $hard = false ) { @@ -400,7 +400,7 @@ function restart_service( $service, $pretty_name = null, $hard = false ) { } else { echo colorize( PHP_EOL . "{$pretty_name} service was not running. Starting it." . PHP_EOL ); $exit_status = ensure_service_running( $service ); - if ( $exit_status !== 0 ) { + if ( $exit_status === 0 ) { echo colorize( "โœ… {$pretty_name} service started." . PHP_EOL ); } else { echo colorize( "โŒ {$pretty_name} service could not be started." . PHP_EOL ); @@ -422,10 +422,8 @@ function restart_all_services() { function start_all_services() { $services = get_services(); foreach ( $services as $service ) { - ensure_service_running_no_callbacks( $service ); + ensure_service_running( $service ); } - - services_callback_stack()->call(); } /** @@ -1219,7 +1217,6 @@ function execute_command_pool( $pool ) { */ function cli_command( array $command = [], $requirements = false ) { if ( $requirements ) { - ensure_service_running( 'slic' ); ensure_wordpress_ready(); } @@ -1585,7 +1582,7 @@ function setup_architecture_env() { putenv( 'SLIC_CHROME_CONTAINER=seleniarm/standalone-chromium:4.1.2-20220227' ); } else { putenv( 'SLIC_ARCHITECTURE=x86' ); - putenv( 'SLIC_CHROME_CONTAINER=selenium/standalone-chrome:3.141.59-oxygen' ); + putenv( 'SLIC_CHROME_CONTAINER=selenium/standalone-chrome:3.141.59' ); } } diff --git a/src/utils.php b/src/utils.php index 5af7db0..ce7d53d 100644 --- a/src/utils.php +++ b/src/utils.php @@ -200,7 +200,7 @@ function gid() { * * On OSes that will handle user ID and group ID mapping at the Docker daemon level, macOS and Windows, the * `SLIC_UID` and `SLIC_GID` env variables will be set to empty strings. - * This, in turn, will fill the `user` parameter of the stack services to `user: ":"` that will prompt docker-compose + * This, in turn, will fill the `user` parameter of the stack services to `user: ":"` that will prompt docker compose * to not set the user at all, the wanted behavior on such OSes. * * @param bool $reset Whether to re-fetch and reset the user id and group or not. diff --git a/src/wordpress.php b/src/wordpress.php index e0b2a42..983535a 100644 --- a/src/wordpress.php +++ b/src/wordpress.php @@ -105,196 +105,6 @@ function get_allowed_use_subdirectories(): array { return [ 'common' ]; } -/** - * Ensures WordPress file are correctly unzipped and placed. - * - * If a different version of WordPress is already installed, then - * it will be removed. - * - * @param string|null $version The WordPress version to set up - * the files for. - * - * @return bool Always `true` to indicate files are in place. - */ -function ensure_wordpress_files( $version = null ): bool { - // By default, download the latest WordPress version. - $source_url = 'https://wordpress.org/latest.zip'; - - if ( $version !== null ) { - // The provided WordPress version will override any env defined version. - $source_url = "https://wordpress.org/wordpress-$version.zip"; - } else { - // If set, then use the WordPress version defined by the env. - $env_wp_version = getenv( 'SLIC_WP_VERSION' ); - $version = $env_wp_version; - if ( ! empty( $env_wp_version ) ) { - $source_url = "https://wordpress.org/wordpress-$env_wp_version.zip"; - } - } - - $version = $version ?: 'latest'; - - debug( "Checking if WordPress version $version is installed and configured ... " . PHP_EOL ); - - if ( $version === 'latest' ) { - debug( "Resolving latest version to a semantic version string ..." . PHP_EOL ); - $version = get_wordpress_latest_version(); - } - - $wp_root_dir = getenv( 'SLIC_WP_DIR' ); - $version_file = $wp_root_dir . '/wp-includes/version.php'; - - // Check only if the specified version is not latest. - if ( is_file( $version_file ) ) { - include_once $version_file; - - // `$wp_version` is globally defined in the `wp-includes/version.php` file. - if ( isset( $wp_version ) && version_compare( $wp_version, $version ) === 0 ) { - - debug( "WordPress current version ($wp_version) matches the requested one ($version)" . PHP_EOL ); - - return true; - } - - if ( isset( $wp_version ) ) { - echo "WordPress current version ($wp_version) does not match the requested one ($version), removing WordPress directory ... "; - } - - // Remove the previous version of WordPress. - quietly_tear_down_stack(); - - if ( ! rrmdir( $wp_root_dir ) ) { - magenta( "Failed to remove the previous WordPress directory, try manually." . PHP_EOL ); - exit( 1 ); - } - - echo light_cyan( "done" . PHP_EOL ); - } else { - debug( "Previous WordPress directory not found." . PHP_EOL ); - } - - // Tear down the stack to avoid containers from locking the bound directories. - quietly_tear_down_stack(); - - // Ensure the destination directory exists. - if ( ! is_dir( $wp_root_dir ) && ! mkdir( $wp_root_dir, 0755, false ) && ! is_dir( $wp_root_dir ) ) { - echo magenta( "Failed to create WordPress root directory {$wp_root_dir}" ); - exit( 1 ); - } - - // Download WordPress. - $zip_file = cache( "/wordpress/wordpress-$version.zip" ); - if ( ! is_file( $zip_file ) ) { - debug( "WordPress zip file $zip_file not found." . PHP_EOL ); - - $zip_file = download_file( $source_url, $zip_file ); - - if ( $zip_file === false ) { - echo magenta( "Failed to download WordPress file from $source_url." ); - exit( 1 ); - } - } - - // Unzip WordPress. - if ( ! is_file( $wp_root_dir . '/wp-load.php' ) && ! unzip_file( $zip_file, $wp_root_dir ) ) { - echo magenta( "Failed to extract WordPress file $zip_file to $wp_root_dir." ); - exit( 1 ); - } - - return true; -} - -/** - * Ensures WordPress is correctly configured. - * - * @return bool Always `true` to indicate WordPress - * is set up correctly. - */ -function ensure_wordpress_configured(): bool { - $wp_root_dir = getenv( 'SLIC_WP_DIR' ); - $wp_config_file = $wp_root_dir . '/wp-config.php'; - - if ( is_file( $wp_config_file ) ) { - debug( "Found $wp_config_file, assuming WordPress already configured." . PHP_EOL ); - - // If the wp-config.php file already exists, assume WordPress is already configured correctly. - return true; - } - - $wp_config_sample_file = $wp_root_dir . '/wp-config-sample.php'; - - if ( ! is_file( $wp_config_sample_file ) ) { - echo magenta( "Config sample file $wp_config_sample_file not found." ); - exit( 1 ); - } - - $wp_config_contents = file_get_contents( $wp_config_sample_file ); - - if ( empty( $wp_config_contents ) ) { - echo magenta( "Config sample file $wp_config_sample_file could not be read or is empty." ); - exit( 1 ); - } - - debug( "Setting up credentials in $wp_config_file ..." . PHP_EOL ); - - // Set up the db credentials, rely on the placeholders that come with a default WordPress installation. - $wp_config_contents = str_replace( [ - 'query( 'SHOW TABLES' ); + if ( slic_realtime()( cli_command( [ 'core', 'is-installed' ] ) ) === 0 ) { + debug( "WordPress is already installed." . PHP_EOL ); - if ( ! $tables instanceof \mysqli_result ) { - echo magenta( 'Failed to query the WordPress database: ' . mysqli_error( $db ) ); - exit( 1 ); - } - - $tables_list = $tables->fetch_all( MYSQLI_NUM ); - if ( ! empty( $tables_list ) ) { - $default_tables = get_default_tables_list(); - $tables_list = array_column( $tables_list, 0 ); - - if ( count( array_diff( $default_tables, $tables_list ) ) === 0 ) { - - debug( "Default tables found: assuming WordPress is installed." . PHP_EOL ); - - return true; - } + return true; } - debug( "Default tables not found: assuming WordPress is not installed." . PHP_EOL ); - - $wp_root_dir = getenv( 'SLIC_WP_DIR' ); - $install_file = realpath( $wp_root_dir . '/wp-admin/install.php' ); - - if ( ! is_file( $install_file ) ) { - echo magenta( "WordPress installation file $install_file not found." ); + $install = [ + 'core', + 'install', + '--url=http://wordpress.test', + '--title=Slic', + '--admin_user=admin', + '--admin_password=password', + '--admin_email=admin@wordpress.test', + '--skip-email', + ]; + if ( slic_realtime()( cli_command( $install ) ) !== 0 ) { + // There will be debug detailing the issue. + echo magenta( "Failed to install WordPress." ); exit( 1 ); } - // In a separate process, call the installation file directly setting up the expected request vars. - $code = 'putenv( "DB_HOST=' . get_localhost_db_host() . '" ); ' . - '$_GET["step"] = 2; ' . - '$_POST["weblog_title"] = "Slic Test Site"; ' . - '$_POST["user_name"] = "admin"; ' . - '$_POST["admin_password"] = "password"; ' . - '$_POST["admin_password2"] = "password"; ' . - '$_POST["admin_email"] = "admin@wordpress.test"; ' . - '$_POST["blog_public"] = 1; ' . - 'function wp_mail(){ return true; } ' . // It's pluggable, this will mute it. - 'include "' . $install_file . '";'; - - $command = escapeshellarg( PHP_BINARY ) . ' -r \'' . $code . '\''; - - debug( "Installing WordPress with command $command ... " . PHP_EOL ); - - exec( $command, $output, $status ); - - $error_lines = array_filter( $output, static function ( $line ) { - return strpos( $line, 'Error' ) !== false; - } ); - $output_has_errors = count( $error_lines ); - - if ( $status !== 0 || $output_has_errors ) { - $circa_error_lines = array_map( static function ( $line_number, $line ) use ( $output ) { - return strip_tags( implode( PHP_EOL, array_slice( $output, $line_number, 10 ) ) ); - }, array_keys( $error_lines ), $error_lines ); - echo magenta( "WordPress installation failed with message(s):" . PHP_EOL . implode( PHP_EOL, $circa_error_lines ) ); + if ( slic_realtime()( cli_command( [ 'core', 'is-installed' ] ) ) !== 0 ) { + echo magenta( "Failed to check WordPress is installed." ); exit( 1 ); } @@ -448,8 +206,7 @@ function get_wordpress_latest_version(): string { * @return bool Always `true` to indicate success. */ function ensure_wordpress_ready( string $version = null ): bool { - ensure_wordpress_files( $version ); - ensure_wordpress_configured(); + ensure_services_running( [ 'slic', 'wordpress' ] ); ensure_wordpress_installed(); return true;