diff --git a/bin/cloud b/bin/cloud index 3a26bcca..647c85b7 100755 --- a/bin/cloud +++ b/bin/cloud @@ -1,7 +1,9 @@ #!/usr/bin/env php =8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.3.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-06T18:31:59+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "1ee70e699b41909c209a0c930f11034b93578654" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", + "reference": "1ee70e699b41909c209a0c930f11034b93578654", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-30T20:28:31+00:00" + }, { "name": "symfony/options-resolver", "version": "v6.3.0", @@ -8813,176 +8983,6 @@ ], "time": "2023-05-23T14:45:45+00:00" }, - { - "name": "symfony/http-client", - "version": "v6.3.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "0314e2d49939a9831929d6fc81c01c6df137fd0a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/0314e2d49939a9831929d6fc81c01c6df137fd0a", - "reference": "0314e2d49939a9831929d6fc81c01c6df137fd0a", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.3" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" - }, - "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", - "homepage": "https://symfony.com", - "keywords": [ - "http" - ], - "support": { - "source": "https://github.com/symfony/http-client/tree/v6.3.8" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-11-06T18:31:59+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v3.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "1ee70e699b41909c209a0c930f11034b93578654" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", - "reference": "1ee70e699b41909c209a0c930f11034b93578654", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-30T20:28:31+00:00" - }, { "name": "symfony/stopwatch", "version": "v6.3.0", diff --git a/src/Action/Action.php b/src/Action/Action.php index 33d035ba..14897c9c 100644 --- a/src/Action/Action.php +++ b/src/Action/Action.php @@ -33,7 +33,7 @@ public function __invoke(array $config, ActionBuilder $action, RepositoryInterfa $logger = $compiled->getBuilder()->getNode(); } else { $logger = new Node\Expr\New_( - new Node\Name\FullyQualified(\Psr\Log\NullLogger::class), + new Node\Name\FullyQualified('Psr\\Log\\NullLogger'), ); } diff --git a/src/Builder/API/APIRuntime.php b/src/Builder/API/APIRuntime.php index 312c06af..84f36637 100644 --- a/src/Builder/API/APIRuntime.php +++ b/src/Builder/API/APIRuntime.php @@ -23,7 +23,7 @@ class: new Node\Name\FullyQualified('Kiboko\\Component\\Pipeline\\PipelineRunner args: [ new Node\Arg( value: new Node\Expr\New_( - class: new Node\Name\FullyQualified(\Psr\Log\NullLogger::class) + class: new Node\Name\FullyQualified('Psr\\Log\\NullLogger') ) ), ] diff --git a/src/Builder/Hook/HookRuntime.php b/src/Builder/Hook/HookRuntime.php index 64d4e1e2..f9cce9fa 100644 --- a/src/Builder/Hook/HookRuntime.php +++ b/src/Builder/Hook/HookRuntime.php @@ -23,7 +23,7 @@ class: new Node\Name\FullyQualified('Kiboko\\Component\\Pipeline\\PipelineRunner args: [ new Node\Arg( value: new Node\Expr\New_( - class: new Node\Name\FullyQualified(\Psr\Log\NullLogger::class) + class: new Node\Name\FullyQualified('Psr\\Log\\NullLogger') ) ), ] diff --git a/src/Builder/Pipeline/ConsoleRuntime.php b/src/Builder/Pipeline/ConsoleRuntime.php index 87fe608f..1c42e17f 100644 --- a/src/Builder/Pipeline/ConsoleRuntime.php +++ b/src/Builder/Pipeline/ConsoleRuntime.php @@ -16,7 +16,7 @@ class: new Node\Name\FullyQualified('Kiboko\\Component\\Runtime\\Pipeline\\Conso args: [ new Node\Arg( value: new Node\Expr\New_( - class: new Node\Name\FullyQualified(\Symfony\Component\Console\Output\ConsoleOutput::class), + class: new Node\Name\FullyQualified('Symfony\\Component\\Console\\Output\\ConsoleOutput'), ) ), new Node\Arg( @@ -29,7 +29,7 @@ class: new Node\Name\FullyQualified('Kiboko\\Component\\Pipeline\\PipelineRunner args: [ new Node\Arg( value: new Node\Expr\New_( - class: new Node\Name\FullyQualified(\Psr\Log\NullLogger::class), + class: new Node\Name\FullyQualified('Psr\\Log\\NullLogger'), ) ), ], diff --git a/src/Builder/Workflow/WorkflowRuntime.php b/src/Builder/Workflow/WorkflowRuntime.php index 5100c839..7e97f832 100644 --- a/src/Builder/Workflow/WorkflowRuntime.php +++ b/src/Builder/Workflow/WorkflowRuntime.php @@ -16,7 +16,7 @@ class: new Node\Name\FullyQualified('Kiboko\\Component\\Runtime\\Workflow\\Conso args: [ new Node\Arg( value: new Node\Expr\New_( - class: new Node\Name\FullyQualified(\Symfony\Component\Console\Output\ConsoleOutput::class), + class: new Node\Name\FullyQualified('Symfony\\Component\\Console\\Output\\ConsoleOutput'), ) ), new Node\Arg( @@ -25,7 +25,7 @@ class: new Node\Name\FullyQualified('Kiboko\\Component\\Pipeline\\PipelineRunner args: [ new Node\Arg( value: new Node\Expr\New_( - class: new Node\Name\FullyQualified(\Psr\Log\NullLogger::class), + class: new Node\Name\FullyQualified('Psr\\Log\\NullLogger'), ) ), ], diff --git a/src/Cloud/Auth.php b/src/Cloud/Auth.php index 1a069ccd..838a87b1 100644 --- a/src/Cloud/Auth.php +++ b/src/Cloud/Auth.php @@ -166,10 +166,10 @@ public function token(string $url): string throw new AccessDeniedException('There is no available token to authenticate to the service.'); } - // $date = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339_EXTENDED, $this->configuration[$url]['date']); - // if ($date <= new \DateTimeImmutable('-1 hour')) { - // throw new AccessDeniedException('The stored token has expired, you need a fresh token to authenticate to the service.'); - // } + $date = \DateTimeImmutable::createFromFormat(\DateTimeInterface::RFC3339_EXTENDED, $this->configuration[$url]['date']); + if ($date <= new \DateTimeImmutable('-1 hour')) { + throw new AccessDeniedException('The stored token has expired, you need a fresh token to authenticate to the service.'); + } return $this->configuration[$url]['token']; } diff --git a/src/Cloud/Command/Pipeline/DeclarePipelineCommand.php b/src/Cloud/Command/Pipeline/DeclarePipelineCommand.php index dca6f23d..7220a9d3 100644 --- a/src/Cloud/Command/Pipeline/DeclarePipelineCommand.php +++ b/src/Cloud/Command/Pipeline/DeclarePipelineCommand.php @@ -13,10 +13,7 @@ public function __construct( public string $code, public string $label, public DTO\StepList $steps, - public DTO\Autoload $autoload, - public DTO\PackageList $packages, - public DTO\RepositoryList $repositories, - public DTO\AuthList $auths, + public DTO\Composer $composer, public DTO\OrganizationId $organizationId, public DTO\WorkspaceId $project, ) { diff --git a/src/Cloud/Command/Workflow/AddAfterWorkflowJobCommand.php b/src/Cloud/Command/Workflow/AddAfterWorkflowJobCommand.php new file mode 100644 index 00000000..ac120466 --- /dev/null +++ b/src/Cloud/Command/Workflow/AddAfterWorkflowJobCommand.php @@ -0,0 +1,18 @@ + */ + public array $codes; + + public function __construct( + public DTO\WorkflowId $workflowId, + DTO\JobCode ...$codes, + ) { + $this->codes = $codes; + } +} diff --git a/src/Cloud/CommandBus.php b/src/Cloud/CommandBus.php index 940c0695..ce856758 100644 --- a/src/Cloud/CommandBus.php +++ b/src/Cloud/CommandBus.php @@ -30,6 +30,8 @@ public static function withStandardHandlers(Client $client): self Satellite\Cloud\Command\Pipeline\AddBeforePipelineStepCommand::class => new Satellite\Cloud\Handler\Pipeline\AddBeforePipelineStepCommandHandler($client), Satellite\Cloud\Command\Pipeline\ReplacePipelineStepCommand::class => new Satellite\Cloud\Handler\Pipeline\ReplacePipelineStepCommandHandler($client), Satellite\Cloud\Command\Pipeline\RemovePipelineStepCommand::class => new Satellite\Cloud\Handler\Pipeline\RemovePipelineStepCommandHandler($client), + Satellite\Cloud\Command\Workflow\DeclareWorkflowCommand::class => new Satellite\Cloud\Handler\Workflow\DeclareWorkflowCommandHandler($client), + Satellite\Cloud\Command\Workflow\RemoveWorkflowCommand::class => new Satellite\Cloud\Handler\Workflow\RemoveWorkflowCommandHandler($client), ]); } diff --git a/src/Cloud/Console/Command/ArgumentType.php b/src/Cloud/Console/Command/ArgumentType.php new file mode 100644 index 00000000..695f72ff --- /dev/null +++ b/src/Cloud/Console/Command/ArgumentType.php @@ -0,0 +1,11 @@ +warning('The current version of your configuration does not allow you to use Cloud commands. Please update your configuration at least to version 0.3.'); + + return self::INVALID; + } + $auth = new Satellite\Cloud\Auth(); try { @@ -121,17 +127,18 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O } $context = new Satellite\Cloud\Context($client, $auth, $url); - $pipeline = new Satellite\Cloud\Pipeline($context); - if (!\array_key_exists('version', $configuration)) { - foreach ($pipeline->create(Satellite\Cloud\Pipeline::fromLegacyConfiguration($configuration['satellite'])) as $command) { + + foreach ($configuration['satellites'] as $code => $satellite) { + $satellite['code'] = $code; + $instance = match (true) { + \array_key_exists('pipeline', $satellite) => new Satellite\Cloud\Pipeline($context), + \array_key_exists('workflow', $satellite) => new Satellite\Cloud\Workflow($context), + default => throw new \RuntimeException('Invalid runtime satellite configuration.'), + }; + + foreach ($instance->create($instance::fromLegacyConfiguration($satellite)) as $command) { $bus->push($command); } - } else { - foreach ($configuration['satellites'] as $satellite) { - foreach ($pipeline->create(Satellite\Cloud\Pipeline::fromLegacyConfiguration($satellite)) as $command) { - $bus->push($command); - } - } } $bus->execute(); diff --git a/src/Cloud/Console/Command/RemoveCommand.php b/src/Cloud/Console/Command/RemoveCommand.php index f48e1ae7..58da5a9f 100644 --- a/src/Cloud/Console/Command/RemoveCommand.php +++ b/src/Cloud/Console/Command/RemoveCommand.php @@ -22,11 +22,13 @@ protected function configure(): void $this->addOption('url', 'u', mode: Console\Input\InputArgument::OPTIONAL, description: 'Base URL of the cloud instance', default: 'https://app.gyroscops.com'); $this->addOption('beta', mode: Console\Input\InputOption::VALUE_NONE, description: 'Shortcut to set the cloud instance to https://beta.gyroscops.com'); $this->addOption('ssl', mode: Console\Input\InputOption::VALUE_NEGATABLE, description: 'Enable or disable SSL'); - $this->addArgument('config', Console\Input\InputArgument::REQUIRED); + $this->addOption('output', mode: Console\Input\InputOption::VALUE_OPTIONAL, description: 'Specify the path of the resulting tarball when building for Docker'); + $this->addArgument('config', mode: Console\Input\InputArgument::REQUIRED); } protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int { + $configuration = []; $style = new Console\Style\SymfonyStyle( $input, $output, @@ -56,10 +58,6 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O } catch (LoaderLoadException) { } } - - if (!isset($configuration)) { - throw new \RuntimeException('Could not find configuration file.'); - } } for ($directory = getcwd(); '/' !== $directory; $directory = \dirname($directory)) { @@ -88,6 +86,12 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O return self::FAILURE; } + if (!\array_key_exists('version', $configuration)) { + $style->warning('The current version of your configuration does not allow you to use Cloud commands. Please update your configuration at least to version 0.3.'); + + return self::INVALID; + } + $auth = new Satellite\Cloud\Auth(); try { $token = $auth->token($url); @@ -120,9 +124,13 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O } $context = new Satellite\Cloud\Context($client, $auth, $url); - $pipeline = new Satellite\Cloud\Pipeline($context); - $model = Satellite\Cloud\Pipeline::fromApiWithCode($client, $configuration['satellite']['pipeline']['code'], $configuration['satellite']['pipeline']); - foreach ($pipeline->remove($model->id()) as $command) { + $instance = match (true) { + \array_key_exists('pipeline', $configuration) => new Satellite\Cloud\Pipeline($context), + \array_key_exists('workflow', $configuration) => new Satellite\Cloud\Workflow($context), + default => throw new \RuntimeException('Invalid runtime satellite configuration.'), + }; + + foreach ($instance->remove($instance::fromApiWithCode($client, array_key_first($configuration['satellites']))->id()) as $command) { $bus->push($command); } diff --git a/src/Cloud/Console/Command/UpdateCommand.php b/src/Cloud/Console/Command/UpdateCommand.php index 7fb93962..e6128bb2 100644 --- a/src/Cloud/Console/Command/UpdateCommand.php +++ b/src/Cloud/Console/Command/UpdateCommand.php @@ -73,7 +73,7 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O } $context = new Satellite\Console\RuntimeContext( - $input->getOption('output') ?? 'php://fd/3', + 'php://fd/3', new Satellite\ExpressionLanguage\ExpressionLanguage(), ); @@ -88,6 +88,12 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O return self::FAILURE; } + if (!\array_key_exists('version', $configuration)) { + $style->warning('The current version of your configuration does not allow you to use Cloud commands. Please update your configuration at least to version 0.3.'); + + return self::INVALID; + } + $auth = new Satellite\Cloud\Auth(); try { $token = $auth->token($url); @@ -120,9 +126,13 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O } $context = new Satellite\Cloud\Context($client, $auth, $url); - $pipeline = new Satellite\Cloud\Pipeline($context); - $model = Satellite\Cloud\Pipeline::fromApiWithCode($client, $configuration['satellite']['pipeline']['code'], $configuration['satellite']['pipeline']); - foreach ($pipeline->update($model, Satellite\Cloud\Pipeline::fromLegacyConfiguration($configuration['satellite'])) as $command) { + $instance = match (true) { + \array_key_exists('pipeline', $configuration) => new Satellite\Cloud\Pipeline($context), + \array_key_exists('workflow', $configuration) => new Satellite\Cloud\Workflow($context), + default => throw new \RuntimeException('Invalid runtime satellite configuration.'), + }; + + foreach ($instance->update($instance::fromApiWithCode($client, array_key_first($configuration['satellites'])), $instance::fromLegacyConfiguration($configuration['satellite'])) as $command) { $bus->push($command); } diff --git a/src/Cloud/DTO/Composer.php b/src/Cloud/DTO/Composer.php new file mode 100644 index 00000000..0805047a --- /dev/null +++ b/src/Cloud/DTO/Composer.php @@ -0,0 +1,36 @@ +autoload; + } + + public function packages(): PackageList + { + return $this->packages; + } + + public function repositories(): RepositoryList + { + return $this->repositories; + } + + public function auths(): AuthList + { + return $this->auths; + } +} diff --git a/src/Cloud/DTO/JobCode.php b/src/Cloud/DTO/JobCode.php new file mode 100644 index 00000000..fd76d9f2 --- /dev/null +++ b/src/Cloud/DTO/JobCode.php @@ -0,0 +1,23 @@ +reference; + } + + public function __toString(): string + { + return $this->reference; + } +} diff --git a/src/Cloud/DTO/JobList.php b/src/Cloud/DTO/JobList.php new file mode 100644 index 00000000..6dc57b87 --- /dev/null +++ b/src/Cloud/DTO/JobList.php @@ -0,0 +1,60 @@ +jobs = $job; + } + + public function getIterator(): \Traversable + { + $jobs = $this->jobs; + + /* @phpstan-ignore-next-line */ + usort($jobs, fn (DTO\Workflow\JobInterface $left, DTO\Workflow\JobInterface $right) => $left->order <=> $right->order); + + return new \ArrayIterator($jobs); + } + + public function codes(): array + { + $jobs = $this->jobs; + + /* @phpstan-ignore-next-line */ + usort($jobs, fn (DTO\Workflow\JobInterface $left, DTO\Workflow\JobInterface $right) => $left->order <=> $right->order); + + /* @phpstan-ignore-next-line */ + return array_map(fn (DTO\Workflow\JobInterface $job) => $job->code->asString(), $jobs); + } + + public function get(string $code): DTO\Workflow\JobInterface + { + foreach ($this->jobs as $job) { + if ($job->code->asString() === $code) { + return $job; + } + } + + throw new \OutOfBoundsException('There was no job found matching the provided code'); + } + + public function count(): int + { + return \count($this->jobs); + } + + public function map(callable $callback): array + { + return array_map($callback, $this->jobs); + } +} diff --git a/src/Cloud/DTO/Pipeline.php b/src/Cloud/DTO/Pipeline.php index 38874c78..a5309031 100644 --- a/src/Cloud/DTO/Pipeline.php +++ b/src/Cloud/DTO/Pipeline.php @@ -4,16 +4,13 @@ namespace Kiboko\Component\Satellite\Cloud\DTO; -final readonly class Pipeline implements PipelineInterface +final readonly class Pipeline implements SatelliteInterface, PipelineInterface { public function __construct( private string $label, private string $code, private StepList $steps, - private Autoload $autoload = new Autoload(), - private PackageList $packages = new PackageList(), - private RepositoryList $repositories = new RepositoryList(), - private AuthList $auths = new AuthList(), + private Composer $composer, ) { } @@ -32,23 +29,8 @@ public function steps(): StepList return $this->steps; } - public function autoload(): Autoload + public function composer(): Composer { - return $this->autoload; - } - - public function packages(): PackageList - { - return $this->packages; - } - - public function repositories(): RepositoryList - { - return $this->repositories; - } - - public function auths(): AuthList - { - return $this->auths; + return $this->composer; } } diff --git a/src/Cloud/DTO/PipelineInterface.php b/src/Cloud/DTO/PipelineInterface.php index 3e243065..afdc7a19 100644 --- a/src/Cloud/DTO/PipelineInterface.php +++ b/src/Cloud/DTO/PipelineInterface.php @@ -11,12 +11,4 @@ public function code(): string; public function label(): string; public function steps(): StepList; - - public function autoload(): Autoload; - - public function packages(): PackageList; - - public function repositories(): RepositoryList; - - public function auths(): AuthList; } diff --git a/src/Cloud/DTO/ReferencedPipeline.php b/src/Cloud/DTO/ReferencedPipeline.php index 05098b9a..d5c21f1f 100644 --- a/src/Cloud/DTO/ReferencedPipeline.php +++ b/src/Cloud/DTO/ReferencedPipeline.php @@ -34,21 +34,21 @@ public function steps(): StepList public function autoload(): Autoload { - return $this->decorated->autoload(); + return $this->decorated->composer()->autoload(); } public function packages(): PackageList { - return $this->decorated->packages(); + return $this->decorated->composer()->packages(); } public function repositories(): RepositoryList { - return $this->decorated->repositories(); + return $this->decorated->composer()->repositories(); } public function auths(): AuthList { - return $this->decorated->auths(); + return $this->decorated->composer()->auths(); } } diff --git a/src/Cloud/DTO/ReferencedWorkflow.php b/src/Cloud/DTO/ReferencedWorkflow.php new file mode 100644 index 00000000..e338b089 --- /dev/null +++ b/src/Cloud/DTO/ReferencedWorkflow.php @@ -0,0 +1,54 @@ +id; + } + + public function code(): string + { + return $this->decorated->code(); + } + + public function label(): string + { + return $this->decorated->label(); + } + + public function jobs(): JobList + { + return $this->decorated->jobs(); + } + + public function autoload(): Autoload + { + return $this->decorated->composer()->autoload(); + } + + public function packages(): PackageList + { + return $this->decorated->composer()->packages(); + } + + public function repositories(): RepositoryList + { + return $this->decorated->composer()->repositories(); + } + + public function auths(): AuthList + { + return $this->decorated->composer()->auths(); + } +} diff --git a/src/Cloud/DTO/SatelliteId.php b/src/Cloud/DTO/SatelliteId.php new file mode 100644 index 00000000..355e25af --- /dev/null +++ b/src/Cloud/DTO/SatelliteId.php @@ -0,0 +1,23 @@ +reference; + } + + public function __toString(): string + { + return $this->reference; + } +} diff --git a/src/Cloud/DTO/SatelliteInterface.php b/src/Cloud/DTO/SatelliteInterface.php new file mode 100644 index 00000000..1859ddf8 --- /dev/null +++ b/src/Cloud/DTO/SatelliteInterface.php @@ -0,0 +1,14 @@ +code; + } + + public function label(): string + { + return $this->label; + } + + public function composer(): Composer + { + return $this->composer; + } + + public function jobs(): JobList + { + return $this->jobs; + } +} diff --git a/src/Cloud/DTO/Workflow/Action.php b/src/Cloud/DTO/Workflow/Action.php new file mode 100644 index 00000000..94d6d121 --- /dev/null +++ b/src/Cloud/DTO/Workflow/Action.php @@ -0,0 +1,18 @@ +reference; + } + + public function __toString(): string + { + return $this->reference; + } +} diff --git a/src/Cloud/DTO/WorkflowInterface.php b/src/Cloud/DTO/WorkflowInterface.php new file mode 100644 index 00000000..06b78a1b --- /dev/null +++ b/src/Cloud/DTO/WorkflowInterface.php @@ -0,0 +1,14 @@ +codes(); + $rightPositions = $right->codes(); + + foreach ($rightPositions as $desiredPosition => $code) { + // If the $right code does not exist in the $left list, then the step must be added + if (false !== array_search($code, $leftPositions, true)) { + continue; + } + + if (0 === $desiredPosition) { + $commands->push(new \Kiboko\Component\Satellite\Cloud\Command\Workflow\PrependWorkflowJobCommand($this->workflowId, $right->get($code))); + } else { + $commands->push(new \Kiboko\Component\Satellite\Cloud\Command\Workflow\AddAfterWorkflowJobCommand($this->workflowId, new DTO\JobCode($rightPositions[$desiredPosition - 1]), $right->get($code))); + } + } + + $offset = 0; + $needsReorder = false; + foreach ($leftPositions as $currentPosition => $code) { + // If the $left code does not exist in the $right list, then the step must be removed + if (($desiredPosition = array_search($code, $rightPositions, true)) === false) { + ++$offset; + $commands->push(new \Kiboko\Component\Satellite\Cloud\Command\Workflow\RemoveWorkflowJobCommand($this->workflowId, new DTO\StepCode($code))); + continue; + } + + if (($desiredPosition + $offset) > $currentPosition) { + $needsReorder = true; + } + } + + if (true === $needsReorder) { + $commands->push(new \Kiboko\Component\Satellite\Cloud\Command\Workflow\ReorderWorkflowJobCommand( + $this->workflowId, + ...array_map(fn (string $code) => new DTO\JobCode($code), $rightPositions) + )); + } + + return $commands; + } +} diff --git a/src/Cloud/Event/Workflow/WorkflowDeclared.php b/src/Cloud/Event/Workflow/WorkflowDeclared.php new file mode 100644 index 00000000..6b6dcdf7 --- /dev/null +++ b/src/Cloud/Event/Workflow/WorkflowDeclared.php @@ -0,0 +1,18 @@ +id; + } +} diff --git a/src/Cloud/Event/Workflow/WorkflowRemoved.php b/src/Cloud/Event/Workflow/WorkflowRemoved.php new file mode 100644 index 00000000..59152c67 --- /dev/null +++ b/src/Cloud/Event/Workflow/WorkflowRemoved.php @@ -0,0 +1,18 @@ +id; + } +} diff --git a/src/Cloud/Handler/Pipeline/AddAfterPipelineStepCommandHandler.php b/src/Cloud/Handler/Pipeline/AddAfterPipelineStepCommandHandler.php index e07d1f9a..d694c628 100644 --- a/src/Cloud/Handler/Pipeline/AddAfterPipelineStepCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/AddAfterPipelineStepCommandHandler.php @@ -35,11 +35,6 @@ public function __invoke(Cloud\Command\Pipeline\AddAfterPipelineStepCommand $com throw new Cloud\AddAfterPipelineStepFailedException('Something went wrong while trying to add a new step after an existing pipeline step. It seems the data you sent was invalid, please check your input.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\AddAfterPipelineStepFailedException('Something went wrong while trying to add a new step after an existing pipeline step.'); - } - return new Cloud\Event\AddedAfterPipelineStep($result->id); } } diff --git a/src/Cloud/Handler/Pipeline/AddBeforePipelineStepCommandHandler.php b/src/Cloud/Handler/Pipeline/AddBeforePipelineStepCommandHandler.php index 680679bd..74390b24 100644 --- a/src/Cloud/Handler/Pipeline/AddBeforePipelineStepCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/AddBeforePipelineStepCommandHandler.php @@ -35,11 +35,6 @@ public function __invoke(Cloud\Command\Pipeline\AddBeforePipelineStepCommand $co throw new Cloud\AddBeforePipelineStepFailedException('Something went wrong while trying to add a new step before an existing pipeline step. It seems the data you sent was invalid, please check your input.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\AddBeforePipelineStepFailedException('Something went wrong while trying to add a new step before an existing pipeline step.'); - } - return new Cloud\Event\AddedBeforePipelineStep($result->id); } } diff --git a/src/Cloud/Handler/Pipeline/AddPipelineComposerPSR4AutoloadCommandHandler.php b/src/Cloud/Handler/Pipeline/AddPipelineComposerPSR4AutoloadCommandHandler.php index c7dbc610..6c869ad8 100644 --- a/src/Cloud/Handler/Pipeline/AddPipelineComposerPSR4AutoloadCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/AddPipelineComposerPSR4AutoloadCommandHandler.php @@ -30,11 +30,6 @@ public function __invoke(Cloud\Command\Pipeline\AddPipelineComposerPSR4AutoloadC throw new Cloud\AddPipelineComposerPSR4AutoloadFailedException('Something went wrong while trying to add PSR4 autoloads into the pipeline. It seems the data you sent was invalid, please check your input.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\AddPipelineComposerPSR4AutoloadFailedException('Something went wrong while trying to add PSR4 autoloads into the pipeline.'); - } - return new Cloud\Event\AddedPipelineComposerPSR4Autoload($result->id, $result->namespace, $result->paths); } } diff --git a/src/Cloud/Handler/Pipeline/AddPipelineStepProbeCommandHandler.php b/src/Cloud/Handler/Pipeline/AddPipelineStepProbeCommandHandler.php index 24489f19..97fa00e5 100644 --- a/src/Cloud/Handler/Pipeline/AddPipelineStepProbeCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/AddPipelineStepProbeCommandHandler.php @@ -33,11 +33,6 @@ public function __invoke(Cloud\Command\Pipeline\AddPipelineStepProbeCommand $com throw new Cloud\AddPipelineStepProbeFailedException('Something went wrong while trying to add a probe into an existing pipeline step. It seems the data you sent was invalid, please check your input.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\AddPipelineStepProbeFailedException('Something went wrong while trying to add a probe into an existing pipeline step.'); - } - return new Cloud\Event\AddedPipelineStepProbe($result->id); } } diff --git a/src/Cloud/Handler/Pipeline/AppendPipelineStepCommandHandler.php b/src/Cloud/Handler/Pipeline/AppendPipelineStepCommandHandler.php index 54687946..7b8c0b53 100644 --- a/src/Cloud/Handler/Pipeline/AppendPipelineStepCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/AppendPipelineStepCommandHandler.php @@ -35,11 +35,6 @@ public function __invoke(Cloud\Command\Pipeline\AppendPipelineStepCommand $comma throw new Cloud\AppendPipelineStepFailedException('Something went wrong while trying to append a pipeline step. It seems the data you sent was invalid, please check your input.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\AppendPipelineStepFailedException('Something went wrong while trying to append a pipeline step.'); - } - return new Cloud\Event\AppendedPipelineStep($result->id); } } diff --git a/src/Cloud/Handler/Pipeline/CompilePipelineCommandHandler.php b/src/Cloud/Handler/Pipeline/CompilePipelineCommandHandler.php index b9312e48..c434e0bf 100644 --- a/src/Cloud/Handler/Pipeline/CompilePipelineCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/CompilePipelineCommandHandler.php @@ -28,11 +28,6 @@ public function __invoke(Cloud\Command\Pipeline\CompilePipelineCommand $command) throw new Cloud\CompilePipelineFailedException('Something went wrong while trying to compile the pipeline. It seems the data you sent was invalid, please check your input.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\CompilePipelineFailedException('Something went wrong while trying to compile the pipeline.'); - } - return new Cloud\Event\CompiledPipeline($result->id); } } diff --git a/src/Cloud/Handler/Pipeline/DeclarePipelineCommandHandler.php b/src/Cloud/Handler/Pipeline/DeclarePipelineCommandHandler.php index 66b41643..aa487e3f 100644 --- a/src/Cloud/Handler/Pipeline/DeclarePipelineCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/DeclarePipelineCommandHandler.php @@ -7,6 +7,7 @@ use Gyroscops\Api; use Kiboko\Component\Satellite\Cloud; use Kiboko\Component\Satellite\Cloud\DTO\Probe; +use Kiboko\Component\Satellite\Cloud\DTO\PSR4AutoloadConfig; use Kiboko\Component\Satellite\Cloud\DTO\Step; final readonly class DeclarePipelineCommandHandler @@ -33,8 +34,26 @@ public function __invoke(Cloud\Command\Pipeline\DeclarePipelineCommand $command) fn (Probe $probe) => (new Api\Model\Probe())->setCode($probe->code)->setLabel($probe->label)) ) )) - // TODO : implements the composer declaration - // ->setComposer(), + ->setComposer( + (new Api\Model\Composer()) + ->setAutoloads($command->composer->autoload()->map( + fn (PSR4AutoloadConfig $autoloadConfig) => (new Api\Model\ComposerAutoload()) + ->setNamespace($autoloadConfig->namespace) + ->setPaths($autoloadConfig->paths) + )) + ->setPackages($command->composer->packages()->transform()) + ->setAuthentications($command->composer->auths()->map( + fn (Cloud\DTO\Auth $auth) => (new Api\Model\ComposerAuthentication()) + ->setUrl($auth->url) + ->setToken($auth->token) + )) + ->setRepositories($command->composer->repositories()->map( + fn (Cloud\DTO\Repository $repository) => (new Api\Model\ComposerRepository()) + ->setName($repository->name) + ->setType($repository->type) + ->setUrl($repository->url) + )) + ), ); } catch (Api\Exception\DeclarePipelinePipelineCollectionBadRequestException $exception) { throw new Cloud\DeclarePipelineFailedException('Something went wrong while declaring the pipeline. Maybe your client is not up to date, you may want to update your Gyroscops client.', previous: $exception); @@ -42,11 +61,6 @@ public function __invoke(Cloud\Command\Pipeline\DeclarePipelineCommand $command) throw new Cloud\DeclarePipelineFailedException('Something went wrong while declaring the pipeline. It seems the data you sent was invalid, please check your input.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\DeclarePipelineFailedException('Something went wrong while declaring the pipeline.'); - } - return new Cloud\Event\PipelineDeclared($result->id); } } diff --git a/src/Cloud/Handler/Pipeline/RemovePipelineCommandHandler.php b/src/Cloud/Handler/Pipeline/RemovePipelineCommandHandler.php index 33c4b1a7..cf4be559 100644 --- a/src/Cloud/Handler/Pipeline/RemovePipelineCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/RemovePipelineCommandHandler.php @@ -23,11 +23,6 @@ public function __invoke(Cloud\Command\Pipeline\RemovePipelineCommand $command): throw new Cloud\RemovePipelineFailedException('Something went wrong while trying to remove a step from the pipeline. Maybe you are trying to delete a pipeline that never existed or has already been deleted.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\RemovePipelineFailedException('Something went wrong while trying to remove a step from the pipeline.'); - } - return new Cloud\Event\RemovedPipeline((string) $command->pipeline); } } diff --git a/src/Cloud/Handler/Pipeline/RemovePipelineStepCommandHandler.php b/src/Cloud/Handler/Pipeline/RemovePipelineStepCommandHandler.php index 623e325e..313b6714 100644 --- a/src/Cloud/Handler/Pipeline/RemovePipelineStepCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/RemovePipelineStepCommandHandler.php @@ -26,11 +26,6 @@ public function __invoke(Cloud\Command\Pipeline\RemovePipelineStepCommand $comma throw new Cloud\RemovePipelineStepFailedException('Something went wrong while trying to remove a probe from the step. Maybe you are trying to delete a step that never existed or has already been deleted.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\RemovePipelineStepFailedException('Something went wrong while trying to remove a probe from the step.'); - } - return new Cloud\Event\RemovedPipelineStep((string) $command->code); } } diff --git a/src/Cloud/Handler/Pipeline/RemovePipelineStepProbeCommandHandler.php b/src/Cloud/Handler/Pipeline/RemovePipelineStepProbeCommandHandler.php index b09aa58b..e163f8ce 100644 --- a/src/Cloud/Handler/Pipeline/RemovePipelineStepProbeCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/RemovePipelineStepProbeCommandHandler.php @@ -28,11 +28,6 @@ public function __invoke(Cloud\Command\Pipeline\RemovePipelineStepProbeCommand $ throw new Cloud\RemovePipelineStepProbeFailedException('Something went wrong while removing a probe from the step. Maybe you are trying to delete a probe that never existed or has already been deleted.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\RemovePipelineStepProbeFailedException('Something went wrong while removing a probe from the step.'); - } - return new Cloud\Event\RemovedPipelineStepProbe($result->id); } } diff --git a/src/Cloud/Handler/Pipeline/ReplacePipelineStepCommandHandler.php b/src/Cloud/Handler/Pipeline/ReplacePipelineStepCommandHandler.php index cc9ab593..b1782299 100644 --- a/src/Cloud/Handler/Pipeline/ReplacePipelineStepCommandHandler.php +++ b/src/Cloud/Handler/Pipeline/ReplacePipelineStepCommandHandler.php @@ -35,11 +35,6 @@ public function __invoke(Cloud\Command\Pipeline\ReplacePipelineStepCommand $comm throw new Cloud\ReplacePipelineStepFailedException('Something went wrong while replacing a step from the pipeline. It seems the data you sent was invalid, please check your input.', previous: $exception); } - if (null === $result) { - // TODO: change the exception message, it doesn't give enough details on how to fix the issue - throw new Cloud\ReplacePipelineStepFailedException('Something went wrong while replacing a step from the pipeline.'); - } - return new Cloud\Event\ReplacedPipelineStep($result->id); } } diff --git a/src/Cloud/Handler/Workflow/DeclareWorkflowCommandHandler.php b/src/Cloud/Handler/Workflow/DeclareWorkflowCommandHandler.php new file mode 100644 index 00000000..54bb1f33 --- /dev/null +++ b/src/Cloud/Handler/Workflow/DeclareWorkflowCommandHandler.php @@ -0,0 +1,92 @@ +client->declareWorkflowWorkflowCollection( + (new Api\Model\WorkflowDeclareWorkflowCommandInput()) + ->setLabel($command->label) + ->setCode($command->code) + ->setComposer( + (new Api\Model\Composer()) + ->setAutoloads($command->composer->autoload()->map( + fn (PSR4AutoloadConfig $autoloadConfig) => (new Api\Model\ComposerAutoload()) + ->setNamespace($autoloadConfig->namespace) + ->setType('psr-4') + ->setPaths($autoloadConfig->paths) + )) + ->setPackages($command->composer->packages()->transform()) + ->setAuthentications($command->composer->auths()->map( + fn (Cloud\DTO\Auth $auth) => (new Api\Model\ComposerAuthentication()) + ->setUrl($auth->url) + ->setToken($auth->token) + )) + ->setRepositories($command->composer->repositories()->map( + fn (Cloud\DTO\Repository $repository) => (new Api\Model\ComposerRepository()) + ->setName($repository->name) + ->setType($repository->type) + ->setUrl($repository->url) + )) + ) + ->setJobs( + $command->jobs->map( + function (Cloud\DTO\Workflow\JobInterface $job) { + if ($job instanceof Cloud\DTO\Workflow\Pipeline) { + return (new Api\Model\Job()) + ->setCode($job->code->asString()) + ->setLabel($job->label) + ->setPipeline( + (new Api\Model\Pipeline()) + ->setSteps( + $job->stepList->map( + fn (Cloud\DTO\Step $step) => (new Api\Model\Step()) + ->setCode($step->code->asString()) + ->setLabel($step->label) + ->setConfiguration($step->config) + ) + ) + ) + ; + } + + if ($job instanceof Cloud\DTO\Workflow\Action) { + return (new Api\Model\Job()) + ->setCode($job->code->asString()) + ->setLabel($job->label) + ->setAction( + (new Api\Model\Action()) + ->setConfiguration($job->configuration) + ) + ; + } + + throw new \RuntimeException('Unexpected instance of PipelineInterface.'); + } + ) + ), + ); + } catch (Api\Exception\DeclareWorkflowWorkflowCollectionBadRequestException $exception) { + throw new Cloud\DeclareWorkflowFailedException('Something went wrong while declaring the workflow. Maybe your client is not up to date, you may want to update your Gyroscops client.', previous: $exception); + } catch (Api\Exception\DeclareWorkflowWorkflowCollectionUnprocessableEntityException $exception) { + throw new Cloud\DeclareWorkflowFailedException('Something went wrong while declaring the workflow. It seems the data you sent was invalid, please check your input.', previous: $exception); + } + + return new Cloud\Event\Workflow\WorkflowDeclared($result->getId()); + } +} diff --git a/src/Cloud/Handler/Workflow/RemoveWorkflowCommandHandler.php b/src/Cloud/Handler/Workflow/RemoveWorkflowCommandHandler.php new file mode 100644 index 00000000..feb27c12 --- /dev/null +++ b/src/Cloud/Handler/Workflow/RemoveWorkflowCommandHandler.php @@ -0,0 +1,34 @@ +client->softDeleteWorkflowItem( + $command->id->asString() + ); + } catch (Api\Exception\SoftDeleteWorkflowItemBadRequestException $exception) { + throw new Cloud\RemoveWorkflowFailedException('Something went wrong while removing the workflow. Maybe your client is not up to date, you may want to update your Gyroscops client.', previous: $exception); + } catch (Api\Exception\SoftDeleteWorkflowItemUnprocessableEntityException $exception) { + throw new Cloud\RemoveWorkflowFailedException('Something went wrong while removing the workflow. It seems the data you sent was invalid, please check your input.', previous: $exception); + } catch (Api\Exception\SoftDeleteWorkflowItemNotFoundException $exception) { + throw new Cloud\RemoveWorkflowFailedException('Something went wrong while removing the workflow. It seems the data you want to delete do not exists.', previous: $exception); + } + + return new Cloud\Event\Workflow\WorkflowRemoved($result->getId()); + } +} diff --git a/src/Cloud/Pipeline.php b/src/Cloud/Pipeline.php index 52d3e80d..ff7e9417 100644 --- a/src/Cloud/Pipeline.php +++ b/src/Cloud/Pipeline.php @@ -6,6 +6,7 @@ use Gyroscops\Api; use Kiboko\Component\Satellite\Cloud\DTO\AuthList; +use Kiboko\Component\Satellite\Cloud\DTO\Composer; use Kiboko\Component\Satellite\Cloud\DTO\Package; use Kiboko\Component\Satellite\Cloud\DTO\PipelineId; use Kiboko\Component\Satellite\Cloud\DTO\ProbeList; @@ -27,7 +28,7 @@ public static function fromLegacyConfiguration(array $configuration): DTO\Pipeli return new DTO\Pipeline( $configuration['pipeline']['name'] ?? sprintf('Pipeline %s', $random), - $configuration['pipeline']['code'] ?? sprintf('pipeline_%s', $random), + $configuration['code'] ?? sprintf('pipeline_%s', $random), new DTO\StepList( ...array_map(function (array $stepConfig, int $order) { $name = $stepConfig['name'] ?? sprintf('step%d', $order); @@ -51,34 +52,36 @@ public static function fromLegacyConfiguration(array $configuration): DTO\Pipeli ); }, $configuration['pipeline']['steps'], range(0, (is_countable($configuration['pipeline']['steps']) ? \count($configuration['pipeline']['steps']) : 0) - 1)) ), - new DTO\Autoload( - ...array_map( - fn (string $namespace, array $paths): DTO\PSR4AutoloadConfig => new DTO\PSR4AutoloadConfig($namespace, ...$paths['paths']), - array_keys($configuration['composer']['autoload']['psr4'] ?? []), - $configuration['composer']['autoload']['psr4'] ?? [], - ) - ), - new DTO\PackageList( - ...array_map( - function (string $namespace) { - $parts = explode(':', $namespace); - - return new Package($parts[0], $parts[1]); - }, - $configuration['composer']['require'] ?? [], - ) - ), - new RepositoryList( - ...array_map( - fn (array $repository): DTO\Repository => new DTO\Repository($repository['name'], $repository['type'], $repository['url']), - $configuration['composer']['repositories'] ?? [], - ) - ), - new AuthList( - ...array_map( - fn (array $repository): DTO\Auth => new DTO\Auth($repository['url'], $repository['token']), - $configuration['composer']['auth'] ?? [], - ) + new Composer( + new DTO\Autoload( + ...array_map( + fn (string $namespace, array $paths): DTO\PSR4AutoloadConfig => new DTO\PSR4AutoloadConfig($namespace, ...$paths['paths']), + array_keys($configuration['composer']['autoload']['psr4'] ?? []), + $configuration['composer']['autoload']['psr4'] ?? [], + ) + ), + new DTO\PackageList( + ...array_map( + function (string $namespace) { + $parts = explode(':', $namespace); + + return new Package($parts[0], $parts[1]); + }, + $configuration['composer']['require'] ?? [], + ) + ), + new RepositoryList( + ...array_map( + fn (array $repository): DTO\Repository => new DTO\Repository($repository['name'], $repository['type'], $repository['url']), + $configuration['composer']['repositories'] ?? [], + ) + ), + new AuthList( + ...array_map( + fn (array $repository): DTO\Auth => new DTO\Auth($repository['url'], $repository['token']), + $configuration['composer']['auth'] ?? [], + ) + ), ), ); } @@ -150,56 +153,55 @@ private static function fromApiModel(Api\Client $client, Api\Model\PipelineRead ); }, $steps, range(0, \count($steps))) ), - new DTO\Autoload( - ...array_map( - fn (string $namespace, array $paths): DTO\PSR4AutoloadConfig => new DTO\PSR4AutoloadConfig($namespace, ...$paths['paths']), - array_keys($configuration['composer']['autoload']['psr4'] ?? []), - $model->getAutoload(), - ) - ), - new DTO\PackageList( - ...array_map( - function (string $namespace) { - $parts = explode(':', $namespace); - - return new Package($parts[0], $parts[1]); - }, - $model->getPackages(), - ) - ), - new RepositoryList( - ...array_map( - fn (array $repository): DTO\Repository => new DTO\Repository($repository['name'], $repository['type'], $repository['url']), - $model->getRepositories(), - ) - ), - new AuthList( - ...array_map( - fn (array $repository): DTO\Auth => new DTO\Auth($repository['url'], $repository['token']), - $model->getAuths(), - ) + new Composer( + new DTO\Autoload( + ...array_map( + fn (string $namespace, array $paths): DTO\PSR4AutoloadConfig => new DTO\PSR4AutoloadConfig($namespace, ...$paths['paths']), + array_keys($configuration['composer']['autoload']['psr4'] ?? []), + $model->getAutoload(), + ) + ), + new DTO\PackageList( + ...array_map( + function (string $namespace) { + $parts = explode(':', $namespace); + + return new Package($parts[0], $parts[1]); + }, + $model->getPackages(), + ) + ), + new RepositoryList( + ...array_map( + fn (array $repository): DTO\Repository => new DTO\Repository($repository['name'], $repository['type'], $repository['url']), + $model->getRepositories(), + ) + ), + new AuthList( + ...array_map( + fn (array $repository): DTO\Auth => new DTO\Auth($repository['url'], $repository['token']), + $model->getAuths(), + ) + ), ), ); } - public function create(DTO\PipelineInterface $pipeline): DTO\CommandBatch + public function create(DTO\PipelineInterface&DTO\SatelliteInterface $pipeline): DTO\CommandBatch { return new DTO\CommandBatch( new Command\Pipeline\DeclarePipelineCommand( $pipeline->code(), $pipeline->label(), $pipeline->steps(), - $pipeline->autoload(), - $pipeline->packages(), - $pipeline->repositories(), - $pipeline->auths(), + $pipeline->composer(), $this->context->organization(), $this->context->workspace(), ) ); } - public function update(ReferencedPipeline $actual, DTO\PipelineInterface $desired): DTO\CommandBatch + public function update(ReferencedPipeline $actual, DTO\PipelineInterface&DTO\SatelliteInterface $desired): DTO\CommandBatch { if ($actual->code() !== $desired->code()) { throw new \RuntimeException('Code does not match between actual and desired pipeline definition.'); @@ -213,7 +215,7 @@ public function update(ReferencedPipeline $actual, DTO\PipelineInterface $desire $commands = $diff->diff($actual->steps(), $desired->steps()); // Check the changes in the list of autoloads - if (\count($actual->autoload()) !== \count($desired->autoload())) { + if (\count($actual->autoload()) !== \count($desired->composer()->autoload())) { // TODO: make diff of the autoload } diff --git a/src/Cloud/PipelineInterface.php b/src/Cloud/PipelineInterface.php index 30792e4f..8b322880 100644 --- a/src/Cloud/PipelineInterface.php +++ b/src/Cloud/PipelineInterface.php @@ -15,9 +15,9 @@ public static function fromApiWithId(Client $client, PipelineId $id, array $conf public static function fromApiWithCode(Client $client, string $code, array $configuration): DTO\ReferencedPipeline; - public function create(DTO\PipelineInterface $pipeline): DTO\CommandBatch; + public function create(DTO\PipelineInterface&DTO\SatelliteInterface $pipeline): DTO\CommandBatch; - public function update(DTO\ReferencedPipeline $actual, DTO\PipelineInterface $desired): DTO\CommandBatch; + public function update(DTO\ReferencedPipeline $actual, DTO\PipelineInterface&DTO\SatelliteInterface $desired): DTO\CommandBatch; public function remove(PipelineId $id): DTO\CommandBatch; } diff --git a/src/Cloud/RemoveWorkflowFailedException.php b/src/Cloud/RemoveWorkflowFailedException.php new file mode 100644 index 00000000..f40b6b36 --- /dev/null +++ b/src/Cloud/RemoveWorkflowFailedException.php @@ -0,0 +1,9 @@ + new Step( + $step['name'] ?? sprintf('step%d', $order), + new StepCode($step['code'] ?? sprintf('step%d', $order)), + $step, + new ProbeList(), + $order + ), + $config['pipeline']['steps'], + range(0, (is_countable($config['pipeline']['steps']) ? \count($config['pipeline']['steps']) : 0) - 1) + ), + ), + $order + ); + } + + if (\array_key_exists('action', $config)) { + $name = $config['action']['name'] ?? sprintf('action%d', $order); + $code = $config['action']['code'] ?? sprintf('action%d', $order); + unset($config['action']['name'], $config['action']['code']); + + array_walk_recursive($config, function (&$value): void { + if ($value instanceof Expression) { + $value = '@='.$value; + } + }); + + $configuration = $config['action']; + unset($config['action']); + $config += $configuration; + + return new DTO\Workflow\Action( + $name, + new JobCode($code), + $config, + $order, + ); + } + + throw new \RuntimeException('This type is currently not supported.'); + }, + $configuration['workflow']['jobs'], + range(0, (is_countable($configuration['workflow']['jobs']) ? \count($configuration['workflow']['jobs']) : 0) - 1) + ) + ), + new Composer( + new DTO\Autoload( + ...array_map( + fn (string $namespace, array $paths): DTO\PSR4AutoloadConfig => new DTO\PSR4AutoloadConfig($namespace, ...$paths['paths']), + array_keys($configuration['composer']['autoload']['psr4'] ?? []), + $configuration['composer']['autoload']['psr4'] ?? [], + ) + ), + new DTO\PackageList( + ...array_map( + function (string $namespace) { + $parts = explode(':', $namespace); + + return new Package($parts[0], $parts[1] ?? '*'); + }, + $configuration['composer']['require'] ?? [], + ) + ), + new RepositoryList( + ...array_map( + fn (array $repository): DTO\Repository => new DTO\Repository($repository['name'], $repository['type'], $repository['url']), + $configuration['composer']['repositories'] ?? [], + ) + ), + new AuthList( + ...array_map( + fn (array $repository): DTO\Auth => new DTO\Auth($repository['url'], $repository['token']), + $configuration['composer']['auth'] ?? [], + ) + ), + ), + ); + } + + public static function fromApiWithId(Api\Client $client, WorkflowId $id): ReferencedWorkflow + { + /** @var Api\Model\WorkflowJsonldRead|Api\Model\WorkflowRead $item */ + $item = $client->getWorkflowItem($id->asString()); + + try { + \assert($item instanceof Api\Model\PipelineRead); + } catch (\AssertionError) { + throw new AccessDeniedException('Could not retrieve the workflow.'); + } + + return new ReferencedWorkflow( + new WorkflowId($item->getId()), + self::fromApiModel($client, $item) + ); + } + + public static function fromApiWithCode(Api\Client $client, string $code): ReferencedWorkflow + { + $collection = $client->getWorkflowCollection(['code' => $code]); + + try { + \assert(\is_array($collection)); + } catch (\AssertionError) { + throw new AccessDeniedException('Could not retrieve the workflow.'); + } + + try { + \assert(1 === \count($collection)); + \assert($collection[0] instanceof Api\Model\WorkflowRead); + } catch (\AssertionError) { + throw new \OverflowException('There seems to be several workflows with the same code, please contact your Customer Success Manager.'); + } + + return new ReferencedWorkflow( + new WorkflowId($collection[0]->getId()), + self::fromApiModel($client, $collection[0]) + ); + } + + private static function fromApiModel(Api\Client $client, Api\Model\WorkflowRead $model): DTO\Workflow + { + /** @var Api\Model\WorkflowJsonldRead|Api\Model\WorkflowRead $workflow */ + $workflow = $client->getWorkflowItem($model->getId()); + + return new DTO\Workflow( + $workflow->getLabel(), + $workflow->getCode(), + new DTO\JobList( + ...array_map(function (Api\Model\Job $job, int $order) { + if (null !== $job->getPipeline()) { + return new DTO\Workflow\Pipeline( + $job->getLabel(), + new JobCode($job->getCode()), + new StepList(...array_map( + fn (Api\Model\PipelineStepRead $step, int $order) => new Step( + $step->getLabel(), + new StepCode($step->getCode()), + $step->getConfiguration(), + /* TODO : implement probes when it is enabled */ + new ProbeList(), + $order + ), + $steps = $job->getPipeline()->getSteps(), + range(0, \count((array) $steps)), + )), + $order + ); + } + + if (null !== $job->getAction()) { + return new DTO\Workflow\Action( + $job->getLabel(), + new JobCode($job->getCode()), + $job->getAction()->getConfiguration(), + $order, + ); + } + + throw new \RuntimeException('This type of job is not currently supported.'); + }, $jobs = $workflow->getJobs(), range(0, \count((array) $jobs))) + ), + new Composer( + new DTO\Autoload( + ...array_map( + fn (string $namespace, array $paths): DTO\PSR4AutoloadConfig => new DTO\PSR4AutoloadConfig($namespace, ...$paths['paths']), + array_keys($workflow->getAutoload()), + $model->getAutoload(), + ) + ), + new DTO\PackageList( + ...array_map( + function (string $namespace) { + $parts = explode(':', $namespace); + + return new Package($parts[0], $parts[1] ?? '*'); + }, + $workflow->getPackages(), + ) + ), + new RepositoryList( + ...array_map( + fn (array $repository): DTO\Repository => new DTO\Repository($repository['name'], $repository['type'], $repository['url']), + $workflow->getRepositories(), + ) + ), + new AuthList( + ...array_map( + fn (array $repository): DTO\Auth => new DTO\Auth($repository['url'], $repository['token']), + $workflow->getAuths(), + ) + ), + ), + ); + } + + public function create(DTO\SatelliteInterface&DTO\WorkflowInterface $workflow): DTO\CommandBatch + { + return new DTO\CommandBatch( + new Command\Workflow\DeclareWorkflowCommand( + $workflow->code(), + $workflow->label(), + $workflow->jobs(), + $workflow->composer(), + $this->context->organization(), + $this->context->workspace(), + ) + ); + } + + public function update(ReferencedWorkflow $actual, DTO\SatelliteInterface&DTO\WorkflowInterface $desired): DTO\CommandBatch + { + if ($actual->code() !== $desired->code()) { + throw new \RuntimeException('Code does not match between actual and desired workflow definition.'); + } + if ($actual->label() !== $desired->label()) { + throw new \RuntimeException('Label does not match between actual and desired workflow definition.'); + } + + $diff = new Diff\JobListDiff($actual->id()); + $commands = $diff->diff($actual->jobs(), $desired->jobs()); + + return new DTO\CommandBatch(...$commands); + } + + public function remove(WorkflowId $id): DTO\CommandBatch + { + return new DTO\CommandBatch( + new Command\Workflow\RemoveWorkflowCommand($id), + ); + } +} diff --git a/src/Cloud/WorkflowInterface.php b/src/Cloud/WorkflowInterface.php new file mode 100644 index 00000000..f5ed2f91 --- /dev/null +++ b/src/Cloud/WorkflowInterface.php @@ -0,0 +1,22 @@ + [ - new Node\Name\FullyQualified(\Kiboko\Contract\Pipeline\TransformerInterface::class), + new Node\Name\FullyQualified('Kiboko\\Contract\\Pipeline\\TransformerInterface::class'), ], 'stmts' => [ new Node\Stmt\ClassMethod( diff --git a/src/Plugin/Batching/Builder/Merge.php b/src/Plugin/Batching/Builder/Merge.php index df3732b9..cf6e70e1 100644 --- a/src/Plugin/Batching/Builder/Merge.php +++ b/src/Plugin/Batching/Builder/Merge.php @@ -146,7 +146,7 @@ class: new Node\Stmt\Class_( var: new Node\Expr\Variable('line'), expr: new Node\Expr\Yield_( value: new Node\Expr\New_( - class: new Node\Name\FullyQualified('Kiboko\\Component\\Bucket\\AcceptanceResultBucket:'), + class: new Node\Name\FullyQualified('Kiboko\\Component\\Bucket\\AcceptanceResultBucket'), args: [ new Node\Arg( new Node\Expr\PropertyFetch(