diff --git a/src/Command/Acsf/AcsfCommandFactory.php b/src/Command/Acsf/AcsfCommandFactory.php index 3cf3e8400..df2689c0c 100644 --- a/src/Command/Acsf/AcsfCommandFactory.php +++ b/src/Command/Acsf/AcsfCommandFactory.php @@ -12,8 +12,6 @@ use Acquia\Cli\Helpers\LocalMachineHelper; use Acquia\Cli\Helpers\SshHelper; use Acquia\Cli\Helpers\TelemetryHelper; -use AcquiaLogstream\LogstreamManager; -use GuzzleHttp\Client; use Psr\Log\LoggerInterface; class AcsfCommandFactory implements CommandFactoryInterface { @@ -26,11 +24,9 @@ public function __construct( private TelemetryHelper $telemetryHelper, private string $projectDir, private AcsfClientService $cloudApiClientService, - private LogstreamManager $logstreamManager, private SshHelper $sshHelper, private string $sshDir, private LoggerInterface $logger, - private Client $httpClient ) { } @@ -43,11 +39,9 @@ public function createCommand(): AcsfApiBaseCommand { $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, - $this->logstreamManager, $this->sshHelper, $this->sshDir, $this->logger, - $this->httpClient ); } @@ -60,11 +54,9 @@ public function createListCommand(): AcsfListCommand { $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, - $this->logstreamManager, $this->sshHelper, $this->sshDir, $this->logger, - $this->httpClient ); } diff --git a/src/Command/Api/ApiCommandFactory.php b/src/Command/Api/ApiCommandFactory.php index 65790bc0f..b9821c55a 100644 --- a/src/Command/Api/ApiCommandFactory.php +++ b/src/Command/Api/ApiCommandFactory.php @@ -12,8 +12,6 @@ use Acquia\Cli\Helpers\LocalMachineHelper; use Acquia\Cli\Helpers\SshHelper; use Acquia\Cli\Helpers\TelemetryHelper; -use AcquiaLogstream\LogstreamManager; -use GuzzleHttp\Client; use Psr\Log\LoggerInterface; class ApiCommandFactory implements CommandFactoryInterface { @@ -26,11 +24,9 @@ public function __construct( private TelemetryHelper $telemetryHelper, private string $projectDir, private ClientService $cloudApiClientService, - private LogstreamManager $logstreamManager, private SshHelper $sshHelper, private string $sshDir, private LoggerInterface $logger, - private Client $httpClient ) { } @@ -43,11 +39,9 @@ public function createCommand(): ApiBaseCommand { $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, - $this->logstreamManager, $this->sshHelper, $this->sshDir, $this->logger, - $this->httpClient ); } @@ -60,11 +54,9 @@ public function createListCommand(): ApiListCommand { $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, - $this->logstreamManager, $this->sshHelper, $this->sshDir, $this->logger, - $this->httpClient ); } diff --git a/src/Command/App/LogTailCommand.php b/src/Command/App/LogTailCommand.php index d88c62bd2..05d5c13f8 100644 --- a/src/Command/App/LogTailCommand.php +++ b/src/Command/App/LogTailCommand.php @@ -4,9 +4,18 @@ namespace Acquia\Cli\Command\App; +use Acquia\Cli\ApiCredentialsInterface; use Acquia\Cli\Attribute\RequireAuth; +use Acquia\Cli\CloudApi\ClientService; use Acquia\Cli\Command\CommandBase; +use Acquia\Cli\DataStore\AcquiaCliDatastore; +use Acquia\Cli\DataStore\CloudDataStore; +use Acquia\Cli\Helpers\LocalMachineHelper; +use Acquia\Cli\Helpers\SshHelper; +use Acquia\Cli\Helpers\TelemetryHelper; use AcquiaCloudApi\Endpoints\Logs; +use AcquiaLogstream\LogstreamManager; +use Psr\Log\LoggerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -16,6 +25,22 @@ #[AsCommand(name: 'app:log:tail', description: 'Tail the logs from your environments', aliases: ['tail', 'log:tail'])] final class LogTailCommand extends CommandBase { + public function __construct( + public LocalMachineHelper $localMachineHelper, + protected CloudDataStore $datastoreCloud, + protected AcquiaCliDatastore $datastoreAcli, + protected ApiCredentialsInterface $cloudCredentials, + protected TelemetryHelper $telemetryHelper, + protected string $projectDir, + protected ClientService $cloudApiClientService, + public SshHelper $sshHelper, + protected string $sshDir, + LoggerInterface $logger, + protected LogstreamManager $logstreamManager, + ) { + parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger); + } + protected function configure(): void { $this ->acceptEnvironmentId(); diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 97858acbc..4228cefb6 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -24,12 +24,15 @@ use AcquiaCloudApi\Connector\Connector; use AcquiaCloudApi\Endpoints\Account; use AcquiaCloudApi\Endpoints\Applications; +use AcquiaCloudApi\Endpoints\Databases; use AcquiaCloudApi\Endpoints\Environments; use AcquiaCloudApi\Endpoints\Notifications; use AcquiaCloudApi\Endpoints\Organizations; use AcquiaCloudApi\Endpoints\Subscriptions; use AcquiaCloudApi\Response\AccountResponse; use AcquiaCloudApi\Response\ApplicationResponse; +use AcquiaCloudApi\Response\DatabaseResponse; +use AcquiaCloudApi\Response\DatabasesResponse; use AcquiaCloudApi\Response\EnvironmentResponse; use AcquiaCloudApi\Response\EnvironmentsResponse; use AcquiaCloudApi\Response\NotificationResponse; @@ -102,11 +105,9 @@ public function __construct( protected TelemetryHelper $telemetryHelper, protected string $projectDir, protected ClientService $cloudApiClientService, - protected LogstreamManager $logstreamManager, public SshHelper $sshHelper, protected string $sshDir, LoggerInterface $logger, - protected \GuzzleHttp\Client $httpClient ) { $this->logger = $logger; $this->setLocalDbPassword(); @@ -444,6 +445,197 @@ public function promptChooseFromObjectsOrArrays(array|ArrayObject $items, string return NULL; } + protected function getHostFromDatabaseResponse(mixed $environment, DatabaseResponse $database): string { + if ($this->isAcsfEnv($environment)) { + return $database->db_host . '.enterprise-g1.hosting.acquia.com'; + } + + return $database->db_host; + } + + protected function rsyncFiles(string $sourceDir, string $destinationDir, ?callable $outputCallback): void { + $this->localMachineHelper->checkRequiredBinariesExist(['rsync']); + $command = [ + 'rsync', + // -a archive mode; same as -rlptgoD. + // -z compress file data during the transfer. + // -v increase verbosity. + // -P show progress during transfer. + // -h output numbers in a human-readable format. + // -e specify the remote shell to use. + '-avPhze', + 'ssh -o StrictHostKeyChecking=no', + $sourceDir . '/', + $destinationDir, + ]; + $process = $this->localMachineHelper->execute($command, $outputCallback, NULL, ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL)); + if (!$process->isSuccessful()) { + throw new AcquiaCliException('Unable to sync files. {message}', ['message' => $process->getErrorOutput()]); + } + } + + protected function getCloudFilesDir(EnvironmentResponse $chosenEnvironment, string $site): string { + $sitegroup = self::getSiteGroupFromSshUrl($chosenEnvironment->sshUrl); + if ($this->isAcsfEnv($chosenEnvironment)) { + return '/mnt/files/' . $sitegroup . '.' . $chosenEnvironment->name . '/sites/g/files/' . $site . '/files'; + } + return $this->getCloudSitesPath($chosenEnvironment, $sitegroup) . "/$site/files"; + } + + protected function getLocalFilesDir(string $site): string { + return $this->dir . '/docroot/sites/' . $site . '/files'; + } + + /** + * @param string|null $site + * @return DatabaseResponse[] + */ + protected function determineCloudDatabases(Client $acquiaCloudClient, EnvironmentResponse $chosenEnvironment, string $site = NULL, bool $multipleDbs = FALSE): array { + $databasesRequest = new Databases($acquiaCloudClient); + $databases = $databasesRequest->getAll($chosenEnvironment->uuid); + + if (count($databases) === 1) { + $this->logger->debug('Only a single database detected on Cloud'); + return [$databases[0]]; + } + $this->logger->debug('Multiple databases detected on Cloud'); + if ($site && !$multipleDbs) { + if ($site === 'default') { + $this->logger->debug('Site is set to default. Assuming default database'); + $site = self::getSiteGroupFromSshUrl($chosenEnvironment->sshUrl); + } + $databaseNames = array_column((array) $databases, 'name'); + $databaseKey = array_search($site, $databaseNames, TRUE); + if ($databaseKey !== FALSE) { + return [$databases[$databaseKey]]; + } + } + return $this->promptChooseDatabases($chosenEnvironment, $databases, $multipleDbs); + } + + /** + * @return array + */ + private function promptChooseDatabases( + EnvironmentResponse $cloudEnvironment, + DatabasesResponse $environmentDatabases, + bool $multipleDbs + ): array { + $choices = []; + if ($multipleDbs) { + $choices['all'] = 'All'; + } + $defaultDatabaseIndex = 0; + if ($this->isAcsfEnv($cloudEnvironment)) { + $acsfSites = $this->getAcsfSites($cloudEnvironment); + } + foreach ($environmentDatabases as $index => $database) { + $suffix = ''; + if (isset($acsfSites)) { + foreach ($acsfSites['sites'] as $domain => $acsfSite) { + if ($acsfSite['conf']['gardens_db_name'] === $database->name) { + $suffix .= ' (' . $domain . ')'; + break; + } + } + } + if ($database->flags->default) { + $defaultDatabaseIndex = $index; + $suffix .= ' (default)'; + } + $choices[] = $database->name . $suffix; + } + + $question = new ChoiceQuestion( + $multipleDbs ? 'Choose databases. You may choose multiple. Use commas to separate choices.' : 'Choose a database.', + $choices, + $defaultDatabaseIndex + ); + $question->setMultiselect($multipleDbs); + if ($multipleDbs) { + $chosenDatabaseKeys = $this->io->askQuestion($question); + $chosenDatabases = []; + if (count($chosenDatabaseKeys) === 1 && $chosenDatabaseKeys[0] === 'all') { + if (count($environmentDatabases) > 10) { + $this->io->warning('You have chosen to pull down more than 10 databases. This could exhaust your disk space.'); + } + return (array) $environmentDatabases; + } + foreach ($chosenDatabaseKeys as $chosenDatabaseKey) { + $chosenDatabases[] = $environmentDatabases[$chosenDatabaseKey]; + } + + return $chosenDatabases; + } + + $chosenDatabaseLabel = $this->io->choice('Choose a database', $choices, $defaultDatabaseIndex); + $chosenDatabaseIndex = array_search($chosenDatabaseLabel, $choices, TRUE); + return [$environmentDatabases[$chosenDatabaseIndex]]; + } + + protected function determineEnvironment(InputInterface $input, OutputInterface $output, bool $allowProduction = FALSE): array|string|EnvironmentResponse { + if ($input->getArgument('environmentId')) { + $environmentId = $input->getArgument('environmentId'); + $chosenEnvironment = $this->getCloudEnvironment($environmentId); + } + else { + $cloudApplicationUuid = $this->determineCloudApplication(); + $cloudApplication = $this->getCloudApplication($cloudApplicationUuid); + $output->writeln('Using Cloud Application ' . $cloudApplication->name . ''); + $acquiaCloudClient = $this->cloudApiClientService->getClient(); + $chosenEnvironment = $this->promptChooseEnvironmentConsiderProd($acquiaCloudClient, $cloudApplicationUuid, $allowProduction); + } + $this->logger->debug("Using environment $chosenEnvironment->label $chosenEnvironment->uuid"); + + return $chosenEnvironment; + } + + // Todo: obviously combine this with promptChooseEnvironment + private function promptChooseEnvironmentConsiderProd(Client $acquiaCloudClient, string $applicationUuid, bool $allowProduction = FALSE): EnvironmentResponse { + $environmentResource = new Environments($acquiaCloudClient); + $applicationEnvironments = iterator_to_array($environmentResource->getAll($applicationUuid)); + $choices = []; + foreach ($applicationEnvironments as $key => $environment) { + if (!$allowProduction && $environment->flags->production) { + unset($applicationEnvironments[$key]); + // Re-index array so keys match those in $choices. + $applicationEnvironments = array_values($applicationEnvironments); + continue; + } + $choices[] = "$environment->label, $environment->name (vcs: {$environment->vcs->path})"; + } + $chosenEnvironmentLabel = $this->io->choice('Choose a Cloud Platform environment', $choices, $choices[0]); + $chosenEnvironmentIndex = array_search($chosenEnvironmentLabel, $choices, TRUE); + + return $applicationEnvironments[$chosenEnvironmentIndex]; + } + + protected function isLocalGitRepoDirty(): bool { + $this->localMachineHelper->checkRequiredBinariesExist(['git']); + $process = $this->localMachineHelper->executeFromCmd( + // Problem with this is that it stages changes for the user. They may + // not want that. + 'git add . && git diff-index --cached --quiet HEAD', + NULL, $this->dir, FALSE); + + return !$process->isSuccessful(); + } + + protected function getLocalGitCommitHash(): string { + $this->localMachineHelper->checkRequiredBinariesExist(['git']); + $process = $this->localMachineHelper->execute([ + 'git', + 'rev-parse', + 'HEAD', + ], NULL, $this->dir, FALSE); + + if (!$process->isSuccessful()) { + throw new AcquiaCliException("Unable to determine Git commit hash."); + } + + return trim($process->getOutput()); + } + /** * Load configuration from .git/config. * @@ -1260,6 +1452,85 @@ protected function getOutputCallback(OutputInterface $output, Checklist $checkli }; } + protected function executeAllScripts(Closure $outputCallback, Checklist $checklist): void { + $this->runComposerScripts($outputCallback, $checklist); + $this->runDrushCacheClear($outputCallback, $checklist); + $this->runDrushSqlSanitize($outputCallback, $checklist); + } + + protected function runComposerScripts(callable $outputCallback, Checklist $checklist): void { + if (!file_exists(Path::join($this->dir, 'composer.json'))) { + $this->io->note('composer.json file not found. Skipping composer install.'); + return; + } + if (!$this->localMachineHelper->commandExists('composer')) { + $this->io->note('Composer not found. Skipping composer install.'); + return; + } + if (file_exists(Path::join($this->dir, 'vendor'))) { + $this->io->note('Composer dependencies already installed. Skipping composer install.'); + return; + } + $checklist->addItem("Installing Composer dependencies"); + $this->composerInstall($outputCallback); + $checklist->completePreviousItem(); + } + + protected function runDrushCacheClear(Closure $outputCallback, Checklist $checklist): void { + if ($this->getDrushDatabaseConnectionStatus()) { + $checklist->addItem('Clearing Drupal caches via Drush'); + // @todo Add support for Drush 8. + $process = $this->localMachineHelper->execute([ + 'drush', + 'cache:rebuild', + '--yes', + '--no-interaction', + '--verbose', + ], $outputCallback, $this->dir, FALSE); + if (!$process->isSuccessful()) { + throw new AcquiaCliException('Unable to rebuild Drupal caches via Drush. {message}', ['message' => $process->getErrorOutput()]); + } + $checklist->completePreviousItem(); + } + else { + $this->logger->notice('Drush does not have an active database connection. Skipping cache:rebuild'); + } + } + + protected function runDrushSqlSanitize(Closure $outputCallback, Checklist $checklist): void { + if ($this->getDrushDatabaseConnectionStatus()) { + $checklist->addItem('Sanitizing database via Drush'); + $process = $this->localMachineHelper->execute([ + 'drush', + 'sql:sanitize', + '--yes', + '--no-interaction', + '--verbose', + ], $outputCallback, $this->dir, FALSE); + if (!$process->isSuccessful()) { + throw new AcquiaCliException('Unable to sanitize Drupal database via Drush. {message}', ['message' => $process->getErrorOutput()]); + } + $checklist->completePreviousItem(); + $this->io->newLine(); + $this->io->text('Your database was sanitized via drush sql:sanitize. This has changed all user passwords to randomly generated strings. To log in to your Drupal site, use drush uli'); + } + else { + $this->logger->notice('Drush does not have an active database connection. Skipping sql:sanitize.'); + } + } + + private function composerInstall(?callable $outputCallback): void { + $process = $this->localMachineHelper->execute([ + 'composer', + 'install', + '--no-interaction', + ], $outputCallback, $this->dir, FALSE); + if (!$process->isSuccessful()) { + throw new AcquiaCliException('Unable to install Drupal dependencies via Composer. {message}', + ['message' => $process->getErrorOutput()]); + } + } + protected function getDrushDatabaseConnectionStatus(Closure $outputCallback = NULL): bool { if (isset($this->drushHasActiveDatabaseConnection)) { return $this->drushHasActiveDatabaseConnection; diff --git a/src/Command/Env/EnvDeleteCommand.php b/src/Command/Env/EnvDeleteCommand.php index 0c527ecae..d68dafc04 100644 --- a/src/Command/Env/EnvDeleteCommand.php +++ b/src/Command/Env/EnvDeleteCommand.php @@ -27,7 +27,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $cloudAppUuid = $this->determineCloudApplication(TRUE); $acquiaCloudClient = $this->cloudApiClientService->getClient(); $environmentsResource = new Environments($acquiaCloudClient); - $environment = $this->determineEnvironment($environmentsResource, $cloudAppUuid); + $environment = $this->determineEnvironmentCde($environmentsResource, $cloudAppUuid); $environmentsResource->delete($environment->uuid); $this->io->success([ @@ -37,7 +37,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - private function determineEnvironment(Environments $environmentsResource, string $cloudAppUuid): EnvironmentResponse { + private function determineEnvironmentCde(Environments $environmentsResource, string $cloudAppUuid): EnvironmentResponse { if ($this->input->getArgument('environmentId')) { // @todo Validate. $environmentId = $this->input->getArgument('environmentId'); diff --git a/src/Command/Pull/PullCodeCommand.php b/src/Command/Pull/PullCodeCommand.php index aa38ea27f..b4a57409a 100644 --- a/src/Command/Pull/PullCodeCommand.php +++ b/src/Command/Pull/PullCodeCommand.php @@ -27,13 +27,16 @@ protected function configure(): void { } protected function execute(InputInterface $input, OutputInterface $output): int { - $this->pullCode($input, $output); - $this->checkEnvironmentPhpVersions($this->sourceEnvironment); - $this->matchIdePhpVersion($output, $this->sourceEnvironment); + $this->setDirAndRequireProjectCwd($input); + $clone = $this->determineCloneProject($output); + $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); + $this->pullCode($input, $output, $clone, $sourceEnvironment); + $this->checkEnvironmentPhpVersions($sourceEnvironment); + $this->matchIdePhpVersion($output, $sourceEnvironment); if (!$input->getOption('no-scripts')) { $outputCallback = $this->getOutputCallback($output, $this->checklist); - $this->runComposerScripts($outputCallback); - $this->runDrushCacheClear($outputCallback); + $this->runComposerScripts($outputCallback, $this->checklist); + $this->runDrushCacheClear($outputCallback, $this->checklist); } return Command::SUCCESS; diff --git a/src/Command/Pull/PullCommand.php b/src/Command/Pull/PullCommand.php index da45900de..e223e90a1 100644 --- a/src/Command/Pull/PullCommand.php +++ b/src/Command/Pull/PullCommand.php @@ -37,12 +37,16 @@ protected function configure(): void { protected function execute(InputInterface $input, OutputInterface $output): int { parent::execute($input, $output); + $this->setDirAndRequireProjectCwd($input); + $clone = $this->determineCloneProject($output); + $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); + if (!$input->getOption('no-code')) { - $this->pullCode($input, $output); + $this->pullCode($input, $output, $clone, $sourceEnvironment); } if (!$input->getOption('no-files')) { - $this->pullFiles($input, $output); + $this->pullFiles($input, $output, $sourceEnvironment); } if (!$input->getOption('no-databases')) { @@ -50,7 +54,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if (!$input->getOption('no-scripts')) { - $this->executeAllScripts($input, $this->getOutputCallback($output, $this->checklist)); + $this->executeAllScripts($this->getOutputCallback($output, $this->checklist), $this->checklist); } return Command::SUCCESS; diff --git a/src/Command/Pull/PullCommandBase.php b/src/Command/Pull/PullCommandBase.php index 8c8faf361..40750cc5b 100644 --- a/src/Command/Pull/PullCommandBase.php +++ b/src/Command/Pull/PullCommandBase.php @@ -4,25 +4,30 @@ namespace Acquia\Cli\Command\Pull; +use Acquia\Cli\ApiCredentialsInterface; +use Acquia\Cli\CloudApi\ClientService; use Acquia\Cli\Command\CommandBase; +use Acquia\Cli\DataStore\AcquiaCliDatastore; +use Acquia\Cli\DataStore\CloudDataStore; use Acquia\Cli\Exception\AcquiaCliException; use Acquia\Cli\Helpers\IdeCommandTrait; +use Acquia\Cli\Helpers\LocalMachineHelper; +use Acquia\Cli\Helpers\SshHelper; +use Acquia\Cli\Helpers\TelemetryHelper; use Acquia\Cli\Output\Checklist; use Acquia\DrupalEnvironmentDetector\AcquiaDrupalEnvironmentDetector; use AcquiaCloudApi\Connector\Client; use AcquiaCloudApi\Endpoints\DatabaseBackups; -use AcquiaCloudApi\Endpoints\Databases; use AcquiaCloudApi\Endpoints\Domains; -use AcquiaCloudApi\Endpoints\Environments; use AcquiaCloudApi\Response\BackupResponse; use AcquiaCloudApi\Response\DatabaseResponse; -use AcquiaCloudApi\Response\DatabasesResponse; use AcquiaCloudApi\Response\EnvironmentResponse; use Closure; use Exception; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\TransferStats; use Psr\Http\Message\UriInterface; +use Psr\Log\LoggerInterface; use React\EventLoop\Loop; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\ProgressBar; @@ -31,7 +36,6 @@ use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleSectionOutput; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Filesystem\Path; abstract class PullCommandBase extends CommandBase { @@ -40,12 +44,26 @@ abstract class PullCommandBase extends CommandBase { protected Checklist $checklist; - protected EnvironmentResponse $sourceEnvironment; - private string $site; private UriInterface $backupDownloadUrl; + public function __construct( + public LocalMachineHelper $localMachineHelper, + protected CloudDataStore $datastoreCloud, + protected AcquiaCliDatastore $datastoreAcli, + protected ApiCredentialsInterface $cloudCredentials, + protected TelemetryHelper $telemetryHelper, + protected string $projectDir, + protected ClientService $cloudApiClientService, + public SshHelper $sshHelper, + protected string $sshDir, + LoggerInterface $logger, + protected \GuzzleHttp\Client $httpClient + ) { + parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger); + } + /** * @see https://github.com/drush-ops/drush/blob/c21a5a24a295cc0513bfdecead6f87f1a2cf91a2/src/Sql/SqlMysql.php#L168 * @return string[] @@ -70,18 +88,6 @@ private function listTablesQuoted(string $out): array { return $tables; } - protected function getCloudFilesDir(EnvironmentResponse $chosenEnvironment, string $site): string { - $sitegroup = self::getSiteGroupFromSshUrl($chosenEnvironment->sshUrl); - if ($this->isAcsfEnv($chosenEnvironment)) { - return '/mnt/files/' . $sitegroup . '.' . $chosenEnvironment->name . '/sites/g/files/' . $site . '/files'; - } - return $this->getCloudSitesPath($chosenEnvironment, $sitegroup) . "/$site/files"; - } - - protected function getLocalFilesDir(string $site): string { - return $this->dir . '/docroot/sites/' . $site . '/files'; - } - public static function getBackupPath(object $environment, DatabaseResponse $database, object $backupResponse): string { // Databases have a machine name not exposed via the API; we can only // approximately reconstruct it and match the filename you'd get downloading @@ -110,11 +116,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - protected function pullCode(InputInterface $input, OutputInterface $output): void { - $this->setDirAndRequireProjectCwd($input); - $clone = $this->determineCloneProject($output); - $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); - + protected function pullCode(InputInterface $input, OutputInterface $output, bool $clone, EnvironmentResponse $sourceEnvironment): void { if ($clone) { $this->checklist->addItem('Cloning git repository from the Cloud Platform'); $this->cloneFromCloud($sourceEnvironment, $this->getOutputCallback($output, $this->checklist)); @@ -167,9 +169,7 @@ protected function pullDatabase(InputInterface $input, OutputInterface $output, } } - protected function pullFiles(InputInterface $input, OutputInterface $output): void { - $this->setDirAndRequireProjectCwd($input); - $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); + protected function pullFiles(InputInterface $input, OutputInterface $output, EnvironmentResponse $sourceEnvironment): void { $this->checklist->addItem('Copying Drupal\'s public files from the Cloud Platform'); $site = $this->determineSite($sourceEnvironment, $input); $this->rsyncFilesFromCloud($sourceEnvironment, $this->getOutputCallback($output, $this->checklist), $site); @@ -421,129 +421,6 @@ private function importDatabaseDump(string $localDumpFilepath, string $dbHost, s } } - protected function isLocalGitRepoDirty(): bool { - $this->localMachineHelper->checkRequiredBinariesExist(['git']); - $process = $this->localMachineHelper->executeFromCmd( - // Problem with this is that it stages changes for the user. They may - // not want that. - 'git add . && git diff-index --cached --quiet HEAD', - NULL, $this->dir, FALSE); - - return !$process->isSuccessful(); - } - - protected function getLocalGitCommitHash(): string { - $this->localMachineHelper->checkRequiredBinariesExist(['git']); - $process = $this->localMachineHelper->execute([ - 'git', - 'rev-parse', - 'HEAD', - ], NULL, $this->dir, FALSE); - - if (!$process->isSuccessful()) { - throw new AcquiaCliException("Unable to determine Git commit hash."); - } - - return trim($process->getOutput()); - } - - private function promptChooseEnvironment(Client $acquiaCloudClient, string $applicationUuid, bool $allowProduction = FALSE): EnvironmentResponse { - $environmentResource = new Environments($acquiaCloudClient); - $applicationEnvironments = iterator_to_array($environmentResource->getAll($applicationUuid)); - $choices = []; - foreach ($applicationEnvironments as $key => $environment) { - if (!$allowProduction && $environment->flags->production) { - unset($applicationEnvironments[$key]); - // Re-index array so keys match those in $choices. - $applicationEnvironments = array_values($applicationEnvironments); - continue; - } - $choices[] = "$environment->label, $environment->name (vcs: {$environment->vcs->path})"; - } - $chosenEnvironmentLabel = $this->io->choice('Choose a Cloud Platform environment', $choices, $choices[0]); - $chosenEnvironmentIndex = array_search($chosenEnvironmentLabel, $choices, TRUE); - - return $applicationEnvironments[$chosenEnvironmentIndex]; - } - - /** - * @return array - */ - private function promptChooseDatabases( - EnvironmentResponse $cloudEnvironment, - DatabasesResponse $environmentDatabases, - bool $multipleDbs - ): array { - $choices = []; - if ($multipleDbs) { - $choices['all'] = 'All'; - } - $defaultDatabaseIndex = 0; - if ($this->isAcsfEnv($cloudEnvironment)) { - $acsfSites = $this->getAcsfSites($cloudEnvironment); - } - foreach ($environmentDatabases as $index => $database) { - $suffix = ''; - if (isset($acsfSites)) { - foreach ($acsfSites['sites'] as $domain => $acsfSite) { - if ($acsfSite['conf']['gardens_db_name'] === $database->name) { - $suffix .= ' (' . $domain . ')'; - break; - } - } - } - if ($database->flags->default) { - $defaultDatabaseIndex = $index; - $suffix .= ' (default)'; - } - $choices[] = $database->name . $suffix; - } - - $question = new ChoiceQuestion( - $multipleDbs ? 'Choose databases. You may choose multiple. Use commas to separate choices.' : 'Choose a database.', - $choices, - $defaultDatabaseIndex - ); - $question->setMultiselect($multipleDbs); - if ($multipleDbs) { - $chosenDatabaseKeys = $this->io->askQuestion($question); - $chosenDatabases = []; - if (count($chosenDatabaseKeys) === 1 && $chosenDatabaseKeys[0] === 'all') { - if (count($environmentDatabases) > 10) { - $this->io->warning('You have chosen to pull down more than 10 databases. This could exhaust your disk space.'); - } - return (array) $environmentDatabases; - } - foreach ($chosenDatabaseKeys as $chosenDatabaseKey) { - $chosenDatabases[] = $environmentDatabases[$chosenDatabaseKey]; - } - - return $chosenDatabases; - } - - $chosenDatabaseLabel = $this->io->choice('Choose a database', $choices, $defaultDatabaseIndex); - $chosenDatabaseIndex = array_search($chosenDatabaseLabel, $choices, TRUE); - return [$environmentDatabases[$chosenDatabaseIndex]]; - } - - protected function runComposerScripts(callable $outputCallback = NULL): void { - if (!file_exists(Path::join($this->dir, 'composer.json'))) { - $this->io->note('composer.json file not found. Skipping composer install.'); - return; - } - if (!$this->localMachineHelper->commandExists('composer')) { - $this->io->note('Composer not found. Skipping composer install.'); - return; - } - if (file_exists(Path::join($this->dir, 'vendor'))) { - $this->io->note('Composer dependencies already installed. Skipping composer install.'); - return; - } - $this->checklist->addItem("Installing Composer dependencies"); - $this->composerInstall($outputCallback); - $this->checklist->completePreviousItem(); - } - private function determineSite(string|EnvironmentResponse|array $environment, InputInterface $input): mixed { if (isset($this->site)) { return $this->site; @@ -564,27 +441,6 @@ private function determineSite(string|EnvironmentResponse|array $environment, In return $site; } - protected function rsyncFiles(string $sourceDir, string $destinationDir, ?callable $outputCallback): void { - $this->localMachineHelper->checkRequiredBinariesExist(['rsync']); - $command = [ - 'rsync', - // -a archive mode; same as -rlptgoD. - // -z compress file data during the transfer. - // -v increase verbosity. - // -P show progress during transfer. - // -h output numbers in a human-readable format. - // -e specify the remote shell to use. - '-avPhze', - 'ssh -o StrictHostKeyChecking=no', - $sourceDir . '/', - $destinationDir, - ]; - $process = $this->localMachineHelper->execute($command, $outputCallback, NULL, ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL)); - if (!$process->isSuccessful()) { - throw new AcquiaCliException('Unable to sync files. {message}', ['message' => $process->getErrorOutput()]); - } - } - private function rsyncFilesFromCloud(EnvironmentResponse $chosenEnvironment, Closure $outputCallback, string $site): void { $sourceDir = $chosenEnvironment->sshUrl . ':' . $this->getCloudFilesDir($chosenEnvironment, $site); $destinationDir = $this->getLocalFilesDir($site); @@ -593,34 +449,7 @@ private function rsyncFilesFromCloud(EnvironmentResponse $chosenEnvironment, Clo $this->rsyncFiles($sourceDir, $destinationDir, $outputCallback); } - /** - * @param string|null $site - * @return DatabaseResponse[] - */ - protected function determineCloudDatabases(Client $acquiaCloudClient, EnvironmentResponse $chosenEnvironment, string $site = NULL, bool $multipleDbs = FALSE): array { - $databasesRequest = new Databases($acquiaCloudClient); - $databases = $databasesRequest->getAll($chosenEnvironment->uuid); - - if (count($databases) === 1) { - $this->logger->debug('Only a single database detected on Cloud'); - return [$databases[0]]; - } - $this->logger->debug('Multiple databases detected on Cloud'); - if ($site && !$multipleDbs) { - if ($site === 'default') { - $this->logger->debug('Site is set to default. Assuming default database'); - $site = self::getSiteGroupFromSshUrl($chosenEnvironment->sshUrl); - } - $databaseNames = array_column((array) $databases, 'name'); - $databaseKey = array_search($site, $databaseNames, TRUE); - if ($databaseKey !== FALSE) { - return [$databases[$databaseKey]]; - } - } - return $this->promptChooseDatabases($chosenEnvironment, $databases, $multipleDbs); - } - - private function determineCloneProject(OutputInterface $output): bool { + protected function determineCloneProject(OutputInterface $output): bool { $finder = $this->localMachineHelper->getFinder()->files()->in($this->dir)->ignoreDotFiles(FALSE); // If we are in an IDE, assume we should pull into /home/ide/project. @@ -665,29 +494,6 @@ private function cloneFromCloud(EnvironmentResponse $chosenEnvironment, Closure $this->projectDir = $this->dir; } - protected function determineEnvironment(InputInterface $input, OutputInterface $output, bool $allowProduction = FALSE): array|string|EnvironmentResponse { - if (isset($this->sourceEnvironment)) { - return $this->sourceEnvironment; - } - - if ($input->getArgument('environmentId')) { - $environmentId = $input->getArgument('environmentId'); - $chosenEnvironment = $this->getCloudEnvironment($environmentId); - } - else { - $cloudApplicationUuid = $this->determineCloudApplication(); - $cloudApplication = $this->getCloudApplication($cloudApplicationUuid); - $output->writeln('Using Cloud Application ' . $cloudApplication->name . ''); - $acquiaCloudClient = $this->cloudApiClientService->getClient(); - $chosenEnvironment = $this->promptChooseEnvironment($acquiaCloudClient, $cloudApplicationUuid, $allowProduction); - } - $this->logger->debug("Using environment $chosenEnvironment->label $chosenEnvironment->uuid"); - - $this->sourceEnvironment = $chosenEnvironment; - - return $this->sourceEnvironment; - } - protected function checkEnvironmentPhpVersions(EnvironmentResponse $environment): void { $version = $this->getIdePhpVersion(); if (empty($version)) { @@ -717,76 +523,6 @@ private function environmentPhpVersionMatches(EnvironmentResponse $environment): return $environment->configuration->php->version === $currentPhpVersion; } - protected function executeAllScripts(\Symfony\Component\Console\Input\InputInterface $input, Closure $outputCallback): void { - $this->setDirAndRequireProjectCwd($input); - $this->runComposerScripts($outputCallback); - $this->runDrushCacheClear($outputCallback); - $this->runDrushSqlSanitize($outputCallback); - } - - protected function runDrushCacheClear(Closure $outputCallback): void { - if ($this->getDrushDatabaseConnectionStatus()) { - $this->checklist->addItem('Clearing Drupal caches via Drush'); - // @todo Add support for Drush 8. - $process = $this->localMachineHelper->execute([ - 'drush', - 'cache:rebuild', - '--yes', - '--no-interaction', - '--verbose', - ], $outputCallback, $this->dir, FALSE); - if (!$process->isSuccessful()) { - throw new AcquiaCliException('Unable to rebuild Drupal caches via Drush. {message}', ['message' => $process->getErrorOutput()]); - } - $this->checklist->completePreviousItem(); - } - else { - $this->logger->notice('Drush does not have an active database connection. Skipping cache:rebuild'); - } - } - - protected function runDrushSqlSanitize(Closure $outputCallback): void { - if ($this->getDrushDatabaseConnectionStatus()) { - $this->checklist->addItem('Sanitizing database via Drush'); - $process = $this->localMachineHelper->execute([ - 'drush', - 'sql:sanitize', - '--yes', - '--no-interaction', - '--verbose', - ], $outputCallback, $this->dir, FALSE); - if (!$process->isSuccessful()) { - throw new AcquiaCliException('Unable to sanitize Drupal database via Drush. {message}', ['message' => $process->getErrorOutput()]); - } - $this->checklist->completePreviousItem(); - $this->io->newLine(); - $this->io->text('Your database was sanitized via drush sql:sanitize. This has changed all user passwords to randomly generated strings. To log in to your Drupal site, use drush uli'); - } - else { - $this->logger->notice('Drush does not have an active database connection. Skipping sql:sanitize.'); - } - } - - private function composerInstall(?callable $outputCallback): void { - $process = $this->localMachineHelper->execute([ - 'composer', - 'install', - '--no-interaction', - ], $outputCallback, $this->dir, FALSE); - if (!$process->isSuccessful()) { - throw new AcquiaCliException('Unable to install Drupal dependencies via Composer. {message}', - ['message' => $process->getErrorOutput()]); - } - } - - protected function getHostFromDatabaseResponse(mixed $environment, DatabaseResponse $database): string { - if ($this->isAcsfEnv($environment)) { - return $database->db_host . '.enterprise-g1.hosting.acquia.com'; - } - - return $database->db_host; - } - private function getDatabaseBackup( Client $acquiaCloudClient, string|EnvironmentResponse|array $environment, diff --git a/src/Command/Pull/PullDatabaseCommand.php b/src/Command/Pull/PullDatabaseCommand.php index 516528de6..6ab5a7445 100644 --- a/src/Command/Pull/PullDatabaseCommand.php +++ b/src/Command/Pull/PullDatabaseCommand.php @@ -42,8 +42,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $noScripts = $noImport || $noScripts; $this->pullDatabase($input, $output, $onDemand, $noImport, $multipleDbs); if (!$noScripts) { - $this->runDrushCacheClear($this->getOutputCallback($output, $this->checklist)); - $this->runDrushSqlSanitize($this->getOutputCallback($output, $this->checklist)); + $this->runDrushCacheClear($this->getOutputCallback($output, $this->checklist), $this->checklist); + $this->runDrushSqlSanitize($this->getOutputCallback($output, $this->checklist), $this->checklist); } return Command::SUCCESS; diff --git a/src/Command/Pull/PullFilesCommand.php b/src/Command/Pull/PullFilesCommand.php index c0f4e9d34..032dc0b15 100644 --- a/src/Command/Pull/PullFilesCommand.php +++ b/src/Command/Pull/PullFilesCommand.php @@ -22,7 +22,9 @@ protected function configure(): void { protected function execute(InputInterface $input, OutputInterface $output): int { parent::execute($input, $output); - $this->pullFiles($input, $output); + $this->setDirAndRequireProjectCwd($input); + $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); + $this->pullFiles($input, $output, $sourceEnvironment); return Command::SUCCESS; } diff --git a/src/Command/Pull/PullScriptsCommand.php b/src/Command/Pull/PullScriptsCommand.php index 98965a1c0..7ff3f3018 100644 --- a/src/Command/Pull/PullScriptsCommand.php +++ b/src/Command/Pull/PullScriptsCommand.php @@ -4,6 +4,8 @@ namespace Acquia\Cli\Command\Pull; +use Acquia\Cli\Command\CommandBase; +use Acquia\Cli\Output\Checklist; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -11,7 +13,9 @@ use Symfony\Component\Console\Output\OutputInterface; #[AsCommand(name: 'pull:run-scripts', description: 'Execute post pull scripts')] -final class PullScriptsCommand extends PullCommandBase { +final class PullScriptsCommand extends CommandBase { + + protected Checklist $checklist; protected function configure(): void { $this @@ -19,8 +23,14 @@ protected function configure(): void { ->addOption('dir', NULL, InputArgument::OPTIONAL, 'The directory containing the Drupal project to be refreshed'); } + protected function initialize(InputInterface $input, OutputInterface $output): void { + parent::initialize($input, $output); + $this->checklist = new Checklist($output); + } + protected function execute(InputInterface $input, OutputInterface $output): int { - $this->executeAllScripts($input, $this->getOutputCallback($output, $this->checklist)); + $this->setDirAndRequireProjectCwd($input); + $this->executeAllScripts($this->getOutputCallback($output, $this->checklist), $this->checklist); return Command::SUCCESS; } diff --git a/src/Command/Push/PushArtifactCommand.php b/src/Command/Push/PushArtifactCommand.php index 7b2c8ca70..556f59bfa 100644 --- a/src/Command/Push/PushArtifactCommand.php +++ b/src/Command/Push/PushArtifactCommand.php @@ -4,7 +4,7 @@ namespace Acquia\Cli\Command\Push; -use Acquia\Cli\Command\Pull\PullCommandBase; +use Acquia\Cli\Command\CommandBase; use Acquia\Cli\Exception\AcquiaCliException; use Acquia\Cli\Output\Checklist; use Closure; @@ -17,7 +17,7 @@ use Symfony\Component\Filesystem\Path; #[AsCommand(name: 'push:artifact', description: 'Build and push a code artifact to a Cloud Platform environment')] -final class PushArtifactCommand extends PullCommandBase { +final class PushArtifactCommand extends CommandBase { /** * Composer vendor directories. @@ -39,6 +39,8 @@ final class PushArtifactCommand extends PullCommandBase { private string $destinationGitRef; + protected Checklist $checklist; + protected function configure(): void { $this ->addOption('dir', NULL, InputArgument::OPTIONAL, 'The directory containing the Drupal project to be pushed') @@ -60,6 +62,11 @@ protected function configure(): void { ->addUsage('--destination-git-urls=example@svn-1.prod.hosting.acquia.com:example.git --destination-git-branch=main-build'); } + protected function initialize(InputInterface $input, OutputInterface $output): void { + parent::initialize($input, $output); + $this->checklist = new Checklist($output); + } + protected function execute(InputInterface $input, OutputInterface $output): int { $this->setDirAndRequireProjectCwd($input); if ($input->getOption('no-clone')) { diff --git a/src/Command/Push/PushCodeCommand.php b/src/Command/Push/PushCodeCommand.php index 49c9cbc69..9f96c0d10 100644 --- a/src/Command/Push/PushCodeCommand.php +++ b/src/Command/Push/PushCodeCommand.php @@ -5,7 +5,6 @@ namespace Acquia\Cli\Command\Push; use Acquia\Cli\Attribute\RequireAuth; -use Acquia\Cli\Command\Pull\PullCommandBase; use Acquia\DrupalEnvironmentDetector\AcquiaDrupalEnvironmentDetector; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -14,7 +13,7 @@ #[RequireAuth] #[AsCommand(name: 'push:code', description: 'Push code from your IDE to a Cloud Platform environment')] -final class PushCodeCommand extends PullCommandBase { +final class PushCodeCommand extends PushCommandBase { protected function configure(): void { $this diff --git a/src/Command/Push/PushCommandBase.php b/src/Command/Push/PushCommandBase.php new file mode 100644 index 000000000..2b6c29bd0 --- /dev/null +++ b/src/Command/Push/PushCommandBase.php @@ -0,0 +1,14 @@ +determineCloudDatabases($acquiaCloudClient, $destinationEnvironment, $input->getArgument('site')); // We only support pushing a single database. $database = $databases[0]; - $answer = $this->io->confirm("Overwrite the {$database->name} database on {$destinationEnvironment->name} with a copy of the database from the current machine?"); + $answer = $this->io->confirm("Overwrite the $database->name database on {$destinationEnvironment->name} with a copy of the database from the current machine?"); if (!$answer) { return Command::SUCCESS; } diff --git a/src/Command/Push/PushFilesCommand.php b/src/Command/Push/PushFilesCommand.php index e2ff087f9..f4e7c89cb 100644 --- a/src/Command/Push/PushFilesCommand.php +++ b/src/Command/Push/PushFilesCommand.php @@ -5,7 +5,6 @@ namespace Acquia\Cli\Command\Push; use Acquia\Cli\Attribute\RequireAuth; -use Acquia\Cli\Command\Pull\PullCommandBase; use Acquia\Cli\Output\Checklist; use AcquiaCloudApi\Response\EnvironmentResponse; use Symfony\Component\Console\Attribute\AsCommand; @@ -15,7 +14,7 @@ #[RequireAuth] #[AsCommand(name: 'push:files', description: 'Copy Drupal public files from your local environment to a Cloud Platform environment')] -final class PushFilesCommand extends PullCommandBase { +final class PushFilesCommand extends PushCommandBase { protected function configure(): void { $this diff --git a/tests/phpunit/src/CommandTestBase.php b/tests/phpunit/src/CommandTestBase.php index 5ff444de6..59454888c 100644 --- a/tests/phpunit/src/CommandTestBase.php +++ b/tests/phpunit/src/CommandTestBase.php @@ -485,11 +485,9 @@ protected function getCommandFactory(): CommandFactoryInterface { $this->telemetryHelper, $this->projectDir, $this->clientServiceProphecy->reveal(), - $this->logStreamManagerProphecy->reveal(), $this->sshHelper, $this->sshDir, $this->logger, - $this->httpClientProphecy->reveal() ); } diff --git a/tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php b/tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php index cf832a3ff..290b2acfa 100644 --- a/tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php +++ b/tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php @@ -138,11 +138,9 @@ protected function getCommandFactory(): CommandFactoryInterface { $this->telemetryHelper, $this->projectDir, $this->clientServiceProphecy->reveal(), - $this->logStreamManagerProphecy->reveal(), $this->sshHelper, $this->sshDir, $this->logger, - $this->httpClientProphecy->reveal() ); } diff --git a/tests/phpunit/src/Commands/App/LogTailCommandTest.php b/tests/phpunit/src/Commands/App/LogTailCommandTest.php index 60dffec56..b78614eb1 100644 --- a/tests/phpunit/src/Commands/App/LogTailCommandTest.php +++ b/tests/phpunit/src/Commands/App/LogTailCommandTest.php @@ -7,14 +7,32 @@ use Acquia\Cli\Command\App\LogTailCommand; use Acquia\Cli\Command\CommandBase; use Acquia\Cli\Tests\CommandTestBase; +use AcquiaLogstream\LogstreamManager; +use Prophecy\Prophecy\ObjectProphecy; /** * @property \Acquia\Cli\Command\App\LogTailCommand $command */ class LogTailCommandTest extends CommandTestBase { + protected LogstreamManager|ObjectProphecy $logStreamManagerProphecy; + protected function createCommand(): CommandBase { - return $this->injectCommand(LogTailCommand::class); + $this->logStreamManagerProphecy = $this->prophet->prophesize(LogstreamManager::class); + + return new LogTailCommand( + $this->localMachineHelper, + $this->datastoreCloud, + $this->datastoreAcli, + $this->cloudCredentials, + $this->telemetryHelper, + $this->acliRepoRoot, + $this->clientServiceProphecy->reveal(), + $this->sshHelper, + $this->sshDir, + $this->logger, + $this->logStreamManagerProphecy->reveal() + ); } public function testLogTailCommand(): void { @@ -58,4 +76,13 @@ public function testLogTailCommandWithEnvArg(): void { $this->assertStringContainsString('Drupal request', $output); } + private function mockLogStreamRequest(): void { + $response = $this->getMockResponseFromSpec('/environments/{environmentId}/logstream', + 'get', '200'); + $this->clientProphecy->request('get', + '/environments/24-a47ac10b-58cc-4372-a567-0e02b2c3d470/logstream') + ->willReturn($response) + ->shouldBeCalled(); + } + } diff --git a/tests/phpunit/src/Commands/Pull/PullCodeCommandTest.php b/tests/phpunit/src/Commands/Pull/PullCodeCommandTest.php index 02d6d683d..33558e141 100644 --- a/tests/phpunit/src/Commands/Pull/PullCodeCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullCodeCommandTest.php @@ -8,6 +8,7 @@ use Acquia\Cli\Command\Ide\IdePhpVersionCommand; use Acquia\Cli\Command\Pull\PullCodeCommand; use Acquia\Cli\Tests\Commands\Ide\IdeHelper; +use GuzzleHttp\Client; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Symfony\Component\Filesystem\Path; @@ -19,7 +20,21 @@ class PullCodeCommandTest extends PullCommandTestBase { protected function createCommand(): CommandBase { - return $this->injectCommand(PullCodeCommand::class); + $this->httpClientProphecy = $this->prophet->prophesize(Client::class); + + return new PullCodeCommand( + $this->localMachineHelper, + $this->datastoreCloud, + $this->datastoreAcli, + $this->cloudCredentials, + $this->telemetryHelper, + $this->acliRepoRoot, + $this->clientServiceProphecy->reveal(), + $this->sshHelper, + $this->sshDir, + $this->logger, + $this->httpClientProphecy->reveal() + ); } public function testCloneRepo(): void { diff --git a/tests/phpunit/src/Commands/Pull/PullCommandTest.php b/tests/phpunit/src/Commands/Pull/PullCommandTest.php index 901859b03..b33433952 100644 --- a/tests/phpunit/src/Commands/Pull/PullCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullCommandTest.php @@ -7,6 +7,7 @@ use Acquia\Cli\Command\CommandBase; use Acquia\Cli\Command\Pull\PullCommand; use Acquia\Cli\Exception\AcquiaCliException; +use GuzzleHttp\Client; /** * @property \Acquia\Cli\Command\Pull\PullCommand $command @@ -19,7 +20,21 @@ public function setUp(): void { } protected function createCommand(): CommandBase { - return $this->injectCommand(PullCommand::class); + $this->httpClientProphecy = $this->prophet->prophesize(Client::class); + + return new PullCommand( + $this->localMachineHelper, + $this->datastoreCloud, + $this->datastoreAcli, + $this->cloudCredentials, + $this->telemetryHelper, + $this->acliRepoRoot, + $this->clientServiceProphecy->reveal(), + $this->sshHelper, + $this->sshDir, + $this->logger, + $this->httpClientProphecy->reveal() + ); } public function testMissingLocalRepo(): void { diff --git a/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php b/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php index 649e37ff3..f4ac37d48 100644 --- a/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php +++ b/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php @@ -6,6 +6,7 @@ use Acquia\Cli\Tests\Commands\Ide\IdeRequiredTestTrait; use Acquia\Cli\Tests\CommandTestBase; +use GuzzleHttp\Client; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Symfony\Component\Finder\Finder; @@ -15,6 +16,8 @@ abstract class PullCommandTestBase extends CommandTestBase { use IdeRequiredTestTrait; + protected Client|ObjectProphecy $httpClientProphecy; + public function setUp(): void { parent::setUp(); } diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 4e6d7636d..9547dc546 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -11,6 +11,7 @@ use Acquia\Cli\Helpers\LocalMachineHelper; use Acquia\Cli\Helpers\SshHelper; use AcquiaCloudApi\Response\BackupsResponse; +use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Psr7\Uri; use Prophecy\Argument; @@ -58,7 +59,21 @@ public function mockGetBackup(mixed $environment): void { } protected function createCommand(): CommandBase { - return $this->injectCommand(PullDatabaseCommand::class); + $this->httpClientProphecy = $this->prophet->prophesize(Client::class); + + return new PullDatabaseCommand( + $this->localMachineHelper, + $this->datastoreCloud, + $this->datastoreAcli, + $this->cloudCredentials, + $this->telemetryHelper, + $this->acliRepoRoot, + $this->clientServiceProphecy->reveal(), + $this->sshHelper, + $this->sshDir, + $this->logger, + $this->httpClientProphecy->reveal() + ); } public function testPullDatabases(): void { diff --git a/tests/phpunit/src/Commands/Pull/PullFilesCommandTest.php b/tests/phpunit/src/Commands/Pull/PullFilesCommandTest.php index a949ea687..5ff6dc14b 100644 --- a/tests/phpunit/src/Commands/Pull/PullFilesCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullFilesCommandTest.php @@ -9,13 +9,28 @@ use Acquia\Cli\Exception\AcquiaCliException; use Acquia\Cli\Helpers\LocalMachineHelper; use Acquia\Cli\Tests\Commands\Ide\IdeHelper; +use GuzzleHttp\Client; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; class PullFilesCommandTest extends PullCommandTestBase { protected function createCommand(): CommandBase { - return $this->injectCommand(PullFilesCommand::class); + $this->httpClientProphecy = $this->prophet->prophesize(Client::class); + + return new PullFilesCommand( + $this->localMachineHelper, + $this->datastoreCloud, + $this->datastoreAcli, + $this->cloudCredentials, + $this->telemetryHelper, + $this->acliRepoRoot, + $this->clientServiceProphecy->reveal(), + $this->sshHelper, + $this->sshDir, + $this->logger, + $this->httpClientProphecy->reveal() + ); } public function testRefreshAcsfFiles(): void { diff --git a/tests/phpunit/src/TestBase.php b/tests/phpunit/src/TestBase.php index c328b8c20..05198ea79 100644 --- a/tests/phpunit/src/TestBase.php +++ b/tests/phpunit/src/TestBase.php @@ -20,7 +20,6 @@ use Acquia\Cli\Helpers\TelemetryHelper; use AcquiaCloudApi\Connector\Client; use AcquiaCloudApi\Exception\ApiErrorException; -use AcquiaLogstream\LogstreamManager; use Closure; use GuzzleHttp\Psr7\Response; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; @@ -71,8 +70,6 @@ abstract class TestBase extends TestCase { protected Client|ObjectProphecy $clientProphecy; - protected LogstreamManager|ObjectProphecy $logStreamManagerProphecy; - /** * @var array */ @@ -117,8 +114,6 @@ abstract class TestBase extends TestCase { protected string $passphraseFilepath = '~/.passphrase'; - protected \GuzzleHttp\Client|ObjectProphecy $httpClientProphecy; - protected vfsStreamDirectory $vfsRoot; protected string $realFixtureDir; @@ -194,9 +189,6 @@ protected function setUp(): void { $this->realFixtureDir = realpath(Path::join(__DIR__, '..', '..', 'fixtures')); - $this->logStreamManagerProphecy = $this->prophet->prophesize(LogstreamManager::class); - $this->httpClientProphecy = $this->prophet->prophesize(\GuzzleHttp\Client::class); - parent::setUp(); } @@ -341,11 +333,9 @@ protected function injectCommand(string $commandName): Command { $this->telemetryHelper, $this->acliRepoRoot, $this->clientServiceProphecy->reveal(), - $this->logStreamManagerProphecy->reveal(), $this->sshHelper, $this->sshDir, $this->logger, - $this->httpClientProphecy->reveal() ); } @@ -572,17 +562,6 @@ protected function getMockEnvironmentsResponse(): object { 'get', 200); } - protected function mockLogStreamRequest(): object { - $response = $this->getMockResponseFromSpec('/environments/{environmentId}/logstream', - 'get', '200'); - $this->clientProphecy->request('get', - '/environments/24-a47ac10b-58cc-4372-a567-0e02b2c3d470/logstream') - ->willReturn($response) - ->shouldBeCalled(); - - return $response; - } - /** * @return array */