From 7021f29da6715b70cfea9dc51b11ecf396daecb0 Mon Sep 17 00:00:00 2001 From: Mathieu Santostefano Date: Tue, 20 Feb 2024 09:42:08 +0000 Subject: [PATCH] Move docker related stuff to docker NS --- .castor/{utils.php => docker.php} | 229 +++++++++++++++++++++++++++++ .castor/infra.php | 237 ------------------------------ .castor/init.php | 4 +- .castor/qa.php | 2 + .github/workflows/ci.yml | 1 + castor.php | 17 ++- 6 files changed, 247 insertions(+), 243 deletions(-) rename .castor/{utils.php => docker.php} (51%) delete mode 100644 .castor/infra.php diff --git a/.castor/utils.php b/.castor/docker.php similarity index 51% rename from .castor/utils.php rename to .castor/docker.php index c187a32..22d40b0 100644 --- a/.castor/utils.php +++ b/.castor/docker.php @@ -1,13 +1,20 @@ listing(array_map(fn ($url) => "https://{$url}", $urls)); } +#[AsTask(description: 'Builds the infrastructure', aliases: ['build'])] +function build(): void +{ + $userId = variable('user_id'); + $phpVersion = variable('php_version'); + + $command = [ + 'build', + '--build-arg', "USER_ID={$userId}", + '--build-arg', "PHP_VERSION={$phpVersion}", + ]; + + docker_compose($command, withBuilder: true); +} + +#[AsTask(description: 'Builds and starts the infrastructure', aliases: ['up'])] +function up(): void +{ + try { + docker_compose(['up', '--remove-orphans', '--detach', '--no-build']); + } catch (ExceptionInterface $e) { + io()->error('An error occured while starting the infrastructure.'); + io()->note('Did you forget to run "castor infra:build"?'); + io()->note('Or you forget to login to the registry?'); + + throw $e; + } +} + +#[AsTask(description: 'Stops the infrastructure', aliases: ['stop'])] +function stop(): void +{ + docker_compose(['stop']); +} + +#[AsTask(description: 'Opens a shell (bash) into a builder container', aliases: ['builder'])] +function builder(): void +{ + $c = context() + ->withTimeout(null) + ->withTty() + ->withEnvironment($_ENV + $_SERVER) + ->withAllowFailure() + ; + docker_compose_run('bash', c: $c); +} + +#[AsTask(description: 'Displays infrastructure logs', aliases: ['logs'])] +function logs(): void +{ + docker_compose(['logs', '-f', '--tail', '150'], c: context()->withTty()); +} + +#[AsTask(description: 'Lists containers status', aliases: ['ps'])] +function ps(): void +{ + docker_compose(['ps'], withBuilder: false); +} + +#[AsTask(description: 'Cleans the infrastructure (remove container, volume, networks)', aliases: ['destroy'])] +function destroy( + #[AsOption(description: 'Force the destruction without confirmation', shortcut: 'f')] + bool $force = false, +): void { + if (!$force) { + io()->warning('This will permanently remove all containers, volumes, networks... created for this project.'); + io()->note('You can use the --force option to avoid this confirmation.'); + if (!io()->confirm('Are you sure?', false)) { + io()->comment('Aborted.'); + + return; + } + } + + docker_compose(['down', '--remove-orphans', '--volumes', '--rmi=local'], withBuilder: true); + $files = finder() + ->in(variable('root_dir') . '/infrastructure/docker/services/router/certs/') + ->name('*.pem') + ->files() + ; + fs()->remove($files); +} + +#[AsTask(description: 'Generates SSL certificates (with mkcert if available or self-signed if not)')] +function generate_certificates( + #[AsOption(description: 'Force the certificates re-generation without confirmation', shortcut: 'f')] + bool $force = false, +): void { + $sslDir = variable('root_dir') . '/infrastructure/docker/services/router/certs'; + + if (file_exists("$sslDir/cert.pem") && !$force) { + io()->comment('SSL certificates already exists.'); + io()->note('Run "castor infra:generate-certificates --force" to generate new certificates.'); + + return; + } + + if ($force) { + if (file_exists($f = "$sslDir/cert.pem")) { + io()->comment('Removing existing certificates in infrastructure/docker/services/router/certs/*.pem.'); + unlink($f); + } + + if (file_exists($f = "$sslDir/key.pem")) { + unlink($f); + } + } + + $finder = new ExecutableFinder(); + $mkcert = $finder->find('mkcert'); + + if ($mkcert) { + $pathCaRoot = capture(['mkcert', '-CAROOT']); + + if (!is_dir($pathCaRoot)) { + io()->warning('You must have mkcert CA Root installed on your host with "mkcert -install" command.'); + + return; + } + + $rootDomain = variable('root_domain'); + + run([ + 'mkcert', + '-cert-file', "$sslDir/cert.pem", + '-key-file', "$sslDir/key.pem", + $rootDomain, + "*.{$rootDomain}", + ...variable('extra_domains'), + ]); + + io()->success('Successfully generated SSL certificates with mkcert.'); + + if ($force) { + io()->note('Please restart the infrastructure to use the new certificates with "castor up" or "castor start".'); + } + + return; + } + + run(['infrastructure/docker/services/router/generate-ssl.sh'], quiet: true); + + io()->success('Successfully generated self-signed SSL certificates in infrastructure/docker/services/router/certs/*.pem.'); + io()->comment('Consider installing mkcert to generate locally trusted SSL certificates and run "castor infra:generate-certificates --force".'); + + if ($force) { + io()->note('Please restart the infrastructure to use the new certificates with "castor up" or "castor start".'); + } +} + +#[AsTask(description: 'Starts the workers', namespace: 'infra:worker', name: 'start', aliases: ['start-workers'])] +function workers_start(): void +{ + $workers = get_workers(); + + if (!$workers) { + return; + } + + run([ + 'docker', + 'update', + '--restart=unless-stopped', + ...$workers, + ], quiet: true); + + run([ + 'docker', + 'start', + ...$workers, + ], quiet: true); +} + +#[AsTask(description: 'Stops the workers', namespace: 'infra:worker', name: 'stop', aliases: ['stop-workers'])] +function workers_stop(): void +{ + $workers = get_workers(); + + if (!$workers) { + return; + } + + run([ + 'docker', + 'update', + '--restart=no', + ...$workers, + ]); + + run([ + 'docker', + 'stop', + ...$workers, + ]); +} + #[AsContext(default: true)] function create_default_context(): Context { @@ -195,6 +398,7 @@ function docker_compose(array $subCommand, Context $c = null, bool $withBuilder $command[] = '-f'; $command[] = variable('root_dir') . '/infrastructure/docker/' . $file; } + if ($withBuilder) { $command[] = '-f'; $command[] = variable('root_dir') . '/infrastructure/docker/docker-compose.builder.yml'; @@ -217,3 +421,28 @@ function run_in_docker_or_locally_for_mac(string $command, Context $c = null): v docker_compose_run($command, c: $c); } } + +/** + * Find worker containers for the current project. + * + * @return array + */ +function get_workers(): array +{ + $command = [ + 'docker', + 'ps', + '-a', + '--filter', 'label=docker-starter.worker.' . variable('project_name'), + '--quiet', + ]; + $out = capture($command); + + if (!$out) { + return []; + } + + $workers = explode("\n", $out); + + return array_map('trim', $workers); +} diff --git a/.castor/infra.php b/.castor/infra.php deleted file mode 100644 index 9cfb18d..0000000 --- a/.castor/infra.php +++ /dev/null @@ -1,237 +0,0 @@ -error('An error occured while starting the infrastructure.'); - io()->note('Did you forget to run "castor infra:build"?'); - io()->note('Or you forget to login to the registry?'); - - throw $e; - } -} - -#[AsTask(description: 'Stops the infrastructure', aliases: ['stop'])] -function stop(): void -{ - docker_compose(['stop']); -} - -#[AsTask(description: 'Opens a shell (bash) into a builder container', aliases: ['builder'])] -function builder(): void -{ - $c = context() - ->withTimeout(null) - ->withTty() - ->withEnvironment($_ENV + $_SERVER) - ->withAllowFailure() - ; - docker_compose_run('bash', c: $c); -} - -#[AsTask(description: 'Displays infrastructure logs', aliases: ['logs'])] -function logs(): void -{ - docker_compose(['logs', '-f', '--tail', '150'], c: context()->withTty()); -} - -#[AsTask(description: 'Lists containers status', aliases: ['ps'])] -function ps(): void -{ - docker_compose(['ps'], withBuilder: false); -} - -#[AsTask(description: 'Cleans the infrastructure (remove container, volume, networks)', aliases: ['destroy'])] -function destroy( - #[AsOption(description: 'Force the destruction without confirmation', shortcut: 'f')] - bool $force = false, -): void { - if (!$force) { - io()->warning('This will permanently remove all containers, volumes, networks... created for this project.'); - io()->note('You can use the --force option to avoid this confirmation.'); - if (!io()->confirm('Are you sure?', false)) { - io()->comment('Aborted.'); - - return; - } - } - - docker_compose(['down', '--remove-orphans', '--volumes', '--rmi=local'], withBuilder: true); - $files = finder() - ->in(variable('root_dir') . '/infrastructure/docker/services/router/certs/') - ->name('*.pem') - ->files() - ; - fs()->remove($files); -} - -#[AsTask(description: 'Generates SSL certificates (with mkcert if available or self-signed if not)')] -function generate_certificates( - #[AsOption(description: 'Force the certificates re-generation without confirmation', shortcut: 'f')] - bool $force = false, -): void { - $sslDir = variable('root_dir') . '/infrastructure/docker/services/router/certs'; - - if (file_exists("$sslDir/cert.pem") && !$force) { - io()->comment('SSL certificates already exists.'); - io()->note('Run "castor infra:generate-certificates --force" to generate new certificates.'); - - return; - } - - if ($force) { - if (file_exists($f = "$sslDir/cert.pem")) { - io()->comment('Removing existing certificates in infrastructure/docker/services/router/certs/*.pem.'); - unlink($f); - } - - if (file_exists($f = "$sslDir/key.pem")) { - unlink($f); - } - } - - $finder = new ExecutableFinder(); - $mkcert = $finder->find('mkcert'); - - if ($mkcert) { - $pathCaRoot = capture(['mkcert', '-CAROOT']); - - if (!is_dir($pathCaRoot)) { - io()->warning('You must have mkcert CA Root installed on your host with "mkcert -install" command.'); - - return; - } - - $rootDomain = variable('root_domain'); - - run([ - 'mkcert', - '-cert-file', "$sslDir/cert.pem", - '-key-file', "$sslDir/key.pem", - $rootDomain, - "*.{$rootDomain}", - ...variable('extra_domains'), - ]); - - io()->success('Successfully generated SSL certificates with mkcert.'); - - if ($force) { - io()->note('Please restart the infrastructure to use the new certificates with "castor up" or "castor start".'); - } - - return; - } - - run(['infrastructure/docker/services/router/generate-ssl.sh'], quiet: true); - - io()->success('Successfully generated self-signed SSL certificates in infrastructure/docker/services/router/certs/*.pem.'); - io()->comment('Consider installing mkcert to generate locally trusted SSL certificates and run "castor infra:generate-certificates --force".'); - - if ($force) { - io()->note('Please restart the infrastructure to use the new certificates with "castor up" or "castor start".'); - } -} - -#[AsTask(description: 'Starts the workers', namespace: 'infra:worker', name: 'start', aliases: ['start-workers'])] -function workers_start(): void -{ - $workers = get_workers(); - - if (!$workers) { - return; - } - - run([ - 'docker', - 'update', - '--restart=unless-stopped', - ...$workers, - ], quiet: true); - - run([ - 'docker', - 'start', - ...$workers, - ], quiet: true); -} - -#[AsTask(description: 'Stops the workers', namespace: 'infra:worker', name: 'stop', aliases: ['stop-workers'])] -function workers_stop(): void -{ - $workers = get_workers(); - - if (!$workers) { - return; - } - - run([ - 'docker', - 'update', - '--restart=no', - ...$workers, - ]); - - run([ - 'docker', - 'stop', - ...$workers, - ]); -} - -/** - * Find worker containers for the current project. - * - * @return array - */ -function get_workers(): array -{ - $command = [ - 'docker', - 'ps', - '-a', - '--filter', 'label=docker-starter.worker.' . variable('project_name'), - '--quiet', - ]; - - $out = capture($command); - if (!$out) { - return []; - } - - $workers = explode("\n", $out); - - return array_map('trim', $workers); -} diff --git a/.castor/init.php b/.castor/init.php index 427d010..907c678 100644 --- a/.castor/init.php +++ b/.castor/init.php @@ -4,6 +4,8 @@ use function Castor\fs; use function Castor\variable; +use function docker\build; +use function docker\docker_compose_run; #[AsTask(description: 'Initialize the project')] function init(): void @@ -35,7 +37,7 @@ function symfony(bool $webApp = false): void $gitIgnoreContent = file_get_contents($gitIgnore); } - infra\build(); + build(); docker_compose_run('composer create-project symfony/skeleton sf'); fs()->mirror($base . '/sf/', $base); diff --git a/.castor/qa.php b/.castor/qa.php index 3380dbe..ab62570 100644 --- a/.castor/qa.php +++ b/.castor/qa.php @@ -3,6 +3,8 @@ namespace qa; use Castor\Attribute\AsTask; +use function docker\docker_compose_run; +use function docker\docker_exit_code; #[AsTask(description: 'Runs all QA tasks')] function all(): int diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f50e63a..5d096f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,7 @@ jobs: success('The stack is now up and running.');