Skip to content

Commit

Permalink
Improvements to Environment architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
Luc45 committed Feb 26, 2024
1 parent d617c62 commit ae0cd5b
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 69 deletions.
1 change: 1 addition & 0 deletions src/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"ext-curl": "*",
"ext-zip": "*",
"symfony/console": "^5",
"symfony/process": "^5",
"symfony/filesystem": "^5",
"lucatume/di52": "^3",
"stecman/symfony-console-completion": "^0.11.0"
Expand Down
64 changes: 63 additions & 1 deletion src/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/src/Commands/Environment/DownEnvironmentCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected function execute( InputInterface $input, OutputInterface $output ): in

private function stopEnvironment( string $temporary_environment, OutputInterface $output ) {
// Implement the logic to stop the environment
$this->e2e_environment->down( $temporary_environment );
$this->e2e_environment->down( $this->environment_monitor->get_env_info_by_id( $temporary_environment ) );
$output->writeln( "<info>Environment '$temporary_environment' stopped.</info>" );
}
}
3 changes: 3 additions & 0 deletions src/src/Commands/Environment/ListEnvironmentCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ protected function execute( InputInterface $input, OutputInterface $output ): in
if ( $k === 'created_at' ) {
$v = $elapsed;
}
if ( is_array( $v ) ) {
$v = implode( ', ', $v );
}
$definitions[] = [ ucwords( $k ) => $v ];
}

Expand Down
2 changes: 1 addition & 1 deletion src/src/Commands/Environment/RestartEnvironmentCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function __construct(
}

protected function configure() {
$this->setDescription( 'Restarts the local test environment.' );
$this->setDescription( 'Restarts a local test environment.' );
}

protected function execute( InputInterface $input, OutputInterface $output ): int {
Expand Down
2 changes: 1 addition & 1 deletion src/src/Commands/Environment/UpEnvironmentCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ protected function configure() {

DynamicCommandCreator::add_schema_to_command( $this, $schemas['e2e'], [ 'compatibility' ] );

$this->setDescription( 'Starts the local test environment.' )
$this->setDescription( 'Starts a local test environment.' )
->setAliases( [ 'env:start' ] );
}

Expand Down
22 changes: 13 additions & 9 deletions src/src/Environment/Docker.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@ public function find_docker() {
}
}

