diff --git a/composer.json b/composer.json
index 50a3eab6f9..6bbab203aa 100644
--- a/composer.json
+++ b/composer.json
@@ -4,7 +4,7 @@
"license": "MIT",
"require": {
"doctrine/cache": "~1.4.2",
- "platformsh/client": "0.1.22",
+ "platformsh/client": "0.1.25",
"symfony/console": ">= 2.5.2 < 2.7.0",
"symfony/yaml": "~2.5",
"symfony/finder": "~2.5",
diff --git a/composer.lock b/composer.lock
index 25fa5adc2f..bf78cc2ca8 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "a23456cf58f3af5ab6bcc50e628ef6cd",
+ "hash": "04cbd376b25031efc594be557a34bcb0",
"packages": [
{
"name": "cocur/slugify",
@@ -678,16 +678,16 @@
},
{
"name": "platformsh/client",
- "version": "v0.1.22",
+ "version": "v0.1.25",
"source": {
"type": "git",
"url": "https://github.com/platformsh/platformsh-client-php.git",
- "reference": "ef62cecf911d58f8aa07dabd3fe91a29dce80d70"
+ "reference": "59ff57d37fa00cab88fb02ffe23bc4db9bb7251a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/ef62cecf911d58f8aa07dabd3fe91a29dce80d70",
- "reference": "ef62cecf911d58f8aa07dabd3fe91a29dce80d70",
+ "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/59ff57d37fa00cab88fb02ffe23bc4db9bb7251a",
+ "reference": "59ff57d37fa00cab88fb02ffe23bc4db9bb7251a",
"shasum": ""
},
"require": {
@@ -715,7 +715,7 @@
}
],
"description": "Platform.sh API client",
- "time": "2015-09-04 11:36:40"
+ "time": "2015-09-23 11:43:38"
},
{
"name": "react/promise",
diff --git a/src/Command/Activity/ActivityListCommand.php b/src/Command/Activity/ActivityListCommand.php
index 11ec95ae49..d1f3f9728c 100644
--- a/src/Command/Activity/ActivityListCommand.php
+++ b/src/Command/Activity/ActivityListCommand.php
@@ -56,7 +56,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
);
}
- if ($output instanceof StreamOutput && ($input->getOption('pipe') || !$this->isTerminal($output))) {
+ if ($output instanceof StreamOutput && $input->getOption('pipe')) {
$stream = $output->getStream();
array_unshift($rows, $headers);
foreach ($rows as $row) {
diff --git a/src/Command/Auth/LoginCommand.php b/src/Command/Auth/LoginCommand.php
index 0a4bae1a17..e4bb49ef61 100644
--- a/src/Command/Auth/LoginCommand.php
+++ b/src/Command/Auth/LoginCommand.php
@@ -1,6 +1,7 @@
getHelper('question');
$question = new Question('Your email address: ');
@@ -96,10 +98,47 @@ function ($answer) {
try {
$this->authenticateUser($email, $password);
- } catch (\InvalidArgumentException $e) {
- $output->writeln("\nLogin failed. Please check your credentials.\n");
- $output->writeln("Forgot your password? Visit: https://accounts.platform.sh/user/password\n");
- $this->configureAccount($input, $output);
+ } catch (BadResponseException $e) {
+ // If a two-factor authentication challenge is received, then ask
+ // the user for their TOTP code, and then retry authenticateUser().
+ if ($e->getResponse()->getHeader('X-Drupal-TFA')) {
+ $question = new Question("Your application verification code: ");
+ $question->setValidator(function ($answer) use ($email, $password) {
+ if (trim($answer) == '') {
+ throw new \RuntimeException("The code cannot be empty.");
+ }
+ try {
+ $this->authenticateUser($email, $password, $answer);
+ }
+ catch (BadResponseException $e) {
+ // If there is a two-factor authentication error, show
+ // the error description that the server provides.
+ //
+ // A RuntimeException here causes the user to be asked
+ // again for their TOTP code.
+ if ($e->getResponse()->getHeader('X-Drupal-TFA')) {
+ $json = $e->getResponse()->json();
+ throw new \RuntimeException($json['error_description']);
+ }
+ else {
+ throw $e;
+ }
+ }
+
+ return $answer;
+ });
+ $question->setMaxAttempts(5);
+ $output->writeln("\nTwo-factor authentication is required.");
+ $helper->ask($input, $output, $question);
+ }
+ elseif ($e->getResponse()->getStatusCode() === 401) {
+ $output->writeln("\nLogin failed. Please check your credentials.\n");
+ $output->writeln("Forgot your password? Visit: https://accounts.platform.sh/user/password\n");
+ $this->configureAccount($input, $output);
+ }
+ else {
+ throw $e;
+ }
}
}
diff --git a/src/Command/Environment/EnvironmentBranchCommand.php b/src/Command/Environment/EnvironmentBranchCommand.php
index eb1aea595f..df1ab7b185 100644
--- a/src/Command/Environment/EnvironmentBranchCommand.php
+++ b/src/Command/Environment/EnvironmentBranchCommand.php
@@ -55,6 +55,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
{
$this->envArgName = 'parent';
$this->validateInput($input, true);
+ $selectedProject = $this->getSelectedProject();
$branchName = $input->getArgument('name');
if (empty($branchName)) {
@@ -62,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
// List environments.
return $this->runOtherCommand(
'environments',
- array('--project' => $this->getSelectedProject()->id)
+ array('--project' => $selectedProject->id)
);
}
$this->stdErr->writeln("You must specify the name of the new branch.");
@@ -79,7 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 1;
}
- if ($environment = $this->getEnvironment($machineName, $this->getSelectedProject())) {
+ if ($environment = $this->getEnvironment($machineName, $selectedProject)) {
$checkout = $this->getHelper('question')
->confirm(
"The environment $machineName already exists. Check out?",
@@ -129,6 +130,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
$activity = $selectedEnvironment->branch($branchName, $machineName);
+ // Clear the environments cache, as branching has started.
+ $this->clearEnvironmentsCache($selectedProject);
+
if ($projectRoot) {
$gitHelper = new GitHelper(new ShellHelper($this->stdErr));
$gitHelper->setDefaultRepositoryDir($projectRoot . '/' . LocalProject::REPOSITORY_DIR);
@@ -169,6 +173,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
"The environment $branchName has been branched.",
'Branching failed'
);
+
+ // Clear the environments cache again.
+ $this->clearEnvironmentsCache($selectedProject);
}
$build = $input->getOption('build');
diff --git a/src/Command/Environment/EnvironmentSshCommand.php b/src/Command/Environment/EnvironmentSshCommand.php
index 6c93a4f523..5ca9257e28 100644
--- a/src/Command/Environment/EnvironmentSshCommand.php
+++ b/src/Command/Environment/EnvironmentSshCommand.php
@@ -37,7 +37,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$sshUrl = $this->getSelectedEnvironment()
->getSshUrl($input->getOption('app'));
- if ($input->getOption('pipe') || !$this->isTerminal($output)) {
+ if ($input->getOption('pipe')) {
$output->write($sshUrl);
return 0;
diff --git a/src/Command/Local/LocalDrushAliasesCommand.php b/src/Command/Local/LocalDrushAliasesCommand.php
index 41c3583a3e..14701df0ef 100644
--- a/src/Command/Local/LocalDrushAliasesCommand.php
+++ b/src/Command/Local/LocalDrushAliasesCommand.php
@@ -50,7 +50,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$projectConfig = LocalProject::getProjectConfig($projectRoot);
$current_group = isset($projectConfig['alias-group']) ? $projectConfig['alias-group'] : $projectConfig['id'];
- if ($input->getOption('pipe') || !$this->isTerminal($output)) {
+ if ($input->getOption('pipe')) {
$output->writeln($current_group);
return 0;
diff --git a/src/Command/Local/LocalInitCommand.php b/src/Command/Local/LocalInitCommand.php
index edecd6cca3..980b3ec63f 100644
--- a/src/Command/Local/LocalInitCommand.php
+++ b/src/Command/Local/LocalInitCommand.php
@@ -56,6 +56,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 1;
}
$gitUrl = $project->getGitUrl();
+ $projectId = $project->id;
}
$inside = strpos(getcwd(), $realPath) === 0;
diff --git a/src/Command/PlatformCommand.php b/src/Command/PlatformCommand.php
index 652a4d6ea5..6e92ed4ccb 100644
--- a/src/Command/PlatformCommand.php
+++ b/src/Command/PlatformCommand.php
@@ -274,12 +274,13 @@ public function isLocal()
*
* @param string $email The user's email.
* @param string $password The user's password.
+ * @param string $totp The user's TFA one-time password.
*/
- protected function authenticateUser($email, $password)
+ protected function authenticateUser($email, $password, $totp = null)
{
$this->getClient(false)
->getConnector()
- ->logIn($email, $password, true);
+ ->logIn($email, $password, true, $totp);
}
/**
@@ -450,6 +451,14 @@ public function getProjects($refresh = false)
*/
protected function getProject($id, $host = null, $refresh = false)
{
+ // Allow the specified project to be a full URL.
+ if (strpos($id, '//') !== false) {
+ $url = $id;
+ $id = basename($url);
+ $host = parse_url($url, PHP_URL_HOST);
+ }
+
+ // Find the project in the user's main project list.
$projects = $this->getProjects($refresh);
if (isset($projects[$id])) {
return $projects[$id];
@@ -553,7 +562,7 @@ protected function getEnvironment($id, Project $project = null, $refresh = false
*
* @param Project $project
*/
- protected function clearEnvironmentsCache(Project $project = null)
+ public function clearEnvironmentsCache(Project $project = null)
{
$project = $project ?: $this->getSelectedProject();
self::$cache->delete('environments:' . $project->id);
diff --git a/src/Command/Project/ProjectGetCommand.php b/src/Command/Project/ProjectGetCommand.php
index b3a04be5d6..e7c888346b 100644
--- a/src/Command/Project/ProjectGetCommand.php
+++ b/src/Command/Project/ProjectGetCommand.php
@@ -122,7 +122,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$local = new LocalProject();
$hostname = parse_url($project->getUri(), PHP_URL_HOST) ?: null;
- $local->createProjectFiles($projectRoot, $projectId, $hostname);
+ $local->createProjectFiles($projectRoot, $project->id, $hostname);
$environments = $this->getEnvironments($project, true);
diff --git a/src/Command/Project/ProjectListCommand.php b/src/Command/Project/ProjectListCommand.php
index 990073f870..ae8e637824 100644
--- a/src/Command/Project/ProjectListCommand.php
+++ b/src/Command/Project/ProjectListCommand.php
@@ -37,7 +37,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$projects = $this->getProjects($refresh);
- if ($input->getOption('pipe') || !$this->isTerminal($output)) {
+ if ($input->getOption('pipe')) {
$output->writeln(array_keys($projects));
return 0;
diff --git a/src/Command/User/UserRoleCommand.php b/src/Command/User/UserRoleCommand.php
index f934b996be..1edef4a3e1 100644
--- a/src/Command/User/UserRoleCommand.php
+++ b/src/Command/User/UserRoleCommand.php
@@ -89,7 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->stdErr->writeln("User $email updated");
}
- if ($input->getOption('pipe') || !$this->isTerminal($output)) {
+ if ($input->getOption('pipe')) {
if ($level == 'project') {
$output->writeln($selectedUser->role);
} elseif ($level == 'environment') {
diff --git a/src/Command/Variable/VariableGetCommand.php b/src/Command/Variable/VariableGetCommand.php
index 987b84daf1..a9c45f3ee5 100644
--- a/src/Command/Variable/VariableGetCommand.php
+++ b/src/Command/Variable/VariableGetCommand.php
@@ -76,7 +76,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
}
- if ($input->getOption('pipe') || !$this->isTerminal($output)) {
+ if ($input->getOption('pipe')) {
foreach ($results as $variable) {
$output->writeln($variable['id'] . "\t" . $variable['value']);
}
diff --git a/src/Console/EventSubscriber.php b/src/Console/EventSubscriber.php
index d945563b31..38f9e8aaae 100644
--- a/src/Console/EventSubscriber.php
+++ b/src/Console/EventSubscriber.php
@@ -4,9 +4,11 @@
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ParseException;
+use Platformsh\Cli\Command\PlatformCommand;
use Platformsh\Cli\Exception\ConnectionFailedException;
use Platformsh\Cli\Exception\LoginRequiredException;
use Platformsh\Cli\Exception\PermissionDeniedException;
+use Platformsh\Client\Exception\EnvironmentStateException;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -77,5 +79,14 @@ public function onException(ConsoleExceptionEvent $event)
$event->stopPropagation();
}
}
+
+ // When an environment is found to be in the wrong state, perhaps our
+ // cache is old - we should invalidate it.
+ if ($exception instanceof EnvironmentStateException) {
+ $command = $event->getCommand();
+ if ($command instanceof PlatformCommand) {
+ $command->clearEnvironmentsCache();
+ }
+ }
}
}