Skip to content

Commit 858f36a

Browse files
authored
Merge pull request #2 from paul-unifra/main
fix: bugs in AWS Secrets Manager push service
2 parents 9c29d47 + a12f0bd commit 858f36a

File tree

1 file changed

+86
-35
lines changed

1 file changed

+86
-35
lines changed

src/commands/setup/push-secrets.ts

Lines changed: 86 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import { confirm, input, select } from '@inquirer/prompts'
12
import { Command, Flags } from '@oclif/core'
3+
import chalk from 'chalk'
4+
import { exec } from 'child_process'
25
import * as fs from 'fs'
6+
import * as yaml from 'js-yaml'
37
import * as path from 'path'
4-
import { exec } from 'child_process'
58
import { promisify } from 'util'
6-
import { select, input, confirm } from '@inquirer/prompts'
7-
import * as yaml from 'js-yaml'
8-
import chalk from 'chalk'
99

1010
const execAsync = promisify(exec)
1111

@@ -14,32 +14,85 @@ interface SecretService {
1414
}
1515

1616
class AWSSecretService implements SecretService {
17-
constructor(private region: string, private prefixName: string) { }
17+
constructor(private region: string, private prefixName: string, private debug: boolean) { }
1818

19-
private async convertToJson(filePath: string): Promise<string> {
20-
const content = await fs.promises.readFile(filePath, 'utf-8')
21-
const lines = content.split('\n')
22-
const jsonContent: Record<string, string> = {}
23-
24-
for (const line of lines) {
25-
if (line.trim() && !line.startsWith('#')) {
26-
const [key, ...valueParts] = line.split(':')
27-
const value = valueParts.join(':').trim()
28-
jsonContent[key.trim()] = value.replace(/^"/, '').replace(/"$/, '')
19+
private async secretExists(secretName: string): Promise<boolean> {
20+
const fullSecretName = `${this.prefixName}/${secretName}`
21+
try {
22+
await execAsync(`aws secretsmanager describe-secret --secret-id "${fullSecretName}" --region ${this.region}`)
23+
return true
24+
} catch (error: any) {
25+
if (error.message.includes('ResourceNotFoundException')) {
26+
return false
2927
}
28+
throw error
3029
}
30+
}
31+
32+
private async createOrUpdateSecret(content: Record<string, string>, secretName: string): Promise<void> {
33+
const fullSecretName = `${this.prefixName}/${secretName}`
34+
const jsonContent = JSON.stringify(content)
35+
const escapedJsonContent = jsonContent.replace(/'/g, "'\\''")
36+
37+
if (await this.secretExists(secretName)) {
38+
const shouldOverride = await confirm({
39+
message: chalk.yellow(`Secret ${fullSecretName} already exists. Do you want to override it?`),
40+
default: false,
41+
})
42+
43+
if (!shouldOverride) {
44+
console.log(chalk.yellow(`Skipping secret: ${fullSecretName}`))
45+
return
46+
}
3147

32-
return JSON.stringify(jsonContent)
48+
const command = `aws secretsmanager put-secret-value --secret-id "${fullSecretName}" --secret-string '${escapedJsonContent}' --region ${this.region}`
49+
if (this.debug) {
50+
console.log(chalk.yellow('--- Debug Output ---'))
51+
console.log(chalk.cyan(`Command: ${command}`))
52+
console.log(chalk.yellow('-------------------'))
53+
}
54+
try {
55+
await execAsync(command)
56+
console.log(chalk.green(`Successfully updated secret: ${fullSecretName}`))
57+
} catch (error) {
58+
console.error(chalk.red(`Failed to update secret: ${fullSecretName}`))
59+
console.error(chalk.red(`Error details: ${error}`))
60+
throw error
61+
}
62+
} else {
63+
const command = `aws secretsmanager create-secret --name "${fullSecretName}" --secret-string '${escapedJsonContent}' --region ${this.region}`
64+
if (this.debug) {
65+
console.log(chalk.yellow('--- Debug Output ---'))
66+
console.log(chalk.cyan(`Command: ${command}`))
67+
console.log(chalk.yellow('-------------------'))
68+
}
69+
try {
70+
await execAsync(command)
71+
console.log(chalk.green(`Successfully created secret: ${fullSecretName}`))
72+
} catch (error) {
73+
console.error(chalk.red(`Failed to create secret: ${fullSecretName}`))
74+
console.error(chalk.red(`Error details: ${error}`))
75+
throw error
76+
}
77+
}
3378
}
3479

35-
private async pushToAWSSecret(content: string, secretName: string): Promise<void> {
36-
const command = `aws secretsmanager create-secret --name "${this.prefixName}/${secretName}" --secret-string "${JSON.stringify(content).slice(1, -1)}" --region ${this.region}`
37-
try {
38-
await execAsync(command)
39-
console.log(chalk.green(`Successfully pushed secret: ${this.prefixName}/${secretName}`))
40-
} catch (error) {
41-
console.error(chalk.red(`Failed to push secret: ${this.prefixName}/${secretName}`))
80+
private async convertEnvToDict(filePath: string): Promise<Record<string, string>> {
81+
const content = await fs.promises.readFile(filePath, 'utf-8')
82+
const result: Record<string, string> = {}
83+
84+
const lines = content.split('\n')
85+
for (const line of lines) {
86+
const match = line.match(/^([^=]+)=(.*)$/)
87+
if (match) {
88+
const key = match[1].trim()
89+
let value = match[2].trim()
90+
value = value.replace(/^["'](.*)["']$/, '$1')
91+
result[key] = value
92+
}
4293
}
94+
95+
return result
4396
}
4497

4598
async pushSecrets(): Promise<void> {
@@ -51,7 +104,7 @@ class AWSSecretService implements SecretService {
51104
const secretName = path.basename(file, '.json')
52105
console.log(chalk.cyan(`Processing JSON secret: ${secretName}`))
53106
const content = await fs.promises.readFile(path.join(secretsDir, file), 'utf-8')
54-
await this.pushToAWSSecret(content, secretName)
107+
await this.createOrUpdateSecret({ 'migrate-db.json': content }, secretName)
55108
}
56109

57110
// Process ENV files
@@ -62,20 +115,19 @@ class AWSSecretService implements SecretService {
62115
const secretName = path.basename(file, '.env')
63116
if (secretName.startsWith('l2-sequencer-')) {
64117
console.log(chalk.cyan(`Processing L2 Sequencer secret: ${secretName}`))
65-
const content = await this.convertToJson(path.join(secretsDir, file))
66-
l2SequencerSecrets = { ...l2SequencerSecrets, ...JSON.parse(content) }
118+
const data = await this.convertEnvToDict(path.join(secretsDir, file))
119+
l2SequencerSecrets = { ...l2SequencerSecrets, ...data }
67120
} else {
68-
console.log(chalk.cyan(`Processing ENV secret: ${secretName}`))
69-
const content = await this.convertToJson(path.join(secretsDir, file))
70-
await this.pushToAWSSecret(content, secretName)
121+
console.log(chalk.cyan(`Processing ENV secret: ${secretName}-env`))
122+
const data = await this.convertEnvToDict(path.join(secretsDir, file))
123+
await this.createOrUpdateSecret(data, `${secretName}-env`)
71124
}
72125
}
73126

74127
// Push combined L2 Sequencer secrets
75128
if (Object.keys(l2SequencerSecrets).length > 0) {
76-
console.log(chalk.cyan(`Processing combined L2 Sequencer secrets: l2-sequencer-secret`))
77-
const combinedContent = JSON.stringify(l2SequencerSecrets)
78-
await this.pushToAWSSecret(combinedContent, 'l2-sequencer-secret')
129+
console.log(chalk.cyan(`Processing combined L2 Sequencer secrets: l2-sequencer-secret-env`))
130+
await this.createOrUpdateSecret(l2SequencerSecrets, 'l2-sequencer-secret-env')
79131
}
80132
}
81133
}
@@ -321,7 +373,6 @@ export default class SetupPushSecrets extends Command {
321373
const valuesDir = path.join(process.cwd(), this.flags['values-dir']);
322374
if (!fs.existsSync(valuesDir)) {
323375
this.error(chalk.red(`Values directory not found at ${valuesDir}`));
324-
return;
325376
}
326377

327378
let credentials: Record<string, string>;
@@ -422,7 +473,7 @@ export default class SetupPushSecrets extends Command {
422473

423474
if (secretService === 'aws') {
424475
const awsCredentials = await this.getAWSCredentials()
425-
service = new AWSSecretService(awsCredentials.secretRegion, awsCredentials.prefixName)
476+
service = new AWSSecretService(awsCredentials.secretRegion, awsCredentials.prefixName, flags.debug)
426477
provider = 'aws'
427478
} else if (secretService === 'vault') {
428479
service = new HashicorpVaultDevService(flags.debug)
@@ -451,4 +502,4 @@ export default class SetupPushSecrets extends Command {
451502
this.error(chalk.red(`Failed to push secrets: ${error}`))
452503
}
453504
}
454-
}
505+
}

0 commit comments

Comments
 (0)