Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions src/Commands/ConfigInit.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use Gitcd\Helpers\GitHub;
use Gitcd\Helpers\GitHubApp;
use Gitcd\Utils\Json;
use Gitcd\Helpers\DeploymentState;
use Gitcd\Commands\Init\DotMenuTrait;

Class ConfigInit extends Command {
Expand Down Expand Up @@ -269,8 +270,7 @@ protected function flowEncrypt(
' Continue to encrypt files? [<fg=green>Y</>/n] ', true
);
if (!$helper->ask($input, $output, $question)) {
Json::write('deployment.secrets', 'encrypted', $repo_dir);
Json::save($repo_dir);
DeploymentState::setSecretsMode($repo_dir, 'encrypted');
return Command::SUCCESS;
}
}
Expand All @@ -284,8 +284,7 @@ protected function flowEncrypt(
$output->writeln(" <fg=gray>No unencrypted .env files found in the config repo.</>");
$output->writeln(" <fg=gray>All secrets appear to be encrypted already.</>");
$output->writeln('');
Json::write('deployment.secrets', 'encrypted', $repo_dir);
Json::save($repo_dir);
DeploymentState::setSecretsMode($repo_dir, 'encrypted');
$this->showCompletion($output, $foldername, Config::read('env', 'unknown'));
return Command::SUCCESS;
}
Expand Down Expand Up @@ -316,8 +315,7 @@ protected function flowEncrypt(
}
}

Json::write('deployment.secrets', 'encrypted', $repo_dir);
Json::save($repo_dir);
DeploymentState::setSecretsMode($repo_dir, 'encrypted');

