Skip to content

Commit

Permalink
CLI-1402: [push:db] Warn if view database connection details permissi…
Browse files Browse the repository at this point in the history
…on is missing (#1810)

* CLI-1402: Require view database connection details permission

* Cleanup

* add tests

* stable sdk

* add help test

* kill mutant

* allow pull:db
  • Loading branch information
danepowell authored Oct 18, 2024
1 parent 1ffc996 commit 1dba2de
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 168 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"symfony/yaml": "^6.3",
"thecodingmachine/safe": "^2.4",
"typhonius/acquia-logstream": "^0.0.13",
"typhonius/acquia-php-sdk-v2": "^3.1.1",
"typhonius/acquia-php-sdk-v2": "^3.3.2",
"vlucas/phpdotenv": "^5.5",
"zumba/amplitude-php": "^1.0.4"
},
Expand Down
304 changes: 152 additions & 152 deletions composer.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
namespace Acquia\Cli\Attribute;

/**
* Specify that a command requires authentication.
* Specify that a command requires local database.
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class RequireDb
class RequireLocalDb
{
}
13 changes: 13 additions & 0 deletions src/Attribute/RequireRemoteDb.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Acquia\Cli\Attribute;

/**
* Specify that a command requires remote database.
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class RequireRemoteDb
{
}
4 changes: 2 additions & 2 deletions src/Command/Archive/ArchiveExportCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Acquia\Cli\Command\Archive;

use Acquia\Cli\Attribute\RequireAuth;
use Acquia\Cli\Attribute\RequireDb;
use Acquia\Cli\Attribute\RequireLocalDb;
use Acquia\Cli\Command\CommandBase;
use Acquia\Cli\Exception\AcquiaCliException;
use Acquia\Cli\Output\Checklist;
Expand All @@ -21,7 +21,7 @@
use Symfony\Component\Filesystem\Path;

#[RequireAuth]
#[RequireDb]
#[RequireLocalDb]
#[AsCommand(name: 'archive:export', description: 'Export an archive of the Drupal application including code, files, and database')]
final class ArchiveExportCommand extends CommandBase
{
Expand Down
8 changes: 6 additions & 2 deletions src/Command/CommandBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
use Acquia\Cli\AcsfApi\AcsfClientService;
use Acquia\Cli\ApiCredentialsInterface;
use Acquia\Cli\Attribute\RequireAuth;
use Acquia\Cli\Attribute\RequireDb;
use Acquia\Cli\Attribute\RequireLocalDb;
use Acquia\Cli\Attribute\RequireRemoteDb;
use Acquia\Cli\CloudApi\ClientService;
use Acquia\Cli\Command\Ssh\SshKeyCommandBase;
use Acquia\Cli\DataStore\AcquiaCliDatastore;
Expand Down Expand Up @@ -119,10 +120,13 @@ public function __construct(
if ((new \ReflectionClass(static::class))->getAttributes(RequireAuth::class)) {
$this->appendHelp('This command requires authentication via the Cloud Platform API.');
}
if ((new \ReflectionClass(static::class))->getAttributes(RequireDb::class)) {
if ((new \ReflectionClass(static::class))->getAttributes(RequireLocalDb::class)) {
$this->appendHelp('This command requires an active database connection. Set the following environment variables prior to running this command: '
. 'ACLI_DB_HOST, ACLI_DB_NAME, ACLI_DB_USER, ACLI_DB_PASSWORD');
}
if ((new \ReflectionClass(static::class))->getAttributes(RequireRemoteDb::class)) {
$this->appendHelp('This command requires the \'View database connection details\' permission.');
}
}

public function appendHelp(string $helpText): void
Expand Down
4 changes: 2 additions & 2 deletions src/Command/Pull/PullCodeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Acquia\Cli\Command\Pull;

use Acquia\Cli\Attribute\RequireAuth;
use Acquia\Cli\Attribute\RequireDb;
use Acquia\Cli\Attribute\RequireLocalDb;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -14,7 +14,7 @@
use Symfony\Component\Console\Output\OutputInterface;

#[RequireAuth]
#[RequireDb]
#[RequireLocalDb]
#[AsCommand(name: 'pull:code', description: 'Copy code from a Cloud Platform environment')]
final class PullCodeCommand extends PullCommandBase
{
Expand Down
4 changes: 2 additions & 2 deletions src/Command/Pull/PullCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Acquia\Cli\Command\Pull;

use Acquia\Cli\Attribute\RequireAuth;
use Acquia\Cli\Attribute\RequireDb;
use Acquia\Cli\Attribute\RequireLocalDb;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -14,7 +14,7 @@
use Symfony\Component\Console\Output\OutputInterface;

#[RequireAuth]
#[RequireDb]
#[RequireLocalDb]
#[AsCommand(name: 'pull:all', description: 'Copy code, database, and files from a Cloud Platform environment', aliases: [
'refresh',
'pull',
Expand Down
1 change: 1 addition & 0 deletions src/Command/Pull/PullCommandBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ protected function pullCode(InputInterface $input, OutputInterface $output, bool
/**
* @param bool $onDemand Force on-demand backup.
* @param bool $noImport Skip import.
* @throws \Acquia\Cli\Exception\AcquiaCliException
*/
protected function pullDatabase(InputInterface $input, OutputInterface $output, EnvironmentResponse $sourceEnvironment, bool $onDemand = false, bool $noImport = false, bool $multipleDbs = false): void
{
Expand Down
8 changes: 6 additions & 2 deletions src/Command/Pull/PullDatabaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
namespace Acquia\Cli\Command\Pull;

use Acquia\Cli\Attribute\RequireAuth;
use Acquia\Cli\Attribute\RequireDb;
use Acquia\Cli\Attribute\RequireLocalDb;
use Acquia\Cli\Attribute\RequireRemoteDb;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[RequireAuth]
#[RequireDb]
#[RequireLocalDb]
#[AsCommand(name: 'pull:database', description: 'Import database backup from a Cloud Platform environment', aliases: ['pull:db'])]
final class PullDatabaseCommand extends PullCommandBase
{
Expand Down Expand Up @@ -49,6 +50,9 @@ protected function configure(): void
);
}

