From 108dc92a187baeeb2cdd725b47b84a73cd69b07f Mon Sep 17 00:00:00 2001 From: Jiri Semmler Date: Thu, 27 Aug 2026 16:15:47 +0200 Subject: [PATCH] feat: support purging all deleted projects at once `storage:deleted-projects-purge` required an explicit list of project IDs, so purging everything meant first collecting the IDs by hand. Accept `ALL` and let the command list the deleted projects itself via the Manage API, paginating and skipping the ones already purged. The argument description also said IDs were space-separated while the code has always split on commas; corrected, and the README section now documents the arguments, options and behaviour like the neighbouring commands do. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 14 +++++- .../Console/Command/DeletedProjectsPurge.php | 46 ++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8690cc5..65012d0 100644 --- a/README.md +++ b/README.md @@ -610,8 +610,20 @@ Behavior: Purge already deleted projects (remove residual metadata, optionally ignoring backend errors) using a Manage API token. ``` -php cli.php storage:deleted-projects-purge [--ignore-backend-errors] [--ignore-backend-errors] +php cli.php storage:deleted-projects-purge [-f|--force] [--ignore-backend-errors] ``` +Arguments: +- `url` (required): Stack URL, including `https://`. +- `token` (required): Manage API token. +- `projectIds` (required): Comma-separated list of project IDs to purge (e.g. `1,7,146`), or `ALL` to purge every deleted project that isn't purged yet. + +Options: +- `--force` / `-f`: Actually purge the projects. Without this flag, the command only reports what would be purged (dry-run). +- `--ignore-backend-errors`: Ignore errors from the backend and just delete buckets and workspaces metadata. + +Behavior: +- With `projectIds` set to `ALL`, lists all deleted projects via the Manage API (paginated) and purges each one not already purged. +- For each project ID, skips it if already purged or not found, otherwise purges it and waits (up to 10 minutes) for the purge to complete. ### Set data retention for multiple projects Set data retention days for specific projects listed in a CSV piped via STDIN. diff --git a/src/Keboola/Console/Command/DeletedProjectsPurge.php b/src/Keboola/Console/Command/DeletedProjectsPurge.php index a7db173..62180cf 100644 --- a/src/Keboola/Console/Command/DeletedProjectsPurge.php +++ b/src/Keboola/Console/Command/DeletedProjectsPurge.php @@ -20,7 +20,7 @@ protected function configure(): void ->setDescription('Purge deleted projects.') ->addArgument('url', InputArgument::REQUIRED, 'URL of stack including https://') ->addArgument('token', InputArgument::REQUIRED, 'manage api token') - ->addArgument('projectIds', InputArgument::REQUIRED, 'IDs of projects to purge (separate multiple IDs with a space)') + ->addArgument('projectIds', InputArgument::REQUIRED, 'Comma-separated IDs of projects to purge, or "ALL" to purge every deleted project') ->addOption('ignore-backend-errors', null, InputOption::VALUE_NONE, "Ignore errors from backend and just delete buckets and workspaces metadata") ->addOption('force', null, InputOption::VALUE_NONE, 'Actually perform destructive operations (purge). Without this flag, the command will only simulate actions.'); } @@ -48,7 +48,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'url' => $url, 'token' => $token, ]); - $projectIds = array_filter(explode(',', $projectIds), 'is_numeric'); + + if (strtoupper(trim($projectIds)) === 'ALL') { + $projectIds = $this->fetchAllDeletedProjectIds($client, $output); + $output->writeln(sprintf('Found %d not yet purged deleted project(s).', count($projectIds))); + } else { + $projectIds = array_filter(explode(',', $projectIds), 'is_numeric'); + } foreach ($projectIds as $projectId) { $this->purgeProject( @@ -63,6 +69,42 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + /** + * @return int[] + */ + private function fetchAllDeletedProjectIds(Client $client, OutputInterface $output): array + { + $projectIds = []; + $limit = 100; + $offset = 0; + + do { + $output->writeln(sprintf('Fetching deleted projects (offset %d)...', $offset)); + $deletedProjects = $client->listDeletedProjects([ + 'limit' => $limit, + 'offset' => $offset, + ]); + + foreach ($deletedProjects as $deletedProject) { + assert(is_array($deletedProject)); + if (($deletedProject['isPurged'] ?? false) === true) { + continue; + } + $projectIds[] = (int) $deletedProject['id']; + } + + $output->writeln(sprintf( + ' - got %d project(s), %d not yet purged so far', + count($deletedProjects), + count($projectIds), + )); + + $offset += $limit; + } while (count($deletedProjects) === $limit); + + return $projectIds; + } + private function purgeProject( Client $client, OutputInterface $output,