public function testGetAppConfigNested() { $fakeAppRoot = 'tests/data/repositories/multiple/nest/nested'; $app = new LocalApplication($fakeAppRoot); $config = $app->getConfig(); $this->assertEquals(['name' => 'nested1'], $config); $this->assertEquals('nested1', $app->getName()); $this->assertEquals('nested1', $app->getId()); }
/** * Process shared file mounts in the application. * * For each "mount", this creates a corresponding directory in the project's * shared files directory, and symlinks it into the appropriate path in the * build. */ protected function processSharedFileMounts() { $sharedDir = $this->getSharedDir(); if ($sharedDir === false) { return; } // If the build directory is a symlink, then skip, so that we don't risk // modifying the user's repository. if (is_link($this->buildDir)) { return; } $sharedFileMounts = $this->app->getSharedFileMounts(); if (empty($sharedFileMounts)) { return; } $sharedDirRelative = $this->config->get('local.shared_dir'); $this->output->writeln('Creating symbolic links to mimic shared file mounts'); foreach ($sharedFileMounts as $appPath => $sharedPath) { $target = $sharedDir . '/' . $sharedPath; $targetRelative = $sharedDirRelative . '/' . $sharedPath; $link = $this->buildDir . '/' . $appPath; if (file_exists($link) && !is_link($link)) { $this->output->writeln(' Overwriting existing file <comment>' . $appPath . '</comment>'); } if (!file_exists($target)) { $this->fsHelper->mkdir($target, 0775); } $this->output->writeln(' Symlinking <info>' . $appPath . '</info> to <info>' . $targetRelative . '</info>'); $this->fsHelper->symlink($target, $link); } }
protected function execute(InputInterface $input, OutputInterface $output) { $this->validateInput($input); $drushCommand = $input->getArgument('cmd'); $sshOptions = ''; // Pass through options that the CLI shares with Drush and SSH. foreach (['yes', 'no', 'quiet'] as $option) { if ($input->getOption($option)) { $drushCommand .= " --{$option}"; } } if ($output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) { $drushCommand .= " --debug"; $sshOptions .= ' -vv'; } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) { $drushCommand .= " --verbose"; $sshOptions .= ' -v'; } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $drushCommand .= " --verbose"; } elseif ($output->getVerbosity() == OutputInterface::VERBOSITY_QUIET) { $drushCommand .= " --quiet"; $sshOptions .= ' -q'; } $appName = $this->selectApp($input, function (LocalApplication $app) { return Drupal::isDrupal($app->getRoot()); }); $selectedEnvironment = $this->getSelectedEnvironment(); $sshUrl = $selectedEnvironment->getSshUrl($appName); // Get the LocalApplication object for the specified application, if // available. $projectRoot = $this->getProjectRoot(); if ($projectRoot && $this->selectedProjectIsCurrent()) { $app = LocalApplication::getApplication($appName, $projectRoot, self::$config); } // Use the local application configuration (if available) to determine // the correct Drupal root. if (isset($app)) { $drupalRoot = '/app/' . $app->getDocumentRoot(); } else { // Fall back to the PLATFORM_DOCUMENT_ROOT environment variable, // which is usually correct, except where the document_root was // specified as '/'. $documentRootEnvVar = self::$config->get('service.env_prefix') . 'DOCUMENT_ROOT'; $drupalRoot = '${' . $documentRootEnvVar . ':-/app/public}'; $this->debug('<comment>Warning:</comment> using $' . $documentRootEnvVar . ' for the Drupal root. This fails in cases where the document_root is /.'); } $dimensions = $this->getApplication()->getTerminalDimensions(); $columns = $dimensions[0] ?: 80; $sshDrushCommand = "COLUMNS={$columns} drush --root=\"{$drupalRoot}\""; if ($environmentUrl = $selectedEnvironment->getLink('public-url')) { $sshDrushCommand .= " --uri=" . escapeshellarg($environmentUrl); } $sshDrushCommand .= ' ' . $drushCommand . ' 2>&1'; $command = 'ssh' . $sshOptions . ' ' . escapeshellarg($sshUrl) . ' ' . escapeshellarg($sshDrushCommand); return $this->getHelper('shell')->executeSimple($command); }
/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (!($projectRoot = $this->getProjectRoot())) { throw new RootNotFoundException(); } $repository = $projectRoot . '/' . LocalProject::REPOSITORY_DIR; $apps = LocalApplication::getApplications($repository); $rows = []; foreach ($apps as $app) { $rows[] = [$app->getName(), $app->getRoot()]; } $table = new Table($input, $output); $table->render($rows, ['Name', 'Path']); }
/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (!($projectRoot = $this->getProjectRoot())) { throw new RootNotFoundException(); } $apps = LocalApplication::getApplications($projectRoot, self::$config); $rows = []; foreach ($apps as $app) { $config = $app->getConfig(); $type = isset($config['type']) ? $config['type'] : null; $rows[] = [$app->getName(), $type, $app->getRoot()]; } $table = new Table($input, $output); $table->render($rows, ['Name', 'Type', 'Path']); }
/** * Find the name of the app the user wants to use for an SSH command. * * @param InputInterface $input * The user input object. * @param callable|null $filter * A filter callback that takes one argument: a LocalApplication object. * * @return string|null * The application name, or null if it could not be found. */ protected function selectApp(InputInterface $input, callable $filter = null) { $appName = $input->getOption('app'); if ($appName) { return $appName; } $projectRoot = $this->getProjectRoot(); if (!$projectRoot || !$this->selectedProjectIsCurrent() || !is_dir($projectRoot . '/' . LocalProject::REPOSITORY_DIR)) { return null; } $this->debug('Searching for applications in local repository'); /** @var LocalApplication[] $apps */ $apps = LocalApplication::getApplications($projectRoot . '/' . LocalProject::REPOSITORY_DIR); if ($filter) { $apps = array_filter($apps, $filter); } if (count($apps) > 1 && $input->isInteractive()) { /** @var \Platformsh\Cli\Helper\PlatformQuestionHelper $questionHelper */ $questionHelper = $this->getHelper('question'); $choices = []; foreach ($apps as $app) { $choices[$app->getName()] = $app->getName(); } $appName = $questionHelper->choose($choices, 'Enter a number to choose an app:', $input, $this->stdErr); } $input->setOption('app', $appName); return $appName; }
/** * @param LocalApplication $app * @param string $sourceDir * @param string $destination * * @return bool */ protected function buildApp($app, $sourceDir, $destination = null) { $verbose = $this->output->isVerbose(); $destination = $destination ?: $sourceDir . '/' . $this->config->get('local.web_root'); $appRoot = $app->getRoot(); $appConfig = $app->getConfig(); $multiApp = $appRoot != $sourceDir; $appId = $app->getId(); $toolstack = $app->getToolstack(); if (!$toolstack) { $this->output->writeln("Toolstack not found for application <error>{$appId}</error>"); return false; } // Find the right build directory. $buildName = $multiApp ? str_replace('/', '-', $appId) : 'default'; $tmpBuildDir = $sourceDir . '/' . $this->config->get('local.build_dir') . '/' . $buildName . '-tmp'; if (file_exists($tmpBuildDir)) { if (!$this->fsHelper->remove($tmpBuildDir)) { $this->output->writeln(sprintf('Failed to remove directory <error>%s</error>', $tmpBuildDir)); return false; } } // If the destination is inside the source directory, ensure it isn't // copied or symlinked into the build. if (strpos($destination, $sourceDir) === 0) { $toolstack->addIgnoredFiles([ltrim(substr($destination, strlen($sourceDir)), '/')]); } // Warn about a mismatched PHP version. if (isset($appConfig['type']) && strpos($appConfig['type'], ':')) { list($stack, $version) = explode(':', $appConfig['type'], 2); if ($stack === 'php' && version_compare($version, PHP_VERSION, '>')) { $this->output->writeln(sprintf('<comment>Warning:</comment> the application <comment>%s</comment> expects PHP %s, but the system version is %s.', $appId, $version, PHP_VERSION)); } } $toolstack->setOutput($this->output); $buildSettings = $this->settings + ['multiApp' => $multiApp, 'sourceDir' => $sourceDir]; $toolstack->prepare($tmpBuildDir, $app, $this->config, $buildSettings); $archive = false; if (empty($this->settings['no-archive']) && empty($this->settings['no-cache'])) { $treeId = $this->getTreeId($appRoot); if ($treeId) { if ($verbose) { $this->output->writeln("Tree ID: {$treeId}"); } $archive = $sourceDir . '/' . $this->config->get('local.archive_dir') . '/' . $treeId . '.tar.gz'; } } if ($archive && file_exists($archive)) { $message = "Extracting archive for application <info>{$appId}</info>"; $this->output->writeln($message); $this->fsHelper->extractArchive($archive, $tmpBuildDir); } else { $message = "Building application <info>{$appId}</info>"; if (isset($appConfig['type'])) { $message .= ' (runtime type: ' . $appConfig['type'] . ')'; } $this->output->writeln($message); $toolstack->build(); if ($this->runPostBuildHooks($appConfig, $toolstack->getAppDir()) === false) { // The user may not care if build hooks fail, but we should // not archive the result. $archive = false; } if ($archive && $toolstack->canArchive()) { $this->output->writeln("Saving build archive"); if (!is_dir(dirname($archive))) { mkdir(dirname($archive)); } $this->fsHelper->archiveDir($tmpBuildDir, $archive); } } // The build is complete. Move the directory. $buildDir = substr($tmpBuildDir, 0, strlen($tmpBuildDir) - 4); if (file_exists($buildDir)) { if (empty($this->settings['no-backup']) && is_dir($buildDir) && !is_link($buildDir)) { $previousBuildArchive = dirname($buildDir) . '/' . basename($buildDir) . '-old.tar.gz'; $this->output->writeln("Backing up previous build to: " . $previousBuildArchive); $this->fsHelper->archiveDir($buildDir, $previousBuildArchive); } if (!$this->fsHelper->remove($buildDir, true)) { $this->output->writeln(sprintf('Failed to remove directory <error>%s</error>', $buildDir)); return false; } } if (!rename($tmpBuildDir, $buildDir)) { $this->output->writeln(sprintf('Failed to move temporary build directory into <error>%s</error>', $buildDir)); return false; } $toolstack->setBuildDir($buildDir); $toolstack->install(); $this->runPostDeployHooks($appConfig, $buildDir); $webRoot = $toolstack->getWebRoot(); // Symlink the built web root ($webRoot) into www or www/appId. if (!is_dir($webRoot)) { $this->output->writeln("\nWeb root not found: <error>{$webRoot}</error>\n"); return false; } if ($multiApp) { $appDir = str_replace('/', '-', $appId); if (is_link($destination)) { $this->fsHelper->remove($destination); } $destination .= "/{$appDir}"; } $this->fsHelper->symlink($webRoot, $destination); $message = "\nBuild complete for application <info>{$appId}</info>"; $this->output->writeln($message); $this->output->writeln("Web root: <info>{$destination}</info>\n"); return true; }
protected function execute(InputInterface $input, OutputInterface $output) { $this->validateInput($input); if ($input instanceof ArgvInput) { $helper = new ArgvHelper(); $drushCommand = $helper->getPassedCommand($this, $input); } if (empty($drushCommand)) { $drushCommand = $input->getArgument('cmd'); } $sshOptions = ''; // Pass through options that the CLI shares with Drush and SSH. foreach (['yes', 'no', 'quiet'] as $option) { if ($input->getOption($option)) { $drushCommand .= " --{$option}"; } } if ($output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) { $drushCommand .= " --debug"; $sshOptions .= ' -vv'; } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) { $drushCommand .= " --verbose"; $sshOptions .= ' -v'; } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $drushCommand .= " --verbose"; } elseif ($output->getVerbosity() == OutputInterface::VERBOSITY_QUIET) { $drushCommand .= " --quiet"; $sshOptions .= ' -q'; } $appName = $this->selectApp($input, function (LocalApplication $app) { return Drupal::isDrupal($app->getRoot()); }); $selectedEnvironment = $this->getSelectedEnvironment(); $sshUrl = $selectedEnvironment->getSshUrl($appName); // Get the LocalApplication object for the specified application, if // available. $projectRoot = $this->getProjectRoot(); if ($projectRoot && $this->selectedProjectIsCurrent() && is_dir($projectRoot . '/' . LocalProject::REPOSITORY_DIR)) { $apps = LocalApplication::getApplications($projectRoot . '/' . LocalProject::REPOSITORY_DIR); if (count($apps) === 1 && $appName === null) { $app = reset($apps); } else { foreach ($apps as $possibleApp) { if ($possibleApp->getName() === $appName) { $app = $possibleApp; break; } } } } // Use the local application configuration (if available) to determine // the correct Drupal root. if (isset($app) && isset($app->getConfig()['web']['document_root'])) { $documentRoot = trim($app->getConfig()['web']['document_root'], '/') ?: 'public'; $drupalRoot = '/app/' . $documentRoot; } else { // Fall back to the PLATFORM_DOCUMENT_ROOT environment variable, // which is usually correct, except where the document_root was // specified as '/'. $drupalRoot = '${PLATFORM_DOCUMENT_ROOT:-/app/public}'; $this->debug('<comment>Warning:</comment> using $PLATFORM_DOCUMENT_ROOT for the Drupal root. This fails in cases where the document_root is /.'); } $dimensions = $this->getApplication()->getTerminalDimensions(); $columns = $dimensions[0] ?: 80; $sshDrushCommand = "COLUMNS={$columns} drush --root=\"{$drupalRoot}\""; if ($environmentUrl = $selectedEnvironment->getLink('public-url')) { $sshDrushCommand .= " --uri=" . escapeshellarg($environmentUrl); } $sshDrushCommand .= ' ' . $drushCommand . ' 2>&1'; $command = 'ssh' . $sshOptions . ' ' . escapeshellarg($sshUrl) . ' ' . escapeshellarg($sshDrushCommand); if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $this->stdErr->writeln("Running command: <info>{$command}</info>"); } passthru($command, $return_var); return $return_var; }
public function testGetSharedFileMounts() { $appRoot = 'tests/data/apps/drupal/project'; $app = new LocalApplication($appRoot); $this->assertEquals(['public/sites/default/files' => 'files', 'tmp' => 'tmp', 'private' => 'private', 'drush-backups' => 'drush-backups'], $app->getSharedFileMounts()); }
/** * @param LocalApplication $app * @param string $sourceDir * @param string $destination * * @return bool */ protected function buildApp($app, $sourceDir, $destination) { $verbose = $this->output->isVerbose(); $appRoot = $app->getRoot(); $appConfig = $app->getConfig(); $multiApp = $appRoot != $sourceDir; $appName = $app->getName(); $appId = $app->getId(); $buildName = date('Y-m-d--H-i-s'); if (!empty($this->settings['environmentId'])) { $buildName .= '--' . $this->settings['environmentId']; } if ($multiApp) { $buildName .= '--' . str_replace('/', '-', $appId); } if (!empty($this->settings['projectRoot'])) { $buildDir = $this->settings['projectRoot'] . '/' . LocalProject::BUILD_DIR . '/' . $buildName; } else { $buildDir = $sourceDir . '/' . LocalProject::BUILD_DIR . '/' . $buildName; } // Get the configured document root. $documentRoot = $this->getDocumentRoot($appConfig); $toolstack = $app->getToolstack(); if (!$toolstack) { $this->output->writeln("Toolstack not found for application <error>{$appId}</error>"); return false; } // Warn about a mismatched PHP version. if (isset($appConfig['type']) && strpos($appConfig['type'], ':')) { list($stack, $version) = explode(':', $appConfig['type'], 2); if ($stack === 'php' && version_compare($version, PHP_VERSION, '>')) { $this->output->writeln(sprintf('<comment>Warning:</comment> the application <comment>%s</comment> expects PHP %s, but the system version is %s.', $appId, $version, PHP_VERSION)); } } $toolstack->setOutput($this->output); $buildSettings = $this->settings + ['multiApp' => $multiApp, 'appName' => $appName]; $toolstack->prepare($buildDir, $documentRoot, $appRoot, $buildSettings); $archive = false; if (empty($this->settings['noArchive']) && empty($this->settings['noCache']) && !empty($this->settings['projectRoot'])) { $treeId = $this->getTreeId($appRoot); if ($treeId) { if ($verbose) { $this->output->writeln("Tree ID: {$treeId}"); } $archive = $this->settings['projectRoot'] . '/' . LocalProject::ARCHIVE_DIR . '/' . $treeId . '.tar.gz'; } } if ($archive && file_exists($archive)) { $message = "Extracting archive for application <info>{$appId}</info>"; $this->output->writeln($message); $this->fsHelper->extractArchive($archive, $buildDir); } else { $message = "Building application <info>{$appId}</info>"; if (isset($appConfig['type'])) { $message .= ' (runtime type: ' . $appConfig['type'] . ')'; } $this->output->writeln($message); $toolstack->build(); if ($this->runPostBuildHooks($appConfig, $toolstack->getAppRoot()) === false) { // The user may not care if build hooks fail, but we should // not archive the result. $archive = false; } if ($archive && $toolstack->canArchive()) { $this->output->writeln("Saving build archive"); if (!is_dir(dirname($archive))) { mkdir(dirname($archive)); } $this->fsHelper->archiveDir($buildDir, $archive); } } $toolstack->install(); $webRoot = $toolstack->getWebRoot(); // Symlink the built web root ($webRoot) into www or www/appId. if (!is_dir($webRoot)) { $this->output->writeln("Web root not found: <error>{$webRoot}</error>"); return false; } if ($multiApp) { $appDir = str_replace('/', '-', $appId); if (is_link($destination)) { $this->fsHelper->remove($destination); } $destination .= "/{$appDir}"; } $this->fsHelper->symlink($webRoot, $destination); $this->output->writeln("Web root: {$destination}"); $message = "Build complete for application <info>{$appId}</info>"; $this->output->writeln($message); return true; }
protected function execute(InputInterface $input, OutputInterface $output) { $this->validateInput($input); $projectRoot = $this->getProjectRoot(); if (!$projectRoot) { throw new RootNotFoundException(); } $appName = $this->selectApp($input); $sshUrl = $this->getSelectedEnvironment()->getSshUrl($appName); // Get and parse app config. $app = LocalApplication::getApplication($appName, $projectRoot); $appConfig = $app->getConfig(); if (empty($appConfig['relationships'])) { $this->stdErr->writeln('No application relationships found.'); return 1; } $util = new RelationshipsUtil($this->stdErr); $database = $util->chooseDatabase($sshUrl, $input); if (empty($database)) { $this->stdErr->writeln('No database selected.'); return 1; } // Find the database's service name in the relationships. $dbServiceName = false; foreach ($appConfig['relationships'] as $relationshipName => $relationship) { if ($database['_relationship_name'] === $relationshipName) { list($dbServiceName, ) = explode(':', $relationship, 2); break; } } if (!$dbServiceName) { $this->stdErr->writeln('Service name not found for relationship: ' . $database['_relationship_name']); return 1; } // Load services yaml. $services = Yaml::parse(file_get_contents($projectRoot . '/.platform/services.yaml')); if (!empty($services[$dbServiceName]['disk'])) { $allocatedDisk = $services[$dbServiceName]['disk']; } else { $this->stdErr->writeln('The allocated disk size could not be determined for service: ' . $dbServiceName); return 1; } $this->stdErr->write('Querying database <comment>' . $dbServiceName . '</comment> to estimate disk usage. '); $this->stdErr->writeln('This might take a while.'); /** @var ShellHelper $shellHelper */ $shellHelper = $this->getHelper('shell'); $command = ['ssh']; $command[] = $sshUrl; switch ($database['scheme']) { case 'pgsql': $command[] = $this->psqlQuery($database); $result = $shellHelper->execute($command); $resultArr = explode(PHP_EOL, $result); $estimatedUsage = array_sum($resultArr) / 1048576; break; default: $command[] = $this->mysqlQuery($database); $estimatedUsage = $shellHelper->execute($command); break; } $percentsUsed = $estimatedUsage * 100 / $allocatedDisk; $table = new Table($input, $output); $propertyNames = ['max', 'used', 'percent_used']; $machineReadable = $table->formatIsMachineReadable(); $values = [(int) $allocatedDisk . ($machineReadable ? '' : 'MB'), (int) $estimatedUsage . ($machineReadable ? '' : 'MB'), (int) $percentsUsed . '%']; $table->renderSimple($values, $propertyNames); return 0; }
/** * @param Environment $environment * @param LocalApplication $app * @param bool $multiApp * * @return array|false */ protected function generateRemoteAlias($environment, $app, $multiApp = false) { if (!$environment->hasLink('ssh') || !$environment->hasLink('public-url')) { return false; } $sshUrl = parse_url($environment->getLink('ssh')); if (!$sshUrl) { return false; } $sshUser = $sshUrl['user']; if ($multiApp) { $sshUser .= '--' . $app->getName(); } $uri = $environment->getLink('public-url'); if ($multiApp) { $guess = str_replace('http://', 'http://' . $app->getName() . '---', $uri); if (in_array($guess, $environment->getRouteUrls())) { $uri = $guess; } } $appConfig = $app->getConfig(); $documentRoot = '/public'; if (isset($appConfig['web']['document_root']) && $appConfig['web']['document_root'] !== '/') { $documentRoot = $appConfig['web']['document_root']; } return ['uri' => $uri, 'remote-host' => $sshUrl['host'], 'remote-user' => $sshUser, 'root' => '/app/' . ltrim($documentRoot, '/'), self::AUTO_REMOVE_KEY => true, 'command-specific' => ['site-install' => ['sites-subdir' => 'default']]]; }
/** * @param Environment $environment * @param LocalApplication $app * @param bool $multiApp * * @return array|false */ protected function generateRemoteAlias($environment, $app, $multiApp = false) { if (!$environment->hasLink('ssh') || !$environment->hasLink('public-url')) { return false; } $sshUrl = parse_url($environment->getLink('ssh')); if (!$sshUrl) { return false; } $sshUser = $sshUrl['user']; if ($multiApp) { $sshUser .= '--' . $app->getName(); } $uri = $environment->getLink('public-url'); if ($multiApp) { $guess = str_replace('http://', 'http://' . $app->getName() . '---', $uri); if (in_array($guess, $environment->getRouteUrls())) { $uri = $guess; } } return ['uri' => $uri, 'remote-host' => $sshUrl['host'], 'remote-user' => $sshUser, 'root' => '/app/' . $app->getDocumentRoot(), $this->getAutoRemoveKey() => true, 'command-specific' => ['site-install' => ['sites-subdir' => 'default']]]; }
/** * Get a list of application names in the local project. * * @return string[] */ public function getAppNames() { $apps = []; if ($projectRoot = $this->welcomeCommand->getProjectRoot()) { foreach (LocalApplication::getApplications($projectRoot) as $app) { $name = $app->getName(); if ($name !== null) { $apps[] = $name; } } } return $apps; }