$this->showCompletion($output, $foldername, Config::read('env', 'unknown'));
return Command::SUCCESS;
Expand Down Expand Up @@ -668,8 +666,7 @@ protected function stepSecrets(
$output->writeln(" <fg=cyan>protocol secrets:setup {$hexKey}</>");
$output->writeln('');

Json::write('deployment.secrets', 'encrypted', $repo_dir);
Json::save($repo_dir);
DeploymentState::setSecretsMode($repo_dir, 'encrypted');

$this->offerEncryptFiles($input, $output, $helper, $repo_dir, $configrepo);
} else {
Expand Down Expand Up @@ -704,8 +701,7 @@ protected function offerEncryptFiles(
Shell::run("git -C '$configrepo' add -A");
Shell::run("git -C '$configrepo' commit -m 'encrypt secrets' 2>/dev/null");

Json::write('deployment.secrets', 'encrypted', $repo_dir);
Json::save($repo_dir);
DeploymentState::setSecretsMode($repo_dir, 'encrypted');
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Commands/ConfigSave.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use Gitcd\Helpers\FileEncryption;
use Gitcd\Helpers\Secrets;
use Gitcd\Utils\Json;
use Gitcd\Helpers\DeploymentState;

Class ConfigSave extends Command {

Expand Down Expand Up @@ -99,7 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

// Re-encrypt .env files if encryption is configured
$secretsMode = Json::read('deployment.secrets', 'file', $repo_dir);
$secretsMode = DeploymentState::secretsMode($repo_dir);
if ($secretsMode === 'encrypted' && Secrets::hasKey()) {
$unencrypted = FileEncryption::findUnencryptedEnvFiles($configrepo);
if (!empty($unencrypted)) {
Expand Down
9 changes: 4 additions & 5 deletions src/Commands/Migrate.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
use Gitcd\Helpers\Secrets;
use Gitcd\Helpers\GitHub;
use Gitcd\Utils\Json;
use Gitcd\Helpers\DeploymentState;

Class Migrate extends Command {

Expand Down Expand Up @@ -93,7 +94,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int

// Step 1: Check current state
$currentStrategy = Json::read('deployment.strategy', null, $repo_dir);
$currentSecrets = Json::read('deployment.secrets', null, $repo_dir);
$currentSecrets = DeploymentState::secretsMode($repo_dir);

if ($currentStrategy === 'release' && $currentSecrets === 'encrypted' && !$secretsOnly) {
$output->writeln('<comment>This repo is already using release-based deployment with encrypted secrets.</comment>');
Expand Down Expand Up @@ -148,8 +149,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int

if ($secretsOnly) {
// Update just the secrets setting in protocol.json
Json::write('deployment.secrets', 'encrypted', $repo_dir);
Json::save($repo_dir);
DeploymentState::setSecretsMode($repo_dir, 'encrypted');
$output->writeln('');
$output->writeln('<info>Secrets migration complete. protocol.json updated with secrets: "encrypted"</info>');
return Command::SUCCESS;
Expand All @@ -162,8 +162,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
Json::write('deployment.strategy', 'release', $repo_dir);
Json::write('deployment.pointer', 'github_variable', $repo_dir);
Json::write('deployment.pointer_name', 'PROTOCOL_ACTIVE_RELEASE', $repo_dir);
Json::write('deployment.secrets', 'encrypted', $repo_dir);
Json::save($repo_dir);
DeploymentState::setSecretsMode($repo_dir, 'encrypted');
$output->writeln(' - Updated deployment strategy to "release"');
$output->writeln(' - Updated secrets mode to "encrypted"');

Expand Down
4 changes: 2 additions & 2 deletions src/Commands/ProtocolInit.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
use Gitcd\Helpers\GitHubApp;
use Gitcd\Utils\Json;
use Gitcd\Utils\Yaml;
use Gitcd\Helpers\DeploymentState;
use Gitcd\Commands\Init\ProjectType;
use Gitcd\Helpers\BlueGreen;
use Gitcd\Helpers\BlueGreen\ReleaseBuilder;
Expand Down Expand Up @@ -1621,8 +1622,7 @@ protected function configureSecrets(
if ($helper->ask($input, $output, $question)) {
$command = $this->getApplication()->find('secrets:setup');
$command->run(new ArrayInput([]), $output);
Json::write('deployment.secrets', 'encrypted', $repo_dir);
Json::save($repo_dir);
DeploymentState::setSecretsMode($repo_dir, 'encrypted');
} else {
$this->writeInfo($output, '<fg=gray>Skipped. Run</> <fg=cyan>protocol secrets:setup</> <fg=gray>later.</>');
}
Expand Down
2 changes: 1 addition & 1 deletion src/Commands/ProtocolStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ private function buildContext(InputInterface $input): array
$currentBranch = null;
$awaitingRelease = false;
$dockerImage = Json::read('docker.image', null, $repo_dir);
$secretsMode = Json::read('deployment.secrets', 'file', $repo_dir);
$secretsMode = DeploymentState::secretsMode($repo_dir);
$gitRemote = null;
$activeDir = $repo_dir;
}
Expand Down
53 changes: 52 additions & 1 deletion src/Helpers/DeploymentState.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class DeploymentState

/**
* Get the current deployment strategy.
*
* Strategy is node-specific (stored in NodeConfig), never from protocol.json.
* No node config means local dev = "none".
*/
public static function strategy(string $repoDir): string
{
Expand All @@ -44,7 +47,7 @@ public static function strategy(string $repoDir): string
}
}

return Json::read('deployment.strategy', 'none', $repoDir);
return 'none';
}

/**
Expand Down Expand Up @@ -200,6 +203,30 @@ public static function target(string $repoDir): ?string
return NodeConfig::read($project, 'release.target');
}

/**
* Get the secrets mode for this deployment.
*
* Production nodes (those with a NodeConfig entry) read exclusively
* from NodeConfig (~/.protocol/.node/nodes/<project>.json) and never
* fall back to the repo-level protocol.json.
*
* Development/staging (no NodeConfig) reads from protocol.json.
*
* @return string "file", "encrypted", or "aws"
*/
public static function secretsMode(string $repoDir): string
{
$project = self::resolveProjectName($repoDir);
if ($project) {
$mode = NodeConfig::read($project, 'deployment.secrets');
if ($mode) {
return $mode;
}
}

return Json::read('deployment.secrets', 'file', $repoDir);
}

// ─── Write ───────────────────────────────────────────────────────

/**
Expand Down Expand Up @@ -398,6 +425,30 @@ public static function setStrategy(string $repoDir, string $strategy): void
Json::save($repoDir);
}

/**
* Set the secrets mode for this deployment.
*
* Writes to NodeConfig for production nodes, and always to
* the repo-level protocol.json for dev/staging.
*
* @param string $mode "file", "encrypted", or "aws"
*/
public static function setSecretsMode(string $repoDir, string $mode): void
{
Log::info('deployment', "setSecretsMode: mode={$mode}");

$project = self::resolveProjectName($repoDir);
if ($project) {
NodeConfig::modify($project, function (array $nodeData) use ($mode) {
$nodeData['deployment']['secrets'] = $mode;
return $nodeData;
});
}

Json::write('deployment.secrets', $mode, $repoDir);
Json::save($repoDir);
}

// ─── Helpers ─────────────────────────────────────────────────────

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Helpers/SecretsProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
namespace Gitcd\Helpers;

use Gitcd\Utils\Json;
use Gitcd\Helpers\DeploymentState;
use Symfony\Component\Yaml\Yaml;

class SecretsProvider
Expand All @@ -53,7 +54,7 @@ class SecretsProvider
*/
public static function resolveToTempFile(string $repoDir): ?string
{
$mode = Json::read('deployment.secrets', 'file', $repoDir);
$mode = DeploymentState::secretsMode($repoDir);
self::log("resolveToTempFile: mode={$mode} repoDir={$repoDir}");

if ($mode === 'encrypted') {
Expand Down
3 changes: 2 additions & 1 deletion src/Helpers/Soc2Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Gitcd\Utils\Json;
use Gitcd\Utils\NodeConfig;
use Gitcd\Helpers\DeploymentState;

class Soc2Check extends BaseAuditChecker
{
Expand All @@ -30,7 +31,7 @@ public function runAll(): array
*/
protected function checkSecretsEncrypted(): void
{
$mode = Json::read('deployment.secrets', 'file', $this->repoDir);
$mode = DeploymentState::secretsMode($this->repoDir);
$hasKey = Secrets::hasKey();

if ($mode === 'aws') {
Expand Down
8 changes: 4 additions & 4 deletions src/Plugins/awssecrets/AwsSecretsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public static function region($repoDir = false): string

/**
* Get the secret name in AWS Secrets Manager.
* Always derived dynamically as protocol/{projectName}/{environment}.
*/
public static function secretName($repoDir = false): string
{
$default = self::defaultSecretName($repoDir);
return self::config('secret_name', $default, $repoDir);
return self::defaultSecretName($repoDir);
}

/**
Expand Down Expand Up @@ -72,9 +72,9 @@ public static function getClient($repoDir = false): SecretsManagerClient
*/
public static function log(string $message): void
{
$dir = NODE_DATA_DIR;
$dir = '/var/log/protocol/';
if (!is_dir($dir)) {
mkdir($dir, 0700, true);
mkdir($dir, 0755, true);
}
$logFile = $dir . 'aws-secrets.log';
$entry = date('Y-m-d\TH:i:sP') . ' ' . $message . "\n";
Expand Down
20 changes: 8 additions & 12 deletions src/Plugins/awssecrets/Commands/AwsSecretsInit.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Gitcd\Helpers\Shell;
use Gitcd\Helpers\Config;
use Gitcd\Utils\Json;
use Gitcd\Helpers\DeploymentState;

class AwsSecretsInit extends Command
{
Expand Down Expand Up @@ -201,7 +202,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln(" <info>✓</info> Secrets Manager access confirmed");
$output->writeln('');

// ── Step 2: Configure Region & Secret Name ───────────────────
// ── Step 2: Configure Region ──────────────────────────────────
$output->writeln(' <fg=white;options=bold>Step 2/3:</> Configuration');
$output->writeln('');

Expand All @@ -213,12 +214,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$region = $helper->ask($input, $output, $question);
$output->writeln('');

// Secret name
$defaultName = AwsSecretsHelper::config('secret_name', null, $repoDir)
?: AwsSecretsHelper::defaultSecretName($repoDir);

$question = new Question(" Secret name [<fg=cyan>{$defaultName}</>]: ", $defaultName);
$secretName = $helper->ask($input, $output, $question);
// Secret name is always derived: protocol/{name}/{env}
$secretName = AwsSecretsHelper::defaultSecretName($repoDir);
$output->writeln(" Secret name: <fg=cyan>{$secretName}</> <fg=gray>(protocol/{name}/{env})</>");
$output->writeln('');

// ── Step 3: Check Secret ─────────────────────────────────────
Expand All @@ -243,14 +241,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int

// ── Save Configuration ───────────────────────────────────────
Json::write('aws.region', $region, $repoDir);
Json::write('aws.secret_name', $secretName, $repoDir);
Json::write('deployment.secrets', 'aws', $repoDir);
Json::save($repoDir);
DeploymentState::setSecretsMode($repoDir, 'aws');

$output->writeln(' <info>Configuration saved to protocol.json:</info>');
$output->writeln(" aws.region = <fg=white>{$region}</>");
$output->writeln(" aws.secret_name = <fg=white>{$secretName}</>");
$output->writeln(" aws.region = <fg=white>{$region}</>");
$output->writeln(" deployment.secrets = <fg=white>aws</>");
$output->writeln(" secret name = <fg=white>{$secretName}</> <fg=gray>(derived)</>");
$output->writeln('');
$output->writeln(' Next steps:');
$output->writeln(' <fg=cyan>protocol aws:push</> Push your .env secrets to AWS');
Expand Down
Loading