diff --git a/legacy/composer.json b/legacy/composer.json index 01e568ed..0e25d3fe 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": "3.x-dev as 3.0.0-beta5", "symfony/console": "^7", "symfony/yaml": "^7", "symfony/finder": "^7", diff --git a/legacy/composer.lock b/legacy/composer.lock index e1da463a..01fd6c1e 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": "26136587641513bab720b8bd7f1b2c0e", "packages": [ { "name": "cocur/slugify", @@ -923,16 +923,16 @@ }, { "name": "platformsh/client", - "version": "3.0.0-beta5", + "version": "3.x-dev", "source": { "type": "git", "url": "https://github.com/platformsh/platformsh-client-php.git", - "reference": "61c1a7439e8c84a65f0077558333c492d834a5d7" + "reference": "ae46040164a44efaeec15758ec4220e3cbf3e3fd" }, "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/ae46040164a44efaeec15758ec4220e3cbf3e3fd", + "reference": "ae46040164a44efaeec15758ec4220e3cbf3e3fd", "shasum": "" }, "require": { @@ -947,6 +947,7 @@ "phpunit/phpunit": "^11", "symplify/easy-coding-standard": "^12.3" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -965,9 +966,9 @@ "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" + "source": "https://github.com/platformsh/platformsh-client-php/tree/3.x" }, - "time": "2026-03-20T09:21:39+00:00" + "time": "2026-08-07T13:05:25+00:00" }, { "name": "platformsh/console-form", @@ -6315,13 +6316,19 @@ "version": "dev-allow-php-8", "alias": "1.1.3", "alias_normalized": "1.1.3.0" + }, + { + "package": "platformsh/client", + "version": "3.9999999.9999999.9999999-dev", + "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, @@ -6330,7 +6337,7 @@ "php": ">=8.2", "ext-json": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.2" }, diff --git a/legacy/phpstan-baseline.neon b/legacy/phpstan-baseline.neon index e249aa61..58f9b428 100644 --- a/legacy/phpstan-baseline.neon +++ b/legacy/phpstan-baseline.neon @@ -201,7 +201,7 @@ parameters: path: src/Command/Resources/ResourcesSetCommand.php - - message: "#^Access to an undefined property Platformsh\\\\Client\\\\Model\\\\Deployment\\\\Service\\|Platformsh\\\\Client\\\\Model\\\\Deployment\\\\WebApp\\|Platformsh\\\\Client\\\\Model\\\\Deployment\\\\Worker\\:\\:\\$container_profile\\.$#" + message: "#^Access to an undefined property Platformsh\\\\Client\\\\Model\\\\Deployment\\\\Service\\|Platformsh\\\\Client\\\\Model\\\\Deployment\\\\Task\\|Platformsh\\\\Client\\\\Model\\\\Deployment\\\\WebApp\\|Platformsh\\\\Client\\\\Model\\\\Deployment\\\\Worker\\:\\:\\$container_profile\\.$#" count: 3 path: src/Command/Resources/ResourcesSizeListCommand.php diff --git a/legacy/src/Command/Autoscaling/AutoscalingSettingsGetCommand.php b/legacy/src/Command/Autoscaling/AutoscalingSettingsGetCommand.php index 1643c248..ef8a7c27 100644 --- a/legacy/src/Command/Autoscaling/AutoscalingSettingsGetCommand.php +++ b/legacy/src/Command/Autoscaling/AutoscalingSettingsGetCommand.php @@ -12,6 +12,7 @@ use Platformsh\Cli\Service\Table; use Platformsh\Client\Exception\EnvironmentStateException; use Platformsh\Client\Model\Deployment\Service; +use Platformsh\Client\Model\Deployment\Task; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -76,6 +77,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $autoscalingSettings = $autoscalingSettings->getData(); $services = $this->resourcesUtil->allServices($deployment); + // Autoscaling does not apply to tasks. + $services = array_filter($services, static fn($service): bool => !$service instanceof Task); if (empty($services)) { $this->stdErr->writeln('No apps, workers, or services found.'); return 1; diff --git a/legacy/src/Command/Autoscaling/AutoscalingSettingsSetCommand.php b/legacy/src/Command/Autoscaling/AutoscalingSettingsSetCommand.php index 7a191b37..c671c7df 100644 --- a/legacy/src/Command/Autoscaling/AutoscalingSettingsSetCommand.php +++ b/legacy/src/Command/Autoscaling/AutoscalingSettingsSetCommand.php @@ -13,6 +13,7 @@ use Platformsh\Cli\Command\CommandBase; use Platformsh\Client\Exception\EnvironmentStateException; 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\Exception\InvalidArgumentException; @@ -94,6 +95,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $autoscalingSettings = $autoscalingSettings->getData(); $services = $this->resourcesUtil->allServices($deployment); + // Autoscaling does not apply to tasks. + $services = array_filter($services, static fn($service): bool => !$service instanceof Task); if (empty($services)) { $this->stdErr->writeln('No apps, workers, or services found.'); return 1; diff --git a/legacy/src/Command/Resources/ResourcesGetCommand.php b/legacy/src/Command/Resources/ResourcesGetCommand.php index 1fdfea67..75444bc9 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 */ @@ -48,9 +49,10 @@ public function __construct(private readonly Api $api, private readonly Config $ protected function configure(): void { $this - ->addOption('service', 's', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter by service name. This can select any service, including apps and workers.') + ->addOption('service', 's', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter by service name. This can select any service, including apps, workers and tasks.') ->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,9 @@ 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']]; + $profileSize = $properties['resources']['profile_size'] ?? null; + if ($containerProfile !== null && $profileSize !== null && isset($containerProfiles[$containerProfile][$profileSize])) { + $profileInfo = $containerProfiles[$containerProfile][$profileSize]; if ($cpuTypeOption != "" && isset($profileInfo['cpu_type']) && $profileInfo['cpu_type'] != $cpuTypeOption) { continue; } @@ -176,7 +182,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 31ba953f..a33272fa 100644 --- a/legacy/src/Command/Resources/ResourcesSetCommand.php +++ b/legacy/src/Command/Resources/ResourcesSetCommand.php @@ -19,6 +19,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\Project; @@ -28,7 +29,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) @@ -41,7 +42,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." @@ -71,7 +72,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, workers and tasks.') + ->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()); @@ -79,9 +84,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')), '', @@ -129,6 +135,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'])) { @@ -197,10 +208,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; @@ -219,12 +232,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'])) { @@ -267,7 +280,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; @@ -275,8 +288,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']))) { @@ -284,7 +297,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, @@ -300,21 +313,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; } @@ -420,7 +433,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 */ @@ -440,11 +453,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); @@ -496,7 +510,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'; @@ -504,13 +518,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'; @@ -518,6 +535,9 @@ protected function typeName(WebApp|Worker|Service $service): string if ($service instanceof Worker) { return 'worker'; } + if ($service instanceof Task) { + return 'task'; + } return 'service'; } @@ -526,10 +546,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)); @@ -549,7 +569,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( @@ -594,7 +614,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( @@ -618,7 +638,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') { @@ -633,7 +653,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)); } @@ -671,7 +691,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..1ae66ca3 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,12 +139,27 @@ 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 = []; foreach ($services as $name => $service) { - $type = $service->type; - [$prefix] = explode(':', $service->type, 2); + // Read the type from properties: a service without one (e.g. a + // task whose deployment has no resolved type) is skipped, not fatal. + $properties = $service->getProperties(); + if (!isset($properties['type']) || !is_string($properties['type'])) { + continue; + } + $type = $properties['type']; + [$prefix] = explode(':', $type, 2); $byType[$type][] = $name; $byType[$prefix][] = $name; }