Hello community, here is the log from the commit of package platformsh-cli for openSUSE:Factory checked in at 2020-08-14 10:05:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/platformsh-cli (Old) and /work/SRC/openSUSE:Factory/.platformsh-cli.new.3399 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "platformsh-cli" Fri Aug 14 10:05:11 2020 rev:103 rq:826562 version:3.61.0 Changes: -------- --- /work/SRC/openSUSE:Factory/platformsh-cli/platformsh-cli.changes 2020-07-24 09:59:45.685632153 +0200 +++ /work/SRC/openSUSE:Factory/.platformsh-cli.new.3399/platformsh-cli.changes 2020-08-14 10:05:34.221355028 +0200 @@ -1,0 +2,41 @@ +Thu Aug 13 18:32:04 UTC 2020 - [email protected] + +- Update to version 3.61.0: + * Release v3.61.0 + * Add proxy support to the installer (#953) + * Provide autocomplete choice of projects if there are many + * Use --globoff by default in curl commands + * Disable use of SSH agent unless explicitly enabled in the user config file + * Explicitly request the offline_access scope when logging in + * Add a comment to auto-generated temporary SSH keys (#970) + * Release v3.60.4 + * Restore PLATFORMSH_CLI_ACCOUNTS_API environment variable option for backwards compatibility + * Release v3.60.3 + * Fix locating projects that are not in the user's list + * Release v3.60.2 + * Ignore project-level variable activities (#968) + * Detect the SSH version and show appropriate messages (#969) + * Refine --no-refresh help in the ssh-cert:info command + * Fix extra prompt for input in mongodump command + * Add hidden ssh-cert:info utility/debug command + * Revamp config to no longer use a service.accounts_url nor an api.accounts_api_url + * Fix Match syntax with multiple hosts + * Release v3.60.1 + * Add the SSH certificate to the SSH agent, if possible (#963) + * Clean up the "domain already claimed" error in the domain:add command + * Put host condition back on "Match ... exec" + * Use ed25519 for autogenerated SSH keys + * Always create a new SSH key when generating a certificate + * README: add extensions needed by cli (#958) + * Release v3.60.0 + * The root SSH config file should have a Host line too + * Add comments to the session-specific SSH config + * Support multiple domain wildcards in SSH config + * SSH config: end the Host block explicitly with "Host *" (#960) + * Autoload SSH certificates on login or SSH + * Show the ssh-cert:load command in the list + * Avoid unnecessary SSH messages in verbose mode + * Installer: allow iconv as an alternative to mbstring + * Fix detection of internal vs external Git domains for platform:get (get) command errors + +------------------------------------------------------------------- Old: ---- platformsh-cli-3.59.1.tar.xz New: ---- platformsh-cli-3.61.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ platformsh-cli.spec ++++++ --- /var/tmp/diff_new_pack.RZFlIo/_old 2020-08-14 10:05:36.465356165 +0200 +++ /var/tmp/diff_new_pack.RZFlIo/_new 2020-08-14 10:05:36.465356165 +0200 @@ -17,7 +17,7 @@ Name: platformsh-cli -Version: 3.59.1 +Version: 3.61.0 Release: 0 Summary: Tool for managing Platform.sh services from the command line # See licenses.txt for dependency licenses. ++++++ _service ++++++ --- /var/tmp/diff_new_pack.RZFlIo/_old 2020-08-14 10:05:36.497356182 +0200 +++ /var/tmp/diff_new_pack.RZFlIo/_new 2020-08-14 10:05:36.497356182 +0200 @@ -2,7 +2,7 @@ <service name="tar_scm" mode="disabled"> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> - <param name="revision">refs/tags/v3.59.1</param> + <param name="revision">refs/tags/v3.61.0</param> <param name="url">git://github.com/platformsh/platformsh-cli.git</param> <param name="scm">git</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.RZFlIo/_old 2020-08-14 10:05:36.517356192 +0200 +++ /var/tmp/diff_new_pack.RZFlIo/_new 2020-08-14 10:05:36.517356192 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">git://github.com/platformsh/platformsh-cli.git</param> - <param name="changesrevision">3854cfd67303add357cece3c5f330ac9e443bf33</param> + <param name="changesrevision">8e6262a3ef2ec7b2b16a6675fb34fc2300489f46</param> </service> </servicedata> ++++++ licenses.txt ++++++ --- /var/tmp/diff_new_pack.RZFlIo/_old 2020-08-14 10:05:36.557356212 +0200 +++ /var/tmp/diff_new_pack.RZFlIo/_new 2020-08-14 10:05:36.557356212 +0200 @@ -16,7 +16,7 @@ padraic/phar-updater v1.0.6 BSD-3-Clause paragonie/random_compat v2.0.18 MIT pjcdawkins/guzzle-oauth2-plugin v2.3.1 MIT -platformsh/client v0.35.2 MIT +platformsh/client v0.37.2 MIT platformsh/console-form v0.0.24 MIT psr/container 1.0.0 MIT psr/log 1.1.3 MIT ++++++ platformsh-cli-3.59.1.tar.xz -> platformsh-cli-3.61.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/README.md new/platformsh-cli-3.61.0/README.md --- old/platformsh-cli-3.59.1/README.md 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/README.md 2020-08-13 13:27:12.000000000 +0200 @@ -5,7 +5,8 @@ ## Requirements * Operating system: Linux, OS X, or Windows 10 -* PHP 5.5.9 or higher, with cURL support +* PHP 5.5.9 or higher, with the following extensions: `curl`, `json`, + `mbstring`, `pcre`, and `phar`. The installation command will check for these. * Git * A Bash-like shell: * On OS X or Linux/Unix: SH, Bash, Dash or ZSH - usually the built-in shell will work. @@ -182,6 +183,8 @@ service:mongo:restore (mongorestore) Restore a binary archive dump of data into MongoDB service:mongo:shell (mongo) Use the MongoDB shell service:redis-cli (redis) Access the Redis CLI +ssh-cert + ssh-cert:load Generate an SSH certificate ssh-key ssh-key:add Add a new SSH key ssh-key:delete Delete an SSH key diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/composer.json new/platformsh-cli-3.61.0/composer.json --- old/platformsh-cli-3.59.1/composer.json 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/composer.json 2020-08-13 13:27:12.000000000 +0200 @@ -8,7 +8,7 @@ "guzzlehttp/guzzle": "^5.3", "guzzlehttp/ringphp": "^1.1", "platformsh/console-form": ">=0.0.24 <2.0", - "platformsh/client": ">=0.35.2 <2.0", + "platformsh/client": ">=0.37.2 <2.0", "symfony/console": "^3.0 >=3.2", "symfony/yaml": "^3.0 || ^2.6", "symfony/finder": "^3.0", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/composer.lock new/platformsh-cli-3.61.0/composer.lock --- old/platformsh-cli-3.59.1/composer.lock 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/composer.lock 2020-08-13 13:27:12.000000000 +0200 @@ -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": "34395bedceaf33b0af253532c389a178", + "content-hash": "622a98f40b55807f6d8bb25538914875", "packages": [ { "name": "cocur/slugify", @@ -665,16 +665,16 @@ }, { "name": "platformsh/client", - "version": "v0.35.2", + "version": "v0.37.2", "source": { "type": "git", "url": "https://github.com/platformsh/platformsh-client-php.git", - "reference": "ce7f949dc6788ee90b8172d991845a5a66788531" + "reference": "2f63f43d16faa370895f90f1e6febd5519e2664d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/ce7f949dc6788ee90b8172d991845a5a66788531", - "reference": "ce7f949dc6788ee90b8172d991845a5a66788531", + "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/2f63f43d16faa370895f90f1e6febd5519e2664d", + "reference": "2f63f43d16faa370895f90f1e6febd5519e2664d", "shasum": "" }, "require": { @@ -704,7 +704,7 @@ } ], "description": "Platform.sh API client", - "time": "2020-07-21T07:54:05+00:00" + "time": "2020-08-05T09:08:05+00:00" }, { "name": "platformsh/console-form", @@ -3448,5 +3448,6 @@ "platform-dev": [], "platform-overrides": { "php": "5.5.9" - } + }, + "plugin-api-version": "1.1.0" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/config.yaml new/platformsh-cli-3.61.0/config.yaml --- old/platformsh-cli-3.59.1/config.yaml 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/config.yaml 2020-08-13 13:27:12.000000000 +0200 @@ -85,7 +85,10 @@ applications_config_file: '.platform/applications.yaml' docs_url: 'https://docs.platform.sh' docs_search_url: 'https://www.google.com/search?q=site%3Adocs.platform.sh%20{{ terms }}' - accounts_url: 'https://accounts.platform.sh' + api_tokens_url: 'https://accounts.platform.sh/user/api-tokens' + register_url: 'https://auth.api.platform.sh' + reset_password_url: 'https://auth.api.platform.sh/reset-password' + console_url: 'https://console.platform.sh' pricing_url: 'https://platform.sh/pricing' api_token_help_url: 'https://docs.platform.sh/gettingstarted/cli/api-tokens.html' available_regions: @@ -113,9 +116,6 @@ # Overridden by {application.env_prefix}API_URL env var. base_url: 'https://api.platform.sh/' - # Overridden by {application.env_prefix}ACCOUNTS_API env var. - accounts_api_url: 'https://accounts.platform.sh/api/v1/' - # Overridden by {application.env_prefix}OAUTH2_AUTH_URL env var. oauth2_auth_url: 'https://auth.api.platform.sh/oauth2/authorize' @@ -141,13 +141,17 @@ # Overridden by {application.env_prefix}CERTIFIER_URL env var. certifier_url: 'https://ssh.api.platform.sh' - # Wildcard domain for SSH configuration. - # Overridden by {application.env_prefix}SSH_DOMAIN_WILDCARD env var. - ssh_domain_wildcard: '*.platform.sh' + # Wildcard domains for SSH configuration. + # Can be replaced by a single value using the {application.env_prefix}SSH_DOMAIN_WILDCARD env var. + ssh_domain_wildcards: ['*.platform.sh'] + + # Whether auto-generated SSH certificate identities should be added to the SSH agent. + # Enabling this may be useful for those who use agent forwarding. + add_to_ssh_agent: false # Whether to auto-load an SSH certificate on login and SSH commands. # Overridden by {application.env_prefix}AUTO_LOAD_SSH_CERT env var - auto_load_ssh_cert: false + auto_load_ssh_cert: true # Whether the API supports project invitations. invitations: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/dist/installer.php new/platformsh-cli-3.61.0/dist/installer.php --- old/platformsh-cli-3.59.1/dist/installer.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/dist/installer.php 2020-08-13 13:27:12.000000000 +0200 @@ -52,6 +52,7 @@ private $configDir; private $executable; private $cliName; + private $userAgent; private $pharName; private $argv; @@ -69,6 +70,12 @@ $this->configDir = '.platformsh'; $this->executable = 'platform'; $this->cliName = 'Platform.sh CLI'; + $this->userAgent = sprintf( + 'platformsh-cli-installer (%s; %s; PHP %s)', + php_uname('s'), + php_uname('r'), + PHP_VERSION + ); $this->pharName = $this->executable . '.phar'; } @@ -120,8 +127,6 @@ ); $required_extensions = [ - // Either mbstring or iconv is required by Symfony Console (even though this is not enforced in its composer.json). - 'mbstring', 'openssl', 'pcre', ]; @@ -135,6 +140,15 @@ ); } + // Either mbstring or iconv is required by Symfony Console (even though this is not enforced in its composer.json). + $this->check( + 'One or both of the "mbstring" or "iconv" PHP extensions is installed.', + 'One or both of the "mbstring" or "iconv" PHP extensions is required.', + function () { + return \extension_loaded('mbstring') || \extension_loaded('iconv'); + } + ); + $this->check( 'The "curl" PHP extension is installed.', 'The "curl" PHP extension is strongly recommended.', @@ -212,7 +226,7 @@ $url = str_replace($removePath, '/' . ltrim($url, '/'), $this->manifestUrl); } - if (!file_put_contents($this->pharName, file_get_contents($url))) { + if (!file_put_contents($this->pharName, file_get_contents($url, false, $this->getStreamContext(300)))) { return TaskResult::failure('The download failed'); } @@ -301,7 +315,7 @@ * @return TaskResult */ private function findLatestVersion($manifestUrl) { - $manifest = file_get_contents($manifestUrl); + $manifest = file_get_contents($manifestUrl, false, $this->getStreamContext(15)); if ($manifest === false) { return TaskResult::failure('Failed to download manifest file: ' . $manifestUrl); } @@ -521,6 +535,48 @@ return false; } + + /** + * Constructs a stream context for downloading files. + * + * @param int $timeout + * + * @return resource + */ + private function getStreamContext($timeout) { + $opts = [ + 'http' => [ + 'method' => 'GET', + 'follow_location' => 1, + 'timeout' => $timeout, + 'user_agent' => $this->userAgent, + ], + ]; + if ($proxy = $this->getProxy()) { + $opts['http']['proxy'] = $proxy; + } + + return stream_context_create($opts); + } + + /** + * Finds a proxy address based on the https_proxy or http_proxy environment variable. + * + * @return string|null + */ + private function getProxy() { + // The proxy variables should be ignored in a non-CLI context. + // This check has probably already been run, but it's important. + if (PHP_SAPI !== 'cli') { + return null; + } + foreach (['https', 'http'] as $scheme) { + if ($proxy = getenv($scheme . '_proxy')) { + return $proxy; + } + } + return null; + } } class TaskResult { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/dist/manifest.json new/platformsh-cli-3.61.0/dist/manifest.json --- old/platformsh-cli-3.59.1/dist/manifest.json 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/dist/manifest.json 2020-08-13 13:27:12.000000000 +0200 @@ -17,10 +17,10 @@ }, { "name": "platform.phar", - "sha1": "be6d7919c5a776dbcbdc7887c47d27651f5f49f3", - "sha256": "cd367d27beb28c7ee9852c5fbe5ae808f1dbff448d1ff8fed7ceab9a792b6839", - "url": "https://github.com/platformsh/platformsh-cli/releases/download/v3.59.1/platform.phar", - "version": "3.59.1", + "sha1": "93f8a19ff692c19e60e07494a484ea613affef24", + "sha256": "6f5114c2a27a3d4631d857cf938eda184bbb431568bfbf006958a6dd3526323f", + "url": "https://github.com/platformsh/platformsh-cli/releases/download/v3.61.0/platform.phar", + "version": "3.61.0", "php": { "min": "5.5.9" }, @@ -284,6 +284,16 @@ "notes": "* Support new user invitations\n* Installer improvements:\n - Create a .zshrc file if it doesn't already exist, on ZSH.\n - Require the mbstring extension.", "show from": "3.58.0", "hide from": "3.59.0" + }, + { + "notes": "New features:\n\n* Autoload SSH certificates on login or SSH, by default.\n When supported by all relevant servers (soon), SSH certificates will:\n - Enable support for multi-factor authentication on projects that require it for SSH access.\n - Make it no longer necessary to add or administer SSH keys manually.\n\n Note: SSH certificates can be checked or reloaded using the `ssh-cert:load` command.\n\nOther changes:\n\n* SSH configuration improvements:\n - Denote top-level SSH configuration with a `Host` block, to avoid issues where a previous `Host` or `Match` configuration had not been closed.\n - End the Host blocks explicitly with `Host *`\n* Fix detection of third-party Git domains for `project:get` (`get`) command errors.", + "show from": "3.59.0", + "hide from": "3.60.0" + }, + { + "notes": "New features:\n\n* Project choice will now be via an autocomplete, rather than a list, if there\n are too many to list (more than 25, or more than the terminal height). Start\n typing a project ID, or use the up and down arrows to scroll through the\n autocomplete suggestions.\n* Similarly, the environment choice can now be via a list, if there are few\n enough to display like that, otherwise the older autocomplete will show.\n* The installer now supports a proxy defined with one of the `https_proxy` or\n `http_proxy` environment variables.\n\nOther changes:\n\n* Use --globoff by default in curl commands\n* Disable use of the SSH agent unless explicitly enabled in the user config file.\n Add this to the user config file to automatically add keys/certificates to the SSH agent:\n ```yaml\n # ~/.platformsh/config.yaml\n api:\n add_to_ssh_agent: true\n ```\n* Explicitly request the `offline_access` scope when logging in.\n* Add a comment to auto-generated temporary SSH keys.", + "show from": "3.60.0", + "hide from": "3.61.0" } ] } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/resources/oauth-listener/index.php new/platformsh-cli-3.61.0/resources/oauth-listener/index.php --- old/platformsh-cli-3.59.1/resources/oauth-listener/index.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/resources/oauth-listener/index.php 2020-08-13 13:27:12.000000000 +0200 @@ -12,6 +12,7 @@ private $response; private $codeChallenge; private $prompt; + private $scope; public function __construct() { $required = [ @@ -31,6 +32,7 @@ $this->file = $_ENV['CLI_OAUTH_FILE']; $this->prompt = $_ENV['CLI_OAUTH_PROMPT']; $this->codeChallenge = $_ENV['CLI_OAUTH_CODE_CHALLENGE']; + $this->scope = isset($_ENV['CLI_OAUTH_SCOPE']) ? $_ENV['CLI_OAUTH_SCOPE'] : ''; $this->localUrl = $localUrl = 'http://127.0.0.1:' . $_SERVER['SERVER_PORT']; $this->response = new Response(); } @@ -48,6 +50,7 @@ 'response_type' => 'code', 'code_challenge' => $this->codeChallenge, 'code_challenge_method' => 'S256', + 'scope' => $this->scope, ], null, '&', PHP_QUERY_RFC3986); } @@ -185,7 +188,7 @@ } ?> <!DOCTYPE html> -<html> +<html lang="en"> <head> <meta charset="utf-8"> <title><?php echo $response->title; ?></title> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Application.php new/platformsh-cli-3.61.0/src/Application.php --- old/platformsh-cli-3.59.1/src/Application.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Application.php 2020-08-13 13:27:12.000000000 +0200 @@ -196,6 +196,7 @@ $commands[] = new Command\Backup\BackupListCommand(); $commands[] = new Command\Backup\BackupRestoreCommand(); $commands[] = new Command\SourceOperation\RunCommand(); + $commands[] = new Command\SshCert\SshCertInfoCommand(); $commands[] = new Command\SshCert\SshCertLoadCommand(); $commands[] = new Command\SshKey\SshKeyAddCommand(); $commands[] = new Command\SshKey\SshKeyDeleteCommand(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Auth/ApiTokenLoginCommand.php new/platformsh-cli-3.61.0/src/Command/Auth/ApiTokenLoginCommand.php --- old/platformsh-cli-3.59.1/src/Command/Auth/ApiTokenLoginCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Auth/ApiTokenLoginCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -17,7 +17,6 @@ protected function configure() { $service = $this->config()->get('service.name'); - $accountsUrl = $this->config()->get('service.accounts_url'); $executable = $this->config()->get('application.executable'); $this->setName('auth:api-token-login'); @@ -28,9 +27,9 @@ $this->setDescription('Log in to ' . $service . ' using an API token'); $help = 'Use this command to log in to your ' . $service . ' account using an API token.' - . "\n\nYou can create an account at:\n <info>" . $accountsUrl . '</info>' + . "\n\nYou can create an account at:\n <info>" . $this->config()->get('service.register_url') . '</info>' . "\n\nIf you have an account, but you do not already have an API token, you can create one here:\n <info>" - . $accountsUrl . '/user/api-tokens</info>' + . $this->config()->get('service.api_tokens_url') . '</info>' . "\n\nAlternatively, to log in to the CLI with a browser, run:\n <info>" . $executable . ' auth:browser-login</info>'; $this->setHelp($help); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Auth/BrowserLoginCommand.php new/platformsh-cli-3.61.0/src/Command/Auth/BrowserLoginCommand.php --- old/platformsh-cli-3.59.1/src/Command/Auth/BrowserLoginCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Auth/BrowserLoginCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -145,6 +145,7 @@ 'CLI_OAUTH_AUTH_URL' => $this->config()->get('api.oauth2_auth_url'), 'CLI_OAUTH_CLIENT_ID' => $this->config()->get('api.oauth2_client_id'), 'CLI_OAUTH_PROMPT' => $input->getOption('force') ? 'consent select_account' : 'consent', + 'CLI_OAUTH_SCOPE' => 'offline_access', 'CLI_OAUTH_FILE' => $responseFile, ] + $this->getParentEnv()); $process->setTimeout(null); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Auth/PasswordLoginCommand.php new/platformsh-cli-3.61.0/src/Command/Auth/PasswordLoginCommand.php --- old/platformsh-cli-3.59.1/src/Command/Auth/PasswordLoginCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Auth/PasswordLoginCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -15,7 +15,6 @@ protected function configure() { $service = $this->config()->get('service.name'); - $accountsUrl = $this->config()->get('service.accounts_url'); $executable = $this->config()->get('application.executable'); $this->setName('auth:password-login'); @@ -27,9 +26,9 @@ $this->setDescription('Log in to ' . $service . ' using a username and password'); $help = 'Use this command to log in to your ' . $service . ' account in the terminal.' - . "\n\nYou can create an account at:\n <info>" . $accountsUrl . '</info>' + . "\n\nYou can create an account at:\n <info>" . $this->config()->get('service.register_url') . '</info>' . "\n\nIf you have an account, but you do not already have a password, you can set one here:\n <info>" - . $accountsUrl . '/user/password</info>' + . $this->config()->get('service.reset_password_url') . '</info>' . "\n\nAlternatively, to log in to the CLI with a browser, run:\n <info>" . $executable . ' auth:browser-login</info>' . "\n\n" . $this->getNonInteractiveAuthHelp(); @@ -134,7 +133,7 @@ '<error>Login failed. Please check your credentials.</error>', '', "Forgot your password? Or don't have a password yet? Visit:", - ' <comment>' . $this->config()->get('service.accounts_url') . '/user/password</comment>', + ' <comment>' . $this->config()->get('service.reset_password_url') . '</comment>', '', ]); $this->configureAccount($input, $output); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/CommandBase.php new/platformsh-cli-3.61.0/src/Command/CommandBase.php --- old/platformsh-cli-3.59.1/src/Command/CommandBase.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/CommandBase.php 2020-08-13 13:27:12.000000000 +0200 @@ -27,6 +27,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Terminal; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; @@ -533,6 +534,7 @@ $this->debug('Selected environment ' . $this->api()->getEnvironmentLabel($environment) . ' based on branch name: ' . $currentBranch); return $environment; } + $this->debug('No environment was found to match the current Git branch: ' . $currentBranch); } return false; @@ -756,7 +758,7 @@ $this->project = $detectCurrent ? $this->getCurrentProject() : false; if (!$this->project && isset($this->input) && $this->input->isInteractive()) { $projects = $this->api()->getProjects(); - if (count($projects) > 0 && count($projects) < 25) { + if (count($projects) > 0) { $this->debug('No project specified: offering a choice...'); $projectId = $this->offerProjectChoice($projects); @@ -849,6 +851,7 @@ } if ($selectDefaultEnv) { + $this->debug('No environment specified or detected: finding a default...'); $environments = $this->api()->getEnvironments($this->project); $defaultId = $this->api()->getDefaultEnvironmentId($environments); if ($defaultId && isset($environments[$defaultId])) { @@ -858,7 +861,7 @@ } if ($required && isset($this->input) && $this->input->isInteractive()) { - $this->debug('No environment specified: offering a choice...'); + $this->debug('No environment specified or detected: offering a choice...'); $this->environment = $this->offerEnvironmentChoice($this->api()->getEnvironments($this->project)); return; } @@ -1101,35 +1104,52 @@ * @return string * The chosen project ID. */ - protected final function offerProjectChoice(array $projects, $text = 'Enter a number to choose a project:') + final protected function offerProjectChoice(array $projects, $text = 'Enter a number to choose a project:') { if (!isset($this->input) || !isset($this->output) || !$this->input->isInteractive()) { throw new \BadMethodCallException('Not interactive: a project choice cannot be offered.'); } - // Build and sort a list of project options. + /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */ + $questionHelper = $this->getService('question_helper'); + + if (count($projects) >= 25 || count($projects) > (new Terminal())->getHeight() - 3) { + $autocomplete = []; + foreach ($projects as $project) { + if ($project->title) { + $autocomplete[$project->id] = $project->id . ' - <question>' . $project->title . '</question>'; + } else { + $autocomplete[$project->id] = $project->id; + } + } + asort($autocomplete, SORT_NATURAL | SORT_FLAG_CASE); + return $questionHelper->askInput('Enter a project ID', null, array_values($autocomplete), function ($value) use ($autocomplete) { + list($id, ) = explode(' - ', $value); + if (!isset($autocomplete[$id]) && !$this->api()->getProject($id)) { + throw new \RuntimeException('Project not found: ' . $id); + } + return $id; + }); + } + $projectList = []; foreach ($projects as $project) { $projectList[$project->id] = $this->api()->getProjectLabel($project, false); } asort($projectList, SORT_NATURAL | SORT_FLAG_CASE); - /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */ - $questionHelper = $this->getService('question_helper'); - - $id = $questionHelper->choose($projectList, $text, null, false); - - return $id; + return $questionHelper->choose($projectList, $text, null, false); } /** * Offers a choice of environments. * * @param Environment[] $environments + * @param string $text * * @return Environment */ - protected final function offerEnvironmentChoice(array $environments) + final protected function offerEnvironmentChoice(array $environments, $text = 'Enter a number to choose an environment:') { if (!isset($this->input) || !isset($this->output) || !$this->input->isInteractive()) { throw new \BadMethodCallException('Not interactive: an environment choice cannot be offered.'); @@ -1139,19 +1159,30 @@ $questionHelper = $this->getService('question_helper'); $default = $this->api()->getDefaultEnvironmentId($environments); - // Build and sort a list of options (environment IDs). - $ids = array_keys($environments); - sort($ids, SORT_NATURAL | SORT_FLAG_CASE); + if (count($environments) > (new Terminal())->getHeight() / 2) { + $ids = array_keys($environments); + sort($ids, SORT_NATURAL | SORT_FLAG_CASE); + + $id = $questionHelper->askInput('Enter an environment ID', $default, array_keys($environments), function ($value) use ($environments) { + if (!isset($environments[$value])) { + throw new \RuntimeException('Environment not found: ' . $value); + } - $id = $questionHelper->askInput('Environment ID', $default, array_keys($environments), function ($value) use ($environments) { - if (!isset($environments[$value])) { - throw new \RuntimeException('Environment not found: ' . $value); + return $value; + }); + } else { + $environmentList = []; + foreach ($environments as $environment) { + $environmentList[$environment->id] = $this->api()->getEnvironmentLabel($environment, false); } + asort($environmentList, SORT_NATURAL | SORT_FLAG_CASE); - return $value; - }); + if ($default) { + $text .= "\n" . 'Default: <question>' . $default . '</question>'; + } - $this->stdErr->writeln(''); + $id = $questionHelper->choose($environmentList, $text, $default); + } return $environments[$id]; } @@ -1633,9 +1664,11 @@ $this->stdErr->writeln('You are logged in.'); // Generate a new certificate from the certifier API. + /** @var \Platformsh\Cli\Service\SshConfig $sshConfig */ + $sshConfig = $this->getService('ssh_config'); /** @var \Platformsh\Cli\SshCert\Certifier $certifier */ $certifier = $this->getService('certifier'); - if ($certifier->isAutoLoadEnabled()) { + if ($certifier->isAutoLoadEnabled() && $sshConfig->checkRequiredVersion()) { $this->stdErr->writeln(''); $this->stdErr->writeln('Generating SSH certificate...'); try { @@ -1650,8 +1683,6 @@ // Write SSH configuration. /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */ $questionHelper = $this->getService('question_helper'); - /** @var \Platformsh\Cli\Service\SshConfig $sshConfig */ - $sshConfig = $this->getService('ssh_config'); if ($sshConfig->configureSessionSsh()) { $sshConfig->addUserSshConfig($questionHelper); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Domain/DomainCommandBase.php new/platformsh-cli-3.61.0/src/Command/Domain/DomainCommandBase.php --- old/platformsh-cli-3.59.1/src/Command/Domain/DomainCommandBase.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Domain/DomainCommandBase.php 2020-08-13 13:27:12.000000000 +0200 @@ -84,16 +84,26 @@ protected function handleApiException(ClientException $e, Project $project) { $response = $e->getResponse(); - if ($response !== null && $response->getStatusCode() === 403) { + if (!$response) { + throw $e; + } + if ($response->getStatusCode() === 403) { $project->ensureFull(); $data = $project->getData(); if (!$project->hasLink('#manage-domains') && !empty($data['subscription']['plan']) && $data['subscription']['plan'] === 'development') { $this->stdErr->writeln('This project is on a Development plan. Upgrade the plan to add domains.'); + return; + } + } + if ($response->getStatusCode() === 400) { + $data = $response->json(); + if (isset($data['detail']) && strpos($data['detail'], 'already claimed') !== false) { + $this->stdErr->writeln('This domain is already claimed by another service.'); + return; } - } else { - throw $e; } + throw $e; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Project/ProjectGetCommand.php new/platformsh-cli-3.61.0/src/Command/Project/ProjectGetCommand.php --- old/platformsh-cli-3.59.1/src/Command/Project/ProjectGetCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Project/ProjectGetCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -304,8 +304,17 @@ */ protected function suggestSshRemedies($gitUrl) { - $internalDomain = $this->config()->get('detection.git_domain'); - $isInternal = substr($gitUrl, -strlen($internalDomain)) === $internalDomain; + // Remove the path from the git URI to get the SSH part. + $gitSshUri = ''; + if (strpos($gitUrl, ':') !== false) { + list($gitSshUri,) = explode(':', $gitUrl, 2); + } + + // Determine whether the URL is for an internal Git repository, as + // opposed to a third-party one (like GitLab/GitHub). + $internalDomainSuffix = $this->config()->get('detection.git_domain'); + $isInternal = substr($gitSshUri, -strlen($internalDomainSuffix)) === $internalDomainSuffix; + if (!$isInternal) { $this->stdErr->writeln(''); $this->stdErr->writeln( @@ -333,11 +342,10 @@ $this->config()->get('application.executable') )); - if (strpos($gitUrl, ':') !== false) { - list($gitSshUrl,) = explode(':', $gitUrl, 2); + if ($gitSshUri !== '') { $this->stdErr->writeln(''); $this->stdErr->writeln('You can test your connection to the Git server by running:'); - $this->stdErr->writeln(sprintf('<comment>ssh -v %s</comment>', escapeshellarg($gitSshUrl))); + $this->stdErr->writeln(sprintf('<comment>ssh -v %s</comment>', escapeshellarg($gitSshUri))); } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Project/Variable/ProjectVariableSetCommand.php new/platformsh-cli-3.61.0/src/Command/Project/Variable/ProjectVariableSetCommand.php --- old/platformsh-cli-3.59.1/src/Command/Project/Variable/ProjectVariableSetCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Project/Variable/ProjectVariableSetCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -62,21 +62,14 @@ } // Set the variable to a new value. - $result = $this->getSelectedProject() + $this->getSelectedProject() ->setVariable($variableName, $variableValue, $json, !$supressBuild, !$supressRuntime); $this->stdErr->writeln("Variable <info>$variableName</info> set to: $variableValue"); - $success = true; - if (!$result->countActivities()) { - $this->redeployWarning(); - } elseif ($this->shouldWait($input)) { - /** @var \Platformsh\Cli\Service\ActivityMonitor $activityMonitor */ - $activityMonitor = $this->getService('activity_monitor'); - $success = $activityMonitor->waitMultiple($result->getActivities(), $this->getSelectedProject()); - } + $this->redeployWarning(); - return $success ? 0 : 1; + return 0; } /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Service/MongoDB/MongoDumpCommand.php new/platformsh-cli-3.61.0/src/Command/Service/MongoDB/MongoDumpCommand.php --- old/platformsh-cli-3.59.1/src/Command/Service/MongoDB/MongoDumpCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Service/MongoDB/MongoDumpCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -40,7 +40,6 @@ $host = $this->selectHost($input, getenv($envPrefix . 'RELATIONSHIPS') !== false); if ($host instanceof RemoteHost) { - $this->validateInput($input); $appName = $this->selectApp($input); } else { $appName = getenv($envPrefix . 'APPLICATION_NAME'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/SshCert/SshCertInfoCommand.php new/platformsh-cli-3.61.0/src/Command/SshCert/SshCertInfoCommand.php --- old/platformsh-cli-3.59.1/src/Command/SshCert/SshCertInfoCommand.php 1970-01-01 01:00:00.000000000 +0100 +++ new/platformsh-cli-3.61.0/src/Command/SshCert/SshCertInfoCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -0,0 +1,69 @@ +<?php +namespace Platformsh\Cli\Command\SshCert; + +use Platformsh\Cli\Command\CommandBase; +use Platformsh\Cli\Service\PropertyFormatter; +use Platformsh\Cli\SshCert\Certificate; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class SshCertInfoCommand extends CommandBase +{ + protected $hiddenInList = true; + + protected function configure() + { + $this + ->setName('ssh-cert:info') + ->setDescription('Display information about the current SSH certificate') + ->addOption('no-refresh', null, InputOption::VALUE_NONE, 'Do not refresh the certificate if it is invalid') + ->addOption('property', 'P', InputOption::VALUE_REQUIRED, 'The certificate property to display'); + PropertyFormatter::configureInput($this->getDefinition()); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + // Initialize the API service to ensure event listeners etc. + $this->api(); + + /** @var \Platformsh\Cli\SshCert\Certifier $certifier */ + $certifier = $this->getService('certifier'); + /** @var \Platformsh\Cli\Service\SshConfig $sshConfig */ + $sshConfig = $this->getService('ssh_config'); + + $cert = $certifier->getExistingCertificate(); + if (!$cert || !$this->isValid($cert)) { + if ($input->getOption('no-refresh')) { + $this->stdErr->writeln('No valid SSH certificate found.'); + $this->stdErr->writeln('To generate a certificate, run this command again without the <comment>--no-refresh</comment> option.'); + return 1; + } + if (!$sshConfig->checkRequiredVersion()) { + return 1; + } + // Generate a new certificate. + $cert = $certifier->generateCertificate(); + } + + /** @var \Platformsh\Cli\Service\PropertyFormatter $formatter */ + $formatter = $this->getService('property_formatter'); + $properties = [ + 'filename' => $cert->certificateFilename(), + 'key_filename' => $cert->privateKeyFilename(), + 'key_id' => $cert->metadata()->getKeyId(), + 'key_type' => $cert->metadata()->getKeyType(), + 'valid_after' => $formatter->formatDate($cert->metadata()->getValidAfter()), + 'valid_before' => $formatter->formatDate($cert->metadata()->getValidBefore()), + 'extensions' => $cert->metadata()->getExtensions(), + ]; + + $formatter->displayData($output, $properties, $input->getOption('property')); + + return 0; + } + + private function isValid(Certificate $cert) { + return !$cert->hasExpired(0) && $cert->metadata()->getKeyId() === $this->api()->getMyAccount()['id']; + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/SshCert/SshCertLoadCommand.php new/platformsh-cli-3.61.0/src/Command/SshCert/SshCertLoadCommand.php --- old/platformsh-cli-3.59.1/src/Command/SshCert/SshCertLoadCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/SshCert/SshCertLoadCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -9,20 +9,20 @@ class SshCertLoadCommand extends CommandBase { - protected $hiddenInList = true; - protected function configure() { $this ->setName('ssh-cert:load') ->addOption('refresh-only', null, InputOption::VALUE_NONE, 'Only refresh the certificate, if necessary (do not write SSH config)') ->addOption('new', null, InputOption::VALUE_NONE, 'Force the certificate to be refreshed') - ->addOption('new-key', null, InputOption::VALUE_NONE, 'Force the certificate to be refreshed with a new SSH key pair') - ->setDescription('Generate a new certificate from the certifier API'); + ->addOption('new-key', null, InputOption::VALUE_NONE, '[Deprecated] Use --new instead') + ->setDescription('Generate an SSH certificate'); } protected function execute(InputInterface $input, OutputInterface $output) { + $this->warnAboutDeprecatedOptions(['new-key'], 'The --new-key option is deprecated. Use --new instead.'); + // Initialize the API service to ensure event listeners etc. $this->api(); @@ -42,15 +42,19 @@ $refresh = false; } + /** @var \Platformsh\Cli\Service\SshConfig $sshConfig */ + $sshConfig = $this->getService('ssh_config'); + if ($refresh) { + if (!$sshConfig->checkRequiredVersion()) { + return 1; + } $this->stdErr->writeln('Generating SSH certificate...'); - $sshCert = $certifier->generateCertificate($input->getOption('new-key')); + $sshCert = $certifier->generateCertificate(); $this->displayCertificate($sshCert); } - /** @var \Platformsh\Cli\Service\SshConfig $sshConfig */ - $sshConfig = $this->getService('ssh_config'); - $sshConfig->configureSessionSsh(); + $hasSessionConfig = $sshConfig->configureSessionSsh(); if ($input->getOption('refresh-only')) { return 0; @@ -58,7 +62,7 @@ /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */ $questionHelper = $this->getService('question_helper'); - $success = $sshConfig->addUserSshConfig($questionHelper); + $success = !$hasSessionConfig || $sshConfig->addUserSshConfig($questionHelper); return $success ? 0 : 1; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Variable/VariableCreateCommand.php new/platformsh-cli-3.61.0/src/Command/Variable/VariableCreateCommand.php --- old/platformsh-cli-3.59.1/src/Command/Variable/VariableCreateCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Variable/VariableCreateCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -155,7 +155,7 @@ $this->displayVariable($result->getEntity()); $success = true; - if (!$result->countActivities()) { + if (!$result->countActivities() || $level === self::LEVEL_PROJECT) { $this->redeployWarning(); } elseif ($this->shouldWait($input)) { /** @var \Platformsh\Cli\Service\ActivityMonitor $activityMonitor */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Variable/VariableDeleteCommand.php new/platformsh-cli-3.61.0/src/Command/Variable/VariableDeleteCommand.php --- old/platformsh-cli-3.59.1/src/Command/Variable/VariableDeleteCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Variable/VariableDeleteCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -81,7 +81,7 @@ $this->stdErr->writeln("Deleted variable <info>$variableName</info>"); $success = true; - if (!$result->countActivities()) { + if (!$result->countActivities() || $level === self::LEVEL_PROJECT) { $this->redeployWarning(); } elseif ($this->shouldWait($input)) { /** @var \Platformsh\Cli\Service\ActivityMonitor $activityMonitor */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/Variable/VariableUpdateCommand.php new/platformsh-cli-3.61.0/src/Command/Variable/VariableUpdateCommand.php --- old/platformsh-cli-3.59.1/src/Command/Variable/VariableUpdateCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/Variable/VariableUpdateCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -84,7 +84,8 @@ $this->displayVariable($variable); $success = true; - if (!$result->countActivities()) { + + if (!$result->countActivities() || $level === self::LEVEL_PROJECT) { $this->redeployWarning(); } elseif ($this->shouldWait($input)) { /** @var \Platformsh\Cli\Service\ActivityMonitor $activityMonitor */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/WebCommand.php new/platformsh-cli-3.61.0/src/Command/WebCommand.php --- old/platformsh-cli-3.59.1/src/Command/WebCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/WebCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -48,7 +48,7 @@ } } } else { - $url = $this->config()->get('service.accounts_url'); + $url = $this->config()->getWithDefault('service.console_url', $this->config()->get('service.accounts_url')); } /** @var \Platformsh\Cli\Service\Url $urlService */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Command/WelcomeCommand.php new/platformsh-cli-3.61.0/src/Command/WelcomeCommand.php --- old/platformsh-cli-3.59.1/src/Command/WelcomeCommand.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Command/WelcomeCommand.php 2020-08-13 13:27:12.000000000 +0200 @@ -90,9 +90,7 @@ $messages = []; $messages[] = '<comment>This project is suspended.</comment>'; if ($project->owner === $this->api()->getMyAccount()['id']) { - $messages[] = '<comment>Update your payment details to re-activate it: ' - . $this->config()->get('service.accounts_url') - . '</comment>'; + $messages[] = '<comment>Update your payment details to re-activate it</comment>'; } $messages[] = ''; $this->stdErr->writeln($messages); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Service/Api.php new/platformsh-cli-3.61.0/src/Service/Api.php --- old/platformsh-cli-3.59.1/src/Service/Api.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Service/Api.php 2020-08-13 13:27:12.000000000 +0200 @@ -246,8 +246,10 @@ */ private function getConnectorOptions() { $connectorOptions = []; - $connectorOptions['accounts'] = rtrim($this->config->get('api.accounts_api_url'), '/') . '/'; $connectorOptions['api_url'] = $this->config->getWithDefault('api.base_url', ''); + if ($this->config->has('api.accounts_api_url')) { + $connectorOptions['accounts'] = $this->config->get('api.accounts_api_url'); + } $connectorOptions['certifier_url'] = $this->config->get('api.certifier_url'); $connectorOptions['verify'] = !$this->config->get('api.skip_ssl'); $connectorOptions['debug'] = $this->config->get('api.debug') ? STDERR : false; @@ -728,7 +730,7 @@ { $data = $this->getMyAccount($reset); - return SshKey::wrapCollection($data['ssh_keys'], rtrim($this->config->get('api.accounts_api_url'), '/') . '/', $this->getHttpClient()); + return SshKey::wrapCollection($data['ssh_keys'], rtrim($this->config->get('api.base_url'), '/') . '/', $this->getHttpClient()); } /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Service/Config.php new/platformsh-cli-3.61.0/src/Service/Config.php --- old/platformsh-cli-3.59.1/src/Service/Config.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Service/Config.php 2020-08-13 13:27:12.000000000 +0200 @@ -308,15 +308,13 @@ 'DRUSH' => 'local.drush_executable', 'SESSION_ID' => 'api.session_id', 'SKIP_SSL' => 'api.skip_ssl', - 'API_URL' => 'api.base_url', 'ACCOUNTS_API' => 'api.accounts_api_url', - 'ACCOUNTS_API_URL' => 'api.accounts_api_url', + 'API_URL' => 'api.base_url', 'OAUTH2_AUTH_URL' => 'api.oauth2_auth_url', 'OAUTH2_TOKEN_URL' => 'api.oauth2_token_url', 'OAUTH2_REVOKE_URL' => 'api.oauth2_revoke_url', 'CERTIFIER_URL' => 'api.certifier_url', 'AUTO_LOAD_SSH_CERT' => 'api.auto_load_ssh_cert', - 'SSH_DOMAIN_WILDCARD' => 'api.ssh_domain_wildcard', 'UPDATES_CHECK' => 'updates.check', ]; @@ -326,6 +324,11 @@ NestedArrayUtil::setNestedArrayValue($this->config, explode('.', $key), $value, true); } } + + // Special case: replace the list api.ssh_domain_wildcards with the value of {PREFIX}SSH_DOMAIN_WILDCARD. + if (($value = $this->getEnv('SSH_DOMAIN_WILDCARD')) !== false) { + $this->config['api']['ssh_domain_wildcards'] = [$value]; + } } /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Service/CurlCli.php new/platformsh-cli-3.61.0/src/Service/CurlCli.php --- old/platformsh-cli-3.59.1/src/Service/CurlCli.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Service/CurlCli.php 2020-08-13 13:27:12.000000000 +0200 @@ -25,6 +25,7 @@ $definition->addOption(new InputOption('include', 'i', InputOption::VALUE_NONE, 'Include headers in the output')); $definition->addOption(new InputOption('head', 'I', InputOption::VALUE_NONE, 'Fetch headers only')); $definition->addOption(new InputOption('disable-compression', null, InputOption::VALUE_NONE, 'Do not use the curl --compressed flag')); + $definition->addOption(new InputOption('enable-glob', null, InputOption::VALUE_NONE, 'Enable curl globbing (remove the --globoff flag)')); $definition->addOption(new InputOption('header', 'H', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Extra header(s)')); } @@ -77,6 +78,10 @@ $commandline .= ' --compressed'; } + if (!$input->getOption('enable-glob')) { + $commandline .= ' --globoff'; + } + foreach ($input->getOption('header') as $header) { $commandline .= ' --header ' . escapeshellarg($header); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Service/QuestionHelper.php new/platformsh-cli-3.61.0/src/Service/QuestionHelper.php --- old/platformsh-cli-3.59.1/src/Service/QuestionHelper.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Service/QuestionHelper.php 2020-08-13 13:27:12.000000000 +0200 @@ -127,7 +127,7 @@ public function askInput($questionText, $default = null, array $autoCompleterValues = [], callable $validator = null) { if ($default !== null) { - $questionText .= ' <question>[' . $default . ']</question>'; + $questionText .= ' (default: <question>' . $default . '</question>)'; } $questionText .= ': '; $question = new Question($questionText, $default); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Service/Ssh.php new/platformsh-cli-3.61.0/src/Service/Ssh.php --- old/platformsh-cli-3.59.1/src/Service/Ssh.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Service/Ssh.php 2020-08-13 13:27:12.000000000 +0200 @@ -84,8 +84,9 @@ // Inject the SSH certificate. $sshCert = $this->certifier->getExistingCertificate(); if ($sshCert || $this->certifier->isAutoLoadEnabled()) { - if (!$sshCert || $sshCert->hasExpired()) { - $stdErr = $this->output instanceof ConsoleOutputInterface ? $this->output->getErrorOutput() : $this->output; + $stdErr = $this->output instanceof ConsoleOutputInterface ? $this->output->getErrorOutput() : $this->output; + + if ((!$sshCert || $sshCert->hasExpired()) && $this->sshConfig->checkRequiredVersion()) { $stdErr->writeln('Generating SSH certificate...', OutputInterface::VERBOSITY_VERBOSE); try { $sshCert = $this->certifier->generateCertificate(); @@ -96,7 +97,9 @@ } if ($sshCert) { - $options['CertificateFile'] = $sshCert->certificateFilename(); + if ($this->sshConfig->supportsCertificateFile()) { + $options['CertificateFile'] = $sshCert->certificateFilename(); + } $options['IdentityFile'] = [$sshCert->privateKeyFilename()]; foreach ($this->sshConfig->getUserDefaultSshIdentityFiles() as $identityFile) { $options['IdentityFile'][] = $identityFile; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Service/SshConfig.php new/platformsh-cli-3.61.0/src/Service/SshConfig.php --- old/platformsh-cli-3.59.1/src/Service/SshConfig.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Service/SshConfig.php 2020-08-13 13:27:12.000000000 +0200 @@ -8,6 +8,7 @@ use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Process\Process; class SshConfig { private $config; @@ -15,6 +16,7 @@ private $stdErr; private $sshKey; private $certifier; + private $openSshVersion; public function __construct(Config $config, Filesystem $fs, OutputInterface $output, SshKey $sshKey, Certifier $certifier) { @@ -33,12 +35,18 @@ */ public function configureSessionSsh() { + if (!$this->supportsInclude()) { + return false; + } + // Backwards compatibility: delete the old SSH configuration file. $legacy = $this->getCliSshDir() . DIRECTORY_SEPARATOR . 'sess-cli-default.config'; if (\file_exists($legacy)) { $this->fs->remove($legacy); } + $domainWildcards = $this->config->get('api.ssh_domain_wildcards'); + $lines = []; if ($certificate = $this->certifier->getExistingCertificate()) { @@ -47,14 +55,31 @@ if (!OsUtil::isWindows()) { $refreshCommand .= ' 2>/dev/null'; } - $lines[] = sprintf('Match host %s exec "%s"', $this->config->get('api.ssh_domain_wildcard'), $refreshCommand); - $lines[] = sprintf(' CertificateFile %s', $certificate->certificateFilename()); - $lines[] = sprintf(' IdentityFile %s', $certificate->privateKeyFilename()); + // Use Match solely to run the refresh command. + $lines[] = '# Auto-refresh the SSH certificate:'; + if ($domainWildcards) { + $lines[] = sprintf('Match host "%s" exec "%s"', \implode(',', $domainWildcards), $refreshCommand); + } else { + $lines[] = sprintf('Match exec "%s"', $refreshCommand); + } + $lines[] = ''; + + // Indentation in the SSH config is for readability (it has no other effect). + $lines[] = '# Include the certificate and its key:'; + $lines[] = sprintf('CertificateFile %s', $certificate->certificateFilename()); + $lines[] = sprintf('IdentityFile %s', $certificate->privateKeyFilename()); + $lines[] = ''; + } + + if ($domainWildcards) { + $lines[] = 'Host ' . implode(' ', $domainWildcards); } $sessionIdentityFile = $this->sshKey->selectIdentity(); if ($sessionIdentityFile !== null) { + $lines[] = '# This SSH key was detected as corresponding to the session:'; $lines[] = sprintf('IdentityFile %s', $sessionIdentityFile); + $lines[] = ''; } $sessionSpecificFilename = $this->getSessionSshDir() . DIRECTORY_SEPARATOR . 'config'; @@ -67,24 +92,31 @@ } // Add default files if there is no preferred session identity file. - if ($sessionIdentityFile === null) { - $defaultFiles = $this->getUserDefaultSshIdentityFiles(); + if ($sessionIdentityFile === null && ($defaultFiles = $this->getUserDefaultSshIdentityFiles())) { + $lines[] = '# Include SSH "default" identity files:'; foreach ($defaultFiles as $identityFile) { $lines[] = sprintf('IdentityFile %s', $identityFile); } + $lines[] = ''; } $this->writeSshIncludeFile($sessionSpecificFilename, $lines); - $this->writeSshIncludeFile( - $includerFilename, - [ - '# This file is included from your SSH config file (~/.ssh/config).', - '# In turn, it includes the configuration for the currently active CLI session.', - '# It is updated automatically when certain CLI commands are run.', - 'Host ' . $this->config->get('api.ssh_domain_wildcard'), - ' Include ' . $sessionSpecificFilename, - ] - ); + + $includerLines = [ + '# This file is included from your SSH config file (~/.ssh/config).', + '# In turn, it includes the configuration for the currently active CLI session.', + '# It is updated automatically when certain CLI commands are run.', + ]; + + $wildcards = $this->config->get('api.ssh_domain_wildcards'); + if (count($wildcards)) { + $includerLines[] = 'Host ' . implode(' ', $wildcards); + $includerLines[] = ' Include ' . $sessionSpecificFilename; + $this->writeSshIncludeFile( + $includerFilename, + $includerLines + ); + } return true; } @@ -111,7 +143,7 @@ $this->stdErr->writeln(sprintf('Updating SSH configuration include file: <info>%s</info>', $filename), OutputInterface::VERBOSITY_VERBOSE); $this->fs->writeFile($filename, $contents, false); } else { - $this->stdErr->writeln(sprintf('Validated SSH configuration include file: <info>%s</info>', $filename), OutputInterface::VERBOSITY_VERBOSE); + $this->stdErr->writeln(sprintf('Validated SSH configuration include file: <info>%s</info>', $filename), OutputInterface::VERBOSITY_VERY_VERBOSE); } $this->chmod($filename, 0600); } @@ -135,9 +167,22 @@ */ public function addUserSshConfig(QuestionHelper $questionHelper) { + if (!$this->supportsInclude()) { + return false; + } + $filename = $this->getUserSshConfigFilename(); - $suggestedConfig = 'Include ' . $this->getCliSshDir() . DIRECTORY_SEPARATOR . '*.config'; + $wildcards = $this->config->get('api.ssh_domain_wildcards'); + if (!$wildcards) { + return true; + } + + $suggestedConfig = \implode("\n", [ + 'Host ' . \implode(' ', $wildcards), + ' Include ' . $this->getCliSshDir() . DIRECTORY_SEPARATOR . '*.config', + 'Host *', + ]); $manualMessage = 'To configure SSH manually, add the following lines to: <comment>' . $filename . '</comment>' . "\n" . $suggestedConfig; @@ -321,4 +366,96 @@ $this->stdErr->writeln('The configuration could not be automatically removed: ' . $e->getMessage()); } } + + /** + * Finds the locally installed OpenSSH version. + * + * @param bool $reset + * + * @return string|false + */ + private function findVersion($reset = false) + { + if (isset($this->openSshVersion) && !$reset) { + return $this->openSshVersion; + } + $this->openSshVersion = false; + $process = new Process('ssh -V'); + $process->run(); + $errorOutput = $process->getErrorOutput(); + if (!$process->isSuccessful()) { + if ($this->stdErr->isVerbose()) { + $this->stdErr->writeln('Unable to determine the installed OpenSSH version. The command output was:'); + $this->stdErr->writeln($errorOutput); + } + return false; + } + if (\preg_match('/OpenSSH_([0-9.]+[^ ,]*)/', $errorOutput, $matches)) { + $this->openSshVersion = $matches[1]; + } + return $this->openSshVersion; + } + + /** + * Checks if the installed OpenSSH version is below a given value. + * + * @param string $test + * + * @return bool + * True if the version is determined and it is lower than the $test value, false otherwise. + */ + private function versionIsBelow($test) + { + $version = $this->findVersion(); + if (!$version) { + return false; + } + return \version_compare($version, $test, '<'); + } + + /** + * Checks if the installed OpenSSH version supports the 'Include' syntax. + * + * @return bool + */ + public function supportsInclude() { + return !$this->versionIsBelow('7.3'); + } + + /** + * Checks if the installed OpenSSH version supports the 'CertificateFile' syntax. + * + * @return bool + */ + public function supportsCertificateFile() { + return !$this->versionIsBelow('7.2'); + } + + /** + * Checks if the required version is supported, and prints a warning. + * + * @return bool + * False if an incorrect version is installed, true otherwise. + */ + public function checkRequiredVersion() + { + $version = $this->findVersion(); + if (!$version) { + return true; + } + if (\version_compare($version, '6.5', '<')) { + $this->stdErr->writeln(\sprintf( + 'OpenSSH version <error>%s</error> is installed. Version 6.5 or above is required. Some features depend on version 7.3 or above.', + $version + )); + return false; + } + if (\version_compare($version, '7.3', '<')) { + $this->stdErr->writeln(\sprintf( + 'OpenSSH version <comment>%s</comment> is installed. Some features depend on version 7.3 or above.', + $version + )); + } + return true; + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/Service/SshKey.php new/platformsh-cli-3.61.0/src/Service/SshKey.php --- old/platformsh-cli-3.59.1/src/Service/SshKey.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/Service/SshKey.php 2020-08-13 13:27:12.000000000 +0200 @@ -29,11 +29,20 @@ * accounts, and (2) if the user has only one key, but it is in a * non-standard location. * + * @param bool $reset + * * @return string|null * An absolute filename of an SSH private key, or null if there is no * selected key. */ - public function selectIdentity() { + public function selectIdentity($reset = false) { + // Cache, mainly to avoid repetition of the output message. + static $selectedIdentity = false; + if (!$reset && $selectedIdentity !== false) { + return $selectedIdentity; + } + $selectedIdentity = null; + $accountKeyFingerprints = $this->listAccountKeyFingerprints(); if (!$accountKeyFingerprints) { return null; @@ -52,7 +61,7 @@ if ($key = $this->findIdentityMatchingPublicKeys($accountKeyFingerprints)) { $this->stdErr->writeln(sprintf('Automatically selected SSH identity: <info>%s</info>', $key), OutputInterface::VERBOSITY_VERBOSE); - return $key; + return $selectedIdentity = $key; } return null; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.59.1/src/SshCert/Certifier.php new/platformsh-cli-3.61.0/src/SshCert/Certifier.php --- old/platformsh-cli-3.59.1/src/SshCert/Certifier.php 2020-07-21 15:02:11.000000000 +0200 +++ new/platformsh-cli-3.61.0/src/SshCert/Certifier.php 2020-08-13 13:27:12.000000000 +0200 @@ -11,7 +11,8 @@ class Certifier { - const PRIVATE_KEY_FILENAME = 'id_rsa'; + const KEY_ALGORITHM = 'ed25519'; + const PRIVATE_KEY_FILENAME = 'id_ed25519'; private $api; private $config; @@ -41,16 +42,24 @@ /** * Generates a new certificate. * - * @param bool $newKeyPair Whether to recreate the SSH key pair. - * * @return Certificate */ - public function generateCertificate($newKeyPair = false) + public function generateCertificate() { $dir = $this->config->getSessionDir(true) . DIRECTORY_SEPARATOR . 'ssh'; $this->fs->mkdir($dir, 0700); - $sshPair = $this->generateSshKey($dir, $newKeyPair); + // Remove the old certificate and key from the SSH agent. + if ($this->config->getWithDefault('api.add_to_ssh_agent', false)) { + $this->shell->execute(['ssh-add', '-d', $dir . DIRECTORY_SEPARATOR . self::PRIVATE_KEY_FILENAME], null, false, !$this->stdErr->isVeryVerbose()); + } + + // Ensure the user is logged in to the API, so that an auto-login will + // not be triggered after we have generated keys (auto-login triggers a + // logout, which wipes keys). + $apiClient = $this->api->getClient(); + + $sshPair = $this->generateSshKey($dir, true); $publicContents = file_get_contents($sshPair['public']); if (!$publicContents) { throw new \RuntimeException('Failed to read public key file: ' . $publicContents); @@ -64,13 +73,21 @@ } $this->stdErr->writeln('Requesting certificate from the API', OutputInterface::VERBOSITY_VERBOSE); - $certificate = $this->api->getClient()->getSshCertificate($publicContents); + $certificate = $apiClient->getSshCertificate($publicContents); $this->fs->writeFile($certificateFilename, $certificate); $this->chmod($certificateFilename, 0600); $certificate = new Certificate($certificateFilename, $sshPair['private']); + // Add the key to the SSH agent, if possible, silently. + // In verbose mode the full command will be printed, so the user can + // re-run it to check error details. + if ($this->config->getWithDefault('api.add_to_ssh_agent', false)) { + $lifetime = ($certificate->metadata()->getValidBefore() - time()) ?: 3600; + $this->shell->execute(['ssh-add', '-t', $lifetime, $sshPair['private']], null, false, !$this->stdErr->isVerbose()); + } + return $certificate; } @@ -116,7 +133,14 @@ $this->fs->remove($sshInfo); } // Generate new keys and set permissions. - $this->shell->execute(['ssh-keygen', '-t', 'rsa', '-N', '', '-f', $sshInfo['private']], null, true); + $args = [ + 'ssh-keygen', + '-t', self::KEY_ALGORITHM, + '-f', $sshInfo['private'], + '-N', '', // No passphrase + '-C', $this->config->get('application.slug') . '-temporary-cert', // Key comment + ]; + $this->shell->execute($args, null, true); $this->chmod($sshInfo['private'], 0600); $this->chmod($sshInfo['public'], 0600); ++++++ platformsh-cli-vendor.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/autoload.php new/vendor/autoload.php --- old/vendor/autoload.php 2020-07-21 21:13:49.465291866 +0200 +++ new/vendor/autoload.php 2020-08-13 20:32:07.793394032 +0200 @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInitc6c1301a1038b3fa5191b9699022ee9b::getLoader(); +return ComposerAutoloaderInitf33106493991023d7e9aa3a168d1ea0f::getLoader(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/composer/autoload_real.php new/vendor/composer/autoload_real.php --- old/vendor/composer/autoload_real.php 2020-07-21 21:13:49.465291866 +0200 +++ new/vendor/composer/autoload_real.php 2020-08-13 20:32:07.793394032 +0200 @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInitc6c1301a1038b3fa5191b9699022ee9b +class ComposerAutoloaderInitf33106493991023d7e9aa3a168d1ea0f { private static $loader; @@ -22,15 +22,15 @@ return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInitc6c1301a1038b3fa5191b9699022ee9b', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitf33106493991023d7e9aa3a168d1ea0f', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInitc6c1301a1038b3fa5191b9699022ee9b', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitf33106493991023d7e9aa3a168d1ea0f', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInitc6c1301a1038b3fa5191b9699022ee9b::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInitf33106493991023d7e9aa3a168d1ea0f::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { @@ -51,19 +51,19 @@ $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInitc6c1301a1038b3fa5191b9699022ee9b::$files; + $includeFiles = Composer\Autoload\ComposerStaticInitf33106493991023d7e9aa3a168d1ea0f::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequirec6c1301a1038b3fa5191b9699022ee9b($fileIdentifier, $file); + composerRequiref33106493991023d7e9aa3a168d1ea0f($fileIdentifier, $file); } return $loader; } } -function composerRequirec6c1301a1038b3fa5191b9699022ee9b($fileIdentifier, $file) +function composerRequiref33106493991023d7e9aa3a168d1ea0f($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/composer/autoload_static.php new/vendor/composer/autoload_static.php --- old/vendor/composer/autoload_static.php 2020-07-21 21:13:49.465291866 +0200 +++ new/vendor/composer/autoload_static.php 2020-08-13 20:32:07.793394032 +0200 @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInitc6c1301a1038b3fa5191b9699022ee9b +class ComposerStaticInitf33106493991023d7e9aa3a168d1ea0f { public static $files = array ( '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', @@ -193,9 +193,9 @@ public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInitc6c1301a1038b3fa5191b9699022ee9b::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInitc6c1301a1038b3fa5191b9699022ee9b::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInitc6c1301a1038b3fa5191b9699022ee9b::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInitf33106493991023d7e9aa3a168d1ea0f::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitf33106493991023d7e9aa3a168d1ea0f::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitf33106493991023d7e9aa3a168d1ea0f::$classMap; }, null, ClassLoader::class); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/composer/installed.json new/vendor/composer/installed.json --- old/vendor/composer/installed.json 2020-07-21 21:13:49.021289253 +0200 +++ new/vendor/composer/installed.json 2020-08-13 20:32:07.141387931 +0200 @@ -682,17 +682,17 @@ }, { "name": "platformsh/client", - "version": "v0.35.2", - "version_normalized": "0.35.2.0", + "version": "v0.37.2", + "version_normalized": "0.37.2.0", "source": { "type": "git", "url": "https://github.com/platformsh/platformsh-client-php.git", - "reference": "ce7f949dc6788ee90b8172d991845a5a66788531" + "reference": "2f63f43d16faa370895f90f1e6febd5519e2664d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/ce7f949dc6788ee90b8172d991845a5a66788531", - "reference": "ce7f949dc6788ee90b8172d991845a5a66788531", + "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/2f63f43d16faa370895f90f1e6febd5519e2664d", + "reference": "2f63f43d16faa370895f90f1e6febd5519e2664d", "shasum": "" }, "require": { @@ -706,7 +706,7 @@ "require-dev": { "phpunit/phpunit": "~4.5" }, - "time": "2020-07-21T07:54:05+00:00", + "time": "2020-08-05T09:08:05+00:00", "type": "library", "installation-source": "dist", "autoload": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/platformsh/client/src/Connection/Connector.php new/vendor/platformsh/client/src/Connection/Connector.php --- old/vendor/platformsh/client/src/Connection/Connector.php 2020-07-21 09:54:05.000000000 +0200 +++ new/vendor/platformsh/client/src/Connection/Connector.php 2020-08-05 11:08:05.000000000 +0200 @@ -8,7 +8,6 @@ use GuzzleHttp\Client; use GuzzleHttp\ClientInterface; use GuzzleHttp\Collection; -use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Subscriber\Cache\CacheSubscriber; use GuzzleHttp\Url; @@ -36,7 +35,10 @@ /** * @param array $config * Possible configuration keys are: - * - accounts (string): The endpoint URL for the accounts API. + * - api_url (string): The API base URL. + * - token_url (string): The OAuth 2.0 token URL. + * - revoke_url (string): The OAuth 2.0 revocation URL. + * - certifier_url (string): The SSH certificate issuer URL. * - client_id (string): The OAuth2 client ID for this client. * - debug (bool): Whether or not Guzzle debugging should be enabled * (default: false). @@ -56,9 +58,13 @@ */ public function __construct(array $config = [], SessionInterface $session = null) { + if (isset($config['accounts'])) { + \trigger_error('The "accounts" URL option is deprecated. APIs are accessed based on the "api_url" and OAuth 2.0 URL options instead.', E_USER_DEPRECATED); + } + $defaults = [ - 'accounts' => 'https://accounts.platform.sh/api/v1/', 'api_url' => 'https://api.platform.sh', + 'accounts' => 'https://api.platform.sh/', 'client_id' => 'platformsh-client-php', 'client_secret' => '', 'debug' => false, @@ -124,6 +130,10 @@ /** * Get the configured accounts endpoint URL. * + * @deprecated Use ConnectorInterface::getApiUrl() instead + * + * @see ConnectorInterface::getApiUrl() + * * @return string */ public function getAccountsEndpoint() @@ -132,9 +142,7 @@ } /** - * Get the configured API gateway URL. - * - * @return string + * {@inheritDoc} */ public function getApiUrl() { @@ -165,6 +173,27 @@ } /** + * Get a configured OAuth 2.0 URL. + * + * @param string $key Either 'token_url' or 'revoke_url' + * + * @return string + */ + private function getOAuthUrl($key) + { + $url = $this->config[$key]; + + // Backwards compatibility. + if (strpos($url, '//') === false) { + $url = Url::fromString($this->config['accounts']) + ->combine($this->config[$key]) + ->__toString(); + } + + return $url; + } + + /** * Revokes the access and refresh tokens saved in the session. */ private function revokeTokens() @@ -173,9 +202,7 @@ 'refresh_token' => $this->session->get('refreshToken'), 'access_token' => $this->session->get('accessToken'), ]); - $url = Url::fromString($this->config['accounts']) - ->combine($this->config['revoke_url']) - ->__toString(); + $url = $this->getOAuthUrl('revoke_url'); foreach ($revocations as $type => $token) { $options = [ 'body' => [ @@ -186,17 +213,7 @@ ], 'auth' => false, ]; - try { - $this->getClient()->post($url, $options); - } catch (ClientException $e) { - // Ignore unsupported token type errors. - if ($e->getResponse()) { - $data = $e->getResponse()->json(); - if ($data['error'] !== 'unsupported_token_type') { - throw $e; - } - } - } + $this->getClient()->post($url, $options); } } @@ -218,7 +235,6 @@ } $this->logOut(); $client = $this->getGuzzleClient([ - 'base_url' => $this->config['accounts'], 'defaults' => [ 'debug' => $this->config['debug'], 'verify' => $this->config['verify'], @@ -231,7 +247,7 @@ 'client_secret' => $this->config['client_secret'], 'username' => $username, 'password' => $password, - 'token_url' => $this->config['token_url'], + 'token_url' => $this->getOAuthUrl('token_url'), ] ); if (isset($totp)) { @@ -314,7 +330,6 @@ } $options = [ - 'base_url' => $this->config['accounts'], 'defaults' => [ 'headers' => ['User-Agent' => $this->config['user_agent']], 'debug' => $this->config['debug'], @@ -331,7 +346,7 @@ 'client_secret' => $this->config['client_secret'], 'api_token' => $this->config['api_token'], 'refresh_token' => $this->session->get('refreshToken'), - 'token_url' => $this->config['token_url'], + 'token_url' => $this->getOAuthUrl('token_url'), ] ); } @@ -341,7 +356,7 @@ 'client_id' => $this->config['client_id'], 'client_secret' => $this->config['client_secret'], 'refresh_token' => $this->session->get('refreshToken'), - 'token_url' => $this->config['token_url'], + 'token_url' => $this->getOAuthUrl('token_url'), ] ); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/platformsh/client/src/Connection/ConnectorInterface.php new/vendor/platformsh/client/src/Connection/ConnectorInterface.php --- old/vendor/platformsh/client/src/Connection/ConnectorInterface.php 2020-07-21 09:54:05.000000000 +0200 +++ new/vendor/platformsh/client/src/Connection/ConnectorInterface.php 2020-08-05 11:08:05.000000000 +0200 @@ -50,11 +50,11 @@ public function getClient(); /** - * Get the configured accounts endpoint URL. + * Get the configured API gateway URL (without trailing slash). * * @return string */ - public function getAccountsEndpoint(); + public function getApiUrl(); /** * Set the API token to use for Platform.sh requests. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/platformsh/client/src/PlatformClient.php new/vendor/platformsh/client/src/PlatformClient.php --- old/vendor/platformsh/client/src/PlatformClient.php 2020-07-21 09:54:05.000000000 +0200 +++ new/vendor/platformsh/client/src/PlatformClient.php 2020-08-05 11:08:05.000000000 +0200 @@ -22,9 +22,6 @@ /** @var ConnectorInterface */ protected $connector; - /** @var string */ - protected $accountsEndpoint; - /** @var array */ protected $accountInfo; @@ -34,7 +31,6 @@ public function __construct(ConnectorInterface $connector = null) { $this->connector = $connector ?: new Connector(); - $this->accountsEndpoint = $this->connector->getAccountsEndpoint(); } /** @@ -46,6 +42,14 @@ } /** + * Returns the base URL of the API, without trailing slash. + */ + private function apiUrl() + { + return $this->connector->getApiUrl() ?: rtrim($this->connector->getAccountsEndpoint(), '/'); + } + + /** * Get a single project by its ID. * * @param string $id @@ -116,7 +120,7 @@ { if (!isset($this->accountInfo) || $reset) { $client = $this->connector->getClient(); - $url = $this->accountsEndpoint . 'me'; + $url = $this->apiUrl() . '/me'; try { $this->accountInfo = (array) $client->get($url)->json(); } @@ -164,7 +168,7 @@ protected function locateProject($id) { $client = $this->connector->getClient(); - $url = $this->accountsEndpoint . 'projects/' . rawurlencode($id); + $url = $this->apiUrl() . '/projects/' . rawurlencode($id); try { $result = (array) $client->get($url)->json(); } @@ -178,7 +182,13 @@ throw ApiResponseException::create($e->getRequest(), $e->getResponse(), $e->getPrevious()); } - return isset($result['endpoint']) ? $result['endpoint'] : false; + if (isset($result['endpoint'])) { + return $result['endpoint']; + } + if (isset($result['_links']['self']['href'])) { + return $result['_links']['self']['href']; + } + return false; } /** @@ -192,7 +202,7 @@ { $data = $this->getAccountInfo($reset); - return SshKey::wrapCollection($data['ssh_keys'], $this->accountsEndpoint, $this->connector->getClient()); + return SshKey::wrapCollection($data['ssh_keys'], $this->apiUrl() . '/ssh_keys', $this->connector->getClient()); } /** @@ -204,7 +214,7 @@ */ public function getSshKey($id) { - $url = $this->accountsEndpoint . 'ssh_keys'; + $url = $this->apiUrl() . '/ssh_keys'; return SshKey::get($id, $url, $this->connector->getClient()); } @@ -220,7 +230,7 @@ public function addSshKey($value, $title = null) { $values = $this->cleanRequest(['value' => $value, 'title' => $title]); - $url = $this->accountsEndpoint . 'ssh_keys'; + $url = $this->apiUrl() . '/ssh_keys'; return SshKey::create($values, $url, $this->connector->getClient()); } @@ -262,7 +272,7 @@ public function createSubscription($region, $plan = 'development', $title = null, $storage = null, $environments = null, array $activationCallback = null, $catalog = null) { - $url = $this->accountsEndpoint . 'subscriptions'; + $url = $this->apiUrl() . '/subscriptions'; $values = $this->cleanRequest([ 'project_region' => $region, 'plan' => $plan, @@ -283,7 +293,7 @@ */ public function getSubscriptions() { - $url = $this->accountsEndpoint . 'subscriptions'; + $url = $this->apiUrl() . '/subscriptions'; return Subscription::getCollection($url, 0, [], $this->connector->getClient()); } @@ -296,7 +306,7 @@ */ public function getSubscription($id) { - $url = $this->accountsEndpoint . 'subscriptions'; + $url = $this->apiUrl() . '/subscriptions'; return Subscription::get($id, $url, $this->connector->getClient()); } @@ -326,7 +336,7 @@ try { $response = $this->connector ->getClient() - ->get($this->accountsEndpoint . 'subscriptions/estimate', $options); + ->get($this->apiUrl() . '/subscriptions/estimate', $options); } catch (BadResponseException $e) { throw ApiResponseException::create($e->getRequest(), $e->getResponse(), $e->getPrevious()); } @@ -341,7 +351,7 @@ */ public function getPlans() { - return Plan::getCollection($this->accountsEndpoint . 'plans', 0, [], $this->getConnector()->getClient()); + return Plan::getCollection($this->apiUrl() . '/plans', 0, [], $this->getConnector()->getClient()); } /** @@ -351,7 +361,7 @@ */ public function getRegions() { - return Region::getCollection($this->accountsEndpoint . 'regions', 0, [], $this->getConnector()->getClient()); + return Region::getCollection($this->apiUrl() . '/regions', 0, [], $this->getConnector()->getClient()); } /** @@ -385,7 +395,7 @@ */ public function getCatalog() { - return Catalog::create([], $this->accountsEndpoint . 'setup/catalog', $this->getConnector()->getClient()); + return Catalog::create([], $this->apiUrl() . '/setup/catalog', $this->getConnector()->getClient()); } /** @@ -401,7 +411,7 @@ */ public function getSetupOptions($vendor = NULL, $plan = NULL, $options_url = NULL, $username = NULL, $organization = NULL) { - $url = $this->accountsEndpoint . 'setup/options'; + $url = $this->apiUrl() . '/setup/options'; $options = $this->cleanRequest([ 'vendor' => $vendor, 'plan' => $plan, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/platformsh/client/src/SshCert/Metadata.php new/vendor/platformsh/client/src/SshCert/Metadata.php --- old/vendor/platformsh/client/src/SshCert/Metadata.php 2020-07-21 09:54:05.000000000 +0200 +++ new/vendor/platformsh/client/src/SshCert/Metadata.php 2020-08-05 11:08:05.000000000 +0200 @@ -3,27 +3,17 @@ namespace Platformsh\Client\SshCert; /** - * Parses an OpenSSH RSA certificate. + * Parses an OpenSSH certificate (RSA or ED25519). * * @see https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD */ class Metadata { - private $keyType; - private $nonce; - private $rsaExponent; - private $publicModulus; - private $serial; - private $type; private $keyId; - private $validPrincipals; + private $keyType; private $validAfter; private $validBefore; - private $criticalOptions; private $extensions; - private $reserved; - private $signatureKey; - private $signature; /** * Constructor @@ -33,7 +23,7 @@ public function __construct($string) { list($type, $cert) = \explode(' ', $string); - if ($type !== '[email protected]') { + if (!\in_array($type, ['[email protected]', '[email protected]'], true)) { throw new \InvalidArgumentException('Unsupported key type: ' . $type); } $bytes = \base64_decode($cert, true); @@ -41,20 +31,23 @@ throw new \InvalidArgumentException('Unable to decode SSH certificate'); } $this->keyType = $this->readString($bytes); - $this->nonce = $this->readString($bytes); - $this->rsaExponent = $this->readString($bytes); - $this->publicModulus = $this->readString($bytes); - $this->serial = $this->readUint64($bytes); - $this->type = $this->readUint32($bytes); + $this->readString($bytes); // ignore nonce + // @todo refactor this? + if ($type === '[email protected]') { + $this->readString($bytes); // ignore ED25519 public key + } else { + $this->readString($bytes); // ignore RSA exponent + $this->readString($bytes); // ignore RSA modulus + } + $this->readUint64($bytes); // ignore serial number + $this->readUint32($bytes); // ignore certificate type (1 for user, 2 for host) $this->keyId = $this->readString($bytes); - $this->validPrincipals = $this->readArray($bytes); + $this->readArray($bytes); // ignore valid principals $this->validAfter = $this->readUint64($bytes); $this->validBefore = $this->readUint64($bytes); - $this->criticalOptions = $this->readTuples($bytes); + $this->readTuples($bytes); // ignore critical options $this->extensions = $this->readTuples($bytes); - $this->reserved = $this->readString($bytes); - $this->signatureKey = $this->readString($bytes); - $this->signature = $this->readString($bytes); + // ignore the reserved, signature and signature key fields } /** @@ -179,4 +172,15 @@ public function getKeyId() { return $this->keyId; } + + /** + * Return's the certificate key type. + * + * This will be an identifier such as [email protected] or [email protected]. + * + * @return string + */ + public function getKeyType() { + return $this->keyType; + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/platformsh/client/tests/PlatformClientTest.php new/vendor/platformsh/client/tests/PlatformClientTest.php --- old/vendor/platformsh/client/tests/PlatformClientTest.php 2020-07-21 09:54:05.000000000 +0200 +++ new/vendor/platformsh/client/tests/PlatformClientTest.php 2020-08-05 11:08:05.000000000 +0200 @@ -44,18 +44,13 @@ $this->connector->setMockResult(['projects' => [$testProject]]); $projects = $this->client->getProjects(); $this->assertEquals($testProject['name'], $projects[0]['name']); - $this->assertEquals($this->apiUrl . '/projects/test', $projects[0]['endpoint']); + $this->assertEquals($testProject['endpoint'], $projects[0]['endpoint']); + $this->assertEquals($this->apiUrl . '/projects/test/invitations', $projects[0]->getLink('invitations')); $project = $this->client->getProject('test'); $this->assertEquals($testProject['name'], $project['name']); - $this->assertEquals($this->apiUrl . '/projects/test', $project['endpoint']); - - // Test endpoint without an API gateway URL configured. - $connector = new MockConnector(['api_url' => '']); - $connector->setMockResult(['projects' => [$testProject]]); - $project = (new PlatformClient($connector))->getProject('test'); - $this->assertEquals($testProject['name'], $project['name']); $this->assertEquals($testProject['endpoint'], $project['endpoint']); + $this->assertEquals($this->apiUrl . '/projects/test/invitations', $project->getLink('invitations')); } public function testGetProjectDirect()