public function find_docker_compose() {
public function run_on_docker( EnvInfo $env_info, array $command ) {

}

public function find_docker_compose(): array {
// Find out if it's docker-compose (v1) or docker compose (v2)
$dockerComposeV2 = 'docker compose';
$dockerComposeV1 = 'docker-compose';
$docker_compose_v2 = 'docker compose';
$docker_compose_v1 = 'docker-compose';

$dockerComposeV2Version = trim( shell_exec( $dockerComposeV2 . ' --version' ) );
$dockerComposeV1Version = trim( shell_exec( $dockerComposeV1 . ' --version' ) );
$v1_version = trim( shell_exec( $docker_compose_v2 . ' --version' ) );
$v2_version = trim( shell_exec( $docker_compose_v1 . ' --version' ) );

if ( $dockerComposeV2Version ) {
return $dockerComposeV2;
} elseif ( $dockerComposeV1Version ) {
return $dockerComposeV1;
if ( $v1_version ) {
return [ 'docker-compose' ];
} elseif ( $v2_version ) {
return [ 'docker', 'compose' ];
} else {
throw new \RuntimeException( 'Could not find docker-compose or docker compose' );
}
Expand Down
140 changes: 118 additions & 22 deletions src/src/Environment/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

use QIT_CLI\Cache;
use QIT_CLI\Config;
use QIT_CLI\SafeRemove;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Process\Process;

abstract class Environment {
/** @var EnvironmentDownloader */
Expand Down Expand Up @@ -34,12 +37,16 @@ abstract class Environment {
/** @var string */
protected $temporary_environment_path;

/** @var OutputInterface */
protected $output;

public function __construct(
EnvironmentDownloader $environment_Downloader,
Cache $cache,
EnvironmentMonitor $environment_monitor,
Filesystem $filesystem,
Docker $docker
Docker $docker,
OutputInterface $output
) {
$this->environment_downloader = $environment_Downloader;
$this->cache = $cache;
Expand All @@ -51,58 +58,147 @@ public function __construct(
$this->source_environment_path = Config::get_qit_dir() . '/environments/' . $this->get_name();
$this->temporary_environment_id = uniqid();
$this->temporary_environment_path = static::get_temp_envs_dir() . $this->get_name() . '-' . $this->temporary_environment_id;
$this->output = $output;
}

abstract public function get_name(): string;

abstract protected function do_up( EnvInfo $env_info ): void;
abstract protected function prepare_generate_docker_compose( Process $process ): void;

public function up(): void {
// Start the benchmark.
$start = microtime( true );

$this->environment_downloader->maybe_download( $this->get_name() );
$this->maybe_create_cache_dir();
$this->copy_environment();
$env_info = $this->init_env_info();
$this->environment_monitor->environment_added_or_updated( $env_info );
$this->generate_docker_compose();
$this->up_docker_compose( $env_info );

// Make sure cache directory exists.
if ( ! file_exists( $this->cache_dir ) ) {
if ( mkdir( $this->cache_dir ) === false ) {
throw new \RuntimeException( 'Failed to create cache directory on ' . $this->cache_dir );
}
}
$server_started = microtime( true );
echo "Server started at " . round( microtime( true ) - $start, 2 ) . " seconds\n";

// Start the benchmark.
$start = microtime( true );
echo "Temporary environment: $this->temporary_environment_path\n";
}

// Copy the reference environment to a temporary one.
// Copies the source environment to the temporary environment.
protected function copy_environment(): void {
$this->filesystem->mirror( $this->source_environment_path, $this->temporary_environment_path );

if ( ! file_exists( $this->temporary_environment_path . '/docker-compose-generator.php' ) ) {
throw new \RuntimeException( "Failed to copy the environment." );
}
}

// Creates the cache directory if it doesn't exist.
protected function maybe_create_cache_dir(): void {
if ( ! file_exists( $this->cache_dir ) ) {
if ( mkdir( $this->cache_dir, 0755 ) === false ) {
throw new \RuntimeException( 'Failed to create cache directory on ' . $this->cache_dir );
}
}
}

// Create the Env Info.
// Initialize the default env info for the temporary environment.
protected function init_env_info(): EnvInfo {
$env_info = new EnvInfo();
$env_info->type = $this->get_name();
$env_info->temporary_env = $this->temporary_environment_path;
$env_info->created_at = time();
$env_info->status = 'pending';

$this->environment_monitor->environment_added_or_updated( $env_info );
return $env_info;
}

$this->do_up( $env_info );
protected function generate_docker_compose(): void {
$process = new Process( [ PHP_BINARY, $this->temporary_environment_path . '/docker-compose-generator.php' ] );
$this->prepare_generate_docker_compose( $process );
$process->setEnv( array_merge( $process->getEnv(), [
'CACHE_DIR' => $this->cache_dir,
'QIT_ENV_ID' => $this->temporary_environment_id,
] ) );

$server_started = microtime( true );
echo "Server started at " . round( microtime( true ) - $start, 2 ) . " seconds\n";
try {
$process->mustRun();

echo "Temporary environment: $this->temporary_environment_path\n";
if ( $this->output->isVerbose() ) {
$this->output->writeln( $process->getOutput() );
}
} catch ( \Exception $e ) {
throw new \RuntimeException( "Failed to generate docker-compose.yml" );
}
}

protected function up_docker_compose( EnvInfo $env_info ) {
$this->add_container_names( $env_info );

$up_process = new Process( array_merge( $this->docker->find_docker_compose(), [ '-f', $env_info->temporary_env . '/docker-compose.yml', 'up', '-d' ] ) );
$up_process->setTty( true );

$up_process->run( function ( $type, $buffer ) {
$this->output->write( $buffer );
} );

if ( ! $up_process->isSuccessful() ) {
$this->down( $env_info );
throw new \RuntimeException( "Failed to start the environment." );
}

$env_info->status = 'started';

$this->environment_monitor->environment_added_or_updated( $env_info );
}

public function down( string $env_info_id ) {
// Stops the given environment.
$docker_compose = $this->docker->find_docker_compose();
public function down( EnvInfo $env_info ): void {
$down_process = new Process( array_merge( $this->docker->find_docker_compose(), [ '-f', $env_info->temporary_env . '/docker-compose.yml', 'down' ] ) );
$down_process->setTty( true );

$down_process->run( function ( $type, $buffer ) {
$this->output->write( $buffer );
} );

$env_info = $this->environment_monitor->get_env_info_by_id( $env_info_id );
if ( $down_process->isSuccessful() ) {
$this->output->writeln( "Removing temporary environment: " . $env_info->temporary_env );
SafeRemove::delete_dir( $env_info->temporary_env, static::get_temp_envs_dir() );
} else {
$this->output->writeln( "Failed to remove temporary environment: " . $env_info->temporary_env );
}

exec( $docker_compose . ' -f ' . $env_info->temporary_env . '/docker-compose.yml down', $down_output, $down_result_code );
$this->environment_monitor->environment_stopped( $env_info );
}

protected function add_container_names( EnvInfo $env_info ): void {
/*
* Get the docker containers names, eg:
* [+] Running 4/0
* ✔ DRY-RUN MODE - Container qit_env_cache_65dcc53c66545 Running 0.0s
* ✔ DRY-RUN MODE - Container qit_env_db_65dcc53c66545 Running 0.0s
* ✔ DRY-RUN MODE - Container qit_env_php_65dcc53c66545 Running 0.0s
* ✔ DRY-RUN MODE - Container qit_env_nginx_65dcc53c66545 Created 0.0s
* end of 'compose up' output, interactive run is not supported in dry-run mode
*/
$up_dry_run_process = new Process( array_merge( $this->docker->find_docker_compose(), [ '-f', $this->temporary_environment_path . '/docker-compose.yml', 'up', '--dry-run' ] ) );
$up_dry_run_process->run();

$containers = [];

foreach ( explode( "\n", $up_dry_run_process->getOutput() . "\n" . $up_dry_run_process->getErrorOutput() ) as $line ) {
if ( preg_match( '/(qit_env_[\w\d]+)/', $line, $matches ) ) {
$containers[] = $matches[1];
}
}

$containers = array_unique( $containers );

if ( empty( $containers ) ) {
throw new \RuntimeException( "Failed to start the environment. No containers found." );
}

$env_info->docker_images = $containers;
}

public static function get_temp_envs_dir(): string {
$dir = Config::get_qit_dir() . '/temporary-envs/';

Expand Down
Loading

0 comments on commit ae0cd5b

Please sign in to comment.