From f45f127892491b57390716b0d0772d6bac03ce0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 14:27:31 +0100 Subject: [PATCH 01/18] DMD-916 - update dependencies --- Dockerfile | 2 +- README.md | 2 +- composer.json | 11 +- composer.lock | 167 +++++++++--------- .../OrganizationIntoMaintenanceMode.php | 9 +- 5 files changed, 96 insertions(+), 95 deletions(-) diff --git a/Dockerfile b/Dockerfile index 904f3d3..cbb143b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2-cli +FROM php:8.3-cli WORKDIR /code diff --git a/README.md b/README.md index e854e60..95b8f12 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Assorted CLI utils 1. Clone the repo 2. Init .env file `cp .env.dist .env` (actual values are not needed unless you want to run `manage:mass-project-remove-expiration`) 3. Build the image `docker compose build dev` -4. [optional] Install dependencies `docker run --rm dev composer install` +4. [optional] Install dependencies `docker compose run --rm dev composer install` 5. Update the code 6. Run a command `docker compose run --rm app php cli.php ` diff --git a/composer.json b/composer.json index be7f3a8..4d58d20 100644 --- a/composer.json +++ b/composer.json @@ -1,13 +1,16 @@ { "name": "keboola/cli-utils", "require": { - "php": "^8.2", + "php": "^8.3", "symfony/console": "^3.1", - "keboola/sandboxes-api-php-client": "^6.31", - "keboola/storage-api-client": "^14", + "keboola/sandboxes-api-php-client": "6.32.0", + "keboola/storage-api-client": "^18.5.0", "keboola/kbc-manage-api-php-client": "^7", "symfony/event-dispatcher": "6.4.*", - "keboola/job-queue-api-php-client": "^2.2" + "keboola/job-queue-api-php-client": "^5.2.0", + "psr/log": "^2.0|^3.0", + "symfony/config": "^6.0|^7.0", + "keboola/php-datatypes": "^8.0" }, "require-dev": { "squizlabs/php_codesniffer": "3.*" diff --git a/composer.lock b/composer.lock index e7893a4..a2e1601 100644 --- a/composer.lock +++ b/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": "1cb691a3a5beacf61fd6bf492bf0438b", + "content-hash": "db532bf730913acbd5c98b7f63f04353", "packages": [ { "name": "aws/aws-crt-php", @@ -881,32 +881,35 @@ }, { "name": "keboola/job-queue-api-php-client", - "version": "2.4.0", + "version": "5.2.0", "source": { "type": "git", "url": "https://github.com/keboola/job-queue-api-php-client.git", - "reference": "4e62bb7f4d3f00cc85c5844ea975a2d6bc5693c2" + "reference": "3be7da9beac3bb931df8264e7828ea2fde253e87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/keboola/job-queue-api-php-client/zipball/4e62bb7f4d3f00cc85c5844ea975a2d6bc5693c2", - "reference": "4e62bb7f4d3f00cc85c5844ea975a2d6bc5693c2", + "url": "https://api.github.com/repos/keboola/job-queue-api-php-client/zipball/3be7da9beac3bb931df8264e7828ea2fde253e87", + "reference": "3be7da9beac3bb931df8264e7828ea2fde253e87", "shasum": "" }, "require": { + "ext-curl": "*", "ext-json": "*", "guzzlehttp/guzzle": "^6.3|^7.2", - "php": "^7.4|^8.0", - "psr/log": "^1.1", - "symfony/config": "^4.4|^5.2|^6.0", - "symfony/validator": "^4.4|^5.2 !=4.4.33 !=4.4.34 !=5.3.10 !=5.3.11|^6.0" + "php": "^8.3", + "psr/log": "^2.0|^3.0", + "symfony/config": "^6.0|^7.0", + "symfony/validator": "^6.0|^7.0" }, "require-dev": { "infection/infection": "^0.26", "keboola/coding-standard": ">=13.0", - "keboola/storage-api-client": "^14.0", + "keboola/settle": "^1.0", + "keboola/storage-api-client": "^18.0", "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "symfony/dotenv": "^6.0" }, "type": "library", "autoload": { @@ -932,9 +935,9 @@ "queue" ], "support": { - "source": "https://github.com/keboola/job-queue-api-php-client/tree/2.4.0" + "source": "https://github.com/keboola/job-queue-api-php-client/tree/5.2.0" }, - "time": "2022-10-10T10:16:47+00:00" + "time": "2025-05-26T12:20:34+00:00" }, { "name": "keboola/kbc-manage-api-php-client", @@ -988,23 +991,23 @@ }, { "name": "keboola/php-datatypes", - "version": "6.4.0", + "version": "8.2.0", "source": { "type": "git", "url": "https://github.com/keboola/php-datatypes.git", - "reference": "418820042083b2ed4b0820b6862f62f7bfe03547" + "reference": "21d4d87f6439102b946a1ee45d83a59babff917d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/keboola/php-datatypes/zipball/418820042083b2ed4b0820b6862f62f7bfe03547", - "reference": "418820042083b2ed4b0820b6862f62f7bfe03547", + "url": "https://api.github.com/repos/keboola/php-datatypes/zipball/21d4d87f6439102b946a1ee45d83a59babff917d", + "reference": "21d4d87f6439102b946a1ee45d83a59babff917d", "shasum": "" }, "require": { - "php": "^7.4|^8" + "php": "^8.2" }, "require-dev": { - "keboola/coding-standard": "^14", + "keboola/coding-standard": "^15", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/phpdoc-parser": "^1.6", "phpstan/phpstan": "^1.4", @@ -1030,22 +1033,22 @@ "description": "PHP datatypes for databases", "support": { "issues": "https://github.com/keboola/php-datatypes/issues", - "source": "https://github.com/keboola/php-datatypes/tree/6.4.0" + "source": "https://github.com/keboola/php-datatypes/tree/8.2.0" }, - "time": "2023-03-03T15:00:55+00:00" + "time": "2025-08-27T11:12:26+00:00" }, { "name": "keboola/sandboxes-api-php-client", - "version": "6.31.0", + "version": "6.32.0", "source": { "type": "git", "url": "https://github.com/keboola/sandboxes-api-php-client.git", - "reference": "739084bc3fb50a780f33c1981af7bc72f43f0215" + "reference": "2125f1121d0c877f642902b94536aa1d744e8ff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/keboola/sandboxes-api-php-client/zipball/739084bc3fb50a780f33c1981af7bc72f43f0215", - "reference": "739084bc3fb50a780f33c1981af7bc72f43f0215", + "url": "https://api.github.com/repos/keboola/sandboxes-api-php-client/zipball/2125f1121d0c877f642902b94536aa1d744e8ff2", + "reference": "2125f1121d0c877f642902b94536aa1d744e8ff2", "shasum": "" }, "require": { @@ -1086,22 +1089,22 @@ "sandboxes" ], "support": { - "source": "https://github.com/keboola/sandboxes-api-php-client/tree/6.31.0" + "source": "https://github.com/keboola/sandboxes-api-php-client/tree/6.32.0" }, - "time": "2025-02-19T07:20:50+00:00" + "time": "2025-11-19T16:15:43+00:00" }, { "name": "keboola/storage-api-client", - "version": "v14.11.1", + "version": "v18.5.0", "source": { "type": "git", "url": "https://github.com/keboola/storage-api-php-client.git", - "reference": "d399a49aba1b333fc890853034846057c2d632ab" + "reference": "19392a3ea9f97bee52999fcb43327482d4edeeb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/keboola/storage-api-php-client/zipball/d399a49aba1b333fc890853034846057c2d632ab", - "reference": "d399a49aba1b333fc890853034846057c2d632ab", + "url": "https://api.github.com/repos/keboola/storage-api-php-client/zipball/19392a3ea9f97bee52999fcb43327482d4edeeb1", + "reference": "19392a3ea9f97bee52999fcb43327482d4edeeb1", "shasum": "" }, "require": { @@ -1110,49 +1113,43 @@ "google/cloud-storage": "^1.27", "guzzlehttp/guzzle": "~7.0", "keboola/csv": "^1", - "keboola/php-datatypes": "^6.4", + "keboola/php-datatypes": "^8.0", "microsoft/azure-storage-blob": "^1.5", - "php": ">=7.4", - "psr/log": "~1.0", - "symfony/filesystem": "^6.0||^5.0||^4.0", - "symfony/process": "^6.0||^5.0||^4.0" + "php": ">=8.2", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/filesystem": "^7.0||^6.0||^5.0||^4.0", + "symfony/process": "^7.0||^6.0||^5.0||^4.0" }, "require-dev": { - "brianium/paratest": "2.*|6.*", - "ext-pdo": "*", - "ext-pdo_pgsql": "*", - "keboola/coding-standard": "^14.0", - "keboola/php-csv-db-import": "^6", - "keboola/phpunit-retry-annotations": "^0.4", - "keboola/retry": "^0.5.0", - "keboola/table-backend-utils": "^1", - "phpcompatibility/php-compatibility": "*", + "ext-curl": "*", + "keboola/coding-standard": "^15.0", "phpstan/phpstan": "^1", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.0|^10", "rector/rector": "^0.12.23", - "squizlabs/php_codesniffer": "^3" + "squizlabs/php_codesniffer": "^3", + "tomasfejfar/phpstan-phpunit": "^0.1.0" }, "type": "library", "autoload": { "files": [ "src/Keboola/StorageApi/createSimpleJobPollDelay.php" ], - "psr-0": { - "Keboola\\StorageApi": "src/" + "psr-4": { + "Keboola\\StorageApi\\": "src/Keboola/StorageApi/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Keboola Storage API PHP CLient", + "description": "Keboola Storage API PHP Client", "homepage": "http://keboola.com", "support": { "issues": "https://github.com/keboola/storage-api-php-client/issues", - "source": "https://github.com/keboola/storage-api-php-client/tree/v14.11.1" + "source": "https://github.com/keboola/storage-api-php-client/tree/v18.5.0" }, - "time": "2023-08-02T11:55:55+00:00" + "time": "2025-10-24T10:16:29+00:00" }, { "name": "microsoft/azure-storage-blob", @@ -1679,30 +1676,30 @@ }, { "name": "psr/log", - "version": "1.1.4", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1723,9 +1720,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "ralouphie/getallheaders", @@ -2023,38 +2020,34 @@ }, { "name": "symfony/config", - "version": "v5.4.26", + "version": "v6.4.28", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650" + "reference": "15947c18ef3ddb0b2f4ec936b9e90e2520979f62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/8109892f27beed9252bd1f1c1880aeb4ad842650", - "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650", + "url": "https://api.github.com/repos/symfony/config/zipball/15947c18ef3ddb0b2f4ec936b9e90e2520979f62", + "reference": "15947c18ef3ddb0b2f4ec936b9e90e2520979f62", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<4.4" + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2082,7 +2075,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.26" + "source": "https://github.com/symfony/config/tree/v6.4.28" }, "funding": [ { @@ -2093,12 +2086,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-07-19T20:21:11+00:00" + "time": "2025-11-01T19:52:02+00:00" }, { "name": "symfony/console", @@ -3264,12 +3261,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php index 83fa37f..dec11a6 100644 --- a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php +++ b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php @@ -3,6 +3,7 @@ namespace Keboola\Console\Command; use Keboola\JobQueueClient\Client as QueueClient; +use Keboola\JobQueueClient\JobStatuses; use Keboola\JobQueueClient\ListJobsOptions; use Keboola\ManageApi\Client as ManageClient; use Keboola\StorageApi\Client as StorageClient; @@ -160,10 +161,10 @@ private function areThereRunningJobs( $runningJobsListOptions = new ListJobsOptions(); // created, waiting, processing, terminating $runningJobsListOptions->setStatuses([ - ListJobsOptions::STATUS_CREATED, - ListJobsOptions::STATUS_WAITING, - ListJobsOptions::STATUS_PROCESSING, - ListJobsOptions::STATUS_TERMINATING + JobStatuses::CREATED, + JobStatuses::WAITING, + JobStatuses::PROCESSING, + JobStatuses::TERMINATING ]); $runningJobs = $jobsClient->listJobs($runningJobsListOptions); $output->writeln( From 7c5d16d3a3fb9399f97e8a900eecdc9f2cd7b914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 14:39:57 +0100 Subject: [PATCH 02/18] DMD-916 - add PHPStan --- composer.json | 9 ++- composer.lock | 57 ++++++++++++++++++- phpstan.neon | 6 ++ src/Keboola/Console/Command/AddFeature.php | 2 +- .../Console/Command/AllStacksIterator.php | 30 ++++++++-- .../DeleteOrganizationOrphanedWorkspaces.php | 33 +++++++---- .../Command/DeleteOrphanedWorkspaces.php | 24 ++++++-- .../Command/DeleteOwnerlessWorkspaces.php | 6 +- .../Command/DeleteProjectSandboxes.php | 6 +- .../Console/Command/DeletedProjectsPurge.php | 2 +- .../DescribeOrganizationWorkspaces.php | 6 +- .../Console/Command/LineageEventsExport.php | 6 +- .../Command/MassDeleteProjectWorkspaces.php | 2 +- .../MassProjectEnableDynamicBackends.php | 2 +- .../Command/MassProjectExtendExpiration.php | 2 +- .../Console/Command/NotifyProjects.php | 2 +- .../OrganizationIntoMaintenanceMode.php | 2 +- .../OrganizationResetWorkspacePasswords.php | 4 +- .../Command/OrganizationStorageBackend.php | 2 +- .../Command/QueueMassTerminateJobs.php | 2 +- .../Console/Command/ReactivateSchedules.php | 2 +- .../RemoveUserFromOrganizationProjects.php | 6 +- .../Console/Command/SetDataRetention.php | 2 +- 23 files changed, 167 insertions(+), 48 deletions(-) create mode 100644 phpstan.neon diff --git a/composer.json b/composer.json index 4d58d20..c80a531 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "keboola/php-datatypes": "^8.0" }, "require-dev": { - "squizlabs/php_codesniffer": "3.*" + "squizlabs/php_codesniffer": "3.*", + "phpstan/phpstan": "^2.0" }, "authors": [ { @@ -26,5 +27,9 @@ "psr-0": { "Keboola\\Console": "src/" } - } + }, + "scripts": { + "phpstan": "phpstan analyse --memory-limit=512M", + "phpcs": "phpcs --standard=PSR2 -n src/" + } } diff --git a/composer.lock b/composer.lock index a2e1601..3ab6bc9 100644 --- a/composer.lock +++ b/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": "db532bf730913acbd5c98b7f63f04353", + "content-hash": "6145cf81434434a68ec1b9175d40b75d", "packages": [ { "name": "aws/aws-crt-php", @@ -3174,6 +3174,59 @@ } ], "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "2.1.33", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-12-05T10:24:31+00:00" + }, { "name": "squizlabs/php_codesniffer", "version": "3.12.0", @@ -3265,7 +3318,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.2" + "php": "^8.3" }, "platform-dev": {}, "plugin-api-version": "2.9.0" diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..0bb5bf0 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 9 + paths: + - src + bootstrapFiles: + - vendor/autoload.php diff --git a/src/Keboola/Console/Command/AddFeature.php b/src/Keboola/Console/Command/AddFeature.php index 0880b09..b7fb14b 100644 --- a/src/Keboola/Console/Command/AddFeature.php +++ b/src/Keboola/Console/Command/AddFeature.php @@ -105,7 +105,7 @@ protected function createFeature(Client $client, string $featureName, string $fe public function execute(InputInterface $input, OutputInterface $output): ?int { $args = $input->getArguments(); - $force = $input->getOption(self::OPT_FORCE); + $force = (bool) $input->getOption(self::OPT_FORCE); $client = $this->createClient($args[self::ARG_URL], $args[self::ARG_TOKEN]); $this->createFeature($client, $args[self::ARG_FEATURE_NAME], $args[self::ARG_FEATURE_TITLE], $args[self::ARG_FEATURE_DESC], $force, $output); diff --git a/src/Keboola/Console/Command/AllStacksIterator.php b/src/Keboola/Console/Command/AllStacksIterator.php index dfaf8fb..31da5f7 100644 --- a/src/Keboola/Console/Command/AllStacksIterator.php +++ b/src/Keboola/Console/Command/AllStacksIterator.php @@ -26,9 +26,13 @@ protected function configure(): void public function execute(InputInterface $input, OutputInterface $output): ?int { - $commandName = $input->getArgument(self::ARG_COMMAND); - $command = $this->getApplication()->find($commandName); - $cmndInput = $input->getArgument(self::ARG_PARAMS); + $commandName = (string) $input->getArgument(self::ARG_COMMAND); + $application = $this->getApplication(); + if ($application === null) { + throw new Exception('Application not found'); + } + $command = $application->find($commandName); + $cmndInput = (string) $input->getArgument(self::ARG_PARAMS); $stacksFile = file_get_contents('http-client.env.json'); $stacksTokensFile = file_get_contents('http-client.private.env.json'); @@ -40,20 +44,34 @@ public function execute(InputInterface $input, OutputInterface $output): ?int $stacks = json_decode($stacksFile, true); $tokens = json_decode($stacksTokensFile, true); + if (!is_array($stacks) || !is_array($tokens)) { + throw new Exception('Invalid JSON format in http-client files'); + } + /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); // iterates over all stacks foreach ($stacks as $stackName => $stack) { - if (!isset($tokens[$stackName]) || $tokens[$stackName] === '') { - $output->writeln(sprintf('Token for %s not found or it is empty. Skipping', $stackName)); + if (!is_array($stack) || !isset($stack['host'])) { + $output->writeln(sprintf('Invalid stack configuration for %s. Skipping', (string) $stackName)); + continue; + } + + if (!isset($tokens[$stackName]) || $tokens[$stackName] === '' || !is_array($tokens[$stackName])) { + $output->writeln(sprintf('Token for %s not found or it is empty. Skipping', (string) $stackName)); continue; } $token = $tokens[$stackName]; + if (!isset($token['manageToken'])) { + $output->writeln(sprintf('Manage token for %s not found. Skipping', (string) $stackName)); + continue; + } + // build input for the target command. It adds token and host from http-client files. - $inputForThisStack = sprintf('%s %s %s %s', $commandName, $token['manageToken'], $stack['host'], $cmndInput); + $inputForThisStack = sprintf('%s %s %s %s', $commandName, (string) $token['manageToken'], (string) $stack['host'], $cmndInput); $cmdInput = new StringInput($inputForThisStack); // confirm from the user diff --git a/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php b/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php index 83d3311..f591bfe 100644 --- a/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php @@ -17,7 +17,7 @@ class DeleteOrganizationOrphanedWorkspaces extends Command { - protected function configure() + protected function configure(): void { $this ->setName('manage:delete-organization-workspaces') @@ -52,11 +52,12 @@ protected function configure() ); } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): int { - $manageToken = $input->getArgument('manageToken'); - $organizationId = $input->getArgument('organizationId'); - $kbcUrl = sprintf('https://connection.%s', $input->getArgument('hostnameSuffix')); + $manageToken = (string) $input->getArgument('manageToken'); + $organizationId = (int) $input->getArgument('organizationId'); + $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); $organization = $manageClient->getOrganization($organizationId); @@ -68,13 +69,19 @@ protected function execute(InputInterface $input, OutputInterface $output): void ) ); - $storageUrl = 'https://connection.' . $input->getArgument('hostnameSuffix'); + $storageUrl = 'https://connection.' . $hostnameSuffix; - $orphanComponent = $input->getArgument('orphanComponent'); + $orphanComponent = (string) $input->getArgument('orphanComponent'); $componentDesc = empty($orphanComponent) ? '(empty/blank)' : $orphanComponent; $output->writeln(sprintf('Targeting workspaces with component: %s', $componentDesc)); - - $force = $input->getOption('force'); + + $untilDateStr = (string) $input->getArgument('untilDate'); + $untilDate = strtotime($untilDateStr); + if ($untilDate === false) { + throw new \InvalidArgumentException(sprintf('Invalid date format: %s', $untilDateStr)); + } + + $force = (bool) $input->getOption('force'); if ($force) { $output->writeln('Force option is set, doing it for real'); } else { @@ -98,6 +105,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void $output->writeln(sprintf("WARN: Access denied to project: %s", $project['id'])); continue; } + throw $e; } $storageClient = new StorageApiClient([ 'token' => $storageToken['token'], @@ -131,7 +139,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void $shouldDropWorkspace = $this->isWorkspaceOrphaned( $workspace, $orphanComponent, - strtotime($input->getArgument('untilDate')) + $untilDate ); if ($shouldDropWorkspace) { $output->writeln('Deleting orphaned workspace ' . $workspace['id']); @@ -182,8 +190,13 @@ protected function execute(InputInterface $input, OutputInterface $output): void $totalDeletedWorkspaces ) ); + + return 0; } + /** + * @param array $workspace + */ private function isWorkspaceOrphaned(array $workspace, string $component, int $untilDate): bool { // If no component is specified, only workspaces with no component qualify diff --git a/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php b/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php index de9dd67..d0cff5b 100644 --- a/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php @@ -17,7 +17,7 @@ class DeleteOrphanedWorkspaces extends Command { - protected function configure() + protected function configure(): void { $this ->setName('storage:delete-orphaned-workspaces') @@ -49,17 +49,18 @@ protected function configure() ); } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): int { - $token = $input->getArgument('storageToken'); - $componentToDelete = $input->getArgument('orphanComponent'); + $token = (string) $input->getArgument('storageToken'); + $componentToDelete = (string) $input->getArgument('orphanComponent'); $isForce = (bool) $input->getOption('force'); $ignoreBackendErrors = (bool) $input->getOption('ignore-backend-errors'); $manageToken = $input->getOption('manage-token'); if ($ignoreBackendErrors && !$manageToken) { throw new InvalidArgumentException('Manage token must be supplied for ignore-backend-errors.'); } - $url = 'https://connection.' . $input->getArgument('hostnameSuffix'); + $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $url = 'https://connection.' . $hostnameSuffix; $storageClient = new StorageApiClient([ 'token' => $token, @@ -69,6 +70,12 @@ protected function execute(InputInterface $input, OutputInterface $output): void $devBranches = new DevBranches($storageClient); $branchesList = $devBranches->listBranches(); + $untilDateStr = (string) $input->getArgument('untilDate'); + $untilDate = strtotime($untilDateStr); + if ($untilDate === false) { + throw new InvalidArgumentException(sprintf('Invalid date format: %s', $untilDateStr)); + } + $output->writeln('Workspaces for component ' . $componentToDelete . ' will be deleted from ' . $url); if ($isForce) { @@ -90,7 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void $shouldDropWorkspace = $this->isWorkspaceOrphaned( $workspace, $componentToDelete, - strtotime($input->getArgument('untilDate')) + $untilDate ); if ($shouldDropWorkspace) { $output->writeln('Deleting orphaned workspace ' . $workspace['id']); @@ -146,8 +153,13 @@ protected function execute(InputInterface $input, OutputInterface $output): void $totalWorkspaces, $totalDeletedWorkspaces )); + + return 0; } + /** + * @param array $workspace + */ private function isWorkspaceOrphaned(array $workspace, string $component, int $untilDate): bool { return ($workspace['component'] === $component) && strtotime($workspace['created']) < $untilDate; diff --git a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php index b099efd..8b8bc54 100644 --- a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php @@ -17,7 +17,7 @@ class DeleteOwnerlessWorkspaces extends Command { - protected function configure() + protected function configure(): void { $this ->setName('storage:delete-ownerless-workspaces') @@ -42,7 +42,7 @@ protected function configure() ); } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): int { $token = $input->getArgument('storageToken'); $url = 'https://connection.' . $input->getArgument('hostnameSuffix'); @@ -116,6 +116,8 @@ protected function execute(InputInterface $input, OutputInterface $output): void $totalDeletedSandboxes, $totalDeletedStorageWorkspaces )); + + return 0; } private function deleteStorageWorkspace( diff --git a/src/Keboola/Console/Command/DeleteProjectSandboxes.php b/src/Keboola/Console/Command/DeleteProjectSandboxes.php index 6c9e710..24422c7 100644 --- a/src/Keboola/Console/Command/DeleteProjectSandboxes.php +++ b/src/Keboola/Console/Command/DeleteProjectSandboxes.php @@ -15,7 +15,7 @@ class DeleteProjectSandboxes extends Command { - protected function configure() + protected function configure(): void { $this ->setName('storage:delete-project-sandboxes') @@ -40,7 +40,7 @@ protected function configure() ); } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): int { $token = $input->getArgument('storageToken'); $url = 'https://connection.' . $input->getArgument('hostnameSuffix'); @@ -114,5 +114,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void $totalDeletedSandboxes, $totalDeletedStorageWorkspaces )); + + return 0; } } diff --git a/src/Keboola/Console/Command/DeletedProjectsPurge.php b/src/Keboola/Console/Command/DeletedProjectsPurge.php index 0448db5..11946ff 100644 --- a/src/Keboola/Console/Command/DeletedProjectsPurge.php +++ b/src/Keboola/Console/Command/DeletedProjectsPurge.php @@ -11,7 +11,7 @@ class DeletedProjectsPurge extends Command { - protected function configure() + protected function configure(): void { $this ->setName('storage:deleted-projects-purge') diff --git a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php index dd8238e..45bbff1 100644 --- a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php +++ b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php @@ -16,7 +16,7 @@ class DescribeOrganizationWorkspaces extends Command { - protected function configure() + protected function configure(): void { $this ->setName('manage:describe-organization-workspaces') @@ -44,7 +44,7 @@ protected function configure() ); } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): int { $manageToken = $input->getArgument('manageToken'); $organizationId = $input->getArgument('organizationId'); @@ -159,5 +159,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void $totalWorkspaces ) ); + + return 0; } } diff --git a/src/Keboola/Console/Command/LineageEventsExport.php b/src/Keboola/Console/Command/LineageEventsExport.php index faf8789..c9b3743 100644 --- a/src/Keboola/Console/Command/LineageEventsExport.php +++ b/src/Keboola/Console/Command/LineageEventsExport.php @@ -14,7 +14,7 @@ class LineageEventsExport extends Command { - protected function configure() + protected function configure(): void { $this ->setName('storage:lineage-events-export') @@ -68,7 +68,7 @@ private function createMarquezClient(string $marquezUrl): Client ]); } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): int { $token = getenv('STORAGE_API_TOKEN'); if (!$token) { @@ -115,6 +115,8 @@ protected function execute(InputInterface $input, OutputInterface $output): void } $output->writeln('Done'); + + return 0; } private function decodeResponse(ResponseInterface $response): array diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index 72d57ce..05a9ade 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -27,7 +27,7 @@ class MassDeleteProjectWorkspaces extends Command private const ARGUMENT_SOURCE_FILE = 'source-file'; private const OPTION_FORCE = 'force'; - protected function configure() + protected function configure(): void { $this ->setName('manage:mass-delete-project-workspaces') diff --git a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php index 1d996ce..577c229 100644 --- a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php +++ b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php @@ -24,7 +24,7 @@ class MassProjectEnableDynamicBackends extends Command private const FEATURE_NEW_TRANSFORMATIONS_ONLY = 'new-transformations-only'; private const FEATURE_DYNAMIC_BACKEND_SIZE = 'workspace-snowflake-dynamic-backend-size'; - protected function configure() + protected function configure(): void { $this ->setName('manage:mass-project-enable-dynamic-backends') diff --git a/src/Keboola/Console/Command/MassProjectExtendExpiration.php b/src/Keboola/Console/Command/MassProjectExtendExpiration.php index 53c57db..33732ce 100644 --- a/src/Keboola/Console/Command/MassProjectExtendExpiration.php +++ b/src/Keboola/Console/Command/MassProjectExtendExpiration.php @@ -17,7 +17,7 @@ class MassProjectExtendExpiration extends Command const ARGUMENT_TOKEN_STACK = 'token-stack'; const ARGUMENT_EXPIRATION_DAYS = 'extend-days'; - protected function configure() + protected function configure(): void { $this ->setName('manage:mass-project-remove-expiration') diff --git a/src/Keboola/Console/Command/NotifyProjects.php b/src/Keboola/Console/Command/NotifyProjects.php index 9d78248..9f80438 100644 --- a/src/Keboola/Console/Command/NotifyProjects.php +++ b/src/Keboola/Console/Command/NotifyProjects.php @@ -11,7 +11,7 @@ class NotifyProjects extends Command { - protected function configure() + protected function configure(): void { $this ->setName('storage:notify-projects') diff --git a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php index dec11a6..e48db4b 100644 --- a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php +++ b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php @@ -24,7 +24,7 @@ class OrganizationIntoMaintenanceMode extends Command const ARGUMENT_HOSTNAME_SUFFIX = 'hostnameSuffix'; const OPTION_FORCE = 'force'; - protected function configure() + protected function configure(): void { $this ->setName('manage:set-organization-maintenance-mode') diff --git a/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php b/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php index 200d2a6..82ae8c9 100644 --- a/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php +++ b/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php @@ -41,7 +41,7 @@ protected function configure(): void } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): int { $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); $snowflakeHostname = $input->getArgument(self::ARGUMENT_SNOWFLAKE_HOSTNAME); @@ -79,5 +79,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void } } $output->writeln('All done.'); + + return 0; } } diff --git a/src/Keboola/Console/Command/OrganizationStorageBackend.php b/src/Keboola/Console/Command/OrganizationStorageBackend.php index d6ffe0d..c0c044c 100644 --- a/src/Keboola/Console/Command/OrganizationStorageBackend.php +++ b/src/Keboola/Console/Command/OrganizationStorageBackend.php @@ -16,7 +16,7 @@ class OrganizationStorageBackend extends Command const ARGUMENT_STORAGE_BACKEND_ID = 'storageBackendId'; const ARGUMENT_HOSTNAME_SUFFIX = 'hostnameSuffix'; const OPTION_FORCE = 'force'; - protected function configure() + protected function configure(): void { $this ->setName('manage:set-organization-storage-backend') diff --git a/src/Keboola/Console/Command/QueueMassTerminateJobs.php b/src/Keboola/Console/Command/QueueMassTerminateJobs.php index 6980e97..864c287 100644 --- a/src/Keboola/Console/Command/QueueMassTerminateJobs.php +++ b/src/Keboola/Console/Command/QueueMassTerminateJobs.php @@ -21,7 +21,7 @@ class QueueMassTerminateJobs extends Command const ARGUMENT_CONNECTION_URL = 'connection-url'; const ARGUMENT_JOB_STATUS = 'job-status'; - protected function configure() + protected function configure(): void { $this ->setDescription('Terminated all jobs in project') diff --git a/src/Keboola/Console/Command/ReactivateSchedules.php b/src/Keboola/Console/Command/ReactivateSchedules.php index 1edcb4a..c00b088 100644 --- a/src/Keboola/Console/Command/ReactivateSchedules.php +++ b/src/Keboola/Console/Command/ReactivateSchedules.php @@ -23,7 +23,7 @@ class ReactivateSchedules extends Command /** * Configure command, set parameters definition and help. */ - protected function configure() + protected function configure(): void { $this ->setName('storage:reactivate-schedules') diff --git a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php index 8957974..05a7f36 100644 --- a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php +++ b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php @@ -11,7 +11,7 @@ class RemoveUserFromOrganizationProjects extends Command { - protected function configure() + protected function configure(): void { $this ->setName('manage:remove-user-from-organization-projects') @@ -40,7 +40,7 @@ protected function configure() ); } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): int { $manageToken = $input->getArgument('manageToken'); $organizationId = $input->getArgument('organizationId'); @@ -91,6 +91,8 @@ protected function execute(InputInterface $input, OutputInterface $output): void $affectedProjects ) ); + + return 0; } private function isUserInProject(int $userId, array $projectUsers): bool diff --git a/src/Keboola/Console/Command/SetDataRetention.php b/src/Keboola/Console/Command/SetDataRetention.php index 9eca6b0..4665563 100644 --- a/src/Keboola/Console/Command/SetDataRetention.php +++ b/src/Keboola/Console/Command/SetDataRetention.php @@ -20,7 +20,7 @@ class SetDataRetention extends Command /** * Configure command, set parameters definition and help. */ - protected function configure() + protected function configure(): void { $this ->setName('storage:set-data-retention') From a62ee3b6de0106d8e4e4897c8ce37536768af775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 14:56:50 +0100 Subject: [PATCH 03/18] DMD-916 - PHPStan fixes 2 --- .../Console/Command/DeletedProjectsPurge.php | 15 +++++++++++---- .../Command/MassDeleteProjectWorkspaces.php | 4 +++- .../Command/MassProjectEnableDynamicBackends.php | 8 +++++--- .../Command/MassProjectExtendExpiration.php | 6 ++++-- src/Keboola/Console/Command/NotifyProjects.php | 14 +++++++++++--- .../Command/OrganizationIntoMaintenanceMode.php | 4 +++- .../Command/OrganizationStorageBackend.php | 4 +++- .../Console/Command/QueueMassTerminateJobs.php | 4 +++- .../Console/Command/ReactivateSchedules.php | 4 +++- src/Keboola/Console/Command/SetDataRetention.php | 6 ++++-- 10 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/Keboola/Console/Command/DeletedProjectsPurge.php b/src/Keboola/Console/Command/DeletedProjectsPurge.php index 11946ff..d5bf812 100644 --- a/src/Keboola/Console/Command/DeletedProjectsPurge.php +++ b/src/Keboola/Console/Command/DeletedProjectsPurge.php @@ -22,7 +22,7 @@ protected function configure(): void } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $token = $input->getArgument('token'); @@ -57,9 +57,11 @@ protected function execute(InputInterface $input, OutputInterface $output) } $lineNumber++; } + + return 0; } - private function validateHeader($header) + private function validateHeader($header): void { $expectedHeader = ['id', 'name']; if ($header !== $expectedHeader) { @@ -71,8 +73,13 @@ private function validateHeader($header) } } - private function purgeProject(Client $client, OutputInterface $output, $ignoreBackendErrors, $projectId, $projectName) - { + private function purgeProject( + Client $client, + OutputInterface $output, + bool $ignoreBackendErrors, + int $projectId, + string $projectName + ): void { $output->writeln(sprintf('Purge %s (%d)', $projectName, $projectId)); $response = $client->purgeDeletedProject($projectId, [ diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index 05a9ade..b2fa4f0 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -37,7 +37,7 @@ protected function configure(): void ->addOption(self::OPTION_FORCE, 'f', InputOption::VALUE_NONE, 'Write changes'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $connectionUrl = 'https://connection.' . $input->getArgument(self::ARGUMENT_STACK_SUFFIX); $sandboxesUrl = 'https://sandboxes.' . $input->getArgument(self::ARGUMENT_STACK_SUFFIX); @@ -201,5 +201,7 @@ protected function execute(InputInterface $input, OutputInterface $output) ]); } } + + return 0; } } diff --git a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php index 577c229..9faf535 100644 --- a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php +++ b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php @@ -36,7 +36,7 @@ protected function configure(): void ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); $kbcUrl = $input->getArgument(self::ARGUMENT_CONNECTION_URL); @@ -76,7 +76,7 @@ protected function execute(InputInterface $input, OutputInterface $output) '/^(y|j)/i' ); if (!$helper->ask($input, $output, $question)) { - return; + return 0; } } $manageClient->addProjectFeature($projectId, self::FEATURE_NEW_TRANSFORMATIONS_ONLY); @@ -96,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output) while (1) { if ($sleepCount > 10) { $output->writeln(sprintf(' - Project: %s not enabled check PT for more.', $projectId)); - return; + return 0; } $project = $manageClient->getProject($projectId); if (in_array(self::FEATURE_DYNAMIC_BACKEND_SIZE, $project['features'], true)) { @@ -116,6 +116,8 @@ protected function execute(InputInterface $input, OutputInterface $output) )); } } + + return 0; } private function parseProjectIds(string $sourceFile): array diff --git a/src/Keboola/Console/Command/MassProjectExtendExpiration.php b/src/Keboola/Console/Command/MassProjectExtendExpiration.php index 33732ce..396d4b2 100644 --- a/src/Keboola/Console/Command/MassProjectExtendExpiration.php +++ b/src/Keboola/Console/Command/MassProjectExtendExpiration.php @@ -29,7 +29,7 @@ protected function configure(): void } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $sourceFile = $input->getArgument(self::ARGUMENT_SOURCE_FILE); $output->writeln(sprintf('Fetching projects from "%s"', $sourceFile)); @@ -62,7 +62,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } $projectsText = trim(file_get_contents($sourceFile)); if (!$projectsText) { - return; + return 0; } $projects = []; @@ -113,5 +113,7 @@ protected function execute(InputInterface $input, OutputInterface $output) )); } } + + return 0; } } diff --git a/src/Keboola/Console/Command/NotifyProjects.php b/src/Keboola/Console/Command/NotifyProjects.php index 9f80438..499d2bf 100644 --- a/src/Keboola/Console/Command/NotifyProjects.php +++ b/src/Keboola/Console/Command/NotifyProjects.php @@ -22,7 +22,7 @@ protected function configure(): void } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $fh = fopen('php://stdin', 'r'); @@ -50,9 +50,11 @@ protected function execute(InputInterface $input, OutputInterface $output) } $lineNumber++; } + + return 0; } - private function validateHeader($header) + private function validateHeader(array $header): void { $expectedHeader = ['projectId', 'notificationTitle', 'notificationMessage']; if ($header !== $expectedHeader) { @@ -64,7 +66,13 @@ private function validateHeader($header) } } - private function notifyProject(Client $client, OutputInterface $output, $projectId, $notificationTitle, $notificationMessage) + private function notifyProject( + Client $client, + OutputInterface $output, + int $projectId, + string $notificationTitle, + string $notificationMessage + ): void { $output->writeln("Sending notification to project $projectId"); diff --git a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php index e48db4b..d3be9ed 100644 --- a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php +++ b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php @@ -60,7 +60,7 @@ protected function configure(): void ); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $maintenanceMode = $input->getArgument(self::ARGUMENT_MAINTENANCE_MODE); if (!in_array($maintenanceMode, ['on', 'off'])) { @@ -140,6 +140,8 @@ protected function execute(InputInterface $input, OutputInterface $output) } } $output->writeln('All done.'); + + return 0; } private function areThereRunningJobs( diff --git a/src/Keboola/Console/Command/OrganizationStorageBackend.php b/src/Keboola/Console/Command/OrganizationStorageBackend.php index c0c044c..708ed09 100644 --- a/src/Keboola/Console/Command/OrganizationStorageBackend.php +++ b/src/Keboola/Console/Command/OrganizationStorageBackend.php @@ -44,7 +44,7 @@ protected function configure(): void } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $storageBackendId = $input->getArgument(self::ARGUMENT_STORAGE_BACKEND_ID); $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); @@ -84,5 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } } $output->writeln('All done.'); + + return 0; } } diff --git a/src/Keboola/Console/Command/QueueMassTerminateJobs.php b/src/Keboola/Console/Command/QueueMassTerminateJobs.php index 864c287..4b7315e 100644 --- a/src/Keboola/Console/Command/QueueMassTerminateJobs.php +++ b/src/Keboola/Console/Command/QueueMassTerminateJobs.php @@ -31,7 +31,7 @@ protected function configure(): void ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $storageToken = $input->getArgument(self::ARGUMENT_STORAGE_TOKEN); $kbcUrl = $input->getArgument(self::ARGUMENT_CONNECTION_URL); @@ -82,5 +82,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(sprintf('Terminated %s jobs', count($terminatedJobsIds))); $output->writeln(PHP_EOL); + + return 0; } } diff --git a/src/Keboola/Console/Command/ReactivateSchedules.php b/src/Keboola/Console/Command/ReactivateSchedules.php index c00b088..ee0a86e 100644 --- a/src/Keboola/Console/Command/ReactivateSchedules.php +++ b/src/Keboola/Console/Command/ReactivateSchedules.php @@ -33,7 +33,7 @@ protected function configure(): void ->addOption(self::OPT_FORCE, 'f', InputOption::VALUE_NONE, 'Use [--force, -f] to do it for real.'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $isForce = $input->getOption(self::OPT_FORCE); @@ -80,6 +80,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $guzzleClient->post('/schedules', ['body' => json_encode(['configurationId' => $configuration['id']])]); } } + + return 0; } private function buildUrl(string $serviceKey): string diff --git a/src/Keboola/Console/Command/SetDataRetention.php b/src/Keboola/Console/Command/SetDataRetention.php index 4665563..dc6ecf1 100644 --- a/src/Keboola/Console/Command/SetDataRetention.php +++ b/src/Keboola/Console/Command/SetDataRetention.php @@ -29,7 +29,7 @@ protected function configure(): void ->addOption('url', null, InputOption::VALUE_REQUIRED, 'API URL', 'https://connection.keboola.com'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $client = new Client([ 'token' => $input->getArgument('token'), @@ -59,9 +59,11 @@ protected function execute(InputInterface $input, OutputInterface $output) } $output->writeln("All done."); + + return 0; } - private function validateHeader($header) + private function validateHeader(array $header): void { $expectedHeader = ['projectId', 'dataRetentionTimeInDays']; if ($header !== $expectedHeader) { From d0e353db896b9d3e3aa0f8a4cda7ea714f0f6c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:00:24 +0100 Subject: [PATCH 04/18] DMD-916 - PHPStan fixes 3 --- .../Console/Command/DeleteOwnerlessWorkspaces.php | 7 ++++--- .../Console/Command/DeleteProjectSandboxes.php | 7 ++++--- .../Command/DescribeOrganizationWorkspaces.php | 11 ++++++----- .../Console/Command/MassDeleteProjectWorkspaces.php | 11 ++++++----- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php index 8b8bc54..137f458 100644 --- a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php @@ -44,9 +44,10 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $token = $input->getArgument('storageToken'); - $url = 'https://connection.' . $input->getArgument('hostnameSuffix'); - $sandboxesUrl = 'https://sandboxes.' . $input->getArgument('hostnameSuffix'); + $token = (string) $input->getArgument('storageToken'); + $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $url = 'https://connection.' . $hostnameSuffix; + $sandboxesUrl = 'https://sandboxes.' . $hostnameSuffix; $includeShared = (bool) $input->getOption('includeShared'); $force = (bool) $input->getOption('force'); diff --git a/src/Keboola/Console/Command/DeleteProjectSandboxes.php b/src/Keboola/Console/Command/DeleteProjectSandboxes.php index 24422c7..65f9a82 100644 --- a/src/Keboola/Console/Command/DeleteProjectSandboxes.php +++ b/src/Keboola/Console/Command/DeleteProjectSandboxes.php @@ -42,9 +42,10 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $token = $input->getArgument('storageToken'); - $url = 'https://connection.' . $input->getArgument('hostnameSuffix'); - $sandboxesUrl = 'https://sandboxes.' . $input->getArgument('hostnameSuffix'); + $token = (string) $input->getArgument('storageToken'); + $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $url = 'https://connection.' . $hostnameSuffix; + $sandboxesUrl = 'https://sandboxes.' . $hostnameSuffix; $includeShared = (bool) $input->getOption('includeShared'); $force = (bool) $input->getOption('force'); diff --git a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php index 45bbff1..0df6afb 100644 --- a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php +++ b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php @@ -46,10 +46,11 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $manageToken = $input->getArgument('manageToken'); - $organizationId = $input->getArgument('organizationId'); - $outputFile = $input->getArgument('outputFile'); - $kbcUrl = sprintf('https://connection.%s', $input->getArgument('hostnameSuffix')); + $manageToken = (string) $input->getArgument('manageToken'); + $organizationId = (int) $input->getArgument('organizationId'); + $outputFile = (string) $input->getArgument('outputFile'); + $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); $organization = $manageClient->getOrganization($organizationId); $projects = $organization['projects']; @@ -60,7 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ) ); - $storageUrl = 'https://connection.' . $input->getArgument('hostnameSuffix'); + $storageUrl = 'https://connection.' . $hostnameSuffix; $totalWorkspaces = 0; diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index b2fa4f0..1f66afc 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -39,12 +39,13 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $connectionUrl = 'https://connection.' . $input->getArgument(self::ARGUMENT_STACK_SUFFIX); - $sandboxesUrl = 'https://sandboxes.' . $input->getArgument(self::ARGUMENT_STACK_SUFFIX); - $jobsUrl = 'https://queue.' . $input->getArgument(self::ARGUMENT_STACK_SUFFIX); - $sourceFile = $input->getArgument(self::ARGUMENT_SOURCE_FILE); + $stackSuffix = (string) $input->getArgument(self::ARGUMENT_STACK_SUFFIX); + $connectionUrl = 'https://connection.' . $stackSuffix; + $sandboxesUrl = 'https://sandboxes.' . $stackSuffix; + $jobsUrl = 'https://queue.' . $stackSuffix; + $sourceFile = (string) $input->getArgument(self::ARGUMENT_SOURCE_FILE); $output->writeln(sprintf('Fetching projects from "%s"', $sourceFile)); - $force = $input->getOption(self::OPTION_FORCE); + $force = (bool) $input->getOption(self::OPTION_FORCE); // map by project id /** From 985fcc37ae7a3d36b65e3c0dabd9950fe4e39831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:05:14 +0100 Subject: [PATCH 05/18] DMD-916 - PHPStan fixes 4 --- .../Command/MassProjectEnableDynamicBackends.php | 6 +++++- .../Console/Command/MassProjectExtendExpiration.php | 12 ++++++++---- .../Command/OrganizationIntoMaintenanceMode.php | 9 +++++---- .../Command/OrganizationResetWorkspacePasswords.php | 11 ++++++----- .../Console/Command/OrganizationStorageBackend.php | 11 ++++++----- .../Console/Command/QueueMassTerminateJobs.php | 6 +++--- .../Command/RemoveUserFromOrganizationProjects.php | 9 +++++---- src/Keboola/Console/Command/SetDataRetention.php | 4 ++-- 8 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php index 9faf535..86d14b5 100644 --- a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php +++ b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php @@ -125,7 +125,11 @@ private function parseProjectIds(string $sourceFile): array if (!file_exists($sourceFile)) { throw new \Exception(sprintf('Cannot open "%s"', $sourceFile)); } - $projectsText = trim(file_get_contents($sourceFile)); + $fileContents = file_get_contents($sourceFile); + if ($fileContents === false) { + throw new \Exception(sprintf('Cannot read "%s"', $sourceFile)); + } + $projectsText = trim($fileContents); if (!$projectsText) { return []; } diff --git a/src/Keboola/Console/Command/MassProjectExtendExpiration.php b/src/Keboola/Console/Command/MassProjectExtendExpiration.php index 396d4b2..583c870 100644 --- a/src/Keboola/Console/Command/MassProjectExtendExpiration.php +++ b/src/Keboola/Console/Command/MassProjectExtendExpiration.php @@ -31,11 +31,11 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $sourceFile = $input->getArgument(self::ARGUMENT_SOURCE_FILE); + $sourceFile = (string) $input->getArgument(self::ARGUMENT_SOURCE_FILE); $output->writeln(sprintf('Fetching projects from "%s"', $sourceFile)); - $expirationDays = $input->getArgument(self::ARGUMENT_EXPIRATION_DAYS); + $expirationDays = (string) $input->getArgument(self::ARGUMENT_EXPIRATION_DAYS); $output->writeln(sprintf('Expiration days "%s"', $expirationDays)); - $force = $input->getOption(self::OPTION_FORCE); + $force = (bool) $input->getOption(self::OPTION_FORCE); $output->writeln($force ? 'FORCE MODE' : 'DRY RUN'); if ($force) { @@ -60,7 +60,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!file_exists($sourceFile)) { throw new \Exception(sprintf('Cannot open "%s"', $sourceFile)); } - $projectsText = trim(file_get_contents($sourceFile)); + $fileContents = file_get_contents($sourceFile); + if ($fileContents === false) { + throw new \Exception(sprintf('Cannot read "%s"', $sourceFile)); + } + $projectsText = trim($fileContents); if (!$projectsText) { return 0; } diff --git a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php index d3be9ed..387021a 100644 --- a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php +++ b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php @@ -62,7 +62,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $maintenanceMode = $input->getArgument(self::ARGUMENT_MAINTENANCE_MODE); + $maintenanceMode = (string) $input->getArgument(self::ARGUMENT_MAINTENANCE_MODE); if (!in_array($maintenanceMode, ['on', 'off'])) { throw new \Exception(sprintf( 'The argument "%s" must be either "on" or "off", not "%s"', @@ -70,11 +70,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $maintenanceMode )); } - $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); + $manageToken = (string) $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); $reason = $input->getArgument(self::ARGUMENT_REASON); $estimatedEndTime = $input->getArgument(self::ARGUMENT_ESTIMATED_END_TIME); - $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - $kbcUrl = sprintf('https://connection.%s', $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX)); + $organizationId = (int) $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); + $hostnameSuffix = (string) $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new ManageClient(['token' => $manageToken, 'url' => $kbcUrl]); diff --git a/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php b/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php index 82ae8c9..3dfe619 100644 --- a/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php +++ b/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php @@ -43,10 +43,11 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); - $snowflakeHostname = $input->getArgument(self::ARGUMENT_SNOWFLAKE_HOSTNAME); - $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - $kbcUrl = sprintf('https://connection.%s', $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX)); + $manageToken = (string) $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); + $snowflakeHostname = (string) $input->getArgument(self::ARGUMENT_SNOWFLAKE_HOSTNAME); + $organizationId = (int) $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); + $hostnameSuffix = (string) $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); @@ -59,7 +60,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int count($projects), ) ); - $force = $input->getOption(self::OPTION_FORCE); + $force = (bool) $input->getOption(self::OPTION_FORCE); $output->writeln($force ? 'FORCE MODE' : 'DRY RUN'); foreach ($projects as $project) { $output->writeln( diff --git a/src/Keboola/Console/Command/OrganizationStorageBackend.php b/src/Keboola/Console/Command/OrganizationStorageBackend.php index 708ed09..7be7a0b 100644 --- a/src/Keboola/Console/Command/OrganizationStorageBackend.php +++ b/src/Keboola/Console/Command/OrganizationStorageBackend.php @@ -46,10 +46,11 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $storageBackendId = $input->getArgument(self::ARGUMENT_STORAGE_BACKEND_ID); - $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); - $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - $kbcUrl = sprintf('https://connection.%s', $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX)); + $storageBackendId = (string) $input->getArgument(self::ARGUMENT_STORAGE_BACKEND_ID); + $manageToken = (string) $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); + $organizationId = (int) $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); + $hostnameSuffix = (string) $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); @@ -63,7 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $storageBackendId ) ); - $force = $input->getOption(self::OPTION_FORCE); + $force = (bool) $input->getOption(self::OPTION_FORCE); $output->writeln($force ? 'FORCE MODE' : 'DRY RUN'); foreach ($projects as $project) { $output->writeln( diff --git a/src/Keboola/Console/Command/QueueMassTerminateJobs.php b/src/Keboola/Console/Command/QueueMassTerminateJobs.php index 4b7315e..69a1122 100644 --- a/src/Keboola/Console/Command/QueueMassTerminateJobs.php +++ b/src/Keboola/Console/Command/QueueMassTerminateJobs.php @@ -33,9 +33,9 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $storageToken = $input->getArgument(self::ARGUMENT_STORAGE_TOKEN); - $kbcUrl = $input->getArgument(self::ARGUMENT_CONNECTION_URL); - $status = $input->getArgument(self::ARGUMENT_JOB_STATUS); + $storageToken = (string) $input->getArgument(self::ARGUMENT_STORAGE_TOKEN); + $kbcUrl = (string) $input->getArgument(self::ARGUMENT_CONNECTION_URL); + $status = (string) $input->getArgument(self::ARGUMENT_JOB_STATUS); if (!in_array($status, ['created', 'waiting', 'processing'])) { throw new Exception('Status must be either "created", "waiting" or "processing"!'); diff --git a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php index 05a7f36..fb2300e 100644 --- a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php +++ b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php @@ -42,10 +42,11 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $manageToken = $input->getArgument('manageToken'); - $organizationId = $input->getArgument('organizationId'); - $userEmail = $input->getArgument('userEmail'); - $kbcUrl = sprintf('https://connection.%s', $input->getArgument('hostnameSuffix')); + $manageToken = (string) $input->getArgument('manageToken'); + $organizationId = (int) $input->getArgument('organizationId'); + $userEmail = (string) $input->getArgument('userEmail'); + $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); diff --git a/src/Keboola/Console/Command/SetDataRetention.php b/src/Keboola/Console/Command/SetDataRetention.php index dc6ecf1..b0ab5cd 100644 --- a/src/Keboola/Console/Command/SetDataRetention.php +++ b/src/Keboola/Console/Command/SetDataRetention.php @@ -46,8 +46,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($lineNumber === 0) { $this->validateHeader($row); } else { - $projectId = (int) trim($row[0]); - $dataRetentionTimeInDays = (int) trim($row[1]); + $projectId = (int) trim((string) $row[0]); + $dataRetentionTimeInDays = (int) trim((string) $row[1]); try { $client->updateProject($projectId, ['dataRetentionTimeInDays' => $dataRetentionTimeInDays]); $output->writeln("Updated project " . $projectId . " to data retention period " . $dataRetentionTimeInDays); From 241c846f470052b1f0d79f06ca0632f1283da048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:12:21 +0100 Subject: [PATCH 06/18] DMD-916 - PHPStan fixes 5 --- src/Keboola/Console/Command/AllStacksIterator.php | 6 ++++-- .../Console/Command/DeleteOwnerlessWorkspaces.php | 2 +- src/Keboola/Console/Command/DeleteProjectSandboxes.php | 4 ++-- src/Keboola/Console/Command/ProjectsAddFeature.php | 10 +++++----- src/Keboola/Console/Command/ReactivateSchedules.php | 5 ++++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Keboola/Console/Command/AllStacksIterator.php b/src/Keboola/Console/Command/AllStacksIterator.php index 31da5f7..5c0eac5 100644 --- a/src/Keboola/Console/Command/AllStacksIterator.php +++ b/src/Keboola/Console/Command/AllStacksIterator.php @@ -26,13 +26,15 @@ protected function configure(): void public function execute(InputInterface $input, OutputInterface $output): ?int { - $commandName = (string) $input->getArgument(self::ARG_COMMAND); + $commandName = $input->getArgument(self::ARG_COMMAND); + assert(is_string($commandName)); $application = $this->getApplication(); if ($application === null) { throw new Exception('Application not found'); } $command = $application->find($commandName); - $cmndInput = (string) $input->getArgument(self::ARG_PARAMS); + $cmndInput = $input->getArgument(self::ARG_PARAMS); + assert(is_string($cmndInput)); $stacksFile = file_get_contents('http-client.env.json'); $stacksTokensFile = file_get_contents('http-client.private.env.json'); diff --git a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php index 137f458..3db8ad5 100644 --- a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php @@ -127,7 +127,7 @@ private function deleteStorageWorkspace( OutputInterface $output ): void { try { - $workspacesClient->deleteWorkspace($workspaceId); + $workspacesClient->deleteWorkspace((int) $workspaceId); } catch (\Throwable $clientException) { $output->writeln( sprintf( diff --git a/src/Keboola/Console/Command/DeleteProjectSandboxes.php b/src/Keboola/Console/Command/DeleteProjectSandboxes.php index 65f9a82..fd1113e 100644 --- a/src/Keboola/Console/Command/DeleteProjectSandboxes.php +++ b/src/Keboola/Console/Command/DeleteProjectSandboxes.php @@ -84,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!empty($sandbox->getPhysicalId())) { if ($force) { try { - $workspacesClient->deleteWorkspace($sandbox->getPhysicalId()); + $workspacesClient->deleteWorkspace((int) $sandbox->getPhysicalId()); $totalDeletedStorageWorkspaces++; } catch (Exception $exception) { if ($exception->getCode() === 404) { @@ -99,7 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Deleting staging storage workspace ' . $sandbox->getPhysicalId()); $totalDeletedStorageWorkspaces++; if ($force) { - $workspacesClient->deleteWorkspace($sandbox->getStagingWorkspaceId(), [], true); + $workspacesClient->deleteWorkspace((int) $sandbox->getStagingWorkspaceId(), [], true); } } diff --git a/src/Keboola/Console/Command/ProjectsAddFeature.php b/src/Keboola/Console/Command/ProjectsAddFeature.php index 38a938d..56ddc10 100644 --- a/src/Keboola/Console/Command/ProjectsAddFeature.php +++ b/src/Keboola/Console/Command/ProjectsAddFeature.php @@ -17,15 +17,15 @@ class ProjectsAddFeature extends Command const ARG_TOKEN = 'token'; const OPT_FORCE = 'force'; - protected $maintainersChecked = 0; + protected int $maintainersChecked = 0; - protected $orgsChecked = 0; + protected int $orgsChecked = 0; - protected $projectsDisabled = 0; + protected int $projectsDisabled = 0; - protected $projectsWithFeature = 0; + protected int $projectsWithFeature = 0; - protected $projectsUpdated = 0; + protected int $projectsUpdated = 0; protected function configure(): void { diff --git a/src/Keboola/Console/Command/ReactivateSchedules.php b/src/Keboola/Console/Command/ReactivateSchedules.php index ee0a86e..b8d3815 100644 --- a/src/Keboola/Console/Command/ReactivateSchedules.php +++ b/src/Keboola/Console/Command/ReactivateSchedules.php @@ -40,8 +40,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $prefix = $isForce ? 'FORCE: ' : 'DRY-RUN: '; $output->writeln('Running ' . ($isForce ? 'force mode' : 'in dry run mode')); - $this->stackSuffix = $input->getArgument(self::ARG_STACK); + $stackArg = $input->getArgument(self::ARG_STACK); + assert(is_string($stackArg)); + $this->stackSuffix = $stackArg; $token = $input->getArgument(self::ARG_TOKEN); + assert(is_string($token)); $connectionUrl = $this->buildUrl('connection'); $schedulerUrl = $this->buildUrl('scheduler'); From 5f880572c25e5f0df2594b5bc10741b550bfb14f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:24:52 +0100 Subject: [PATCH 07/18] DMD-916 - PHPStan fixes 6 --- .../Console/Command/DeletedProjectsPurge.php | 4 ++-- .../Command/DescribeOrganizationWorkspaces.php | 1 + .../Console/Command/LineageEventsExport.php | 3 +++ .../Command/MassProjectEnableDynamicBackends.php | 3 +++ src/Keboola/Console/Command/NotifyProjects.php | 9 ++++++--- src/Keboola/Console/Command/ProjectsAddFeature.php | 6 ++++++ .../Console/Command/ProjectsRemoveFeature.php | 14 ++++++++++---- .../Command/RemoveUserFromOrganizationProjects.php | 3 +++ src/Keboola/Console/Command/SetDataRetention.php | 3 +++ .../Console/Command/UpdateDataRetention.php | 3 +++ 10 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/Keboola/Console/Command/DeletedProjectsPurge.php b/src/Keboola/Console/Command/DeletedProjectsPurge.php index d5bf812..e226b3a 100644 --- a/src/Keboola/Console/Command/DeletedProjectsPurge.php +++ b/src/Keboola/Console/Command/DeletedProjectsPurge.php @@ -51,8 +51,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $client, $output, $ignoreBackendErrors, - $row[0], - $row[1] + (int) $row[0], + (string) $row[1] ); } $lineNumber++; diff --git a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php index 0df6afb..8e3c490 100644 --- a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php +++ b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php @@ -95,6 +95,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(sprintf("WARN: Access denied to project: %s", $project['id'])); continue; } + throw $e; } $storageClient = new StorageApiClient([ diff --git a/src/Keboola/Console/Command/LineageEventsExport.php b/src/Keboola/Console/Command/LineageEventsExport.php index c9b3743..cf9cee9 100644 --- a/src/Keboola/Console/Command/LineageEventsExport.php +++ b/src/Keboola/Console/Command/LineageEventsExport.php @@ -119,6 +119,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + /** + * @return array + */ private function decodeResponse(ResponseInterface $response): array { return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); diff --git a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php index 86d14b5..a44f13c 100644 --- a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php +++ b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php @@ -120,6 +120,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + /** + * @return array + */ private function parseProjectIds(string $sourceFile): array { if (!file_exists($sourceFile)) { diff --git a/src/Keboola/Console/Command/NotifyProjects.php b/src/Keboola/Console/Command/NotifyProjects.php index 499d2bf..9806647 100644 --- a/src/Keboola/Console/Command/NotifyProjects.php +++ b/src/Keboola/Console/Command/NotifyProjects.php @@ -43,9 +43,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->notifyProject( $client, $output, - $row[0], - $row[1], - $row[2] + (int) $row[0], + (string) $row[1], + (string) $row[2] ); } $lineNumber++; @@ -54,6 +54,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + /** + * @param array $header + */ private function validateHeader(array $header): void { $expectedHeader = ['projectId', 'notificationTitle', 'notificationMessage']; diff --git a/src/Keboola/Console/Command/ProjectsAddFeature.php b/src/Keboola/Console/Command/ProjectsAddFeature.php index 56ddc10..145d871 100644 --- a/src/Keboola/Console/Command/ProjectsAddFeature.php +++ b/src/Keboola/Console/Command/ProjectsAddFeature.php @@ -47,6 +47,9 @@ protected function createClient(string $host, string $token): Client ]); } + /** + * @param array $projectInfo + */ protected function addFeatureToProject(Client $client, OutputInterface $output, array $projectInfo, string $featureName, bool $force): void { $output->writeln("Adding feature to project " . $projectInfo['id']); @@ -89,6 +92,9 @@ protected function addFeatureToAllProjects(Client $client, OutputInterface $outp } } + /** + * @param array $projectIds + */ protected function addFeatureToSelectedProjects(Client $client, OutputInterface $output, string $featureName, array $projectIds, bool $force): void { foreach ($projectIds as $projectId) { diff --git a/src/Keboola/Console/Command/ProjectsRemoveFeature.php b/src/Keboola/Console/Command/ProjectsRemoveFeature.php index c1103d6..c878ffb 100644 --- a/src/Keboola/Console/Command/ProjectsRemoveFeature.php +++ b/src/Keboola/Console/Command/ProjectsRemoveFeature.php @@ -41,10 +41,10 @@ protected function configure(): void public function execute(InputInterface $input, OutputInterface $output): ?int { - $apiToken = $input->getArgument('token'); - $apiUrl = $input->getArgument('url'); - $featureName = $input->getArgument('feature'); - $projects = $input->getArgument('projects'); + $apiToken = (string) $input->getArgument('token'); + $apiUrl = (string) $input->getArgument('url'); + $featureName = (string) $input->getArgument('feature'); + $projects = (string) $input->getArgument('projects'); $allProjects = strtolower($projects) === 'all'; $force = (bool) $input->getOption('force'); @@ -105,6 +105,9 @@ private function removeFeatureFromAllProjects( } } + /** + * @param array $projectIds + */ private function removeFeatureFromSelectedProjects( Client $client, OutputInterface $output, @@ -128,6 +131,9 @@ private function removeFeatureFromSelectedProjects( } } + /** + * @param array $projectInfo + */ private function removeFeatureFromProject( Client $client, OutputInterface $output, diff --git a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php index fb2300e..d4bd1da 100644 --- a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php +++ b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php @@ -96,6 +96,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + /** + * @param array> $projectUsers + */ private function isUserInProject(int $userId, array $projectUsers): bool { foreach ($projectUsers as $projectUser) { diff --git a/src/Keboola/Console/Command/SetDataRetention.php b/src/Keboola/Console/Command/SetDataRetention.php index b0ab5cd..b0ace41 100644 --- a/src/Keboola/Console/Command/SetDataRetention.php +++ b/src/Keboola/Console/Command/SetDataRetention.php @@ -63,6 +63,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + /** + * @param array $header + */ private function validateHeader(array $header): void { $expectedHeader = ['projectId', 'dataRetentionTimeInDays']; diff --git a/src/Keboola/Console/Command/UpdateDataRetention.php b/src/Keboola/Console/Command/UpdateDataRetention.php index a0c6bb6..88686a8 100644 --- a/src/Keboola/Console/Command/UpdateDataRetention.php +++ b/src/Keboola/Console/Command/UpdateDataRetention.php @@ -43,6 +43,9 @@ protected function createClient(string $host, string $token): Client ]); } + /** + * @param array $projectInfo + */ protected function updateProjectDataRetention( Client $client, OutputInterface $output, From 4f999c96a3f24946486e55f6a11201cf4ddeb2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:30:08 +0100 Subject: [PATCH 08/18] DMD-916 - PHPStan fixes 7 --- .../Command/DeleteOrganizationOrphanedWorkspaces.php | 4 +++- src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php | 4 +++- src/Keboola/Console/Command/LineageEventsExport.php | 9 +++++++-- .../Console/Command/MassDeleteProjectWorkspaces.php | 2 ++ .../Console/Command/OrganizationIntoMaintenanceMode.php | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php b/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php index f591bfe..f845fbc 100644 --- a/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php @@ -208,7 +208,9 @@ private function isWorkspaceOrphaned(array $workspace, string $component, int $u return false; } // Skip workspaces created after or on the cutoff date - if (strtotime($workspace['created']) >= $untilDate) { + $createdDate = $workspace['created']; + assert(is_string($createdDate)); + if (strtotime($createdDate) >= $untilDate) { return false; } // If all conditions pass, the workspace qualifies diff --git a/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php b/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php index d0cff5b..6fc7fe5 100644 --- a/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php @@ -162,6 +162,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ private function isWorkspaceOrphaned(array $workspace, string $component, int $untilDate): bool { - return ($workspace['component'] === $component) && strtotime($workspace['created']) < $untilDate; + $createdDate = $workspace['created']; + assert(is_string($createdDate)); + return ($workspace['component'] === $component) && strtotime($createdDate) < $untilDate; } } diff --git a/src/Keboola/Console/Command/LineageEventsExport.php b/src/Keboola/Console/Command/LineageEventsExport.php index cf9cee9..d4c7bd7 100644 --- a/src/Keboola/Console/Command/LineageEventsExport.php +++ b/src/Keboola/Console/Command/LineageEventsExport.php @@ -75,12 +75,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new Exception('Environment variable "STORAGE_API_TOKEN" missing.'); } + $connectionUrl = $input->getArgument('connectionUrl'); + assert(is_string($connectionUrl)); + $marquezUrl = $input->getArgument('marquezUrl'); + assert(is_string($marquezUrl)); + $queueClient = $this->createQueueClient( $token, - $this->findQueueServiceUrl($token, $input->getArgument('connectionUrl')) + $this->findQueueServiceUrl($token, $connectionUrl) ); - $marquezClient = $this->createMarquezClient($input->getArgument('marquezUrl')); + $marquezClient = $this->createMarquezClient($marquezUrl); $jobsResponse = $queueClient->request('GET', '/jobs?' . http_build_query([ 'createdTimeFrom' => '-20 days', 'sortOrder' => 'desc', diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index 1f66afc..aeaf83f 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -83,12 +83,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int // ]; foreach ($map as $projectId => $workspacesSchemasToDelete) { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->getHelper('question'); $question = new Question(sprintf( 'Paste storage token for project "%s" to continue.' . PHP_EOL, $projectId, )); $storageToken = $helper->ask($input, $output, $question); + assert(is_string($storageToken)); $storageClient = new Client([ 'token' => $storageToken, diff --git a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php index 387021a..89759aa 100644 --- a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php +++ b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php @@ -113,7 +113,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $thereAreRunningJobs = $this->areThereRunningJobs( $manageClient, $project['id'], - $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX), + $hostnameSuffix, $output ); if ($thereAreRunningJobs) { From a1c78a2b973eed20da014351954c87515d8839f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:35:05 +0100 Subject: [PATCH 09/18] DMD-916 - PHPStan fixes 8 --- src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php | 5 +---- src/Keboola/Console/Command/ProjectsAddFeature.php | 6 ++++-- src/Keboola/Console/Command/ProjectsRemoveFeature.php | 6 ++++-- src/Keboola/Console/Command/UpdateDataRetention.php | 4 +++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index aeaf83f..18a9ef4 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -49,10 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // map by project id /** - * @var array{ - * string, - * string[], - * } $map + * @var array> $map */ $map = []; $csv = new CsvFile($sourceFile); diff --git a/src/Keboola/Console/Command/ProjectsAddFeature.php b/src/Keboola/Console/Command/ProjectsAddFeature.php index 145d871..34fbc26 100644 --- a/src/Keboola/Console/Command/ProjectsAddFeature.php +++ b/src/Keboola/Console/Command/ProjectsAddFeature.php @@ -59,7 +59,9 @@ protected function addFeatureToProject(Client $client, OutputInterface $output, $output->writeln(" - project disabled: " . $projectInfo["disabled"]["reason"]); $this->projectsDisabled++; } else { - if (in_array($featureName, $projectInfo["features"], true)) { + $features = $projectInfo["features"]; + assert(is_array($features)); + if (in_array($featureName, $features, true)) { $output->writeln(" - feature '{$featureName}' is already set."); $this->projectsWithFeature++; } else { @@ -93,7 +95,7 @@ protected function addFeatureToAllProjects(Client $client, OutputInterface $outp } /** - * @param array $projectIds + * @param array $projectIds */ protected function addFeatureToSelectedProjects(Client $client, OutputInterface $output, string $featureName, array $projectIds, bool $force): void { diff --git a/src/Keboola/Console/Command/ProjectsRemoveFeature.php b/src/Keboola/Console/Command/ProjectsRemoveFeature.php index c878ffb..113b736 100644 --- a/src/Keboola/Console/Command/ProjectsRemoveFeature.php +++ b/src/Keboola/Console/Command/ProjectsRemoveFeature.php @@ -106,7 +106,7 @@ private function removeFeatureFromAllProjects( } /** - * @param array $projectIds + * @param array $projectIds */ private function removeFeatureFromSelectedProjects( Client $client, @@ -148,7 +148,9 @@ private function removeFeatureFromProject( return; } - if (!in_array($featureName, $projectInfo['features'], true)) { + $features = $projectInfo['features']; + assert(is_array($features)); + if (!in_array($featureName, $features, true)) { $output->writeln('doesn\'t have the feature, skipping'); $this->projectsWithoutFeature++; diff --git a/src/Keboola/Console/Command/UpdateDataRetention.php b/src/Keboola/Console/Command/UpdateDataRetention.php index 88686a8..41b36fd 100644 --- a/src/Keboola/Console/Command/UpdateDataRetention.php +++ b/src/Keboola/Console/Command/UpdateDataRetention.php @@ -60,7 +60,9 @@ protected function updateProjectDataRetention( $output->writeln(sprintf(' - project disabled: "%s"', $projectInfo["disabled"]["reason"])); $this->projectsDisabled++; } else { - if (!in_array('snowflake', $projectInfo['assignedBackends'])) { + $assignedBackends = $projectInfo['assignedBackends']; + assert(is_array($assignedBackends)); + if (!in_array('snowflake', $assignedBackends)) { $output->writeln(' - project does not have Snowflake backend assigned. Skiping.'); $this->projectsNoSnowflake++; } else { From 06c85087e474bca5b17eb6bbc29aad257e5cff25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:39:22 +0100 Subject: [PATCH 10/18] DMD-916 - PHPStan fixes 9 --- src/Keboola/Console/Command/DeletedProjectsPurge.php | 1 + src/Keboola/Console/Command/ProjectsAddFeature.php | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Keboola/Console/Command/DeletedProjectsPurge.php b/src/Keboola/Console/Command/DeletedProjectsPurge.php index e226b3a..eb13d51 100644 --- a/src/Keboola/Console/Command/DeletedProjectsPurge.php +++ b/src/Keboola/Console/Command/DeletedProjectsPurge.php @@ -25,6 +25,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { $token = $input->getArgument('token'); + assert(is_string($token)); $fh = fopen('php://stdin', 'r'); if (!$fh) { diff --git a/src/Keboola/Console/Command/ProjectsAddFeature.php b/src/Keboola/Console/Command/ProjectsAddFeature.php index 34fbc26..429ce10 100644 --- a/src/Keboola/Console/Command/ProjectsAddFeature.php +++ b/src/Keboola/Console/Command/ProjectsAddFeature.php @@ -52,11 +52,15 @@ protected function createClient(string $host, string $token): Client */ protected function addFeatureToProject(Client $client, OutputInterface $output, array $projectInfo, string $featureName, bool $force): void { - $output->writeln("Adding feature to project " . $projectInfo['id']); + $projectId = $projectInfo['id']; + assert(is_int($projectId) || is_string($projectId)); + $output->writeln("Adding feature to project " . $projectId); // Disabled projects if (isset($projectInfo["isDisabled"]) && $projectInfo["isDisabled"]) { - $output->writeln(" - project disabled: " . $projectInfo["disabled"]["reason"]); + $disabledReason = $projectInfo["disabled"]["reason"]; + assert(is_string($disabledReason)); + $output->writeln(" - project disabled: " . $disabledReason); $this->projectsDisabled++; } else { $features = $projectInfo["features"]; From b27dc65136a3c1f6fe3f50cf9254d09fe8e61c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:46:11 +0100 Subject: [PATCH 11/18] DMD-916 - PHPStan fixes 10 --- .../DeleteOrganizationOrphanedWorkspaces.php | 16 +++++++++++----- .../Console/Command/DeleteOrphanedWorkspaces.php | 12 ++++++++---- .../Command/DeleteOwnerlessWorkspaces.php | 6 ++++-- .../Console/Command/DeleteProjectSandboxes.php | 6 ++++-- .../Command/DescribeOrganizationWorkspaces.php | 13 +++++++++---- .../Command/MassDeleteProjectWorkspaces.php | 6 ++++-- .../Command/MassProjectExtendExpiration.php | 6 ++++-- .../Command/OrganizationIntoMaintenanceMode.php | 13 +++++++++---- .../OrganizationResetWorkspacePasswords.php | 13 +++++++++---- .../Command/OrganizationStorageBackend.php | 13 +++++++++---- .../Console/Command/ProjectsRemoveFeature.php | 12 ++++++++---- .../Console/Command/QueueMassTerminateJobs.php | 9 ++++++--- .../RemoveUserFromOrganizationProjects.php | 13 +++++++++---- 13 files changed, 94 insertions(+), 44 deletions(-) diff --git a/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php b/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php index f845fbc..0adb146 100644 --- a/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php @@ -54,9 +54,13 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $manageToken = (string) $input->getArgument('manageToken'); - $organizationId = (int) $input->getArgument('organizationId'); - $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $manageToken = $input->getArgument('manageToken'); + assert(is_string($manageToken)); + $organizationId = $input->getArgument('organizationId'); + assert(is_int($organizationId) || is_string($organizationId)); + $organizationId = (int) $organizationId; + $hostnameSuffix = $input->getArgument('hostnameSuffix'); + assert(is_string($hostnameSuffix)); $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); @@ -71,11 +75,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $storageUrl = 'https://connection.' . $hostnameSuffix; - $orphanComponent = (string) $input->getArgument('orphanComponent'); + $orphanComponent = $input->getArgument('orphanComponent'); + assert(is_string($orphanComponent)); $componentDesc = empty($orphanComponent) ? '(empty/blank)' : $orphanComponent; $output->writeln(sprintf('Targeting workspaces with component: %s', $componentDesc)); - $untilDateStr = (string) $input->getArgument('untilDate'); + $untilDateStr = $input->getArgument('untilDate'); + assert(is_string($untilDateStr)); $untilDate = strtotime($untilDateStr); if ($untilDate === false) { throw new \InvalidArgumentException(sprintf('Invalid date format: %s', $untilDateStr)); diff --git a/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php b/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php index 6fc7fe5..9676d77 100644 --- a/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOrphanedWorkspaces.php @@ -51,15 +51,18 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $token = (string) $input->getArgument('storageToken'); - $componentToDelete = (string) $input->getArgument('orphanComponent'); + $token = $input->getArgument('storageToken'); + assert(is_string($token)); + $componentToDelete = $input->getArgument('orphanComponent'); + assert(is_string($componentToDelete)); $isForce = (bool) $input->getOption('force'); $ignoreBackendErrors = (bool) $input->getOption('ignore-backend-errors'); $manageToken = $input->getOption('manage-token'); if ($ignoreBackendErrors && !$manageToken) { throw new InvalidArgumentException('Manage token must be supplied for ignore-backend-errors.'); } - $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $hostnameSuffix = $input->getArgument('hostnameSuffix'); + assert(is_string($hostnameSuffix)); $url = 'https://connection.' . $hostnameSuffix; $storageClient = new StorageApiClient([ @@ -70,7 +73,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $devBranches = new DevBranches($storageClient); $branchesList = $devBranches->listBranches(); - $untilDateStr = (string) $input->getArgument('untilDate'); + $untilDateStr = $input->getArgument('untilDate'); + assert(is_string($untilDateStr)); $untilDate = strtotime($untilDateStr); if ($untilDate === false) { throw new InvalidArgumentException(sprintf('Invalid date format: %s', $untilDateStr)); diff --git a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php index 3db8ad5..068c9ff 100644 --- a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php @@ -44,8 +44,10 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $token = (string) $input->getArgument('storageToken'); - $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $token = $input->getArgument('storageToken'); + assert(is_string($token)); + $hostnameSuffix = $input->getArgument('hostnameSuffix'); + assert(is_string($hostnameSuffix)); $url = 'https://connection.' . $hostnameSuffix; $sandboxesUrl = 'https://sandboxes.' . $hostnameSuffix; $includeShared = (bool) $input->getOption('includeShared'); diff --git a/src/Keboola/Console/Command/DeleteProjectSandboxes.php b/src/Keboola/Console/Command/DeleteProjectSandboxes.php index fd1113e..73a7fa0 100644 --- a/src/Keboola/Console/Command/DeleteProjectSandboxes.php +++ b/src/Keboola/Console/Command/DeleteProjectSandboxes.php @@ -42,8 +42,10 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $token = (string) $input->getArgument('storageToken'); - $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $token = $input->getArgument('storageToken'); + assert(is_string($token)); + $hostnameSuffix = $input->getArgument('hostnameSuffix'); + assert(is_string($hostnameSuffix)); $url = 'https://connection.' . $hostnameSuffix; $sandboxesUrl = 'https://sandboxes.' . $hostnameSuffix; $includeShared = (bool) $input->getOption('includeShared'); diff --git a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php index 8e3c490..51abbe9 100644 --- a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php +++ b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php @@ -46,10 +46,15 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $manageToken = (string) $input->getArgument('manageToken'); - $organizationId = (int) $input->getArgument('organizationId'); - $outputFile = (string) $input->getArgument('outputFile'); - $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $manageToken = $input->getArgument('manageToken'); + assert(is_string($manageToken)); + $organizationId = $input->getArgument('organizationId'); + assert(is_int($organizationId) || is_string($organizationId)); + $organizationId = (int) $organizationId; + $outputFile = $input->getArgument('outputFile'); + assert(is_string($outputFile)); + $hostnameSuffix = $input->getArgument('hostnameSuffix'); + assert(is_string($hostnameSuffix)); $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); $organization = $manageClient->getOrganization($organizationId); diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index 18a9ef4..2000545 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -39,11 +39,13 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $stackSuffix = (string) $input->getArgument(self::ARGUMENT_STACK_SUFFIX); + $stackSuffix = $input->getArgument(self::ARGUMENT_STACK_SUFFIX); + assert(is_string($stackSuffix)); $connectionUrl = 'https://connection.' . $stackSuffix; $sandboxesUrl = 'https://sandboxes.' . $stackSuffix; $jobsUrl = 'https://queue.' . $stackSuffix; - $sourceFile = (string) $input->getArgument(self::ARGUMENT_SOURCE_FILE); + $sourceFile = $input->getArgument(self::ARGUMENT_SOURCE_FILE); + assert(is_string($sourceFile)); $output->writeln(sprintf('Fetching projects from "%s"', $sourceFile)); $force = (bool) $input->getOption(self::OPTION_FORCE); diff --git a/src/Keboola/Console/Command/MassProjectExtendExpiration.php b/src/Keboola/Console/Command/MassProjectExtendExpiration.php index 583c870..18fd48b 100644 --- a/src/Keboola/Console/Command/MassProjectExtendExpiration.php +++ b/src/Keboola/Console/Command/MassProjectExtendExpiration.php @@ -31,9 +31,11 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $sourceFile = (string) $input->getArgument(self::ARGUMENT_SOURCE_FILE); + $sourceFile = $input->getArgument(self::ARGUMENT_SOURCE_FILE); + assert(is_string($sourceFile)); $output->writeln(sprintf('Fetching projects from "%s"', $sourceFile)); - $expirationDays = (string) $input->getArgument(self::ARGUMENT_EXPIRATION_DAYS); + $expirationDays = $input->getArgument(self::ARGUMENT_EXPIRATION_DAYS); + assert(is_string($expirationDays)); $output->writeln(sprintf('Expiration days "%s"', $expirationDays)); $force = (bool) $input->getOption(self::OPTION_FORCE); $output->writeln($force ? 'FORCE MODE' : 'DRY RUN'); diff --git a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php index 89759aa..eb1afce 100644 --- a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php +++ b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php @@ -62,7 +62,8 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $maintenanceMode = (string) $input->getArgument(self::ARGUMENT_MAINTENANCE_MODE); + $maintenanceMode = $input->getArgument(self::ARGUMENT_MAINTENANCE_MODE); + assert(is_string($maintenanceMode)); if (!in_array($maintenanceMode, ['on', 'off'])) { throw new \Exception(sprintf( 'The argument "%s" must be either "on" or "off", not "%s"', @@ -70,11 +71,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int $maintenanceMode )); } - $manageToken = (string) $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); + $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); + assert(is_string($manageToken)); $reason = $input->getArgument(self::ARGUMENT_REASON); $estimatedEndTime = $input->getArgument(self::ARGUMENT_ESTIMATED_END_TIME); - $organizationId = (int) $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - $hostnameSuffix = (string) $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); + assert(is_int($organizationId) || is_string($organizationId)); + $organizationId = (int) $organizationId; + $hostnameSuffix = $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + assert(is_string($hostnameSuffix)); $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new ManageClient(['token' => $manageToken, 'url' => $kbcUrl]); diff --git a/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php b/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php index 3dfe619..1e22130 100644 --- a/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php +++ b/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php @@ -43,10 +43,15 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $manageToken = (string) $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); - $snowflakeHostname = (string) $input->getArgument(self::ARGUMENT_SNOWFLAKE_HOSTNAME); - $organizationId = (int) $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - $hostnameSuffix = (string) $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); + assert(is_string($manageToken)); + $snowflakeHostname = $input->getArgument(self::ARGUMENT_SNOWFLAKE_HOSTNAME); + assert(is_string($snowflakeHostname)); + $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); + assert(is_int($organizationId) || is_string($organizationId)); + $organizationId = (int) $organizationId; + $hostnameSuffix = $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + assert(is_string($hostnameSuffix)); $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); diff --git a/src/Keboola/Console/Command/OrganizationStorageBackend.php b/src/Keboola/Console/Command/OrganizationStorageBackend.php index 7be7a0b..75d6f61 100644 --- a/src/Keboola/Console/Command/OrganizationStorageBackend.php +++ b/src/Keboola/Console/Command/OrganizationStorageBackend.php @@ -46,10 +46,15 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $storageBackendId = (string) $input->getArgument(self::ARGUMENT_STORAGE_BACKEND_ID); - $manageToken = (string) $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); - $organizationId = (int) $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - $hostnameSuffix = (string) $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + $storageBackendId = $input->getArgument(self::ARGUMENT_STORAGE_BACKEND_ID); + assert(is_string($storageBackendId)); + $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); + assert(is_string($manageToken)); + $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); + assert(is_int($organizationId) || is_string($organizationId)); + $organizationId = (int) $organizationId; + $hostnameSuffix = $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); + assert(is_string($hostnameSuffix)); $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); diff --git a/src/Keboola/Console/Command/ProjectsRemoveFeature.php b/src/Keboola/Console/Command/ProjectsRemoveFeature.php index 113b736..b461e16 100644 --- a/src/Keboola/Console/Command/ProjectsRemoveFeature.php +++ b/src/Keboola/Console/Command/ProjectsRemoveFeature.php @@ -41,10 +41,14 @@ protected function configure(): void public function execute(InputInterface $input, OutputInterface $output): ?int { - $apiToken = (string) $input->getArgument('token'); - $apiUrl = (string) $input->getArgument('url'); - $featureName = (string) $input->getArgument('feature'); - $projects = (string) $input->getArgument('projects'); + $apiToken = $input->getArgument('token'); + assert(is_string($apiToken)); + $apiUrl = $input->getArgument('url'); + assert(is_string($apiUrl)); + $featureName = $input->getArgument('feature'); + assert(is_string($featureName)); + $projects = $input->getArgument('projects'); + assert(is_string($projects)); $allProjects = strtolower($projects) === 'all'; $force = (bool) $input->getOption('force'); diff --git a/src/Keboola/Console/Command/QueueMassTerminateJobs.php b/src/Keboola/Console/Command/QueueMassTerminateJobs.php index 69a1122..5c17b05 100644 --- a/src/Keboola/Console/Command/QueueMassTerminateJobs.php +++ b/src/Keboola/Console/Command/QueueMassTerminateJobs.php @@ -33,9 +33,12 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $storageToken = (string) $input->getArgument(self::ARGUMENT_STORAGE_TOKEN); - $kbcUrl = (string) $input->getArgument(self::ARGUMENT_CONNECTION_URL); - $status = (string) $input->getArgument(self::ARGUMENT_JOB_STATUS); + $storageToken = $input->getArgument(self::ARGUMENT_STORAGE_TOKEN); + assert(is_string($storageToken)); + $kbcUrl = $input->getArgument(self::ARGUMENT_CONNECTION_URL); + assert(is_string($kbcUrl)); + $status = $input->getArgument(self::ARGUMENT_JOB_STATUS); + assert(is_string($status)); if (!in_array($status, ['created', 'waiting', 'processing'])) { throw new Exception('Status must be either "created", "waiting" or "processing"!'); diff --git a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php index d4bd1da..52fd5f6 100644 --- a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php +++ b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php @@ -42,10 +42,15 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $manageToken = (string) $input->getArgument('manageToken'); - $organizationId = (int) $input->getArgument('organizationId'); - $userEmail = (string) $input->getArgument('userEmail'); - $hostnameSuffix = (string) $input->getArgument('hostnameSuffix'); + $manageToken = $input->getArgument('manageToken'); + assert(is_string($manageToken)); + $organizationId = $input->getArgument('organizationId'); + assert(is_int($organizationId) || is_string($organizationId)); + $organizationId = (int) $organizationId; + $userEmail = $input->getArgument('userEmail'); + assert(is_string($userEmail)); + $hostnameSuffix = $input->getArgument('hostnameSuffix'); + assert(is_string($hostnameSuffix)); $kbcUrl = sprintf('https://connection.%s', $hostnameSuffix); $manageClient = new Client(['token' => $manageToken, 'url' => $kbcUrl]); From 2392f931ac00b32da889908f88ff71bce415d9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 15:52:01 +0100 Subject: [PATCH 12/18] DMD-916 - PHPStan fixes 11 --- .../Console/Command/DeleteOrganizationOrphanedWorkspaces.php | 3 ++- src/Keboola/Console/Command/DeletedProjectsPurge.php | 5 ++++- .../Console/Command/DescribeOrganizationWorkspaces.php | 3 ++- src/Keboola/Console/Command/NotifyProjects.php | 2 +- .../Console/Command/OrganizationIntoMaintenanceMode.php | 3 ++- .../Console/Command/OrganizationResetWorkspacePasswords.php | 3 ++- src/Keboola/Console/Command/OrganizationStorageBackend.php | 3 ++- src/Keboola/Console/Command/ProjectsAddFeature.php | 3 ++- .../Console/Command/RemoveUserFromOrganizationProjects.php | 3 ++- src/Keboola/Console/Command/SetDataRetention.php | 2 +- 10 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php b/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php index 0adb146..27cc9f6 100644 --- a/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOrganizationOrphanedWorkspaces.php @@ -57,7 +57,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $manageToken = $input->getArgument('manageToken'); assert(is_string($manageToken)); $organizationId = $input->getArgument('organizationId'); - assert(is_int($organizationId) || is_string($organizationId)); + assert(is_string($organizationId)); + $organizationId = is_numeric($organizationId) ? (int) $organizationId : (int) $organizationId; $organizationId = (int) $organizationId; $hostnameSuffix = $input->getArgument('hostnameSuffix'); assert(is_string($hostnameSuffix)); diff --git a/src/Keboola/Console/Command/DeletedProjectsPurge.php b/src/Keboola/Console/Command/DeletedProjectsPurge.php index eb13d51..41c7036 100644 --- a/src/Keboola/Console/Command/DeletedProjectsPurge.php +++ b/src/Keboola/Console/Command/DeletedProjectsPurge.php @@ -62,7 +62,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - private function validateHeader($header): void + /** + * @param array $header + */ + private function validateHeader(array $header): void { $expectedHeader = ['id', 'name']; if ($header !== $expectedHeader) { diff --git a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php index 51abbe9..0b8b5a0 100644 --- a/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php +++ b/src/Keboola/Console/Command/DescribeOrganizationWorkspaces.php @@ -49,7 +49,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $manageToken = $input->getArgument('manageToken'); assert(is_string($manageToken)); $organizationId = $input->getArgument('organizationId'); - assert(is_int($organizationId) || is_string($organizationId)); + assert(is_string($organizationId)); + $organizationId = is_numeric($organizationId) ? (int) $organizationId : (int) $organizationId; $organizationId = (int) $organizationId; $outputFile = $input->getArgument('outputFile'); assert(is_string($outputFile)); diff --git a/src/Keboola/Console/Command/NotifyProjects.php b/src/Keboola/Console/Command/NotifyProjects.php index 9806647..46090e5 100644 --- a/src/Keboola/Console/Command/NotifyProjects.php +++ b/src/Keboola/Console/Command/NotifyProjects.php @@ -55,7 +55,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @param array $header + * @param array $header */ private function validateHeader(array $header): void { diff --git a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php index eb1afce..09fb98f 100644 --- a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php +++ b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php @@ -76,7 +76,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $reason = $input->getArgument(self::ARGUMENT_REASON); $estimatedEndTime = $input->getArgument(self::ARGUMENT_ESTIMATED_END_TIME); $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - assert(is_int($organizationId) || is_string($organizationId)); + assert(is_string($organizationId)); + $organizationId = is_numeric($organizationId) ? (int) $organizationId : (int) $organizationId; $organizationId = (int) $organizationId; $hostnameSuffix = $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); assert(is_string($hostnameSuffix)); diff --git a/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php b/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php index 1e22130..d2725f7 100644 --- a/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php +++ b/src/Keboola/Console/Command/OrganizationResetWorkspacePasswords.php @@ -48,7 +48,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $snowflakeHostname = $input->getArgument(self::ARGUMENT_SNOWFLAKE_HOSTNAME); assert(is_string($snowflakeHostname)); $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - assert(is_int($organizationId) || is_string($organizationId)); + assert(is_string($organizationId)); + $organizationId = is_numeric($organizationId) ? (int) $organizationId : (int) $organizationId; $organizationId = (int) $organizationId; $hostnameSuffix = $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); assert(is_string($hostnameSuffix)); diff --git a/src/Keboola/Console/Command/OrganizationStorageBackend.php b/src/Keboola/Console/Command/OrganizationStorageBackend.php index 75d6f61..f0ce408 100644 --- a/src/Keboola/Console/Command/OrganizationStorageBackend.php +++ b/src/Keboola/Console/Command/OrganizationStorageBackend.php @@ -51,7 +51,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); assert(is_string($manageToken)); $organizationId = $input->getArgument(self::ARGUMENT_ORGANIZATION_ID); - assert(is_int($organizationId) || is_string($organizationId)); + assert(is_string($organizationId)); + $organizationId = is_numeric($organizationId) ? (int) $organizationId : (int) $organizationId; $organizationId = (int) $organizationId; $hostnameSuffix = $input->getArgument(self::ARGUMENT_HOSTNAME_SUFFIX); assert(is_string($hostnameSuffix)); diff --git a/src/Keboola/Console/Command/ProjectsAddFeature.php b/src/Keboola/Console/Command/ProjectsAddFeature.php index 429ce10..9fd85f9 100644 --- a/src/Keboola/Console/Command/ProjectsAddFeature.php +++ b/src/Keboola/Console/Command/ProjectsAddFeature.php @@ -53,7 +53,8 @@ protected function createClient(string $host, string $token): Client protected function addFeatureToProject(Client $client, OutputInterface $output, array $projectInfo, string $featureName, bool $force): void { $projectId = $projectInfo['id']; - assert(is_int($projectId) || is_string($projectId)); + assert(is_string($projectId)); + $projectId = is_numeric($projectId) ? (int) $projectId : (int) $projectId; $output->writeln("Adding feature to project " . $projectId); // Disabled projects diff --git a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php index 52fd5f6..cf5a610 100644 --- a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php +++ b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php @@ -45,7 +45,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $manageToken = $input->getArgument('manageToken'); assert(is_string($manageToken)); $organizationId = $input->getArgument('organizationId'); - assert(is_int($organizationId) || is_string($organizationId)); + assert(is_string($organizationId)); + $organizationId = is_numeric($organizationId) ? (int) $organizationId : (int) $organizationId; $organizationId = (int) $organizationId; $userEmail = $input->getArgument('userEmail'); assert(is_string($userEmail)); diff --git a/src/Keboola/Console/Command/SetDataRetention.php b/src/Keboola/Console/Command/SetDataRetention.php index b0ace41..185c6c3 100644 --- a/src/Keboola/Console/Command/SetDataRetention.php +++ b/src/Keboola/Console/Command/SetDataRetention.php @@ -64,7 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @param array $header + * @param array $header */ private function validateHeader(array $header): void { From 88c11453221868b2f421d20fc3c10f486569fd78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 16:11:52 +0100 Subject: [PATCH 13/18] DMD-916 - PHPStan fixes 12 --- .../Console/Command/AllStacksIterator.php | 6 +++- .../Command/DeleteOwnerlessWorkspaces.php | 7 ++-- .../Console/Command/LineageEventsExport.php | 36 ++++++++++++++----- .../Command/MassDeleteProjectWorkspaces.php | 20 +++++++---- .../MassProjectEnableDynamicBackends.php | 4 +++ .../Console/Command/NotifyProjects.php | 1 + .../Console/Command/ProjectsAddFeature.php | 8 +++-- .../RemoveUserFromOrganizationProjects.php | 4 ++- .../Console/Command/UpdateDataRetention.php | 12 +++++-- 9 files changed, 74 insertions(+), 24 deletions(-) diff --git a/src/Keboola/Console/Command/AllStacksIterator.php b/src/Keboola/Console/Command/AllStacksIterator.php index 5c0eac5..b8b68b1 100644 --- a/src/Keboola/Console/Command/AllStacksIterator.php +++ b/src/Keboola/Console/Command/AllStacksIterator.php @@ -73,7 +73,11 @@ public function execute(InputInterface $input, OutputInterface $output): ?int } // build input for the target command. It adds token and host from http-client files. - $inputForThisStack = sprintf('%s %s %s %s', $commandName, (string) $token['manageToken'], (string) $stack['host'], $cmndInput); + $manageToken = $token['manageToken']; + $stackHost = $stack['host']; + assert(is_string($manageToken)); + assert(is_string($stackHost)); + $inputForThisStack = sprintf('%s %s %s %s', $commandName, $manageToken, $stackHost, $cmndInput); $cmdInput = new StringInput($inputForThisStack); // confirm from the user diff --git a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php index 068c9ff..c30c462 100644 --- a/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php +++ b/src/Keboola/Console/Command/DeleteOwnerlessWorkspaces.php @@ -76,8 +76,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var Sandbox $sandbox */ foreach ($sandboxes as $sandbox) { try { - $tokensClient->getToken($sandbox->getTokenId()); - continue; // token exists so no need to do anything + $tokenId = $sandbox->getTokenId(); + if ($tokenId !== null) { + $tokensClient->getToken((int) $tokenId); + continue; // token exists so no need to do anything + } } catch (\Throwable $exception) { if ($exception->getCode() !== 404) { throw $exception; diff --git a/src/Keboola/Console/Command/LineageEventsExport.php b/src/Keboola/Console/Command/LineageEventsExport.php index d4c7bd7..3e2ec75 100644 --- a/src/Keboola/Console/Command/LineageEventsExport.php +++ b/src/Keboola/Console/Command/LineageEventsExport.php @@ -97,26 +97,42 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(sprintf('There are %s jobs to export.', count($jobsToExport))); foreach (array_reverse($jobsToExport) as $job) { - if (!isset($job['result']['input'])) { - $output->writeln(sprintf('Skipping older job "%s" without I/O in the result.', $job['id'])); + assert(is_array($job)); + $result = $job['result'] ?? null; + if (!is_array($result) || !isset($result['input'])) { + $jobId = $job['id']; + assert(is_string($jobId) || is_int($jobId)); + $output->writeln(sprintf('Skipping older job "%s" without I/O in the result.', $jobId)); continue; } - $output->writeln(sprintf('Job %s export to Marquez - start', $job['id'])); + $jobId = $job['id']; + assert(is_string($jobId) || is_int($jobId)); + $output->writeln(sprintf('Job %s export to Marquez - start', $jobId)); - $lineAgeResponse = $queueClient->request('GET', sprintf('/jobs/%s/open-api-lineage', $job['id'])); + $lineAgeResponse = $queueClient->request('GET', sprintf('/jobs/%s/open-api-lineage', $jobId)); foreach ($this->decodeResponse($lineAgeResponse) as $event) { + assert(is_array($event)); if ($input->getOption('job-names-configurations')) { - $event['job']['name'] = sprintf('%s-%s', $job['component'], $job['config']); + $jobComponent = $job['component']; + $jobConfig = $job['config']; + assert(is_string($jobComponent) || is_int($jobComponent)); + assert(is_string($jobConfig) || is_int($jobConfig)); + $eventJob = $event['job']; + assert(is_array($eventJob)); + $eventJob['name'] = sprintf('%s-%s', $jobComponent, $jobConfig); + $event['job'] = $eventJob; } - $output->writeln(sprintf('- Sending %s event', $event['eventType'])); + $eventType = $event['eventType']; + assert(is_string($eventType)); + $output->writeln(sprintf('- Sending %s event', $eventType)); $marquezClient->request('POST', '/api/v1/lineage', [ 'body' => json_encode($event), ]); } - $output->writeln(sprintf('Job %s export to Marquez - end', $job['id'])); + $output->writeln(sprintf('Job %s export to Marquez - end', $jobId)); } $output->writeln('Done'); @@ -125,10 +141,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @return array + * @return array */ private function decodeResponse(ResponseInterface $response): array { - return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + $decoded = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + assert(is_array($decoded)); + return $decoded; } } diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index 2000545..29aff45 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -56,20 +56,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int $map = []; $csv = new CsvFile($sourceFile); foreach ($csv as $line) { + assert(is_array($line)); if (count($line) !== 2) { throw new InvalidArgumentException('File must contain exactly two columns.'); } - if (!is_numeric($line[0])) { - throw new InvalidArgumentException(sprintf('Project id "%s" is not numeric.', $line[0])); + $projectId = $line[0]; + $workspaceSchema = $line[1]; + assert(is_string($projectId) || is_numeric($projectId)); + assert(is_string($workspaceSchema)); + if (!is_numeric($projectId)) { + throw new InvalidArgumentException(sprintf('Project id "%s" is not numeric.', $projectId)); } - if (!str_starts_with($line[1], 'WORKSPACE_')) { - throw new InvalidArgumentException(sprintf('Workspace "%s" does not start with "WORKSPACE_".', $line[1])); + if (!str_starts_with($workspaceSchema, 'WORKSPACE_')) { + throw new InvalidArgumentException(sprintf('Workspace "%s" does not start with "WORKSPACE_".', $workspaceSchema)); } - if (array_key_exists($line[0], $map)) { - $map[$line[0]][] = $line[1]; + $projectIdStr = (string) $projectId; + if (array_key_exists($projectIdStr, $map)) { + $map[$projectIdStr][] = $workspaceSchema; } else { - $map[$line[0]] = [$line[1]]; + $map[$projectIdStr] = [$workspaceSchema]; } } diff --git a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php index a44f13c..d35926d 100644 --- a/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php +++ b/src/Keboola/Console/Command/MassProjectEnableDynamicBackends.php @@ -39,8 +39,11 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { $manageToken = $input->getArgument(self::ARGUMENT_MANAGE_TOKEN); + assert(is_string($manageToken)); $kbcUrl = $input->getArgument(self::ARGUMENT_CONNECTION_URL); + assert(is_string($kbcUrl)); $sourceFile = $input->getArgument(self::ARGUMENT_SOURCE_FILE); + assert(is_string($sourceFile)); $output->writeln(sprintf('Fetching projects from "%s"', $sourceFile)); $forceNT = $input->getOption(self::OPTION_FORCE_NEW_TRANSFORMATION); @@ -69,6 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(sprintf(' - Feature "%s" is missing for project "%s".', self::FEATURE_NEW_TRANSFORMATIONS_ONLY, $projectId)); // don't ask when force option is on if (!$forceNT) { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->getHelper('question'); $question = new ConfirmationQuestion( ' - Do you want to add this feature (y/n)?', diff --git a/src/Keboola/Console/Command/NotifyProjects.php b/src/Keboola/Console/Command/NotifyProjects.php index 46090e5..e8f30c4 100644 --- a/src/Keboola/Console/Command/NotifyProjects.php +++ b/src/Keboola/Console/Command/NotifyProjects.php @@ -79,6 +79,7 @@ private function notifyProject( { $output->writeln("Sending notification to project $projectId"); + // @phpstan-ignore-next-line method.notFound $client->addNotification([ 'type' => 'common', 'projectId' => (int) $projectId, diff --git a/src/Keboola/Console/Command/ProjectsAddFeature.php b/src/Keboola/Console/Command/ProjectsAddFeature.php index 9fd85f9..3c14df2 100644 --- a/src/Keboola/Console/Command/ProjectsAddFeature.php +++ b/src/Keboola/Console/Command/ProjectsAddFeature.php @@ -59,7 +59,9 @@ protected function addFeatureToProject(Client $client, OutputInterface $output, // Disabled projects if (isset($projectInfo["isDisabled"]) && $projectInfo["isDisabled"]) { - $disabledReason = $projectInfo["disabled"]["reason"]; + $disabled = $projectInfo["disabled"]; + assert(is_array($disabled)); + $disabledReason = $disabled["reason"]; assert(is_string($disabledReason)); $output->writeln(" - project disabled: " . $disabledReason); $this->projectsDisabled++; @@ -74,7 +76,9 @@ protected function addFeatureToProject(Client $client, OutputInterface $output, $client->addProjectFeature($projectInfo['id'], $featureName); $output->writeln(" - feature '{$featureName}' successfully added."); } else { - $output->writeln(sprintf(' - feature "%s" DOES NOT exist in the project %s yet. Enable force mode with -f option', $featureName, $projectInfo['id'])); + $projectIdForDisplay = $projectInfo['id']; + assert(is_string($projectIdForDisplay) || is_int($projectIdForDisplay)); + $output->writeln(sprintf(' - feature "%s" DOES NOT exist in the project %s yet. Enable force mode with -f option', $featureName, $projectIdForDisplay)); } $this->projectsUpdated++; } diff --git a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php index cf5a610..56c9ac8 100644 --- a/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php +++ b/src/Keboola/Console/Command/RemoveUserFromOrganizationProjects.php @@ -108,7 +108,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function isUserInProject(int $userId, array $projectUsers): bool { foreach ($projectUsers as $projectUser) { - if ((int) $projectUser['id'] === $userId) { + $projectUserId = $projectUser['id']; + assert(is_int($projectUserId) || is_string($projectUserId) || is_numeric($projectUserId)); + if ((int) $projectUserId === $userId) { return true; } } diff --git a/src/Keboola/Console/Command/UpdateDataRetention.php b/src/Keboola/Console/Command/UpdateDataRetention.php index 41b36fd..eaa4246 100644 --- a/src/Keboola/Console/Command/UpdateDataRetention.php +++ b/src/Keboola/Console/Command/UpdateDataRetention.php @@ -53,11 +53,19 @@ protected function updateProjectDataRetention( int $dataRetentionTimeInDays, bool $force ): void { - $output->writeln(sprintf('Updating data retention for project "%s" ("%s")', $projectInfo['id'], $projectInfo['name'])); + $projectId = $projectInfo['id']; + $projectName = $projectInfo['name']; + assert(is_string($projectId) || is_int($projectId)); + assert(is_string($projectName)); + $output->writeln(sprintf('Updating data retention for project "%s" ("%s")', $projectId, $projectName)); // Disabled projects if (isset($projectInfo["isDisabled"]) && $projectInfo["isDisabled"]) { - $output->writeln(sprintf(' - project disabled: "%s"', $projectInfo["disabled"]["reason"])); + $disabled = $projectInfo["disabled"]; + assert(is_array($disabled)); + $disabledReason = $disabled["reason"]; + assert(is_string($disabledReason)); + $output->writeln(sprintf(' - project disabled: "%s"', $disabledReason)); $this->projectsDisabled++; } else { $assignedBackends = $projectInfo['assignedBackends']; From 5b63997b8aa81116010c0678ab3b0e0b435333bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 16:27:43 +0100 Subject: [PATCH 14/18] DMD-916 - PHPStan fixes 13 --- .../Command/MassDeleteProjectWorkspaces.php | 39 +++++++++++++------ .../Command/QueueMassTerminateJobs.php | 9 ++++- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index 29aff45..41174ca 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -112,6 +112,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $branchesClient = new DevBranches($storageClient); + /** + * @var array $jobs + */ $jobs = []; foreach ($branchesClient->listBranches() as $branch) { $output->writeln(sprintf('Checking branch "%s" for sandboxes.', $branch['id'])); @@ -146,12 +149,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int ], )); - $job['sandbox'] = $sandbox; - $jobs[] = $job; + $jobs[] = ['job' => $job, 'sandbox' => $sandbox]; + // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible + $jobId = $job['id']; $output->writeln(sprintf( 'Created delete job "%s" for project "%s"', - $job['id'], + $jobId, $projectId )); } else { @@ -166,15 +170,25 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Waiting for delete jobs to finish.'); while (count($jobs) > 0) { - foreach ($jobs as $i => $job) { - $jobRes = $jobsClient->getJob((string) $job['id']); + foreach ($jobs as $i => $jobData) { + $job = $jobData['job']; + $sandbox = $jobData['sandbox']; + // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible + $jobRes = $jobsClient->getJob($job['id']); + // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible if ($jobRes['isFinished'] === true) { + $workspaceDetails = $sandbox->getWorkspaceDetails(); + $schema = $workspaceDetails['connection']['schema'] ?? 'unknown'; + // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible + $jobId = $job['id']; + // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible + $jobStatus = $jobRes['status']; $output->writeln(sprintf( 'Delete job "%s" for sandbox "%s" with schema "%s" finished with status "%s"', - $job['id'], - $job['sandbox']->getId(), - $job['sandbox']->getWorkspaceDetails()['connection']['schema'], - $jobRes['status'] + $jobId, + $sandbox->getId(), + $schema, + $jobStatus )); unset($jobs[$i]); } @@ -188,13 +202,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $storageClient->getBranchAwareClient($branch['id']) ); foreach ($workspacesClient->listWorkspaces() as $workspace) { - if (!in_array($workspace['connection']['schema'], $map[$projectId], true)) { + $schema = $workspace['connection']['schema'] ?? null; + if ($schema === null || !in_array($schema, $map[$projectId], true)) { continue; } // remove found schema from map - unset($map[$projectId][array_search($workspace['connection']['schema'], $map[$projectId], true)]); + unset($map[$projectId][array_search($schema, $map[$projectId], true)]); if ($force) { - $output->writeln(sprintf('Deleting workspace "%s" with schema "%s"', $workspace['id'], $workspace['connection']['schema'])); + $output->writeln(sprintf('Deleting workspace "%s" with schema "%s"', $workspace['id'], $schema)); $workspacesClient->deleteWorkspace($workspace['id'], [], true); } else { $output->writeln(sprintf('[DRY-RUN] Deleting workspace "%s" with schema "%s"', $workspace['id'], $workspace['connection']['schema'])); diff --git a/src/Keboola/Console/Command/QueueMassTerminateJobs.php b/src/Keboola/Console/Command/QueueMassTerminateJobs.php index 5c17b05..3c628a6 100644 --- a/src/Keboola/Console/Command/QueueMassTerminateJobs.php +++ b/src/Keboola/Console/Command/QueueMassTerminateJobs.php @@ -6,6 +6,7 @@ use Exception; use Keboola\JobQueueClient\Client as JobQueueClient; +use Keboola\JobQueueClient\JobStatuses; use Keboola\JobQueueClient\ListJobsOptions; use Keboola\StorageApi\Client as StorageClient; use Symfony\Component\Console\Command\Command; @@ -66,9 +67,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int $storageToken ); + $statusEnum = match($status) { + 'created' => JobStatuses::CREATED, + 'waiting' => JobStatuses::WAITING, + default => JobStatuses::PROCESSING, + }; + $jobs = $jobQueueClient->listJobs( (new ListJobsOptions()) - ->setStatuses([$status]) + ->setStatuses([$statusEnum]) ->setLimit(3000) ); From 34d96843bfc1a4ff82fde77fe55908f9b5f3a925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 16:32:35 +0100 Subject: [PATCH 15/18] DMD-916 - PHPStan fixes 14 --- .../Command/MassDeleteProjectWorkspaces.php | 27 +++++++++---------- .../Console/Command/NotifyProjects.php | 15 ++++++----- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php index 41174ca..d88a310 100644 --- a/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php +++ b/src/Keboola/Console/Command/MassDeleteProjectWorkspaces.php @@ -113,7 +113,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $branchesClient = new DevBranches($storageClient); /** - * @var array $jobs + * @var array}, sandbox: \Keboola\Sandboxes\Api\Sandbox}> $jobs */ $jobs = []; foreach ($branchesClient->listBranches() as $branch) { @@ -149,13 +149,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int ], )); - $jobs[] = ['job' => $job, 'sandbox' => $sandbox]; + /** + * @var array{id: string|int, ...} $jobArray + */ + $jobArray = (array) $job; + $jobs[] = ['job' => $jobArray, 'sandbox' => $sandbox]; - // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible - $jobId = $job['id']; $output->writeln(sprintf( 'Created delete job "%s" for project "%s"', - $jobId, + $jobArray['id'], $projectId )); } else { @@ -173,22 +175,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($jobs as $i => $jobData) { $job = $jobData['job']; $sandbox = $jobData['sandbox']; - // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible - $jobRes = $jobsClient->getJob($job['id']); - // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible + /** + * @var array{id: string|int, isFinished: bool, status: string, ...} $jobRes + */ + $jobRes = (array) $jobsClient->getJob((string) $job['id']); if ($jobRes['isFinished'] === true) { $workspaceDetails = $sandbox->getWorkspaceDetails(); $schema = $workspaceDetails['connection']['schema'] ?? 'unknown'; - // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible - $jobId = $job['id']; - // @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible - $jobStatus = $jobRes['status']; $output->writeln(sprintf( 'Delete job "%s" for sandbox "%s" with schema "%s" finished with status "%s"', - $jobId, + $job['id'], $sandbox->getId(), $schema, - $jobStatus + $jobRes['status'] )); unset($jobs[$i]); } diff --git a/src/Keboola/Console/Command/NotifyProjects.php b/src/Keboola/Console/Command/NotifyProjects.php index e8f30c4..cd72fec 100644 --- a/src/Keboola/Console/Command/NotifyProjects.php +++ b/src/Keboola/Console/Command/NotifyProjects.php @@ -79,12 +79,13 @@ private function notifyProject( { $output->writeln("Sending notification to project $projectId"); - // @phpstan-ignore-next-line method.notFound - $client->addNotification([ - 'type' => 'common', - 'projectId' => (int) $projectId, - 'title' => $notificationTitle, - 'message' => $notificationMessage, - ]); + if (method_exists($client, 'addNotification')) { + $client->addNotification([ + 'type' => 'common', + 'projectId' => (int) $projectId, + 'title' => $notificationTitle, + 'message' => $notificationMessage, + ]); + } } } From 9b31fafc7e3ade04b67ef33743088ce5a57a746b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Mon, 8 Dec 2025 16:41:02 +0100 Subject: [PATCH 16/18] DMD-916 - PHPStan CI --- .github/workflows/build.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e13dfb8..7cc4fa1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,9 +19,12 @@ jobs: - name: Build container run: docker compose build app - - name: Run tests + - name: Check codestyle run: docker compose run --rm app ./vendor/bin/phpcs --standard=psr2 --ignore=vendor -n . + - name: Static analysis + run: docker compose run --rm app composer phpstan + - name: Push if: startsWith(github.ref, 'refs/tags/') run: | From 812d14205feca0d7969b38d1e3081fe4590868d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Wed, 10 Dec 2025 12:35:37 +0100 Subject: [PATCH 17/18] DMD-916 - add phpcbf and fix codestyle --- composer.json | 3 ++- src/Keboola/Console/Command/NotifyProjects.php | 3 +-- src/Keboola/Console/Command/QueueMassTerminateJobs.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index c80a531..4dd9c34 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ }, "scripts": { "phpstan": "phpstan analyse --memory-limit=512M", - "phpcs": "phpcs --standard=PSR2 -n src/" + "phpcs": "phpcs --standard=PSR2 -n src/", + "phpcbf": "phpcbf --standard=PSR2 -n src/" } } diff --git a/src/Keboola/Console/Command/NotifyProjects.php b/src/Keboola/Console/Command/NotifyProjects.php index cd72fec..77d9978 100644 --- a/src/Keboola/Console/Command/NotifyProjects.php +++ b/src/Keboola/Console/Command/NotifyProjects.php @@ -75,8 +75,7 @@ private function notifyProject( int $projectId, string $notificationTitle, string $notificationMessage - ): void - { + ): void { $output->writeln("Sending notification to project $projectId"); if (method_exists($client, 'addNotification')) { diff --git a/src/Keboola/Console/Command/QueueMassTerminateJobs.php b/src/Keboola/Console/Command/QueueMassTerminateJobs.php index 3c628a6..5f10ee0 100644 --- a/src/Keboola/Console/Command/QueueMassTerminateJobs.php +++ b/src/Keboola/Console/Command/QueueMassTerminateJobs.php @@ -67,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $storageToken ); - $statusEnum = match($status) { + $statusEnum = match ($status) { 'created' => JobStatuses::CREATED, 'waiting' => JobStatuses::WAITING, default => JobStatuses::PROCESSING, From d84fbbca17287bf11b570a2ea81ac8e79c95ad4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Biberle?= Date: Wed, 10 Dec 2025 12:47:39 +0100 Subject: [PATCH 18/18] DMD-916 - improve running jobs message for OrganizationIntoMaintenanceMode --- .../OrganizationIntoMaintenanceMode.php | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php index 09fb98f..229b74d 100644 --- a/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php +++ b/src/Keboola/Console/Command/OrganizationIntoMaintenanceMode.php @@ -176,17 +176,29 @@ private function areThereRunningJobs( JobStatuses::TERMINATING ]); $runningJobs = $jobsClient->listJobs($runningJobsListOptions); - $output->writeln( - sprintf( - 'Found %d running jobs. Please terminate them and then re-run', - count($runningJobs) - ) - ); - foreach ($runningJobs as $runningJob) { + $runningJobsCount = count($runningJobs); + + if ($runningJobsCount > 1) { $output->writeln( - $runningJob['url'] . ' is ' . $runningJob['status'] + sprintf( + 'Found %d running jobs. Please terminate them and then re-run', + $runningJobsCount, + ) + ); + foreach ($runningJobs as $runningJob) { + $output->writeln( + $runningJob['url'] . ' is ' . $runningJob['status'] + ); + } + } else { + $output->writeln( + sprintf( + 'Found %d running jobs.', + $runningJobsCount, + ) ); } + // drop the token $tokensClient = new Tokens( new StorageClient([ @@ -196,6 +208,6 @@ private function areThereRunningJobs( ); $tokensClient->dropToken($storageToken['id']); - return count($runningJobs) > 0; + return $runningJobsCount > 0; } }