diff --git a/src/Application.php b/src/Application.php index e8410c4..7bb05bf 100644 --- a/src/Application.php +++ b/src/Application.php @@ -32,6 +32,7 @@ public function __construct() $this->safeAddCommand(new Commands\PreviousLogs); $this->safeAddCommand(new Commands\Cloud); $this->safeAddCommand(new Commands\Tinker); + $this->safeAddCommand(new Commands\SecretCreate); } public function safeAddCommand(Command $command): ?Command diff --git a/src/BrefCloudClient.php b/src/BrefCloudClient.php index bf23938..e4e6911 100644 --- a/src/BrefCloudClient.php +++ b/src/BrefCloudClient.php @@ -13,6 +13,26 @@ class BrefCloudClient private const STAGING_URL = 'https://staging.bref.cloud'; private const LOCAL_URL = 'http://localhost:8000'; + public const AWS_REGIONS = [ + 'us-east-1', + 'us-east-2', + 'us-west-1', + 'us-west-2', + 'eu-west-1', + 'eu-west-2', + 'eu-west-3', + 'eu-central-1', + 'eu-north-1', + 'ap-northeast-1', + 'ap-northeast-2', + 'ap-northeast-3', + 'ap-south-1', + 'ap-southeast-1', + 'ap-southeast-2', + 'ca-central-1', + 'sa-east-1', + ]; + public readonly string $url; private HttpClientInterface $client; @@ -161,6 +181,7 @@ public function markDeploymentFinished( * url: string|null, * outputs: array, * app: array{id: int, name: string}, + * aws_account_id: int|null, * } * * @throws HttpExceptionInterface @@ -179,6 +200,7 @@ public function getEnvironment(int $id): array * url: string|null, * outputs: array, * app: array{id: int, name: string}, + * aws_account_id: int|null, * } * * @throws HttpExceptionInterface @@ -276,4 +298,24 @@ public function addAwsAccount(mixed $teamId, string $accountName, string $roleAr ], ]); } + + public function createSecret(int $teamId, string $appName, string $environmentName, string $name, string $value, ?int $awsAccountId = null, ?string $region = null): void + { + $body = [ + 'team_id' => $teamId, + 'app_name' => $appName, + 'environment_name' => $environmentName, + 'name' => $name, + 'value' => $value, + ]; + if ($awsAccountId !== null) { + $body['aws_account_id'] = $awsAccountId; + } + if ($region !== null) { + $body['region'] = $region; + } + $this->client->request('POST', '/api/v1/secrets', [ + 'json' => $body, + ]); + } } diff --git a/src/Commands/Deploy.php b/src/Commands/Deploy.php index 626a5d8..7998c9d 100644 --- a/src/Commands/Deploy.php +++ b/src/Commands/Deploy.php @@ -216,14 +216,7 @@ private function selectAwsRegion(): string { $region = IO::ask(new ChoiceQuestion( 'Please select the AWS region to deploy to:', - [ - 'us-east-1', - 'us-east-2', - 'eu-west-1', - 'eu-west-2', - 'eu-west-3', - // @TODO: regions - ], + BrefCloudClient::AWS_REGIONS, )); if (! is_string($region)) { diff --git a/src/Commands/SecretCreate.php b/src/Commands/SecretCreate.php new file mode 100644 index 0000000..7537493 --- /dev/null +++ b/src/Commands/SecretCreate.php @@ -0,0 +1,138 @@ +setName('secret:create') + ->setDescription('Create a secret for an environment') + ->addArgument('name', InputArgument::OPTIONAL, 'The secret name') + ->addArgument('value', InputArgument::OPTIONAL, 'The secret value') + ->addOption('app', null, InputOption::VALUE_REQUIRED, 'The app name (if no config file exists)'); + + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + // If --app and --team are provided, we can skip loading the config file + if ($input->getOption('app') && $input->getOption('team')) { + $appName = $input->getOption('app'); + $team = $input->getOption('team'); + $environment = $input->getOption('env'); + } else { + [ + 'appName' => $appName, + 'environmentName' => $environment, + 'team' => $team, + ] = $this->parseStandardOptions($input); + + // Override app name if --app option provided + if ($input->getOption('app')) { + $appName = $input->getOption('app'); + } + } + + // Get secret name (from argument or prompt) + $name = $input->getArgument('name'); + if (empty($name)) { + $question = new Question('Secret name: '); + $question->setValidator(function (?string $value): string { + if (empty($value)) { + throw new Exception('Secret name cannot be empty'); + } + return $value; + }); + $name = IO::ask($question); + } + + // Get secret value (from argument or prompt) + $value = $input->getArgument('value'); + if ($value === null) { + $question = new Question('Secret value: '); + $question->setHidden(true)->setHiddenFallback(false); + $value = IO::ask($question); + } + + $brefCloud = new BrefCloudClient; + + // Resolve team slug to team ID + $teams = $brefCloud->listTeams(); + $teamId = null; + foreach ($teams as $t) { + if ($t['slug'] === $team) { + $teamId = $t['id']; + break; + } + } + if ($teamId === null) { + throw new Exception("Team '$team' not found"); + } + + // Check if the environment exists and is deployed + $awsAccountId = null; + $region = null; + try { + $existingEnv = $brefCloud->findEnvironment($team, $appName, $environment); + if (! empty($existingEnv['region'])) { + // Environment is deployed, use its aws_account_id and region + $awsAccountId = $existingEnv['aws_account_id']; + $region = $existingEnv['region']; + } + } catch (HttpExceptionInterface $e) { + if ($e->getResponse()->getStatusCode() !== 404) { + throw $e; + } + IO::verbose('Environment not found: ' . $e->getMessage()); + // Environment doesn't exist + } + + if ($awsAccountId === null) { + // Environment doesn't exist or isn't deployed, need to select AWS account and region + $awsAccounts = $brefCloud->listAwsAccounts(); + if (empty($awsAccounts)) { + throw new Exception('No AWS accounts found. Please connect an AWS account first using "bref connect".'); + } + $awsAccountsByName = []; + foreach ($awsAccounts as $account) { + $awsAccountsByName[$account['name']] = $account['id']; + } + $awsAccountName = IO::ask(new ChoiceQuestion( + 'Select the AWS account:', + array_keys($awsAccountsByName), + )); + if (! is_string($awsAccountName)) { + throw new Exception('No AWS account selected'); + } + $awsAccountId = $awsAccountsByName[$awsAccountName]; + + $region = IO::ask(new ChoiceQuestion( + 'Select the AWS region:', + BrefCloudClient::AWS_REGIONS, + )); + if (! is_string($region)) { + throw new Exception('No region selected'); + } + } + + IO::spin('Creating secret'); + $brefCloud->createSecret($teamId, $appName, $environment, $name, $value, $awsAccountId, $region); + IO::spinSuccess('Secret created'); + + return 0; + } +}