Hello community,
here is the log from the commit of package platformsh-cli for openSUSE:Factory checked in at 2019-01-21 11:00:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/platformsh-cli (Old)
and /work/SRC/openSUSE:Factory/.platformsh-cli.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "platformsh-cli"
Mon Jan 21 11:00:46 2019 rev:61 rq:667022 version:3.39.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/platformsh-cli/platformsh-cli.changes 2019-01-10 15:23:37.638312692 +0100
+++ /work/SRC/openSUSE:Factory/.platformsh-cli.new.28833/platformsh-cli.changes 2019-01-21 11:02:29.767109127 +0100
@@ -1,0 +2,14 @@
+Fri Jan 18 16:06:31 UTC 2019 - jimmy@boombatower.com
+
+- Update to version 3.39.0:
+ * Release v3.39.0
+ * Add tunnel:single command: allow opening a tunnel without posix/pcntl (#768)
+ * Drush 9 compatibility: move alias auto-remove key under "options"
+ * Remove Drush 9-beta compatibility
+ * Fix: login does not sufficiently log out from old account (#770)
+ * Account for differing app roots in Drush aliases
+ * Unused function
+ * Release v3.38.1
+ * Fix incorrect "You cannot use both the <environment> argument and the --environment option" error in environment:activate command
+
+-------------------------------------------------------------------
Old:
----
platformsh-cli-3.38.0.tar.xz
New:
----
platformsh-cli-3.39.0.tar.xz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ platformsh-cli.spec ++++++
--- /var/tmp/diff_new_pack.ASgxs6/_old 2019-01-21 11:02:30.659107979 +0100
+++ /var/tmp/diff_new_pack.ASgxs6/_new 2019-01-21 11:02:30.659107979 +0100
@@ -17,7 +17,7 @@
Name: platformsh-cli
-Version: 3.38.0
+Version: 3.39.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.ASgxs6/_old 2019-01-21 11:02:30.683107948 +0100
+++ /var/tmp/diff_new_pack.ASgxs6/_new 2019-01-21 11:02:30.683107948 +0100
@@ -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.38.0</param>
+ <param name="revision">refs/tags/v3.39.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.ASgxs6/_old 2019-01-21 11:02:30.699107927 +0100
+++ /var/tmp/diff_new_pack.ASgxs6/_new 2019-01-21 11:02:30.703107922 +0100
@@ -1,6 +1,6 @@
<servicedata>
<service name="tar_scm">
<param name="url">git://github.com/platformsh/platformsh-cli.git</param>
- <param name="changesrevision">89037ac97082af56f5346c0fc5dfeb3d8a2d0061</param>
+ <param name="changesrevision">d0dd97e777b3c7b2fe5432004477ef3eb9f44fcf</param>
</service>
</servicedata>
++++++ licenses.txt ++++++
--- /var/tmp/diff_new_pack.ASgxs6/_old 2019-01-21 11:02:30.731107886 +0100
+++ /var/tmp/diff_new_pack.ASgxs6/_new 2019-01-21 11:02:30.735107880 +0100
@@ -17,7 +17,7 @@
padraic/humbug_get_contents 1.1.2 BSD-3-Clause
padraic/phar-updater v1.0.6 BSD-3-Clause
paragonie/random_compat v2.0.17 MIT
-platformsh/client v0.23.0 MIT
+platformsh/client v0.23.2 MIT
platformsh/console-form v0.0.23 MIT
psr/container 1.0.0 MIT
psr/log 1.1.0 MIT
++++++ platformsh-cli-3.38.0.tar.xz -> platformsh-cli-3.39.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/README.md new/platformsh-cli-3.39.0/README.md
--- old/platformsh-cli-3.38.0/README.md 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/README.md 2019-01-18 12:39:27.000000000 +0100
@@ -184,6 +184,7 @@
tunnel:info View relationship info for SSH tunnels
tunnel:list (tunnels) List SSH tunnels
tunnel:open Open SSH tunnels to an app's relationships
+ tunnel:single Open a single SSH tunnel to an app relationship
user
user:add Add a user to the project
user:delete Delete a user from the project
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/composer.json new/platformsh-cli-3.39.0/composer.json
--- old/platformsh-cli-3.38.0/composer.json 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/composer.json 2019-01-18 12:39:27.000000000 +0100
@@ -8,7 +8,7 @@
"guzzlehttp/guzzle": "^5.3",
"guzzlehttp/ringphp": "^1.1",
"platformsh/console-form": ">=0.0.22 <2.0",
- "platformsh/client": ">=0.23.0 <2.0",
+ "platformsh/client": ">=0.23.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.38.0/composer.lock new/platformsh-cli-3.39.0/composer.lock
--- old/platformsh-cli-3.38.0/composer.lock 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/composer.lock 2019-01-18 12:39:27.000000000 +0100
@@ -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": "7316086542b599673424191e18406f6a",
+ "content-hash": "b2769ae9f93d12b94dc0614825c1d4b6",
"packages": [
{
"name": "cocur/slugify",
@@ -712,16 +712,16 @@
},
{
"name": "platformsh/client",
- "version": "v0.23.0",
+ "version": "v0.23.2",
"source": {
"type": "git",
"url": "https://github.com/platformsh/platformsh-client-php.git",
- "reference": "9b0fc3004d5abdc3cc54b0bea80f779e23660289"
+ "reference": "028f723d4581b09c28f2ab5029b51a2e9363bd15"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/9b0fc3...",
- "reference": "9b0fc3004d5abdc3cc54b0bea80f779e23660289",
+ "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/028f72...",
+ "reference": "028f723d4581b09c28f2ab5029b51a2e9363bd15",
"shasum": ""
},
"require": {
@@ -757,7 +757,7 @@
}
],
"description": "Platform.sh API client",
- "time": "2019-01-09T14:36:49+00:00"
+ "time": "2019-01-16T17:37:27+00:00"
},
{
"name": "platformsh/console-form",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/config.yaml new/platformsh-cli-3.39.0/config.yaml
--- old/platformsh-cli-3.38.0/config.yaml 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/config.yaml 2019-01-18 12:39:27.000000000 +0100
@@ -1,7 +1,7 @@
# Metadata about the CLI application itself.
application:
name: 'Platform.sh CLI'
- version: '3.38.0'
+ version: '3.39.0'
executable: 'platform'
package_name: 'platformsh/cli'
installer_url: 'https://platform.sh/cli/installer'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/dist/manifest.json new/platformsh-cli-3.39.0/dist/manifest.json
--- old/platformsh-cli-3.38.0/dist/manifest.json 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/dist/manifest.json 2019-01-18 12:39:27.000000000 +0100
@@ -1,10 +1,10 @@
[
{
"name": "platform.phar",
- "sha1": "5144fb0283e09583c7f5933324168194b6df4e61",
- "sha256": "f6aa40652c031374338fdedc2b5fe45419477cb8d8c688639819f8cac11ddd13",
- "url": "https://github.com/platformsh/platformsh-cli/releases/download/v3.38.0/platf...",
- "version": "3.38.0",
+ "sha1": "a522451e18a9aa6d50c93b76903e13efd7a4c9a6",
+ "sha256": "f764ee7f83e659276e2876e3b80fda134045ab2ed9192d7f8574f2d56d559b28",
+ "url": "https://github.com/platformsh/platformsh-cli/releases/download/v3.39.0/platf...",
+ "version": "3.39.0",
"php": {
"min": "5.5.9"
},
@@ -162,6 +162,11 @@
"notes": "* Allow restoring snapshots to another environment in snapshot:restore command\n (with new --target and --branch-from options).\n* Sort interactive project/environment choices alphabetically.\n* Fix \"Name\"/\"name\" column should always have been \"Title\"/\"title\" in\n environment:list command.\n* Define \"created\" and \"updated\" columns in environment:list command.\n* Remove interactivity check from installer.\n* Fix: environments with an empty name (e.g. '0') not being recognized.\n* Fix documentation in user:list, user:add and user:update commands.\n* Various changes and new features in certificate:list command:\n - only show non-expired certificates by default\n - add --ignore-expiry option for the previous behavior\n - add --exclude-domain option to filter out certificates from the list\n - add --pipe-domains option to list domains covered by certificates\n - add \"certs\" alias\n - define \"domains\" column (for the --columns option)",
"show from": "3.37.0",
"hide from": "3.38.0"
+ },
+ {
+ "notes": "New features:\n\n* Add tunnel:single command (allow opening a tunnel without posix/pcntl).\n\nBug fixes:\n\n* Fix bug where login to a new account does not log out from the old account.\n* Account for differing app roots in Drush aliases - allows generated Drush\n aliases to work with Platform.sh Enterprise environments.\n* Fix Drush 9 alias compatibility (and remove Drush 9-beta compatibility).",
+ "show from": "3.38.0",
+ "hide from": "3.39.0"
}
]
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Application.php new/platformsh-cli-3.39.0/src/Application.php
--- old/platformsh-cli-3.38.0/src/Application.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Application.php 2019-01-18 12:39:27.000000000 +0100
@@ -190,6 +190,7 @@
$commands[] = new Command\Tunnel\TunnelInfoCommand();
$commands[] = new Command\Tunnel\TunnelListCommand();
$commands[] = new Command\Tunnel\TunnelOpenCommand();
+ $commands[] = new Command\Tunnel\TunnelSingleCommand();
$commands[] = new Command\User\UserAddCommand();
$commands[] = new Command\User\UserDeleteCommand();
$commands[] = new Command\User\UserListCommand();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Command/Auth/BrowserLoginCommand.php new/platformsh-cli-3.39.0/src/Command/Auth/BrowserLoginCommand.php
--- old/platformsh-cli-3.38.0/src/Command/Auth/BrowserLoginCommand.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Command/Auth/BrowserLoginCommand.php 2019-01-18 12:39:27.000000000 +0100
@@ -208,13 +208,18 @@
$this->stdErr->writeln('Login information received. Verifying...');
$token = $this->getAccessToken($code, $localUrl, $tokenUrl);
- // Finalize login: clear the cache and save the new credentials.
+ // Finalize login: call logOut() on the old connector, clear the cache
+ // and save the new credentials.
+ $connector = $this->api()->getClient(false)->getConnector();
+ $session = $connector->getSession();
+ $connector->logOut();
+
/** @var \Doctrine\Common\Cache\CacheProvider $cache */
$cache = $this->getService('cache');
$cache->flushAll();
// Save the new tokens to the persistent session.
- $this->saveAccessToken($token, $this->api()->getClient(false)->getConnector()->getSession());
+ $this->saveAccessToken($token, $session);
// Reset the API client so that it will use the new tokens.
$client = $this->api()->getClient(false, true);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Command/CommandBase.php new/platformsh-cli-3.39.0/src/Command/CommandBase.php
--- old/platformsh-cli-3.38.0/src/Command/CommandBase.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Command/CommandBase.php 2019-01-18 12:39:27.000000000 +0100
@@ -974,7 +974,9 @@
// Select the environment.
$envOptionName = 'environment';
- if ($input->hasArgument($this->envArgName) && $input->getArgument($this->envArgName) !== null) {
+ if ($input->hasArgument($this->envArgName)
+ && $input->getArgument($this->envArgName) !== null
+ && $input->getArgument($this->envArgName) !== []) {
if ($input->hasOption($envOptionName) && $input->getOption($envOptionName) !== null) {
throw new ConsoleInvalidArgumentException(
sprintf(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelCloseCommand.php new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelCloseCommand.php
--- old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelCloseCommand.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelCloseCommand.php 2019-01-18 12:39:27.000000000 +0100
@@ -20,7 +20,6 @@
protected function execute(InputInterface $input, OutputInterface $output)
{
- $this->checkSupport();
$tunnels = $this->getTunnelInfo();
$allTunnelsCount = count($tunnels);
if (!$allTunnelsCount) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelCommandBase.php new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelCommandBase.php
--- old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelCommandBase.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelCommandBase.php 2019-01-18 12:39:27.000000000 +0100
@@ -15,19 +15,6 @@
protected $tunnelInfo;
protected $canBeRunMultipleTimes = false;
- public function checkSupport()
- {
- $messages = [];
- foreach (['pcntl', 'posix'] as $extension) {
- if (!extension_loaded($extension)) {
- $messages[] = sprintf('The "%s" extension is required.', $extension);
- }
- }
- if (count($messages)) {
- throw new \RuntimeException(implode("\n", $messages));
- }
- }
-
/**
* Check whether a tunnel is already open.
*
@@ -257,7 +244,7 @@
}
$project = $this->getSelectedProject();
$environment = $this->hasSelectedEnvironment() ? $this->getSelectedEnvironment() : null;
- $appName = $this->selectApp($input);
+ $appName = $this->hasSelectedEnvironment() ? $this->selectApp($input) : null;
foreach ($tunnels as $key => $tunnel) {
if ($tunnel['projectId'] !== $project->id
|| ($environment !== null && $tunnel['environmentId'] !== $environment->id)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelInfoCommand.php new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelInfoCommand.php
--- old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelInfoCommand.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelInfoCommand.php 2019-01-18 12:39:27.000000000 +0100
@@ -23,9 +23,6 @@
protected function execute(InputInterface $input, OutputInterface $output)
{
- $this->checkSupport();
- $this->validateInput($input);
-
$tunnels = $this->getTunnelInfo();
$relationships = [];
foreach ($this->filterTunnels($tunnels, $input) as $key => $tunnel) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelListCommand.php new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelListCommand.php
--- old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelListCommand.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelListCommand.php 2019-01-18 12:39:27.000000000 +0100
@@ -23,7 +23,6 @@
protected function execute(InputInterface $input, OutputInterface $output)
{
- $this->checkSupport();
$tunnels = $this->getTunnelInfo();
$allTunnelsCount = count($tunnels);
if (!$allTunnelsCount) {
@@ -31,12 +30,19 @@
return 1;
}
+ $executable = $this->config()->get('application.executable');
+
// Filter tunnels according to the current project and environment, if
// available.
if (!$input->getOption('all')) {
$tunnels = $this->filterTunnels($tunnels, $input);
if (!count($tunnels)) {
- $this->stdErr->writeln('No tunnels found. Use --all to view all tunnels.');
+ $this->stdErr->writeln('No tunnels found.');
+ $this->stdErr->writeln(sprintf(
+ 'List all tunnels with: <info>%s tunnels --all</info>',
+ $executable
+ ));
+
return 1;
}
}
@@ -57,7 +63,6 @@
$table->render($rows, $headers);
if (!$table->formatIsMachineReadable()) {
- $executable = $this->config()->get('application.executable');
$this->stdErr->writeln('');
if (!$input->getOption('all') && count($tunnels) < $allTunnelsCount) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelOpenCommand.php new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelOpenCommand.php
--- old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelOpenCommand.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelOpenCommand.php 2019-01-18 12:39:27.000000000 +0100
@@ -20,6 +20,19 @@
$this->addEnvironmentOption();
$this->addAppOption();
Ssh::configureInput($this->getDefinition());
+ $this->setHelp(<<confirm($confirmText, false)) {
return 1;
}
+ $this->stdErr->writeln('');
}
$appName = $this->selectApp($input);
@@ -135,8 +149,8 @@
$this->saveTunnelInfo();
$this->stdErr->writeln(sprintf(
- 'SSH tunnel opened on port %s to relationship: <info>%s</info>',
- $localPort,
+ 'SSH tunnel opened on port <info>%s</info> to relationship: <info>%s</info>',
+ $tunnel['localPort'],
$relationshipString
));
$processIds[] = $pid;
@@ -167,4 +181,17 @@
return 0;
}
+
+ private function checkSupport()
+ {
+ $messages = [];
+ foreach (['pcntl', 'posix'] as $extension) {
+ if (!extension_loaded($extension)) {
+ $messages[] = sprintf('The "%s" extension is required.', $extension);
+ }
+ }
+ if (count($messages)) {
+ throw new \RuntimeException(implode("\n", $messages));
+ }
+ }
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelSingleCommand.php new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelSingleCommand.php
--- old/platformsh-cli-3.38.0/src/Command/Tunnel/TunnelSingleCommand.php 1970-01-01 01:00:00.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Command/Tunnel/TunnelSingleCommand.php 2019-01-18 12:39:27.000000000 +0100
@@ -0,0 +1,202 @@
+setName('tunnel:single')
+ ->setDescription('Open a single SSH tunnel to an app relationship')
+ ->addOption('port', null, InputOption::VALUE_REQUIRED, 'The local port');
+ $this->addProjectOption();
+ $this->addEnvironmentOption();
+ $this->addAppOption();
+ Relationships::configureInput($this->getDefinition());
+ Ssh::configureInput($this->getDefinition());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->validateInput($input);
+ $project = $this->getSelectedProject();
+ $environment = $this->getSelectedEnvironment();
+
+ $appName = $this->selectApp($input);
+ $sshUrl = $environment->getSshUrl($appName);
+
+ /** @var \Platformsh\Cli\Service\Relationships $relationshipsService */
+ $relationshipsService = $this->getService('relationships');
+ $relationships = $relationshipsService->getRelationships($sshUrl);
+ if (!$relationships) {
+ $this->stdErr->writeln('No relationships found.');
+ return 1;
+ }
+
+ $service = $relationshipsService->chooseService($sshUrl, $input, $output);
+ if (!$service) {
+ return 1;
+ }
+
+ if ($environment->id === 'master') {
+ /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */
+ $questionHelper = $this->getService('question_helper');
+ $confirmText = sprintf(
+ 'Are you sure you want to open an SSH tunnel to'
+ . ' the relationship <comment>%s</comment> on the'
+ . ' <comment>%s</comment> (production) environment?',
+ $service['_relationship_name'],
+ $environment->id
+ );
+ if (!$questionHelper->confirm($confirmText, false)) {
+ return 1;
+ }
+ $this->stdErr->writeln('');
+ }
+
+ /** @var \Platformsh\Cli\Service\Ssh $ssh */
+ $ssh = $this->getService('ssh');
+ $sshArgs = $ssh->getSshArgs();
+
+ $remoteHost = $service['host'];
+ $remotePort = $service['port'];
+
+ if ($localPort = $input->getOption('port')) {
+ if (!PortUtil::validatePort($localPort)) {
+ $this->stdErr->writeln(sprintf('Invalid port: <error>%s</error>', $localPort));
+
+ return 1;
+ }
+ if (PortUtil::isPortInUse($localPort)) {
+ $this->stdErr->writeln(sprintf('Port already in use: <error>%s</error>', $localPort));
+
+ return 1;
+ }
+ } else {
+ $localPort = $this->getPort();
+ }
+
+ $tunnel = [
+ 'projectId' => $project->id,
+ 'environmentId' => $environment->id,
+ 'appName' => $appName,
+ 'relationship' => $service['_relationship_name'],
+ 'serviceKey' => $service['_relationship_key'],
+ 'remotePort' => $remotePort,
+ 'remoteHost' => $remoteHost,
+ 'localPort' => $localPort,
+ 'service' => $service,
+ 'pid' => null,
+ ];
+
+ $relationshipString = $this->formatTunnelRelationship($tunnel);
+
+ if ($openTunnelInfo = $this->isTunnelOpen($tunnel)) {
+ $this->stdErr->writeln(sprintf(
+ 'A tunnel is already open for the relationship <info>%s</info> (on port %s)',
+ $relationshipString,
+ $openTunnelInfo['localPort']
+ ));
+
+ return 1;
+ }
+
+ $pidFile = $this->getPidFile($tunnel);
+
+ $processManager = new ProcessManager();
+ $process = $this->createTunnelProcess($sshUrl, $remoteHost, $remotePort, $localPort, $sshArgs);
+ $pid = $processManager->startProcess($process, $pidFile, $this->stdErr);
+
+ // Wait a very small time to capture any immediate errors.
+ usleep(100000);
+ if (!$process->isRunning() && !$process->isSuccessful()) {
+ $this->stdErr->writeln(trim($process->getErrorOutput()));
+ $this->stdErr->writeln(sprintf(
+ 'Failed to open tunnel for relationship: <error>%s</error>',
+ $relationshipString
+ ));
+ unlink($pidFile);
+
+ return 1;
+ }
+
+ $tunnel['pid'] = $pid;
+ $this->tunnelInfo[] = $tunnel;
+ $this->saveTunnelInfo();
+
+ $this->stdErr->writeln('');
+
+ $this->stdErr->writeln(sprintf(
+ 'SSH tunnel opened on port %s to relationship: <info>%s</info>',
+ $tunnel['localPort'],
+ $relationshipString
+ ));
+
+ $localService = array_merge($service, array_intersect_key([
+ 'host' => self::LOCAL_IP,
+ 'port' => $tunnel['localPort'],
+ ], $service));
+ $info = [
+ 'username' => 'Username',
+ 'password' => 'Password',
+ 'scheme' => 'Scheme',
+ 'host' => 'Host',
+ 'port' => 'Port',
+ 'path' => 'Path',
+ ];
+ foreach ($info as $key => $category) {
+ if (isset($localService[$key])) {
+ $this->stdErr->writeln(sprintf(' <info>%s</info>: %s', $category, $localService[$key]));
+ }
+ }
+
+ $this->stdErr->writeln('');
+
+ if (isset($localService['scheme']) && in_array($localService['scheme'], ['http', 'https'], true)) {
+ $this->stdErr->writeln(sprintf('URL: <info>%s</info>', $this->getServiceUrl($localService)));
+ $this->stdErr->writeln('');
+ }
+
+ $this->stdErr->writeln('Quitting this command (with Ctrl+C or equivalent) will close the tunnel.');
+
+ $this->stdErr->writeln('');
+
+ $processManager->monitor($this->stdErr);
+
+ return $process->isSuccessful() ? 0 : 1;
+ }
+
+ /**
+ * Build a URL to a service.
+ *
+ * @param array $service
+ *
+ * @return string
+ */
+ private function getServiceUrl(array $service)
+ {
+ $map = ['username' => 'user', 'password' => 'pass'];
+ $urlParts = [];
+ foreach ($service as $key => $value) {
+ $newKey = isset($map[$key]) ? $map[$key] : $key;
+ $urlParts[$newKey] = $value;
+ }
+
+ return Url::buildUrl($urlParts);
+ }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Console/ProcessManager.php new/platformsh-cli-3.39.0/src/Console/ProcessManager.php
--- old/platformsh-cli-3.38.0/src/Console/ProcessManager.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Console/ProcessManager.php 2019-01-18 12:39:27.000000000 +0100
@@ -163,11 +163,17 @@
// If the process has been stopped via another method, remove it
// from the list, and log a message.
$exitCode = $process->getExitCode();
- if ($exitCode === 143 || $exitCode === 147) {
- $log->writeln(sprintf('Process killed: %s', $process->getCommandLine()));
+ if ($signal = $this->getSignal($exitCode)) {
+ $log->writeln(sprintf('Process stopped with signal %s: %s', $signal, $process->getCommandLine()));
} elseif ($exitCode > 0) {
$log->writeln(sprintf(
- 'Process stopped unexpectedly with exit code %s: %s',
+ 'Process failed with exit code %s: %s',
+ $exitCode,
+ $process->getCommandLine()
+ ));
+ } else {
+ $log->writeln(sprintf(
+ 'Process stopped with exit code %s: %s',
$exitCode,
$process->getCommandLine()
));
@@ -178,4 +184,55 @@
}
}
}
+
+ /**
+ * @param int $exitCode
+ *
+ * @return string|false
+ */
+ private function getSignal($exitCode)
+ {
+ if ($exitCode < 128 || $exitCode > 162) {
+ return false;
+ }
+
+ $signals = [
+ 1 => 'SIGHUP',
+ 2 => 'SIGINT',
+ 3 => 'SIGQUIT',
+ 4 => 'SIGILL',
+ 5 => 'SIGTRAP',
+ 6 => 'SIGABRT',
+ 7 => 'SIGEMT',
+ 8 => 'SIGFPE',
+ 9 => 'SIGKILL',
+ 10 => 'SIGBUS',
+ 11 => 'SIGSEGV',
+ 12 => 'SIGSYS',
+ 13 => 'SIGPIPE',
+ 14 => 'SIGALRM',
+ 15 => 'SIGTERM',
+ 16 => 'SIGUSR1',
+ 17 => 'SIGUSR2',
+ 18 => 'SIGCHLD',
+ 19 => 'SIGPWR',
+ 20 => 'SIGWINCH',
+ 21 => 'SIGURG',
+ 22 => 'SIGPOLL',
+ 23 => 'SIGSTOP',
+ 24 => 'SIGTSTP',
+ 25 => 'SIGCONT',
+ 26 => 'SIGTTIN',
+ 27 => 'SIGTTOU',
+ 28 => 'SIGVTALRM',
+ 29 => 'SIGPROF',
+ 30 => 'SIGXCPU',
+ 31 => 'SIGXFSZ',
+ 32 => 'SIGWAITING',
+ 33 => 'SIGLWP',
+ 34 => 'SIGAIO',
+ ];
+
+ return $signals[$exitCode - 128];
+ }
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Service/Drush.php new/platformsh-cli-3.39.0/src/Service/Drush.php
--- old/platformsh-cli-3.38.0/src/Service/Drush.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Service/Drush.php 2019-01-18 12:39:27.000000000 +0100
@@ -255,18 +255,6 @@
}
/**
- * @return string
- */
- protected function getAutoRemoveKey()
- {
- return preg_replace(
- '/[^a-z-]+/',
- '-',
- str_replace('.', '', strtolower($this->config->get('application.name')))
- ) . '-auto-remove';
- }
-
- /**
* Get the alias group for a project.
*
* @param Project $project
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/Service/Relationships.php new/platformsh-cli-3.39.0/src/Service/Relationships.php
--- old/platformsh-cli-3.38.0/src/Service/Relationships.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/Service/Relationships.php 2019-01-18 12:39:27.000000000 +0100
@@ -75,31 +75,66 @@
}
if (empty($relationships)) {
- $stdErr->writeln(sprintf('No relationships found matching scheme(s): <error>%s</error>.', implode(', ', $schemes)));
+ if (!empty($schemes)) {
+ $stdErr->writeln(sprintf('No relationships found matching scheme(s): <error>%s</error>.', implode(', ', $schemes)));
+ } else {
+ $stdErr->writeln(sprintf('No relationships found'));
+ }
return false;
}
+ // Collapse relationships and services into a flat list.
+ $choices = [];
+ foreach ($relationships as $name => $relationship) {
+ $serviceCount = count($relationship);
+ foreach ($relationship as $key => $service) {
+ $identifier = $name . ($serviceCount > 1 ? '.' . $key : '');
+ $choices[$identifier] = $identifier;
+ }
+ }
+
// Use the --relationship option, if specified.
+ $identifier = false;
if ($input->hasOption('relationship')
&& ($relationshipName = $input->getOption('relationship'))) {
- if (!isset($relationships[$relationshipName])) {
- $stdErr->writeln('Relationship not found: ' . $relationshipName);
+ // Normalise the relationship name to remove a trailing ".0".
+ if (substr($relationshipName, -2) === '.0') {
+ $relationshipName = substr($relationshipName, 0, strlen($relationshipName) - 2);
+ }
+ if (!isset($choices[$relationshipName])) {
+ $stdErr->writeln('Relationship not found: <error>' . $relationshipName . '</error>');
return false;
}
- $relationships = array_intersect_key($relationships, [$relationshipName => true]);
+ $identifier = $relationshipName;
}
- $questionHelper = new QuestionHelper($input, $output);
- $choices = [];
- $separator = '.';
- foreach ($relationships as $name => $relationship) {
- $serviceCount = count($relationship);
- foreach ($relationship as $key => $service) {
- $choices[$name . $separator . $key] = $name . ($serviceCount > 1 ? '.' . $key : '');
+ if (!$identifier && count($choices) === 1) {
+ $identifier = reset($choices);
+ }
+
+ if (!$identifier && !$input->isInteractive()) {
+ $stdErr->writeln('More than one relationship found.');
+ if ($input->hasOption('relationship')) {
+ $stdErr->writeln('Use the <error>--relationship</error> (-r) option to specify a relationship. Options:');
+ foreach (array_keys($choices) as $identifier) {
+ $stdErr->writeln(' ' . $identifier);
+ }
}
+ return false;
+ }
+
+ if (!$identifier) {
+ $questionHelper = new QuestionHelper($input, $output);
+ $identifier = $questionHelper->choose($choices, 'Enter a number to choose a relationship:');
+ $stdErr->writeln('');
+ }
+
+ if (strpos($identifier, '.') !== false) {
+ list($name, $key) = explode('.', $identifier, 2);
+ } else {
+ $name = $identifier;
+ $key = 0;
}
- $choice = $questionHelper->choose($choices, 'Enter a number to choose a relationship:');
- list($name, $key) = explode($separator, $choice, 2);
$service = $relationships[$name][$key];
// Add metadata about the service.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/SiteAlias/DrushAlias.php new/platformsh-cli-3.39.0/src/SiteAlias/DrushAlias.php
--- old/platformsh-cli-3.38.0/src/SiteAlias/DrushAlias.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/SiteAlias/DrushAlias.php 2019-01-18 12:39:27.000000000 +0100
@@ -60,7 +60,7 @@
$autoRemoveKey = $this->getAutoRemoveKey();
$userDefinedAliases = [];
foreach ($existingAliases as $name => $alias) {
- if (!empty($alias[$autoRemoveKey])) {
+ if (!empty($alias[$autoRemoveKey]) || !empty($alias['options'][$autoRemoveKey])) {
// This is probably for a deleted environment.
continue;
}
@@ -220,7 +220,9 @@
{
return [
'root' => $app->getLocalWebRoot(),
- $this->getAutoRemoveKey() => true,
+ 'options' => [
+ $this->getAutoRemoveKey() => true,
+ ],
];
}
@@ -238,9 +240,13 @@
return false;
}
+ // The 'root' can be a relative path, relative to the home directory.
+ // Conveniently, the home directory is the same as the app root.
$alias = [
- 'root' => '/app/' . $app->getDocumentRoot(),
- $this->getAutoRemoveKey() => true,
+ 'root' => $app->getDocumentRoot(),
+ 'options' => [
+ $this->getAutoRemoveKey() => true,
+ ],
];
$sshUrl = $environment->getSshUrl($app->getName());
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/src/SiteAlias/DrushYaml.php new/platformsh-cli-3.39.0/src/SiteAlias/DrushYaml.php
--- old/platformsh-cli-3.38.0/src/SiteAlias/DrushYaml.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/src/SiteAlias/DrushYaml.php 2019-01-18 12:39:27.000000000 +0100
@@ -12,13 +12,6 @@
*/
protected function getFilename($groupName)
{
- // Preserve backwards compatibility for Drush 9-beta.
- // See issue https://github.com/platformsh/platformsh-cli/issues/655
- $version = $this->drush->getVersion();
- if ($version !== false && version_compare($version, '8', '>') && version_compare($version, '9.0.0-rc1', '<')) {
- return $this->drush->getSiteAliasDir() . '/' . $groupName . '.alias.yml';
- }
-
return $this->drush->getSiteAliasDir() . '/' . $groupName . '.site.yml';
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/platformsh-cli-3.38.0/tests/Service/DrushServiceTest.php new/platformsh-cli-3.39.0/tests/Service/DrushServiceTest.php
--- old/platformsh-cli-3.38.0/tests/Service/DrushServiceTest.php 2019-01-09 15:57:31.000000000 +0100
+++ new/platformsh-cli-3.39.0/tests/Service/DrushServiceTest.php 2019-01-18 12:39:27.000000000 +0100
@@ -75,8 +75,8 @@
$this->assertArrayHasKey('_local', $aliases);
// Check that YAML aliases exist.
- $this->assertFileExists($homeDir . '/.drush/site-aliases/test.alias.yml');
- $aliases = Yaml::parse(file_get_contents($homeDir . '/.drush/site-aliases/test.alias.yml'));
+ $this->assertFileExists($homeDir . '/.drush/site-aliases/test.site.yml');
+ $aliases = Yaml::parse(file_get_contents($homeDir . '/.drush/site-aliases/test.site.yml'));
$this->assertArrayHasKey('master', $aliases);
$this->assertArrayHasKey('_local', $aliases);
}
@@ -145,8 +145,8 @@
$this->assertArrayHasKey('_local--drupal2', $aliases);
// Check that YAML aliases exist.
- $this->assertFileExists($homeDir . '/.drush/site-aliases/test.alias.yml');
- $aliases = Yaml::parse(file_get_contents($homeDir . '/.drush/site-aliases/test.alias.yml'));
+ $this->assertFileExists($homeDir . '/.drush/site-aliases/test.site.yml');
+ $aliases = Yaml::parse(file_get_contents($homeDir . '/.drush/site-aliases/test.site.yml'));
$this->assertArrayHasKey('master--drupal1', $aliases);
$this->assertArrayHasKey('_local--drupal1', $aliases);
$this->assertArrayHasKey('master--drupal2', $aliases);
++++++ 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 2019-01-09 19:05:00.765710176 +0100
+++ new/vendor/autoload.php 2019-01-18 17:06:36.218319811 +0100
@@ -4,4 +4,4 @@
require_once __DIR__ . '/composer/autoload_real.php';
-return ComposerAutoloaderInit434b3fb8d6425b9c0fdd81e20c14d3f7::getLoader();
+return ComposerAutoloaderInitf77d77365fc1f7ea6d2d32ddf8a94760::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 2019-01-09 19:05:00.765710176 +0100
+++ new/vendor/composer/autoload_real.php 2019-01-18 17:06:36.218319811 +0100
@@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
-class ComposerAutoloaderInit434b3fb8d6425b9c0fdd81e20c14d3f7
+class ComposerAutoloaderInitf77d77365fc1f7ea6d2d32ddf8a94760
{
private static $loader;
@@ -19,15 +19,15 @@
return self::$loader;
}
- spl_autoload_register(array('ComposerAutoloaderInit434b3fb8d6425b9c0fdd81e20c14d3f7', 'loadClassLoader'), true, true);
+ spl_autoload_register(array('ComposerAutoloaderInitf77d77365fc1f7ea6d2d32ddf8a94760', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
- spl_autoload_unregister(array('ComposerAutoloaderInit434b3fb8d6425b9c0fdd81e20c14d3f7', 'loadClassLoader'));
+ spl_autoload_unregister(array('ComposerAutoloaderInitf77d77365fc1f7ea6d2d32ddf8a94760', '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\ComposerStaticInit434b3fb8d6425b9c0fdd81e20c14d3f7::getInitializer($loader));
+ call_user_func(\Composer\Autoload\ComposerStaticInitf77d77365fc1f7ea6d2d32ddf8a94760::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
@@ -48,19 +48,19 @@
$loader->register(true);
if ($useStaticLoader) {
- $includeFiles = Composer\Autoload\ComposerStaticInit434b3fb8d6425b9c0fdd81e20c14d3f7::$files;
+ $includeFiles = Composer\Autoload\ComposerStaticInitf77d77365fc1f7ea6d2d32ddf8a94760::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
- composerRequire434b3fb8d6425b9c0fdd81e20c14d3f7($fileIdentifier, $file);
+ composerRequiref77d77365fc1f7ea6d2d32ddf8a94760($fileIdentifier, $file);
}
return $loader;
}
}
-function composerRequire434b3fb8d6425b9c0fdd81e20c14d3f7($fileIdentifier, $file)
+function composerRequiref77d77365fc1f7ea6d2d32ddf8a94760($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 2019-01-09 19:05:00.765710176 +0100
+++ new/vendor/composer/autoload_static.php 2019-01-18 17:06:36.218319811 +0100
@@ -4,7 +4,7 @@
namespace Composer\Autoload;
-class ComposerStaticInit434b3fb8d6425b9c0fdd81e20c14d3f7
+class ComposerStaticInitf77d77365fc1f7ea6d2d32ddf8a94760
{
public static $files = array (
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
@@ -201,9 +201,9 @@
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
- $loader->prefixLengthsPsr4 = ComposerStaticInit434b3fb8d6425b9c0fdd81e20c14d3f7::$prefixLengthsPsr4;
- $loader->prefixDirsPsr4 = ComposerStaticInit434b3fb8d6425b9c0fdd81e20c14d3f7::$prefixDirsPsr4;
- $loader->classMap = ComposerStaticInit434b3fb8d6425b9c0fdd81e20c14d3f7::$classMap;
+ $loader->prefixLengthsPsr4 = ComposerStaticInitf77d77365fc1f7ea6d2d32ddf8a94760::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitf77d77365fc1f7ea6d2d32ddf8a94760::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitf77d77365fc1f7ea6d2d32ddf8a94760::$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 2019-01-09 19:05:00.345706414 +0100
+++ new/vendor/composer/installed.json 2019-01-18 17:06:35.814316193 +0100
@@ -731,17 +731,17 @@
},
{
"name": "platformsh/client",
- "version": "v0.23.0",
- "version_normalized": "0.23.0.0",
+ "version": "v0.23.2",
+ "version_normalized": "0.23.2.0",
"source": {
"type": "git",
"url": "https://github.com/platformsh/platformsh-client-php.git",
- "reference": "9b0fc3004d5abdc3cc54b0bea80f779e23660289"
+ "reference": "028f723d4581b09c28f2ab5029b51a2e9363bd15"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/9b0fc3...",
- "reference": "9b0fc3004d5abdc3cc54b0bea80f779e23660289",
+ "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/028f72...",
+ "reference": "028f723d4581b09c28f2ab5029b51a2e9363bd15",
"shasum": ""
},
"require": {
@@ -754,7 +754,7 @@
"require-dev": {
"phpunit/phpunit": "~4.5"
},
- "time": "2019-01-09T14:36:49+00:00",
+ "time": "2019-01-16T17:37:27+00:00",
"type": "library",
"extra": {
"patches": {
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 2019-01-09 15:36:49.000000000 +0100
+++ new/vendor/platformsh/client/src/Connection/Connector.php 2019-01-16 18:37:27.000000000 +0100
@@ -33,9 +33,6 @@
/** @var SessionInterface */
protected $session;
- /** @var bool */
- protected $loggedOut = false;
-
/**
* @param array $config
* Possible configuration keys are:
@@ -117,7 +114,7 @@
*/
public function logOut()
{
- $this->loggedOut = true;
+ $this->oauth2Plugin = null;
try {
$this->revokeTokens();
@@ -156,9 +153,7 @@
public function __destruct()
{
- if ($this->loggedOut) {
- $this->session->clear();
- } elseif ($this->oauth2Plugin) {
+ if ($this->oauth2Plugin) {
// Save the access token for future requests.
$token = $this->getOauth2Plugin()->getAccessToken(false);
if ($token !== null) {
@@ -180,10 +175,10 @@
*/
public function logIn($username, $password, $force = false, $totp = null)
{
- $this->loggedOut = false;
if (!$force && $this->isLoggedIn() && $this->session->get('username') === $username) {
return;
}
+ $this->logOut();
$client = $this->getGuzzleClient([
'base_url' => $this->config['accounts'],
'defaults' => [