Skip to content

Commit

Permalink
Add debug info for session storage
Browse files Browse the repository at this point in the history
  • Loading branch information
pjcdawkins committed Sep 19, 2024
1 parent a5161c2 commit 8305fcc
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 17 deletions.
34 changes: 34 additions & 0 deletions src/CredentialHelper/KeyringUnavailableException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Platformsh\Cli\CredentialHelper;

use Platformsh\Cli\Util\OsUtil;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;

/**
* An exception thrown when the login keyring is unavailable.
*/
class KeyringUnavailableException extends \RuntimeException
{
public static function fromTimeout(ProcessTimedOutException $_)
{
$type = OsUtil::isOsX() ? 'keychain' : 'keyring';
$message = sprintf('The credential helper process timed out while trying to access the %s.', $type);
$message .= "\n" . sprintf('This may be due to a system password prompt: is the login %s unlocked?', $type);
return new KeyringUnavailableException($message);
}

public static function fromFailure(ProcessFailedException $e)
{
$type = OsUtil::isOsX() ? 'keychain' : 'keyring';
$message = sprintf('The credential helper process failed while trying to access the %s.', $type);
$process = $e->getProcess();
if ($process->getExitCode() === 2 && strpos($process->getErrorOutput(), 'libsecret-CRITICAL') !== false) {
$message .= "\n" . sprintf('This can happen when the password dialog is dismissed. Is the login %s unlocked?', $type);
} else {
$message .= "\n" . $e->getMessage();
}
return new KeyringUnavailableException($message);
}
}
57 changes: 40 additions & 17 deletions src/Service/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
use GuzzleHttp\Event\ErrorEvent;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Message\ResponseInterface;
use Platformsh\Cli\CredentialHelper\KeyringUnavailableException;
use Platformsh\Cli\CredentialHelper\Manager;
use Platformsh\Cli\CredentialHelper\SessionStorage;
use Platformsh\Cli\CredentialHelper\SessionStorage as CredentialHelperStorage;
use Platformsh\Cli\Event\EnvironmentsChangedEvent;
use Platformsh\Cli\Event\LoginRequiredEvent;
use Platformsh\Cli\Exception\ProcessFailedException;
use Platformsh\Cli\GuzzleDebugSubscriber;
use Platformsh\Cli\Model\Route;
use Platformsh\Cli\Util\NestedArrayUtil;
Expand Down Expand Up @@ -44,6 +46,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Process\Exception\ProcessTimedOutException;

/**
* Decorates the PlatformClient API client to provide aggressive caching.
Expand Down Expand Up @@ -168,7 +171,7 @@ public function hasApiToken($includeStored = true)
public function listSessionIds()
{
$ids = [];
if ($this->sessionStorage instanceof SessionStorage) {
if ($this->sessionStorage instanceof CredentialHelperStorage) {
$ids = $this->sessionStorage->listSessionIds();
}
$dir = $this->config->getSessionDir();
Expand All @@ -192,7 +195,7 @@ public function listSessionIds()
*/
public function anySessionsExist()
{
if ($this->sessionStorage instanceof SessionStorage && $this->sessionStorage->hasAnySessions()) {
if ($this->sessionStorage instanceof CredentialHelperStorage && $this->sessionStorage->hasAnySessions()) {
return true;
}
$dir = $this->config->getSessionDir();
Expand Down Expand Up @@ -231,7 +234,7 @@ public function logout()
*/
public function deleteAllSessions()
{
if ($this->sessionStorage instanceof SessionStorage) {
if ($this->sessionStorage instanceof CredentialHelperStorage) {
$this->sessionStorage->deleteAll();
}
$dir = $this->config->getSessionDir();
Expand Down Expand Up @@ -550,8 +553,20 @@ public function getClient($autoLogin = true, $reset = false)
// (unless an access token was set directly).
if (!isset($options['api_token']) || $options['api_token_type'] !== 'access') {
$this->initSessionStorage();
// This will load from the session for the first time.
$session->setStorage($this->sessionStorage);
$this->debug('Loading session');
try {
$session->setStorage($this->sessionStorage);
} catch (\RuntimeException $e) {
if ($this->sessionStorage instanceof CredentialHelperStorage) {
$previous = $e->getPrevious();
if ($previous instanceof ProcessTimedOutException) {
throw KeyringUnavailableException::fromTimeout($previous);
} elseif ($previous instanceof ProcessFailedException) {
throw KeyringUnavailableException::fromFailure($previous);
}
}
throw $e;
}
}

$connector = new Connector($options, $session);
Expand Down Expand Up @@ -584,16 +599,24 @@ public function getClient($autoLogin = true, $reset = false)
* Initializes session credential storage.
*/
private function initSessionStorage() {
// Attempt to use the docker-credential-helpers.
$manager = new Manager($this->config);
if ($manager->isSupported()) {
$manager->install();
$this->sessionStorage = new SessionStorage($manager, $this->config->get('application.slug'));
return;
}
if (!isset($this->sessionStorage)) {
// Attempt to use the docker-credential-helpers.
$manager = new Manager($this->config);
if ($manager->isSupported()) {
if ($manager->isInstalled()) {
$this->debug('Using Docker credential helper for session storage');
} else {
$this->debug('Installing Docker credential helper for session storage');
$manager->install();
}
$this->sessionStorage = new CredentialHelperStorage($manager, $this->config->get('application.slug'));
return;
}

// Fall back to file storage.
$this->sessionStorage = new File($this->config->getSessionDir());
// Fall back to file storage.
$this->debug('Using filesystem for session storage');
$this->sessionStorage = new File($this->config->getSessionDir());
}
}

/**
Expand Down Expand Up @@ -961,9 +984,9 @@ public function getUser($id = null, $reset = false)
}
$this->cache->save($cacheKey, $user->getData(), (int) $this->config->getWithDefault('api.users_ttl', 600));
} else {
$this->debug('Loaded user info from cache: ' . $id);
$connector = $this->getClient()->getConnector();
$user = new User($data, $connector->getApiUrl() . '/users', $connector->getClient());
$this->debug('Loaded user info from cache: ' . $id);
}
return $user;
}
Expand Down Expand Up @@ -1273,8 +1296,8 @@ public function getCurrentDeployment(Environment $environment, $refresh = false,
$data['_uri'] = $deployment->getUri();
$this->cache->save($cacheKey, $data);
} else {
$deployment = new EnvironmentDeployment($data, $data['_uri'], $this->getHttpClient(), true);
$this->debug('Loaded environment deployment from cache for environment: ' . $environment->id);
$deployment = new EnvironmentDeployment($data, $data['_uri'], $this->getHttpClient(), true);
}

return self::$deploymentsCache[$cacheKey] = $deployment;
Expand Down

0 comments on commit 8305fcc

Please sign in to comment.