Hello community, here is the log from the commit of package platformsh-cli for openSUSE:Factory checked in at 2020-08-29 20:45:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/platformsh-cli (Old) and /work/SRC/openSUSE:Factory/.platformsh-cli.new.3399 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "platformsh-cli" Sat Aug 29 20:45:07 2020 rev:105 rq:830378 version:3.62.2 Changes: -------- --- /work/SRC/openSUSE:Factory/platformsh-cli/platformsh-cli.changes 2020-08-19 18:57:37.551872755 +0200 +++ /work/SRC/openSUSE:Factory/.platformsh-cli.new.3399/platformsh-cli.changes 2020-08-29 20:45:22.065540761 +0200 @@ -1,0 +2,11 @@ +Sat Aug 29 02:16:38 UTC 2020 - [email protected] + +- Update to version 3.62.2: + * Release v3.62.2 + * SSH diagnostics fixes + * Diagnose whether MFA is required for a failed SSH connection (#974) + * Release v3.62.1 + * Trigger prompt=select_account after the "Log in anyway?" question, in browser login command + * Increase drush alias command timeout + +------------------------------------------------------------------- Old: ---- platformsh-cli-3.62.0.tar.xz New: ---- platformsh-cli-3.62.2.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ platformsh-cli.spec ++++++ --- /var/tmp/diff_new_pack.qoLp0h/_old 2020-08-29 20:45:23.537541376 +0200 +++ /var/tmp/diff_new_pack.qoLp0h/_new 2020-08-29 20:45:23.541541377 +0200 @@ -17,7 +17,7 @@ Name: platformsh-cli -Version: 3.62.0 +Version: 3.62.2 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.qoLp0h/_old 2020-08-29 20:45:23.581541393 +0200 +++ /var/tmp/diff_new_pack.qoLp0h/_new 2020-08-29 20:45:23.585541396 +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.62.0</param> + <param name="revision">refs/tags/v3.62.2</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.qoLp0h/_old 2020-08-29 20:45:23.617541409 +0200 +++ /var/tmp/diff_new_pack.qoLp0h/_new 2020-08-29 20:45:23.621541410 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">git://github.com/platformsh/platformsh-cli.git</param> - <param name="changesrevision">01e939e5285acfb3eaf69a1ac6f2e42ce4c7b9dc</param> + <param name="changesrevision">98d1b6ae90baa7c33072e81e2d8d7b85690b094a</param> </service> </servicedata> ++++++ platformsh-cli-3.62.0.tar.xz -> platformsh-cli-3.62.2.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/config.yaml new/platformsh-cli-3.62.2/config.yaml --- old/platformsh-cli-3.62.0/config.yaml 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/config.yaml 2020-08-25 17:32:57.000000000 +0200 @@ -88,6 +88,7 @@ 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' + mfa_setup_url: ~ 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' @@ -142,6 +143,7 @@ certifier_url: 'https://ssh.api.platform.sh' # Wildcard domains for SSH configuration. + # This is also used to detect whether to run diagnostics on an SSH connection error. # Can be replaced by a single value using the {application.env_prefix}SSH_DOMAIN_WILDCARD env var. ssh_domain_wildcards: ['*.platform.sh'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/dist/manifest.json new/platformsh-cli-3.62.2/dist/manifest.json --- old/platformsh-cli-3.62.0/dist/manifest.json 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/dist/manifest.json 2020-08-25 17:32:57.000000000 +0200 @@ -17,10 +17,10 @@ }, { "name": "platform.phar", - "sha1": "214e3b502a811eed1ffa0ef2448fafe007feed24", - "sha256": "268164bb583dec30862098ccf9cc96cf9c998f54cb5bb01c34a58dd7715a7841", - "url": "https://github.com/platformsh/platformsh-cli/releases/download/v3.62.0/platform.phar", - "version": "3.62.0", + "sha1": "dd6a6ac135aebc59a57055585b3f16b53b4aec3c", + "sha256": "0f6dfdcf921d5be61818e830863188035267793e702c979ad84ed303db0d48c3", + "url": "https://github.com/platformsh/platformsh-cli/releases/download/v3.62.2/platform.phar", + "version": "3.62.2", "php": { "min": "5.5.9" }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/services.yaml new/platformsh-cli-3.62.2/services.yaml --- old/platformsh-cli-3.62.0/services.yaml 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/services.yaml 2020-08-25 17:32:57.000000000 +0200 @@ -111,7 +111,7 @@ rsync: class: '\Platformsh\Cli\Service\Rsync' - arguments: ['@shell', '@ssh'] + arguments: ['@shell', '@ssh', '@ssh_diagnostics'] self_updater: class: '\Platformsh\Cli\Service\SelfUpdater' @@ -129,6 +129,10 @@ class: '\Platformsh\Cli\Service\SshConfig' arguments: ['@config', '@fs', '@output', '@ssh_key', '@certifier'] + ssh_diagnostics: + class: '\Platformsh\Cli\Service\SshDiagnostics' + arguments: ['@ssh', '@output', '@certifier', '@ssh_key', '@api', '@config'] + ssh_key: class: '\Platformsh\Cli\Service\SshKey' arguments: ['@config', '@api', '@output'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Command/Auth/BrowserLoginCommand.php new/platformsh-cli-3.62.2/src/Command/Auth/BrowserLoginCommand.php --- old/platformsh-cli-3.62.0/src/Command/Auth/BrowserLoginCommand.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Command/Auth/BrowserLoginCommand.php 2020-08-25 17:32:57.000000000 +0200 @@ -60,7 +60,8 @@ $this->stdErr->writeln(''); } $connector = $this->api()->getClient(false)->getConnector(); - if (!$input->getOption('force') && $connector->isLoggedIn()) { + $force = $input->getOption('force'); + if (!$force && $connector->isLoggedIn()) { // Get account information, simultaneously checking whether the API // login is still valid. If the request works, then do not log in // again (unless --force is used). If the request fails, proceed @@ -79,6 +80,7 @@ if (!$questionHelper->confirm('Log in anyway?', false)) { return 1; } + $force = true; } else { // USE THE FORCE $this->stdErr->writeln('Use the <comment>--force</comment> (<comment>-f</comment>) option to log in again.'); @@ -144,7 +146,7 @@ 'CLI_OAUTH_CODE_CHALLENGE' => $this->convertVerifierToChallenge($codeVerifier), '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_PROMPT' => $force ? 'consent select_account' : 'consent', 'CLI_OAUTH_SCOPE' => 'offline_access', 'CLI_OAUTH_FILE' => $responseFile, ] + $this->getParentEnv()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Command/CommandBase.php new/platformsh-cli-3.62.2/src/Command/CommandBase.php --- old/platformsh-cli-3.62.0/src/Command/CommandBase.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Command/CommandBase.php 2020-08-25 17:32:57.000000000 +0200 @@ -1648,10 +1648,12 @@ /** @var Ssh $ssh */ $ssh = $this->getService('ssh'); + /** @var \Platformsh\Cli\Service\SshDiagnostics $sshDiagnostics */ + $sshDiagnostics = $this->getService('ssh_diagnostics'); $this->debug('Selected host: ' . $remoteContainer->getSshUrl()); - return new RemoteHost($remoteContainer->getSshUrl(), $ssh, $shell); + return new RemoteHost($remoteContainer->getSshUrl(), $ssh, $shell, $sshDiagnostics); } /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Command/Environment/EnvironmentLogCommand.php new/platformsh-cli-3.62.2/src/Command/Environment/EnvironmentLogCommand.php --- old/platformsh-cli-3.62.0/src/Command/Environment/EnvironmentLogCommand.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Command/Environment/EnvironmentLogCommand.php 2020-08-25 17:32:57.000000000 +0200 @@ -42,16 +42,13 @@ } $container = $this->selectRemoteContainer($input); - $sshUrl = $container->getSshUrl(); - - /** @var \Platformsh\Cli\Service\Shell $shell */ - $shell = $this->getService('shell'); + $host = $this->selectHost($input, false, $container); $logDir = '/var/log'; // Special handling for Platform.sh Enterprise (Integrated UI) // environments. - if (preg_match('/^ent-.*?platform\.sh$/', $sshUrl)) { + if (preg_match('/^ent-.*?platform\.sh$/', $container->getSshUrl())) { $logDir = '/var/log/platform/"$USER"'; $this->debug('Detected Platform.sh Enterprise environment: using log directory: ' . $logDir); } @@ -71,11 +68,11 @@ $questionHelper = $this->getService('question_helper'); // Read the list of files from the environment. - $cacheKey = sprintf('log-files:%s', $sshUrl); + $cacheKey = sprintf('log-files:%s', $host->getCacheKey()); /** @var \Doctrine\Common\Cache\CacheProvider $cache */ $cache = $this->getService('cache'); if (!$result = $cache->fetch($cacheKey)) { - $result = $shell->execute(['ssh', $sshUrl, 'ls -1 ' . $logDir . '/*.log']); + $result = $host->runCommand('ls -1 ' . $logDir . '/*.log'); // Cache the list for 1 day. $cache->save($cacheKey, $result, 86400); @@ -100,11 +97,9 @@ $command .= ' -f'; } - $this->stdErr->writeln(sprintf('Reading log file <info>%s:%s</info>', $sshUrl, $logFilename)); - - $sshCommand = sprintf('ssh -C %s %s', escapeshellarg($sshUrl), escapeshellarg($command)); + $this->stdErr->writeln(sprintf('Reading log file <info>%s:%s</info>', $host->getLabel(), $logFilename)); - return $shell->executeSimple($sshCommand); + return $host->runCommandDirect($command); } /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Command/Environment/EnvironmentPushCommand.php new/platformsh-cli-3.62.2/src/Command/Environment/EnvironmentPushCommand.php --- old/platformsh-cli-3.62.0/src/Command/Environment/EnvironmentPushCommand.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Command/Environment/EnvironmentPushCommand.php 2020-08-25 17:32:57.000000000 +0200 @@ -2,6 +2,7 @@ namespace Platformsh\Cli\Command\Environment; use Platformsh\Cli\Command\CommandBase; +use Platformsh\Cli\Exception\ProcessFailedException; use Platformsh\Cli\Exception\RootNotFoundException; use Platformsh\Cli\Service\Ssh; use Platformsh\Client\Exception\EnvironmentStateException; @@ -166,8 +167,12 @@ $targetEnvironment ? 'existing' : 'new', $target )); - $success = $git->execute($gitArgs, null, false, false, $env); - if (!$success) { + try { + $git->execute($gitArgs, null, true, false, $env); + } catch (ProcessFailedException $e) { + /** @var \Platformsh\Cli\Service\SshDiagnostics $diagnostics */ + $diagnostics = $this->getService('ssh_diagnostics'); + $diagnostics->diagnoseFailure($project->getGitUrl(), $e->getProcess()->getExitCode(), $e->getProcess()); return 1; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Command/Environment/EnvironmentScpCommand.php new/platformsh-cli-3.62.2/src/Command/Environment/EnvironmentScpCommand.php --- old/platformsh-cli-3.62.0/src/Command/Environment/EnvironmentScpCommand.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Command/Environment/EnvironmentScpCommand.php 2020-08-25 17:32:57.000000000 +0200 @@ -80,6 +80,13 @@ /** @var \Platformsh\Cli\Service\Shell $shell */ $shell = $this->getService('shell'); - return $shell->executeSimple($command); + $exitCode = $shell->executeSimple($command); + if ($exitCode !== 0) { + /** @var \Platformsh\Cli\Service\SshDiagnostics $diagnostics */ + $diagnostics = $this->getService('ssh_diagnostics'); + $diagnostics->diagnoseFailure($sshUrl, $exitCode); + } + + return $exitCode; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Command/Environment/EnvironmentSshCommand.php new/platformsh-cli-3.62.2/src/Command/Environment/EnvironmentSshCommand.php --- old/platformsh-cli-3.62.0/src/Command/Environment/EnvironmentSshCommand.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Command/Environment/EnvironmentSshCommand.php 2020-08-25 17:32:57.000000000 +0200 @@ -64,18 +64,21 @@ /** @var \Platformsh\Cli\Service\Ssh $ssh */ $ssh = $this->getService('ssh'); $sshOptions = []; - $command = $ssh->getSshCommand($sshOptions); if ($this->isTerminal(STDIN)) { - $command .= ' -t'; - } - $command .= ' ' . escapeshellarg($sshUrl); - if ($remoteCommand) { - $command .= ' ' . escapeshellarg($remoteCommand); + $sshOptions['RequestTTY'] = 'force'; } + $command = $ssh->getSshCommand($sshOptions, $sshUrl, $remoteCommand); /** @var \Platformsh\Cli\Service\Shell $shell */ $shell = $this->getService('shell'); - return $shell->executeSimple($command); + $exitCode = $shell->executeSimple($command); + if ($exitCode !== 0) { + /** @var \Platformsh\Cli\Service\SshDiagnostics $diagnostics */ + $diagnostics = $this->getService('ssh_diagnostics'); + $diagnostics->diagnoseFailure($sshUrl, $exitCode); + } + + return $exitCode; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Command/Project/ProjectGetCommand.php new/platformsh-cli-3.62.2/src/Command/Project/ProjectGetCommand.php --- old/platformsh-cli-3.62.0/src/Command/Project/ProjectGetCommand.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Command/Project/ProjectGetCommand.php 2020-08-25 17:32:57.000000000 +0200 @@ -4,6 +4,7 @@ use Cocur\Slugify\Slugify; use Platformsh\Cli\Command\CommandBase; use Platformsh\Cli\Exception\DependencyMissingException; +use Platformsh\Cli\Exception\ProcessFailedException; use Platformsh\Cli\Local\BuildFlavor\Drupal; use Platformsh\Cli\Service\Ssh; use Platformsh\Client\Model\Project; @@ -12,6 +13,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; class ProjectGetCommand extends CommandBase { @@ -111,13 +113,13 @@ try { $repoExists = $git->remoteRefExists($gitUrl, 'refs/heads/' . $environment->id) || $git->remoteRefExists($gitUrl); - } catch (\RuntimeException $e) { + } catch (ProcessFailedException $e) { // The ls-remote command failed. $this->stdErr->writeln(sprintf( 'Failed to connect to the Git repository: <error>' . $gitUrl . '</error>' )); - $this->suggestSshRemedies($gitUrl); + $this->suggestSshRemedies($gitUrl, $e->getProcess()); return 1; } @@ -301,8 +303,9 @@ * Suggest SSH key commands for the user, if the Git connection fails. * * @param string $gitUrl + * @param Process $process */ - protected function suggestSshRemedies($gitUrl) + protected function suggestSshRemedies($gitUrl, Process $process) { // Remove the path from the git URI to get the SSH part. $gitSshUri = ''; @@ -310,12 +313,12 @@ list($gitSshUri,) = explode(':', $gitUrl, 2); } + /** @var \Platformsh\Cli\Service\SshDiagnostics $sshDiagnostics */ + $sshDiagnostics = $this->getService('ssh_diagnostics'); + // 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) { + if ($gitSshUri === '' || !$sshDiagnostics->sshHostIsInternal($gitSshUri)) { $this->stdErr->writeln(''); $this->stdErr->writeln( 'Please make sure you have the correct access rights and the repository exists.' @@ -323,29 +326,6 @@ return; } - /** @var \Platformsh\Cli\Service\SshKey $sshKey */ - $sshKey = $this->getService('ssh_key'); - - $this->stdErr->writeln(''); - - if (!$sshKey->hasLocalKey()) { - $this->stdErr->writeln(sprintf( - 'You probably need to add an SSH key, with: <comment>%s ssh-key:add</comment>', - $this->config()->get('application.executable') - )); - return; - } - - $this->stdErr->writeln('Please check your SSH credentials'); - $this->stdErr->writeln(sprintf( - 'You can list your keys by running: <comment>%s ssh-keys</comment>', - $this->config()->get('application.executable') - )); - - 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($gitSshUri))); - } + $sshDiagnostics->diagnoseFailure($gitSshUri, $process->getExitCode(), $process); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Model/Host/RemoteHost.php new/platformsh-cli-3.62.2/src/Model/Host/RemoteHost.php --- old/platformsh-cli-3.62.0/src/Model/Host/RemoteHost.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Model/Host/RemoteHost.php 2020-08-25 17:32:57.000000000 +0200 @@ -2,8 +2,10 @@ namespace Platformsh\Cli\Model\Host; +use Platformsh\Cli\Exception\ProcessFailedException; use Platformsh\Cli\Service\Shell; use Platformsh\Cli\Service\Ssh; +use Platformsh\Cli\Service\SshDiagnostics; use Platformsh\Cli\Util\OsUtil; class RemoteHost implements HostInterface @@ -12,12 +14,14 @@ private $sshService; private $shell; private $extraSshArgs = []; + private $sshDiagnostics; - public function __construct($sshUrl, Ssh $sshService, Shell $shell) + public function __construct($sshUrl, Ssh $sshService, Shell $shell, SshDiagnostics $sshDiagnostics) { $this->sshUrl = $sshUrl; $this->sshService = $sshService; $this->shell = $shell; + $this->sshDiagnostics = $sshDiagnostics; } /** @@ -41,7 +45,12 @@ */ public function runCommand($command, $mustRun = true, $quiet = true, $input = null) { - return $this->shell->execute($this->wrapCommandLine($command), null, $mustRun, $quiet, [], 3600, $input); + try { + return $this->shell->execute($this->wrapCommandLine($command), null, $mustRun, $quiet, [], 3600, $input); + } catch (ProcessFailedException $e) { + $this->sshDiagnostics->diagnoseFailure($this->sshUrl, $e->getProcess()->getExitCode(), $e->getProcess(), false); + throw new ProcessFailedException($e->getProcess(), false); + } } /** @@ -64,7 +73,11 @@ */ public function runCommandDirect($commandLine, $append = '') { - return $this->shell->executeSimple($this->wrapCommandLine($commandLine) . $append); + $exitCode = $this->shell->executeSimple($this->wrapCommandLine($commandLine) . $append); + if ($exitCode !== 0) { + $this->sshDiagnostics->diagnoseFailure($this->sshUrl, $exitCode, null, false); + } + return $exitCode; } /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Service/Api.php new/platformsh-cli-3.62.2/src/Service/Api.php --- old/platformsh-cli-3.62.0/src/Service/Api.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Service/Api.php 2020-08-25 17:32:57.000000000 +0200 @@ -21,6 +21,7 @@ use Platformsh\Client\Model\ProjectAccess; use Platformsh\Client\Model\Resource as ApiResource; use Platformsh\Client\Model\SshKey; +use Platformsh\Client\Model\User; use Platformsh\Client\PlatformClient; use Platformsh\Client\Session\SessionInterface; use Platformsh\Client\Session\Storage\File; @@ -768,6 +769,35 @@ } /** + * Get user information. + * + * This is from the /users API which deals with basic authentication + * related data. + * + * @param string|null $id + * The user ID. Defaults to the current user. + * @param bool $reset + * + * @return User + */ + public function getUser($id = null, $reset = false) + { + $id = $id ?: $this->getMyAccount()['id']; // @todo achieve this more efficiently + $cacheKey = 'user:' . $id; + if ($reset || !($data = $this->cache->fetch($cacheKey))) { + $user = $this->getClient()->getUser($id); + if (!$user) { + throw new \InvalidArgumentException('User not found: ' . $id); + } + $this->cache->save($cacheKey, $user->getData(), $this->config->get('api.users_ttl')); + } else { + $connector = $this->getClient()->getConnector(); + $user = new User($data, $connector->getApiUrl() . '/users', $connector->getClient()); + } + return $user; + } + + /** * Clear the environments cache for a project. * * Use this after creating/deleting/updating environment(s). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Service/Drush.php new/platformsh-cli-3.62.2/src/Service/Drush.php --- old/platformsh-cli-3.62.0/src/Service/Drush.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Service/Drush.php 2020-08-25 17:32:57.000000000 +0200 @@ -269,10 +269,10 @@ $aliases = []; - // Run the command with a 5-second timeout. An exception will be thrown - // if it fails. + // Run the command with a timeout. An exception will be thrown if it fails. + // A user experienced timeouts when this was set to 5 seconds, so it was increased to 30. try { - $result = $this->shellHelper->execute($args, null, true, true, [], 5); + $result = $this->shellHelper->execute($args, null, true, true, [], 30); if (is_string($result)) { $aliases = (array) json_decode($result, true); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Service/Rsync.php new/platformsh-cli-3.62.2/src/Service/Rsync.php --- old/platformsh-cli-3.62.0/src/Service/Rsync.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Service/Rsync.php 2020-08-25 17:32:57.000000000 +0200 @@ -2,8 +2,7 @@ namespace Platformsh\Cli\Service; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\NullOutput; +use Platformsh\Cli\Exception\ProcessFailedException; /** * Helper class which runs rsync. @@ -13,17 +12,20 @@ private $shell; private $ssh; + private $sshDiagnostics; /** * Constructor. * - * @param Shell|null $shellHelper - * @param Ssh|null $ssh + * @param Shell $shellHelper + * @param Ssh $ssh + * @param SshDiagnostics $sshDiagnostics */ - public function __construct(Shell $shellHelper = null, Ssh $ssh = null) + public function __construct(Shell $shellHelper, Ssh $ssh, SshDiagnostics $sshDiagnostics) { - $this->shell = $shellHelper ?: new Shell(); - $this->ssh = $ssh ?: new Ssh(new ArrayInput([]), new NullOutput()); + $this->shell = $shellHelper; + $this->ssh = $ssh; + $this->sshDiagnostics = $sshDiagnostics; } /** @@ -72,7 +74,12 @@ // contents rather than the directory itself. $from = rtrim($localDir, '/') . '/'; $to = sprintf('%s:%s', $sshUrl, $remoteDir); - $this->doSync($from, $to, $options); + try { + $this->doSync($from, $to, $options); + } catch (ProcessFailedException $e) { + $this->sshDiagnostics->diagnoseFailure($sshUrl, $e->getProcess()->getExitCode(), $e->getProcess()); + throw new ProcessFailedException($e->getProcess(), false); + } } /** @@ -87,7 +94,12 @@ { $from = sprintf('%s:%s/', $sshUrl, $remoteDir); $to = $localDir; - $this->doSync($from, $to, $options); + try { + $this->doSync($from, $to, $options); + } catch (ProcessFailedException $e) { + $this->sshDiagnostics->diagnoseFailure($sshUrl, $e->getProcess()->getExitCode(), $e->getProcess()); + throw new ProcessFailedException($e->getProcess(), false); + } } /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Service/Ssh.php new/platformsh-cli-3.62.2/src/Service/Ssh.php --- old/platformsh-cli-3.62.0/src/Service/Ssh.php 2020-08-18 00:08:04.000000000 +0200 +++ new/platformsh-cli-3.62.2/src/Service/Ssh.php 2020-08-25 17:32:57.000000000 +0200 @@ -38,11 +38,15 @@ } /** + * Returns arguments for an SSH command. + * * @param array $extraOptions + * @param string|null $uri + * @param string|null $remoteCommand * * @return array */ - public function getSshArgs(array $extraOptions = []) + public function getSshArgs(array $extraOptions = [], $uri = null, $remoteCommand = null) { $options = array_merge($this->getSshOptions(), $extraOptions); @@ -59,6 +63,13 @@ $args[] = $name . ' ' . $value; } + if ($uri !== null) { + $args[] = $uri; + } + if ($remoteCommand !== null) { + $args[] = $remoteCommand; + } + return $args; } @@ -132,13 +143,15 @@ * Returns an SSH command line. * * @param array $extraOptions + * @param string|null $uri + * @param string|null $remoteCommand * * @return string */ - public function getSshCommand(array $extraOptions = []) + public function getSshCommand(array $extraOptions = [], $uri = null, $remoteCommand = null) { $command = 'ssh'; - $args = $this->getSshArgs($extraOptions); + $args = $this->getSshArgs($extraOptions, $uri, $remoteCommand); if (!empty($args)) { $command .= ' ' . implode(' ', array_map('escapeshellarg', $args)); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/src/Service/SshDiagnostics.php new/platformsh-cli-3.62.2/src/Service/SshDiagnostics.php --- old/platformsh-cli-3.62.0/src/Service/SshDiagnostics.php 1970-01-01 01:00:00.000000000 +0100 +++ new/platformsh-cli-3.62.2/src/Service/SshDiagnostics.php 2020-08-25 17:32:57.000000000 +0200 @@ -0,0 +1,178 @@ +<?php + +namespace Platformsh\Cli\Service; + +use Platformsh\Cli\SshCert\Certifier; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; + +class SshDiagnostics +{ + const _SSH_ERROR_EXIT_CODE = 255; + + private $ssh; + private $sshKey; + private $certifier; + private $stdErr; + private $api; + private $config; + + private $connectionTestResult; + + public function __construct(Ssh $ssh, OutputInterface $output, Certifier $certifier, SshKey $sshKey, Api $api, Config $config) + { + $this->ssh = $ssh; + $this->stdErr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + $this->sshKey = $sshKey; + $this->certifier = $certifier; + $this->api = $api; + $this->config = $config; + } + + /** + * Checks whether the SSH connection fails due to MFA requirements. + * + * @param string $uri + * @param Process|null $failedProcess + * + * @return bool + */ + private function connectionFailedDueToMFA($uri, $failedProcess = null) + { + if (($cert = $this->certifier->getExistingCertificate()) && $cert->hasMfa()) { + // MFA is verified. + return false; + } + $failedProcess = $failedProcess ?: $this->testConnection($uri); + + return stripos($failedProcess->getErrorOutput(), 'reason: access requires MFA') !== false; + } + + /** + * Tests the SSH connection (and caches the result). + * + * @param string $uri + * @param bool $reset + * + * @return Process + * A process (already run) that tested the SSH connection. + */ + private function testConnection($uri, $reset = false) + { + if (!$reset && isset($this->connectionTestResult)) { + return $this->connectionTestResult; + } + $this->stdErr->writeln('Making test connection to diagnose SSH errors', OutputInterface::VERBOSITY_VERBOSE); + $process = new Process($this->ssh->getSshCommand([], $uri, 'exit')); + $process->run(); + return $this->connectionTestResult = $process; + } + + /** + * Hackily finds a host from an SSH URI. + * + * @param string $uri + * + * @return string|false + */ + private function getHost($uri) + { + // Parse the SSH URI to get the hostname. + if (\strpos($uri, '@') !== false) { + list(, $uri) = \explode('@', $uri, 2); + } + if (\strpos($uri, '://') !== false) { + list(, $uri) = \explode('://', $uri, 2); + } + if (\strpos($uri, ':') !== false) { + list($uri, ) = \explode(':', $uri, 2); + } + return \parse_url('ssh://' . $uri, PHP_URL_HOST); + } + + /** + * Checks if an SSH URI is for an internal (first-party) SSH server. + * + * @param string $uri + * + * @return bool + * True if the URI is for an internal server, false if it's external or it cannot be determined. + */ + public function sshHostIsInternal($uri) + { + $host = $this->getHost($uri); + if ($host === false) { + return false; + } + // Check against the wildcard list. + foreach ($this->config->getWithDefault('api.ssh_domain_wildcards', []) as $wildcard) { + if (\strpos($host, \str_replace('*.', '', $wildcard)) !== false) { + return true; + } + } + return false; + } + + /** + * Diagnoses and reports reasons for an SSH command failure. + * + * @param string $uri + * The SSH connection URI. + * @param int $exitCode + * The exit code from the SSH command. + * @param Process|null $failedProcess + * The failed SSH process. Another SSH command will run automatically if a process is not available. + * @param bool $blankLine + * Whether to output a blank line first if anything will be printed. + */ + public function diagnoseFailure($uri, $exitCode, Process $failedProcess = null, $blankLine = true) + { + if ($exitCode !== self::_SSH_ERROR_EXIT_CODE) { + return; + } + // Only check when the SSH URI matches an internal SSH host. + if (!$this->sshHostIsInternal($uri)) { + return; + } + + $executable = $this->config->get('application.executable'); + + if ($this->connectionFailedDueToMFA($uri, $failedProcess)) { + if ($blankLine) { + $this->stdErr->writeln(''); + } + + if (!$this->certifier->getExistingCertificate() && !$this->certifier->isAutoLoadEnabled()) { + $this->stdErr->writeln('The SSH connection failed. An SSH certificate is required.'); + $this->stdErr->writeln(sprintf('Generate one using: <comment>%s ssh-cert:load</comment>', $executable)); + return; + } + + $this->stdErr->writeln('The SSH connection failed because access requires MFA (multi-factor authentication).'); + + if ($this->api->getUser()->mfa_enabled) { + $this->stdErr->writeln('MFA is currently enabled on your account, but reverification is required.'); + $this->stdErr->writeln(\sprintf('Log in again with: <comment>%s login -f</comment>', $executable)); + } else { + $this->stdErr->writeln('MFA is not yet enabled on your account.'); + if ($this->config->has('api.mfa_setup_url')) { + $this->stdErr->writeln(\sprintf('Set up MFA at: <comment>%s</comment>', $this->config->get('api.mfa_setup_url'))); + } + $this->stdErr->writeln(\sprintf('Then log in again with: <comment>%s login -f</comment>', $executable)); + } + return; + } + + if (!$this->sshKey->hasLocalKey()) { + if ($blankLine) { + $this->stdErr->writeln(''); + } + $this->stdErr->writeln(sprintf( + 'You probably need to add an SSH key, with: <comment>%s ssh-key:add</comment>', + $executable + )); + return; + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/tests/Container.php new/platformsh-cli-3.62.2/tests/Container.php --- old/platformsh-cli-3.62.0/tests/Container.php 1970-01-01 01:00:00.000000000 +0100 +++ new/platformsh-cli-3.62.2/tests/Container.php 2020-08-25 17:32:57.000000000 +0200 @@ -0,0 +1,18 @@ +<?php + +namespace Platformsh\Cli\Tests; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +class Container +{ + public static function instance() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator()); + $loader->load(CLI_ROOT . '/services.yaml'); + return $container; + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.62.0/tests/Service/SshDiagnosticsTest.php new/platformsh-cli-3.62.2/tests/Service/SshDiagnosticsTest.php --- old/platformsh-cli-3.62.0/tests/Service/SshDiagnosticsTest.php 1970-01-01 01:00:00.000000000 +0100 +++ new/platformsh-cli-3.62.2/tests/Service/SshDiagnosticsTest.php 2020-08-25 17:32:57.000000000 +0200 @@ -0,0 +1,48 @@ +<?php + +namespace Platformsh\Cli\Tests\Service; + +use Platformsh\Cli\Service\Config; +use Platformsh\Cli\Service\SshDiagnostics; +use Platformsh\Cli\Tests\Container; +use Symfony\Component\Console\Input\ArrayInput; + +class SshDiagnosticsTest extends \PHPUnit_Framework_TestCase { + private $sd; + + public function setUp() { + $container = Container::instance(); + $container->set('input', new ArrayInput([])); + $container->set('config', (new Config())->withOverrides([ + 'api.ssh_domain_wildcards' => ['*.ssh.example.com'], + ])); + /** @var SshDiagnostics $this->sd */ + $this->sd = $container->get('ssh_diagnostics'); + } + + public function testGetHost() + { + $method = new \ReflectionMethod($this->sd, 'getHost'); + $method->setAccessible(true); + $this->assertEquals('xyz.ssh.example.com', $method->invoke($this->sd, '[email protected]')); + $this->assertEquals('bar.ssh.example.com', $method->invoke($this->sd, '[email protected]:/var/log/example')); + $this->assertEquals('foo.ssh.example.com', $method->invoke($this->sd, 'ssh://foo.ssh.example.com')); + $this->assertEquals('ssh.example.com', $method->invoke($this->sd, 'ssh://[email protected]:foo.git')); + $this->assertEquals('abc', $method->invoke($this->sd, 'abc')); + $this->assertEquals('github.com', $method->invoke($this->sd, 'user:[email protected]:bar.git')); + $this->assertEquals(false, $method->invoke($this->sd, '###')); + } + + public function testHostIsInternal() + { + $method = new \ReflectionMethod($this->sd, 'sshHostIsInternal'); + $method->setAccessible(true); + $this->assertTrue($method->invoke($this->sd, '[email protected]')); + $this->assertTrue($method->invoke($this->sd, '[email protected]:/var/log/example')); + $this->assertTrue($method->invoke($this->sd, 'ssh://foo.ssh.example.com')); + $this->assertTrue($method->invoke($this->sd, 'ssh://[email protected]')); + $this->assertTrue($method->invoke($this->sd, 'abc.ssh.example.com')); + $this->assertFalse($method->invoke($this->sd, 'abc.github.com')); + $this->assertFalse($method->invoke($this->sd, '[email protected]:bar.git')); + } +} ++++++ 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-08-19 00:37:30.162054503 +0200 +++ new/vendor/autoload.php 2020-08-29 04:16:40.657406079 +0200 @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit5db2f2fae7409a3711582bdd8205ecdf::getLoader(); +return ComposerAutoloaderInita24d3268f8dba2dc48202e3cbf76953c::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-08-19 00:37:30.162054503 +0200 +++ new/vendor/composer/autoload_real.php 2020-08-29 04:16:40.657406079 +0200 @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit5db2f2fae7409a3711582bdd8205ecdf +class ComposerAutoloaderInita24d3268f8dba2dc48202e3cbf76953c { private static $loader; @@ -22,15 +22,15 @@ return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit5db2f2fae7409a3711582bdd8205ecdf', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInita24d3268f8dba2dc48202e3cbf76953c', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit5db2f2fae7409a3711582bdd8205ecdf', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInita24d3268f8dba2dc48202e3cbf76953c', '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\ComposerStaticInit5db2f2fae7409a3711582bdd8205ecdf::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInita24d3268f8dba2dc48202e3cbf76953c::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\ComposerStaticInit5db2f2fae7409a3711582bdd8205ecdf::$files; + $includeFiles = Composer\Autoload\ComposerStaticInita24d3268f8dba2dc48202e3cbf76953c::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire5db2f2fae7409a3711582bdd8205ecdf($fileIdentifier, $file); + composerRequirea24d3268f8dba2dc48202e3cbf76953c($fileIdentifier, $file); } return $loader; } } -function composerRequire5db2f2fae7409a3711582bdd8205ecdf($fileIdentifier, $file) +function composerRequirea24d3268f8dba2dc48202e3cbf76953c($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-08-19 00:37:30.162054503 +0200 +++ new/vendor/composer/autoload_static.php 2020-08-29 04:16:40.653406059 +0200 @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit5db2f2fae7409a3711582bdd8205ecdf +class ComposerStaticInita24d3268f8dba2dc48202e3cbf76953c { 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 = ComposerStaticInit5db2f2fae7409a3711582bdd8205ecdf::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit5db2f2fae7409a3711582bdd8205ecdf::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit5db2f2fae7409a3711582bdd8205ecdf::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInita24d3268f8dba2dc48202e3cbf76953c::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInita24d3268f8dba2dc48202e3cbf76953c::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInita24d3268f8dba2dc48202e3cbf76953c::$classMap; }, null, ClassLoader::class); }
