Skip to content

Commit 9fd50f2

Browse files
authored
Merge pull request #1 from sidebase/feat/add-exec-command
feat: add exec command implementation
2 parents c1f28d2 + 0844be3 commit 9fd50f2

3 files changed

Lines changed: 94 additions & 0 deletions

File tree

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@
2727
<!-- Badges End -->
2828

2929
**Simple AWS SSM Secrets Manager CLI**
30+
3031
Securely manage your AWS SSM Parameters — authenticate once via your OS keyring and easily list, get, write, or delete secrets.
3132

3233
## ✨ Features
3334

3435
* 🔐 **Secure local credential storage** using native OS keyrings
3536
(via [`keyring-node`](https://github.com/Brooooooklyn/keyring-node), powered by [`keyring-rs`](https://github.com/open-source-cooperative/keyring-rs))
3637
* 🧩 **List / get / put / delete** SSM parameters
38+
* 🏃 **Run** commands with environment variables from SSM parameters
3739
* 🧠 **Output formatting** as `.env` or JSON
3840
* 🪄 Works with AWS SSM Parameter Store, recursive listing included
3941
* 🧰 Both **CLI** and **programmatic API** available
@@ -167,6 +169,36 @@ Outputs:
167169
✅ Parameter deleted
168170
```
169171

172+
### 💿 Execute a command with SSM environment
173+
174+
Fetches all parameters from a given SSM path, transforms them into environment
175+
variables, and executes the provided command with that environment.
176+
177+
Variable names are uppercased and stripped of the path prefix.
178+
Example: `/my/app/parameter` becomes `PARAMETER` environment variable.
179+
180+
```bash
181+
ssm-secrets exec my/app -- node server.js
182+
````
183+
184+
If you need to pass `--argument`s to your command, separate them using a double dash:
185+
186+
```bash
187+
ssm-secrets exec my/app -- node server.js --inspect
188+
```
189+
190+
Options:
191+
* `--no-overwrite`
192+
Do not overwrite existing environment variables.
193+
194+
* `--ignore <names...>`
195+
Ignore specific parameter names (case-sensitive, without path prefix).
196+
Example:
197+
198+
```bash
199+
ssm-secrets exec my/app --ignore FOO bar -- node server.js
200+
```
201+
170202
## ⚙️ Programmatic API
171203

172204
You can also use the API directly in Node.js:
@@ -221,6 +253,7 @@ ssm-secrets auth
221253
ssm-secrets put my/app DB_USER myuser
222254
ssm-secrets put my/app DB_PASS mypassword
223255
ssm-secrets list my/app --format env
256+
ssm-secrets exec my/app -- node server.js
224257
```
225258
226259
Output:

src/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { listCommand } from './commands/list.js'
66
import { getCommand } from './commands/get.js'
77
import { putCommand } from './commands/put.js'
88
import { deleteCommand } from './commands/delete.js'
9+
import { execCommand } from './commands/exec.js'
910

1011
const program = new Command()
1112
program.name('ssm-secrets').description('Simple AWS SSM secrets manager CLI').version(packageJson.version)
@@ -15,6 +16,7 @@ listCommand(program)
1516
getCommand(program)
1617
putCommand(program)
1718
deleteCommand(program)
19+
execCommand(program)
1820

1921
try {
2022
await program.parseAsync(process.argv)

src/commands/exec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Command } from 'commander'
2+
import { listParameters } from '../aws.js'
3+
import { prettifyParameter } from '../utils.js'
4+
import { spawn } from 'node:child_process'
5+
6+
const SUMMARY = 'Execute the command with the environment from SSM'
7+
const DESCRIPTION = `${SUMMARY}.
8+
This command will fetch the parameters from SSM and provide them as environment variables to the specified command.
9+
10+
The variable names are stripped of path prefix and uppercased.
11+
Example: parameter with path my/app/parameter becomes PARAMETER environment variable.`
12+
13+
export function execCommand(program: Command) {
14+
program
15+
.command('exec')
16+
.summary(SUMMARY)
17+
.description(DESCRIPTION)
18+
.argument('<path>', 'SSM parameter path')
19+
.argument('<command>', 'Command to execute')
20+
.argument('[args...]', 'Arguments for the command. Use -- before command to ensure that arguments are passed correctly: ssm-secrets exec my/path -- command --argument')
21+
.option('--no-overwrite', 'Do not overwrite existing environment variables')
22+
.option(
23+
'--ignore <ignores...>',
24+
'Do not include the parameters with the given names to environment. '
25+
+ 'The parameter names are case-sensitive and should match the name inside SSM excluding path prefix. '
26+
+ 'Example: if you want to ignore my/app/parameter1 and my/app/parameter2, use --ignore parameter1 parameter2'
27+
)
28+
.action(async (path: string, command: string, args: string[], options: { overwrite: boolean, ignore: string[] | undefined }) => {
29+
// Normalize options
30+
const ignore = options.ignore ?? []
31+
32+
const ssmParameters = await listParameters(path)
33+
34+
const filteredPrettyParams = ssmParameters
35+
.map(param => prettifyParameter(param, path))
36+
.filter(param => param.Name !== undefined && !ignore.includes(param.Name))
37+
38+
const envs = Object.fromEntries(filteredPrettyParams.map(param => [param.Name?.toUpperCase(), param.Value]))
39+
40+
// Merge into environment
41+
const env = options.overwrite
42+
? {
43+
...process.env,
44+
...envs,
45+
}
46+
: {
47+
...envs,
48+
...process.env,
49+
}
50+
51+
// Replace current process
52+
const child = spawn(command, args, {
53+
env,
54+
stdio: 'inherit',
55+
})
56+
57+
child.on('exit', code => process.exit(code ?? 0))
58+
})
59+
}

0 commit comments

Comments
 (0)