diff --git a/legacy/composer.json b/legacy/composer.json index 01e568ed..a0c31e0c 100644 --- a/legacy/composer.json +++ b/legacy/composer.json @@ -7,7 +7,7 @@ "doctrine/cache": "~1.5", "guzzlehttp/guzzle": "^7", "platformsh/console-form": "^1@beta", - "platformsh/client": "^3@beta", + "platformsh/client": "dev-add-task-deployment-model as 3.0.0-beta5", "symfony/console": "^7", "symfony/yaml": "^7", "symfony/finder": "^7", @@ -31,6 +31,11 @@ { "type": "vcs", "url": "https://github.com/pjcdawkins/humbug_get_contents" + }, + { + "type": "vcs", + "url": "https://github.com/platformsh/platformsh-client-php.git", + "no-api": true } ], "suggest": { diff --git a/legacy/composer.lock b/legacy/composer.lock index 431e4732..af63ed93 100644 --- a/legacy/composer.lock +++ b/legacy/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "22eb7daddf22918459b152dbe3f918d7", + "content-hash": "0f5045a8499f3e8c39aa3d332df47772", "packages": [ { "name": "cocur/slugify", @@ -923,16 +923,16 @@ }, { "name": "platformsh/client", - "version": "3.0.0-beta5", + "version": "dev-add-task-deployment-model", "source": { "type": "git", "url": "https://github.com/platformsh/platformsh-client-php.git", - "reference": "61c1a7439e8c84a65f0077558333c492d834a5d7" + "reference": "8b737080273375651aeb4095bc81e2be7e321689" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/61c1a7439e8c84a65f0077558333c492d834a5d7", - "reference": "61c1a7439e8c84a65f0077558333c492d834a5d7", + "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/8b737080273375651aeb4095bc81e2be7e321689", + "reference": "8b737080273375651aeb4095bc81e2be7e321689", "shasum": "" }, "require": { @@ -953,7 +953,11 @@ "Platformsh\\Client\\": "src" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Platformsh\\Client\\Tests\\": "tests" + } + }, "license": [ "MIT" ], @@ -963,11 +967,7 @@ } ], "description": "Platform.sh API client", - "support": { - "issues": "https://github.com/platformsh/platformsh-client-php/issues", - "source": "https://github.com/platformsh/platformsh-client-php/tree/3.0.0-beta5" - }, - "time": "2026-03-20T09:21:39+00:00" + "time": "2026-06-23T13:46:20+00:00" }, { "name": "platformsh/console-form", @@ -6332,13 +6332,19 @@ "version": "dev-allow-php-8", "alias": "1.1.3", "alias_normalized": "1.1.3.0" + }, + { + "package": "platformsh/client", + "version": "dev-add-task-deployment-model", + "alias": "3.0.0-beta5", + "alias_normalized": "3.0.0.0-beta5" } ], "minimum-stability": "stable", "stability-flags": { - "padraic/humbug_get_contents": 20, - "platformsh/client": 10, "platformsh/console-form": 10, + "platformsh/client": 20, + "padraic/humbug_get_contents": 20, "platformsh/oauth2": 10 }, "prefer-stable": false, @@ -6347,9 +6353,9 @@ "php": ">=8.2", "ext-json": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.2" }, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } diff --git a/legacy/src/Command/Resources/ResourcesGetCommand.php b/legacy/src/Command/Resources/ResourcesGetCommand.php index 1fdfea67..5e2e6fa5 100644 --- a/legacy/src/Command/Resources/ResourcesGetCommand.php +++ b/legacy/src/Command/Resources/ResourcesGetCommand.php @@ -11,6 +11,7 @@ use Platformsh\Cli\Service\PropertyFormatter; use Platformsh\Cli\Service\Table; use Platformsh\Client\Exception\EnvironmentStateException; +use Platformsh\Client\Model\Deployment\Task; use Platformsh\Client\Model\Deployment\WebApp; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\ArgvInput; @@ -18,7 +19,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'resources:get', description: 'View the resources of apps and services on an environment', aliases: ['resources', 'res'])] +#[AsCommand(name: 'resources:get', description: 'View the resources of apps, tasks and services on an environment', aliases: ['resources', 'res'])] class ResourcesGetCommand extends ResourcesCommandBase { /** @var array */ @@ -51,6 +52,7 @@ protected function configure(): void ->addOption('service', 's', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter by service name. This can select any service, including apps and workers.') ->addOption('app', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter by app name') ->addOption('worker', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter by worker name') + ->addOption('task', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter by task name') ->addOption('type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter by service, app or worker type, e.g. "postgresql"') ->addOption('cpu-type', null, InputOption::VALUE_OPTIONAL, 'Filter by CPU type, e.g "guaranteed"'); $this->selector->addProjectOption($this->getDefinition()); @@ -118,14 +120,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $hasObjectStorage = false; foreach ($services as $name => $service) { $properties = $service->getProperties(); + // Tasks may not specify a profile yet; default so CPU/memory still resolve. + $containerProfile = $properties['container_profile'] ?? ($service instanceof Task ? 'BALANCED' : null); if (!$this->table->formatIsMachineReadable() && !empty($autoscalingEnabled[$name])) { $name .= ' ' . $autoscalingIndicator; $hasAutoscalingIndicator = true; } $row = [ 'service' => $name, - 'type' => $this->propertyFormatter->format($service->type, 'service_type'), - 'profile' => $properties['container_profile'] ?: $empty, + // The deployment exposes task sizing only (no service type). + 'type' => $service instanceof Task ? $empty : $this->propertyFormatter->format($service->type, 'service_type'), + 'profile' => $containerProfile ?: $empty, 'profile_size' => $empty, 'base_memory' => $empty, 'memory_ratio' => $empty, @@ -137,8 +142,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'memory' => $empty, ]; - if (isset($properties['container_profile']) && isset($containerProfiles[$properties['container_profile']][$properties['resources']['profile_size']])) { - $profileInfo = $containerProfiles[$properties['container_profile']][$properties['resources']['profile_size']]; + if ($containerProfile !== null && isset($containerProfiles[$containerProfile][$properties['resources']['profile_size']])) { + $profileInfo = $containerProfiles[$containerProfile][$properties['resources']['profile_size']]; if ($cpuTypeOption != "" && isset($profileInfo['cpu_type']) && $profileInfo['cpu_type'] != $cpuTypeOption) { continue; } @@ -176,7 +181,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - $row['instance_count'] = isset($properties['instance_count']) ? $this->propertyFormatter->format($properties['instance_count'], 'instance_count') : '1'; + // Tasks are run-to-completion containers and have no instance count. + if ($service instanceof Task) { + $row['instance_count'] = $notApplicable; + } else { + $row['instance_count'] = isset($properties['instance_count']) ? $this->propertyFormatter->format($properties['instance_count'], 'instance_count') : '1'; + } $rows[] = $row; } diff --git a/legacy/src/Command/Resources/ResourcesSetCommand.php b/legacy/src/Command/Resources/ResourcesSetCommand.php index bfc7001b..694c9a57 100644 --- a/legacy/src/Command/Resources/ResourcesSetCommand.php +++ b/legacy/src/Command/Resources/ResourcesSetCommand.php @@ -18,6 +18,7 @@ use Platformsh\Client\Exception\EnvironmentStateException; use Platformsh\Client\Model\Deployment\EnvironmentDeployment; use Platformsh\Client\Model\Deployment\Service; +use Platformsh\Client\Model\Deployment\Task; use Platformsh\Client\Model\Deployment\WebApp; use Platformsh\Client\Model\Deployment\Worker; use Symfony\Component\Console\Attribute\AsCommand; @@ -26,7 +27,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'resources:set', description: 'Set the resources of apps and services on an environment')] +#[AsCommand(name: 'resources:set', description: 'Set the resources of apps, tasks and services on an environment')] class ResourcesSetCommand extends ResourcesCommandBase { public function __construct(private readonly ActivityMonitor $activityMonitor, private readonly Api $api, private readonly Config $config, private readonly Io $io, private readonly QuestionHelper $questionHelper, private readonly ResourcesUtil $resourcesUtil, private readonly Selector $selector, private readonly SubCommandRunner $subCommandRunner) @@ -39,7 +40,7 @@ protected function configure(): void 'size', 'S', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Set the profile size (CPU and memory) of apps, workers, or services.' + 'Set the profile size (CPU and memory) of apps, workers, tasks, or services.' . "\nItems are in the format name:value and may be comma-separated." . "\nThe % or * characters may be used as a wildcard for the name." . "\nList available sizes with the resources:sizes command." @@ -69,7 +70,11 @@ protected function configure(): void . "\nOnly applicable to apps; a value of 0 disables the bucket.", ) ->addOption('force', 'f', InputOption::VALUE_NONE, 'Try to run the update, even if it might exceed your limits') - ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Show the changes that would be made, without changing anything'); + ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Show the changes that would be made, without changing anything') + ->addOption('service', 's', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Limit to a service name. This can select any service, including apps and workers.') + ->addOption('app', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Limit to an app name') + ->addOption('worker', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Limit to a worker name') + ->addOption('task', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Limit to a task name'); $this->selector->addProjectOption($this->getDefinition()); $this->selector->addEnvironmentOption($this->getDefinition()); @@ -77,9 +82,10 @@ protected function configure(): void $this->activityMonitor->addWaitOptions($this->getDefinition()); $helpLines = [ - 'Configure the resources allocated to apps, workers and services on an environment.', + 'Configure the resources allocated to apps, workers, tasks and services on an environment.', '', 'The resources may be the profile size, the instance count, or the disk size (MB).', + 'Tasks are run-to-completion containers: only their profile size can be set.', '', sprintf('Profile sizes are predefined CPU & memory values that can be viewed by running: %s resources:sizes', $this->config->getStr('application.executable')), '', @@ -127,6 +133,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } + $services = $this->resourcesUtil->filterServices($services, $input); + if (empty($services)) { + return 1; + } + // Determine the limit of the number of instances, which can vary per project. $instanceLimit = null; if (($projectInfo = $nextDeployment->getProperty('project_info')) && isset($projectInfo['capabilities']['instance_limit'])) { @@ -195,10 +206,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $group = $this->group($service); $properties = $service->getProperties(); - $current[$group][$name]['resources']['profile_size'] = $properties['resources']['profile_size']; - $current[$group][$name]['instance_count'] = $properties['instance_count']; - $current[$group][$name]['disk'] = $properties['disk']; - $current[$group][$name]['sizes'] = $containerProfiles[$properties['container_profile']]; + // Tasks may lack a profile, instance_count and disk; default safely. + $containerProfile = $properties['container_profile'] ?? ($service instanceof Task ? 'BALANCED' : null); + $current[$group][$name]['resources']['profile_size'] = $properties['resources']['profile_size'] ?? null; + $current[$group][$name]['instance_count'] = $properties['instance_count'] ?? null; + $current[$group][$name]['disk'] = $properties['disk'] ?? null; + $current[$group][$name]['sizes'] = $containerProfiles[$containerProfile] ?? []; $header = '' . ucfirst($type) . ': ' . $name . ''; $headerShown = false; @@ -217,12 +230,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } elseif ($showCompleteForm || (!isset($properties['resources']['profile_size']) && $input->isInteractive())) { - if (isset($properties['container_profile'])) { - $header .= "\n" . sprintf('Container profile: %s', $properties['container_profile']); + if ($containerProfile !== null) { + $header .= "\n" . sprintf('Container profile: %s', $containerProfile); } $ensureHeader(); $new = isset($properties['resources']['profile_size']) ? 'a new' : 'a'; - $profileSizes = $containerProfiles[$properties['container_profile']]; + $profileSizes = $containerProfiles[$containerProfile] ?? []; if (isset($properties['resources']['profile_size'])) { $defaultOption = $properties['resources']['profile_size']; } elseif (isset($properties['resources']['default']['profile_size'])) { @@ -265,7 +278,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Check if we have guaranteed CPU changes. if (isset($updates[$group][$name]['resources']['profile_size'])) { $serviceProfileSize = $updates[$group][$name]['resources']['profile_size']; - $serviceProfileType = $properties['container_profile']; + $serviceProfileType = $containerProfile; if (isset($containerProfiles[$serviceProfileType][$serviceProfileSize]) && $containerProfiles[$serviceProfileType][$serviceProfileSize]['cpu_type'] === 'guaranteed') { $hasGuaranteedCPU = true; @@ -273,8 +286,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int } // Set the instance count. - // This is not applicable to a Service, and unavailable when autoscaling is enabled. - if (!$service instanceof Service && empty($autoscalingEnabled[$name])) { + // This is not applicable to a Service or a Task, and unavailable when autoscaling is enabled. + if (!$service instanceof Service && !$service instanceof Task && empty($autoscalingEnabled[$name])) { if (isset($givenCounts[$name])) { $instanceCount = $givenCounts[$name]; if ($instanceCount !== $properties['instance_count'] && !($instanceCount === 1 && !isset($properties['instance_count']))) { @@ -282,7 +295,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } elseif ($showCompleteForm) { $ensureHeader(); - $default = $properties['instance_count'] ?: 1; + $default = (string) ($properties['instance_count'] ?: 1); $instanceCount = $this->questionHelper->askInput( 'Enter the number of instances', $default, @@ -298,21 +311,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Set the disk size. if ($this->resourcesUtil->supportsDisk($service)) { if (isset($givenDiskSizes[$name])) { - if ($givenDiskSizes[$name] !== $service->disk) { + if ($givenDiskSizes[$name] !== ($properties['disk'] ?? null)) { $updates[$group][$name]['disk'] = $givenDiskSizes[$name]; } - } elseif ($showCompleteForm || (empty($service->disk) && $input->isInteractive())) { + } elseif ($showCompleteForm || (empty(($properties['disk'] ?? null)) && $input->isInteractive())) { $ensureHeader(); - if ($service->disk) { - $default = $service->disk; + if (($properties['disk'] ?? null)) { + $default = $properties['disk']; } else { $default = $properties['resources']['default']['disk'] ?? '512'; } - $diskSize = $this->questionHelper->askInput('Enter a disk size in MB', $default, ['512', '1024', '2048'], fn($v) => $this->validateDiskSize($v, $name, $service)); - if ($diskSize !== $service->disk) { + $diskSize = $this->questionHelper->askInput('Enter a disk size in MB', (string) $default, ['512', '1024', '2048'], fn($v) => $this->validateDiskSize($v, $name, $service)); + if ($diskSize !== ($properties['disk'] ?? null)) { $updates[$group][$name]['disk'] = $diskSize; } - } elseif (empty($service->disk)) { + } elseif (empty(($properties['disk'] ?? null))) { $this->stdErr->writeln(sprintf('A disk size is required for the %s %s.', $type, $name)); $errored = true; } @@ -424,7 +437,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int * Summarizes all the changes that would be made. * * @param array>> $updates - * @param array $services + * @param array $services * @param array $containerProfiles * @return void */ @@ -444,11 +457,12 @@ private function summarizeChanges(array $updates, array $services, array $contai * @param array $updates * @param array $containerProfiles */ - private function summarizeChangesPerService(string $name, WebApp|Worker|Service $service, array $updates, array $containerProfiles): void + private function summarizeChangesPerService(string $name, WebApp|Worker|Service|Task $service, array $updates, array $containerProfiles): void { $this->stdErr->writeln(sprintf(' %s: %s', ucfirst($this->typeName($service)), $name)); $properties = $service->getProperties(); + $properties['container_profile'] ??= $service instanceof Task ? 'BALANCED' : null; if (isset($updates['resources']['profile_size'])) { $sizeInfo = $this->resourcesUtil->sizeInfo($properties, $containerProfiles); $newProperties = array_replace_recursive($properties, $updates); @@ -500,7 +514,7 @@ private function summarizeChangesPerService(string $name, WebApp|Worker|Service /** * Returns the group for a service (where it belongs in the deployment object). */ - protected function group(WebApp|Worker|Service $service): string + protected function group(WebApp|Worker|Service|Task $service): string { if ($service instanceof WebApp) { return 'webapps'; @@ -508,13 +522,16 @@ protected function group(WebApp|Worker|Service $service): string if ($service instanceof Worker) { return 'workers'; } + if ($service instanceof Task) { + return 'tasks'; + } return 'services'; } /** * Returns the service type name for a service. */ - protected function typeName(WebApp|Worker|Service $service): string + protected function typeName(WebApp|Worker|Service|Task $service): string { if ($service instanceof WebApp) { return 'app'; @@ -522,6 +539,9 @@ protected function typeName(WebApp|Worker|Service $service): string if ($service instanceof Worker) { return 'worker'; } + if ($service instanceof Task) { + return 'task'; + } return 'service'; } @@ -530,10 +550,10 @@ protected function typeName(WebApp|Worker|Service $service): string * * @throws InvalidArgumentException */ - protected function validateInstanceCount(string $value, string $serviceName, WebApp|Worker|Service $service, ?int $limit, bool $autoscalingEnabled): int + protected function validateInstanceCount(string $value, string $serviceName, WebApp|Worker|Service|Task $service, ?int $limit, bool $autoscalingEnabled): int { - if ($service instanceof Service) { - throw new InvalidArgumentException(sprintf('The instance count of the service %s cannot be changed.', $serviceName)); + if ($service instanceof Service || $service instanceof Task) { + throw new InvalidArgumentException(sprintf('The instance count of the %s %s cannot be changed.', $this->typeName($service), $serviceName)); } if ($autoscalingEnabled) { throw new InvalidArgumentException(sprintf('The instance count of the %s %s cannot be changed when autoscaling is enabled.', $this->typeName($service), $serviceName)); @@ -553,7 +573,7 @@ protected function validateInstanceCount(string $value, string $serviceName, Web * * @throws InvalidArgumentException */ - protected function validateDiskSize(string $value, string $serviceName, WebApp|Worker|Service $service): int + protected function validateDiskSize(string $value, string $serviceName, WebApp|Worker|Service|Task $service): int { if (!$this->resourcesUtil->supportsDisk($service)) { throw new InvalidArgumentException(sprintf( @@ -598,7 +618,7 @@ protected function validateDiskSize(string $value, string $serviceName, WebApp|W * * @throws InvalidArgumentException */ - protected function validateObjectStorage(string $value, string $serviceName, WebApp|Worker|Service $service): int + protected function validateObjectStorage(string $value, string $serviceName, WebApp|Worker|Service|Task $service): int { if (!$service instanceof WebApp) { throw new InvalidArgumentException(sprintf( @@ -622,7 +642,7 @@ protected function validateObjectStorage(string $value, string $serviceName, Web * * @throws InvalidArgumentException */ - protected function validateProfileSize(string $value, string $serviceName, WebApp|Worker|Service $service, EnvironmentDeployment $deployment): string + protected function validateProfileSize(string $value, string $serviceName, WebApp|Worker|Service|Task $service, EnvironmentDeployment $deployment): string { $properties = $service->getProperties(); if ($value === 'default') { @@ -637,7 +657,7 @@ protected function validateProfileSize(string $value, string $serviceName, WebAp } return $properties['resources']['minimum']['profile_size']; } - $containerProfile = $properties['container_profile']; + $containerProfile = $properties['container_profile'] ?? ($service instanceof Task ? 'BALANCED' : null); if (!isset($deployment->container_profiles[$containerProfile])) { throw new \RuntimeException(sprintf('Container profile %s for service %s not found', $containerProfile, $serviceName)); } @@ -675,7 +695,7 @@ protected function validateProfileSize(string $value, string $serviceName, WebAp * * @param InputInterface $input * @param string $optionName The input option name. - * @param array $services + * @param array $services * @param callable|null $validator * Validate the value. The callback takes the arguments ($value, * $serviceName, $service) and returns a normalized value or throws diff --git a/legacy/src/Service/ResourcesUtil.php b/legacy/src/Service/ResourcesUtil.php index 62c38968..3029936e 100644 --- a/legacy/src/Service/ResourcesUtil.php +++ b/legacy/src/Service/ResourcesUtil.php @@ -11,6 +11,7 @@ use Platformsh\Client\Exception\EnvironmentStateException; use Platformsh\Client\Model\Deployment\EnvironmentDeployment; use Platformsh\Client\Model\Deployment\Service; +use Platformsh\Client\Model\Deployment\Task; use Platformsh\Client\Model\Deployment\WebApp; use Platformsh\Client\Model\Deployment\Worker; use Platformsh\Client\Model\Environment; @@ -43,7 +44,7 @@ public function featureEnabled(): bool * * @param EnvironmentDeployment $deployment * - * @return array + * @return array * An array of services keyed by the service name. */ public function allServices(EnvironmentDeployment $deployment): array @@ -51,19 +52,24 @@ public function allServices(EnvironmentDeployment $deployment): array $webapps = $deployment->webapps; $workers = $deployment->workers; $services = $deployment->services; + // Only include tasks the client mapped to Task objects (older clients pass raw arrays). + $tasks = !empty($deployment->getData()['tasks']) + ? array_filter($deployment->tasks, fn($task): bool => $task instanceof Task) + : []; ksort($webapps, SORT_STRING | SORT_FLAG_CASE); ksort($workers, SORT_STRING | SORT_FLAG_CASE); ksort($services, SORT_STRING | SORT_FLAG_CASE); - return array_merge($webapps, $workers, $services); + ksort($tasks, SORT_STRING | SORT_FLAG_CASE); + return array_merge($webapps, $workers, $services, $tasks); } /** * Checks whether a service needs a persistent disk. */ - public function supportsDisk(WebApp|Worker|Service $service): bool + public function supportsDisk(WebApp|Worker|Service|Task $service): bool { - // Workers use the disk of their parent app. - if ($service instanceof Worker) { + // Workers use their parent app's disk; tasks have none. + if ($service instanceof Worker || $service instanceof Task) { return false; } return isset($service->getProperties()['resources']['minimum']['disk']); @@ -96,10 +102,10 @@ public function loadNextDeployment(Environment $environment, bool $reset = false /** * Filters a list of services according to the --service or --type options. * - * @param array $services + * @param array $services * @param InputInterface $input * - * @return WebApp[]|Service[]|Worker[]|false + * @return WebApp[]|Service[]|Worker[]|Task[]|false * False on error, or an array of services. */ public function filterServices(array $services, InputInterface $input): array|false @@ -133,6 +139,15 @@ public function filterServices(array $services, InputInterface $input): array|fa } $services = array_intersect_key($services, array_flip($selectedNames)); } + $requestedTasks = $input->hasOption('task') ? ArrayArgument::getOption($input, 'task') : []; + if (!empty($requestedTasks)) { + $selectedNames = Wildcard::select(array_keys(array_filter($services, fn($s): bool => $s instanceof Task)), $requestedTasks); + if (!$selectedNames) { + $this->stdErr->writeln('No tasks were found matching the name(s): ' . implode(', ', $requestedTasks) . ''); + return false; + } + $services = array_intersect_key($services, array_flip($selectedNames)); + } if ($input->hasOption('type') && ($requestedTypes = ArrayArgument::getOption($input, 'type'))) { $byType = [];