From 0296626e85a3c692be230eaa9d06a012041f16cf Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 22 Jan 2025 11:51:35 +1000 Subject: [PATCH 01/40] format --- src/Aws.php | 2 +- src/Concerns/ChecksIfCommandsShouldBeRunning.php | 2 +- src/Concerns/ExecutesCommands.php | 5 +---- src/Concerns/RegistersAws.php | 2 +- src/Contracts/ExecutesDomainStep.php | 5 +---- src/Contracts/ExecutesMultitenancyStep.php | 5 +---- src/Contracts/RunsOnAws.php | 5 +---- src/Contracts/RunsOnAwsQueue.php | 5 +---- src/Contracts/RunsOnAwsScheduler.php | 5 +---- src/Contracts/RunsOnAwsWeb.php | 5 +---- src/Contracts/RunsOnBuild.php | 5 +---- src/Contracts/Step.php | 5 +---- src/Exceptions/IntegrityCheckException.php | 5 +---- src/Exceptions/ResourceDoesNotExistException.php | 5 +---- src/Exceptions/ResourceExistsException.php | 5 +---- src/Steps/Ami/CreateAutoScalingQueueGroupStep.php | 2 +- src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php | 2 +- src/Steps/Ami/CreateAutoScalingWebGroupStep.php | 2 +- src/Steps/Ami/CreateWebGroupCpuAlarmsStep.php | 2 +- src/Steps/Build/ConfigureEnvAndVersionStep.php | 4 +--- src/Steps/Build/CopyApplicationStep.php | 4 +--- src/Steps/Build/CreateTemporaryEnvStep.php | 4 +--- src/Steps/Build/PurgeBuildStep.php | 4 +--- src/Steps/Build/RestoreTemporaryEnvStep.php | 4 +--- src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php | 5 +---- src/Steps/Deploy/PushArtefactToS3Step.php | 4 +--- src/Steps/Deploy/PushAssetsToS3Step.php | 4 +--- src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php | 5 +---- src/Steps/ExecuteBuildCommandStep.php | 5 +---- src/Steps/Network/SyncKeyPairStep.php | 2 +- 30 files changed, 30 insertions(+), 89 deletions(-) diff --git a/src/Aws.php b/src/Aws.php index 92c09e2..e386797 100644 --- a/src/Aws.php +++ b/src/Aws.php @@ -8,8 +8,8 @@ use Aws\Rds\RdsClient; use Aws\Sns\SnsClient; use Aws\Sqs\SqsClient; -use Aws\Sts\StsClient; use Aws\Ssm\SsmClient; +use Aws\Sts\StsClient; use Aws\Route53\Route53Client; use Aws\CloudWatch\CloudWatchClient; use Aws\CodeDeploy\CodeDeployClient; diff --git a/src/Concerns/ChecksIfCommandsShouldBeRunning.php b/src/Concerns/ChecksIfCommandsShouldBeRunning.php index 9286f48..5c5d08c 100644 --- a/src/Concerns/ChecksIfCommandsShouldBeRunning.php +++ b/src/Concerns/ChecksIfCommandsShouldBeRunning.php @@ -9,8 +9,8 @@ use Codinglabs\Yolo\Contracts\RunsOnAws; use Codinglabs\Yolo\Contracts\RunsOnAwsWeb; use Codinglabs\Yolo\Contracts\RunsOnAwsQueue; -use Codinglabs\Yolo\Contracts\RunsOnAwsScheduler; use Codinglabs\Yolo\Contracts\ExecutesDomainStep; +use Codinglabs\Yolo\Contracts\RunsOnAwsScheduler; use Codinglabs\Yolo\Contracts\ExecutesMultitenancyStep; trait ChecksIfCommandsShouldBeRunning diff --git a/src/Concerns/ExecutesCommands.php b/src/Concerns/ExecutesCommands.php index 2ae6c2f..d5e218e 100644 --- a/src/Concerns/ExecutesCommands.php +++ b/src/Concerns/ExecutesCommands.php @@ -7,10 +7,7 @@ trait ExecutesCommands { - public function __construct(protected string $environment, protected string $command) - { - - } + public function __construct(protected string $environment, protected string $command) {} public function __invoke(): void { diff --git a/src/Concerns/RegistersAws.php b/src/Concerns/RegistersAws.php index 184eb75..72ee196 100644 --- a/src/Concerns/RegistersAws.php +++ b/src/Concerns/RegistersAws.php @@ -8,9 +8,9 @@ use Aws\Rds\RdsClient; use Aws\Sns\SnsClient; use Aws\Sqs\SqsClient; +use Aws\Ssm\SsmClient; use Aws\Sts\StsClient; use GuzzleHttp\Client; -use Aws\Ssm\SsmClient; use Codinglabs\Yolo\Aws; use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; diff --git a/src/Contracts/ExecutesDomainStep.php b/src/Contracts/ExecutesDomainStep.php index 31b6e0b..a18d611 100644 --- a/src/Contracts/ExecutesDomainStep.php +++ b/src/Contracts/ExecutesDomainStep.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface ExecutesDomainStep extends Step -{ - -} +interface ExecutesDomainStep extends Step {} diff --git a/src/Contracts/ExecutesMultitenancyStep.php b/src/Contracts/ExecutesMultitenancyStep.php index 10f49e1..ffd9c6a 100644 --- a/src/Contracts/ExecutesMultitenancyStep.php +++ b/src/Contracts/ExecutesMultitenancyStep.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface ExecutesMultitenancyStep extends Step -{ - -} +interface ExecutesMultitenancyStep extends Step {} diff --git a/src/Contracts/RunsOnAws.php b/src/Contracts/RunsOnAws.php index c2e7262..76192cb 100644 --- a/src/Contracts/RunsOnAws.php +++ b/src/Contracts/RunsOnAws.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface RunsOnAws extends Step -{ - -} +interface RunsOnAws extends Step {} diff --git a/src/Contracts/RunsOnAwsQueue.php b/src/Contracts/RunsOnAwsQueue.php index 2e533d7..a379396 100644 --- a/src/Contracts/RunsOnAwsQueue.php +++ b/src/Contracts/RunsOnAwsQueue.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface RunsOnAwsQueue extends RunsOnAws -{ - -} +interface RunsOnAwsQueue extends RunsOnAws {} diff --git a/src/Contracts/RunsOnAwsScheduler.php b/src/Contracts/RunsOnAwsScheduler.php index 60dacd6..50873a3 100644 --- a/src/Contracts/RunsOnAwsScheduler.php +++ b/src/Contracts/RunsOnAwsScheduler.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface RunsOnAwsScheduler extends RunsOnAws -{ - -} +interface RunsOnAwsScheduler extends RunsOnAws {} diff --git a/src/Contracts/RunsOnAwsWeb.php b/src/Contracts/RunsOnAwsWeb.php index 44ab112..71a3c0e 100644 --- a/src/Contracts/RunsOnAwsWeb.php +++ b/src/Contracts/RunsOnAwsWeb.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface RunsOnAwsWeb extends RunsOnAws -{ - -} +interface RunsOnAwsWeb extends RunsOnAws {} diff --git a/src/Contracts/RunsOnBuild.php b/src/Contracts/RunsOnBuild.php index c46502b..3f8bc4d 100644 --- a/src/Contracts/RunsOnBuild.php +++ b/src/Contracts/RunsOnBuild.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface RunsOnBuild extends Step -{ - -} +interface RunsOnBuild extends Step {} diff --git a/src/Contracts/Step.php b/src/Contracts/Step.php index 0ca6d07..660cca0 100644 --- a/src/Contracts/Step.php +++ b/src/Contracts/Step.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface Step -{ - -} +interface Step {} diff --git a/src/Exceptions/IntegrityCheckException.php b/src/Exceptions/IntegrityCheckException.php index 24ca8cf..8485823 100644 --- a/src/Exceptions/IntegrityCheckException.php +++ b/src/Exceptions/IntegrityCheckException.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Exceptions; -class IntegrityCheckException extends YoloException -{ - -} +class IntegrityCheckException extends YoloException {} diff --git a/src/Exceptions/ResourceDoesNotExistException.php b/src/Exceptions/ResourceDoesNotExistException.php index bb44af9..e5492dd 100644 --- a/src/Exceptions/ResourceDoesNotExistException.php +++ b/src/Exceptions/ResourceDoesNotExistException.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Exceptions; -class ResourceDoesNotExistException extends YoloException -{ - -} +class ResourceDoesNotExistException extends YoloException {} diff --git a/src/Exceptions/ResourceExistsException.php b/src/Exceptions/ResourceExistsException.php index cfedf1e..565fbec 100644 --- a/src/Exceptions/ResourceExistsException.php +++ b/src/Exceptions/ResourceExistsException.php @@ -2,7 +2,4 @@ namespace Codinglabs\Yolo\Exceptions; -class ResourceExistsException extends YoloException -{ - -} +class ResourceExistsException extends YoloException {} diff --git a/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php b/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php index d6409a2..de58e3b 100644 --- a/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php @@ -3,8 +3,8 @@ namespace Codinglabs\Yolo\Steps\Ami; use Codinglabs\Yolo\Aws; -use Illuminate\Support\Str; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Contracts\Step; diff --git a/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php b/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php index 74106e8..7964857 100644 --- a/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php @@ -3,8 +3,8 @@ namespace Codinglabs\Yolo\Steps\Ami; use Codinglabs\Yolo\Aws; -use Illuminate\Support\Str; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Contracts\Step; diff --git a/src/Steps/Ami/CreateAutoScalingWebGroupStep.php b/src/Steps/Ami/CreateAutoScalingWebGroupStep.php index be95847..2891c21 100644 --- a/src/Steps/Ami/CreateAutoScalingWebGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingWebGroupStep.php @@ -3,8 +3,8 @@ namespace Codinglabs\Yolo\Steps\Ami; use Codinglabs\Yolo\Aws; -use Illuminate\Support\Str; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Contracts\Step; diff --git a/src/Steps/Ami/CreateWebGroupCpuAlarmsStep.php b/src/Steps/Ami/CreateWebGroupCpuAlarmsStep.php index ff1be78..92ab0ee 100644 --- a/src/Steps/Ami/CreateWebGroupCpuAlarmsStep.php +++ b/src/Steps/Ami/CreateWebGroupCpuAlarmsStep.php @@ -3,8 +3,8 @@ namespace Codinglabs\Yolo\Steps\Ami; use Codinglabs\Yolo\Aws; -use Illuminate\Support\Str; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; diff --git a/src/Steps/Build/ConfigureEnvAndVersionStep.php b/src/Steps/Build/ConfigureEnvAndVersionStep.php index 2e36c21..5045867 100644 --- a/src/Steps/Build/ConfigureEnvAndVersionStep.php +++ b/src/Steps/Build/ConfigureEnvAndVersionStep.php @@ -12,9 +12,7 @@ class ConfigureEnvAndVersionStep implements Step public function __construct( protected string $environment, protected $filesystem = new Filesystem() - ) { - - } + ) {} public function __invoke(array $options): void { diff --git a/src/Steps/Build/CopyApplicationStep.php b/src/Steps/Build/CopyApplicationStep.php index 62e8641..2596c2a 100644 --- a/src/Steps/Build/CopyApplicationStep.php +++ b/src/Steps/Build/CopyApplicationStep.php @@ -12,9 +12,7 @@ class CopyApplicationStep implements Step public function __construct( protected string $environment, protected $filesystem = new Filesystem() - ) { - - } + ) {} public function __invoke(): void { diff --git a/src/Steps/Build/CreateTemporaryEnvStep.php b/src/Steps/Build/CreateTemporaryEnvStep.php index 8cac097..beee714 100644 --- a/src/Steps/Build/CreateTemporaryEnvStep.php +++ b/src/Steps/Build/CreateTemporaryEnvStep.php @@ -11,9 +11,7 @@ class CreateTemporaryEnvStep implements Step public function __construct( protected string $environment, protected $filesystem = new Filesystem() - ) { - - } + ) {} public function __invoke(): void { diff --git a/src/Steps/Build/PurgeBuildStep.php b/src/Steps/Build/PurgeBuildStep.php index 022f6b0..1ef0828 100644 --- a/src/Steps/Build/PurgeBuildStep.php +++ b/src/Steps/Build/PurgeBuildStep.php @@ -11,9 +11,7 @@ class PurgeBuildStep implements Step public function __construct( protected string $environment, protected $filesystem = new Filesystem() - ) { - - } + ) {} public function __invoke(): void { diff --git a/src/Steps/Build/RestoreTemporaryEnvStep.php b/src/Steps/Build/RestoreTemporaryEnvStep.php index 07a5c7e..b0370ae 100644 --- a/src/Steps/Build/RestoreTemporaryEnvStep.php +++ b/src/Steps/Build/RestoreTemporaryEnvStep.php @@ -11,9 +11,7 @@ class RestoreTemporaryEnvStep implements Step public function __construct( protected string $environment, protected $filesystem = new Filesystem() - ) { - - } + ) {} public function __invoke(): void { diff --git a/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php b/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php index 1031784..a3d3352 100644 --- a/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php +++ b/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php @@ -13,10 +13,7 @@ class CreateCodeDeployDeploymentsStep implements Step { use UsesCodeDeploy; - public function __construct(protected string $environment, protected $filesystem = new Filesystem()) - { - - } + public function __construct(protected string $environment, protected $filesystem = new Filesystem()) {} public function __invoke(): void { diff --git a/src/Steps/Deploy/PushArtefactToS3Step.php b/src/Steps/Deploy/PushArtefactToS3Step.php index 8634863..2089e69 100644 --- a/src/Steps/Deploy/PushArtefactToS3Step.php +++ b/src/Steps/Deploy/PushArtefactToS3Step.php @@ -13,9 +13,7 @@ class PushArtefactToS3Step implements Step public function __construct( protected string $environment, protected $filesystem = new Filesystem() - ) { - - } + ) {} public function __invoke(): void { diff --git a/src/Steps/Deploy/PushAssetsToS3Step.php b/src/Steps/Deploy/PushAssetsToS3Step.php index be5fb84..01ac0b4 100644 --- a/src/Steps/Deploy/PushAssetsToS3Step.php +++ b/src/Steps/Deploy/PushAssetsToS3Step.php @@ -14,9 +14,7 @@ class PushAssetsToS3Step implements Step public function __construct( protected string $environment, protected $filesystem = new Filesystem() - ) { - - } + ) {} public function __invoke(): void { diff --git a/src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php b/src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php index 0bfc7b9..1e0d56f 100644 --- a/src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php +++ b/src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php @@ -15,10 +15,7 @@ class EnsureEnvIsConfiguredCorrectlyStep implements Step { - public function __construct(protected string $environment, protected $filesystem = new Filesystem()) - { - - } + public function __construct(protected string $environment, protected $filesystem = new Filesystem()) {} public function __invoke(): StepResult { diff --git a/src/Steps/ExecuteBuildCommandStep.php b/src/Steps/ExecuteBuildCommandStep.php index d255ae1..31a392c 100644 --- a/src/Steps/ExecuteBuildCommandStep.php +++ b/src/Steps/ExecuteBuildCommandStep.php @@ -12,10 +12,7 @@ class ExecuteBuildCommandStep implements ExecutesCommandStep, RunsOnBuild { - public function __construct(protected string $environment, protected string $command, protected $filesystem = new Filesystem()) - { - - } + public function __construct(protected string $environment, protected string $command, protected $filesystem = new Filesystem()) {} public function __invoke(): void { diff --git a/src/Steps/Network/SyncKeyPairStep.php b/src/Steps/Network/SyncKeyPairStep.php index 1898acf..2c11b94 100644 --- a/src/Steps/Network/SyncKeyPairStep.php +++ b/src/Steps/Network/SyncKeyPairStep.php @@ -7,8 +7,8 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; -use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Commands\Command; +use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; use function Laravel\Prompts\note; use function Laravel\Prompts\intro; From 51440718b986516ede09dfa077adddc05b08cf95 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 22 Jan 2025 11:53:10 +1000 Subject: [PATCH 02/40] wip it --- src/Commands/InitCommand.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index e6b721a..77a26a8 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -21,8 +21,6 @@ protected function configure(): void public function handle(): void { - $this->gitIgnoreFilesAndDirectories(); - if (Manifest::exists()) { if (! confirm("A yolo.yml manifest already exists in the current directory. Do you want to overwrite it?", default: false)) { return; @@ -31,6 +29,7 @@ public function handle(): void intro("Initialising yolo.yml"); + $this->gitIgnoreFilesAndDirectories(); $this->initialiseManifest(); info('Manifest generated successfully.'); @@ -81,8 +80,8 @@ protected function gitIgnoreFilesAndDirectories(): void file_put_contents( Paths::base('.gitignore'), ".yolo" . PHP_EOL . - ".env.staging" . PHP_EOL, - ".env.production" . PHP_EOL . + ".env.staging" . PHP_EOL . + ".env.production" . PHP_EOL, FILE_APPEND ); } From 939604edbcbf014a85264163717e300b7f5a07db Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 22 Jan 2025 12:42:54 +1000 Subject: [PATCH 03/40] wip it --- src/Commands/BuildCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Commands/BuildCommand.php b/src/Commands/BuildCommand.php index ea1083c..3e16319 100644 --- a/src/Commands/BuildCommand.php +++ b/src/Commands/BuildCommand.php @@ -29,6 +29,7 @@ protected function configure(): void ->setName('build') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('app-version', null, InputArgument::OPTIONAL, 'The app version to tag the build with') + ->addOption('no-progress', null, null, 'Hide the progress output') ->setDescription('Prepare a build of the application for deployment'); } From a7aa013ca759dac59d312061eadd01e32e7b7778 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 22 Jan 2025 15:45:19 +1000 Subject: [PATCH 04/40] wip --- src/Concerns/EnsuresResourcesExist.php | 3 ++- src/Concerns/UsesEc2.php | 4 ++-- src/Steps/Network/SyncKeyPairStep.php | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Concerns/EnsuresResourcesExist.php b/src/Concerns/EnsuresResourcesExist.php index f1a5987..37b9604 100644 --- a/src/Concerns/EnsuresResourcesExist.php +++ b/src/Concerns/EnsuresResourcesExist.php @@ -4,6 +4,7 @@ use Closure; use Illuminate\Support\Str; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Exceptions\YoloException; use function Laravel\Prompts\note; use function Laravel\Prompts\alert; @@ -18,7 +19,7 @@ public function ensure(Closure $closure): void alert(sprintf('%s: %s', Str::replaceLast('.php', '', basename($e->getFile())), $e->getMessage())); if ($e->getSuggestion()) { - note('Suggestion: try running "yolo ' . $e->getSuggestion() . '"'); + note(sprintf('Suggestion: try running "yolo %s %s"', $e->getSuggestion(), Helpers::environment())); } exit(1); diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index 02d9101..4389aa3 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -335,7 +335,7 @@ public static function keyPair(): array return static::$keyPair; } - $name = Helpers::keyedResourceName(exclusive: false); + $name = Manifest::get('aws.ec2.key-pair', Helpers::keyedResourceName(exclusive: false)); foreach (Aws::ec2()->describeKeyPairs()['KeyPairs'] as $keyPair) { if ($keyPair['KeyName'] === $name) { @@ -345,7 +345,7 @@ public static function keyPair(): array } ResourceDoesNotExistException::make("Could not find key pair with name $name") - ->suggest('init') + ->suggest('sync:network') ->throw(); } } diff --git a/src/Steps/Network/SyncKeyPairStep.php b/src/Steps/Network/SyncKeyPairStep.php index 2c11b94..c81ec5e 100644 --- a/src/Steps/Network/SyncKeyPairStep.php +++ b/src/Steps/Network/SyncKeyPairStep.php @@ -5,6 +5,7 @@ use Codinglabs\Yolo\Aws; use Codinglabs\Yolo\Paths; use Codinglabs\Yolo\Helpers; +use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Commands\Command; @@ -22,20 +23,22 @@ public function __invoke(array $options, Command $command): StepResult AwsResources::keyPair(); return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { + $name = Manifest::get('aws.ec2.key-pair', Helpers::keyedResourceName(exclusive: false)); + $key = Aws::ec2()->createKeyPair([ - 'KeyName' => Helpers::keyedResourceName(exclusive: false), + 'KeyName' => $name, 'TagSpecifications' => [ [ 'ResourceType' => 'key-pair', ...Aws::tags([ - 'Name' => Helpers::keyedResourceName(exclusive: false), + 'Name' => $name, ]), ], ], ]); $envFilename = ".env"; - $suggestedPath = sprintf("~/.ssh/%s", Helpers::keyedResourceName(exclusive: false)); + $suggestedPath = sprintf("~/.ssh/%s", $name); $suggestedEnv = sprintf('%s=%s', Helpers::keyedEnvName('SSH_KEY'), $suggestedPath); $command->after(function () use ($suggestedPath, $key) { From e671e7a848ee06ae7a93299653fa87f2928f8d4d Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 22 Jan 2025 16:40:51 +1000 Subject: [PATCH 05/40] sync some progress --- .../Ensures/EnsureEnvIsConfiguredCorrectlyStep.php | 4 +--- src/Steps/Ensures/EnsureHostedZonesExistStep.php | 11 +++++------ .../EnsureMultitenancyHostedZonesExistStep.php | 9 ++++----- src/Steps/Ensures/EnsureTranscoderExistsStep.php | 11 +++++------ src/Steps/Start/SetOwnershipAndPermissionsStep.php | 1 - 5 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php b/src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php index 1e0d56f..dcb15d3 100644 --- a/src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php +++ b/src/Steps/Ensures/EnsureEnvIsConfiguredCorrectlyStep.php @@ -11,7 +11,6 @@ use Illuminate\Filesystem\Filesystem; use Codinglabs\Yolo\Exceptions\IntegrityCheckException; use Illuminate\Contracts\Filesystem\FileNotFoundException; -use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; class EnsureEnvIsConfiguredCorrectlyStep implements Step { @@ -28,7 +27,6 @@ public function __invoke(): StepResult /** * @throws IntegrityCheckException - * @throws ResourceDoesNotExistException * @throws FileNotFoundException */ protected function checkTranscoderConfiguration(): void @@ -39,7 +37,7 @@ protected function checkTranscoderConfiguration(): void $dotenv = Dotenv::parse($this->filesystem->get(Paths::build('.env'))); if ($dotenv['AWS_TRANSCODER_PIPELINE'] !== $elasticTranscoderPipeline['Id']) { - throw new IntegrityCheckException("Transcoder piepeline ID {$dotenv['AWS_TRANSCODER_PIPELINE']} does not match {$elasticTranscoderPipeline['Id']}"); + throw new IntegrityCheckException("Transcoder pipeline ID {$dotenv['AWS_TRANSCODER_PIPELINE']} does not match {$elasticTranscoderPipeline['Id']}"); } if ($dotenv['AWS_TRANSCODER_PRESET'] != $elasticTranscoderPreset['Id']) { diff --git a/src/Steps/Ensures/EnsureHostedZonesExistStep.php b/src/Steps/Ensures/EnsureHostedZonesExistStep.php index 1283535..13f0fe5 100644 --- a/src/Steps/Ensures/EnsureHostedZonesExistStep.php +++ b/src/Steps/Ensures/EnsureHostedZonesExistStep.php @@ -6,18 +6,17 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Contracts\ExecutesDomainStep; -use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; +use Codinglabs\Yolo\Concerns\EnsuresResourcesExist; class EnsureHostedZonesExistStep implements ExecutesDomainStep { - /** - * @throws ResourceDoesNotExistException - */ + use EnsuresResourcesExist; + public function __invoke(array $options): StepResult { Manifest::get('apex') - ? AwsResources::hostedZone(Manifest::get('apex')) - : AwsResources::hostedZone(Manifest::get('domain')); + ? $this->ensure(fn () => AwsResources::hostedZone(Manifest::get('apex'))) + : $this->ensure(fn () => AwsResources::hostedZone(Manifest::get('domain'))); return StepResult::SYNCED; } diff --git a/src/Steps/Ensures/EnsureMultitenancyHostedZonesExistStep.php b/src/Steps/Ensures/EnsureMultitenancyHostedZonesExistStep.php index 4c3e3dc..368cfb3 100644 --- a/src/Steps/Ensures/EnsureMultitenancyHostedZonesExistStep.php +++ b/src/Steps/Ensures/EnsureMultitenancyHostedZonesExistStep.php @@ -5,16 +5,15 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Steps\TenantStep; -use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; +use Codinglabs\Yolo\Concerns\EnsuresResourcesExist; class EnsureMultitenancyHostedZonesExistStep extends TenantStep { - /** - * @throws ResourceDoesNotExistException - */ + use EnsuresResourcesExist; + public function __invoke(array $options): StepResult { - AwsResources::hostedZone($this->config['apex']); + $this->ensure(fn () => AwsResources::hostedZone($this->config['apex'])); return StepResult::SYNCED; } diff --git a/src/Steps/Ensures/EnsureTranscoderExistsStep.php b/src/Steps/Ensures/EnsureTranscoderExistsStep.php index c424e10..da2d840 100644 --- a/src/Steps/Ensures/EnsureTranscoderExistsStep.php +++ b/src/Steps/Ensures/EnsureTranscoderExistsStep.php @@ -6,21 +6,20 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; +use Codinglabs\Yolo\Concerns\EnsuresResourcesExist; class EnsureTranscoderExistsStep implements Step { - /** - * @throws ResourceDoesNotExistException - */ + use EnsuresResourcesExist; + public function __invoke(): StepResult { if (Manifest::get('aws.transcoder') === null) { return StepResult::SKIPPED; } - AwsResources::elasticTranscoderPipeline(); - AwsResources::elasticTranscoderPreset(); + $this->ensure(fn () => AwsResources::elasticTranscoderPipeline()); + $this->ensure(fn () => AwsResources::elasticTranscoderPreset()); return StepResult::SUCCESS; } diff --git a/src/Steps/Start/SetOwnershipAndPermissionsStep.php b/src/Steps/Start/SetOwnershipAndPermissionsStep.php index 16e2117..6c9e4b9 100644 --- a/src/Steps/Start/SetOwnershipAndPermissionsStep.php +++ b/src/Steps/Start/SetOwnershipAndPermissionsStep.php @@ -15,7 +15,6 @@ public function __invoke(): array return [ "chown -R ubuntu:ubuntu /var/www", "chmod -R 757 /var/www/$name/storage", - "chmod -R 757 /var/www/$name/resources/views/components/pages", ]; } } From 45d09d92da5d27d14c9b03f3bc9b4d0a9cb1c5d1 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Fri, 24 Jan 2025 16:30:05 +1000 Subject: [PATCH 06/40] pull some improvements to CI syncing --- src/Aws.php | 7 +- src/Enums/DeploymentGroups.php | 10 +++ src/Helpers.php | 28 +++++++ src/Paths.php | 2 +- .../Ci/SyncCodeDeployApplicationStep.php | 16 +++- .../Ci/SyncCodeDeployDeploymentConfigStep.php | 2 +- ...SyncCodeDeployQueueDeploymentGroupStep.php | 64 ++++++++++----- ...CodeDeploySchedulerDeploymentGroupStep.php | 64 ++++++++++----- .../SyncCodeDeployWebDeploymentGroupStep.php | 78 ++++++++++++------- .../CreateCodeDeployDeploymentsStep.php | 7 +- .../UpdateCodeDeployDeploymentGroupStep.php | 7 +- src/Steps/Start/RestartServicesStep.php | 4 +- src/Steps/Start/SyncPhpConfigurationStep.php | 12 +-- 13 files changed, 212 insertions(+), 89 deletions(-) create mode 100644 src/Enums/DeploymentGroups.php diff --git a/src/Aws.php b/src/Aws.php index e386797..784700a 100644 --- a/src/Aws.php +++ b/src/Aws.php @@ -60,12 +60,7 @@ public static function tags(array $tags = [], string $wrap = 'Tags'): array public static function accountId(): string { - return Aws::sts()->getAccessKeyInfo([ - 'AccessKeyId' => Aws::s3() - ->getCredentials() - ->wait() - ->getAccessKeyId(), - ])['Account']; + return Manifest::get('aws.account-id'); } public static function acm(): AcmClient diff --git a/src/Enums/DeploymentGroups.php b/src/Enums/DeploymentGroups.php new file mode 100644 index 0000000..3833f93 --- /dev/null +++ b/src/Enums/DeploymentGroups.php @@ -0,0 +1,10 @@ + $value) { + // check if the key exists in the second array + if (! array_key_exists($key, $actual)) { + // Key not found + return true; + } + + // if the value is an array, call the function recursively + if (is_array($value)) { + if (! is_array($actual[$key]) || static::payloadHasDifferences($value, $actual[$key])) { + // recursive comparison failed or not an array + return true; + } + } else { + // compare the values directly + if ($value !== $actual[$key]) { + // values do not match + return true; + } + } + } + + // all keys and values matched + return false; + } } diff --git a/src/Paths.php b/src/Paths.php index 0738c76..02c367f 100644 --- a/src/Paths.php +++ b/src/Paths.php @@ -61,7 +61,7 @@ public static function s3BuildAssets(string $appVersion): string public static function s3ArtefactsBucket(): ?string { - return Helpers::keyedResourceName('artefacts'); + return Manifest::get('aws.artefacts-bucket', Helpers::keyedResourceName('artefacts')); } public static function s3Artefacts(string $appVersion, $path = null): string diff --git a/src/Steps/Ci/SyncCodeDeployApplicationStep.php b/src/Steps/Ci/SyncCodeDeployApplicationStep.php index b371b35..3f1ff5a 100644 --- a/src/Steps/Ci/SyncCodeDeployApplicationStep.php +++ b/src/Steps/Ci/SyncCodeDeployApplicationStep.php @@ -17,15 +17,25 @@ class SyncCodeDeployApplicationStep implements Step public function __invoke(array $options): StepResult { try { - AwsResources::application(); - return StepResult::SYNCED; + $application = AwsResources::application(); + + if (! Arr::get($options, 'dry-run')) { + // AWS allows updates to the application name only, + // so we'll eager merge tags when syncing + Aws::codeDeploy()->tagResource([ + 'ResourceArn' => static::arnForApplication($application), + ...Aws::tags(), + ]); + } + + return StepResult::IN_SYNC; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { Aws::codeDeploy()->createApplication([ 'applicationName' => static::applicationName(), ...Aws::tags([ 'Name' => static::applicationName(), - ]), + ], wrap: 'tags'), ]); return StepResult::CREATED; diff --git a/src/Steps/Ci/SyncCodeDeployDeploymentConfigStep.php b/src/Steps/Ci/SyncCodeDeployDeploymentConfigStep.php index 8b54921..01b0e75 100644 --- a/src/Steps/Ci/SyncCodeDeployDeploymentConfigStep.php +++ b/src/Steps/Ci/SyncCodeDeployDeploymentConfigStep.php @@ -16,7 +16,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::OneThirdAtATimeDeploymentConfig(); - return StepResult::SYNCED; + return StepResult::IN_SYNC; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { Aws::codeDeploy()->createDeploymentConfig([ diff --git a/src/Steps/Ci/SyncCodeDeployQueueDeploymentGroupStep.php b/src/Steps/Ci/SyncCodeDeployQueueDeploymentGroupStep.php index 4a8462a..f64d00f 100644 --- a/src/Steps/Ci/SyncCodeDeployQueueDeploymentGroupStep.php +++ b/src/Steps/Ci/SyncCodeDeployQueueDeploymentGroupStep.php @@ -8,6 +8,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; +use Codinglabs\Yolo\Enums\DeploymentGroups; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; @@ -18,27 +19,34 @@ class SyncCodeDeployQueueDeploymentGroupStep implements Step public function __invoke(array $options): StepResult { try { - AwsResources::queueDeploymentGroup(); - return StepResult::SYNCED; + $deploymentGroup = AwsResources::queueDeploymentGroup(); + + $differences = Helpers::payloadHasDifferences( + expected: $this->payload(), + actual: static::normaliseDeploymentGroupForComparison($deploymentGroup) + ); + + if (! Arr::get($options, 'dry-run')) { + // always sync tags as they are not compared in the payload + static::applyTagsToDeploymentGroup($deploymentGroup); + + if ($differences) { + Aws::codeDeploy()->updateDeploymentGroup([ + 'currentDeploymentGroupName' => $deploymentGroup['deploymentGroupName'], + ...$this->payload(), + ]); + + return StepResult::SYNCED; + } + } + + return $differences + ? StepResult::OUT_OF_SYNC + : StepResult::IN_SYNC; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { - Aws::codeDeploy()->createDeploymentGroup([ - ...static::deploymentGroupPayload(), - ...[ - 'deploymentGroupName' => Helpers::keyedResourceName('queue'), - 'deploymentConfigName' => 'CodeDeployDefault.AllAtOnce', - 'autoScalingGroups' => [ - AwsResources::autoScalingGroupQueue()['AutoScalingGroupName'], - ], - 'deploymentStyle' => [ - 'deploymentType' => 'IN_PLACE', - 'deploymentOption' => 'WITHOUT_TRAFFIC_CONTROL', - ], - ], - ...Aws::tags([ - 'Name' => Helpers::keyedResourceName('scheduler'), - ]), - ]); + Aws::codeDeploy()->createDeploymentGroup($this->payload()); + static::applyTagsToDeploymentGroup(AwsResources::queueDeploymentGroup()); return StepResult::CREATED; } @@ -46,4 +54,22 @@ public function __invoke(array $options): StepResult return StepResult::WOULD_CREATE; } } + + protected function payload(): array + { + return [ + ...static::deploymentGroupPayload(), + ...[ + 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::QUEUE), + 'deploymentConfigName' => 'CodeDeployDefault.AllAtOnce', + 'autoScalingGroups' => [ + AwsResources::autoScalingGroupQueue()['AutoScalingGroupName'], + ], + 'deploymentStyle' => [ + 'deploymentType' => 'IN_PLACE', + 'deploymentOption' => 'WITHOUT_TRAFFIC_CONTROL', + ], + ], + ]; + } } diff --git a/src/Steps/Ci/SyncCodeDeploySchedulerDeploymentGroupStep.php b/src/Steps/Ci/SyncCodeDeploySchedulerDeploymentGroupStep.php index 1000e25..1810ded 100644 --- a/src/Steps/Ci/SyncCodeDeploySchedulerDeploymentGroupStep.php +++ b/src/Steps/Ci/SyncCodeDeploySchedulerDeploymentGroupStep.php @@ -8,6 +8,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; +use Codinglabs\Yolo\Enums\DeploymentGroups; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; @@ -18,27 +19,34 @@ class SyncCodeDeploySchedulerDeploymentGroupStep implements Step public function __invoke(array $options): StepResult { try { - AwsResources::schedulerDeploymentGroup(); - return StepResult::SYNCED; + $deploymentGroup = AwsResources::schedulerDeploymentGroup(); + + $differences = Helpers::payloadHasDifferences( + expected: $this->payload(), + actual: static::normaliseDeploymentGroupForComparison($deploymentGroup) + ); + + if (! Arr::get($options, 'dry-run')) { + // always sync tags as they are not compared in the payload + static::applyTagsToDeploymentGroup($deploymentGroup); + + if ($differences) { + Aws::codeDeploy()->updateDeploymentGroup([ + 'currentDeploymentGroupName' => $deploymentGroup['deploymentGroupName'], + ...$this->payload(), + ]); + + return StepResult::SYNCED; + } + } + + return $differences + ? StepResult::OUT_OF_SYNC + : StepResult::IN_SYNC; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { - Aws::codeDeploy()->createDeploymentGroup([ - ...static::deploymentGroupPayload(), - ...[ - 'deploymentGroupName' => Helpers::keyedResourceName('scheduler'), - 'deploymentConfigName' => 'CodeDeployDefault.AllAtOnce', - 'autoScalingGroups' => [ - AwsResources::autoScalingGroupScheduler()['AutoScalingGroupName'], - ], - 'deploymentStyle' => [ - 'deploymentType' => 'IN_PLACE', - 'deploymentOption' => 'WITHOUT_TRAFFIC_CONTROL', - ], - ], - ...Aws::tags([ - 'Name' => Helpers::keyedResourceName('scheduler'), - ]), - ]); + Aws::codeDeploy()->createDeploymentGroup($this->payload()); + static::applyTagsToDeploymentGroup(AwsResources::schedulerDeploymentGroup()); return StepResult::CREATED; } @@ -46,4 +54,22 @@ public function __invoke(array $options): StepResult return StepResult::WOULD_CREATE; } } + + protected function payload(): array + { + return [ + ...static::deploymentGroupPayload(), + ...[ + 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::SCHEDULER), + 'deploymentConfigName' => 'CodeDeployDefault.AllAtOnce', + 'autoScalingGroups' => [ + AwsResources::autoScalingGroupScheduler()['AutoScalingGroupName'], + ], + 'deploymentStyle' => [ + 'deploymentType' => 'IN_PLACE', + 'deploymentOption' => 'WITHOUT_TRAFFIC_CONTROL', + ], + ], + ]; + } } diff --git a/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php b/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php index b739b65..a87b2c3 100644 --- a/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php +++ b/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php @@ -8,6 +8,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; +use Codinglabs\Yolo\Enums\DeploymentGroups; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; @@ -18,34 +19,34 @@ class SyncCodeDeployWebDeploymentGroupStep implements Step public function __invoke(array $options): StepResult { try { - AwsResources::webDeploymentGroup(); - return StepResult::SYNCED; + $deploymentGroup = AwsResources::webDeploymentGroup(); + + $differences = Helpers::payloadHasDifferences( + expected: $this->payload(), + actual: static::normaliseDeploymentGroupForComparison($deploymentGroup) + ); + + if (! Arr::get($options, 'dry-run')) { + // always sync tags as they are not compared in the payload + static::applyTagsToDeploymentGroup($deploymentGroup); + + if ($differences) { + Aws::codeDeploy()->updateDeploymentGroup([ + 'currentDeploymentGroupName' => $deploymentGroup['deploymentGroupName'], + ...$this->payload(), + ]); + + return StepResult::SYNCED; + } + } + + return $differences + ? StepResult::OUT_OF_SYNC + : StepResult::IN_SYNC; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { - Aws::codeDeploy()->createDeploymentGroup([ - ...static::deploymentGroupPayload(), - ...[ - 'deploymentGroupName' => Helpers::keyedResourceName('web'), - 'deploymentConfigName' => 'OneThirdAtATime', - 'autoScalingGroups' => [ - AwsResources::autoScalingGroupWeb()['AutoScalingGroupName'], - ], - 'deploymentStyle' => [ - 'deploymentType' => 'IN_PLACE', - 'deploymentOption' => 'WITH_TRAFFIC_CONTROL', - ], - 'loadBalancerInfo' => [ - 'targetGroupInfoList' => [ - [ - 'name' => AwsResources::targetGroup()['TargetGroupName'], - ], - ], - ], - ], - ...Aws::tags([ - 'Name' => Helpers::keyedResourceName('scheduler'), - ]), - ]); + Aws::codeDeploy()->createDeploymentGroup($this->payload()); + static::applyTagsToDeploymentGroup(AwsResources::webDeploymentGroup()); return StepResult::CREATED; } @@ -53,4 +54,29 @@ public function __invoke(array $options): StepResult return StepResult::WOULD_CREATE; } } + + protected function payload(): array + { + return [ + ...static::deploymentGroupPayload(), + ...[ + 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::WEB), + 'deploymentConfigName' => 'OneThirdAtATime', + 'autoScalingGroups' => [ + AwsResources::autoScalingGroupWeb()['AutoScalingGroupName'], + ], + 'deploymentStyle' => [ + 'deploymentType' => 'IN_PLACE', + 'deploymentOption' => 'WITH_TRAFFIC_CONTROL', + ], + 'loadBalancerInfo' => [ + 'targetGroupInfoList' => [ + [ + 'name' => AwsResources::targetGroup()['TargetGroupName'], + ], + ], + ], + ], + ]; + } } diff --git a/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php b/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php index a3d3352..e9b7403 100644 --- a/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php +++ b/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php @@ -7,6 +7,7 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Contracts\Step; use Illuminate\Filesystem\Filesystem; +use Codinglabs\Yolo\Enums\DeploymentGroups; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; class CreateCodeDeployDeploymentsStep implements Step @@ -29,7 +30,7 @@ protected function createSchedulerServerDeployment(string $appVersion): void Aws::codeDeploy()->createDeployment([ ...static::deploymentPayload($appVersion), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName('scheduler'), + 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::SCHEDULER), ], ]); } @@ -39,7 +40,7 @@ protected function createQueueServerDeployment(string $appVersion): void Aws::codeDeploy()->createDeployment([ ...static::deploymentPayload($appVersion), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName('queue'), + 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::QUEUE), ], ]); } @@ -49,7 +50,7 @@ protected function createWebServerDeployment(string $appVersion): void Aws::codeDeploy()->createDeployment([ ...static::deploymentPayload($appVersion), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName('web'), + 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::WEB), ], ]); } diff --git a/src/Steps/Deploy/UpdateCodeDeployDeploymentGroupStep.php b/src/Steps/Deploy/UpdateCodeDeployDeploymentGroupStep.php index 9c83c20..0bc528f 100644 --- a/src/Steps/Deploy/UpdateCodeDeployDeploymentGroupStep.php +++ b/src/Steps/Deploy/UpdateCodeDeployDeploymentGroupStep.php @@ -6,6 +6,7 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Contracts\Step; +use Codinglabs\Yolo\Enums\DeploymentGroups; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; class UpdateCodeDeployDeploymentGroupStep implements Step @@ -16,7 +17,7 @@ public function __invoke(): void { Aws::codeDeploy()->updateDeploymentGroup([ 'applicationName' => static::applicationName(), - 'currentDeploymentGroupName' => Helpers::keyedResourceName('web'), + 'currentDeploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::WEB), 'autoScalingGroups' => [ Manifest::get('aws.autoscaling.web'), ], @@ -24,7 +25,7 @@ public function __invoke(): void Aws::codeDeploy()->updateDeploymentGroup([ 'applicationName' => static::applicationName(), - 'currentDeploymentGroupName' => Helpers::keyedResourceName('queue'), + 'currentDeploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::QUEUE), 'autoScalingGroups' => [ Manifest::get('aws.autoscaling.queue'), ], @@ -32,7 +33,7 @@ public function __invoke(): void Aws::codeDeploy()->updateDeploymentGroup([ 'applicationName' => static::applicationName(), - 'currentDeploymentGroupName' => Helpers::keyedResourceName('scheduler'), + 'currentDeploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::SCHEDULER), 'autoScalingGroups' => [ Manifest::get('aws.autoscaling.scheduler'), ], diff --git a/src/Steps/Start/RestartServicesStep.php b/src/Steps/Start/RestartServicesStep.php index 66747ae..f4e9ca9 100644 --- a/src/Steps/Start/RestartServicesStep.php +++ b/src/Steps/Start/RestartServicesStep.php @@ -13,8 +13,8 @@ public function __invoke(): array 'supervisorctl reread', 'supervisorctl update', 'supervisorctl start all', // note we already stopped supervisor workers in beforeInstall hook, so we "start" rather than "restart" here. - 'systemctl restart php8.3-fpm', - 'systemctl restart nginx', + 'systemctl reload php8.3-fpm', + 'systemctl reload nginx', ]; } } diff --git a/src/Steps/Start/SyncPhpConfigurationStep.php b/src/Steps/Start/SyncPhpConfigurationStep.php index ba43f87..ee56310 100644 --- a/src/Steps/Start/SyncPhpConfigurationStep.php +++ b/src/Steps/Start/SyncPhpConfigurationStep.php @@ -13,14 +13,14 @@ public function __invoke(): StepResult { // .ini for PHP CLI file_put_contents( - "/etc/php/8.3/mods-available/{NAME}_cli.ini", - file_get_contents(Paths::stubs('php/{NAME}_cli.ini.stub')) + "/etc/php/8.3/mods-available/yolo_cli.ini", + file_get_contents(Paths::stubs('php/cli.ini.stub')) ); // .ini for PHP FPM file_put_contents( - "/etc/php/8.3/mods-available/{NAME}_fpm.ini", - file_get_contents(Paths::stubs('php/{NAME}_fpm.ini.stub')) + "/etc/php/8.3/mods-available/yolo_fpm.ini", + file_get_contents(Paths::stubs('php/fpm.ini.stub')) ); // configuration for PHP-FPM pool @@ -32,8 +32,8 @@ public function __invoke(): StepResult // enable mods (Process::fromShellCommandline( command: <<<'sh' - phpenmod -s cli {NAME}_cli - phpenmod -s fpm {NAME}_fpm + phpenmod -s cli yolo_cli + phpenmod -s fpm yolo_fpm sh ))->mustRun(); From 1614e6e66ee8efa4c3cd46f6cf32294b62ee6144 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 29 Jan 2025 14:00:44 +1000 Subject: [PATCH 07/40] merge some safe changes from main --- README.md | 38 ++++++++++++--- src/Commands/DeployCommand.php | 1 + src/Commands/InitCommand.php | 2 + src/Commands/StartCommand.php | 1 + src/Commands/StopCommand.php | 1 + src/Concerns/RunsSteppedCommands.php | 11 ++++- src/Concerns/UsesCodeDeploy.php | 48 +++++++++++++++++-- src/Concerns/UsesEc2.php | 5 +- src/Concerns/UsesElasticTranscoder.php | 8 +++- src/Concerns/UsesRoute53.php | 4 +- src/Enums/StepResult.php | 10 ++-- .../SyncCodeDeployWebDeploymentGroupStep.php | 9 +++- stubs/php/cli.ini.stub | 2 +- stubs/php/fpm.ini.stub | 2 +- stubs/yolo.yml.stub | 1 + yolo | 11 ++--- 16 files changed, 124 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index b2a667d..2f0c7d5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ___ ## Disclaimer YOLO is designed for PHP developers who want to manage AWS using an infrastructure-as-code approach, using plain-old PHP -rather than CloudFormation / Terraform / K8s / Elastic Beanstalk / . +rather than CloudFormation / Terraform / K8s / Elastic Beanstalk / . While YOLO has been battle-tested on apps serving millions of requests per day, it is not supposed to be a set-and-forget solution for busy apps, but rather allows you to grow and adapt your infrastructure as requirements @@ -19,7 +19,7 @@ change over time. Within the Laravel ecosystem there are several high quality commercial alternatives like Forge, Vapor and Cloud, which require less working knowledge of AWS. -It goes without saying, but YOLO at your own risk. +It goes without saying, but use YOLO at your own risk. ## Prerequisites @@ -67,15 +67,13 @@ To get up and running, you'll need to complete the following steps: After the initial install, you can simply add `yolo deploy ` to your CI pipeline to deploy your app. -### `yolo install` +### `yolo init` -The install command is the first command to run after installing YOLO. It does the following things: +The init command is the first command to run after installing YOLO. It does the following things: - initialises the yolo.yml file in the app with a boilerplate production environment - adds some entries to `.gitignore` -- provisions various resources on AWS -- builds and registers an Amazon Machine Image for EC2s -- +- prompts for a few bits of information to setup the manifest file After initialising, you can customise the `yolo.yml` manifest file to suit your app's requirements. @@ -140,6 +138,8 @@ environments: instance-type: t3.small instance-profile: octane: true + codedeploy: + strategy: without-load-balancing|with-load-balancing build: - composer install --no-cache --no-interaction --optimize-autoloader --no-progress --classmap-authoritative --no-dev @@ -159,6 +159,30 @@ environments: - php artisan optimize ``` +## Development + +To debug or add features to YOLO, it is recommended to symlink to the local repository. + +Add this to composer.json with the path to the local repository: + +```json + // ... + +"repositories": [ +{ +"type": "path", +"url": "/Users/username/code/yolo" +} +], +``` + +To call yolo from the app you are debugging, you'll need to tell yolo the path to the app. Set the `YOLO_BASE_PATH` +environment to the root of the app as follows: + +```bash +YOLO_BASE_PATH=$(pwd) yolo +``` + ## Credits - [Steve Thomas](https://github.com/stevethomas) diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index 172a694..be83d03 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -40,6 +40,7 @@ protected function configure(): void ->setName('deploy') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('app-version', null, InputArgument::OPTIONAL, 'The app version to tag the build with') + ->addOption('no-progress', null, null, 'Hide the progress output') ->setDescription('Deploy a build of the application to AWS'); } diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 77a26a8..55351e0 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -42,10 +42,12 @@ protected function initialiseManifest(): void str_replace( search: [ '{NAME}', + '{AWS_ACCOUNT_ID}', '{AWS_REGION}', ], replace: [ text('What is the name of this app?', placeholder: 'eg. codinglabs'), + text('What is the account ID of the AWS account you want to deploy to?'), text('Which AWS region do you want to deploy to?', default: env('AWS_DEFAULT_REGION', 'ap-southeast-2')), ], subject: file_get_contents(Paths::stubs('yolo.yml.stub')) diff --git a/src/Commands/StartCommand.php b/src/Commands/StartCommand.php index c61f5d5..ba0d6e5 100644 --- a/src/Commands/StartCommand.php +++ b/src/Commands/StartCommand.php @@ -38,6 +38,7 @@ protected function configure(): void ->setName('start') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('app-version', null, InputArgument::OPTIONAL, 'The app version to tag the build with') + ->addOption('no-progress', null, null, 'Hide the progress output') ->setDescription('Prepare the server for a new deployment'); } diff --git a/src/Commands/StopCommand.php b/src/Commands/StopCommand.php index e9336b4..7466ac4 100644 --- a/src/Commands/StopCommand.php +++ b/src/Commands/StopCommand.php @@ -23,6 +23,7 @@ protected function configure(): void $this ->setName('stop') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') + ->addOption('no-progress', null, null, 'Hide the progress output') ->setDescription('Stop work before deployment'); } diff --git a/src/Concerns/RunsSteppedCommands.php b/src/Concerns/RunsSteppedCommands.php index 36ec576..9bf2e49 100644 --- a/src/Concerns/RunsSteppedCommands.php +++ b/src/Concerns/RunsSteppedCommands.php @@ -62,13 +62,22 @@ protected function handleSteps(string $environment): int $i + 1, static::normaliseStep($step, pad: true, bold: true, arrow: true), match ($status) { + // green StepResult::CREATED => 'CREATED', - StepResult::SYNCED => 'SYNCED', StepResult::SUCCESS => 'SUCCESS', + StepResult::IN_SYNC => 'IN SYNC', + + // cyan + StepResult::SYNCED => 'SYNCED', + + // yellow StepResult::SKIPPED => 'SKIPPED', StepResult::CONDITIONAL => 'CONDITIONAL', StepResult::WOULD_CREATE => 'WOULD CREATE', StepResult::WOULD_SYNC => 'WOULD SYNC', + StepResult::OUT_OF_SYNC => 'OUT OF SYNC', + + // red StepResult::TIMEOUT => 'TIMEOUT', default => is_string($status) ? $status diff --git a/src/Concerns/UsesCodeDeploy.php b/src/Concerns/UsesCodeDeploy.php index 486f5b5..9c2c8d5 100644 --- a/src/Concerns/UsesCodeDeploy.php +++ b/src/Concerns/UsesCodeDeploy.php @@ -4,7 +4,9 @@ use Codinglabs\Yolo\Aws; use Codinglabs\Yolo\Helpers; +use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; +use Codinglabs\Yolo\Enums\DeploymentGroups; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; trait UsesCodeDeploy @@ -63,7 +65,7 @@ public static function webDeploymentGroup(): array return static::$webDeploymentGroup; } - return static::deploymentGroup(Helpers::keyedResourceName('web')); + return static::deploymentGroup(Helpers::keyedResourceName(DeploymentGroups::WEB)); } /** @throws ResourceDoesNotExistException */ @@ -73,7 +75,7 @@ public static function queueDeploymentGroup(): array return static::$queueDeploymentGroup; } - return static::deploymentGroup(Helpers::keyedResourceName('queue')); + return static::deploymentGroup(Helpers::keyedResourceName(DeploymentGroups::QUEUE)); } /** @throws ResourceDoesNotExistException */ @@ -83,7 +85,7 @@ public static function schedulerDeploymentGroup(): array return static::$schedulerDeploymentGroup; } - return static::deploymentGroup(Helpers::keyedResourceName('scheduler')); + return static::deploymentGroup(Helpers::keyedResourceName(DeploymentGroups::SCHEDULER)); } protected static function deploymentGroup(string $name): array @@ -112,4 +114,44 @@ public static function deploymentGroupPayload(): array 'serviceRoleArn' => sprintf('arn:aws:iam::%s:role/AWSCodeDeployServiceRole', Aws::accountId()), ]; } + + public static function arnForApplication(string $application): string + { + return sprintf( + 'arn:aws:codedeploy:%s:%s:application:%s', + Manifest::get('aws.region'), + Aws::accountId(), + $application + ); + } + + public static function arnForDeploymentGroup(array $deploymentGroup): string + { + return sprintf( + 'arn:aws:codedeploy:%s:%s:deploymentgroup:%s/%s', + Manifest::get('aws.region'), + Aws::accountId(), + $deploymentGroup['applicationName'], + $deploymentGroup['deploymentGroupName'], + ); + } + + protected function applyTagsToDeploymentGroup(array $deploymentGroup): void + { + Aws::codeDeploy()->tagResource([ + 'ResourceArn' => static::arnForDeploymentGroup($deploymentGroup), + ...Aws::tags([ + 'Name' => Helpers::keyedResourceName($deploymentGroup['deploymentGroupName']), + ]), + ]); + } + + public static function normaliseDeploymentGroupForComparison(array $deploymentGroup): array + { + return [ + ...$deploymentGroup, + // flatten autoScalingGroups value as AWS is injecting a name and hook key + 'autoScalingGroups' => [$deploymentGroup['autoScalingGroups'][0]['name'] ?? null], + ]; + } } diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index 4389aa3..ea0e385 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -66,7 +66,6 @@ public static function ec2IpByName(string $name, bool $firstOnly = true): string ->toArray(); } - public static function securityGroups($refresh = false): array { if (! $refresh && isset(static::$securityGroups)) { @@ -155,7 +154,9 @@ public static function launchTemplate($refresh = false): array ])['LaunchTemplates']; if (count($launchTemplates) === 0) { - throw new ResourceDoesNotExistException(sprintf("Could not find launch template %s. Run 'yolo compute:sync %s' to fix.", Helpers::keyedResourceName(), Helpers::environment())); + ResourceDoesNotExistException::make(sprintf("Could not find launch template %s", Helpers::keyedResourceName())) + ->suggest('compute:sync') + ->throw(); } static::$launchTemplate = $launchTemplates[0]; diff --git a/src/Concerns/UsesElasticTranscoder.php b/src/Concerns/UsesElasticTranscoder.php index 0b53f7f..fe786bd 100644 --- a/src/Concerns/UsesElasticTranscoder.php +++ b/src/Concerns/UsesElasticTranscoder.php @@ -26,7 +26,9 @@ public static function elasticTranscoderPipeline(): array } } - throw new ResourceDoesNotExistException("Could not find Elastic Transcoder pipeline with name $name"); + ResourceDoesNotExistException::make("Could not find Elastic Transcoder pipeline with name $name") + ->suggest('sync:compute') + ->throw(); } public static function elasticTranscoderPreset(): array @@ -44,6 +46,8 @@ public static function elasticTranscoderPreset(): array } } - throw new ResourceDoesNotExistException("Could not find Elastic Transcoder preset with name $name"); + ResourceDoesNotExistException::make("Could not find Elastic Transcoder preset with name $name") + ->suggest('sync:compute') + ->throw(); } } diff --git a/src/Concerns/UsesRoute53.php b/src/Concerns/UsesRoute53.php index ef5855a..431cf13 100644 --- a/src/Concerns/UsesRoute53.php +++ b/src/Concerns/UsesRoute53.php @@ -17,6 +17,8 @@ public static function hostedZone(string $domain): array } } - throw new ResourceDoesNotExistException("Could not find Hosted Zone for domain $domain"); + ResourceDoesNotExistException::make("Could not find Hosted Zone for domain $domain") + ->suggest('sync:compute') + ->throw(); } } diff --git a/src/Enums/StepResult.php b/src/Enums/StepResult.php index a2b5636..e6f974c 100644 --- a/src/Enums/StepResult.php +++ b/src/Enums/StepResult.php @@ -4,12 +4,16 @@ enum StepResult { - case SUCCESS; case CREATED; + case SUCCESS; + case WOULD_CREATE; + case SYNCED; + case IN_SYNC; + case OUT_OF_SYNC; + case WOULD_SYNC; + case CONDITIONAL; case TIMEOUT; case SKIPPED; - case WOULD_CREATE; - case WOULD_SYNC; } diff --git a/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php b/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php index a87b2c3..3b81bec 100644 --- a/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php +++ b/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php @@ -5,6 +5,7 @@ use Codinglabs\Yolo\Aws; use Illuminate\Support\Arr; use Codinglabs\Yolo\Helpers; +use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; @@ -61,13 +62,17 @@ protected function payload(): array ...static::deploymentGroupPayload(), ...[ 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::WEB), - 'deploymentConfigName' => 'OneThirdAtATime', + 'deploymentConfigName' => Manifest::get('aws.codedeploy.with-load-balancing', false) + ? 'OneThirdAtATime' + : 'CodeDeployDefault.AllAtOnce', 'autoScalingGroups' => [ AwsResources::autoScalingGroupWeb()['AutoScalingGroupName'], ], 'deploymentStyle' => [ 'deploymentType' => 'IN_PLACE', - 'deploymentOption' => 'WITH_TRAFFIC_CONTROL', + 'deploymentOption' => Manifest::get('aws.codedeploy.with-load-balancing', false) + ? 'WITH_TRAFFIC_CONTROL' + : 'WITHOUT_TRAFFIC_CONTROL', ], 'loadBalancerInfo' => [ 'targetGroupInfoList' => [ diff --git a/stubs/php/cli.ini.stub b/stubs/php/cli.ini.stub index 7c72323..3a40c31 100644 --- a/stubs/php/cli.ini.stub +++ b/stubs/php/cli.ini.stub @@ -1,3 +1,3 @@ -; Environment configuration {NAME} CLI +; Environment configuration for CLI mysqli.reconnect = On date.timezone = UTC diff --git a/stubs/php/fpm.ini.stub b/stubs/php/fpm.ini.stub index ff3dfd0..f308154 100644 --- a/stubs/php/fpm.ini.stub +++ b/stubs/php/fpm.ini.stub @@ -1,4 +1,4 @@ -; Environment configuration for {NAME} FPM +; Environment configuration for FPM expose_php = Off session.gc_probability = '1' session.gc_divisor = '100' diff --git a/stubs/yolo.yml.stub b/stubs/yolo.yml.stub index d34b6b1..51eed62 100644 --- a/stubs/yolo.yml.stub +++ b/stubs/yolo.yml.stub @@ -3,6 +3,7 @@ name: {NAME} environments: production: aws: + account-id: {AWS_ACCOUNT_ID} region: {AWS_REGION} cloudfront: // todo use magic [account level] autoscaling: diff --git a/yolo b/yolo index 217f5ff..1ec1d88 100755 --- a/yolo +++ b/yolo @@ -4,15 +4,12 @@ use Dotenv\Dotenv; use Codinglabs\Yolo\Yolo; -if (file_exists(__DIR__ . '/../../autoload.php')) { - require __DIR__ . '/../../autoload.php'; -} else { - require __DIR__ . '/vendor/autoload.php'; -} +file_exists(__DIR__ . '/../../autoload.php') + ? require __DIR__ . '/../../autoload.php' + : require __DIR__ . '/vendor/autoload.php'; // path to the app root -//const BASE_PATH = __DIR__ . '/../../'; -const BASE_PATH = '/Users/stevethomas/code/codinglabs'; // todo: revert this +define("BASE_PATH", $_ENV['YOLO_BASE_PATH'] ?? __DIR__ . '/../../../'); Dotenv::createImmutable(BASE_PATH)->load(); From d8e19e1ea06ecca9aca062c33ef12074620a2503 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 29 Jan 2025 14:26:18 +1000 Subject: [PATCH 08/40] consolidate all web/queue/scheduler references to use ServerGroup enum --- src/Commands/CommandCommand.php | 3 ++- src/Concerns/RegistersAws.php | 13 +++++++------ src/Concerns/UsesCodeDeploy.php | 8 ++++---- src/Enums/{DeploymentGroups.php => ServerGroup.php} | 2 +- src/Steps/Ami/CreateAutoScalingQueueGroupStep.php | 3 ++- .../Ami/CreateAutoScalingSchedulerGroupStep.php | 3 ++- src/Steps/Ami/CreateAutoScalingWebGroupStep.php | 3 ++- .../Ci/SyncCodeDeployQueueDeploymentGroupStep.php | 4 ++-- .../SyncCodeDeploySchedulerDeploymentGroupStep.php | 4 ++-- .../Ci/SyncCodeDeployWebDeploymentGroupStep.php | 4 ++-- .../Deploy/CreateCodeDeployDeploymentsStep.php | 8 ++++---- .../Deploy/UpdateCodeDeployDeploymentGroupStep.php | 8 ++++---- 12 files changed, 34 insertions(+), 29 deletions(-) rename src/Enums/{DeploymentGroups.php => ServerGroup.php} (80%) diff --git a/src/Commands/CommandCommand.php b/src/Commands/CommandCommand.php index 3009436..f27f510 100644 --- a/src/Commands/CommandCommand.php +++ b/src/Commands/CommandCommand.php @@ -6,6 +6,7 @@ use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Concerns\UsesEc2; use Symfony\Component\Process\Process; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\FormatsSshCommands; use Symfony\Component\Console\Input\InputArgument; use function Laravel\Prompts\info; @@ -25,7 +26,7 @@ protected function configure(): void ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('command', null, InputArgument::OPTIONAL, 'The command to run') ->addOption('ssh-key', null, InputArgument::OPTIONAL, 'The SSH key to use') - ->addOption('group', null, InputArgument::OPTIONAL, 'The server group to run the command in', default: 'scheduler') + ->addOption('group', null, InputArgument::OPTIONAL, 'The server group to run the command in', default: ServerGroup::SCHEDULER->value) ->setDescription('Run a command in the given environment'); } diff --git a/src/Concerns/RegistersAws.php b/src/Concerns/RegistersAws.php index 72ee196..da4359a 100644 --- a/src/Concerns/RegistersAws.php +++ b/src/Concerns/RegistersAws.php @@ -18,6 +18,7 @@ use Aws\CloudWatch\CloudWatchClient; use Aws\CodeDeploy\CodeDeployClient; use Aws\AutoScaling\AutoScalingClient; +use Codinglabs\Yolo\Enums\ServerGroup; use Aws\Credentials\CredentialProvider; use GuzzleHttp\Exception\ConnectException; use Aws\ElasticTranscoder\ElasticTranscoderClient; @@ -83,7 +84,7 @@ protected static function detectCiEnvironment(): bool return env('CI', false) === true; } - protected static function detectAwsEnvironment(string $name = null): bool + protected static function detectAwsEnvironment(ServerGroup $serverGroup = null): bool { if (static::detectLocalEnvironment() || static::detectCiEnvironment()) { // skip if we are local or in continuous integration @@ -95,7 +96,7 @@ protected static function detectAwsEnvironment(string $name = null): bool ->get('http://169.254.169.254/latest/meta-data/instance-id') ->getBody(); - if ($name) { + if ($serverGroup) { $awsResult = Aws::ec2()->describeTags([ 'Filters' => [ [ @@ -109,7 +110,7 @@ protected static function detectAwsEnvironment(string $name = null): bool ] ]); - return ! empty($awsResult['Tags']) && $awsResult['Tags'][0]['Value'] === $name; + return ! empty($awsResult['Tags']) && $awsResult['Tags'][0]['Value'] === $serverGroup->value; } return true; @@ -121,16 +122,16 @@ protected static function detectAwsEnvironment(string $name = null): bool protected static function detectAwsWebEnvironment(): bool { - return static::detectAwsEnvironment('web'); + return static::detectAwsEnvironment(ServerGroup::WEB); } protected static function detectAwsQueueEnvironment(): bool { - return static::detectAwsEnvironment('queue'); + return static::detectAwsEnvironment(ServerGroup::QUEUE); } protected static function detectAwsSchedulerEnvironment(): bool { - return static::detectAwsEnvironment('scheduler'); + return static::detectAwsEnvironment(ServerGroup::QUEUE); } } diff --git a/src/Concerns/UsesCodeDeploy.php b/src/Concerns/UsesCodeDeploy.php index 9c2c8d5..0ed2184 100644 --- a/src/Concerns/UsesCodeDeploy.php +++ b/src/Concerns/UsesCodeDeploy.php @@ -6,7 +6,7 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; -use Codinglabs\Yolo\Enums\DeploymentGroups; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; trait UsesCodeDeploy @@ -65,7 +65,7 @@ public static function webDeploymentGroup(): array return static::$webDeploymentGroup; } - return static::deploymentGroup(Helpers::keyedResourceName(DeploymentGroups::WEB)); + return static::deploymentGroup(Helpers::keyedResourceName(ServerGroup::WEB)); } /** @throws ResourceDoesNotExistException */ @@ -75,7 +75,7 @@ public static function queueDeploymentGroup(): array return static::$queueDeploymentGroup; } - return static::deploymentGroup(Helpers::keyedResourceName(DeploymentGroups::QUEUE)); + return static::deploymentGroup(Helpers::keyedResourceName(ServerGroup::QUEUE)); } /** @throws ResourceDoesNotExistException */ @@ -85,7 +85,7 @@ public static function schedulerDeploymentGroup(): array return static::$schedulerDeploymentGroup; } - return static::deploymentGroup(Helpers::keyedResourceName(DeploymentGroups::SCHEDULER)); + return static::deploymentGroup(Helpers::keyedResourceName(ServerGroup::SCHEDULER)); } protected static function deploymentGroup(string $name): array diff --git a/src/Enums/DeploymentGroups.php b/src/Enums/ServerGroup.php similarity index 80% rename from src/Enums/DeploymentGroups.php rename to src/Enums/ServerGroup.php index 3833f93..a9a0de9 100644 --- a/src/Enums/DeploymentGroups.php +++ b/src/Enums/ServerGroup.php @@ -2,7 +2,7 @@ namespace Codinglabs\Yolo\Enums; -enum DeploymentGroups: string +enum ServerGroup: string { case WEB = 'web'; case QUEUE = 'queue'; diff --git a/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php b/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php index de58e3b..7b6be82 100644 --- a/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php @@ -10,6 +10,7 @@ use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Concerns\UsesEc2; use Codinglabs\Yolo\Enums\StepResult; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\UsesAutoscaling; class CreateAutoScalingQueueGroupStep implements Step @@ -33,7 +34,7 @@ public function __invoke(array $options): StepResult [ 'Key' => 'Name', 'PropagateAtLaunch' => true, - 'Value' => 'queue', + 'Value' => ServerGroup::QUEUE->value, ], ], ], diff --git a/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php b/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php index 7964857..c499df9 100644 --- a/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php @@ -10,6 +10,7 @@ use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Concerns\UsesEc2; use Codinglabs\Yolo\Enums\StepResult; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\UsesAutoscaling; class CreateAutoScalingSchedulerGroupStep implements Step @@ -33,7 +34,7 @@ public function __invoke(array $options): StepResult [ 'Key' => 'Name', 'PropagateAtLaunch' => true, - 'Value' => 'scheduler', + 'Value' => ServerGroup::SCHEDULER->value, ], ], ], diff --git a/src/Steps/Ami/CreateAutoScalingWebGroupStep.php b/src/Steps/Ami/CreateAutoScalingWebGroupStep.php index 2891c21..4ca2e0e 100644 --- a/src/Steps/Ami/CreateAutoScalingWebGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingWebGroupStep.php @@ -9,6 +9,7 @@ use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\UsesAutoscaling; class CreateAutoScalingWebGroupStep implements Step @@ -31,7 +32,7 @@ public function __invoke(array $options): StepResult [ 'Key' => 'Name', 'PropagateAtLaunch' => true, - 'Value' => 'web', + 'Value' => ServerGroup::WEB->value, ], ], ], diff --git a/src/Steps/Ci/SyncCodeDeployQueueDeploymentGroupStep.php b/src/Steps/Ci/SyncCodeDeployQueueDeploymentGroupStep.php index f64d00f..8f619e1 100644 --- a/src/Steps/Ci/SyncCodeDeployQueueDeploymentGroupStep.php +++ b/src/Steps/Ci/SyncCodeDeployQueueDeploymentGroupStep.php @@ -8,7 +8,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Enums\DeploymentGroups; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; @@ -60,7 +60,7 @@ protected function payload(): array return [ ...static::deploymentGroupPayload(), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::QUEUE), + 'deploymentGroupName' => Helpers::keyedResourceName(ServerGroup::QUEUE), 'deploymentConfigName' => 'CodeDeployDefault.AllAtOnce', 'autoScalingGroups' => [ AwsResources::autoScalingGroupQueue()['AutoScalingGroupName'], diff --git a/src/Steps/Ci/SyncCodeDeploySchedulerDeploymentGroupStep.php b/src/Steps/Ci/SyncCodeDeploySchedulerDeploymentGroupStep.php index 1810ded..210387f 100644 --- a/src/Steps/Ci/SyncCodeDeploySchedulerDeploymentGroupStep.php +++ b/src/Steps/Ci/SyncCodeDeploySchedulerDeploymentGroupStep.php @@ -8,7 +8,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Enums\DeploymentGroups; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; @@ -60,7 +60,7 @@ protected function payload(): array return [ ...static::deploymentGroupPayload(), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::SCHEDULER), + 'deploymentGroupName' => Helpers::keyedResourceName(ServerGroup::SCHEDULER), 'deploymentConfigName' => 'CodeDeployDefault.AllAtOnce', 'autoScalingGroups' => [ AwsResources::autoScalingGroupScheduler()['AutoScalingGroupName'], diff --git a/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php b/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php index 3b81bec..b118d33 100644 --- a/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php +++ b/src/Steps/Ci/SyncCodeDeployWebDeploymentGroupStep.php @@ -9,7 +9,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Enums\DeploymentGroups; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; @@ -61,7 +61,7 @@ protected function payload(): array return [ ...static::deploymentGroupPayload(), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::WEB), + 'deploymentGroupName' => Helpers::keyedResourceName(ServerGroup::WEB), 'deploymentConfigName' => Manifest::get('aws.codedeploy.with-load-balancing', false) ? 'OneThirdAtATime' : 'CodeDeployDefault.AllAtOnce', diff --git a/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php b/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php index e9b7403..d02101d 100644 --- a/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php +++ b/src/Steps/Deploy/CreateCodeDeployDeploymentsStep.php @@ -7,7 +7,7 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Contracts\Step; use Illuminate\Filesystem\Filesystem; -use Codinglabs\Yolo\Enums\DeploymentGroups; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; class CreateCodeDeployDeploymentsStep implements Step @@ -30,7 +30,7 @@ protected function createSchedulerServerDeployment(string $appVersion): void Aws::codeDeploy()->createDeployment([ ...static::deploymentPayload($appVersion), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::SCHEDULER), + 'deploymentGroupName' => Helpers::keyedResourceName(ServerGroup::SCHEDULER), ], ]); } @@ -40,7 +40,7 @@ protected function createQueueServerDeployment(string $appVersion): void Aws::codeDeploy()->createDeployment([ ...static::deploymentPayload($appVersion), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::QUEUE), + 'deploymentGroupName' => Helpers::keyedResourceName(ServerGroup::QUEUE), ], ]); } @@ -50,7 +50,7 @@ protected function createWebServerDeployment(string $appVersion): void Aws::codeDeploy()->createDeployment([ ...static::deploymentPayload($appVersion), ...[ - 'deploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::WEB), + 'deploymentGroupName' => Helpers::keyedResourceName(ServerGroup::WEB), ], ]); } diff --git a/src/Steps/Deploy/UpdateCodeDeployDeploymentGroupStep.php b/src/Steps/Deploy/UpdateCodeDeployDeploymentGroupStep.php index 0bc528f..f89a122 100644 --- a/src/Steps/Deploy/UpdateCodeDeployDeploymentGroupStep.php +++ b/src/Steps/Deploy/UpdateCodeDeployDeploymentGroupStep.php @@ -6,7 +6,7 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Contracts\Step; -use Codinglabs\Yolo\Enums\DeploymentGroups; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Concerns\UsesCodeDeploy; class UpdateCodeDeployDeploymentGroupStep implements Step @@ -17,7 +17,7 @@ public function __invoke(): void { Aws::codeDeploy()->updateDeploymentGroup([ 'applicationName' => static::applicationName(), - 'currentDeploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::WEB), + 'currentDeploymentGroupName' => Helpers::keyedResourceName(ServerGroup::WEB), 'autoScalingGroups' => [ Manifest::get('aws.autoscaling.web'), ], @@ -25,7 +25,7 @@ public function __invoke(): void Aws::codeDeploy()->updateDeploymentGroup([ 'applicationName' => static::applicationName(), - 'currentDeploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::QUEUE), + 'currentDeploymentGroupName' => Helpers::keyedResourceName(ServerGroup::QUEUE), 'autoScalingGroups' => [ Manifest::get('aws.autoscaling.queue'), ], @@ -33,7 +33,7 @@ public function __invoke(): void Aws::codeDeploy()->updateDeploymentGroup([ 'applicationName' => static::applicationName(), - 'currentDeploymentGroupName' => Helpers::keyedResourceName(DeploymentGroups::SCHEDULER), + 'currentDeploymentGroupName' => Helpers::keyedResourceName(ServerGroup::SCHEDULER), 'autoScalingGroups' => [ Manifest::get('aws.autoscaling.scheduler'), ], From 47ce03a238a3475c2c6559c13fb03ba7175eecba Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 29 Jan 2025 15:57:04 +1000 Subject: [PATCH 09/40] merge several fixes from main --- src/Commands/StartCommand.php | 6 +++-- src/Commands/SyncCommand.php | 12 ++++++--- .../SyncMultitenancyTenantsCommand.php | 2 ++ .../ChecksIfCommandsShouldBeRunning.php | 4 +-- src/Contracts/ExecutesDomainStep.php | 5 ---- src/Contracts/ExecutesStandaloneStep.php | 7 +++++ src/Helpers.php | 6 +++-- src/Steps/Domain/SyncQueueAlarmStep.php | 8 +++--- src/Steps/Domain/SyncQueueStep.php | 13 +++++---- .../Ensures/EnsureHostedZonesExistStep.php | 4 +-- src/Steps/Landlord/SyncQueueAlarmStep.php | 5 ++-- src/Steps/Landlord/SyncQueueStep.php | 7 +++-- .../Start/SyncLandlordQueueWorkerStep.php | 7 ++--- src/Steps/Start/SyncTenantQueueWorkerStep.php | 7 ++--- src/Steps/Start/WarmApplicationStep.php | 6 ++--- .../WarmMultitenantedApplicationStep.php | 27 +++++++++++++++++++ src/Steps/Tenant/SyncQueueAlarmStep.php | 5 ++-- src/Steps/Tenant/SyncQueueStep.php | 7 +++-- src/Yolo.php | 4 +-- 19 files changed, 98 insertions(+), 44 deletions(-) delete mode 100644 src/Contracts/ExecutesDomainStep.php create mode 100644 src/Contracts/ExecutesStandaloneStep.php create mode 100644 src/Steps/Start/WarmMultitenantedApplicationStep.php diff --git a/src/Commands/StartCommand.php b/src/Commands/StartCommand.php index ba0d6e5..087e574 100644 --- a/src/Commands/StartCommand.php +++ b/src/Commands/StartCommand.php @@ -21,14 +21,16 @@ class StartCommand extends Command implements RunsOnAws Steps\Start\SyncHousekeepingCronStep::class, // all Steps\Start\SyncSchedulerCronStep::class, // scheduler Steps\Start\SyncPulseWorkerStep::class, // all - Steps\Start\SyncLandlordQueueWorkerStep::class, // queue - Steps\Start\SyncTenantQueueWorkerStep::class, // queue + Steps\Start\SyncQueueWorkerStep::class, // queue + Steps\Start\SyncQueueLandlordWorkerStep::class, // queue + Steps\Start\SyncQueueTenantWorkerStep::class, // queue Steps\Start\SyncOctaneWorkerStep::class, // web Steps\Start\SyncMysqlBackupStep::class, // scheduler Steps\Start\SyncPhpConfigurationStep::class, // all Steps\Start\SyncNginxConfigurationStep::class, // web Steps\Start\RestartServicesStep::class, // all Steps\Start\WarmApplicationStep::class, // web + Steps\Start\WarmMultitenantedApplicationStep::class, // web Steps\Start\ConfigureLoadBalancingStep::class, // web ]; diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index 42e97c8..f33e870 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Commands; +use Codinglabs\Yolo\Manifest; use Symfony\Component\Console\Input\InputArgument; use function Laravel\Prompts\info; use function Laravel\Prompts\intro; @@ -27,9 +28,14 @@ public function handle(): void collect([ Commands\SyncNetworkCommand::class, Commands\SyncStorageCommand::class, - Commands\SyncDomainCommand::class, - Commands\SyncMultitenancyLandlordCommand::class, - Commands\SyncMultitenancyTenantsCommand::class, + ...Manifest::isMultitenanted() + ? [ + Commands\SyncMultitenancyLandlordCommand::class, + Commands\SyncMultitenancyTenantsCommand::class, + ] + : [ + Commands\SyncStandaloneCommand::class, + ], Commands\SyncComputeCommand::class, Commands\SyncCiCommand::class, ])->each(fn ($command) => (new $command())->execute(Helpers::app('input'), Helpers::app('output'))); diff --git a/src/Commands/SyncMultitenancyTenantsCommand.php b/src/Commands/SyncMultitenancyTenantsCommand.php index 2e07a02..e6ad906 100644 --- a/src/Commands/SyncMultitenancyTenantsCommand.php +++ b/src/Commands/SyncMultitenancyTenantsCommand.php @@ -9,7 +9,9 @@ class SyncMultitenancyTenantsCommand extends SteppedCommand { protected array $steps = [ Steps\Tenant\SyncHostedZoneStep::class, + Steps\Tenant\SyncRecordSetStep::class, Steps\Tenant\SyncSslCertificateStep::class, + Steps\Tenant\AttachSslCertificateToLoadBalancerListenerStep::class, Steps\Tenant\SyncQueueStep::class, Steps\Tenant\SyncQueueAlarmStep::class, ]; diff --git a/src/Concerns/ChecksIfCommandsShouldBeRunning.php b/src/Concerns/ChecksIfCommandsShouldBeRunning.php index 5c5d08c..7d55bce 100644 --- a/src/Concerns/ChecksIfCommandsShouldBeRunning.php +++ b/src/Concerns/ChecksIfCommandsShouldBeRunning.php @@ -9,15 +9,15 @@ use Codinglabs\Yolo\Contracts\RunsOnAws; use Codinglabs\Yolo\Contracts\RunsOnAwsWeb; use Codinglabs\Yolo\Contracts\RunsOnAwsQueue; -use Codinglabs\Yolo\Contracts\ExecutesDomainStep; use Codinglabs\Yolo\Contracts\RunsOnAwsScheduler; +use Codinglabs\Yolo\Contracts\ExecutesStandaloneStep; use Codinglabs\Yolo\Contracts\ExecutesMultitenancyStep; trait ChecksIfCommandsShouldBeRunning { public function shouldBeRunning(Command|Step $instance): bool { - if ($instance instanceof ExecutesDomainStep) { + if ($instance instanceof ExecutesStandaloneStep) { return ! Manifest::isMultitenanted(); } diff --git a/src/Contracts/ExecutesDomainStep.php b/src/Contracts/ExecutesDomainStep.php deleted file mode 100644 index a18d611..0000000 --- a/src/Contracts/ExecutesDomainStep.php +++ /dev/null @@ -1,5 +0,0 @@ - or yolo-production-- return $name ? sprintf("yolo$seperator%s$seperator%s$seperator%s", static::environment(), Manifest::name(), $name) : sprintf("yolo$seperator%s$seperator%s", static::environment(), Manifest::name()); } - // non-exclusive assets are shared across multiple yolo applications on the same AWS account + // non-exclusive resources are shared across multiple yolo applications on the same AWS account; + // e.g. yolo-production or yolo-production- return $name ? sprintf("yolo$seperator%s$seperator%s", static::environment(), $name) : sprintf("yolo$seperator%s", static::environment()); diff --git a/src/Steps/Domain/SyncQueueAlarmStep.php b/src/Steps/Domain/SyncQueueAlarmStep.php index 610baef..49d329e 100644 --- a/src/Steps/Domain/SyncQueueAlarmStep.php +++ b/src/Steps/Domain/SyncQueueAlarmStep.php @@ -1,20 +1,20 @@ createQueue([ - 'QueueName' => Helpers::keyedResourceName(), + 'QueueName' => $name, 'Attributes' => [ 'MessageRetentionPeriod' => '1209600', // 14 days ], diff --git a/src/Steps/Ensures/EnsureHostedZonesExistStep.php b/src/Steps/Ensures/EnsureHostedZonesExistStep.php index 13f0fe5..61d59fa 100644 --- a/src/Steps/Ensures/EnsureHostedZonesExistStep.php +++ b/src/Steps/Ensures/EnsureHostedZonesExistStep.php @@ -5,10 +5,10 @@ use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Contracts\ExecutesDomainStep; use Codinglabs\Yolo\Concerns\EnsuresResourcesExist; +use Codinglabs\Yolo\Contracts\ExecutesStandaloneStep; -class EnsureHostedZonesExistStep implements ExecutesDomainStep +class EnsureHostedZonesExistStep implements ExecutesStandaloneStep { use EnsuresResourcesExist; diff --git a/src/Steps/Landlord/SyncQueueAlarmStep.php b/src/Steps/Landlord/SyncQueueAlarmStep.php index 166eb22..5d76d0f 100644 --- a/src/Steps/Landlord/SyncQueueAlarmStep.php +++ b/src/Steps/Landlord/SyncQueueAlarmStep.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Aws; use Illuminate\Support\Arr; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Contracts\ExecutesMultitenancyStep; @@ -13,7 +14,7 @@ class SyncQueueAlarmStep implements ExecutesMultitenancyStep { public function __invoke(array $options): StepResult { - $alarmName = 'landlord-queue-depth-alarm'; + $alarmName = Helpers::keyedResourceName('landlord-queue-depth-alarm'); try { AwsResources::alarm($alarmName); @@ -36,7 +37,7 @@ public function __invoke(array $options): StepResult 'Dimensions' => [ [ 'Name' => 'QueueName', - 'Value' => 'landlord', + 'Value' => Helpers::keyedResourceName('landlord'), ], ], 'EvaluationPeriods' => 3, // number of breached of Period before alarm diff --git a/src/Steps/Landlord/SyncQueueStep.php b/src/Steps/Landlord/SyncQueueStep.php index 6b0d5ef..d2619ea 100644 --- a/src/Steps/Landlord/SyncQueueStep.php +++ b/src/Steps/Landlord/SyncQueueStep.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Aws; use Illuminate\Support\Arr; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Contracts\ExecutesMultitenancyStep; @@ -13,13 +14,15 @@ class SyncQueueStep implements ExecutesMultitenancyStep { public function __invoke(array $options): StepResult { + $name = Helpers::keyedResourceName('landlord'); + try { - AwsResources::queue('landlord'); + AwsResources::queue($name); return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { Aws::sqs()->createQueue([ - 'QueueName' => 'landlord', + 'QueueName' => $name, 'Attributes' => [ 'MessageRetentionPeriod' => '1209600', // 14 days ], diff --git a/src/Steps/Start/SyncLandlordQueueWorkerStep.php b/src/Steps/Start/SyncLandlordQueueWorkerStep.php index ee76743..25209ad 100644 --- a/src/Steps/Start/SyncLandlordQueueWorkerStep.php +++ b/src/Steps/Start/SyncLandlordQueueWorkerStep.php @@ -3,17 +3,18 @@ namespace Codinglabs\Yolo\Steps\Start; use Codinglabs\Yolo\Paths; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Contracts\RunsOnAwsQueue; -class SyncLandlordQueueWorkerStep implements RunsOnAwsQueue +class SyncQueueLandlordWorkerStep implements RunsOnAwsQueue { public function __invoke(): StepResult { file_put_contents( - "/etc/supervisor/conf.d/landlord-queue-worker.conf", + sprintf('/etc/supervisor/conf.d/%s', Helpers::keyedResourceName('landlord-queue-worker.conf')), str_replace( search: [ '{NAME}', @@ -21,7 +22,7 @@ public function __invoke(): StepResult ], replace: [ Manifest::name(), - AwsResources::queue('landlord')['QueueUrl'], + AwsResources::queue(Helpers::keyedResourceName('landlord'))['QueueUrl'], ], subject: file_get_contents(Paths::stubs('supervisor/landlord-queue-worker.conf.stub')) ) diff --git a/src/Steps/Start/SyncTenantQueueWorkerStep.php b/src/Steps/Start/SyncTenantQueueWorkerStep.php index 4cf99a9..10ea2e2 100644 --- a/src/Steps/Start/SyncTenantQueueWorkerStep.php +++ b/src/Steps/Start/SyncTenantQueueWorkerStep.php @@ -3,18 +3,19 @@ namespace Codinglabs\Yolo\Steps\Start; use Codinglabs\Yolo\Paths; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Steps\TenantStep; use Codinglabs\Yolo\Contracts\RunsOnAwsQueue; -class SyncTenantQueueWorkerStep extends TenantStep implements RunsOnAwsQueue +class SyncQueueTenantWorkerStep extends TenantStep implements RunsOnAwsQueue { public function __invoke(array $options): StepResult { file_put_contents( - "/etc/supervisor/conf.d/{$this->tenantId()}-queue-worker.conf", + sprintf('/etc/supervisor/conf.d/%s', Helpers::keyedResourceName("{$this->tenantId()}-queue-worker.conf")), str_replace( search: [ '{NAME}', @@ -24,7 +25,7 @@ public function __invoke(array $options): StepResult replace: [ Manifest::name(), $this->tenantId(), - AwsResources::queue($this->tenantId())['QueueUrl'], + AwsResources::queue(Helpers::keyedResourceName($this->tenantId()))['QueueUrl'], ], subject: file_get_contents(Paths::stubs('supervisor/tenant-queue-worker.conf.stub')) ) diff --git a/src/Steps/Start/WarmApplicationStep.php b/src/Steps/Start/WarmApplicationStep.php index 0494dc2..53411b6 100644 --- a/src/Steps/Start/WarmApplicationStep.php +++ b/src/Steps/Start/WarmApplicationStep.php @@ -3,11 +3,11 @@ namespace Codinglabs\Yolo\Steps\Start; use GuzzleHttp\Client; +use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Steps\TenantStep; use Codinglabs\Yolo\Contracts\RunsOnAwsWeb; -class WarmApplicationStep extends TenantStep implements RunsOnAwsWeb +class WarmApplicationStep implements RunsOnAwsWeb { public function __invoke(array $options): StepResult { @@ -15,7 +15,7 @@ public function __invoke(array $options): StepResult (new Client(['timeout' => 10])) ->get('localhost', [ 'headers' => [ - 'Host' => $this->config()['domain'], + 'Host' => Manifest::get('domain'), 'X-Forwarded-Proto' => 'https', 'User-Agent' => 'YOLO-Warmer/1.0', ], diff --git a/src/Steps/Start/WarmMultitenantedApplicationStep.php b/src/Steps/Start/WarmMultitenantedApplicationStep.php new file mode 100644 index 0000000..c91511d --- /dev/null +++ b/src/Steps/Start/WarmMultitenantedApplicationStep.php @@ -0,0 +1,27 @@ + 10])) + ->get('localhost', [ + 'headers' => [ + 'Host' => $this->config()['domain'], + 'X-Forwarded-Proto' => 'https', + 'User-Agent' => 'YOLO-Warmer/1.0', + ], + ]) + ->getBody(); + + return StepResult::SUCCESS; + } +} diff --git a/src/Steps/Tenant/SyncQueueAlarmStep.php b/src/Steps/Tenant/SyncQueueAlarmStep.php index 5f17064..011782e 100644 --- a/src/Steps/Tenant/SyncQueueAlarmStep.php +++ b/src/Steps/Tenant/SyncQueueAlarmStep.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Aws; use Illuminate\Support\Arr; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Steps\TenantStep; @@ -13,7 +14,7 @@ class SyncQueueAlarmStep extends TenantStep { public function __invoke(array $options): StepResult { - $alarmName = "{$this->tenantId()}-queue-depth-alarm"; + $alarmName = Helpers::keyedResourceName(sprintf('%s-queue-depth-alarm', $this->tenantId())); try { AwsResources::alarm($alarmName); @@ -36,7 +37,7 @@ public function __invoke(array $options): StepResult 'Dimensions' => [ [ 'Name' => 'QueueName', - 'Value' => $this->tenantId(), + 'Value' => Helpers::keyedResourceName($this->tenantId()), ], ], 'EvaluationPeriods' => 3, // number of breached of Period before alarm diff --git a/src/Steps/Tenant/SyncQueueStep.php b/src/Steps/Tenant/SyncQueueStep.php index 2c8caf5..4db11a5 100644 --- a/src/Steps/Tenant/SyncQueueStep.php +++ b/src/Steps/Tenant/SyncQueueStep.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Aws; use Illuminate\Support\Arr; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Steps\TenantStep; @@ -13,13 +14,15 @@ class SyncQueueStep extends TenantStep { public function __invoke(array $options): StepResult { + $name = Helpers::keyedResourceName($this->tenantId()); + try { - AwsResources::queue($this->tenantId()); + AwsResources::queue($name); return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { Aws::sqs()->createQueue([ - 'QueueName' => $this->tenantId(), + 'QueueName' => $name, 'Attributes' => [ 'MessageRetentionPeriod' => '1209600', // 14 days ], diff --git a/src/Yolo.php b/src/Yolo.php index ca1d332..d6ffe85 100644 --- a/src/Yolo.php +++ b/src/Yolo.php @@ -36,9 +36,9 @@ class Yolo Commands\SyncCommand::class, Commands\SyncNetworkCommand::class, Commands\SyncStorageCommand::class, - Commands\SyncDomainCommand::class, - Commands\SyncMultitenancyLandlordCommand::class, + Commands\SyncStandaloneCommand::class, Commands\SyncMultitenancyTenantsCommand::class, + Commands\SyncMultitenancyLandlordCommand::class, Commands\SyncComputeCommand::class, Commands\SyncCiCommand::class, ]; From 8801193b9f644d95f74e784a96dc6621047002d1 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 29 Jan 2025 16:03:09 +1000 Subject: [PATCH 10/40] wip --- src/Commands/SyncDomainCommand.php | 8 +-- src/Commands/SyncStandaloneCommand.php | 23 +++++++ .../SyncHostedZoneStep.php | 2 +- .../SyncQueueAlarmStep.php | 0 .../{Domain => Standalone}/SyncQueueStep.php | 0 .../SyncSslCertificateStep.php | 2 +- ...ep.php => SyncQueueLandlordWorkerStep.php} | 0 ...Step.php => SyncQueueTenantWorkerStep.php} | 0 src/Steps/Start/SyncQueueWorkerStep.php | 34 ++++++++++ src/Steps/Tenant/SyncRecordSetStep.php | 63 +++++++++++++++++++ 10 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 src/Commands/SyncStandaloneCommand.php rename src/Steps/{Domain => Standalone}/SyncHostedZoneStep.php (95%) rename src/Steps/{Domain => Standalone}/SyncQueueAlarmStep.php (100%) rename src/Steps/{Domain => Standalone}/SyncQueueStep.php (100%) rename src/Steps/{Domain => Standalone}/SyncSslCertificateStep.php (96%) rename src/Steps/Start/{SyncLandlordQueueWorkerStep.php => SyncQueueLandlordWorkerStep.php} (100%) rename src/Steps/Start/{SyncTenantQueueWorkerStep.php => SyncQueueTenantWorkerStep.php} (100%) create mode 100644 src/Steps/Start/SyncQueueWorkerStep.php create mode 100644 src/Steps/Tenant/SyncRecordSetStep.php diff --git a/src/Commands/SyncDomainCommand.php b/src/Commands/SyncDomainCommand.php index 467cf5e..a830a9c 100644 --- a/src/Commands/SyncDomainCommand.php +++ b/src/Commands/SyncDomainCommand.php @@ -8,10 +8,10 @@ class SyncDomainCommand extends SteppedCommand { protected array $steps = [ - Steps\Domain\SyncHostedZoneStep::class, - Steps\Domain\SyncSslCertificateStep::class, - Steps\Domain\SyncQueueStep::class, - Steps\Domain\SyncQueueAlarmStep::class, + Steps\Standalone\SyncHostedZoneStep::class, + Steps\Standalone\SyncSslCertificateStep::class, + Steps\Standalone\SyncQueueStep::class, + Steps\Standalone\SyncQueueAlarmStep::class, ]; protected function configure(): void diff --git a/src/Commands/SyncStandaloneCommand.php b/src/Commands/SyncStandaloneCommand.php new file mode 100644 index 0000000..e0f8d5c --- /dev/null +++ b/src/Commands/SyncStandaloneCommand.php @@ -0,0 +1,23 @@ +setName('sync:standalone') + ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') + ->addOption('dry-run', null, null, 'Run the command without making changes') + ->setDescription('Sync configured landlord AWS resources'); + } +} diff --git a/src/Steps/Domain/SyncHostedZoneStep.php b/src/Steps/Standalone/SyncHostedZoneStep.php similarity index 95% rename from src/Steps/Domain/SyncHostedZoneStep.php rename to src/Steps/Standalone/SyncHostedZoneStep.php index fdc91d4..f6bbe0a 100644 --- a/src/Steps/Domain/SyncHostedZoneStep.php +++ b/src/Steps/Standalone/SyncHostedZoneStep.php @@ -1,6 +1,6 @@ 'UPSERT', // CREATE|DELETE|UPSERT + 'ResourceRecordSet' => [ + 'AliasTarget' => [ + 'DNSName' => $ALB['DNSName'], + 'EvaluateTargetHealth' => false, + 'HostedZoneId' => $ALB['CanonicalHostedZoneId'], + ], + 'Name' => $this->config['domain'], + 'Type' => 'A', + ], + ], + ]; + + if (! $this->config['subdomain']) { + // add a www. domain alias for non-subdomains + $changes[] = [ + 'Action' => 'UPSERT', + 'ResourceRecordSet' => [ + 'AliasTarget' => [ + 'DNSName' => $ALB['DNSName'], + 'EvaluateTargetHealth' => false, + 'HostedZoneId' => $ALB['CanonicalHostedZoneId'], + ], + 'Name' => "www.{$this->config['domain']}", + 'Type' => 'A', + ], + ]; + } + + Aws::route53()->changeResourceRecordSets([ + 'ChangeBatch' => [ + 'Changes' => $changes, + 'Comment' => 'Created by yolo CLI', + ], + 'HostedZoneId' => AwsResources::hostedZone($this->config['apex'])['Id'], + ]); + + return StepResult::SYNCED; + } + + return StepResult::WOULD_SYNC; + } +} From 2925d4fd3a5a717c66155e35da5d59309fea2e38 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 29 Jan 2025 16:04:22 +1000 Subject: [PATCH 11/40] wip it --- src/Commands/SyncDomainCommand.php | 26 -------------------------- src/Commands/SyncStandaloneCommand.php | 5 ++++- 2 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 src/Commands/SyncDomainCommand.php diff --git a/src/Commands/SyncDomainCommand.php b/src/Commands/SyncDomainCommand.php deleted file mode 100644 index a830a9c..0000000 --- a/src/Commands/SyncDomainCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -setName('sync:domain') - ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') - ->addOption('dry-run', null, null, 'Run the command without making changes') - ->addOption('no-progress', null, null, 'Hide the progress output') - ->setDescription('Sync configured domain AWS resources'); - } -} diff --git a/src/Commands/SyncStandaloneCommand.php b/src/Commands/SyncStandaloneCommand.php index e0f8d5c..3187561 100644 --- a/src/Commands/SyncStandaloneCommand.php +++ b/src/Commands/SyncStandaloneCommand.php @@ -8,6 +8,8 @@ class SyncStandaloneCommand extends SteppedCommand { protected array $steps = [ + Steps\Standalone\SyncHostedZoneStep::class, + Steps\Standalone\SyncSslCertificateStep::class, Steps\Standalone\SyncQueueStep::class, Steps\Standalone\SyncQueueAlarmStep::class, ]; @@ -18,6 +20,7 @@ protected function configure(): void ->setName('sync:standalone') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('dry-run', null, null, 'Run the command without making changes') - ->setDescription('Sync configured landlord AWS resources'); + ->addOption('no-progress', null, null, 'Hide the progress output') + ->setDescription('Sync configured domain AWS resources'); } } From 99b73b05b0ace008adb3c6eed80820261a4be372 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 29 Jan 2025 16:57:14 +1000 Subject: [PATCH 12/40] prefix more stubs with yolo- --- src/Steps/Start/SyncHousekeepingCronStep.php | 2 +- src/Steps/Start/SyncLogrotateStep.php | 3 ++- src/Steps/Start/SyncNginxConfigurationStep.php | 11 ++++++----- src/Steps/Start/SyncOctaneWorkerStep.php | 3 ++- src/Steps/Start/SyncPhpConfigurationStep.php | 2 +- src/Steps/Start/SyncPulseWorkerStep.php | 3 ++- src/Steps/Start/SyncSchedulerCronStep.php | 3 ++- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Steps/Start/SyncHousekeepingCronStep.php b/src/Steps/Start/SyncHousekeepingCronStep.php index efb62a2..ba6a56d 100644 --- a/src/Steps/Start/SyncHousekeepingCronStep.php +++ b/src/Steps/Start/SyncHousekeepingCronStep.php @@ -11,7 +11,7 @@ class SyncHousekeepingCronStep implements RunsOnAws public function __invoke(array $options): StepResult { file_put_contents( - "/etc/cron.d/housekeeping", + "/etc/cron.d/yolo-housekeeping", file_get_contents(Paths::stubs('cron/housekeeping.stub')) ); diff --git a/src/Steps/Start/SyncLogrotateStep.php b/src/Steps/Start/SyncLogrotateStep.php index caec136..8644375 100644 --- a/src/Steps/Start/SyncLogrotateStep.php +++ b/src/Steps/Start/SyncLogrotateStep.php @@ -3,6 +3,7 @@ namespace Codinglabs\Yolo\Steps\Start; use Codinglabs\Yolo\Paths; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Contracts\RunsOnAws; @@ -12,7 +13,7 @@ class SyncLogrotateStep implements RunsOnAws public function __invoke(): StepResult { file_put_contents( - "/etc/logrotate.d/laravel", + sprintf('/etc/logrotate.d/%s', Helpers::keyedResourceName()), str_replace( search: [ '{NAME}', diff --git a/src/Steps/Start/SyncNginxConfigurationStep.php b/src/Steps/Start/SyncNginxConfigurationStep.php index 2e517a1..30eb885 100644 --- a/src/Steps/Start/SyncNginxConfigurationStep.php +++ b/src/Steps/Start/SyncNginxConfigurationStep.php @@ -3,6 +3,7 @@ namespace Codinglabs\Yolo\Steps\Start; use Codinglabs\Yolo\Paths; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; use Symfony\Component\Process\Process; @@ -31,11 +32,11 @@ public function __invoke(): StepResult ? file_get_contents(Paths::stubs('nginx/vhost_octane')) : file_get_contents(Paths::stubs('nginx/vhost')); - $name = Manifest::name(); + $filename = Helpers::keyedResourceName(); // create a catch-all vhost with forwarding rules file_put_contents( - "/etc/nginx/sites-available/$name", + "/etc/nginx/sites-available/$filename", str_replace( search: [ '{NAME}', @@ -43,7 +44,7 @@ public function __invoke(): StepResult '{SERVER_NAME}', ], replace: [ - $name, + Manifest::name(), $this->forwardingRules(), $this->serverName(), ], @@ -53,7 +54,7 @@ public function __invoke(): StepResult // create a symbolic link to enable the app vhost, using -f force to supress file exists error (Process::fromShellCommandline( - command: "ln -sf /etc/nginx/sites-available/$name /etc/nginx/sites-enabled/" + command: "ln -sf /etc/nginx/sites-available/$filename /etc/nginx/sites-enabled/" ))->mustRun(); return StepResult::SYNCED; @@ -108,7 +109,7 @@ protected function forwardingRules(): string protected function serverName(): string { return Manifest::isMultitenanted() - ? '_' + ? implode(' ', collect(Manifest::tenants())->pluck('domain')->toArray()) : Manifest::get('domain'); } } diff --git a/src/Steps/Start/SyncOctaneWorkerStep.php b/src/Steps/Start/SyncOctaneWorkerStep.php index 19faea9..aeb77be 100644 --- a/src/Steps/Start/SyncOctaneWorkerStep.php +++ b/src/Steps/Start/SyncOctaneWorkerStep.php @@ -3,6 +3,7 @@ namespace Codinglabs\Yolo\Steps\Start; use Codinglabs\Yolo\Paths; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Contracts\RunsOnAwsWeb; @@ -11,7 +12,7 @@ class SyncOctaneWorkerStep implements RunsOnAwsWeb { public function __invoke(array $options): StepResult { - $file = "/etc/supervisor/conf.d/octane-worker.conf"; + $file = sprintf('/etc/supervisor/conf.d/%s', Helpers::keyedResourceName("octane-worker.conf")); if (! Manifest::get('aws.ec2.octane')) { if (file_exists($file)) { diff --git a/src/Steps/Start/SyncPhpConfigurationStep.php b/src/Steps/Start/SyncPhpConfigurationStep.php index ee56310..f047b54 100644 --- a/src/Steps/Start/SyncPhpConfigurationStep.php +++ b/src/Steps/Start/SyncPhpConfigurationStep.php @@ -25,7 +25,7 @@ public function __invoke(): StepResult // configuration for PHP-FPM pool file_put_contents( - "/etc/php/8.3/fpm/pool.d/www_processes.conf", + "/etc/php/8.3/fpm/pool.d/yolo_www_processes.conf", file_get_contents(Paths::stubs('php/www_processes.conf.stub')) ); diff --git a/src/Steps/Start/SyncPulseWorkerStep.php b/src/Steps/Start/SyncPulseWorkerStep.php index b151664..d548d89 100644 --- a/src/Steps/Start/SyncPulseWorkerStep.php +++ b/src/Steps/Start/SyncPulseWorkerStep.php @@ -3,6 +3,7 @@ namespace Codinglabs\Yolo\Steps\Start; use Codinglabs\Yolo\Paths; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Contracts\RunsOnAws; @@ -12,7 +13,7 @@ class SyncPulseWorkerStep implements RunsOnAws public function __invoke(): StepResult { file_put_contents( - "/etc/supervisor/conf.d/pulse-worker.conf", + sprintf('/etc/supervisor/conf.d/%s', Helpers::keyedResourceName('pulse-worker.conf')), str_replace( search: [ '{NAME}', diff --git a/src/Steps/Start/SyncSchedulerCronStep.php b/src/Steps/Start/SyncSchedulerCronStep.php index e9874f5..bb9e8dc 100644 --- a/src/Steps/Start/SyncSchedulerCronStep.php +++ b/src/Steps/Start/SyncSchedulerCronStep.php @@ -3,6 +3,7 @@ namespace Codinglabs\Yolo\Steps\Start; use Codinglabs\Yolo\Paths; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Contracts\RunsOnAwsScheduler; @@ -12,7 +13,7 @@ class SyncSchedulerCronStep implements RunsOnAwsScheduler public function __invoke(array $options): StepResult { file_put_contents( - "/etc/cron.d/scheduler", + sprintf('/etc/cron.d/%s', Helpers::keyedResourceName('scheduler')), str_replace( search: [ '{NAME}', From 0a3296c0337f47d866391956131816d4369c7a2d Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 29 Jan 2025 17:01:42 +1000 Subject: [PATCH 13/40] wip --- src/Commands/DeployCommand.php | 2 +- ...omainRecordSetStep.php => SyncStandaloneRecordSetStep.php} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Steps/Deploy/{SyncDomainRecordSetStep.php => SyncStandaloneRecordSetStep.php} (83%) diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index be83d03..f7ac454 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -29,7 +29,7 @@ class DeployCommand extends Command Steps\Deploy\PushAssetsToS3Step::class, Steps\Deploy\UpdateCodeDeployDeploymentGroupStep::class, Steps\Deploy\CreateCodeDeployDeploymentsStep::class, - Steps\Deploy\SyncDomainRecordSetStep::class, + Steps\Deploy\SyncStandaloneRecordSetStep::class, Steps\Deploy\SyncMultitenancyRecordSetStep::class, Steps\Build\PurgeBuildStep::class, ]; diff --git a/src/Steps/Deploy/SyncDomainRecordSetStep.php b/src/Steps/Deploy/SyncStandaloneRecordSetStep.php similarity index 83% rename from src/Steps/Deploy/SyncDomainRecordSetStep.php rename to src/Steps/Deploy/SyncStandaloneRecordSetStep.php index 79c40eb..e0b825b 100644 --- a/src/Steps/Deploy/SyncDomainRecordSetStep.php +++ b/src/Steps/Deploy/SyncStandaloneRecordSetStep.php @@ -6,9 +6,9 @@ use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Concerns\SyncsRecordSets; -use Codinglabs\Yolo\Contracts\ExecutesDomainStep; +use Codinglabs\Yolo\Contracts\ExecutesStandaloneStep; -class SyncDomainRecordSetStep implements ExecutesDomainStep +class SyncStandaloneRecordSetStep implements ExecutesStandaloneStep { use SyncsRecordSets; From b89b25f664d84d17d9296544e4eb16b06d3c16fd Mon Sep 17 00:00:00 2001 From: stevethomas Date: Wed, 29 Jan 2025 17:20:35 +1000 Subject: [PATCH 14/40] implement SteppedCommand on start and stop --- src/Commands/StartCommand.php | 17 +---------------- src/Commands/StopCommand.php | 17 +---------------- .../ChecksIfCommandsShouldBeRunning.php | 10 ++++------ 3 files changed, 6 insertions(+), 38 deletions(-) diff --git a/src/Commands/StartCommand.php b/src/Commands/StartCommand.php index 087e574..37eb931 100644 --- a/src/Commands/StartCommand.php +++ b/src/Commands/StartCommand.php @@ -4,14 +4,10 @@ use Codinglabs\Yolo\Steps; use Codinglabs\Yolo\Contracts\RunsOnAws; -use Codinglabs\Yolo\Concerns\RunsSteppedCommands; use Symfony\Component\Console\Input\InputArgument; -use function Laravel\Prompts\info; -class StartCommand extends Command implements RunsOnAws +class StartCommand extends SteppedCommand implements RunsOnAws { - use RunsSteppedCommands; - protected array $steps = [ Steps\Start\SyncBashProfileStep::class, // all Steps\Start\ExecuteDeployStepsStep::class, // scheduler - note: migrations run here @@ -43,15 +39,4 @@ protected function configure(): void ->addOption('no-progress', null, null, 'Hide the progress output') ->setDescription('Prepare the server for a new deployment'); } - - public function handle(): void - { - $environment = $this->argument('environment'); - - info("Executing start steps..."); - - $totalTime = $this->handleSteps($environment); - - info(sprintf('Completed successfully in %ss.', $totalTime)); - } } diff --git a/src/Commands/StopCommand.php b/src/Commands/StopCommand.php index 7466ac4..ae752f5 100644 --- a/src/Commands/StopCommand.php +++ b/src/Commands/StopCommand.php @@ -4,14 +4,10 @@ use Codinglabs\Yolo\Steps; use Codinglabs\Yolo\Contracts\RunsOnAws; -use Codinglabs\Yolo\Concerns\RunsSteppedCommands; use Symfony\Component\Console\Input\InputArgument; -use function Laravel\Prompts\info; -class StopCommand extends Command implements RunsOnAws +class StopCommand extends SteppedCommand implements RunsOnAws { - use RunsSteppedCommands; - protected array $steps = [ Steps\Stop\StopWorkOnQueueStep::class, Steps\Stop\StopWorkOnSchedulerStep::class, @@ -26,15 +22,4 @@ protected function configure(): void ->addOption('no-progress', null, null, 'Hide the progress output') ->setDescription('Stop work before deployment'); } - - public function handle(): void - { - $environment = $this->argument('environment'); - - info("Executing stop steps..."); - - $totalTime = $this->handleSteps($environment); - - info(sprintf('Completed successfully in %ss.', $totalTime)); - } } diff --git a/src/Concerns/ChecksIfCommandsShouldBeRunning.php b/src/Concerns/ChecksIfCommandsShouldBeRunning.php index 7d55bce..c3e34f4 100644 --- a/src/Concerns/ChecksIfCommandsShouldBeRunning.php +++ b/src/Concerns/ChecksIfCommandsShouldBeRunning.php @@ -17,12 +17,10 @@ trait ChecksIfCommandsShouldBeRunning { public function shouldBeRunning(Command|Step $instance): bool { - if ($instance instanceof ExecutesStandaloneStep) { - return ! Manifest::isMultitenanted(); - } - - if ($instance instanceof ExecutesMultitenancyStep) { - return Manifest::isMultitenanted(); + if ( + $instance instanceof ExecutesStandaloneStep && Manifest::isMultitenanted() + || $instance instanceof ExecutesMultitenancyStep && ! Manifest::isMultitenanted()) { + return false; } if (Aws::runningInAws()) { From c2ae768c4e2af4df4c4c1cd89c7c7195f1d37661 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Thu, 30 Jan 2025 11:17:21 +1000 Subject: [PATCH 15/40] move namespaces --- src/Commands/CommandCommand.php | 2 +- src/Commands/StartCommand.php | 38 +++++++++---------- src/Commands/StopCommand.php | 6 +-- src/Contracts/ExecutesStandaloneStep.php | 4 +- .../ExecuteAllGroupsDeployStepsStep.php | 2 +- .../Start/{ => All}/RestartServicesStep.php | 2 +- .../SetOwnershipAndPermissionsStep.php | 2 +- .../Start/{ => All}/SyncBashProfileStep.php | 2 +- .../{ => All}/SyncHousekeepingCronStep.php | 2 +- .../Start/{ => All}/SyncLogrotateStep.php | 2 +- .../{ => All}/SyncPhpConfigurationStep.php | 2 +- .../Start/{ => All}/SyncPulseWorkerStep.php | 2 +- .../SyncQueueLandlordWorkerStep.php | 2 +- .../{ => Queue}/SyncQueueTenantWorkerStep.php | 2 +- .../Start/{ => Queue}/SyncQueueWorkerStep.php | 2 +- .../ExecuteDeployStepsStep.php | 2 +- .../{ => Scheduler}/SyncMysqlBackupStep.php | 2 +- .../{ => Scheduler}/SyncSchedulerCronStep.php | 2 +- .../{ => Web}/ConfigureLoadBalancingStep.php | 2 +- .../{ => Web}/SyncNginxConfigurationStep.php | 2 +- .../Start/{ => Web}/SyncOctaneWorkerStep.php | 2 +- .../Start/{ => Web}/WarmApplicationStep.php | 2 +- .../WarmMultitenantedApplicationStep.php | 2 +- .../Stop/{ => Queue}/StopWorkOnQueueStep.php | 2 +- .../StopWorkOnSchedulerStep.php | 2 +- .../Stop/{ => Web}/StopWorkOnWebStep.php | 2 +- 26 files changed, 46 insertions(+), 48 deletions(-) rename src/Steps/Start/{ => All}/ExecuteAllGroupsDeployStepsStep.php (87%) rename src/Steps/Start/{ => All}/RestartServicesStep.php (92%) rename src/Steps/Start/{ => All}/SetOwnershipAndPermissionsStep.php (90%) rename src/Steps/Start/{ => All}/SyncBashProfileStep.php (91%) rename src/Steps/Start/{ => All}/SyncHousekeepingCronStep.php (90%) rename src/Steps/Start/{ => All}/SyncLogrotateStep.php (94%) rename src/Steps/Start/{ => All}/SyncPhpConfigurationStep.php (96%) rename src/Steps/Start/{ => All}/SyncPulseWorkerStep.php (94%) rename src/Steps/Start/{ => Queue}/SyncQueueLandlordWorkerStep.php (95%) rename src/Steps/Start/{ => Queue}/SyncQueueTenantWorkerStep.php (96%) rename src/Steps/Start/{ => Queue}/SyncQueueWorkerStep.php (95%) rename src/Steps/Start/{ => Scheduler}/ExecuteDeployStepsStep.php (85%) rename src/Steps/Start/{ => Scheduler}/SyncMysqlBackupStep.php (95%) rename src/Steps/Start/{ => Scheduler}/SyncSchedulerCronStep.php (93%) rename src/Steps/Start/{ => Web}/ConfigureLoadBalancingStep.php (94%) rename src/Steps/Start/{ => Web}/SyncNginxConfigurationStep.php (98%) rename src/Steps/Start/{ => Web}/SyncOctaneWorkerStep.php (95%) rename src/Steps/Start/{ => Web}/WarmApplicationStep.php (94%) rename src/Steps/Start/{ => Web}/WarmMultitenantedApplicationStep.php (94%) rename src/Steps/Stop/{ => Queue}/StopWorkOnQueueStep.php (90%) rename src/Steps/Stop/{ => Scheduler}/StopWorkOnSchedulerStep.php (96%) rename src/Steps/Stop/{ => Web}/StopWorkOnWebStep.php (90%) diff --git a/src/Commands/CommandCommand.php b/src/Commands/CommandCommand.php index f27f510..eb6aa07 100644 --- a/src/Commands/CommandCommand.php +++ b/src/Commands/CommandCommand.php @@ -5,8 +5,8 @@ use Codinglabs\Yolo\Paths; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Concerns\UsesEc2; -use Symfony\Component\Process\Process; use Codinglabs\Yolo\Enums\ServerGroup; +use Symfony\Component\Process\Process; use Codinglabs\Yolo\Concerns\FormatsSshCommands; use Symfony\Component\Console\Input\InputArgument; use function Laravel\Prompts\info; diff --git a/src/Commands/StartCommand.php b/src/Commands/StartCommand.php index 37eb931..1b5fc46 100644 --- a/src/Commands/StartCommand.php +++ b/src/Commands/StartCommand.php @@ -9,25 +9,25 @@ class StartCommand extends SteppedCommand implements RunsOnAws { protected array $steps = [ - Steps\Start\SyncBashProfileStep::class, // all - Steps\Start\ExecuteDeployStepsStep::class, // scheduler - note: migrations run here - Steps\Start\ExecuteAllGroupsDeployStepsStep::class, // all - Steps\Start\SetOwnershipAndPermissionsStep::class, - Steps\Start\SyncLogrotateStep::class, // all - Steps\Start\SyncHousekeepingCronStep::class, // all - Steps\Start\SyncSchedulerCronStep::class, // scheduler - Steps\Start\SyncPulseWorkerStep::class, // all - Steps\Start\SyncQueueWorkerStep::class, // queue - Steps\Start\SyncQueueLandlordWorkerStep::class, // queue - Steps\Start\SyncQueueTenantWorkerStep::class, // queue - Steps\Start\SyncOctaneWorkerStep::class, // web - Steps\Start\SyncMysqlBackupStep::class, // scheduler - Steps\Start\SyncPhpConfigurationStep::class, // all - Steps\Start\SyncNginxConfigurationStep::class, // web - Steps\Start\RestartServicesStep::class, // all - Steps\Start\WarmApplicationStep::class, // web - Steps\Start\WarmMultitenantedApplicationStep::class, // web - Steps\Start\ConfigureLoadBalancingStep::class, // web + Steps\Start\All\SyncBashProfileStep::class, // all + Steps\Start\Scheduler\ExecuteDeployStepsStep::class, // scheduler - note: migrations run here + Steps\Start\All\ExecuteAllGroupsDeployStepsStep::class, // all + Steps\Start\All\SetOwnershipAndPermissionsStep::class, + Steps\Start\All\SyncLogrotateStep::class, // all + Steps\Start\All\SyncHousekeepingCronStep::class, // all + Steps\Start\Scheduler\SyncSchedulerCronStep::class, // scheduler + Steps\Start\All\SyncPulseWorkerStep::class, // all + Steps\Start\Queue\SyncQueueWorkerStep::class, // queue + Steps\Start\Queue\SyncQueueLandlordWorkerStep::class, // queue + Steps\Start\Queue\SyncQueueTenantWorkerStep::class, // queue + Steps\Start\Web\SyncOctaneWorkerStep::class, // web + Steps\Start\Scheduler\SyncMysqlBackupStep::class, // scheduler + Steps\Start\All\SyncPhpConfigurationStep::class, // all + Steps\Start\Web\SyncNginxConfigurationStep::class, // web + Steps\Start\All\RestartServicesStep::class, // all + Steps\Start\Web\WarmApplicationStep::class, // web + Steps\Start\Web\WarmMultitenantedApplicationStep::class, // web + Steps\Start\Web\ConfigureLoadBalancingStep::class, // web ]; protected function configure(): void diff --git a/src/Commands/StopCommand.php b/src/Commands/StopCommand.php index ae752f5..caf90b8 100644 --- a/src/Commands/StopCommand.php +++ b/src/Commands/StopCommand.php @@ -9,9 +9,9 @@ class StopCommand extends SteppedCommand implements RunsOnAws { protected array $steps = [ - Steps\Stop\StopWorkOnQueueStep::class, - Steps\Stop\StopWorkOnSchedulerStep::class, - Steps\Stop\StopWorkOnWebStep::class, + Steps\Stop\Queue\StopWorkOnQueueStep::class, + Steps\Stop\Scheduler\StopWorkOnSchedulerStep::class, + Steps\Stop\Web\StopWorkOnWebStep::class, ]; protected function configure(): void diff --git a/src/Contracts/ExecutesStandaloneStep.php b/src/Contracts/ExecutesStandaloneStep.php index ae234d9..f9229d1 100644 --- a/src/Contracts/ExecutesStandaloneStep.php +++ b/src/Contracts/ExecutesStandaloneStep.php @@ -2,6 +2,4 @@ namespace Codinglabs\Yolo\Contracts; -interface ExecutesStandaloneStep extends Step -{ -} +interface ExecutesStandaloneStep extends Step {} diff --git a/src/Steps/Start/ExecuteAllGroupsDeployStepsStep.php b/src/Steps/Start/All/ExecuteAllGroupsDeployStepsStep.php similarity index 87% rename from src/Steps/Start/ExecuteAllGroupsDeployStepsStep.php rename to src/Steps/Start/All/ExecuteAllGroupsDeployStepsStep.php index ca94f6b..1472d64 100644 --- a/src/Steps/Start/ExecuteAllGroupsDeployStepsStep.php +++ b/src/Steps/Start/All/ExecuteAllGroupsDeployStepsStep.php @@ -1,6 +1,6 @@ Date: Thu, 30 Jan 2025 11:21:18 +1000 Subject: [PATCH 16/40] merge changes --- .github/workflows/test.yml | 41 +++++++++++++++++ composer.json | 10 +++-- phpunit.xml | 17 +++++++ src/Commands/BuildCommand.php | 14 +----- src/Commands/DeployCommand.php | 15 +------ src/Commands/PrepareCommand.php | 2 - src/Commands/StartCommand.php | 36 +++++++-------- src/Commands/SteppedCommand.php | 2 + src/Commands/SyncStandaloneCommand.php | 2 +- src/Steps/Landlord/SyncQueueAlarmStep.php | 4 +- .../Start/Scheduler/SyncSchedulerCronStep.php | 3 +- .../Scheduler/StopWorkOnSchedulerStep.php | 4 +- tests/Arch/ContractsTest.php | 5 +++ tests/Arch/EnumsTest.php | 5 +++ tests/Arch/InterfacesTest.php | 5 +++ tests/Arch/SecurityTest.php | 3 ++ tests/Arch/StepsTest.php | 22 +++++++++ tests/Pest.php | 45 +++++++++++++++++++ tests/TestCase.php | 10 +++++ 19 files changed, 192 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 phpunit.xml create mode 100644 tests/Arch/ContractsTest.php create mode 100644 tests/Arch/EnumsTest.php create mode 100644 tests/Arch/InterfacesTest.php create mode 100644 tests/Arch/SecurityTest.php create mode 100644 tests/Arch/StepsTest.php create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2184b99 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,41 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + branches: + - main + types: [ opened, synchronize, reopened, ready_for_review ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + if: github.event.pull_request.draft == false + runs-on: ubuntu-22.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: zip, sqlite3 + coverage: none + + - name: Install composer dependencies + run: composer install --no-cache --no-ansi --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/pest --parallel --stop-on-failure + + - name: Notify Slack of failure + if: failure() + run: | + curl -X POST -H 'Content-type: application/json' --data '{"text":"YOLO tests failed on branch ${{github.ref_name}} for action https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"}' https://hooks.slack.com/services/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 diff --git a/composer.json b/composer.json index c3b18f9..359c494 100644 --- a/composer.json +++ b/composer.json @@ -18,14 +18,15 @@ "illuminate/filesystem": "^10|^11", "illuminate/support": "^10|^11", "joetannenbaum/chewie": "^0.1.8", - "spatie/ray": "^1.41", "symfony/console": "^7.1", "symfony/yaml": "^7.1", "vlucas/phpdotenv": "^5.6" }, "require-dev": { "codinglabsau/php-styles": "dev-main", - "phpstan/phpstan": "^1.12" + "pestphp/pest": "^3.7", + "phpstan/phpstan": "^1.12", + "spatie/ray": "^1.41" }, "autoload": { "psr-4": { @@ -39,7 +40,10 @@ "fix": "vendor/bin/php-cs-fixer fix" }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true + } }, "minimum-stability": "dev", "prefer-stable": true diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..0c12bb9 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,17 @@ + + + + + ./tests + + + + + ./src + + + diff --git a/src/Commands/BuildCommand.php b/src/Commands/BuildCommand.php index 3e16319..a723bc0 100644 --- a/src/Commands/BuildCommand.php +++ b/src/Commands/BuildCommand.php @@ -3,16 +3,12 @@ namespace Codinglabs\Yolo\Commands; use Codinglabs\Yolo\Steps; -use Codinglabs\Yolo\Concerns\RunsSteppedCommands; use Symfony\Component\Console\Input\InputArgument; -use function Laravel\Prompts\info; use function Laravel\Prompts\error; use function Laravel\Prompts\intro; -class BuildCommand extends Command +class BuildCommand extends SteppedCommand { - use RunsSteppedCommands; - protected array $steps = [ Steps\Build\PurgeBuildStep::class, Steps\Build\RetrieveEnvFileStep::class, @@ -46,12 +42,6 @@ public function handle(): void intro("Building app version: {$appVersion}"); - $environment = $this->argument('environment'); - - info("Executing build steps..."); - - $totalTime = $this->handleSteps($environment); - - info(sprintf('Completed successfully in %ss.', $totalTime)); + parent::handle(); } } diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index f7ac454..5484b5e 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -5,17 +5,12 @@ use Codinglabs\Yolo\Paths; use Codinglabs\Yolo\Steps; use Codinglabs\Yolo\Helpers; -use Codinglabs\Yolo\Concerns\RunsSteppedCommands; use Symfony\Component\Console\Input\InputArgument; -use function Laravel\Prompts\info; -use function Laravel\Prompts\intro; use function Laravel\Prompts\confirm; use function Laravel\Prompts\warning; -class DeployCommand extends Command +class DeployCommand extends SteppedCommand { - use RunsSteppedCommands; - protected array $steps = [ Steps\Ensures\EnsureTranscoderExistsStep::class, Steps\Ensures\EnsureHostedZonesExistStep::class, @@ -46,8 +41,6 @@ protected function configure(): void public function handle(): void { - $environment = $this->argument('environment'); - $reuseBuild = false; if (is_dir(Paths::yolo())) { @@ -60,10 +53,6 @@ public function handle(): void (new BuildCommand())->execute(Helpers::app('input'), Helpers::app('output')); } - intro("Executing deploy steps..."); - - $totalTime = $this->handleSteps($environment); - - info(sprintf('Completed successfully in %ss.', $totalTime)); + parent::handle(); } } diff --git a/src/Commands/PrepareCommand.php b/src/Commands/PrepareCommand.php index 7feada6..42ebb10 100644 --- a/src/Commands/PrepareCommand.php +++ b/src/Commands/PrepareCommand.php @@ -7,13 +7,11 @@ use Illuminate\Support\Carbon; use Codinglabs\Yolo\Concerns\UsesEc2; use Symfony\Component\Console\Input\InputOption; -use Codinglabs\Yolo\Concerns\RunsSteppedCommands; use Symfony\Component\Console\Input\InputArgument; use function Laravel\Prompts\select; class PrepareCommand extends SteppedCommand { - use RunsSteppedCommands; use UsesEc2; protected array $steps = [ diff --git a/src/Commands/StartCommand.php b/src/Commands/StartCommand.php index 1b5fc46..9fdb4e9 100644 --- a/src/Commands/StartCommand.php +++ b/src/Commands/StartCommand.php @@ -9,25 +9,25 @@ class StartCommand extends SteppedCommand implements RunsOnAws { protected array $steps = [ - Steps\Start\All\SyncBashProfileStep::class, // all - Steps\Start\Scheduler\ExecuteDeployStepsStep::class, // scheduler - note: migrations run here - Steps\Start\All\ExecuteAllGroupsDeployStepsStep::class, // all + Steps\Start\All\SyncBashProfileStep::class, + Steps\Start\Scheduler\ExecuteDeployStepsStep::class, // note: migrations run here + Steps\Start\All\ExecuteAllGroupsDeployStepsStep::class, Steps\Start\All\SetOwnershipAndPermissionsStep::class, - Steps\Start\All\SyncLogrotateStep::class, // all - Steps\Start\All\SyncHousekeepingCronStep::class, // all - Steps\Start\Scheduler\SyncSchedulerCronStep::class, // scheduler - Steps\Start\All\SyncPulseWorkerStep::class, // all - Steps\Start\Queue\SyncQueueWorkerStep::class, // queue - Steps\Start\Queue\SyncQueueLandlordWorkerStep::class, // queue - Steps\Start\Queue\SyncQueueTenantWorkerStep::class, // queue - Steps\Start\Web\SyncOctaneWorkerStep::class, // web - Steps\Start\Scheduler\SyncMysqlBackupStep::class, // scheduler - Steps\Start\All\SyncPhpConfigurationStep::class, // all - Steps\Start\Web\SyncNginxConfigurationStep::class, // web - Steps\Start\All\RestartServicesStep::class, // all - Steps\Start\Web\WarmApplicationStep::class, // web - Steps\Start\Web\WarmMultitenantedApplicationStep::class, // web - Steps\Start\Web\ConfigureLoadBalancingStep::class, // web + Steps\Start\All\SyncLogrotateStep::class, + Steps\Start\All\SyncHousekeepingCronStep::class, + Steps\Start\Scheduler\SyncSchedulerCronStep::class, + Steps\Start\All\SyncPulseWorkerStep::class, + Steps\Start\Queue\SyncQueueWorkerStep::class, + Steps\Start\Queue\SyncQueueLandlordWorkerStep::class, + Steps\Start\Queue\SyncQueueTenantWorkerStep::class, + Steps\Start\Web\SyncOctaneWorkerStep::class, + Steps\Start\Scheduler\SyncMysqlBackupStep::class, + Steps\Start\All\SyncPhpConfigurationStep::class, + Steps\Start\Web\SyncNginxConfigurationStep::class, + Steps\Start\All\RestartServicesStep::class, + Steps\Start\Web\WarmApplicationStep::class, + Steps\Start\Web\WarmMultitenantedApplicationStep::class, + Steps\Start\Web\ConfigureLoadBalancingStep::class, ]; protected function configure(): void diff --git a/src/Commands/SteppedCommand.php b/src/Commands/SteppedCommand.php index 32a3040..c119cb4 100644 --- a/src/Commands/SteppedCommand.php +++ b/src/Commands/SteppedCommand.php @@ -10,6 +10,8 @@ abstract class SteppedCommand extends Command { use RunsSteppedCommands; + protected array $steps; + public function handle(): void { $environment = $this->argument('environment'); diff --git a/src/Commands/SyncStandaloneCommand.php b/src/Commands/SyncStandaloneCommand.php index 3187561..56cc7d8 100644 --- a/src/Commands/SyncStandaloneCommand.php +++ b/src/Commands/SyncStandaloneCommand.php @@ -21,6 +21,6 @@ protected function configure(): void ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('dry-run', null, null, 'Run the command without making changes') ->addOption('no-progress', null, null, 'Hide the progress output') - ->setDescription('Sync configured domain AWS resources'); + ->setDescription('Sync AWS resources for standalone app'); } } diff --git a/src/Steps/Landlord/SyncQueueAlarmStep.php b/src/Steps/Landlord/SyncQueueAlarmStep.php index 5d76d0f..cee0afe 100644 --- a/src/Steps/Landlord/SyncQueueAlarmStep.php +++ b/src/Steps/Landlord/SyncQueueAlarmStep.php @@ -23,12 +23,12 @@ public function __invoke(array $options): StepResult // always sync the alarm with the desired state. } - $snsTopic = AwsResources::topic(); - if (Arr::get($options, 'dry-run')) { return StepResult::WOULD_SYNC; } + $snsTopic = AwsResources::topic(); + Aws::cloudWatch()->putMetricAlarm([ 'ActionsEnabled' => true, 'AlarmName' => $alarmName, diff --git a/src/Steps/Start/Scheduler/SyncSchedulerCronStep.php b/src/Steps/Start/Scheduler/SyncSchedulerCronStep.php index 9bbdaa2..b4240d2 100644 --- a/src/Steps/Start/Scheduler/SyncSchedulerCronStep.php +++ b/src/Steps/Start/Scheduler/SyncSchedulerCronStep.php @@ -6,6 +6,7 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; +use Codinglabs\Yolo\Enums\ServerGroup; use Codinglabs\Yolo\Contracts\RunsOnAwsScheduler; class SyncSchedulerCronStep implements RunsOnAwsScheduler @@ -13,7 +14,7 @@ class SyncSchedulerCronStep implements RunsOnAwsScheduler public function __invoke(array $options): StepResult { file_put_contents( - sprintf('/etc/cron.d/%s', Helpers::keyedResourceName('scheduler')), + sprintf('/etc/cron.d/%s', Helpers::keyedResourceName(ServerGroup::SCHEDULER)), str_replace( search: [ '{NAME}', diff --git a/src/Steps/Stop/Scheduler/StopWorkOnSchedulerStep.php b/src/Steps/Stop/Scheduler/StopWorkOnSchedulerStep.php index 15c2d99..bc9e024 100644 --- a/src/Steps/Stop/Scheduler/StopWorkOnSchedulerStep.php +++ b/src/Steps/Stop/Scheduler/StopWorkOnSchedulerStep.php @@ -2,8 +2,10 @@ namespace Codinglabs\Yolo\Steps\Stop\Scheduler; +use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Enums\StepResult; +use Codinglabs\Yolo\Enums\ServerGroup; use Symfony\Component\Process\Process; use Codinglabs\Yolo\Contracts\RunsOnAwsScheduler; use Codinglabs\Yolo\Concerns\InteractsWithSupervisor; @@ -21,7 +23,7 @@ public function __invoke(): StepResult // disable scheduling Process::fromShellCommandline( - command: "sudo rm /etc/cron.d/scheduler" + command: sprintf('sudo rm /etc/cron.d/%s', Helpers::keyedResourceName(ServerGroup::SCHEDULER)) )->run(); $i = 0; diff --git a/tests/Arch/ContractsTest.php b/tests/Arch/ContractsTest.php new file mode 100644 index 0000000..6f0b224 --- /dev/null +++ b/tests/Arch/ContractsTest.php @@ -0,0 +1,5 @@ +expect('Codinglabs\Yolo\Contracts') + ->toBeInterfaces(); diff --git a/tests/Arch/EnumsTest.php b/tests/Arch/EnumsTest.php new file mode 100644 index 0000000..4250d6e --- /dev/null +++ b/tests/Arch/EnumsTest.php @@ -0,0 +1,5 @@ +expect('Codinglabs\Yolo\Enums') + ->toBeEnums(); diff --git a/tests/Arch/InterfacesTest.php b/tests/Arch/InterfacesTest.php new file mode 100644 index 0000000..6f0b224 --- /dev/null +++ b/tests/Arch/InterfacesTest.php @@ -0,0 +1,5 @@ +expect('Codinglabs\Yolo\Contracts') + ->toBeInterfaces(); diff --git a/tests/Arch/SecurityTest.php b/tests/Arch/SecurityTest.php new file mode 100644 index 0000000..7adcb77 --- /dev/null +++ b/tests/Arch/SecurityTest.php @@ -0,0 +1,3 @@ +preset()->php(); diff --git a/tests/Arch/StepsTest.php b/tests/Arch/StepsTest.php new file mode 100644 index 0000000..193beb7 --- /dev/null +++ b/tests/Arch/StepsTest.php @@ -0,0 +1,22 @@ +expect('Codinglabs\Yolo\Steps') + ->toBeInvokable() + ->toHaveSuffix('Step'); + +arch() + ->expect('Codinglabs\Yolo\Steps\Start\All') + ->toImplement('Codinglabs\Yolo\Contracts\RunsOnAws'); + +arch() + ->expect('Codinglabs\Yolo\Steps\Start\Queue') + ->toImplement('Codinglabs\Yolo\Contracts\RunsOnAwsQueue'); + +arch() + ->expect('Codinglabs\Yolo\Steps\Start\RunsOnAwsScheduler') + ->toImplement('Codinglabs\Yolo\Contracts\RunsOnAws'); + +arch() + ->expect('Codinglabs\Yolo\Steps\Start\Web') + ->toImplement('Codinglabs\Yolo\Contracts\RunsOnAwsWeb'); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..fd279ad --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,45 @@ +extend(Tests\TestCase::class)->in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..cfb05b6 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ + Date: Thu, 30 Jan 2025 11:21:49 +1000 Subject: [PATCH 17/40] wip --- src/Commands/InitCommand.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 55351e0..f039b94 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -15,8 +15,7 @@ protected function configure(): void { $this ->setName('init') - ->setDescription('Create the yolo.yml manifest in the current app root') - ->addOption('dry-run', null, null, 'Run the command without making changes'); + ->setDescription('Create the yolo.yml manifest in the current app root'); } public function handle(): void From b545b3f17f0c3b595105d85503db630d3be57175 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Thu, 30 Jan 2025 17:28:09 +1000 Subject: [PATCH 18/40] merge various improvements from main --- README.md | 106 ++++++++++++------ src/Aws.php | 6 +- ...repareCommand.php => AmiRotateCommand.php} | 6 +- src/Commands/Command.php | 3 +- src/Commands/InitCommand.php | 14 +++ .../Ami/CreateAutoScalingQueueGroupStep.php | 8 +- .../CreateAutoScalingSchedulerGroupStep.php | 8 +- .../Ami/CreateAutoScalingWebGroupStep.php | 7 +- src/Steps/Ami/StopAmiInstanceStep.php | 3 +- src/Steps/Landlord/SyncQueueAlarmStep.php | 1 + src/Steps/Landlord/SyncQueueStep.php | 1 + src/Steps/Tenant/SyncQueueAlarmStep.php | 1 + src/Steps/Tenant/SyncQueueStep.php | 1 + src/Yolo.php | 2 +- 14 files changed, 120 insertions(+), 47 deletions(-) rename src/Commands/{PrepareCommand.php => AmiRotateCommand.php} (93%) diff --git a/README.md b/README.md index 2f0c7d5..58dea36 100644 --- a/README.md +++ b/README.md @@ -12,20 +12,17 @@ ___ YOLO is designed for PHP developers who want to manage AWS using an infrastructure-as-code approach, using plain-old PHP rather than CloudFormation / Terraform / K8s / Elastic Beanstalk / . -While YOLO has been battle-tested on apps serving millions of requests per day, it is not supposed to be a -set-and-forget solution for busy apps, but rather allows you to grow and adapt your infrastructure as requirements -change over time. - -Within the Laravel ecosystem there are several high quality commercial alternatives like Forge, Vapor and Cloud, which -require less working knowledge of AWS. +> [!IMPORTANT] +> While YOLO has been battle-tested on apps serving millions of requests per day, it is not supposed to be a +> set-and-forget solution for busy apps, but rather allows you to proactively manage, grow and adapt your infrastructure +> as requirements +> change over time. It goes without saying, but use YOLO at your own risk. ## Prerequisites -Before getting started, ensure you have [AWS CLI](https://aws.amazon.com/cli/) installed. - -You'll also need access to an AWS account, and some knowledge of AWS services. +You'll need access to an AWS account, and some knowledge of AWS services. ### Permissions & Authentication @@ -54,63 +51,96 @@ any access keys provided in CI are using least-privileged scope. composer require codinglabsau/yolo ``` -## Usage - The entry point to the YOLO CLI is `vendor/bin/yolo` or `yolo` if you have `./vendor/bin` in your path. -To get up and running, you'll need to complete the following steps: +Run `yolo` to see the available commands. -1. Setup authentication and update your .env to point to the correct AWS profile -2. Initialise the YOLO manifest file -3. Run the install command -4. Run the deploy command +### Initialise YOLO -After the initial install, you can simply add `yolo deploy ` to your CI pipeline to deploy your app. +After composer installing, run `yolo init`. The init command does the following: -### `yolo init` +1. initialises the yolo.yml file in the app with a boilerplate production environment +2. adds some entries to `.gitignore` +3. prompts for a few bits of information to setup the manifest file -The init command is the first command to run after installing YOLO. It does the following things: +After initialising, you can customise the `yolo.yml` manifest file to suit your app's requirements. -- initialises the yolo.yml file in the app with a boilerplate production environment -- adds some entries to `.gitignore` -- prompts for a few bits of information to setup the manifest file +## Usage -After initialising, you can customise the `yolo.yml` manifest file to suit your app's requirements. +### Provisioning resources -### Sync Commands +YOLO is designed to create and manage all AWS resources required to run your application. -After initialising YOLO manifest, all remaining infrastructure commands are prefixed with `sync:`. +After initialising the YOLO manifest, the next step is to start provisioning resources on AWS. The sync commands are: - `yolo sync:network ` prepares the VPC, subnets, security groups and SSH keys -- `yolo sync:domain ` prepares domain resources (standard apps only) +- `yolo sync:standalone ` prepares standalone app resources (standalone apps only) - `yolo sync:landlord ` prepares landlord resources (multitenancy apps only) - `yolo sync:tenant ` prepares tenant resources (multitenancy apps only) - `yolo sync:compute ` prepares the compute resources - `yolo sync:ci ` prepares the continuous integration pipeline -### `yolo ami:create ` +Alternatively, you can run all commands with `yolo sync `. + +> [!TIP] +> All sync commands support a `--dry-run` argument; this is a great starting point to see what resources will be created +> or modified without any actual changes occurring on AWS. + +### Managing AMIs + +With all the low-level resource provisioned via the `sync` commands, the next step is to create an Amazon Machine +Image ( +AMI) with Ubuntu OS as the foundation. + +The AMI will be used as the base image for all server instances, and can be rotated +over time to bring in improvements, such as new PHP versions. + +#### Create an AMI + +Run `yolo ami:create ` to prepare an AMI. +> [!TIP] +> This takes a few minutes to complete + +#### Rotating the AMI + +To rotate in the new AMI, run `yolo ami:rotate `. + +You will be prompted to select the AMI (the new one should be at the top of the list). + +After selecting which AMI to use, new EC2 autoscaling groups will be created, and one instance will be launched in each +group using the new AMI. + +The yolo.yml manifest will also be configured with the new autoscaling groups. + +> [!NOTE] +> Rotating in a new AMI does not have any impact on existing traffic until the updated manifest is deployed - the +> previous +> deployment will continue serving requests and autoscaling as per normal. + +### Managing .env files -With all low-level resource provisioned, the next step is to create an AMI for EC2s. All server types will utilise this -AMI. +You'll need to push the initial .env file for the environment. Environment files are stored +in the S3 artefacts bucket, and retrieved during deployment. -### `yolo prepare ` +If you have an existing .env file, be sure to copy that over to `.env.` in the root of the app, otherwise +you can build on the stub provided by the `init` command. -Prepares autoscaling groups, EC2 launch template and alarms. +To push the .env file to the artefacts bucket, run `yolo env:push `. -### `yolo env:push ` +After the initial push, you can retrieve the .env file with `yolo env:pull `. -Prior to building the environment, you'll need to create `.env.` followed by `yolo env:push `. +### Building and deploying -### `yolo build ` +Builds can be created with `yolo build `. The build command takes care of building a deployment-ready directory in `./yolo`. -### `yolo deploy ` +Builds can be deployed with `yolo deploy `. -The build command takes care of building and deploying. You can run this locally, or in CI. The deploy command will call -the build step for you if a local build does not exist. +> [!TIP] +> You can also build and deploy in a single command with `yolo deploy `. ## Full yolo.yml example @@ -126,6 +156,7 @@ environments: aws: region: ap-southeast-2 bucket: + artefacts-bucket: cloudfront: alb: security-group-id: @@ -138,6 +169,7 @@ environments: instance-type: t3.small instance-profile: octane: true + key-pair: codedeploy: strategy: without-load-balancing|with-load-balancing diff --git a/src/Aws.php b/src/Aws.php index 784700a..6a54650 100644 --- a/src/Aws.php +++ b/src/Aws.php @@ -39,13 +39,17 @@ public static function runningInAwsSchedulerEnvironment(): bool return Helpers::app('runningInAwsSchedulerEnvironment'); } - public static function tags(array $tags = [], string $wrap = 'Tags'): array + public static function tags(array $tags = [], string $wrap = 'Tags', bool $associative = false): array { $tags = [ 'yolo:environment' => Helpers::app('environment'), ...$tags, ]; + if ($associative) { + return [$wrap => $tags]; + } + return [ $wrap => collect($tags) ->map(fn ($value, $key) => [ diff --git a/src/Commands/PrepareCommand.php b/src/Commands/AmiRotateCommand.php similarity index 93% rename from src/Commands/PrepareCommand.php rename to src/Commands/AmiRotateCommand.php index 42ebb10..08b2e66 100644 --- a/src/Commands/PrepareCommand.php +++ b/src/Commands/AmiRotateCommand.php @@ -10,12 +10,12 @@ use Symfony\Component\Console\Input\InputArgument; use function Laravel\Prompts\select; -class PrepareCommand extends SteppedCommand +class AmiRotateCommand extends SteppedCommand { use UsesEc2; protected array $steps = [ - // create new launch template version; prompts for the desired AMI ID + // create new launch template version Steps\Ami\CreateLaunchTemplateVersionStep::class, // scheduler group @@ -32,7 +32,7 @@ class PrepareCommand extends SteppedCommand protected function configure(): void { $this - ->setName('prepare') + ->setName('ami:rotate') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('dry-run', null, null, 'Run the command without making changes') ->addOption('no-progress', null, null, 'Hide the progress output') diff --git a/src/Commands/Command.php b/src/Commands/Command.php index 994a38f..e410cff 100644 --- a/src/Commands/Command.php +++ b/src/Commands/Command.php @@ -2,6 +2,7 @@ namespace Codinglabs\Yolo\Commands; +use Codinglabs\Yolo\Aws; use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Concerns\RegistersAws; @@ -56,7 +57,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int Helpers::app()->instance('environment', $this->argument('environment')); - if (! Helpers::keyedEnv('AWS_PROFILE')) { + if (! Aws::runningInAws() && ! Helpers::keyedEnv('AWS_PROFILE')) { error(sprintf("You need to specify YOLO_%s_AWS_PROFILE in your .env file before proceeding", strtoupper(Helpers::environment()))); return 1; } diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index f039b94..b328f55 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -30,6 +30,7 @@ public function handle(): void $this->gitIgnoreFilesAndDirectories(); $this->initialiseManifest(); + $this->initialiseEnv(); info('Manifest generated successfully.'); } @@ -87,4 +88,17 @@ protected function gitIgnoreFilesAndDirectories(): void ); } } + + protected function initialiseEnv(): void + { + if (! file_exists(Paths::base('.env.production'))) { + file_put_contents( + Paths::base('.env.production'), + "APP_ENV=production" . PHP_EOL . + "APP_KEY=" . PHP_EOL . + "APP_DEBUG=false" . PHP_EOL . + FILE_APPEND + ); + } + } } diff --git a/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php b/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php index 7b6be82..d6c548e 100644 --- a/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingQueueGroupStep.php @@ -21,7 +21,7 @@ class CreateAutoScalingQueueGroupStep implements Step public function __invoke(array $options): StepResult { if (! Arr::get($options, 'dry-run')) { - $name = Helpers::keyedResourceName(sprintf('queue-%s', Str::random(8))); + $name = Helpers::keyedResourceName(sprintf('%s-%s', ServerGroup::QUEUE->value, Str::random(8))); Aws::autoscaling()->createAutoScalingGroup([ ...static::autoScalingGroupPayload(), @@ -30,12 +30,18 @@ public function __invoke(array $options): StepResult 'MinSize' => 1, 'MaxSize' => 1, 'DesiredCapacity' => 1, + // special use case to include 'PropagateAtLaunch' attribute 'Tags' => [ [ 'Key' => 'Name', 'PropagateAtLaunch' => true, 'Value' => ServerGroup::QUEUE->value, ], + [ + 'Key' => 'yolo:environment', + 'Value' => Helpers::app('environment'), + 'PropagateAtLaunch' => true, + ], ], ], ]); diff --git a/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php b/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php index c499df9..e9cba0b 100644 --- a/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingSchedulerGroupStep.php @@ -21,7 +21,7 @@ class CreateAutoScalingSchedulerGroupStep implements Step public function __invoke(array $options): StepResult { if (! Arr::get($options, 'dry-run')) { - $name = Helpers::keyedResourceName(sprintf('scheduler-%s', Str::random(8))); + $name = Helpers::keyedResourceName(sprintf('%s-%s', ServerGroup::SCHEDULER->value, Str::random(8))); Aws::autoscaling()->createAutoScalingGroup([ ...static::autoScalingGroupPayload(), @@ -30,12 +30,18 @@ public function __invoke(array $options): StepResult 'MinSize' => 1, 'MaxSize' => 1, 'DesiredCapacity' => 1, + // special use case to include 'PropagateAtLaunch' attribute 'Tags' => [ [ 'Key' => 'Name', 'PropagateAtLaunch' => true, 'Value' => ServerGroup::SCHEDULER->value, ], + [ + 'Key' => 'yolo:environment', + 'Value' => Helpers::app('environment'), + 'PropagateAtLaunch' => true, + ], ], ], ]); diff --git a/src/Steps/Ami/CreateAutoScalingWebGroupStep.php b/src/Steps/Ami/CreateAutoScalingWebGroupStep.php index 4ca2e0e..a1207e7 100644 --- a/src/Steps/Ami/CreateAutoScalingWebGroupStep.php +++ b/src/Steps/Ami/CreateAutoScalingWebGroupStep.php @@ -19,7 +19,7 @@ class CreateAutoScalingWebGroupStep implements Step public function __invoke(array $options): StepResult { if (! Arr::get($options, 'dry-run')) { - $name = Helpers::keyedResourceName(sprintf('web-%s', Str::random(8))); + $name = Helpers::keyedResourceName(sprintf('%s-%s', ServerGroup::WEB->value, Str::random(8))); Aws::autoscaling()->createAutoScalingGroup([ ...static::autoScalingGroupPayload(), @@ -34,6 +34,11 @@ public function __invoke(array $options): StepResult 'PropagateAtLaunch' => true, 'Value' => ServerGroup::WEB->value, ], + [ + 'Key' => 'yolo:environment', + 'Value' => Helpers::app('environment'), + 'PropagateAtLaunch' => true, + ], ], ], ]); diff --git a/src/Steps/Ami/StopAmiInstanceStep.php b/src/Steps/Ami/StopAmiInstanceStep.php index d0d5c32..7136a55 100644 --- a/src/Steps/Ami/StopAmiInstanceStep.php +++ b/src/Steps/Ami/StopAmiInstanceStep.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Aws; use Codinglabs\Yolo\Helpers; +use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Concerns\UsesEc2; use Codinglabs\Yolo\Enums\StepResult; @@ -20,7 +21,7 @@ public function __invoke(): StepResult while (true) { // wait for instance to stop - if (static::ec2ByName('AMI', states: ['stopped'], throws: false)) { + if (AwsResources::ec2ByName('AMI', states: ['stopped'], throws: false)) { break; } diff --git a/src/Steps/Landlord/SyncQueueAlarmStep.php b/src/Steps/Landlord/SyncQueueAlarmStep.php index cee0afe..68d38cb 100644 --- a/src/Steps/Landlord/SyncQueueAlarmStep.php +++ b/src/Steps/Landlord/SyncQueueAlarmStep.php @@ -49,6 +49,7 @@ public function __invoke(array $options): StepResult 'TreatMissingData' => 'notBreaching', 'AlarmActions' => [$snsTopic['TopicArn']], 'OKActions' => [$snsTopic['TopicArn']], + ...Aws::tags(), ]); return StepResult::SYNCED; diff --git a/src/Steps/Landlord/SyncQueueStep.php b/src/Steps/Landlord/SyncQueueStep.php index d2619ea..8708c8c 100644 --- a/src/Steps/Landlord/SyncQueueStep.php +++ b/src/Steps/Landlord/SyncQueueStep.php @@ -26,6 +26,7 @@ public function __invoke(array $options): StepResult 'Attributes' => [ 'MessageRetentionPeriod' => '1209600', // 14 days ], + ...Aws::tags(wrap: 'tags', associative: true), ]); return StepResult::CREATED; diff --git a/src/Steps/Tenant/SyncQueueAlarmStep.php b/src/Steps/Tenant/SyncQueueAlarmStep.php index 011782e..540858a 100644 --- a/src/Steps/Tenant/SyncQueueAlarmStep.php +++ b/src/Steps/Tenant/SyncQueueAlarmStep.php @@ -49,6 +49,7 @@ public function __invoke(array $options): StepResult 'TreatMissingData' => 'notBreaching', 'AlarmActions' => [$snsTopic['TopicArn']], 'OKActions' => [$snsTopic['TopicArn']], + ...Aws::tags(), ]); return StepResult::SYNCED; diff --git a/src/Steps/Tenant/SyncQueueStep.php b/src/Steps/Tenant/SyncQueueStep.php index 4db11a5..2b7e434 100644 --- a/src/Steps/Tenant/SyncQueueStep.php +++ b/src/Steps/Tenant/SyncQueueStep.php @@ -26,6 +26,7 @@ public function __invoke(array $options): StepResult 'Attributes' => [ 'MessageRetentionPeriod' => '1209600', // 14 days ], + ...Aws::tags(wrap: 'tags', associative: true), ]); return StepResult::CREATED; diff --git a/src/Yolo.php b/src/Yolo.php index d6ffe85..1e43324 100644 --- a/src/Yolo.php +++ b/src/Yolo.php @@ -16,7 +16,7 @@ class Yolo // AWS Commands\AmiCreateCommand::class, Commands\AmiListCommand::class, - Commands\PrepareCommand::class, + Commands\AmiRotateCommand::class, Commands\CommandCommand::class, Commands\Ec2ListCommand::class, From 0a7f24045f6d449229c27f1df652f64e68d0c518 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Fri, 31 Jan 2025 13:56:48 +1000 Subject: [PATCH 19/40] wip --- README.md | 65 ++++++++----------- ...eateCommand.php => ImageCreateCommand.php} | 15 +++-- ...miListCommand.php => ImageListCommand.php} | 4 +- ...ateCommand.php => ImagePrepareCommand.php} | 14 ++-- src/Steps/Compute/SyncLaunchTemplateStep.php | 2 +- src/Steps/{Ami => Image}/CreateAmiStep.php | 2 +- .../CreateAutoScalingQueueGroupStep.php | 2 +- .../CreateAutoScalingSchedulerGroupStep.php | 2 +- .../CreateAutoScalingWebGroupStep.php | 2 +- .../CreateLaunchTemplateVersionStep.php | 2 +- .../CreateWebGroupCpuAlarmsStep.php | 2 +- .../{Ami => Image}/LaunchAmiInstanceStep.php | 2 +- .../{Ami => Image}/StopAmiInstanceStep.php | 2 +- .../TerminateAmiInstanceStep.php | 2 +- .../WaitForUserDataToExecuteStep.php | 2 +- src/Yolo.php | 14 ++-- 16 files changed, 63 insertions(+), 71 deletions(-) rename src/Commands/{AmiCreateCommand.php => ImageCreateCommand.php} (63%) rename src/Commands/{AmiListCommand.php => ImageListCommand.php} (94%) rename src/Commands/{AmiRotateCommand.php => ImagePrepareCommand.php} (83%) rename src/Steps/{Ami => Image}/CreateAmiStep.php (95%) rename src/Steps/{Ami => Image}/CreateAutoScalingQueueGroupStep.php (98%) rename src/Steps/{Ami => Image}/CreateAutoScalingSchedulerGroupStep.php (97%) rename src/Steps/{Ami => Image}/CreateAutoScalingWebGroupStep.php (98%) rename src/Steps/{Ami => Image}/CreateLaunchTemplateVersionStep.php (97%) rename src/Steps/{Ami => Image}/CreateWebGroupCpuAlarmsStep.php (98%) rename src/Steps/{Ami => Image}/LaunchAmiInstanceStep.php (98%) rename src/Steps/{Ami => Image}/StopAmiInstanceStep.php (94%) rename src/Steps/{Ami => Image}/TerminateAmiInstanceStep.php (92%) rename src/Steps/{Ami => Image}/WaitForUserDataToExecuteStep.php (94%) diff --git a/README.md b/README.md index 58dea36..60bf9ad 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ recommended. For CI environments like GitHub Actions, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are used instead. Ensure that any access keys provided in CI are using least-privileged scope. -## Installation +## Step 1: Installation -### Install With Composer +### a) Install With Composer ```bash composer require codinglabsau/yolo @@ -55,9 +55,9 @@ The entry point to the YOLO CLI is `vendor/bin/yolo` or `yolo` if you have `./ve Run `yolo` to see the available commands. -### Initialise YOLO +### b) Initialise yolo -After composer installing, run `yolo init`. The init command does the following: +Next, run `yolo init`. The init command does the following: 1. initialises the yolo.yml file in the app with a boilerplate production environment 2. adds some entries to `.gitignore` @@ -65,15 +65,14 @@ After composer installing, run `yolo init`. The init command does the following: After initialising, you can customise the `yolo.yml` manifest file to suit your app's requirements. -## Usage - -### Provisioning resources +## Step 2: Provision resources YOLO is designed to create and manage all AWS resources required to run your application. -After initialising the YOLO manifest, the next step is to start provisioning resources on AWS. +Provision all resources by running `yolo sync `. This command runs all `sync` commands in the correct +order. -The sync commands are: +The full list of available sync commands are: - `yolo sync:network ` prepares the VPC, subnets, security groups and SSH keys - `yolo sync:standalone ` prepares standalone app resources (standalone apps only) @@ -82,47 +81,41 @@ The sync commands are: - `yolo sync:compute ` prepares the compute resources - `yolo sync:ci ` prepares the continuous integration pipeline -Alternatively, you can run all commands with `yolo sync `. - > [!TIP] > All sync commands support a `--dry-run` argument; this is a great starting point to see what resources will be created > or modified without any actual changes occurring on AWS. -### Managing AMIs +## Step 3: Prepare a server image With all the low-level resource provisioned via the `sync` commands, the next step is to create an Amazon Machine Image ( AMI) with Ubuntu OS as the foundation. -The AMI will be used as the base image for all server instances, and can be rotated +The image will be used as the initial disk image for all server instances, and can be updated over time to bring in improvements, such as new PHP versions. -#### Create an AMI +### a) Create an image -Run `yolo ami:create ` to prepare an AMI. -> [!TIP] -> This takes a few minutes to complete +Run `yolo image:create ` to generate a new AMI. -#### Rotating the AMI +### b) Prepare the image for traffic -To rotate in the new AMI, run `yolo ami:rotate `. +To prepare the new image for traffic, run `yolo image:prepare `. -You will be prompted to select the AMI (the new one should be at the top of the list). +You will be prompted to select the AMI (the newest image will be at the top of the list). -After selecting which AMI to use, new EC2 autoscaling groups will be created, and one instance will be launched in each -group using the new AMI. +After selecting which image to use, servers will be spun up, ready to receive app deployments. -The yolo.yml manifest will also be configured with the new autoscaling groups. +The yolo.yml manifest will also be configured to point to the new autoscaling groups. > [!NOTE] -> Rotating in a new AMI does not have any impact on existing traffic until the updated manifest is deployed - the -> previous -> deployment will continue serving requests and autoscaling as per normal. +> Rotating in a new image does not have any impact on existing traffic until the updated manifest is deployed - the +> previous deployment will continue serving requests and autoscaling as per normal. -### Managing .env files +## Step 4. Setup .env file -You'll need to push the initial .env file for the environment. Environment files are stored -in the S3 artefacts bucket, and retrieved during deployment. +You'll need to push the initial .env file for the environment. Environment files are stored in the S3 artefacts bucket, +and retrieved during deployment. If you have an existing .env file, be sure to copy that over to `.env.` in the root of the app, otherwise you can build on the stub provided by the `init` command. @@ -131,7 +124,7 @@ To push the .env file to the artefacts bucket, run `yolo env:push ` After the initial push, you can retrieve the .env file with `yolo env:pull `. -### Building and deploying +## Step 5. Building and deploying Builds can be created with `yolo build `. @@ -197,14 +190,12 @@ To debug or add features to YOLO, it is recommended to symlink to the local repo Add this to composer.json with the path to the local repository: -```json - // ... - +``` "repositories": [ -{ -"type": "path", -"url": "/Users/username/code/yolo" -} + { + "type": "path", + "url": "/Users/username/code/yolo" + } ], ``` diff --git a/src/Commands/AmiCreateCommand.php b/src/Commands/ImageCreateCommand.php similarity index 63% rename from src/Commands/AmiCreateCommand.php rename to src/Commands/ImageCreateCommand.php index 057e94e..f63a57d 100644 --- a/src/Commands/AmiCreateCommand.php +++ b/src/Commands/ImageCreateCommand.php @@ -5,27 +5,28 @@ use Codinglabs\Yolo\Steps; use Symfony\Component\Console\Input\InputArgument; -class AmiCreateCommand extends SteppedCommand +class ImageCreateCommand extends SteppedCommand { protected array $steps = [ Steps\Ensures\EnsureKeyPairExistsStep::class, Steps\Ensures\EnsureLaunchTemplateExistsStep::class, - Steps\Ami\LaunchAmiInstanceStep::class, - Steps\Ami\WaitForUserDataToExecuteStep::class, + Steps\Image\LaunchAmiInstanceStep::class, + Steps\Image\WaitForUserDataToExecuteStep::class, Steps\Ensures\EnsurePhpInstalledStep::class, Steps\Ensures\EnsureSwooleInstalledStep::class, Steps\Ensures\EnsureNodeInstalledStep::class, Steps\Ensures\EnsureNginxInstalledStep::class, - Steps\Ami\StopAmiInstanceStep::class, - Steps\Ami\CreateAmiStep::class, - Steps\Ami\TerminateAmiInstanceStep::class, + Steps\Image\StopAmiInstanceStep::class, + Steps\Image\CreateAmiStep::class, + Steps\Image\TerminateAmiInstanceStep::class, ]; protected function configure(): void { $this - ->setName('ami:create') + ->setName('image:create') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') + ->addOption('no-progress', null, null, 'Hide the progress output') ->setDescription('Prepare a new Amazon Machine Image'); } } diff --git a/src/Commands/AmiListCommand.php b/src/Commands/ImageListCommand.php similarity index 94% rename from src/Commands/AmiListCommand.php rename to src/Commands/ImageListCommand.php index 59a2afb..cb326fe 100644 --- a/src/Commands/AmiListCommand.php +++ b/src/Commands/ImageListCommand.php @@ -7,12 +7,12 @@ use Symfony\Component\Console\Input\InputArgument; use function Laravel\Prompts\table; -class AmiListCommand extends Command +class ImageListCommand extends Command { protected function configure(): void { $this - ->setName('ami:list') + ->setName('image:list') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->setDescription('List the available Amazon Machine Images in the given environment'); } diff --git a/src/Commands/AmiRotateCommand.php b/src/Commands/ImagePrepareCommand.php similarity index 83% rename from src/Commands/AmiRotateCommand.php rename to src/Commands/ImagePrepareCommand.php index 08b2e66..76642c8 100644 --- a/src/Commands/AmiRotateCommand.php +++ b/src/Commands/ImagePrepareCommand.php @@ -10,29 +10,29 @@ use Symfony\Component\Console\Input\InputArgument; use function Laravel\Prompts\select; -class AmiRotateCommand extends SteppedCommand +class ImagePrepareCommand extends SteppedCommand { use UsesEc2; protected array $steps = [ // create new launch template version - Steps\Ami\CreateLaunchTemplateVersionStep::class, + Steps\Image\CreateLaunchTemplateVersionStep::class, // scheduler group - Steps\Ami\CreateAutoScalingSchedulerGroupStep::class, + Steps\Image\CreateAutoScalingSchedulerGroupStep::class, // queue group - Steps\Ami\CreateAutoScalingQueueGroupStep::class, + Steps\Image\CreateAutoScalingQueueGroupStep::class, // web group - Steps\Ami\CreateAutoScalingWebGroupStep::class, - Steps\Ami\CreateWebGroupCpuAlarmsStep::class, + Steps\Image\CreateAutoScalingWebGroupStep::class, + Steps\Image\CreateWebGroupCpuAlarmsStep::class, ]; protected function configure(): void { $this - ->setName('ami:rotate') + ->setName('image:sync') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('dry-run', null, null, 'Run the command without making changes') ->addOption('no-progress', null, null, 'Hide the progress output') diff --git a/src/Steps/Compute/SyncLaunchTemplateStep.php b/src/Steps/Compute/SyncLaunchTemplateStep.php index 0c59534..1bcd0a8 100644 --- a/src/Steps/Compute/SyncLaunchTemplateStep.php +++ b/src/Steps/Compute/SyncLaunchTemplateStep.php @@ -14,7 +14,7 @@ class SyncLaunchTemplateStep implements Step public function __invoke(array $options): StepResult { try { - // ensure the launch template exists; refer to "yolo ami:create" + // ensure the launch template exists; refer to "yolo image:create" // to create new launch template versions with synced attributes. AwsResources::launchTemplate(); return StepResult::SYNCED; diff --git a/src/Steps/Ami/CreateAmiStep.php b/src/Steps/Image/CreateAmiStep.php similarity index 95% rename from src/Steps/Ami/CreateAmiStep.php rename to src/Steps/Image/CreateAmiStep.php index 8936d03..de4e518 100644 --- a/src/Steps/Ami/CreateAmiStep.php +++ b/src/Steps/Image/CreateAmiStep.php @@ -1,6 +1,6 @@ Date: Fri, 31 Jan 2025 16:06:50 +1000 Subject: [PATCH 20/40] wip --- src/Concerns/UsesEc2.php | 8 ++++---- src/Enums/{SecurityGroups.php => SecurityGroup.php} | 2 +- src/Steps/Network/SyncEc2SecurityGroupStep.php | 4 ++-- src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php | 4 ++-- src/Steps/Network/SyncRdsSecurityGroupStep.php | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) rename src/Enums/{SecurityGroups.php => SecurityGroup.php} (88%) diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index ea0e385..e8fd1bf 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -7,7 +7,7 @@ use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; -use Codinglabs\Yolo\Enums\SecurityGroups; +use Codinglabs\Yolo\Enums\SecurityGroup; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; trait UsesEc2 @@ -88,7 +88,7 @@ public static function loadBalancerSecurityGroup(): array return static::$loadBalancerSecurityGroup; } - static::$loadBalancerSecurityGroup = static::securityGroupByName(SecurityGroups::LOAD_BALANCER_SECURITY_GROUP); + static::$loadBalancerSecurityGroup = static::securityGroupByName(SecurityGroup::LOAD_BALANCER_SECURITY_GROUP); return static::$loadBalancerSecurityGroup; } @@ -102,7 +102,7 @@ public static function ec2SecurityGroup(): array return static::$ec2SecurityGroup; } - static::$ec2SecurityGroup = static::securityGroupByName(SecurityGroups::EC2_SECURITY_GROUP); + static::$ec2SecurityGroup = static::securityGroupByName(SecurityGroup::EC2_SECURITY_GROUP); return static::$ec2SecurityGroup; } @@ -116,7 +116,7 @@ public static function rdsSecurityGroup(): array return static::$rdsSecurityGroup; } - static::$rdsSecurityGroup = static::securityGroupByName(SecurityGroups::RDS_SECURITY_GROUP); + static::$rdsSecurityGroup = static::securityGroupByName(SecurityGroup::RDS_SECURITY_GROUP); return static::$rdsSecurityGroup; } diff --git a/src/Enums/SecurityGroups.php b/src/Enums/SecurityGroup.php similarity index 88% rename from src/Enums/SecurityGroups.php rename to src/Enums/SecurityGroup.php index a6266b3..af157c0 100644 --- a/src/Enums/SecurityGroups.php +++ b/src/Enums/SecurityGroup.php @@ -2,7 +2,7 @@ namespace Codinglabs\Yolo\Enums; -enum SecurityGroups: string +enum SecurityGroup: string { case EC2_SECURITY_GROUP = 'ec2-security-group'; case LOAD_BALANCER_SECURITY_GROUP = 'load-balancer-security-group'; diff --git a/src/Steps/Network/SyncEc2SecurityGroupStep.php b/src/Steps/Network/SyncEc2SecurityGroupStep.php index b4034b4..81b08f8 100644 --- a/src/Steps/Network/SyncEc2SecurityGroupStep.php +++ b/src/Steps/Network/SyncEc2SecurityGroupStep.php @@ -8,7 +8,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Enums\SecurityGroups; +use Codinglabs\Yolo\Enums\SecurityGroup; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; class SyncEc2SecurityGroupStep implements Step @@ -21,7 +21,7 @@ public function __invoke(array $options): StepResult return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { - $name = Helpers::keyedResourceName(SecurityGroups::EC2_SECURITY_GROUP, exclusive: false); + $name = Helpers::keyedResourceName(SecurityGroup::EC2_SECURITY_GROUP, exclusive: false); Aws::ec2()->createSecurityGroup([ 'Description' => 'Enable load balancer and SSH traffic', diff --git a/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php b/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php index 51c5b4b..dda4927 100644 --- a/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php +++ b/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php @@ -8,7 +8,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Enums\SecurityGroups; +use Codinglabs\Yolo\Enums\SecurityGroup; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; class SyncLoadBalancerSecurityGroupStep implements Step @@ -21,7 +21,7 @@ public function __invoke(array $options): StepResult return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { - $name = Helpers::keyedResourceName(SecurityGroups::LOAD_BALANCER_SECURITY_GROUP, exclusive: false); + $name = Helpers::keyedResourceName(SecurityGroup::LOAD_BALANCER_SECURITY_GROUP, exclusive: false); Aws::ec2()->createSecurityGroup([ 'Description' => 'Enable HTTP and HTTPS from anywhere', diff --git a/src/Steps/Network/SyncRdsSecurityGroupStep.php b/src/Steps/Network/SyncRdsSecurityGroupStep.php index c3897d8..776420f 100644 --- a/src/Steps/Network/SyncRdsSecurityGroupStep.php +++ b/src/Steps/Network/SyncRdsSecurityGroupStep.php @@ -8,7 +8,7 @@ use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Enums\SecurityGroups; +use Codinglabs\Yolo\Enums\SecurityGroup; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; class SyncRdsSecurityGroupStep implements Step @@ -21,7 +21,7 @@ public function __invoke(array $options): StepResult return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { - $name = Helpers::keyedResourceName(SecurityGroups::RDS_SECURITY_GROUP, exclusive: false); + $name = Helpers::keyedResourceName(SecurityGroup::RDS_SECURITY_GROUP, exclusive: false); Aws::ec2()->createSecurityGroup([ 'Description' => 'Enable EC2 to connect to RDS', From 3346e79887c80eb52d2eae8794ec7703f2d0d597 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Fri, 31 Jan 2025 16:09:20 +1000 Subject: [PATCH 21/40] wip --- README.md | 2 ++ src/Steps/Image/LaunchAmiInstanceStep.php | 18 ++++++++---------- src/Steps/Image/StopAmiInstanceStep.php | 2 +- .../SyncLoadBalancerSecurityGroupStep.php | 1 - 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 60bf9ad..53eeb22 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,8 @@ name: codinglabs environments: production: aws: + account-id: + vpc: region: ap-southeast-2 bucket: artefacts-bucket: diff --git a/src/Steps/Image/LaunchAmiInstanceStep.php b/src/Steps/Image/LaunchAmiInstanceStep.php index f41051e..09d6277 100644 --- a/src/Steps/Image/LaunchAmiInstanceStep.php +++ b/src/Steps/Image/LaunchAmiInstanceStep.php @@ -5,6 +5,7 @@ use Codinglabs\Yolo\Aws; use Codinglabs\Yolo\Paths; use Codinglabs\Yolo\Helpers; +use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\AwsResources; use Codinglabs\Yolo\Contracts\Step; use Codinglabs\Yolo\Enums\StepResult; @@ -14,12 +15,14 @@ class LaunchAmiInstanceStep implements Step { public function __invoke(): StepResult { + $name = Helpers::keyedResourceName('ami'); + if ($instance = AwsResources::ec2ByName( - 'AMI', + $name, states: ['pending', 'running', 'stopping', 'stopped'], throws: false )) { - throw new ResourceExistsException("AMI instance already exists in state '{$instance['State']['Name']}'. It must be manually terminated before creating a new AMI."); + throw new ResourceExistsException(sprintf("AMI instance %s already exists in state '%s'. It must be manually terminated before creating a new AMI.", $name, $instance['State']['Name'])); } Aws::ec2()->runInstances([ @@ -30,12 +33,7 @@ public function __invoke(): StepResult 'TagSpecifications' => [ [ 'ResourceType' => 'instance', - 'Tags' => [ - [ - 'Key' => 'Name', - 'Value' => 'AMI', - ], - ], + ...Aws::tags(['Name' => $name]), ], ], @@ -54,7 +52,7 @@ public function __invoke(): StepResult 'InstanceType' => 't3.xlarge', // use the existing key pair - 'KeyName' => Helpers::keyedResourceName(exclusive: false), + 'KeyName' => Manifest::get('aws.ec2.key-pair', Helpers::keyedResourceName(exclusive: false)), // 1 server only per favor (min+max are both required) 'MaxCount' => 1, @@ -77,7 +75,7 @@ public function __invoke(): StepResult while (true) { // wait for instance to be running with an assigned public IP address - if ($instance = AwsResources::ec2ByName('AMI', throws: false)) { + if ($instance = AwsResources::ec2ByName($name, throws: false)) { Helpers::app()->singleton('amiInstanceId', fn () => $instance['InstanceId']); Helpers::app()->singleton('amiIp', fn () => $instance['PublicIpAddress']); break; diff --git a/src/Steps/Image/StopAmiInstanceStep.php b/src/Steps/Image/StopAmiInstanceStep.php index 55cc770..9f6f507 100644 --- a/src/Steps/Image/StopAmiInstanceStep.php +++ b/src/Steps/Image/StopAmiInstanceStep.php @@ -21,7 +21,7 @@ public function __invoke(): StepResult while (true) { // wait for instance to stop - if (AwsResources::ec2ByName('AMI', states: ['stopped'], throws: false)) { + if (AwsResources::ec2ByName(Helpers::keyedResourceName('ami'), states: ['stopped'], throws: false)) { break; } diff --git a/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php b/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php index dda4927..4457823 100644 --- a/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php +++ b/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php @@ -17,7 +17,6 @@ public function __invoke(array $options): StepResult { try { AwsResources::loadBalancerSecurityGroup(); - return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { From 038a4a64fbfb9324cc6f83e61507860bb533e6e5 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Thu, 6 Feb 2025 17:07:07 +1000 Subject: [PATCH 22/40] sync fixes from main --- README.md | 61 ++++++++++++- src/Commands/ImageListCommand.php | 2 +- src/Commands/ImagePrepareCommand.php | 4 +- src/Concerns/DetectsSubdomains.php | 11 +++ src/Concerns/SyncsRecordSets.php | 86 ++++++++++++------- src/Concerns/UsesEc2.php | 4 +- src/Manifest.php | 15 +++- .../Deploy/SyncMultitenancyRecordSetStep.php | 1 - .../Deploy/SyncStandaloneRecordSetStep.php | 1 - .../Image/CreateWebGroupCpuAlarmsStep.php | 2 + src/Steps/Standalone/SyncQueueAlarmStep.php | 1 + .../Start/Web/SyncNginxConfigurationStep.php | 53 +++++++----- stubs/nginx/forward_non_www | 6 -- stubs/nginx/forward_www | 6 -- stubs/nginx/redirect | 6 ++ 15 files changed, 179 insertions(+), 80 deletions(-) create mode 100644 src/Concerns/DetectsSubdomains.php delete mode 100644 stubs/nginx/forward_non_www delete mode 100644 stubs/nginx/forward_www create mode 100644 stubs/nginx/redirect diff --git a/README.md b/README.md index 53eeb22..a3fd417 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ It goes without saying, but use YOLO at your own risk. You'll need access to an AWS account, and some knowledge of AWS services. +### Domains on Route53 + +The domains for your app should be added to Route53 on the same AWS account as where the app is hosted in advance. + ### Permissions & Authentication YOLO uses AWS profiles for authentication. @@ -135,9 +139,9 @@ Builds can be deployed with `yolo deploy `. > [!TIP] > You can also build and deploy in a single command with `yolo deploy `. -## Full yolo.yml example +## yolo.yml -This is a complete yolo.yml file, showing default values where applicable. +This is a complete yolo.yml manifest file, showing default values where applicable. Note that some keys are intentionally omitted from the stub generated by `yolo init`. @@ -174,8 +178,16 @@ environments: - npm run build - rm -rf package-lock.json resources/js resources/css node_modules database/seeders database/factories resources/seeding - domain: codinglabs.com.au - www: false + domain: example.com # standalone apps only + apex: # standalone apps only + + tenants: # multi-tenanted apps only + boating: # unique key for the tenant + domain: boating-with-yolo.com + + fishing: # unique key for the tenant + domain: fishing-with-yolo.com + pulse-worker: false mysqldump: false @@ -186,6 +198,47 @@ environments: - php artisan optimize ``` +### Domains + +Applications hosted with yolo can be served on any domain or subdomain that you own. + +The domain should be added to Route53 in advance. + +For a standalone application, the domain key can be used: + +```yaml + domain: codinglabs.com.au +``` + +In this example, the app will be served on `codinglabs.com.au`, and `www.codinglabs.com.au` will redirect to +`codinglabs.com.au`. + +If the application is served on any subdomain (including www.) you'll need to specify the apex record as well. + +```yaml + apex: codinglabs.com.au + domain: www.codinglabs.com.au +``` + +In this example, the app will be served on `www.codinglabs.com.au`, and `codinglabs.com.au` will redirect to +`www.codinglabs.com.au`. + +Multi-tenant applications follow the same logic, except that domains are configured under the `tenants` key. + +```yaml + tenants: + boating: + domain: boating.outdoors-with-yolo.com + apex: outdoors-with-yolo.com + + camping: + domain: camping.outdoors-with-yolo.com + apex: outdoors-with-yolo.com + + fishing: + domain: fishing-with-yolo.com +``` + ## Development To debug or add features to YOLO, it is recommended to symlink to the local repository. diff --git a/src/Commands/ImageListCommand.php b/src/Commands/ImageListCommand.php index cb326fe..5fcbebf 100644 --- a/src/Commands/ImageListCommand.php +++ b/src/Commands/ImageListCommand.php @@ -22,7 +22,7 @@ public function handle(): void table( ['AMI Name', 'AMI ID', 'Visibility', 'State', 'Creation Date', 'Last Launched'], collect(Aws::ec2()->describeImages(['Owners' => ['self']])['Images']) - ->sortByDesc('LastLaunchedTime') + ->sortByDesc('CreationDate') ->map(fn ($image) => [ $image['Name'], $image['ImageId'], diff --git a/src/Commands/ImagePrepareCommand.php b/src/Commands/ImagePrepareCommand.php index 76642c8..3da7f6a 100644 --- a/src/Commands/ImagePrepareCommand.php +++ b/src/Commands/ImagePrepareCommand.php @@ -32,7 +32,7 @@ class ImagePrepareCommand extends SteppedCommand protected function configure(): void { $this - ->setName('image:sync') + ->setName('image:prepare') ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('dry-run', null, null, 'Run the command without making changes') ->addOption('no-progress', null, null, 'Hide the progress output') @@ -44,7 +44,7 @@ public function handle(): void { $amis = collect(Aws::ec2()->describeImages(['Owners' => ['self']])['Images']) ->filter(fn (array $image) => $image['State'] === 'available') - ->sortByDesc('LastLaunchedTime') + ->sortByDesc('CreationDate') ->mapWithKeys(fn (array $image) => [ $image['ImageId'] => sprintf( '%s (%s) - created %s', diff --git a/src/Concerns/DetectsSubdomains.php b/src/Concerns/DetectsSubdomains.php new file mode 100644 index 0000000..ede1a16 --- /dev/null +++ b/src/Concerns/DetectsSubdomains.php @@ -0,0 +1,11 @@ +changeResourceRecordSets([ + 'ChangeBatch' => [ + 'Changes' => $this->generateChanges($apex, $domain), + 'Comment' => 'Created by yolo CLI', + ], + 'HostedZoneId' => AwsResources::hostedZone($apex)['Id'], + ]); + } + + protected function generateChanges(string $apex, string $domain): array { $ALB = AwsResources::loadBalancer(); - $changes = [ - // naked domain alias - [ - 'Action' => 'UPSERT', - 'ResourceRecordSet' => [ - 'AliasTarget' => [ - 'DNSName' => $ALB['DNSName'], - 'EvaluateTargetHealth' => false, - 'HostedZoneId' => $ALB['CanonicalHostedZoneId'], + // handle apex and www. subdomains + if ($this->domainHasWwwSubdomain($apex, $domain)) { + // apex record, such as codinglabs.com.au + return [ + [ + 'Action' => 'UPSERT', + 'ResourceRecordSet' => [ + 'AliasTarget' => [ + 'DNSName' => $ALB['DNSName'], + 'HostedZoneId' => $ALB['CanonicalHostedZoneId'], + 'EvaluateTargetHealth' => false, + ], + 'Name' => $apex, + 'Type' => 'A', ], - 'Name' => $domain, - 'Type' => 'A', ], - ], - ]; - - if (! $subdomain) { - // add a www. domain alias for non-subdomains - $changes[] = [ - 'Action' => 'UPSERT', - 'ResourceRecordSet' => [ - 'AliasTarget' => [ - 'DNSName' => $ALB['DNSName'], - 'EvaluateTargetHealth' => false, - 'HostedZoneId' => $ALB['CanonicalHostedZoneId'], + [ + 'Action' => 'UPSERT', + 'ResourceRecordSet' => [ + 'AliasTarget' => [ + 'DNSName' => $ALB['DNSName'], + 'HostedZoneId' => $ALB['CanonicalHostedZoneId'], + 'EvaluateTargetHealth' => false, + ], + 'Name' => str_starts_with($domain, 'www.') + ? $domain + : "www.$domain", + 'Type' => 'A', ], - 'Name' => "www.$domain", - 'Type' => 'A', - ], + ] ]; } - Aws::route53()->changeResourceRecordSets([ - 'ChangeBatch' => [ - 'Changes' => $changes, - 'Comment' => 'Created by yolo CLI', + // subdomain record, like foo.codinglabs.com.au + return [ + 'Action' => 'UPSERT', + 'ResourceRecordSet' => [ + 'AliasTarget' => [ + 'DNSName' => $ALB['DNSName'], + 'HostedZoneId' => $ALB['CanonicalHostedZoneId'], + 'EvaluateTargetHealth' => false, + ], + 'Name' => $domain, + 'Type' => 'A', ], - 'HostedZoneId' => AwsResources::hostedZone($apex)['Id'], - ]); + ]; } } diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index e8fd1bf..0cb8b76 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -173,7 +173,7 @@ public static function launchTemplatePayload(): array 'Name' => Manifest::get('aws.ec2.instance-profile'), ], 'InstanceType' => Manifest::get('aws.ec2.instance-type'), - 'KeyName' => Helpers::keyedResourceName(exclusive: false), + 'KeyName' => Manifest::get('aws.ec2.key-pair', Helpers::keyedResourceName(exclusive: false)), 'SecurityGroupIds' => [ AwsResources::ec2SecurityGroup()['GroupId'], ], @@ -198,7 +198,7 @@ public static function vpc(): array return static::$vpc; } - $name = Helpers::keyedResourceName(exclusive: false); + $name = Manifest::get('aws.vpc', Helpers::keyedResourceName(exclusive: false)); $vpcs = Aws::ec2()->describeVpcs([ 'Filters' => [ diff --git a/src/Manifest.php b/src/Manifest.php index 117f82a..6cf1f0d 100644 --- a/src/Manifest.php +++ b/src/Manifest.php @@ -61,7 +61,13 @@ public static function apex(): string } // prefer the apex key when specified - return static::get('apex', static::get('domain')); + $apex = static::get('apex', static::get('domain')); + + if (str_starts_with($apex, 'www.')) { + return throw new IntegrityCheckException(sprintf("The apex record %s cannot start with 'www'.", $apex)); + } + + return $apex; } public static function isMultitenanted(): bool @@ -73,7 +79,6 @@ public static function isMultitenanted(): bool * @return array */ @@ -82,9 +87,11 @@ public static function tenants(): array return collect(static::get('tenants')) ->mapWithKeys(function (array $config, string $tenantId) { // normalise tenant config - $config['subdomain'] = array_key_exists('apex', $config); $config['apex'] = $config['apex'] ?? $config['domain']; - $config['www'] = array_key_exists('www', $config) && $config['www']; + + if (str_starts_with($config['apex'], 'www.')) { + return throw new IntegrityCheckException(sprintf("The apex record %s cannot start with 'www'.", $config['apex'])); + } return [$tenantId => $config]; })->toArray(); diff --git a/src/Steps/Deploy/SyncMultitenancyRecordSetStep.php b/src/Steps/Deploy/SyncMultitenancyRecordSetStep.php index acbdd14..684e1da 100644 --- a/src/Steps/Deploy/SyncMultitenancyRecordSetStep.php +++ b/src/Steps/Deploy/SyncMultitenancyRecordSetStep.php @@ -17,7 +17,6 @@ public function __invoke(array $options): StepResult $this->syncRecordSet( apex: $this->config['apex'], domain: $this->config['domain'], - subdomain: $this->config['subdomain'] ); return StepResult::SYNCED; diff --git a/src/Steps/Deploy/SyncStandaloneRecordSetStep.php b/src/Steps/Deploy/SyncStandaloneRecordSetStep.php index e0b825b..074586a 100644 --- a/src/Steps/Deploy/SyncStandaloneRecordSetStep.php +++ b/src/Steps/Deploy/SyncStandaloneRecordSetStep.php @@ -18,7 +18,6 @@ public function __invoke(array $options): StepResult $this->syncRecordSet( apex: Manifest::apex(), domain: Manifest::get('domain'), - subdomain: Manifest::get('domain', default: false) ); return StepResult::SYNCED; diff --git a/src/Steps/Image/CreateWebGroupCpuAlarmsStep.php b/src/Steps/Image/CreateWebGroupCpuAlarmsStep.php index c4275e4..d95a2b6 100644 --- a/src/Steps/Image/CreateWebGroupCpuAlarmsStep.php +++ b/src/Steps/Image/CreateWebGroupCpuAlarmsStep.php @@ -47,6 +47,7 @@ public function __invoke(array $options): StepResult 'OKActions' => [ $scaleDownPolicy['PolicyARN'], ], + ...Aws::tags(), ]); $alarmName = Helpers::keyedResourceName(sprintf('web-cpu-critical-alarm-%s', Str::random(8))); @@ -76,6 +77,7 @@ public function __invoke(array $options): StepResult 'OKActions' => [ $snsTopic['TopicArn'], ], + ...Aws::tags(), ]); return StepResult::SYNCED; diff --git a/src/Steps/Standalone/SyncQueueAlarmStep.php b/src/Steps/Standalone/SyncQueueAlarmStep.php index 49d329e..f33576b 100644 --- a/src/Steps/Standalone/SyncQueueAlarmStep.php +++ b/src/Steps/Standalone/SyncQueueAlarmStep.php @@ -49,6 +49,7 @@ public function __invoke(array $options): StepResult 'TreatMissingData' => 'notBreaching', 'AlarmActions' => [$snsTopic['TopicArn']], 'OKActions' => [$snsTopic['TopicArn']], + ...Aws::tags(), ]); return StepResult::SYNCED; diff --git a/src/Steps/Start/Web/SyncNginxConfigurationStep.php b/src/Steps/Start/Web/SyncNginxConfigurationStep.php index a948aba..e0a35a7 100644 --- a/src/Steps/Start/Web/SyncNginxConfigurationStep.php +++ b/src/Steps/Start/Web/SyncNginxConfigurationStep.php @@ -8,9 +8,12 @@ use Codinglabs\Yolo\Enums\StepResult; use Symfony\Component\Process\Process; use Codinglabs\Yolo\Contracts\RunsOnAwsWeb; +use Codinglabs\Yolo\Concerns\DetectsSubdomains; class SyncNginxConfigurationStep implements RunsOnAwsWeb { + use DetectsSubdomains; + public function __invoke(): StepResult { // drop the default nginx vhost, using -f force to suppress file does not exist error @@ -34,7 +37,7 @@ public function __invoke(): StepResult $filename = Helpers::keyedResourceName(); - // create a catch-all vhost with forwarding rules + // create a catch-all vhost with redirecting rules file_put_contents( "/etc/nginx/sites-available/$filename", str_replace( @@ -65,44 +68,54 @@ protected function forwardingRules(): string if (Manifest::isMultitenanted()) { return collect(Manifest::tenants()) ->map(function (array $tenant) { - if ($tenant['subdomain']) { - // skip creating a non-www forwarding rule for subdomains - return "#{$tenant['domain']} is a subdomain, skipping non-www forwarding"; + if (! $this->domainHasWwwSubdomain($tenant['apex'], $tenant['domain'])) { + return sprintf("# %s is a subdomain, skipping redirects", $tenant['domain']); } - $forwardingTemplate = $tenant['www'] - ? file_get_contents(Paths::stubs("nginx/forward_non_www")) - : file_get_contents(Paths::stubs("nginx/forward_www")); + $redirectTemplate = file_get_contents(Paths::stubs('nginx/redirect')); return str_replace( search: [ - '{NON_WWW_FQDN}', - '{WWW_FQDN}', + '{FROM}', + '{TO}', ], replace: [ - $tenant['domain'], - "www." . $tenant['domain'], + str_starts_with($tenant['domain'], 'www.') + ? $tenant['apex'] + : "www.{$tenant['domain']}", + str_starts_with($tenant['domain'], 'www.') + ? $tenant['domain'] + : $tenant['apex'], ], - subject: $forwardingTemplate + subject: $redirectTemplate ); }) ->join("\n"); } - $forwardingTemplate = Manifest::get('www') - ? file_get_contents(Paths::stubs("nginx/forward_non_www")) - : file_get_contents(Paths::stubs("nginx/forward_www")); + if (! $this->domainHasWwwSubdomain(Manifest::get('apex'), Manifest::get('domain'))) { + return sprintf("# %s is a subdomain, skipping redirects", Manifest::get('domain')); + } + + $redirectTemplate = file_get_contents(Paths::stubs('nginx/redirect')); + + $apex = Manifest::apex(); + $domain = Manifest::get('domain'); return str_replace( search: [ - '{NON_WWW_FQDN}', - '{WWW_FQDN}', + '{FROM}', + '{TO}', ], replace: [ - Manifest::get('domain'), - "www." . Manifest::get('domain'), + str_starts_with($domain, 'www.') + ? $apex + : "www.$domain", + str_starts_with($domain, 'www.') + ? $domain + : $apex, ], - subject: $forwardingTemplate + subject: $redirectTemplate ); } diff --git a/stubs/nginx/forward_non_www b/stubs/nginx/forward_non_www deleted file mode 100644 index fcef21a..0000000 --- a/stubs/nginx/forward_non_www +++ /dev/null @@ -1,6 +0,0 @@ -server { - listen 80; - server_name {NON_WWW_FQDN}; - - return 301 https://{WWW_FQDN}$request_uri; -} diff --git a/stubs/nginx/forward_www b/stubs/nginx/forward_www deleted file mode 100644 index fe9950e..0000000 --- a/stubs/nginx/forward_www +++ /dev/null @@ -1,6 +0,0 @@ -server { - listen 80; - server_name {WWW_FQDN}; - - return 301 https://{NON_WWW_FQDN}$request_uri; -} diff --git a/stubs/nginx/redirect b/stubs/nginx/redirect new file mode 100644 index 0000000..f096428 --- /dev/null +++ b/stubs/nginx/redirect @@ -0,0 +1,6 @@ +server { + listen 80; + server_name {FROM}; + + return 301 https://{TO}$request_uri; +} From 8e17223bef2c92cb144ce38723e8f6d4699d1068 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 14:07:40 +1000 Subject: [PATCH 23/40] wip it --- composer.json | 2 +- pint.json | 20 +++++++++++ src/Aws.php | 1 - src/AwsResources.php | 2 +- src/Commands/BuildCommand.php | 4 ++- src/Commands/Command.php | 19 ++++++---- src/Commands/CommandCommand.php | 4 ++- src/Commands/DeployCommand.php | 3 +- src/Commands/Ec2ListCommand.php | 1 + src/Commands/EnvPullCommand.php | 3 +- src/Commands/EnvPushCommand.php | 11 +++--- src/Commands/ImageListCommand.php | 3 +- src/Commands/ImagePrepareCommand.php | 1 + src/Commands/InitCommand.php | 25 ++++++------- src/Commands/SteppedCommand.php | 3 +- src/Commands/SyncCommand.php | 3 +- src/Concerns/EnsuresResourcesExist.php | 1 + src/Concerns/FormatsSshCommands.php | 2 +- src/Concerns/InteractsWithSupervisor.php | 2 +- src/Concerns/RegistersAws.php | 4 +-- src/Concerns/RunsSteppedCommands.php | 2 ++ src/Concerns/SyncsRecordSets.php | 2 +- src/Concerns/SyncsSslCertificates.php | 4 +-- src/Concerns/UsesAutoscaling.php | 5 ++- src/Concerns/UsesCodeDeploy.php | 8 +++-- src/Concerns/UsesEc2.php | 35 +++++++++++-------- src/Concerns/UsesElasticLoadBalancingV2.php | 9 +++-- src/Concerns/UsesElasticTranscoder.php | 1 + src/Concerns/UsesSqs.php | 2 +- src/Helpers.php | 2 +- src/Manifest.php | 2 +- src/Steps/Build/RestoreTemporaryEnvStep.php | 2 +- src/Steps/Build/RetrieveEnvFileStep.php | 2 +- .../Ci/SyncCodeDeployDeploymentConfigStep.php | 1 + .../SyncApplicationLoadBalancerStep.php | 3 +- .../SyncElasticTranscoderPipelineStep.php | 17 ++++----- .../SyncElasticTranscoderPresetStep.php | 21 +++++------ src/Steps/Compute/SyncLaunchTemplateStep.php | 1 + .../Compute/SyncListenerOnPort443Step.php | 3 +- .../Compute/SyncListenerOnPort80Step.php | 3 +- .../SyncMultitenancyListenerOnPort443Step.php | 3 +- src/Steps/Compute/SyncTargetGroupStep.php | 3 +- src/Steps/ExecuteBuildCommandStep.php | 8 ++--- src/Steps/Landlord/SyncQueueStep.php | 1 + .../Network/SyncEc2SecurityGroupStep.php | 6 ++-- .../SyncInternetGatewayAttachmentStep.php | 2 +- src/Steps/Network/SyncInternetGatewayStep.php | 1 + src/Steps/Network/SyncKeyPairStep.php | 8 +++-- .../SyncLoadBalancerSecurityGroupStep.php | 3 +- src/Steps/Network/SyncPublicSubnetAStep.php | 1 + src/Steps/Network/SyncPublicSubnetBStep.php | 1 + src/Steps/Network/SyncPublicSubnetCStep.php | 1 + .../Network/SyncRdsSecurityGroupStep.php | 2 +- src/Steps/Network/SyncRdsSubnetStep.php | 1 + src/Steps/Network/SyncRouteTableStep.php | 3 +- src/Steps/Network/SyncSnsTopicStep.php | 1 + src/Steps/Network/SyncVpcStep.php | 1 + src/Steps/Standalone/SyncHostedZoneStep.php | 1 + src/Steps/Standalone/SyncQueueStep.php | 3 +- .../All/SetOwnershipAndPermissionsStep.php | 2 +- src/Steps/Start/All/SyncBashProfileStep.php | 4 +-- .../Start/All/SyncHousekeepingCronStep.php | 2 +- .../Start/All/SyncPhpConfigurationStep.php | 6 ++-- .../Start/Scheduler/SyncMysqlBackupStep.php | 2 +- .../Start/Web/SyncNginxConfigurationStep.php | 4 +-- src/Steps/Start/Web/SyncOctaneWorkerStep.php | 2 +- .../Storage/SyncS3ArtefactBucketStep.php | 1 + src/Steps/Storage/SyncS3BucketStep.php | 1 + src/Steps/Tenant/SyncHostedZoneStep.php | 1 + src/Steps/Tenant/SyncQueueStep.php | 1 + src/Steps/TenantStep.php | 1 + src/TUI/Dashboard.php | 8 ++--- src/TUI/Renderers/DashboardRenderer.php | 2 +- 73 files changed, 208 insertions(+), 118 deletions(-) create mode 100644 pint.json diff --git a/composer.json b/composer.json index 359c494..49c3072 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "vlucas/phpdotenv": "^5.6" }, "require-dev": { - "codinglabsau/php-styles": "dev-main", + "laravel/pint": "^1.24", "pestphp/pest": "^3.7", "phpstan/phpstan": "^1.12", "spatie/ray": "^1.41" diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..40d9e2b --- /dev/null +++ b/pint.json @@ -0,0 +1,20 @@ +{ + "preset": "laravel", + "rules": { + "concat_space": { + "spacing": "one" + }, + "new_with_parentheses": { + "named_class": true, + "anonymous_class": true + }, + "ordered_imports": { + "imports_order": [ + "const", + "class", + "function" + ], + "sort_algorithm": "length" + } + } +} diff --git a/src/Aws.php b/src/Aws.php index 6a54650..93729a3 100644 --- a/src/Aws.php +++ b/src/Aws.php @@ -61,7 +61,6 @@ public static function tags(array $tags = [], string $wrap = 'Tags', bool $assoc ]; } - public static function accountId(): string { return Manifest::get('aws.account-id'); diff --git a/src/AwsResources.php b/src/AwsResources.php index 9e4b1b8..f388957 100644 --- a/src/AwsResources.php +++ b/src/AwsResources.php @@ -20,8 +20,8 @@ class AwsResources { use UsesAutoscaling; use UsesCertificateManager; - use UsesCodeDeploy; use UsesCloudWatch; + use UsesCodeDeploy; use UsesEc2; use UsesElasticLoadBalancingV2; use UsesElasticTranscoder; diff --git a/src/Commands/BuildCommand.php b/src/Commands/BuildCommand.php index a723bc0..1860b0f 100644 --- a/src/Commands/BuildCommand.php +++ b/src/Commands/BuildCommand.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Steps; use Symfony\Component\Console\Input\InputArgument; + use function Laravel\Prompts\error; use function Laravel\Prompts\intro; @@ -34,7 +35,8 @@ public function handle(): void $appVersion = $this->option('app-version') ?? date('y.W.N.Hi'); if (! str_starts_with($appVersion, date('y.W'))) { - error(sprintf("App version must start with %s", date('y.W'))); + error(sprintf('App version must start with %s', date('y.W'))); + return; } diff --git a/src/Commands/Command.php b/src/Commands/Command.php index e410cff..82f1d79 100644 --- a/src/Commands/Command.php +++ b/src/Commands/Command.php @@ -11,15 +11,17 @@ use Symfony\Component\Console\Output\OutputInterface; use Codinglabs\Yolo\Concerns\ChecksIfCommandsShouldBeRunning; use Symfony\Component\Console\Command\Command as SymfonyCommand; + use function Laravel\Prompts\error; abstract class Command extends SymfonyCommand { - use RegistersAws; - use HasAfterCallbacks; use ChecksIfCommandsShouldBeRunning; + use HasAfterCallbacks; + use RegistersAws; public InputInterface $input; + public OutputInterface $output; protected function execute(InputInterface $input, OutputInterface $output): int @@ -31,34 +33,39 @@ protected function execute(InputInterface $input, OutputInterface $output): int // bail if command should not be running if (! $this->shouldBeRunning($this)) { error(sprintf("Cannot run '%s' in current environment", $this->getName())); + return 1; } // special handling for `yolo init` command to execute early if ($this instanceof InitCommand) { Helpers::app()->instance('environment', 'production'); - return (int)(Helpers::app()->call([$this, 'handle']) ?: 0); + + return (int) (Helpers::app()->call([$this, 'handle']) ?: 0); } if (! Manifest::exists()) { error("Could not find yolo.yml manifest in the current directory - run 'yolo init' to create one"); + return 1; } // special handling for `yolo open` command to execute early if ($this instanceof OpenCommand) { - return (int)(Helpers::app()->call([$this, 'handle']) ?: 0); + return (int) (Helpers::app()->call([$this, 'handle']) ?: 0); } if (! Manifest::environmentExists($this->argument('environment'))) { error(sprintf("Could not find '%s' in the YOLO manifest", $this->argument('environment'))); + return 1; } Helpers::app()->instance('environment', $this->argument('environment')); if (! Aws::runningInAws() && ! Helpers::keyedEnv('AWS_PROFILE')) { - error(sprintf("You need to specify YOLO_%s_AWS_PROFILE in your .env file before proceeding", strtoupper(Helpers::environment()))); + error(sprintf('You need to specify YOLO_%s_AWS_PROFILE in your .env file before proceeding', strtoupper(Helpers::environment()))); + return 1; } @@ -67,7 +74,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // todo: remove once mvp is finished $this->output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); - $exitCode = (int)(Helpers::app()->call([$this, 'handle']) ?: 0); + $exitCode = (int) (Helpers::app()->call([$this, 'handle']) ?: 0); foreach ($this->after as $closure) { $closure(); diff --git a/src/Commands/CommandCommand.php b/src/Commands/CommandCommand.php index eb6aa07..e2f590e 100644 --- a/src/Commands/CommandCommand.php +++ b/src/Commands/CommandCommand.php @@ -9,6 +9,7 @@ use Symfony\Component\Process\Process; use Codinglabs\Yolo\Concerns\FormatsSshCommands; use Symfony\Component\Console\Input\InputArgument; + use function Laravel\Prompts\info; use function Laravel\Prompts\text; use function Laravel\Prompts\confirm; @@ -38,6 +39,7 @@ public function handle(): void if (! $confirmed) { info('🐥 yolo'); + return; } @@ -48,7 +50,7 @@ public function handle(): void foreach ($groups as $group) { $instances = $this->findSshPrefixesForGroup($group); - info("Found " . count($instances) . " instances in group $group on {$this->argument('environment')}"); + info('Found ' . count($instances) . " instances in group $group on {$this->argument('environment')}"); foreach ($instances as $ipAddress => $sshCommand) { warning("Executing command '$command' in group $group on instance $ipAddress on {$this->argument('environment')}..."); diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index 5484b5e..a034981 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -6,6 +6,7 @@ use Codinglabs\Yolo\Steps; use Codinglabs\Yolo\Helpers; use Symfony\Component\Console\Input\InputArgument; + use function Laravel\Prompts\confirm; use function Laravel\Prompts\warning; @@ -48,7 +49,7 @@ public function handle(): void } if (! $reuseBuild) { - warning("Building fresh version..."); + warning('Building fresh version...'); (new BuildCommand())->execute(Helpers::app('input'), Helpers::app('output')); } diff --git a/src/Commands/Ec2ListCommand.php b/src/Commands/Ec2ListCommand.php index 9180c2b..6164cbc 100644 --- a/src/Commands/Ec2ListCommand.php +++ b/src/Commands/Ec2ListCommand.php @@ -6,6 +6,7 @@ use Codinglabs\Yolo\Aws; use Codinglabs\Yolo\Concerns\FormatsSshCommands; use Symfony\Component\Console\Input\InputArgument; + use function Laravel\Prompts\table; class Ec2ListCommand extends Command diff --git a/src/Commands/EnvPullCommand.php b/src/Commands/EnvPullCommand.php index fecf62f..2faa2af 100644 --- a/src/Commands/EnvPullCommand.php +++ b/src/Commands/EnvPullCommand.php @@ -4,6 +4,7 @@ use Symfony\Component\Console\Input\InputArgument; use Codinglabs\Yolo\Steps\Build\RetrieveEnvFileStep; + use function Laravel\Prompts\info; use function Laravel\Prompts\note; @@ -25,6 +26,6 @@ public function handle(): void (new RetrieveEnvFileStep())(); - info("Downloaded successfully"); + info('Downloaded successfully'); } } diff --git a/src/Commands/EnvPushCommand.php b/src/Commands/EnvPushCommand.php index ad1d261..a36766b 100644 --- a/src/Commands/EnvPushCommand.php +++ b/src/Commands/EnvPushCommand.php @@ -8,6 +8,7 @@ use Aws\S3\Exception\S3Exception; use Symfony\Component\Console\Input\InputArgument; use Codinglabs\Yolo\Steps\Build\RetrieveEnvFileStep; + use function Laravel\Prompts\info; use function Laravel\Prompts\note; use function Laravel\Prompts\error; @@ -34,6 +35,7 @@ public function handle(): void if (! file_exists($path)) { error("Could not find $filename"); + return; } @@ -42,7 +44,7 @@ public function handle(): void 'save-as' => Paths::base($temporaryFilename), ]); - note("Comparing changes..."); + note('Comparing changes...'); $oldContents = Dotenv::parse(file_get_contents(Paths::base($temporaryFilename))); $newContents = Dotenv::parse(file_get_contents($path)); @@ -63,12 +65,13 @@ public function handle(): void } $confirm = $differences->isEmpty() - ? confirm("No changes detected - do you want to upload anyway?") - : confirm("Are you sure you want to upload these changes?"); + ? confirm('No changes detected - do you want to upload anyway?') + : confirm('Are you sure you want to upload these changes?'); if (! $confirm) { unlink(Paths::base($temporaryFilename)); info('🐥 yolo'); + return; } } catch (S3Exception $e) { @@ -86,6 +89,6 @@ public function handle(): void unlink(Paths::base($temporaryFilename)); - info("Uploaded successfully."); + info('Uploaded successfully.'); } } diff --git a/src/Commands/ImageListCommand.php b/src/Commands/ImageListCommand.php index 5fcbebf..179d271 100644 --- a/src/Commands/ImageListCommand.php +++ b/src/Commands/ImageListCommand.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use Codinglabs\Yolo\Aws; use Symfony\Component\Console\Input\InputArgument; + use function Laravel\Prompts\table; class ImageListCommand extends Command @@ -33,7 +34,7 @@ public function handle(): void ->format('d/m/Y H:i:s'), isset($image['LastLaunchedTime']) ? Carbon::parse($image['LastLaunchedTime']) - ->diffForHumans() + ->diffForHumans() : 'Never', ]) ); diff --git a/src/Commands/ImagePrepareCommand.php b/src/Commands/ImagePrepareCommand.php index 3da7f6a..a36aa16 100644 --- a/src/Commands/ImagePrepareCommand.php +++ b/src/Commands/ImagePrepareCommand.php @@ -8,6 +8,7 @@ use Codinglabs\Yolo\Concerns\UsesEc2; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; + use function Laravel\Prompts\select; class ImagePrepareCommand extends SteppedCommand diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index b328f55..be2fd4a 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Paths; use Codinglabs\Yolo\Manifest; + use function Laravel\Prompts\info; use function Laravel\Prompts\text; use function Laravel\Prompts\intro; @@ -21,12 +22,12 @@ protected function configure(): void public function handle(): void { if (Manifest::exists()) { - if (! confirm("A yolo.yml manifest already exists in the current directory. Do you want to overwrite it?", default: false)) { + if (! confirm('A yolo.yml manifest already exists in the current directory. Do you want to overwrite it?', default: false)) { return; } } - intro("Initialising yolo.yml"); + intro('Initialising yolo.yml'); $this->gitIgnoreFilesAndDirectories(); $this->initialiseManifest(); @@ -54,9 +55,9 @@ protected function initialiseManifest(): void ) ); - if (confirm("Is the app multi-tenant?", default: false)) { + if (confirm('Is the app multi-tenant?', default: false)) { Manifest::put('tenants', [ - 'tenant-id' => ['domain' => 'tenant-domain.tld'] + 'tenant-id' => ['domain' => 'tenant-domain.tld'], ]); Manifest::put('deploy', [ @@ -64,14 +65,14 @@ protected function initialiseManifest(): void 'php artisan tenants:artisan "migrate --path=database/migrations/tenant --database=tenant --force"', ]); } else { - Manifest::put('domain', text("What is the domain?", placeholder: 'eg. codinglabs.com.au')); + Manifest::put('domain', text('What is the domain?', placeholder: 'eg. codinglabs.com.au')); Manifest::put('deploy', [ 'php artisan migrate --force', ]); } - if ($s3Bucket = text("What is the name of the S3 bucket used for app storage?", placeholder: "Leave blank to skip")) { + if ($s3Bucket = text('What is the name of the S3 bucket used for app storage?', placeholder: 'Leave blank to skip')) { Manifest::put('aws.bucket', $s3Bucket); } } @@ -81,9 +82,9 @@ protected function gitIgnoreFilesAndDirectories(): void if (file_exists(Paths::base('.gitignore'))) { file_put_contents( Paths::base('.gitignore'), - ".yolo" . PHP_EOL . - ".env.staging" . PHP_EOL . - ".env.production" . PHP_EOL, + '.yolo' . PHP_EOL . + '.env.staging' . PHP_EOL . + '.env.production' . PHP_EOL, FILE_APPEND ); } @@ -94,9 +95,9 @@ protected function initialiseEnv(): void if (! file_exists(Paths::base('.env.production'))) { file_put_contents( Paths::base('.env.production'), - "APP_ENV=production" . PHP_EOL . - "APP_KEY=" . PHP_EOL . - "APP_DEBUG=false" . PHP_EOL . + 'APP_ENV=production' . PHP_EOL . + 'APP_KEY=' . PHP_EOL . + 'APP_DEBUG=false' . PHP_EOL . FILE_APPEND ); } diff --git a/src/Commands/SteppedCommand.php b/src/Commands/SteppedCommand.php index c119cb4..2d670ec 100644 --- a/src/Commands/SteppedCommand.php +++ b/src/Commands/SteppedCommand.php @@ -3,6 +3,7 @@ namespace Codinglabs\Yolo\Commands; use Codinglabs\Yolo\Concerns\RunsSteppedCommands; + use function Laravel\Prompts\info; use function Laravel\Prompts\intro; @@ -16,7 +17,7 @@ public function handle(): void { $environment = $this->argument('environment'); - intro(sprintf("Executing %s steps in %s", $this->getName(), $environment)); + intro(sprintf('Executing %s steps in %s', $this->getName(), $environment)); $totalTime = $this->handleSteps($environment); diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index f33e870..ea8980e 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -6,6 +6,7 @@ use Codinglabs\Yolo\Commands; use Codinglabs\Yolo\Manifest; use Symfony\Component\Console\Input\InputArgument; + use function Laravel\Prompts\info; use function Laravel\Prompts\intro; @@ -23,7 +24,7 @@ protected function configure(): void public function handle(): void { - intro("Executing sync commands..."); + intro('Executing sync commands...'); collect([ Commands\SyncNetworkCommand::class, diff --git a/src/Concerns/EnsuresResourcesExist.php b/src/Concerns/EnsuresResourcesExist.php index 37b9604..d8332d4 100644 --- a/src/Concerns/EnsuresResourcesExist.php +++ b/src/Concerns/EnsuresResourcesExist.php @@ -6,6 +6,7 @@ use Illuminate\Support\Str; use Codinglabs\Yolo\Helpers; use Codinglabs\Yolo\Exceptions\YoloException; + use function Laravel\Prompts\note; use function Laravel\Prompts\alert; diff --git a/src/Concerns/FormatsSshCommands.php b/src/Concerns/FormatsSshCommands.php index 6bdfb80..42e2505 100644 --- a/src/Concerns/FormatsSshCommands.php +++ b/src/Concerns/FormatsSshCommands.php @@ -6,7 +6,7 @@ trait FormatsSshCommands { - public static function formatSshCommand(string $ipAddress, string $sshKey = null, string $command = null): string + public static function formatSshCommand(string $ipAddress, ?string $sshKey = null, ?string $command = null): string { $sshKey = match (true) { ! is_null($sshKey) => $sshKey, diff --git a/src/Concerns/InteractsWithSupervisor.php b/src/Concerns/InteractsWithSupervisor.php index 0f2336c..e1ebcb7 100644 --- a/src/Concerns/InteractsWithSupervisor.php +++ b/src/Concerns/InteractsWithSupervisor.php @@ -9,7 +9,7 @@ trait InteractsWithSupervisor public function stopSupervisorWorkers(): void { Process::fromShellCommandline( - command: "sudo supervisorctl stop all" + command: 'sudo supervisorctl stop all' )->mustRun(); } } diff --git a/src/Concerns/RegistersAws.php b/src/Concerns/RegistersAws.php index da4359a..cc44696 100644 --- a/src/Concerns/RegistersAws.php +++ b/src/Concerns/RegistersAws.php @@ -84,7 +84,7 @@ protected static function detectCiEnvironment(): bool return env('CI', false) === true; } - protected static function detectAwsEnvironment(ServerGroup $serverGroup = null): bool + protected static function detectAwsEnvironment(?ServerGroup $serverGroup = null): bool { if (static::detectLocalEnvironment() || static::detectCiEnvironment()) { // skip if we are local or in continuous integration @@ -107,7 +107,7 @@ protected static function detectAwsEnvironment(ServerGroup $serverGroup = null): 'Name' => 'key', 'Values' => ['Name'], ], - ] + ], ]); return ! empty($awsResult['Tags']) && $awsResult['Tags'][0]['Value'] === $serverGroup->value; diff --git a/src/Concerns/RunsSteppedCommands.php b/src/Concerns/RunsSteppedCommands.php index 9bf2e49..39423a1 100644 --- a/src/Concerns/RunsSteppedCommands.php +++ b/src/Concerns/RunsSteppedCommands.php @@ -19,6 +19,7 @@ use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsStep; use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsQueueStep; use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsSchedulerStep; + use function Laravel\Prompts\table; use function Laravel\Prompts\warning; use function Laravel\Prompts\progress; @@ -35,6 +36,7 @@ protected function handleSteps(string $environment): int if (count($steps) === 0) { warning('No steps detected'); + return time() - $now; } diff --git a/src/Concerns/SyncsRecordSets.php b/src/Concerns/SyncsRecordSets.php index 42bba19..ef79411 100644 --- a/src/Concerns/SyncsRecordSets.php +++ b/src/Concerns/SyncsRecordSets.php @@ -53,7 +53,7 @@ protected function generateChanges(string $apex, string $domain): array : "www.$domain", 'Type' => 'A', ], - ] + ], ]; } diff --git a/src/Concerns/SyncsSslCertificates.php b/src/Concerns/SyncsSslCertificates.php index a1b2055..d46ce89 100644 --- a/src/Concerns/SyncsSslCertificates.php +++ b/src/Concerns/SyncsSslCertificates.php @@ -22,7 +22,7 @@ protected function validateCertificate(string $certificateArn, string $apex): vo { do { $certificate = Aws::acm()->describeCertificate([ - 'CertificateArn' => $certificateArn + 'CertificateArn' => $certificateArn, ])['Certificate']; // take a little snooze because the AWS result @@ -47,7 +47,7 @@ protected function validateCertificate(string $certificateArn, string $apex): vo 'Type' => $option['ResourceRecord']['Type'], 'ResourceRecords' => [ [ - 'Value' => $option['ResourceRecord']['Value'] + 'Value' => $option['ResourceRecord']['Value'], ], ], 'TTL' => 300, diff --git a/src/Concerns/UsesAutoscaling.php b/src/Concerns/UsesAutoscaling.php index 067f702..df4a982 100644 --- a/src/Concerns/UsesAutoscaling.php +++ b/src/Concerns/UsesAutoscaling.php @@ -11,8 +11,11 @@ trait UsesAutoscaling { protected static array $asgWeb; + protected static array $asgQueue; + protected static array $asgScheduler; + protected static array $asgWebScalingPolicies; public static function autoScalingGroupWeb(): array @@ -85,7 +88,7 @@ protected static function autoScalingGroupScalingPolicies(string $asgName): arra )['ScalingPolicies']; if (count($autoScalingGroupScalingPolicies) === 0) { - throw new ResourceDoesNotExistException(sprintf("Could not find asg scaling policies %s", $asgName)); + throw new ResourceDoesNotExistException(sprintf('Could not find asg scaling policies %s', $asgName)); } return $autoScalingGroupScalingPolicies; diff --git a/src/Concerns/UsesCodeDeploy.php b/src/Concerns/UsesCodeDeploy.php index 0ed2184..530f206 100644 --- a/src/Concerns/UsesCodeDeploy.php +++ b/src/Concerns/UsesCodeDeploy.php @@ -12,9 +12,13 @@ trait UsesCodeDeploy { protected static string $application; + protected static array $oneThirdAtATimeDeploymentConfig; + protected static array $webDeploymentGroup; + protected static array $queueDeploymentGroup; + protected static array $schedulerDeploymentGroup; public static function applicationName(): string @@ -36,7 +40,7 @@ public static function application(): string } } - throw new ResourceDoesNotExistException(sprintf("Could not find CodeDeploy application %s", Helpers::keyedResourceName())); + throw new ResourceDoesNotExistException(sprintf('Could not find CodeDeploy application %s', Helpers::keyedResourceName())); } public static function OneThirdAtATimeDeploymentConfig(): array @@ -103,7 +107,7 @@ protected static function deploymentGroup(string $name): array } } - throw new ResourceDoesNotExistException(sprintf("Could not find deployment group %s", $name)); + throw new ResourceDoesNotExistException(sprintf('Could not find deployment group %s', $name)); } public static function deploymentGroupPayload(): array diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index 0cb8b76..5ae5e3c 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -13,14 +13,23 @@ trait UsesEc2 { protected static array $vpc; + protected static array $internetGateway; + protected static array $launchTemplate; + protected static array $subnets; + protected static array $routeTable; + protected static array $securityGroups; + protected static array $loadBalancerSecurityGroup; + protected static array $ec2SecurityGroup; + protected static array $rdsSecurityGroup; + protected static array $keyPair; public static function ec2ByName(string $name, array $states = ['running'], bool $firstOnly = true, $throws = true): ?array @@ -154,7 +163,7 @@ public static function launchTemplate($refresh = false): array ])['LaunchTemplates']; if (count($launchTemplates) === 0) { - ResourceDoesNotExistException::make(sprintf("Could not find launch template %s", Helpers::keyedResourceName())) + ResourceDoesNotExistException::make(sprintf('Could not find launch template %s', Helpers::keyedResourceName())) ->suggest('compute:sync') ->throw(); } @@ -206,12 +215,11 @@ public static function vpc(): array 'Name' => 'tag:Name', 'Values' => [$name], ], - ] - ]) - ['Vpcs']; + ], + ])['Vpcs']; if (count($vpcs) === 0) { - throw new ResourceDoesNotExistException(sprintf("Could not find VPC %s", $name)); + throw new ResourceDoesNotExistException(sprintf('Could not find VPC %s', $name)); } static::$vpc = $vpcs[0]; @@ -233,12 +241,11 @@ public static function internetGateway(): array 'Name' => 'tag:Name', 'Values' => [$name], ], - ] - ]) - ['InternetGateways']; + ], + ])['InternetGateways']; if (count($internetGateways) === 0) { - throw new ResourceDoesNotExistException(sprintf("Could not find Internet Gateway %s", $name)); + throw new ResourceDoesNotExistException(sprintf('Could not find Internet Gateway %s', $name)); } static::$internetGateway = $internetGateways[0]; @@ -260,12 +267,11 @@ public static function routeTable(): array 'Name' => 'tag:Name', 'Values' => [$name], ], - ] - ]) - ['RouteTables']; + ], + ])['RouteTables']; if (count($routeTables) === 0) { - throw new ResourceDoesNotExistException(sprintf("Could not find Route Table %s", $name)); + throw new ResourceDoesNotExistException(sprintf('Could not find Route Table %s', $name)); } static::$routeTable = $routeTables[0]; @@ -289,7 +295,7 @@ public static function subnets(): array ])['Subnets']; if (count($subnets) === 0) { - throw new ResourceDoesNotExistException(sprintf("Could not find subnets for VPC %s", AwsResources::vpc()['VpcId'])); + throw new ResourceDoesNotExistException(sprintf('Could not find subnets for VPC %s', AwsResources::vpc()['VpcId'])); } static::$subnets = $subnets; @@ -341,6 +347,7 @@ public static function keyPair(): array foreach (Aws::ec2()->describeKeyPairs()['KeyPairs'] as $keyPair) { if ($keyPair['KeyName'] === $name) { static::$keyPair = $keyPair; + return $keyPair; } } diff --git a/src/Concerns/UsesElasticLoadBalancingV2.php b/src/Concerns/UsesElasticLoadBalancingV2.php index ac5628e..e1dc4ab 100644 --- a/src/Concerns/UsesElasticLoadBalancingV2.php +++ b/src/Concerns/UsesElasticLoadBalancingV2.php @@ -9,6 +9,7 @@ trait UsesElasticLoadBalancingV2 { protected static array $loadBalancer; + protected static array $targetGroup; public static function loadBalancer($refresh = false): array @@ -22,11 +23,12 @@ public static function loadBalancer($refresh = false): array foreach ($loadBalancers['LoadBalancers'] as $loadBalancer) { if ($loadBalancer['LoadBalancerName'] === Helpers::keyedResourceName(exclusive: false)) { static::$loadBalancer = $loadBalancer; + return $loadBalancer; } } - throw new ResourceDoesNotExistException("Could not find load balancer"); + throw new ResourceDoesNotExistException('Could not find load balancer'); } public static function targetGroup(): array @@ -40,11 +42,12 @@ public static function targetGroup(): array foreach ($targetGroups['TargetGroups'] as $targetGroup) { if ($targetGroup['TargetGroupName'] === Helpers::keyedResourceName(exclusive: false)) { static::$targetGroup = $targetGroup; + return $targetGroup; } } - throw new ResourceDoesNotExistException(sprintf("Could not find target group matching name %s", Helpers::keyedResourceName(exclusive: false))); + throw new ResourceDoesNotExistException(sprintf('Could not find target group matching name %s', Helpers::keyedResourceName(exclusive: false))); } public static function loadBalancerListenerOnPort(int $port): array @@ -65,7 +68,7 @@ public static function loadBalancerListenerOnPort(int $port): array public static function listenerCertificate(string $listenerArn, string $certificateArn): array { $listenerCertificates = Aws::elasticLoadBalancingV2()->describeListenerCertificates([ - 'ListenerArn' => $listenerArn + 'ListenerArn' => $listenerArn, ]); foreach ($listenerCertificates['Certificates'] as $listenerCertificate) { diff --git a/src/Concerns/UsesElasticTranscoder.php b/src/Concerns/UsesElasticTranscoder.php index fe786bd..7a17391 100644 --- a/src/Concerns/UsesElasticTranscoder.php +++ b/src/Concerns/UsesElasticTranscoder.php @@ -9,6 +9,7 @@ trait UsesElasticTranscoder { protected static array $elasticTranscoderPipeline; + protected static array $elasticTranscoderPreset; public static function elasticTranscoderPipeline(): array diff --git a/src/Concerns/UsesSqs.php b/src/Concerns/UsesSqs.php index d27696e..887cdfa 100644 --- a/src/Concerns/UsesSqs.php +++ b/src/Concerns/UsesSqs.php @@ -21,7 +21,7 @@ public static function queue(string $queueName): array ...Aws::sqs()->getQueueAttributes([ 'QueueUrl' => $queueUrl, 'AttributeNames' => ['All'], - ])->toArray() + ])->toArray(), ]; } } diff --git a/src/Helpers.php b/src/Helpers.php index 9c2ab10..f188284 100644 --- a/src/Helpers.php +++ b/src/Helpers.php @@ -26,7 +26,7 @@ public static function keyedEnv(string $key): ?string return env(static::keyedEnvName($key)); } - public static function keyedResourceName(string|BackedEnum $name = null, $exclusive = true, string $seperator = '-'): string + public static function keyedResourceName(string|BackedEnum|null $name = null, $exclusive = true, string $seperator = '-'): string { if ($name instanceof BackedEnum) { $name = $name->value; diff --git a/src/Manifest.php b/src/Manifest.php index 6cf1f0d..385592a 100644 --- a/src/Manifest.php +++ b/src/Manifest.php @@ -46,7 +46,7 @@ public static function put(string $key, mixed $value): false|int { $manifest = static::current(); - Arr::set($manifest, sprintf("environments.%s.%s", Helpers::environment(), $key), $value); + Arr::set($manifest, sprintf('environments.%s.%s', Helpers::environment(), $key), $value); return file_put_contents( Paths::manifest(), diff --git a/src/Steps/Build/RestoreTemporaryEnvStep.php b/src/Steps/Build/RestoreTemporaryEnvStep.php index b0370ae..a4ada2d 100644 --- a/src/Steps/Build/RestoreTemporaryEnvStep.php +++ b/src/Steps/Build/RestoreTemporaryEnvStep.php @@ -19,7 +19,7 @@ public function __invoke(): void // final place to be added to the build artefact for deploy. $this->filesystem->move( Paths::build(".env.$this->environment.tmp"), - Paths::build(".env"), + Paths::build('.env'), ); } } diff --git a/src/Steps/Build/RetrieveEnvFileStep.php b/src/Steps/Build/RetrieveEnvFileStep.php index 45c2950..3be04da 100644 --- a/src/Steps/Build/RetrieveEnvFileStep.php +++ b/src/Steps/Build/RetrieveEnvFileStep.php @@ -11,7 +11,7 @@ class RetrieveEnvFileStep implements Step { public function __invoke(array $options = []): void { - $filename = sprintf(".env.%s", Helpers::environment()); + $filename = sprintf('.env.%s', Helpers::environment()); $path = array_key_exists('save-as', $options) ? $options['save-as'] : Paths::base($filename); diff --git a/src/Steps/Ci/SyncCodeDeployDeploymentConfigStep.php b/src/Steps/Ci/SyncCodeDeployDeploymentConfigStep.php index 01b0e75..5ed6da4 100644 --- a/src/Steps/Ci/SyncCodeDeployDeploymentConfigStep.php +++ b/src/Steps/Ci/SyncCodeDeployDeploymentConfigStep.php @@ -16,6 +16,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::OneThirdAtATimeDeploymentConfig(); + return StepResult::IN_SYNC; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Compute/SyncApplicationLoadBalancerStep.php b/src/Steps/Compute/SyncApplicationLoadBalancerStep.php index fcb8f3f..672d95d 100644 --- a/src/Steps/Compute/SyncApplicationLoadBalancerStep.php +++ b/src/Steps/Compute/SyncApplicationLoadBalancerStep.php @@ -16,6 +16,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::loadBalancer(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { @@ -26,7 +27,7 @@ public function __invoke(array $options): StepResult ->pluck('SubnetId') ->toArray(), ...Aws::tags([ - 'Name' => Helpers::keyedResourceName(exclusive: false) + 'Name' => Helpers::keyedResourceName(exclusive: false), ]), ]); diff --git a/src/Steps/Compute/SyncElasticTranscoderPipelineStep.php b/src/Steps/Compute/SyncElasticTranscoderPipelineStep.php index ba948f6..7f3a7cd 100644 --- a/src/Steps/Compute/SyncElasticTranscoderPipelineStep.php +++ b/src/Steps/Compute/SyncElasticTranscoderPipelineStep.php @@ -21,6 +21,7 @@ public function __invoke(array $options): StepResult try { AwsResources::elasticTranscoderPipeline(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { @@ -30,14 +31,14 @@ public function __invoke(array $options): StepResult 'OutputBucket' => Manifest::get('aws.bucket'), 'Role' => 'arn:aws:iam::' . Aws::accountId() . ':role/Elastic_Transcoder_Default_Role', // note: Elastic Transcoder does not appear to support tagging -// 'TagSpecifications' => [ -// [ -// 'ResourceType' => 'pipeline', -// ...Aws::tags([ -// 'Name' => Helpers::keyedResourceName(), -// ]), -// ], -// ], + // 'TagSpecifications' => [ + // [ + // 'ResourceType' => 'pipeline', + // ...Aws::tags([ + // 'Name' => Helpers::keyedResourceName(), + // ]), + // ], + // ], ]); return StepResult::CREATED; diff --git a/src/Steps/Compute/SyncElasticTranscoderPresetStep.php b/src/Steps/Compute/SyncElasticTranscoderPresetStep.php index 8c2cb26..05529cb 100644 --- a/src/Steps/Compute/SyncElasticTranscoderPresetStep.php +++ b/src/Steps/Compute/SyncElasticTranscoderPresetStep.php @@ -21,6 +21,7 @@ public function __invoke(array $options): StepResult try { AwsResources::elasticTranscoderPreset(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { @@ -34,8 +35,8 @@ public function __invoke(array $options): StepResult 'BitRate' => '160', 'Channels' => '2', 'CodecOptions' => [ - 'Profile' => 'AAC-LC' - ] + 'Profile' => 'AAC-LC', + ], ], 'Video' => [ 'Codec' => 'H.264', @@ -66,14 +67,14 @@ public function __invoke(array $options): StepResult 'PaddingPolicy' => 'NoPad', ], // note: Elastic Transcoder does not appear to support tagging -// 'TagSpecifications' => [ -// [ -// 'ResourceType' => 'preset', -// ...Aws::tags([ -// 'Name' => Helpers::keyedResourceName(), -// ]), -// ], -// ], + // 'TagSpecifications' => [ + // [ + // 'ResourceType' => 'preset', + // ...Aws::tags([ + // 'Name' => Helpers::keyedResourceName(), + // ]), + // ], + // ], ]); return StepResult::CREATED; diff --git a/src/Steps/Compute/SyncLaunchTemplateStep.php b/src/Steps/Compute/SyncLaunchTemplateStep.php index 1bcd0a8..8652d71 100644 --- a/src/Steps/Compute/SyncLaunchTemplateStep.php +++ b/src/Steps/Compute/SyncLaunchTemplateStep.php @@ -17,6 +17,7 @@ public function __invoke(array $options): StepResult // ensure the launch template exists; refer to "yolo image:create" // to create new launch template versions with synced attributes. AwsResources::launchTemplate(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Compute/SyncListenerOnPort443Step.php b/src/Steps/Compute/SyncListenerOnPort443Step.php index c59c31d..f930c4c 100644 --- a/src/Steps/Compute/SyncListenerOnPort443Step.php +++ b/src/Steps/Compute/SyncListenerOnPort443Step.php @@ -17,6 +17,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::loadBalancerListenerOnPort(443); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { @@ -36,7 +37,7 @@ public function __invoke(array $options): StepResult ], ], ...Aws::tags([ - 'Name' => Helpers::keyedResourceName('https', exclusive: false) + 'Name' => Helpers::keyedResourceName('https', exclusive: false), ]), ]); diff --git a/src/Steps/Compute/SyncListenerOnPort80Step.php b/src/Steps/Compute/SyncListenerOnPort80Step.php index 9d5162e..65e6953 100644 --- a/src/Steps/Compute/SyncListenerOnPort80Step.php +++ b/src/Steps/Compute/SyncListenerOnPort80Step.php @@ -16,6 +16,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::loadBalancerListenerOnPort(80); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { @@ -30,7 +31,7 @@ public function __invoke(array $options): StepResult ], ], ...Aws::tags([ - 'Name' => Helpers::keyedResourceName('http', exclusive: false) + 'Name' => Helpers::keyedResourceName('http', exclusive: false), ]), ]); diff --git a/src/Steps/Compute/SyncMultitenancyListenerOnPort443Step.php b/src/Steps/Compute/SyncMultitenancyListenerOnPort443Step.php index 785e9c9..d1541c4 100644 --- a/src/Steps/Compute/SyncMultitenancyListenerOnPort443Step.php +++ b/src/Steps/Compute/SyncMultitenancyListenerOnPort443Step.php @@ -17,6 +17,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::loadBalancerListenerOnPort(443); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { @@ -36,7 +37,7 @@ public function __invoke(array $options): StepResult ], ], ...Aws::tags([ - 'Name' => Helpers::keyedResourceName(exclusive: false) + 'Name' => Helpers::keyedResourceName(exclusive: false), ]), ]); diff --git a/src/Steps/Compute/SyncTargetGroupStep.php b/src/Steps/Compute/SyncTargetGroupStep.php index 6635b3e..48d4bbc 100644 --- a/src/Steps/Compute/SyncTargetGroupStep.php +++ b/src/Steps/Compute/SyncTargetGroupStep.php @@ -16,6 +16,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::targetGroup(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { @@ -31,7 +32,7 @@ public function __invoke(array $options): StepResult 'HealthCheckPath' => '/healthy', 'HealthCheckTimeoutSeconds' => 5, ...Aws::tags([ - 'Name' => Helpers::keyedResourceName(exclusive: false) + 'Name' => Helpers::keyedResourceName(exclusive: false), ]), ]); diff --git a/src/Steps/ExecuteBuildCommandStep.php b/src/Steps/ExecuteBuildCommandStep.php index 31a392c..2f11f81 100644 --- a/src/Steps/ExecuteBuildCommandStep.php +++ b/src/Steps/ExecuteBuildCommandStep.php @@ -28,13 +28,13 @@ public function __invoke(): void env: [ ...collect($dotenv) ->filter(fn ($value, $key) => in_array($key, [ - 'APP_ENV', // for npm - 'ASSET_URL', // for vite - ]) || Str::startsWith($key, 'VITE_')) + 'APP_ENV', // for npm + 'ASSET_URL', // for vite + ]) || Str::startsWith($key, 'VITE_')) ->toArray(), ...[ 'CACHE_DRIVER' => 'null', - ] + ], ], timeout: null ); diff --git a/src/Steps/Landlord/SyncQueueStep.php b/src/Steps/Landlord/SyncQueueStep.php index 8708c8c..5bf6851 100644 --- a/src/Steps/Landlord/SyncQueueStep.php +++ b/src/Steps/Landlord/SyncQueueStep.php @@ -18,6 +18,7 @@ public function __invoke(array $options): StepResult try { AwsResources::queue($name); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Network/SyncEc2SecurityGroupStep.php b/src/Steps/Network/SyncEc2SecurityGroupStep.php index 81b08f8..00ec554 100644 --- a/src/Steps/Network/SyncEc2SecurityGroupStep.php +++ b/src/Steps/Network/SyncEc2SecurityGroupStep.php @@ -32,13 +32,13 @@ public function __invoke(array $options): StepResult 'ResourceType' => 'security-group', ...Aws::tags([ 'Name' => $name, - ]) + ]), ], ], ]); $securityGroup = AwsResources::ec2SecurityGroup(); - $publicIp = file_get_contents("https://api.ipify.org"); + $publicIp = file_get_contents('https://api.ipify.org'); Aws::ec2()->authorizeSecurityGroupIngress([ 'GroupId' => $securityGroup['GroupId'], @@ -63,7 +63,7 @@ public function __invoke(array $options): StepResult 'IpRanges' => [ [ 'CidrIp' => "$publicIp/32", - 'Description' => 'YOLO-determined public IP during sync. Delete if unused.' + 'Description' => 'YOLO-determined public IP during sync. Delete if unused.', ], ], ], diff --git a/src/Steps/Network/SyncInternetGatewayAttachmentStep.php b/src/Steps/Network/SyncInternetGatewayAttachmentStep.php index f8660ba..8f72d8d 100644 --- a/src/Steps/Network/SyncInternetGatewayAttachmentStep.php +++ b/src/Steps/Network/SyncInternetGatewayAttachmentStep.php @@ -23,7 +23,7 @@ public function __invoke(array $options): StepResult return StepResult::SYNCED; } - throw new ResourceDoesNotExistException("Could not find Internet Gateway Attachment"); + throw new ResourceDoesNotExistException('Could not find Internet Gateway Attachment'); } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { $vpc = AwsResources::vpc(); diff --git a/src/Steps/Network/SyncInternetGatewayStep.php b/src/Steps/Network/SyncInternetGatewayStep.php index 7a8aa6e..0d8b98e 100644 --- a/src/Steps/Network/SyncInternetGatewayStep.php +++ b/src/Steps/Network/SyncInternetGatewayStep.php @@ -18,6 +18,7 @@ public function __invoke(array $options): StepResult try { AwsResources::internetGateway(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Network/SyncKeyPairStep.php b/src/Steps/Network/SyncKeyPairStep.php index c81ec5e..f17bf0e 100644 --- a/src/Steps/Network/SyncKeyPairStep.php +++ b/src/Steps/Network/SyncKeyPairStep.php @@ -11,6 +11,7 @@ use Codinglabs\Yolo\Commands\Command; use Codinglabs\Yolo\Enums\StepResult; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; + use function Laravel\Prompts\note; use function Laravel\Prompts\intro; use function Laravel\Prompts\warning; @@ -21,6 +22,7 @@ public function __invoke(array $options, Command $command): StepResult { try { AwsResources::keyPair(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { $name = Manifest::get('aws.ec2.key-pair', Helpers::keyedResourceName(exclusive: false)); @@ -37,14 +39,14 @@ public function __invoke(array $options, Command $command): StepResult ], ]); - $envFilename = ".env"; - $suggestedPath = sprintf("~/.ssh/%s", $name); + $envFilename = '.env'; + $suggestedPath = sprintf('~/.ssh/%s', $name); $suggestedEnv = sprintf('%s=%s', Helpers::keyedEnvName('SSH_KEY'), $suggestedPath); $command->after(function () use ($suggestedPath, $key) { intro( sprintf( - "A key pair has been created to access EC2 instances. Save the below private key to somewhere like %s", + 'A key pair has been created to access EC2 instances. Save the below private key to somewhere like %s', $suggestedPath ) ); diff --git a/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php b/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php index 4457823..ed2d848 100644 --- a/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php +++ b/src/Steps/Network/SyncLoadBalancerSecurityGroupStep.php @@ -17,6 +17,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::loadBalancerSecurityGroup(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { @@ -31,7 +32,7 @@ public function __invoke(array $options): StepResult 'ResourceType' => 'security-group', ...Aws::tags([ 'Name' => $name, - ]) + ]), ], ], ]); diff --git a/src/Steps/Network/SyncPublicSubnetAStep.php b/src/Steps/Network/SyncPublicSubnetAStep.php index 48be4ad..df6639f 100644 --- a/src/Steps/Network/SyncPublicSubnetAStep.php +++ b/src/Steps/Network/SyncPublicSubnetAStep.php @@ -20,6 +20,7 @@ public function __invoke(array $options): StepResult try { AwsResources::subnetByName($publicSubnetName); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Network/SyncPublicSubnetBStep.php b/src/Steps/Network/SyncPublicSubnetBStep.php index 948a8bc..a24f0e7 100644 --- a/src/Steps/Network/SyncPublicSubnetBStep.php +++ b/src/Steps/Network/SyncPublicSubnetBStep.php @@ -20,6 +20,7 @@ public function __invoke(array $options): StepResult try { AwsResources::subnetByName($publicSubnetName); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Network/SyncPublicSubnetCStep.php b/src/Steps/Network/SyncPublicSubnetCStep.php index e4c289e..16173ab 100644 --- a/src/Steps/Network/SyncPublicSubnetCStep.php +++ b/src/Steps/Network/SyncPublicSubnetCStep.php @@ -20,6 +20,7 @@ public function __invoke(array $options): StepResult try { AwsResources::subnetByName($publicSubnetName); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Network/SyncRdsSecurityGroupStep.php b/src/Steps/Network/SyncRdsSecurityGroupStep.php index 776420f..9f5e00f 100644 --- a/src/Steps/Network/SyncRdsSecurityGroupStep.php +++ b/src/Steps/Network/SyncRdsSecurityGroupStep.php @@ -32,7 +32,7 @@ public function __invoke(array $options): StepResult 'ResourceType' => 'security-group', ...Aws::tags([ 'Name' => $name, - ]) + ]), ], ], ]); diff --git a/src/Steps/Network/SyncRdsSubnetStep.php b/src/Steps/Network/SyncRdsSubnetStep.php index eb0e6f4..cad4622 100644 --- a/src/Steps/Network/SyncRdsSubnetStep.php +++ b/src/Steps/Network/SyncRdsSubnetStep.php @@ -17,6 +17,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::dbSubnetGroup(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Network/SyncRouteTableStep.php b/src/Steps/Network/SyncRouteTableStep.php index 2aa20ff..eb0ea54 100644 --- a/src/Steps/Network/SyncRouteTableStep.php +++ b/src/Steps/Network/SyncRouteTableStep.php @@ -18,6 +18,7 @@ public function __invoke(array $options): StepResult try { AwsResources::routeTable(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { @@ -28,7 +29,7 @@ public function __invoke(array $options): StepResult 'ResourceType' => 'route-table', ...Aws::tags([ 'Name' => $routeTableName, - ]) + ]), ], ], ]); diff --git a/src/Steps/Network/SyncSnsTopicStep.php b/src/Steps/Network/SyncSnsTopicStep.php index ddfbc38..5198595 100644 --- a/src/Steps/Network/SyncSnsTopicStep.php +++ b/src/Steps/Network/SyncSnsTopicStep.php @@ -16,6 +16,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::topic(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { $name = Helpers::keyedResourceName(exclusive: false); diff --git a/src/Steps/Network/SyncVpcStep.php b/src/Steps/Network/SyncVpcStep.php index 3e930d1..67aadaf 100644 --- a/src/Steps/Network/SyncVpcStep.php +++ b/src/Steps/Network/SyncVpcStep.php @@ -18,6 +18,7 @@ public function __invoke(array $options): StepResult try { AwsResources::vpc(); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Standalone/SyncHostedZoneStep.php b/src/Steps/Standalone/SyncHostedZoneStep.php index f6bbe0a..de91dd6 100644 --- a/src/Steps/Standalone/SyncHostedZoneStep.php +++ b/src/Steps/Standalone/SyncHostedZoneStep.php @@ -17,6 +17,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::hostedZone(Manifest::apex()); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Standalone/SyncQueueStep.php b/src/Steps/Standalone/SyncQueueStep.php index e9bd925..a13b8f4 100644 --- a/src/Steps/Standalone/SyncQueueStep.php +++ b/src/Steps/Standalone/SyncQueueStep.php @@ -11,7 +11,7 @@ use Codinglabs\Yolo\Contracts\ExecutesStandaloneStep; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; -class SyncQueueStep implements Step, ExecutesStandaloneStep +class SyncQueueStep implements ExecutesStandaloneStep, Step { public function __invoke(array $options): StepResult { @@ -19,6 +19,7 @@ public function __invoke(array $options): StepResult try { AwsResources::queue($name); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Start/All/SetOwnershipAndPermissionsStep.php b/src/Steps/Start/All/SetOwnershipAndPermissionsStep.php index 9979e3e..3bbc107 100644 --- a/src/Steps/Start/All/SetOwnershipAndPermissionsStep.php +++ b/src/Steps/Start/All/SetOwnershipAndPermissionsStep.php @@ -13,7 +13,7 @@ public function __invoke(): array $name = Manifest::name(); return [ - "chown -R ubuntu:ubuntu /var/www", + 'chown -R ubuntu:ubuntu /var/www', "chmod -R 757 /var/www/$name/storage", ]; } diff --git a/src/Steps/Start/All/SyncBashProfileStep.php b/src/Steps/Start/All/SyncBashProfileStep.php index efd0370..49408b1 100644 --- a/src/Steps/Start/All/SyncBashProfileStep.php +++ b/src/Steps/Start/All/SyncBashProfileStep.php @@ -11,11 +11,11 @@ class SyncBashProfileStep implements RunsOnAws public function __invoke(array $options): StepResult { file_put_contents( - "/home/ubuntu/.bash_profile", + '/home/ubuntu/.bash_profile', file_get_contents(Paths::stubs('.bash_profile.stub')) ); - chown("/home/ubuntu/.bash_profile", "ubuntu"); + chown('/home/ubuntu/.bash_profile', 'ubuntu'); return StepResult::SYNCED; } diff --git a/src/Steps/Start/All/SyncHousekeepingCronStep.php b/src/Steps/Start/All/SyncHousekeepingCronStep.php index c9324e5..5714d8e 100644 --- a/src/Steps/Start/All/SyncHousekeepingCronStep.php +++ b/src/Steps/Start/All/SyncHousekeepingCronStep.php @@ -11,7 +11,7 @@ class SyncHousekeepingCronStep implements RunsOnAws public function __invoke(array $options): StepResult { file_put_contents( - "/etc/cron.d/yolo-housekeeping", + '/etc/cron.d/yolo-housekeeping', file_get_contents(Paths::stubs('cron/housekeeping.stub')) ); diff --git a/src/Steps/Start/All/SyncPhpConfigurationStep.php b/src/Steps/Start/All/SyncPhpConfigurationStep.php index 08eee48..6d27cc5 100644 --- a/src/Steps/Start/All/SyncPhpConfigurationStep.php +++ b/src/Steps/Start/All/SyncPhpConfigurationStep.php @@ -13,19 +13,19 @@ public function __invoke(): StepResult { // .ini for PHP CLI file_put_contents( - "/etc/php/8.3/mods-available/yolo_cli.ini", + '/etc/php/8.3/mods-available/yolo_cli.ini', file_get_contents(Paths::stubs('php/cli.ini.stub')) ); // .ini for PHP FPM file_put_contents( - "/etc/php/8.3/mods-available/yolo_fpm.ini", + '/etc/php/8.3/mods-available/yolo_fpm.ini', file_get_contents(Paths::stubs('php/fpm.ini.stub')) ); // configuration for PHP-FPM pool file_put_contents( - "/etc/php/8.3/fpm/pool.d/yolo_www_processes.conf", + '/etc/php/8.3/fpm/pool.d/yolo_www_processes.conf', file_get_contents(Paths::stubs('php/www_processes.conf.stub')) ); diff --git a/src/Steps/Start/Scheduler/SyncMysqlBackupStep.php b/src/Steps/Start/Scheduler/SyncMysqlBackupStep.php index d289d27..ad5e8b9 100644 --- a/src/Steps/Start/Scheduler/SyncMysqlBackupStep.php +++ b/src/Steps/Start/Scheduler/SyncMysqlBackupStep.php @@ -15,7 +15,7 @@ public function __invoke(array $options): StepResult return StepResult::SKIPPED; } - $file = "/home/ubuntu/mysqlbackup.sh"; + $file = '/home/ubuntu/mysqlbackup.sh'; file_put_contents( $file, diff --git a/src/Steps/Start/Web/SyncNginxConfigurationStep.php b/src/Steps/Start/Web/SyncNginxConfigurationStep.php index e0a35a7..2cd80c9 100644 --- a/src/Steps/Start/Web/SyncNginxConfigurationStep.php +++ b/src/Steps/Start/Web/SyncNginxConfigurationStep.php @@ -69,7 +69,7 @@ protected function forwardingRules(): string return collect(Manifest::tenants()) ->map(function (array $tenant) { if (! $this->domainHasWwwSubdomain($tenant['apex'], $tenant['domain'])) { - return sprintf("# %s is a subdomain, skipping redirects", $tenant['domain']); + return sprintf('# %s is a subdomain, skipping redirects', $tenant['domain']); } $redirectTemplate = file_get_contents(Paths::stubs('nginx/redirect')); @@ -94,7 +94,7 @@ protected function forwardingRules(): string } if (! $this->domainHasWwwSubdomain(Manifest::get('apex'), Manifest::get('domain'))) { - return sprintf("# %s is a subdomain, skipping redirects", Manifest::get('domain')); + return sprintf('# %s is a subdomain, skipping redirects', Manifest::get('domain')); } $redirectTemplate = file_get_contents(Paths::stubs('nginx/redirect')); diff --git a/src/Steps/Start/Web/SyncOctaneWorkerStep.php b/src/Steps/Start/Web/SyncOctaneWorkerStep.php index 344a2ad..5609009 100644 --- a/src/Steps/Start/Web/SyncOctaneWorkerStep.php +++ b/src/Steps/Start/Web/SyncOctaneWorkerStep.php @@ -12,7 +12,7 @@ class SyncOctaneWorkerStep implements RunsOnAwsWeb { public function __invoke(array $options): StepResult { - $file = sprintf('/etc/supervisor/conf.d/%s', Helpers::keyedResourceName("octane-worker.conf")); + $file = sprintf('/etc/supervisor/conf.d/%s', Helpers::keyedResourceName('octane-worker.conf')); if (! Manifest::get('aws.ec2.octane')) { if (file_exists($file)) { diff --git a/src/Steps/Storage/SyncS3ArtefactBucketStep.php b/src/Steps/Storage/SyncS3ArtefactBucketStep.php index d08037e..595dcba 100644 --- a/src/Steps/Storage/SyncS3ArtefactBucketStep.php +++ b/src/Steps/Storage/SyncS3ArtefactBucketStep.php @@ -18,6 +18,7 @@ public function __invoke(array $options): StepResult try { AwsResources::bucket($bucketName); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Storage/SyncS3BucketStep.php b/src/Steps/Storage/SyncS3BucketStep.php index abddcd5..388ac6b 100644 --- a/src/Steps/Storage/SyncS3BucketStep.php +++ b/src/Steps/Storage/SyncS3BucketStep.php @@ -22,6 +22,7 @@ public function __invoke(array $options): StepResult try { AwsResources::bucket($bucketName); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException $e) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Tenant/SyncHostedZoneStep.php b/src/Steps/Tenant/SyncHostedZoneStep.php index f49f245..12e70c3 100644 --- a/src/Steps/Tenant/SyncHostedZoneStep.php +++ b/src/Steps/Tenant/SyncHostedZoneStep.php @@ -16,6 +16,7 @@ public function __invoke(array $options): StepResult { try { AwsResources::hostedZone($this->config['apex']); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/Tenant/SyncQueueStep.php b/src/Steps/Tenant/SyncQueueStep.php index 2b7e434..4a48141 100644 --- a/src/Steps/Tenant/SyncQueueStep.php +++ b/src/Steps/Tenant/SyncQueueStep.php @@ -18,6 +18,7 @@ public function __invoke(array $options): StepResult try { AwsResources::queue($name); + return StepResult::SYNCED; } catch (ResourceDoesNotExistException) { if (! Arr::get($options, 'dry-run')) { diff --git a/src/Steps/TenantStep.php b/src/Steps/TenantStep.php index 67b6a28..5e84026 100644 --- a/src/Steps/TenantStep.php +++ b/src/Steps/TenantStep.php @@ -8,6 +8,7 @@ abstract class TenantStep implements ExecutesTenantStep { protected string $tenantId; + protected array $config; abstract public function __invoke(array $options): StepResult; diff --git a/src/TUI/Dashboard.php b/src/TUI/Dashboard.php index 63c7726..cf244f6 100644 --- a/src/TUI/Dashboard.php +++ b/src/TUI/Dashboard.php @@ -10,21 +10,21 @@ class Dashboard extends Prompt { - use RegistersRenderers; use CreatesAnAltScreen; + use RegistersRenderers; public array $tabs = [ [ 'tab' => 'Dashboard', - 'content' => "Coming soon", + 'content' => 'Coming soon', ], [ 'tab' => 'Deployments', - 'content' => "Coming soon", + 'content' => 'Coming soon', ], [ 'tab' => 'Infrastructure', - 'content' => "Coming soon", + 'content' => 'Coming soon', ], ]; diff --git a/src/TUI/Renderers/DashboardRenderer.php b/src/TUI/Renderers/DashboardRenderer.php index 69bd484..8aa9f54 100644 --- a/src/TUI/Renderers/DashboardRenderer.php +++ b/src/TUI/Renderers/DashboardRenderer.php @@ -32,7 +32,7 @@ public function __invoke(Dashboard $prompt): string $this->centerHorizontally($tabs->implode(str_repeat(' ', 4)), $width)->each($this->line(...)); - $contentWidth = (int)floor($width * .75); + $contentWidth = (int) floor($width * .75); $this->centerHorizontally($this->dim(str_repeat('─', $contentWidth + 4)), $width)->each($this->line(...)); From c31bd6c1b2e289ed046070a1275d73b6d127a6f5 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 14:08:35 +1000 Subject: [PATCH 24/40] wip --- .github/workflows/analyse.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml index 95ae289..21ef64a 100644 --- a/.github/workflows/analyse.yml +++ b/.github/workflows/analyse.yml @@ -38,7 +38,7 @@ jobs: run: composer install --no-cache --no-ansi --no-interaction --no-progress - name: Code sniff - run: vendor/bin/php-cs-fixer fix --dry-run + run: vendor/bin/pint --test - name: Code analysis run: vendor/bin/phpstan analyse --memory-limit=1G From 06e2eb1f51035e69a8f4fedca03fe72aacbfec6f Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 16:20:29 +1000 Subject: [PATCH 25/40] wip it --- src/Commands/ImagePrepareCommand.php | 99 ---------------------------- src/Commands/StageCommand.php | 26 -------- 2 files changed, 125 deletions(-) delete mode 100644 src/Commands/ImagePrepareCommand.php diff --git a/src/Commands/ImagePrepareCommand.php b/src/Commands/ImagePrepareCommand.php deleted file mode 100644 index 39b5157..0000000 --- a/src/Commands/ImagePrepareCommand.php +++ /dev/null @@ -1,99 +0,0 @@ ->>>>>>> main:src/Commands/StageCommand.php -{ - use UsesEc2; - - protected array $steps = [ - // create new launch template version -<<<<<<<< HEAD:src/Commands/ImagePrepareCommand.php - Steps\Image\CreateLaunchTemplateVersionStep::class, - - // scheduler group - Steps\Image\CreateAutoScalingSchedulerGroupStep::class, - - // queue group - Steps\Image\CreateAutoScalingQueueGroupStep::class, - - // web group - Steps\Image\CreateAutoScalingWebGroupStep::class, - Steps\Image\CreateWebGroupCpuAlarmsStep::class, -======== - Steps\Stage\CreateLaunchTemplateVersionStep::class, - - // web group - Steps\Stage\ConfigureAutoScalingWebGroupStep::class, - Steps\Stage\CreateWebGroupCpuAlarmsStep::class, - - // queue group - Steps\Stage\ConfigureAutoScalingQueueGroupStep::class, - - // scheduler group - Steps\Stage\ConfigureAutoScalingSchedulerGroupStep::class, ->>>>>>>> main:src/Commands/StageCommand.php - ]; - - protected function configure(): void - { - $this -<<<<<<<< HEAD:src/Commands/ImagePrepareCommand.php - ->setName('image:prepare') -======== - ->setName('stage') ->>>>>>>> main:src/Commands/StageCommand.php - ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') - ->addOption('dry-run', null, null, 'Run the command without making changes') - ->addOption('no-progress', null, null, 'Hide the progress output') - ->addOption('ami-id', null, InputOption::VALUE_OPTIONAL, 'The AMI ID to prepare for service') - ->addOption('update', null, InputOption::VALUE_NONE, 'Whether to perform an update') - ->setDescription('Set the stage'); - } - - public function handle(): void - { - $amis = collect(Aws::ec2()->describeImages(['Owners' => ['self']])['Images']) - ->filter(fn (array $image) => $image['State'] === 'available') - ->sortByDesc('CreationDate') - ->mapWithKeys(fn (array $image) => [ - $image['ImageId'] => sprintf( - '%s (%s) - created %s', - $image['Name'], - $image['ImageId'], - Carbon::parse($image['CreationDate']) - ->tz('Australia/Brisbane') - ->diffForHumans(), - ), - ])->toArray(); - - $this->input->setOption('ami-id', select( - label: 'Which AMI do you want to use?', - options: $amis, - default: $this->option('ami-id') ?? array_key_first($amis), - )); - - if (! $this->option('update')) { - $this->input->setOption('update', ! confirm('The --update option was not provided. This will create new resources. Are you sure?')); - } - - parent::handle(); - } -} diff --git a/src/Commands/StageCommand.php b/src/Commands/StageCommand.php index 39b5157..7ff3e77 100644 --- a/src/Commands/StageCommand.php +++ b/src/Commands/StageCommand.php @@ -8,36 +8,15 @@ use Codinglabs\Yolo\Concerns\UsesEc2; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; -<<<<<<<< HEAD:src/Commands/ImagePrepareCommand.php - -use function Laravel\Prompts\select; - -class ImagePrepareCommand extends SteppedCommand -======== - use function Laravel\Prompts\select; use function Laravel\Prompts\confirm; class StageCommand extends SteppedCommand ->>>>>>>> main:src/Commands/StageCommand.php { use UsesEc2; protected array $steps = [ // create new launch template version -<<<<<<<< HEAD:src/Commands/ImagePrepareCommand.php - Steps\Image\CreateLaunchTemplateVersionStep::class, - - // scheduler group - Steps\Image\CreateAutoScalingSchedulerGroupStep::class, - - // queue group - Steps\Image\CreateAutoScalingQueueGroupStep::class, - - // web group - Steps\Image\CreateAutoScalingWebGroupStep::class, - Steps\Image\CreateWebGroupCpuAlarmsStep::class, -======== Steps\Stage\CreateLaunchTemplateVersionStep::class, // web group @@ -49,17 +28,12 @@ class StageCommand extends SteppedCommand // scheduler group Steps\Stage\ConfigureAutoScalingSchedulerGroupStep::class, ->>>>>>>> main:src/Commands/StageCommand.php ]; protected function configure(): void { $this -<<<<<<<< HEAD:src/Commands/ImagePrepareCommand.php - ->setName('image:prepare') -======== ->setName('stage') ->>>>>>>> main:src/Commands/StageCommand.php ->addArgument('environment', InputArgument::REQUIRED, 'The environment name') ->addOption('dry-run', null, null, 'Run the command without making changes') ->addOption('no-progress', null, null, 'Hide the progress output') From a17b93bcf39c2e8c2a1a26e74f91a67d2fb3327c Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 16:22:25 +1000 Subject: [PATCH 26/40] wip it --- src/Concerns/RunsSteppedCommands.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Concerns/RunsSteppedCommands.php b/src/Concerns/RunsSteppedCommands.php index 3957558..a47d433 100644 --- a/src/Concerns/RunsSteppedCommands.php +++ b/src/Concerns/RunsSteppedCommands.php @@ -19,7 +19,6 @@ use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsStep; use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsQueueStep; use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsSchedulerStep; - use function Laravel\Prompts\table; use function Laravel\Prompts\warning; use function Laravel\Prompts\progress; @@ -67,9 +66,6 @@ protected function handleSteps(string $environment): int // green StepResult::CREATED => 'CREATED', StepResult::SUCCESS => 'SUCCESS', - StepResult::IN_SYNC => 'IN SYNC', - - // cyan StepResult::SYNCED => 'SYNCED', // yellow @@ -78,7 +74,6 @@ protected function handleSteps(string $environment): int StepResult::CUSTOM_MANAGED => 'CUSTOM MANAGED', StepResult::WOULD_CREATE => 'WOULD CREATE', StepResult::WOULD_SYNC => 'WOULD SYNC', - StepResult::OUT_OF_SYNC => 'OUT OF SYNC', // red StepResult::MANIFEST_INVALID => 'MANIFEST INVALID', From db5d56736700d12da3e9b8756fea3efa56b0cda9 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 16:24:11 +1000 Subject: [PATCH 27/40] wip it --- src/Concerns/UsesAutoscaling.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Concerns/UsesAutoscaling.php b/src/Concerns/UsesAutoscaling.php index ff36139..1a359fd 100644 --- a/src/Concerns/UsesAutoscaling.php +++ b/src/Concerns/UsesAutoscaling.php @@ -19,16 +19,28 @@ trait UsesAutoscaling public static function autoScalingGroupWeb(): array { + if (isset(static::$asgWeb)) { + return static::$asgWeb; + } + return static::autoScalingGroup(Manifest::get('aws.autoscaling.web')); } public static function autoScalingGroupQueue(): array { + if (isset(static::$asgQueue)) { + return static::$asgQueue; + } + return static::autoScalingGroup(Manifest::get('aws.autoscaling.queue')); } public static function autoScalingGroupScheduler(): array { + if (isset(static::$asgScheduler)) { + return static::$asgScheduler; + } + return static::autoScalingGroup(Manifest::get('aws.autoscaling.scheduler')); } @@ -59,6 +71,10 @@ public static function autoScalingGroupWebScaleDownPolicy(): array protected static function autoScalingGroupWebScalingPolicies(): array { + if (isset(static::$asgWebScalingPolicies)) { + return static::$asgWebScalingPolicies; + } + return static::autoScalingGroupScalingPolicies(Manifest::get('aws.autoscaling.web')); } From bcd3acda08f4269a58482063c08c1cbe9afe56c0 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 16:26:17 +1000 Subject: [PATCH 28/40] wip it --- src/Commands/StageCommand.php | 1 + src/Concerns/RunsSteppedCommands.php | 1 + src/Concerns/UsesEc2.php | 3 ++- src/Steps/Stage/CreateLaunchTemplateVersionStep.php | 4 ---- src/Steps/Stage/CreateWebGroupCpuAlarmsStep.php | 4 ---- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Commands/StageCommand.php b/src/Commands/StageCommand.php index 7ff3e77..c619cad 100644 --- a/src/Commands/StageCommand.php +++ b/src/Commands/StageCommand.php @@ -8,6 +8,7 @@ use Codinglabs\Yolo\Concerns\UsesEc2; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; + use function Laravel\Prompts\select; use function Laravel\Prompts\confirm; diff --git a/src/Concerns/RunsSteppedCommands.php b/src/Concerns/RunsSteppedCommands.php index a47d433..2afc4d9 100644 --- a/src/Concerns/RunsSteppedCommands.php +++ b/src/Concerns/RunsSteppedCommands.php @@ -19,6 +19,7 @@ use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsStep; use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsQueueStep; use Codinglabs\Yolo\Steps\ExecuteCommandOnAwsSchedulerStep; + use function Laravel\Prompts\table; use function Laravel\Prompts\warning; use function Laravel\Prompts\progress; diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index e442a03..3fbdbca 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -86,7 +86,8 @@ public static function securityGroups($refresh = false): array static::$securityGroups = $securityGroups; - return static::$securityGroups; } + return static::$securityGroups; + } /** * @throws ResourceDoesNotExistException diff --git a/src/Steps/Stage/CreateLaunchTemplateVersionStep.php b/src/Steps/Stage/CreateLaunchTemplateVersionStep.php index 67bb93c..2064d02 100644 --- a/src/Steps/Stage/CreateLaunchTemplateVersionStep.php +++ b/src/Steps/Stage/CreateLaunchTemplateVersionStep.php @@ -1,10 +1,6 @@ >>>>>>> main:src/Steps/Stage/CreateLaunchTemplateVersionStep.php use Codinglabs\Yolo\Aws; use Illuminate\Support\Arr; diff --git a/src/Steps/Stage/CreateWebGroupCpuAlarmsStep.php b/src/Steps/Stage/CreateWebGroupCpuAlarmsStep.php index 310186f..e57c75c 100644 --- a/src/Steps/Stage/CreateWebGroupCpuAlarmsStep.php +++ b/src/Steps/Stage/CreateWebGroupCpuAlarmsStep.php @@ -1,10 +1,6 @@ >>>>>>> main:src/Steps/Stage/CreateWebGroupCpuAlarmsStep.php use Codinglabs\Yolo\Aws; use Illuminate\Support\Arr; From 7ac39dff1c9a0dff67fcee97d1156026ef3a5167 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 16:28:05 +1000 Subject: [PATCH 29/40] wip it --- .../Image/CreateLaunchTemplateVersionStep.php | 41 ------- .../Image/CreateWebGroupCpuAlarmsStep.php | 104 ------------------ 2 files changed, 145 deletions(-) delete mode 100644 src/Steps/Image/CreateLaunchTemplateVersionStep.php delete mode 100644 src/Steps/Image/CreateWebGroupCpuAlarmsStep.php diff --git a/src/Steps/Image/CreateLaunchTemplateVersionStep.php b/src/Steps/Image/CreateLaunchTemplateVersionStep.php deleted file mode 100644 index 67bb93c..0000000 --- a/src/Steps/Image/CreateLaunchTemplateVersionStep.php +++ /dev/null @@ -1,41 +0,0 @@ ->>>>>>> main:src/Steps/Stage/CreateLaunchTemplateVersionStep.php - -use Codinglabs\Yolo\Aws; -use Illuminate\Support\Arr; -use Codinglabs\Yolo\AwsResources; -use Codinglabs\Yolo\Contracts\Step; -use Codinglabs\Yolo\Enums\StepResult; - -class CreateLaunchTemplateVersionStep implements Step -{ - public function __invoke(array $options): string|StepResult - { - if (! Arr::get($options, 'dry-run')) { - $launchTemplate = AwsResources::launchTemplate(); - - $launchTemplateVersion = Aws::ec2()->createLaunchTemplateVersion([ - 'LaunchTemplateId' => $launchTemplate['LaunchTemplateId'], - 'LaunchTemplateData' => [ - ...AwsResources::launchTemplatePayload()['LaunchTemplateData'], - 'ImageId' => $options['ami-id'], - ], - ])['LaunchTemplateVersion']; - - // set the updated version as the default - Aws::ec2()->modifyLaunchTemplate([ - 'LaunchTemplateId' => $launchTemplate['LaunchTemplateId'], - 'DefaultVersion' => $launchTemplateVersion['VersionNumber'], - ]); - - return sprintf('version %s', $launchTemplateVersion['VersionNumber']); - } - - return StepResult::WOULD_CREATE; - } -} diff --git a/src/Steps/Image/CreateWebGroupCpuAlarmsStep.php b/src/Steps/Image/CreateWebGroupCpuAlarmsStep.php deleted file mode 100644 index 310186f..0000000 --- a/src/Steps/Image/CreateWebGroupCpuAlarmsStep.php +++ /dev/null @@ -1,104 +0,0 @@ ->>>>>>> main:src/Steps/Stage/CreateWebGroupCpuAlarmsStep.php - -use Codinglabs\Yolo\Aws; -use Illuminate\Support\Arr; -use Illuminate\Support\Str; -use Codinglabs\Yolo\Helpers; -use Codinglabs\Yolo\AwsResources; -use Codinglabs\Yolo\Contracts\Step; -use Codinglabs\Yolo\Enums\StepResult; -use Codinglabs\Yolo\Concerns\UsesAutoscaling; - -class CreateWebGroupCpuAlarmsStep implements Step -{ - use UsesAutoscaling; - - public function __invoke(array $options): StepResult - { - if (! Arr::get($options, 'dry-run')) { - if (Arr::get($options, 'update')) { - return StepResult::SKIPPED; - } - - $alarmName = Helpers::keyedResourceName( - sprintf('web-cpu-scaling-alarm-%s', Str::random(8)), - exclusive: false - ); - $asgWeb = AwsResources::autoScalingGroupWeb(); - $scaleUpPolicy = AwsResources::autoScalingGroupWebScaleUpPolicy(); - $scaleDownPolicy = AwsResources::autoScalingGroupWebScaleDownPolicy(); - - Aws::cloudWatch()->putMetricAlarm([ - 'ActionsEnabled' => true, - 'AlarmName' => $alarmName, - 'AlarmDescription' => 'Alarm if web CPU is too high. Created by yolo CLI', - 'ComparisonOperator' => 'GreaterThanThreshold', - 'Dimensions' => [ - [ - 'Name' => 'AutoScalingGroupName', - 'Value' => $asgWeb['AutoScalingGroupName'], - ], - ], - 'EvaluationPeriods' => 1, // number of breached of Period before alarm - 'MetricName' => 'CPUUtilization', - 'Namespace' => 'AWS/EC2', - 'Period' => 60, // time to evaluate the metric - 'Statistic' => 'Average', - 'Threshold' => 60, // >= 60% average CPU - 'TreatMissingData' => 'notBreaching', - 'AlarmActions' => [ - $scaleUpPolicy['PolicyARN'], - ], - 'OKActions' => [ - $scaleDownPolicy['PolicyARN'], - ], - ...Aws::tags(), - ]); - - $alarmName = Helpers::keyedResourceName( - sprintf('web-cpu-critical-alarm-%s', Str::random(8)), - exclusive: false - ); - $snsTopic = AwsResources::topic(); - - Aws::cloudWatch()->putMetricAlarm([ - 'ActionsEnabled' => true, - 'AlarmName' => $alarmName, - 'AlarmDescription' => 'Alarm if web CPU is critical. Created by yolo CLI', - 'ComparisonOperator' => 'GreaterThanThreshold', - 'Dimensions' => [ - [ - 'Name' => 'AutoScalingGroupName', - 'Value' => $asgWeb['AutoScalingGroupName'], - ], - ], - 'EvaluationPeriods' => 5, // number of breached of Period before alarm - 'MetricName' => 'CPUUtilization', - 'Namespace' => 'AWS/EC2', - 'Period' => 60, // time to evaluate the metric - 'Statistic' => 'Average', - 'Threshold' => 80, // >= 80% average CPU - 'TreatMissingData' => 'notBreaching', - 'AlarmActions' => [ - $snsTopic['TopicArn'], - ], - 'OKActions' => [ - $snsTopic['TopicArn'], - ], - ...Aws::tags(), - ]); - - return StepResult::SYNCED; - } - - return Arr::get($options, 'update') - ? StepResult::WOULD_SKIP - : StepResult::WOULD_CREATE; - } -} From 40105e4127725ff94bb0aecb6b82bffa154f75b4 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 16:37:40 +1000 Subject: [PATCH 30/40] wip it --- src/Commands/OpenCommand.php | 20 ++++++++++++++++++++ src/Concerns/UsesEc2.php | 10 ++++------ src/Concerns/UsesElasticTranscoder.php | 10 ++++------ src/Concerns/UsesRoute53.php | 5 ++--- src/Exceptions/YoloException.php | 8 ++++++++ 5 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 src/Commands/OpenCommand.php diff --git a/src/Commands/OpenCommand.php b/src/Commands/OpenCommand.php new file mode 100644 index 0000000..d1d5401 --- /dev/null +++ b/src/Commands/OpenCommand.php @@ -0,0 +1,20 @@ +setName('open') + ->setDescription('Open the YOLO TUI application'); + } + + public function handle(): void + { + (new Dashboard())->prompt(); + } +} diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index 3fbdbca..d86b9dd 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -214,9 +214,8 @@ public static function launchTemplate(): array ])['LaunchTemplates']; if (count($launchTemplates) === 0) { - ResourceDoesNotExistException::make(sprintf('Could not find launch template %s', Helpers::keyedResourceName())) - ->suggest('compute:sync') - ->throw(); + throw ResourceDoesNotExistException::make(sprintf('Could not find launch template %s', Helpers::keyedResourceName())) + ->suggest('compute:sync'); } return $launchTemplates[0]; @@ -362,8 +361,7 @@ public static function keyPair(): array } } - ResourceDoesNotExistException::make("Could not find key pair with name $name") - ->suggest('sync:network') - ->throw(); + throw ResourceDoesNotExistException::make("Could not find key pair with name $name") + ->suggest('sync:network'); } } diff --git a/src/Concerns/UsesElasticTranscoder.php b/src/Concerns/UsesElasticTranscoder.php index f0cab25..90e7c98 100644 --- a/src/Concerns/UsesElasticTranscoder.php +++ b/src/Concerns/UsesElasticTranscoder.php @@ -23,9 +23,8 @@ public static function elasticTranscoderPipeline(): array } } - ResourceDoesNotExistException::make("Could not find Elastic Transcoder pipeline with name $name") - ->suggest('sync:compute') - ->throw(); + throw ResourceDoesNotExistException::make("Could not find Elastic Transcoder pipeline with name $name") + ->suggest('sync:compute'); } public static function elasticTranscoderPreset(): array @@ -39,8 +38,7 @@ public static function elasticTranscoderPreset(): array } } - ResourceDoesNotExistException::make("Could not find Elastic Transcoder preset with name $name") - ->suggest('sync:compute') - ->throw(); + throw ResourceDoesNotExistException::make("Could not find Elastic Transcoder preset with name $name") + ->suggest('sync:compute'); } } diff --git a/src/Concerns/UsesRoute53.php b/src/Concerns/UsesRoute53.php index 431cf13..c798f44 100644 --- a/src/Concerns/UsesRoute53.php +++ b/src/Concerns/UsesRoute53.php @@ -17,8 +17,7 @@ public static function hostedZone(string $domain): array } } - ResourceDoesNotExistException::make("Could not find Hosted Zone for domain $domain") - ->suggest('sync:compute') - ->throw(); + throw ResourceDoesNotExistException::make("Could not find Hosted Zone for domain $domain") + ->suggest('sync:compute'); } } diff --git a/src/Exceptions/YoloException.php b/src/Exceptions/YoloException.php index 1c1f039..12b195b 100644 --- a/src/Exceptions/YoloException.php +++ b/src/Exceptions/YoloException.php @@ -24,4 +24,12 @@ public function getSuggestion(): string { return $this->suggestion; } + + /** + * @throws self + */ + public function throw(): void + { + throw $this; + } } From 935beb2600334ad8d12eec3accc46630de95d4ad Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 16:42:40 +1000 Subject: [PATCH 31/40] wip it --- src/Steps/Compute/SyncApplicationLoadBalancerStep.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Steps/Compute/SyncApplicationLoadBalancerStep.php b/src/Steps/Compute/SyncApplicationLoadBalancerStep.php index 672d95d..48a0118 100644 --- a/src/Steps/Compute/SyncApplicationLoadBalancerStep.php +++ b/src/Steps/Compute/SyncApplicationLoadBalancerStep.php @@ -33,7 +33,7 @@ public function __invoke(array $options): StepResult while (true) { // wait for load balancer to provision - $loadBalancer = AwsResources::loadBalancer(refresh: true); + $loadBalancer = AwsResources::loadBalancer(); if ($loadBalancer['State']['Code'] === 'active') { break; From dbf405343f0f3cb0e016da60bfa5854171eafc19 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 8 Sep 2025 17:08:36 +1000 Subject: [PATCH 32/40] re-add some missing files --- src/Aws.php | 6 + src/AwsResources.php | 5 + src/Concerns/CreatesSubnets.php | 32 +++++ src/Concerns/UsesEc2.php | 112 +++++++++--------- src/Concerns/UsesRds.php | 25 ++++ src/Contracts/ExecutesDomainStep.php | 5 + src/Enums/PublicSubnets.php | 10 ++ src/Enums/Rds.php | 8 ++ .../Image/CreateAutoScalingQueueGroupStep.php | 61 ---------- .../CreateAutoScalingSchedulerGroupStep.php | 56 --------- .../Image/CreateAutoScalingWebGroupStep.php | 74 ------------ .../Scheduler/ExecuteDeployStepsStep.php | 2 +- 12 files changed, 148 insertions(+), 248 deletions(-) create mode 100644 src/Concerns/CreatesSubnets.php create mode 100644 src/Concerns/UsesRds.php create mode 100644 src/Contracts/ExecutesDomainStep.php create mode 100644 src/Enums/PublicSubnets.php create mode 100644 src/Enums/Rds.php delete mode 100644 src/Steps/Image/CreateAutoScalingQueueGroupStep.php delete mode 100644 src/Steps/Image/CreateAutoScalingSchedulerGroupStep.php delete mode 100644 src/Steps/Image/CreateAutoScalingWebGroupStep.php diff --git a/src/Aws.php b/src/Aws.php index 0934695..80c42e0 100644 --- a/src/Aws.php +++ b/src/Aws.php @@ -6,6 +6,7 @@ use Aws\Acm\AcmClient; use Aws\Ec2\Ec2Client; use Aws\Iam\IamClient; +use Aws\Rds\RdsClient; use Aws\Sns\SnsClient; use Aws\Sqs\SqsClient; use Aws\Ssm\SsmClient; @@ -106,6 +107,11 @@ public static function iam(): IamClient return Helpers::app('iam'); } + public static function rds(): RdsClient + { + return Helpers::app('rds'); + } + public static function route53(): Route53Client { return Helpers::app('route53'); diff --git a/src/AwsResources.php b/src/AwsResources.php index ab121a0..2612dfb 100644 --- a/src/AwsResources.php +++ b/src/AwsResources.php @@ -5,6 +5,7 @@ use Codinglabs\Yolo\Concerns\UsesS3; use Codinglabs\Yolo\Concerns\UsesEc2; use Codinglabs\Yolo\Concerns\UsesIam; +use Codinglabs\Yolo\Concerns\UsesRds; use Codinglabs\Yolo\Concerns\UsesSns; use Codinglabs\Yolo\Concerns\UsesSqs; use Codinglabs\Yolo\Concerns\UsesSsm; @@ -14,16 +15,20 @@ use Codinglabs\Yolo\Concerns\UsesAutoscaling; use Codinglabs\Yolo\Concerns\UsesElasticTranscoder; use Codinglabs\Yolo\Concerns\UsesCertificateManager; +use Codinglabs\Yolo\Concerns\UsesElasticLoadBalancingV2; class AwsResources { use UsesAutoscaling; use UsesCertificateManager; use UsesCloudWatch; + use UsesCloudWatch; use UsesCodeDeploy; use UsesEc2; + use UsesElasticLoadBalancingV2; use UsesElasticTranscoder; use UsesIam; + use UsesRds; use UsesRoute53; use UsesS3; use UsesSns; diff --git a/src/Concerns/CreatesSubnets.php b/src/Concerns/CreatesSubnets.php new file mode 100644 index 0000000..4d77318 --- /dev/null +++ b/src/Concerns/CreatesSubnets.php @@ -0,0 +1,32 @@ +createSubnet([ + 'AvailabilityZone' => $availabilityZones[$index]['ZoneName'], + 'CidrBlock' => "10.1.$index.0/24", + 'VpcId' => $vpc['VpcId'], + 'MapPublicIpOnLaunch' => true, + 'TagSpecifications' => [ + [ + 'ResourceType' => 'subnet', + ...Aws::tags([ + 'Name' => Helpers::keyedResourceName($name, exclusive: false), + ]), + ], + ], + ]); + } +} diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index d86b9dd..90aee81 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -33,6 +33,24 @@ trait UsesEc2 protected static array $keyPair; + public static function availabilityZones(string $region): array + { + $availabilityZones = Aws::ec2()->describeAvailabilityZones([ + 'Filters' => [ + [ + 'Name' => 'region-name', + 'Values' => [$region], + ], + ], + ])['AvailabilityZones']; + + if (count($availabilityZones) === 0) { + throw new ResourceDoesNotExistException("Could not find availability zones for region $region"); + } + + return $availabilityZones; + } + public static function ec2ByName(string $name, array $states = ['running'], bool $firstOnly = true, $throws = true): ?array { $instances = collect(Aws::ec2()->describeInstances([ @@ -146,64 +164,12 @@ public static function securityGroupByName(string|BackedEnum $name): array throw new ResourceDoesNotExistException("Could not find Security Group matching name $name"); } - public static function loadBalancer(): array + public static function launchTemplate($refresh = false): array { - $loadBalancers = Aws::elasticLoadBalancingV2()->describeLoadBalancers(); - - foreach ($loadBalancers['LoadBalancers'] as $loadBalancer) { - if ($loadBalancer['LoadBalancerName'] === Manifest::get('aws.alb')) { - return $loadBalancer; - } + if (! $refresh && isset(static::$launchTemplate)) { + return static::$launchTemplate; } - throw new ResourceDoesNotExistException('Could not find load balancer'); - } - - public static function targetGroup(): array - { - $targetGroups = Aws::elasticLoadBalancingV2()->describeTargetGroups([ - 'LoadBalancerArn' => static::loadBalancer()['LoadBalancerArn'], - ])['TargetGroups']; - - if (count($targetGroups) === 0) { - throw new ResourceDoesNotExistException(sprintf('Could not find target group for ALB %s', static::loadBalancer()['LoadBalancerName'])); - } - - return $targetGroups[0]; - } - - public static function loadBalancerListenerOnPort(int $port): array - { - $listeners = Aws::elasticLoadBalancingV2()->describeListeners([ - 'LoadBalancerArn' => static::loadBalancer()['LoadBalancerArn'], - ]); - - foreach ($listeners['Listeners'] as $listener) { - if ($listener['Port'] === $port) { - return $listener; - } - } - - throw new ResourceDoesNotExistException("Could not find listener on port $port"); - } - - public static function listenerCertificate(string $listenerArn, string $certificateArn): array - { - $listenerCertificates = Aws::elasticLoadBalancingV2()->describeListenerCertificates([ - 'ListenerArn' => $listenerArn, - ]); - - foreach ($listenerCertificates['Certificates'] as $listenerCertificate) { - if ($listenerCertificate['CertificateArn'] === $certificateArn) { - return $listenerCertificate; - } - } - - throw new ResourceDoesNotExistException("Could not find listener certificate on listener $listenerArn"); - } - - public static function launchTemplate(): array - { $launchTemplates = Aws::ec2()->describeLaunchTemplates([ 'Filters' => [ [ @@ -218,7 +184,9 @@ public static function launchTemplate(): array ->suggest('compute:sync'); } - return $launchTemplates[0]; + static::$launchTemplate = $launchTemplates[0]; + + return static::$launchTemplate; } public static function launchTemplatePayload(): array @@ -249,6 +217,19 @@ public static function launchTemplatePayload(): array ]; } + public static function loadBalancer(): array + { + $loadBalancers = Aws::elasticLoadBalancingV2()->describeLoadBalancers(); + + foreach ($loadBalancers['LoadBalancers'] as $loadBalancer) { + if ($loadBalancer['LoadBalancerName'] === Manifest::get('aws.alb')) { + return $loadBalancer; + } + } + + throw new ResourceDoesNotExistException('Could not find load balancer'); + } + public static function vpc(): array { if (isset(static::$vpc)) { @@ -329,6 +310,10 @@ public static function routeTable(): array public static function subnets(): array { + if (isset(static::$subnets)) { + return static::$subnets; + } + $subnets = Aws::ec2()->describeSubnets([ 'Filters' => [ [ @@ -345,6 +330,21 @@ public static function subnets(): array return $subnets; } + public static function subnetByName(string $name): array + { + $fullSubnetName = Helpers::keyedResourceName($name, exclusive: false); + + foreach (static::subnets() as $subnet) { + foreach ($subnet['Tags'] as $tag) { + if ($tag['Key'] === 'Name' && $tag['Value'] === $fullSubnetName) { + return $subnet; + } + } + } + + throw new ResourceDoesNotExistException("Could not find subnet matching name $fullSubnetName"); + } + public static function keyPair(): array { if (isset(static::$keyPair)) { diff --git a/src/Concerns/UsesRds.php b/src/Concerns/UsesRds.php new file mode 100644 index 0000000..e2b3dff --- /dev/null +++ b/src/Concerns/UsesRds.php @@ -0,0 +1,25 @@ +describeDBSubnetGroups(); + + foreach ($dbSubnetGroups['DBSubnetGroups'] as $dbSubnetGroup) { + if ($dbSubnetGroup['DBSubnetGroupName'] === $name) { + return $dbSubnetGroup; + } + } + + throw new ResourceDoesNotExistException("Could not find RDS Subnet Group with name $name"); + } +} diff --git a/src/Contracts/ExecutesDomainStep.php b/src/Contracts/ExecutesDomainStep.php new file mode 100644 index 0000000..a18d611 --- /dev/null +++ b/src/Contracts/ExecutesDomainStep.php @@ -0,0 +1,5 @@ +value, Str::random(8))); - - Aws::autoscaling()->createAutoScalingGroup([ - ...static::autoScalingGroupPayload(), - ...[ - 'AutoScalingGroupName' => $name, - 'MinSize' => 1, - 'MaxSize' => 1, - 'DesiredCapacity' => 1, - // special use case to include 'PropagateAtLaunch' attribute - 'Tags' => [ - [ - 'Key' => 'Name', - 'PropagateAtLaunch' => true, - 'Value' => ServerGroup::QUEUE->value, - ], - [ - 'Key' => 'yolo:environment', - 'Value' => Helpers::app('environment'), - 'PropagateAtLaunch' => true, - ], - ], - ], - ]); - - Aws::autoscaling()->enableMetricsCollection([ - 'AutoScalingGroupName' => $name, - 'Granularity' => '1Minute', - ]); - - Manifest::put('aws.autoscaling.queue', $name); - - return StepResult::SYNCED; - } - - return StepResult::WOULD_CREATE; - } -} diff --git a/src/Steps/Image/CreateAutoScalingSchedulerGroupStep.php b/src/Steps/Image/CreateAutoScalingSchedulerGroupStep.php deleted file mode 100644 index 1ef7aca..0000000 --- a/src/Steps/Image/CreateAutoScalingSchedulerGroupStep.php +++ /dev/null @@ -1,56 +0,0 @@ -value, Str::random(8))); - - Aws::autoscaling()->createAutoScalingGroup([ - ...static::autoScalingGroupPayload(), - ...[ - 'AutoScalingGroupName' => $name, - 'MinSize' => 1, - 'MaxSize' => 1, - 'DesiredCapacity' => 1, - // special use case to include 'PropagateAtLaunch' attribute - 'Tags' => [ - [ - 'Key' => 'Name', - 'PropagateAtLaunch' => true, - 'Value' => ServerGroup::SCHEDULER->value, - ], - [ - 'Key' => 'yolo:environment', - 'Value' => Helpers::app('environment'), - 'PropagateAtLaunch' => true, - ], - ], - ], - ]); - - Manifest::put('aws.autoscaling.scheduler', $name); - - return StepResult::SYNCED; - } - - return StepResult::WOULD_CREATE; - } -} diff --git a/src/Steps/Image/CreateAutoScalingWebGroupStep.php b/src/Steps/Image/CreateAutoScalingWebGroupStep.php deleted file mode 100644 index bc13788..0000000 --- a/src/Steps/Image/CreateAutoScalingWebGroupStep.php +++ /dev/null @@ -1,74 +0,0 @@ -value, Str::random(8))); - - Aws::autoscaling()->createAutoScalingGroup([ - ...static::autoScalingGroupPayload(), - ...[ - 'AutoScalingGroupName' => $name, - 'MinSize' => 1, - 'MaxSize' => 1, - 'DesiredCapacity' => 1, - 'Tags' => [ - [ - 'Key' => 'Name', - 'PropagateAtLaunch' => true, - 'Value' => ServerGroup::WEB->value, - ], - [ - 'Key' => 'yolo:environment', - 'Value' => Helpers::app('environment'), - 'PropagateAtLaunch' => true, - ], - ], - ], - ]); - - Aws::autoscaling()->putScalingPolicy([ - 'AutoScalingGroupName' => $name, - 'PolicyName' => "$name-up", - 'AdjustmentType' => 'ChangeInCapacity', - 'Cooldown' => 60, - 'ScalingAdjustment' => 2, - ]); - - Aws::autoscaling()->putScalingPolicy([ - 'AutoScalingGroupName' => $name, - 'PolicyName' => "$name-down", - 'AdjustmentType' => 'ChangeInCapacity', - 'Cooldown' => 300, - 'ScalingAdjustment' => -1, - ]); - - Aws::autoscaling()->enableMetricsCollection([ - 'AutoScalingGroupName' => $name, - 'Granularity' => '1Minute', - ]); - - Manifest::put('aws.autoscaling.web', $name); - - return StepResult::SYNCED; - } - - return StepResult::WOULD_CREATE; - } -} diff --git a/src/Steps/Start/Scheduler/ExecuteDeployStepsStep.php b/src/Steps/Start/Scheduler/ExecuteDeployStepsStep.php index ac3739c..656b586 100644 --- a/src/Steps/Start/Scheduler/ExecuteDeployStepsStep.php +++ b/src/Steps/Start/Scheduler/ExecuteDeployStepsStep.php @@ -6,7 +6,7 @@ use Codinglabs\Yolo\Contracts\HasSubSteps; use Codinglabs\Yolo\Contracts\RunsOnAwsScheduler; -class ExecuteSchedulerDeployStepsStep implements HasSubSteps, RunsOnAwsScheduler +class ExecuteDeployStepsStep implements HasSubSteps, RunsOnAwsScheduler { public function __invoke(): array { From 7355a6447c3b17ecf3f6ececf225f49c51eb82e6 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Tue, 9 Sep 2025 09:20:59 +1000 Subject: [PATCH 33/40] remove slack webhooks --- .github/workflows/analyse.yml | 5 ----- .github/workflows/test.yml | 5 ----- 2 files changed, 10 deletions(-) diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml index 21ef64a..3d557ea 100644 --- a/.github/workflows/analyse.yml +++ b/.github/workflows/analyse.yml @@ -42,8 +42,3 @@ jobs: - name: Code analysis run: vendor/bin/phpstan analyse --memory-limit=1G - - - name: Notify Slack of failure - if: failure() - run: | - curl -X POST -H 'Content-type: application/json' --data '{"text":"Code analysis failed on branch ${{github.ref_name}} for action https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"}' https://hooks.slack.com/services/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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2184b99..4497dcf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,8 +34,3 @@ jobs: - name: Execute tests run: vendor/bin/pest --parallel --stop-on-failure - - - name: Notify Slack of failure - if: failure() - run: | - curl -X POST -H 'Content-type: application/json' --data '{"text":"YOLO tests failed on branch ${{github.ref_name}} for action https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"}' https://hooks.slack.com/services/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 From 4a8a7a2124a02aba4b7ce77abf8d3c631951e595 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 15 Sep 2025 14:30:40 +1000 Subject: [PATCH 34/40] wip it --- src/AwsResources.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AwsResources.php b/src/AwsResources.php index 2612dfb..d7a7c71 100644 --- a/src/AwsResources.php +++ b/src/AwsResources.php @@ -22,7 +22,6 @@ class AwsResources use UsesAutoscaling; use UsesCertificateManager; use UsesCloudWatch; - use UsesCloudWatch; use UsesCodeDeploy; use UsesEc2; use UsesElasticLoadBalancingV2; From 20ca321b1680751e5959ce5a1ac2c9f5c592e3fd Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 15 Sep 2025 15:52:46 +1000 Subject: [PATCH 35/40] restore missing files --- src/Commands/SyncNetworkCommand.php | 10 +++--- src/Steps/Network/SyncDefaultRouteStep.php | 30 +++++++++++++++++ ...blicSubnetsAssociationToRouteTableStep.php | 32 +++++++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 src/Steps/Network/SyncDefaultRouteStep.php create mode 100644 src/Steps/Network/SyncPublicSubnetsAssociationToRouteTableStep.php diff --git a/src/Commands/SyncNetworkCommand.php b/src/Commands/SyncNetworkCommand.php index 5905634..54e9baf 100644 --- a/src/Commands/SyncNetworkCommand.php +++ b/src/Commands/SyncNetworkCommand.php @@ -20,11 +20,11 @@ class SyncNetworkCommand extends SteppedCommand Steps\Network\SyncPublicSubnetBStep::class, Steps\Network\SyncPublicSubnetCStep::class, Steps\Network\SyncRdsSubnetStep::class, - // - // // route table - // Steps\Network\SyncRouteTableStep::class, - // Steps\Network\SyncDefaultRouteStep::class, - // Steps\Network\SyncPublicSubnetsAssociationToRouteTableStep::class, + + // route table + Steps\Network\SyncRouteTableStep::class, + Steps\Network\SyncDefaultRouteStep::class, + Steps\Network\SyncPublicSubnetsAssociationToRouteTableStep::class, // // // security groups Steps\Network\SyncLoadBalancerSecurityGroupStep::class, diff --git a/src/Steps/Network/SyncDefaultRouteStep.php b/src/Steps/Network/SyncDefaultRouteStep.php new file mode 100644 index 0000000..df8a99f --- /dev/null +++ b/src/Steps/Network/SyncDefaultRouteStep.php @@ -0,0 +1,30 @@ + Route Tables -> $route > Routes. + if (! Arr::get($options, 'dry-run')) { + Aws::ec2()->createRoute([ + 'DestinationCidrBlock' => '0.0.0.0/0', + 'GatewayId' => AwsResources::internetGateway()['InternetGatewayId'], + 'RouteTableId' => AwsResources::routeTable()['RouteTableId'], + ]); + + return StepResult::SYNCED; + } + + return StepResult::WOULD_SYNC; + } +} diff --git a/src/Steps/Network/SyncPublicSubnetsAssociationToRouteTableStep.php b/src/Steps/Network/SyncPublicSubnetsAssociationToRouteTableStep.php new file mode 100644 index 0000000..355e277 --- /dev/null +++ b/src/Steps/Network/SyncPublicSubnetsAssociationToRouteTableStep.php @@ -0,0 +1,32 @@ + Route Tables -> Subnet associations. + if (! Arr::get($options, 'dry-run')) { + foreach (PublicSubnets::cases() as $publicSubnetName) { + Aws::ec2()->associateRouteTable([ + 'RouteTableId' => AwsResources::routeTable()['RouteTableId'], + 'SubnetId' => AwsResources::subnetByName($publicSubnetName->value)['SubnetId'], + ]); + } + + return StepResult::SYNCED; + } + + return StepResult::WOULD_SYNC; + } +} From ddc4c93a9c2a9459b06122f2521c7cf43191666c Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 15 Sep 2025 15:59:25 +1000 Subject: [PATCH 36/40] fix duplicate method --- src/Concerns/UsesEc2.php | 13 ------------- src/Concerns/UsesElasticLoadBalancingV2.php | 3 ++- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Concerns/UsesEc2.php b/src/Concerns/UsesEc2.php index 90aee81..c543bd2 100644 --- a/src/Concerns/UsesEc2.php +++ b/src/Concerns/UsesEc2.php @@ -217,19 +217,6 @@ public static function launchTemplatePayload(): array ]; } - public static function loadBalancer(): array - { - $loadBalancers = Aws::elasticLoadBalancingV2()->describeLoadBalancers(); - - foreach ($loadBalancers['LoadBalancers'] as $loadBalancer) { - if ($loadBalancer['LoadBalancerName'] === Manifest::get('aws.alb')) { - return $loadBalancer; - } - } - - throw new ResourceDoesNotExistException('Could not find load balancer'); - } - public static function vpc(): array { if (isset(static::$vpc)) { diff --git a/src/Concerns/UsesElasticLoadBalancingV2.php b/src/Concerns/UsesElasticLoadBalancingV2.php index e1dc4ab..eecc75f 100644 --- a/src/Concerns/UsesElasticLoadBalancingV2.php +++ b/src/Concerns/UsesElasticLoadBalancingV2.php @@ -4,6 +4,7 @@ use Codinglabs\Yolo\Aws; use Codinglabs\Yolo\Helpers; +use Codinglabs\Yolo\Manifest; use Codinglabs\Yolo\Exceptions\ResourceDoesNotExistException; trait UsesElasticLoadBalancingV2 @@ -21,7 +22,7 @@ public static function loadBalancer($refresh = false): array $loadBalancers = Aws::elasticLoadBalancingV2()->describeLoadBalancers(); foreach ($loadBalancers['LoadBalancers'] as $loadBalancer) { - if ($loadBalancer['LoadBalancerName'] === Helpers::keyedResourceName(exclusive: false)) { + if ($loadBalancer['LoadBalancerName'] === Manifest::get('aws.alb', Helpers::keyedResourceName(exclusive: false))) { static::$loadBalancer = $loadBalancer; return $loadBalancer; From 72029bc5f02ba5c9b5391cb05eba3e0ee1645ab2 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 15 Sep 2025 16:11:40 +1000 Subject: [PATCH 37/40] wip --- src/Commands/StartCommand.php | 4 +++- .../Start/Scheduler/ExecuteDeployStepsStep.php | 15 --------------- 2 files changed, 3 insertions(+), 16 deletions(-) delete mode 100644 src/Steps/Start/Scheduler/ExecuteDeployStepsStep.php diff --git a/src/Commands/StartCommand.php b/src/Commands/StartCommand.php index 9fdb4e9..13cd73f 100644 --- a/src/Commands/StartCommand.php +++ b/src/Commands/StartCommand.php @@ -10,7 +10,9 @@ class StartCommand extends SteppedCommand implements RunsOnAws { protected array $steps = [ Steps\Start\All\SyncBashProfileStep::class, - Steps\Start\Scheduler\ExecuteDeployStepsStep::class, // note: migrations run here + Steps\Start\Scheduler\ExecuteSchedulerDeployStepsStep::class, + Steps\Start\Queue\ExecuteQueueDeployStepsStep::class, + Steps\Start\Web\ExecuteWebDeployStepsStep::class, Steps\Start\All\ExecuteAllGroupsDeployStepsStep::class, Steps\Start\All\SetOwnershipAndPermissionsStep::class, Steps\Start\All\SyncLogrotateStep::class, diff --git a/src/Steps/Start/Scheduler/ExecuteDeployStepsStep.php b/src/Steps/Start/Scheduler/ExecuteDeployStepsStep.php deleted file mode 100644 index 656b586..0000000 --- a/src/Steps/Start/Scheduler/ExecuteDeployStepsStep.php +++ /dev/null @@ -1,15 +0,0 @@ - Date: Mon, 15 Sep 2025 16:12:23 +1000 Subject: [PATCH 38/40] wip --- src/Commands/SyncNetworkCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Commands/SyncNetworkCommand.php b/src/Commands/SyncNetworkCommand.php index 54e9baf..6760d5a 100644 --- a/src/Commands/SyncNetworkCommand.php +++ b/src/Commands/SyncNetworkCommand.php @@ -25,12 +25,12 @@ class SyncNetworkCommand extends SteppedCommand Steps\Network\SyncRouteTableStep::class, Steps\Network\SyncDefaultRouteStep::class, Steps\Network\SyncPublicSubnetsAssociationToRouteTableStep::class, - // - // // security groups + + // security groups Steps\Network\SyncLoadBalancerSecurityGroupStep::class, Steps\Network\SyncEc2SecurityGroupStep::class, - // Steps\Network\SyncRdsSecurityGroupStep::class, - // + Steps\Network\SyncRdsSecurityGroupStep::class, + // sns Steps\Network\SyncSnsTopicStep::class, From d60af077b7b61e64de128747628809c39f0c5d9e Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 15 Sep 2025 16:17:11 +1000 Subject: [PATCH 39/40] wip it --- src/Concerns/RunsSteppedCommands.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Concerns/RunsSteppedCommands.php b/src/Concerns/RunsSteppedCommands.php index 2afc4d9..49248b2 100644 --- a/src/Concerns/RunsSteppedCommands.php +++ b/src/Concerns/RunsSteppedCommands.php @@ -67,7 +67,7 @@ protected function handleSteps(string $environment): int // green StepResult::CREATED => 'CREATED', StepResult::SUCCESS => 'SUCCESS', - StepResult::SYNCED => 'SYNCED', + StepResult::SYNCED => 'SYNCED', // yellow StepResult::SKIPPED => 'SKIPPED', From ebfea1090770d9c2e2a25b379327ddc68af296f3 Mon Sep 17 00:00:00 2001 From: stevethomas Date: Mon, 22 Sep 2025 16:26:35 +1000 Subject: [PATCH 40/40] wip --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cc3ab00..f188d64 100644 --- a/README.md +++ b/README.md @@ -155,8 +155,8 @@ environments: production: aws: account-id: - vpc: region: ap-southeast-2 + vpc: internet-gateway: public-subnets: route-table: @@ -172,8 +172,9 @@ environments: scheduler: ec2: instance-type: t3.small - instance-profile: - octane: true + queue-instance-type: + scheduler-instance-type: + octane: false key-pair: security-group: rds: