diff --git a/legacy/src/Command/Resources/ResourcesGetCommand.php b/legacy/src/Command/Resources/ResourcesGetCommand.php
index 74683613..b32aad85 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\WebApp;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
@@ -30,13 +31,14 @@ class ResourcesGetCommand extends ResourcesCommandBase
'cpu' => 'CPU',
'memory' => 'Memory (MB)',
'disk' => 'Disk (MB)',
+ 'object_storage' => 'Object storage (GB)',
'instance_count' => 'Instances',
'base_memory' => 'Base memory',
'memory_ratio' => 'Memory ratio',
];
/** @var string[] */
- protected array $defaultColumns = ['service', 'profile_size', 'cpu_type', 'cpu', 'memory', 'disk', 'instance_count'];
+ protected array $defaultColumns = ['service', 'profile_size', 'cpu_type', 'cpu', 'memory', 'disk', 'object_storage', 'instance_count'];
public function __construct(private readonly Api $api, private readonly Config $config, private readonly PropertyFormatter $propertyFormatter, private readonly ResourcesUtil $resourcesUtil, private readonly Selector $selector, private readonly Table $table)
{
@@ -113,6 +115,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$cpuTypeOption = $input->getOption('cpu-type');
$autoscalingIndicator = '(A)';
$hasAutoscalingIndicator = false;
+ $hasObjectStorage = false;
foreach ($services as $name => $service) {
$properties = $service->getProperties();
if (!$this->table->formatIsMachineReadable() && !empty($autoscalingEnabled[$name])) {
@@ -127,6 +130,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'base_memory' => $empty,
'memory_ratio' => $empty,
'disk' => $empty,
+ 'object_storage' => $empty,
'instance_count' => $empty,
'cpu_type' => $empty,
'cpu' => $empty,
@@ -162,12 +166,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}
+ // Object storage is only available on apps. Stored in MiB on the
+ // wire; displayed to users in GB.
+ if (!$service instanceof WebApp) {
+ $row['object_storage'] = $notApplicable;
+ } elseif (isset($properties['resources']['disk']['object'])) {
+ $row['object_storage'] = ResourcesUtil::formatObjectStorageGB($properties['resources']['disk']['object']);
+ if ($properties['resources']['disk']['object'] > 0) {
+ $hasObjectStorage = true;
+ }
+ }
+
$row['instance_count'] = isset($properties['instance_count']) ? $this->propertyFormatter->format($properties['instance_count'], 'instance_count') : '1';
$rows[] = $row;
}
- $this->table->render($rows, $this->tableHeader, $this->defaultColumns);
+ $defaultColumns = $this->defaultColumns;
+ if (!$hasObjectStorage) {
+ $defaultColumns = array_values(array_diff($defaultColumns, ['object_storage']));
+ }
+ $this->table->render($rows, $this->tableHeader, $defaultColumns);
if (!$this->table->formatIsMachineReadable()) {
if ($hasAutoscalingIndicator) {
diff --git a/legacy/src/Command/Resources/ResourcesSetCommand.php b/legacy/src/Command/Resources/ResourcesSetCommand.php
index 6d76c659..b618b624 100644
--- a/legacy/src/Command/Resources/ResourcesSetCommand.php
+++ b/legacy/src/Command/Resources/ResourcesSetCommand.php
@@ -60,6 +60,14 @@ protected function configure(): void
. "\nItems are in the format name:value as above."
. "\nA value of 'default' will use the default size, and 'min' or 'minimum' will use the minimum.",
)
+ ->addOption(
+ 'object-storage',
+ null,
+ InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Set the object storage size (in GB) of apps.'
+ . "\nItems are in the format name:value as above."
+ . "\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');
@@ -88,6 +96,7 @@ protected function configure(): void
$this->addExample('Set profile sizes for two apps and a service', '--size frontend:0.1,backend:.25,database:1');
$this->addExample('Give the "backend" app 3 instances', '--count backend:3');
$this->addExample('Give 512 MB disk to the "backend" app and 2 GB to the "database" service', '--disk backend:512,database:2048');
+ $this->addExample('Give 512 GB of object storage to the "backend" app', '--object-storage backend:512');
$this->addExample('Set the same profile size for the "backend" and "frontend" apps using a wildcard', '--size ' . OsUtil::escapeShellArg('*end:0.1'));
$this->addExample('Set the same instance count for all apps using a wildcard', '--count ' . OsUtil::escapeShellArg('*:3'));
}
@@ -143,6 +152,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
// Validate the --disk option.
[$givenDiskSizes, $diskErrored] = $this->parseSetting($input, 'disk', $services, fn($v, $serviceName, $service) => $this->validateDiskSize($v, $serviceName, $service));
$errored = $errored || $diskErrored;
+
+ // Validate the --object-storage option.
+ [$givenObjectStorage, $objectStorageErrored] = $this->parseSetting($input, 'object-storage', $services, fn($v, $serviceName, $service) => $this->validateObjectStorage($v, $serviceName, $service));
+ $errored = $errored || $objectStorageErrored;
if ($errored) {
return 1;
}
@@ -171,7 +184,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$showCompleteForm = $input->isInteractive()
&& $input->getOption('size') === []
&& $input->getOption('count') === []
- && $input->getOption('disk') === [];
+ && $input->getOption('disk') === []
+ && $input->getOption('object-storage') === [];
$updates = [];
$current = [];
@@ -304,6 +318,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}
+ // Set the object storage size (apps only).
+ if ($service instanceof WebApp && isset($givenObjectStorage[$name])) {
+ $currentObject = $properties['resources']['disk']['object'] ?? null;
+ if ($givenObjectStorage[$name] !== $currentObject) {
+ $updates[$group][$name]['resources']['disk']['object'] = $givenObjectStorage[$name];
+ }
+ }
+
if ($headerShown) {
$this->stdErr->writeln('');
}
@@ -455,6 +477,15 @@ private function summarizeChangesPerService(string $name, WebApp|Worker|Service
' MB',
));
}
+ if (isset($updates['resources']['disk']['object'])) {
+ $previousMib = $properties['resources']['disk']['object'] ?? null;
+ $newMib = $updates['resources']['disk']['object'];
+ $this->stdErr->writeln(' Object storage: ' . $this->resourcesUtil->formatChange(
+ $previousMib === null ? null : ResourcesUtil::formatObjectStorageGB($previousMib),
+ ResourcesUtil::formatObjectStorageGB($newMib),
+ ' GB',
+ ));
+ }
}
/**
@@ -553,6 +584,31 @@ protected function validateDiskSize(string $value, string $serviceName, WebApp|W
return $size;
}
+ /**
+ * Validates a given object storage size, returning the value in MiB.
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function validateObjectStorage(string $value, string $serviceName, WebApp|Worker|Service $service): int
+ {
+ if (!$service instanceof WebApp) {
+ throw new InvalidArgumentException(sprintf(
+ 'Object storage is only available on apps; %s is a %s.',
+ $serviceName,
+ $this->typeName($service),
+ ));
+ }
+ $gb = (int) $value;
+ if ($gb != $value || $value < 0) {
+ throw new InvalidArgumentException(sprintf(
+ 'Invalid object storage size %s: it must be a non-negative integer in GB.',
+ $value,
+ ));
+ }
+ // The API stores object storage in MiB. 1 GB is treated as 1024 MiB.
+ return $gb * 1024;
+ }
+
/**
* Validates a given profile size.
*
diff --git a/legacy/src/Service/ResourcesUtil.php b/legacy/src/Service/ResourcesUtil.php
index 62c38968..71b49771 100644
--- a/legacy/src/Service/ResourcesUtil.php
+++ b/legacy/src/Service/ResourcesUtil.php
@@ -225,6 +225,21 @@ public function formatCPU(int|float|string $unformatted): string
return sprintf('%.1f', $unformatted);
}
+ /**
+ * Formats a MiB value as a GB string for object storage display.
+ *
+ * Object storage is in MiB on the wire; the CLI exposes it to users in GB
+ * (where 1 GB is treated as 1024 MiB).
+ */
+ public static function formatObjectStorageGB(int|float $mib): string
+ {
+ $gb = $mib / 1024;
+ if ($gb == (int) $gb) {
+ return (string) (int) $gb;
+ }
+ return rtrim(rtrim(sprintf('%.2f', $gb), '0'), '.');
+ }
+
/**
* Adds a --resources-init option to commands that support it.
*
diff --git a/legacy/tests/Service/ResourcesUtilTest.php b/legacy/tests/Service/ResourcesUtilTest.php
new file mode 100644
index 00000000..f84ecc0e
--- /dev/null
+++ b/legacy/tests/Service/ResourcesUtilTest.php
@@ -0,0 +1,36 @@
+ $case) {
+ [$mib, $expected, $description] = $case;
+ $this->assertSame(
+ $expected,
+ ResourcesUtil::formatObjectStorageGB($mib),
+ "case $key: $description",
+ );
+ }
+ }
+}