@toa.io/cli 0.20.0-dev.30 → 0.20.0-dev.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/readme.md +9 -4
- package/src/commands/conceal.js +25 -4
- package/src/commands/env.js +7 -0
- package/src/handlers/conceal.js +40 -1
- package/src/handlers/env.js +60 -8
- package/src/handlers/reveal.js +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/cli",
|
|
3
|
-
"version": "0.20.0-dev.
|
|
3
|
+
"version": "0.20.0-dev.33",
|
|
4
4
|
"description": "Toa CLI",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"@toa.io/runtime": "*"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@toa.io/console": "0.20.0-dev.
|
|
26
|
-
"@toa.io/generic": "0.20.0-dev.
|
|
27
|
-
"@toa.io/kubernetes": "0.20.0-dev.
|
|
28
|
-
"@toa.io/norm": "0.20.0-dev.
|
|
29
|
-
"@toa.io/yaml": "0.20.0-dev.
|
|
25
|
+
"@toa.io/console": "0.20.0-dev.34",
|
|
26
|
+
"@toa.io/generic": "0.20.0-dev.34",
|
|
27
|
+
"@toa.io/kubernetes": "0.20.0-dev.34",
|
|
28
|
+
"@toa.io/norm": "0.20.0-dev.34",
|
|
29
|
+
"@toa.io/yaml": "0.20.0-dev.34",
|
|
30
30
|
"dotenv": "16.1.1",
|
|
31
31
|
"find-up": "5.0.0",
|
|
32
32
|
"yargs": "17.6.2"
|
|
33
33
|
},
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "7035b1985fe9bb844069308a272d061bfbd38bf0"
|
|
35
35
|
}
|
package/readme.md
CHANGED
|
@@ -35,7 +35,8 @@ Export environment to a `.env` file.
|
|
|
35
35
|
<dd>
|
|
36
36
|
<code>environment</code> deployment environment name (default <code>local</code>).<br/>
|
|
37
37
|
<code>--path</code> path to a Context (default <code>.</code>)<br/>
|
|
38
|
-
<code>--as</code> output file path (default <code>.env</code>)
|
|
38
|
+
<code>--as</code> output file path (default <code>.env</code>)<br/>
|
|
39
|
+
<code>--interactive</code> prompt for secret values
|
|
39
40
|
</dd>
|
|
40
41
|
</dl>
|
|
41
42
|
|
|
@@ -72,7 +73,8 @@ $ toa replay ./path/to/context
|
|
|
72
73
|
$ toa replay --title "should add numbers"
|
|
73
74
|
```
|
|
74
75
|
|
|
75
|
-
If the path is a Context root (containing `context.toa.yaml` file), samples for components within
|
|
76
|
+
If the path is a Context root (containing `context.toa.yaml` file), samples for components within
|
|
77
|
+
the Context will be
|
|
76
78
|
found and replayed sequentially.
|
|
77
79
|
|
|
78
80
|
### export manifest
|
|
@@ -131,11 +133,14 @@ Deploy a generic Kubernetes secret with the prefix `toa-`.
|
|
|
131
133
|
<dd>
|
|
132
134
|
<code>secret</code> Secret name.<br/>
|
|
133
135
|
<code>key-values</code> List of keys and values of the secret as <code>key=value</code>.<br/>
|
|
134
|
-
<code>--namespace</code> Kubernetes namespace where the secret should be deployed
|
|
136
|
+
<code>--namespace</code> Kubernetes namespace where the secret should be deployed.<br/>
|
|
137
|
+
<code>--interactive</code> prompt for secret values<br/>
|
|
138
|
+
<code>--environment</code> environment name for interactive mode<br/>
|
|
139
|
+
<code>--path</code> path to a context for interactive mode
|
|
135
140
|
</dd>
|
|
136
141
|
</dl>
|
|
137
142
|
|
|
138
|
-
>
|
|
143
|
+
> If a secret already exists, then given `key-values` will be added to it.
|
|
139
144
|
|
|
140
145
|
#### Example
|
|
141
146
|
|
package/src/commands/conceal.js
CHANGED
|
@@ -18,14 +18,35 @@ const builder = (yargs) => {
|
|
|
18
18
|
type: 'string',
|
|
19
19
|
desc: 'Target Kubernetes namespace'
|
|
20
20
|
})
|
|
21
|
+
.option('interactive', {
|
|
22
|
+
alias: 'i',
|
|
23
|
+
group: 'Command options:',
|
|
24
|
+
describe: 'Prompt for secrets',
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
default: false
|
|
27
|
+
})
|
|
28
|
+
.option('environment', {
|
|
29
|
+
alias: 'e',
|
|
30
|
+
group: 'Command options:',
|
|
31
|
+
describe: 'Environment name for interactive mode',
|
|
32
|
+
type: 'string'
|
|
33
|
+
})
|
|
34
|
+
.option('path', {
|
|
35
|
+
alias: 'p',
|
|
36
|
+
group: 'Command options:',
|
|
37
|
+
describe: 'Path to a Context for interactive mode',
|
|
38
|
+
type: 'string',
|
|
39
|
+
default: '.'
|
|
40
|
+
})
|
|
21
41
|
.example([
|
|
22
|
-
['$0 conceal
|
|
23
|
-
['$0 conceal
|
|
24
|
-
['$0 conceal
|
|
42
|
+
['$0 conceal -i'],
|
|
43
|
+
['$0 conceal credentials username=developer'],
|
|
44
|
+
['$0 conceal credentials username=developer password=secret'],
|
|
45
|
+
['$0 conceal credentials username=developer --namespace app']
|
|
25
46
|
])
|
|
26
47
|
}
|
|
27
48
|
|
|
28
|
-
exports.command = 'conceal
|
|
49
|
+
exports.command = 'conceal [secret] [key-values...]'
|
|
29
50
|
exports.desc = 'Deploy a secret'
|
|
30
51
|
exports.builder = builder
|
|
31
52
|
exports.handler = conceal
|
package/src/commands/env.js
CHANGED
|
@@ -22,6 +22,13 @@ const builder = (yargs) => {
|
|
|
22
22
|
type: 'string',
|
|
23
23
|
default: '.env'
|
|
24
24
|
})
|
|
25
|
+
.option('interactive', {
|
|
26
|
+
alias: 'i',
|
|
27
|
+
group: 'Command options:',
|
|
28
|
+
describe: 'Prompt for secrets',
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
default: false
|
|
31
|
+
})
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
exports.command = 'env [environment]'
|
package/src/handlers/conceal.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { secrets } = require('@toa.io/kubernetes')
|
|
4
|
+
const boot = require('@toa.io/boot')
|
|
5
|
+
const { context: find } = require('../util/find')
|
|
6
|
+
const { promptSecrets } = require('./env')
|
|
4
7
|
|
|
5
8
|
const conceal = async (argv) => {
|
|
9
|
+
if (argv.interactive) await concealValues(argv)
|
|
10
|
+
else await concealValue(argv)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function concealValue (argv) {
|
|
14
|
+
if (argv['key-values'].length === 0) throw new Error('Key-values must be passed')
|
|
15
|
+
|
|
6
16
|
const values = argv['key-values'].reduce((values, pair) => {
|
|
7
17
|
const [key, value] = pair.split('=')
|
|
8
18
|
|
|
@@ -13,7 +23,36 @@ const conceal = async (argv) => {
|
|
|
13
23
|
|
|
14
24
|
const secret = PREFIX + argv.secret
|
|
15
25
|
|
|
16
|
-
await secrets.
|
|
26
|
+
await secrets.upsert(secret, values, argv.namespace)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function concealValues (argv) {
|
|
30
|
+
const path = find(argv.path)
|
|
31
|
+
const operator = await boot.deployment(path, argv.environment)
|
|
32
|
+
const variables = operator.variables()
|
|
33
|
+
const values = await promptSecrets(variables)
|
|
34
|
+
const groups = groupValues(values)
|
|
35
|
+
|
|
36
|
+
for (const [secret, values] of Object.entries(groups)) {
|
|
37
|
+
await secrets.upsert(secret, values, argv.namespace)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @return {Record<string, Record<string, string>>}
|
|
43
|
+
*/
|
|
44
|
+
function groupValues (values) {
|
|
45
|
+
const secrets = {}
|
|
46
|
+
|
|
47
|
+
for (const [key, value] of Object.entries(values)) {
|
|
48
|
+
const [secret, variable] = key.split('/')
|
|
49
|
+
|
|
50
|
+
if (!(secret in secrets)) secrets[secret] = {}
|
|
51
|
+
|
|
52
|
+
secrets[secret][variable] = value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return secrets
|
|
17
56
|
}
|
|
18
57
|
|
|
19
58
|
const PREFIX = 'toa-'
|
package/src/handlers/env.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { join } = require('node:path')
|
|
4
|
+
const readline = require('node:readline/promises')
|
|
5
|
+
const { stdin: input, stdout: output } = require('node:process')
|
|
6
|
+
|
|
4
7
|
const dotenv = require('dotenv')
|
|
5
8
|
const { file } = require('@toa.io/filesystem')
|
|
6
9
|
const boot = require('@toa.io/boot')
|
|
@@ -12,13 +15,16 @@ async function env (argv) {
|
|
|
12
15
|
const operator = await boot.deployment(path, argv.environment)
|
|
13
16
|
const variables = operator.variables()
|
|
14
17
|
const currentValues = await read(filepath)
|
|
15
|
-
const values = []
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
const result = merge(variables, currentValues)
|
|
20
|
+
|
|
21
|
+
if (argv.interactive) {
|
|
22
|
+
const secrets = await promptSecrets(result)
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
mergeSecrets(result, secrets)
|
|
25
|
+
}
|
|
20
26
|
|
|
21
|
-
await write(filepath,
|
|
27
|
+
await write(filepath, result)
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/**
|
|
@@ -41,25 +47,71 @@ async function read (path) {
|
|
|
41
47
|
* @return {Promise<void>}
|
|
42
48
|
*/
|
|
43
49
|
async function write (path, values) {
|
|
44
|
-
const contents = values.reduce((lines, { name, value }) => lines + `${name}=${value}\n`, '')
|
|
50
|
+
const contents = values.reduce((lines, { name, value }) => lines + `${name}=${value ?? ''}\n`, '')
|
|
45
51
|
|
|
46
52
|
await file.write(path, contents)
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/**
|
|
50
|
-
* @param {toa.deployment.dependency.Variable[]
|
|
56
|
+
* @param {toa.deployment.dependency.Variable[]} variables
|
|
51
57
|
* @param {Record<string, string>} current
|
|
52
58
|
* @return {toa.deployment.dependency.Variable[]}
|
|
53
59
|
*/
|
|
54
60
|
function merge (variables, current) {
|
|
55
61
|
return variables.map((variable) => {
|
|
56
|
-
if (variable.secret === undefined) return variable
|
|
62
|
+
if (variable.secret === undefined || !current[variable.name]) return variable
|
|
57
63
|
|
|
58
64
|
return {
|
|
59
65
|
name: variable.name,
|
|
60
|
-
value: current[variable.name]
|
|
66
|
+
value: current[variable.name]
|
|
61
67
|
}
|
|
62
68
|
})
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
async function promptSecrets (variables) {
|
|
72
|
+
const rl = readline.createInterface({ input, output })
|
|
73
|
+
const secrets = {}
|
|
74
|
+
|
|
75
|
+
for (const variable of variables) {
|
|
76
|
+
if (variable.secret === undefined) continue
|
|
77
|
+
|
|
78
|
+
const key = getKey(variable.secret)
|
|
79
|
+
|
|
80
|
+
secrets[key] = await promptSecret(key, rl)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
rl.close()
|
|
84
|
+
|
|
85
|
+
return secrets
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function promptSecret (key, rl) {
|
|
89
|
+
if (SECRETS[key] === undefined) SECRETS[key] = await rl.question(`${key}: `)
|
|
90
|
+
|
|
91
|
+
return SECRETS[key]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {toa.deployment.dependency.Variable[]} variables
|
|
96
|
+
* @param {Record<string, string>} secrets
|
|
97
|
+
*/
|
|
98
|
+
function mergeSecrets (variables, secrets) {
|
|
99
|
+
for (const variable of variables) {
|
|
100
|
+
if (variable.secret === undefined) continue
|
|
101
|
+
|
|
102
|
+
const key = getKey(variable.secret)
|
|
103
|
+
|
|
104
|
+
variable.value = secrets[key]
|
|
105
|
+
|
|
106
|
+
delete variable.secret
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getKey (secret) {
|
|
111
|
+
return `${secret.name}/${secret.key}`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const SECRETS = {}
|
|
115
|
+
|
|
65
116
|
exports.env = env
|
|
117
|
+
exports.promptSecrets = promptSecrets
|
package/src/handlers/reveal.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { secrets } = require('@toa.io/kubernetes')
|
|
4
|
-
const { remap, decode } = require('@toa.io/generic')
|
|
5
4
|
|
|
6
5
|
const { PREFIX } = require('./conceal')
|
|
7
6
|
|
|
8
7
|
const reveal = async (argv) => {
|
|
9
8
|
const prefixed = PREFIX + argv.secret
|
|
10
|
-
const
|
|
11
|
-
const values = remap(secret.data, decode)
|
|
9
|
+
const data = await secrets.get(prefixed)
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
if (data === null) return
|
|
12
|
+
|
|
13
|
+
for (const [key, value] of Object.entries(data)) {
|
|
14
14
|
const line = `${key}: ${value}`
|
|
15
15
|
|
|
16
16
|
console.log(line)
|