/**
* @throws \Acquia\Cli\Exception\AcquiaCliException
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$noScripts = $input->hasOption('no-scripts') && $input->getOption('no-scripts');
Expand Down
12 changes: 10 additions & 2 deletions src/Command/Push/PushDatabaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
namespace Acquia\Cli\Command\Push;

use Acquia\Cli\Attribute\RequireAuth;
use Acquia\Cli\Attribute\RequireDb;
use Acquia\Cli\Attribute\RequireLocalDb;
use Acquia\Cli\Attribute\RequireRemoteDb;
use Acquia\Cli\Exception\AcquiaCliException;
use Acquia\Cli\Output\Checklist;
use AcquiaCloudApi\Response\DatabaseResponse;
Expand All @@ -16,7 +17,8 @@
use Symfony\Component\Console\Output\OutputInterface;

#[RequireAuth]
#[RequireDb]
#[RequireLocalDb]
#[RequireRemoteDb]
#[AsCommand(name: 'push:database', description: 'Push a database from your local environment to a Cloud Platform environment', aliases: ['push:db'])]
final class PushDatabaseCommand extends PushCommandBase
{
Expand All @@ -27,13 +29,19 @@ protected function configure(): void
->acceptSite();
}

/**
* @throws \Acquia\Cli\Exception\AcquiaCliException
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$destinationEnvironment = $this->determineEnvironment($input, $output);
$acquiaCloudClient = $this->cloudApiClientService->getClient();
$databases = $this->determineCloudDatabases($acquiaCloudClient, $destinationEnvironment, $input->getArgument('site'));
// We only support pushing a single database.
$database = $databases[0];
if ($database->user_name === null) {
throw new AcquiaCliException('Database connection details missing');
}
$answer = $this->io->confirm("Overwrite the <bg=cyan;options=bold>$database->name</> database on <bg=cyan;options=bold>$destinationEnvironment->name</> with a copy of the database from the current machine?");
if (!$answer) {
return Command::SUCCESS;
Expand Down
3 changes: 3 additions & 0 deletions src/EventListener/ExceptionListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public function onConsoleError(ConsoleErrorEvent $event): void
$this->helpMessages[] = 'Check for MySQL warnings above or in the server log (/var/log/mysql/error.log)';
$this->helpMessages[] = 'Frequently, `MySQL server has gone away` messages are caused by max_allowed_packet being exceeded.';
break;
case 'Database connection details missing':
$this->helpMessages[] = 'Check that you have the \'View database connection details\' permission';
break;
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/src/Commands/Pull/PullCommandTestBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ protected function mockListSites(SshHelper|ObjectProphecy $sshHelper): void
public function mockGetBackup(mixed $environment): void
{
$databases = $this->mockRequest('getEnvironmentsDatabases', $environment->id);
$tamper = function ($backups): void {
$tamper = static function ($backups): void {
$backups[0]->completedAt = $backups[0]->completed_at;
};
$backups = new BackupsResponse(
Expand Down
25 changes: 25 additions & 0 deletions tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Acquia\Cli\Command\CommandBase;
use Acquia\Cli\Command\Push\PushDatabaseCommand;
use Acquia\Cli\Exception\AcquiaCliException;
use Acquia\Cli\Helpers\LocalMachineHelper;
use Acquia\Cli\Helpers\SshHelper;
use Acquia\Cli\Tests\CommandTestBase;
Expand Down Expand Up @@ -108,6 +109,30 @@ public function testPushDatabase(int $verbosity, bool $printOutput, bool $pv): v
$this->assertStringContainsString('Overwrite the jxr136 database on dev with a copy of the database from the current machine?', $output);
}

public function testPushDbHelp(): void
{
$help = $this->command->getHelp();
$this->assertStringContainsString('This command requires authentication via the Cloud Platform API.', $help);
$this->assertStringContainsString('This command requires an active database connection. Set the following environment variables prior to running this command: ACLI_DB_HOST, ACLI_DB_NAME, ACLI_DB_USER, ACLI_DB_PASSWORD', $help);
$this->assertStringContainsString('This command requires the \'View database connection details\' permission.', $help);
}

/**
* @throws \Exception
*/
public function testPushDatabaseWithMissingPermission(): void
{
$environment = $this->mockGetEnvironment();
$tamper = static function ($databases): void {
$databases[0]->user_name = null;
};
$this->mockRequest('getEnvironmentsDatabases', $environment->id, null, null, $tamper);

$this->expectException(AcquiaCliException::class);
$this->expectExceptionMessage('Database connection details missing');
$this->executeCommand([], self::inputChooseEnvironment());
}

protected function mockUploadDatabaseDump(
ObjectProphecy $localMachineHelper,
ObjectProphecy $process,
Expand Down
4 changes: 4 additions & 0 deletions tests/phpunit/src/Misc/ExceptionListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ public function providerTestHelp(): array
'Frequently, `MySQL server has gone away` messages are caused by max_allowed_packet being exceeded.',
],
],
[
new AcquiaCliException('Database connection details missing'),
'Check that you have the \'View database connection details\' permission',
],
[
new ApiErrorException((object) [
'error' => '',
Expand Down

0 comments on commit 1dba2de

Please sign in to comment.