diff --git a/src/Command/Archive/ArchiveExportCommand.php b/src/Command/Archive/ArchiveExportCommand.php index e3eed864e..899f4b30e 100644 --- a/src/Command/Archive/ArchiveExportCommand.php +++ b/src/Command/Archive/ArchiveExportCommand.php @@ -26,7 +26,8 @@ protected function configure() ->addOption('file', 'f', InputOption::VALUE_REQUIRED, 'The filename for the archive') ->addOption('exclude-services', null, InputOption::VALUE_NONE, 'Exclude services') ->addOption('exclude-mounts', null, InputOption::VALUE_NONE, 'Exclude mounts') - ->addOption('include-variables', null, InputOption::VALUE_NONE, 'Include variables'); + ->addOption('include-variables', null, InputOption::VALUE_NONE, 'Include variables') + ->addOption('include-sensitive-values', null, InputOption::VALUE_NONE, 'Include sensitive variable values'); $this->addProjectOption(); $this->addEnvironmentOption(); } @@ -189,12 +190,31 @@ protected function execute(InputInterface $input, OutputInterface $output) ]; if ($includeVariables) { + $includeSensitive = $input->getOption('include-sensitive-values'); $this->stdErr->writeln(''); $this->stdErr->writeln('Copying project-level variables'); foreach ($this->getSelectedProject()->getVariables() as $var) { $metadata['variables']['project'][$var->name] = $var->getProperties(); - if ($var->is_sensitive) { - $this->stdErr->writeln(sprintf(' Warning: cannot save value for sensitive project-level variable %s', $var->name)); + if ($var->is_sensitive && !$var->hasProperty('value')) { + if ($var->visible_runtime) { + if ($includeSensitive) { + $value = false; + foreach ($apps as $app) { + try { + $value = $this->fetchSensitiveValue($app->getSshUrl(), $var->name, $var->is_json); + } catch (\RuntimeException $e) { + continue; + } + break; + } + if ($value !== false) { + $metadata['variables']['project'][$var->name]['value'] = $value; + } + } else { + $this->stdErr->writeln(sprintf(' Warning: cannot save value for sensitive project-level variable %s', $var->name)); + $this->stdErr->writeln(' Use --include-sensitive-values to try to fetch this via SSH'); + } + } } } @@ -202,8 +222,24 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->stdErr->writeln('Copying environment-level variables'); foreach ($environment->getVariables() as $envVar) { $metadata['variables']['environment'][$envVar->name] = $envVar->getProperties(); - if ($envVar->is_sensitive) { - $this->stdErr->writeln(sprintf(' Warning: cannot save value for sensitive environment-level variable %s', $envVar->name)); + if ($envVar->is_sensitive && !$envVar->hasProperty('value')) { + if ($includeSensitive) { + $value = false; + foreach ($apps as $app) { + try { + $value = $this->fetchSensitiveValue($app->getSshUrl(), $envVar->name, $envVar->is_json); + } catch (\RuntimeException $e) { + continue; + } + break; + } + if ($value !== false) { + $metadata['variables']['environment'][$envVar->name]['value'] = $value; + } + } else { + $this->stdErr->writeln(sprintf(' Warning: cannot save value for sensitive environment-level variable %s', $envVar->name)); + $this->stdErr->writeln(' Use --include-sensitive-values to try to fetch this via SSH'); + } } } } @@ -302,6 +338,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $mountService = $this->getService('mount'); /** @var \Platformsh\Cli\Service\Rsync $rsync */ $rsync = $this->getService('rsync'); + $rsyncOptions = [ + 'verbose' => $output->isVeryVerbose(), + 'quiet' => !$output->isVerbose(), + ]; foreach ($apps as $app) { $sourcePaths = []; $mounts = $mountService->normalizeMounts($app->getMounts()); @@ -319,7 +359,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->stdErr->writeln('Copying from mount ' . $path . ''); $destination = $archiveDir . '/mounts/' . trim($path, '/'); mkdir($destination, 0755, true); - $rsync->syncDown($app->getSshUrl(), ltrim($path, '/'), $destination); + $rsync->syncDown($app->getSshUrl(), ltrim($path, '/'), $destination, $rsyncOptions); $metadata['mounts'][$path] = [ 'app' => $app->getName(), 'path' => 'mounts/' . trim($path, '/'), @@ -402,4 +442,27 @@ private function getSchemas(Service $service, $relationshipName) return $schemas; } + + /** + * @param string $sshUrl + * @param string $varName + * @param bool $is_json + * + * @return mixed + */ + private function fetchSensitiveValue($sshUrl, $varName, $is_json) + { + /** @var \Platformsh\Cli\Service\RemoteEnvVars $remoteEnvVars */ + $remoteEnvVars = $this->getService('remote_env_vars'); + if (substr($varName, 0, 4) === 'env:') { + return $remoteEnvVars->getEnvVar(substr($varName, 4), $sshUrl, true, 3600, false); + } + + $variables = $remoteEnvVars->getArrayEnvVar('VARIABLES', $sshUrl); + if (array_key_exists($varName, $variables)) { + return $is_json ? json_encode($variables[$varName]) : $variables[$varName]; + } + + throw new \RuntimeException('Variable not found: ' . $varName); + } } diff --git a/src/Command/Archive/ArchiveImportCommand.php b/src/Command/Archive/ArchiveImportCommand.php index 06c4895ae..6dd3581ea 100644 --- a/src/Command/Archive/ArchiveImportCommand.php +++ b/src/Command/Archive/ArchiveImportCommand.php @@ -120,12 +120,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->stdErr->writeln('Importing environment-level variables'); foreach ($metadata['variables']['environment'] as $name => $var) { - if ($var['is_sensitive']) { - $this->stdErr->writeln(' Skipping sensitive variable ' . $name . ''); - continue; - } $this->stdErr->writeln(' Processing variable ' . $name . ''); if (!array_key_exists('value', $var)) { + if ($var['is_sensitive']) { + $this->stdErr->writeln(' Skipping sensitive variable ' . $name . ''); + continue; + } $this->stdErr->writeln(' Error: no variable value found.'); continue; } @@ -158,12 +158,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->stdErr->writeln('Importing project-level variables'); foreach ($metadata['variables']['project'] as $name => $var) { - if ($var['is_sensitive']) { - $this->stdErr->writeln(' Skipping sensitive variable ' . $name . ''); - continue; - } $this->stdErr->writeln(' Processing variable ' . $name . ''); if (!array_key_exists('value', $var)) { + if ($var['is_sensitive']) { + $this->stdErr->writeln(' Skipping sensitive variable ' . $name . ''); + continue; + } $this->stdErr->writeln(' Error: no variable value found.'); continue; } diff --git a/src/Service/RemoteEnvVars.php b/src/Service/RemoteEnvVars.php index d34b493b7..6a71cafe2 100644 --- a/src/Service/RemoteEnvVars.php +++ b/src/Service/RemoteEnvVars.php @@ -38,15 +38,16 @@ public function __construct(Ssh $ssh, CacheProvider $cache, Shell $shellHelper, * @param string $sshUrl The SSH URL to the application. * @param bool $refresh Whether to refresh the cache. * @param int $ttl The cache lifetime of the result. + * @param bool $prefix Whether to prepend the service.env_prefix. * * @throws \Symfony\Component\Process\Exception\RuntimeException * If the SSH command fails. * * @return string The environment variable or an empty string. */ - public function getEnvVar($variable, $sshUrl, $refresh = false, $ttl = 3600) + public function getEnvVar($variable, $sshUrl, $refresh = false, $ttl = 3600, $prefix = true) { - $varName = $this->config->get('service.env_prefix') . $variable; + $varName = $prefix ? $this->config->get('service.env_prefix') . $variable : $variable; $cacheKey = 'env-' . $sshUrl . '-' . $varName; $cached = $this->cache->fetch($cacheKey); if ($refresh || $cached === false) { @@ -69,12 +70,14 @@ public function getEnvVar($variable, $sshUrl, $refresh = false, $ttl = 3600) * @param string $variable * @param string $sshUrl * @param bool $refresh + * @param int $ttl + * @param bool $prefix * * @return array */ - public function getArrayEnvVar($variable, $sshUrl, $refresh = false) + public function getArrayEnvVar($variable, $sshUrl, $refresh = false, $ttl = 3600, $prefix = true) { - $value = $this->getEnvVar($variable, $sshUrl, $refresh); + $value = $this->getEnvVar($variable, $sshUrl, $refresh, $ttl, $prefix); return json_decode(base64_decode($value), true) ?: []; }