@toa.io/extensions.configuration 0.20.0-dev.34 → 0.20.0-dev.35
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 +16 -9
- package/readme.md +19 -27
- package/schemas/annotation.cos.yaml +1 -0
- package/schemas/manifest.cos.yaml +2 -0
- package/source/Aspect.test.ts +15 -0
- package/source/Aspect.ts +23 -0
- package/source/Factory.ts +12 -0
- package/source/configuration.test.ts +89 -0
- package/source/configuration.ts +52 -0
- package/source/deployment.test.ts +21 -0
- package/source/deployment.ts +69 -0
- package/source/index.ts +3 -0
- package/source/manifest.test.ts +15 -0
- package/source/manifest.ts +15 -0
- package/source/schemas.ts +8 -0
- package/tsconfig.json +9 -0
- package/docs/discussion.md +0 -109
- package/source/.deployment/index.js +0 -7
- package/source/.deployment/secrets.js +0 -35
- package/source/.deployment/variables.js +0 -23
- package/source/.manifest/.normalize/verbose.js +0 -51
- package/source/.manifest/index.js +0 -7
- package/source/.manifest/normalize.js +0 -16
- package/source/.manifest/schema.yaml +0 -9
- package/source/.manifest/validate.js +0 -13
- package/source/.provider/env.js +0 -19
- package/source/.provider/form.js +0 -20
- package/source/.provider/index.js +0 -7
- package/source/annotation.fixtures.js +0 -39
- package/source/annotation.js +0 -36
- package/source/annotation.test.js +0 -43
- package/source/aspect.fixtures.js +0 -32
- package/source/aspect.js +0 -36
- package/source/aspect.test.js +0 -76
- package/source/configuration.js +0 -19
- package/source/deployment.fixtures.js +0 -38
- package/source/deployment.js +0 -20
- package/source/deployment.test.js +0 -70
- package/source/factory.js +0 -39
- package/source/factory.test.js +0 -22
- package/source/index.js +0 -13
- package/source/manifest.js +0 -13
- package/source/manifest.test.js +0 -123
- package/source/provider.js +0 -119
- package/source/provider.test.js +0 -130
- package/source/secrets.js +0 -28
- package/source/secrets.test.js +0 -47
- package/types/aspect.d.ts +0 -11
- package/types/factory.d.ts +0 -12
- package/types/provider.d.ts +0 -22
- /package/{docs → notes}/consistency.md +0 -0
package/source/manifest.test.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { generate } = require('randomstring')
|
|
4
|
-
const { random } = require('@toa.io/generic')
|
|
5
|
-
|
|
6
|
-
const { manifest } = require('../')
|
|
7
|
-
|
|
8
|
-
it('should export', () => {
|
|
9
|
-
expect(manifest).toBeInstanceOf(Function)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
describe('validation', () => {
|
|
13
|
-
it('should throw if not an object', () => {
|
|
14
|
-
const call = () => manifest(generate())
|
|
15
|
-
expect(call).toThrow(/must be object/)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('should throw if not a valid schema', () => {
|
|
19
|
-
const object = { type: generate() }
|
|
20
|
-
const call = () => manifest(object)
|
|
21
|
-
|
|
22
|
-
expect(call).toThrow(/one of the allowed values/)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('should throw if schema is not an object type', () => {
|
|
26
|
-
const schema = { type: 'number' }
|
|
27
|
-
const call = () => manifest(schema)
|
|
28
|
-
|
|
29
|
-
expect(call).toThrow(/equal to constant 'object'/)
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('normalization', () => {
|
|
34
|
-
it('should expand concise', () => {
|
|
35
|
-
const concise = {
|
|
36
|
-
foo: generate(),
|
|
37
|
-
bar: {
|
|
38
|
-
baz: random()
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const declaration = manifest(concise)
|
|
43
|
-
|
|
44
|
-
expect(declaration).toMatchObject({
|
|
45
|
-
type: 'object',
|
|
46
|
-
properties: {
|
|
47
|
-
foo: {
|
|
48
|
-
type: 'string',
|
|
49
|
-
default: concise.foo
|
|
50
|
-
},
|
|
51
|
-
bar: {
|
|
52
|
-
type: 'object',
|
|
53
|
-
properties: {
|
|
54
|
-
baz: {
|
|
55
|
-
type: 'number',
|
|
56
|
-
default: concise.bar.baz
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('should expand partially concise', () => {
|
|
65
|
-
const concise = {
|
|
66
|
-
foo: generate(),
|
|
67
|
-
bar: {
|
|
68
|
-
baz: random()
|
|
69
|
-
},
|
|
70
|
-
qux: {
|
|
71
|
-
type: 'string',
|
|
72
|
-
default: null
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const declaration = manifest(concise)
|
|
77
|
-
|
|
78
|
-
expect(declaration).toMatchObject({
|
|
79
|
-
type: 'object',
|
|
80
|
-
properties: {
|
|
81
|
-
foo: {
|
|
82
|
-
type: 'string',
|
|
83
|
-
default: concise.foo
|
|
84
|
-
},
|
|
85
|
-
bar: {
|
|
86
|
-
type: 'object',
|
|
87
|
-
properties: {
|
|
88
|
-
baz: {
|
|
89
|
-
type: 'number',
|
|
90
|
-
default: concise.bar.baz
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
qux: {
|
|
95
|
-
type: 'string',
|
|
96
|
-
default: null
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('should expand arrays', () => {
|
|
103
|
-
const concise = { foo: [1, 2, 3] }
|
|
104
|
-
|
|
105
|
-
const declaration = manifest(concise)
|
|
106
|
-
|
|
107
|
-
expect(declaration).toMatchObject({
|
|
108
|
-
type: 'object',
|
|
109
|
-
properties: {
|
|
110
|
-
foo: {
|
|
111
|
-
type: 'array',
|
|
112
|
-
default: [1, 2, 3]
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('should throw on empty array', () => {
|
|
119
|
-
const concise = { foo: [] }
|
|
120
|
-
|
|
121
|
-
expect(() => manifest(concise)).toThrow(/array items type because it's empty/)
|
|
122
|
-
})
|
|
123
|
-
})
|
package/source/provider.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const clone = require('clone-deep')
|
|
4
|
-
const { decode, encode, empty, overwrite } = require('@toa.io/generic')
|
|
5
|
-
|
|
6
|
-
const { Connector } = require('@toa.io/core')
|
|
7
|
-
|
|
8
|
-
const { secrets } = require('./secrets')
|
|
9
|
-
const { env, form } = require('./.provider')
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @implements {toa.extensions.configuration.Provider}
|
|
13
|
-
*/
|
|
14
|
-
class Provider extends Connector {
|
|
15
|
-
/** @type {toa.schema.Schema} */
|
|
16
|
-
#schema
|
|
17
|
-
|
|
18
|
-
/** @type {Object} */
|
|
19
|
-
#form
|
|
20
|
-
/** @type {Object} */
|
|
21
|
-
#value
|
|
22
|
-
|
|
23
|
-
source
|
|
24
|
-
object
|
|
25
|
-
key
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @param {toa.core.Locator} locator
|
|
29
|
-
* @param {toa.schema.Schema} schema
|
|
30
|
-
*/
|
|
31
|
-
constructor (locator, schema) {
|
|
32
|
-
super()
|
|
33
|
-
|
|
34
|
-
this.source = this.#source.bind(this)
|
|
35
|
-
|
|
36
|
-
this.key = PREFIX + locator.uppercase
|
|
37
|
-
this.#schema = schema
|
|
38
|
-
|
|
39
|
-
// form is required to enable nested defaults
|
|
40
|
-
this.#form = form(schema.schema)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async open () {
|
|
44
|
-
this.#retrieve()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
set (key, value) {
|
|
48
|
-
const object = this.object === undefined ? {} : clone(this.object)
|
|
49
|
-
const properties = key.split('.')
|
|
50
|
-
const property = properties.pop()
|
|
51
|
-
|
|
52
|
-
let cursor = object
|
|
53
|
-
|
|
54
|
-
for (const name of properties) {
|
|
55
|
-
if (cursor[name] === undefined) cursor[name] = {}
|
|
56
|
-
|
|
57
|
-
cursor = cursor[name]
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (value === undefined) delete cursor[property]
|
|
61
|
-
else cursor[property] = value
|
|
62
|
-
|
|
63
|
-
this.#set(object)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
unset (key) {
|
|
67
|
-
this.set(key, undefined)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
reset () {
|
|
71
|
-
this.object = undefined
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export () {
|
|
75
|
-
return this.object === undefined ? undefined : encode(this.object)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
#source () {
|
|
79
|
-
return this.#value
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
#retrieve () {
|
|
83
|
-
const string = process.env[this.key]
|
|
84
|
-
const object = string === undefined ? {} : decode(string)
|
|
85
|
-
|
|
86
|
-
this.#set(object)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
#set (object) {
|
|
90
|
-
object = this.#reveal(object)
|
|
91
|
-
object = env(object)
|
|
92
|
-
|
|
93
|
-
this.#merge(object)
|
|
94
|
-
|
|
95
|
-
this.object = empty(object) ? undefined : object
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
#merge (object) {
|
|
99
|
-
object = clone(object)
|
|
100
|
-
|
|
101
|
-
const form = clone(this.#form)
|
|
102
|
-
const value = overwrite(form, object)
|
|
103
|
-
|
|
104
|
-
this.#schema.validate(value)
|
|
105
|
-
this.#value = value
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
#reveal (object) {
|
|
109
|
-
return secrets(object, (variable) => {
|
|
110
|
-
if (!(variable in process.env)) throw new Error(`Configuration secret value ${variable} is not set`)
|
|
111
|
-
|
|
112
|
-
return process.env[variable]
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const PREFIX = 'TOA_CONFIGURATION_'
|
|
118
|
-
|
|
119
|
-
exports.Provider = Provider
|
package/source/provider.test.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* eslint-disable no-template-curly-in-string */
|
|
4
|
-
|
|
5
|
-
const { generate } = require('randomstring')
|
|
6
|
-
const { encode } = require('@toa.io/generic')
|
|
7
|
-
|
|
8
|
-
const { Provider } = require('./provider')
|
|
9
|
-
|
|
10
|
-
it('should be', async () => {
|
|
11
|
-
expect(Provider).toBeInstanceOf(Function)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
const locator = /** @type {toa.core.Locator} */ { uppercase: generate().toUpperCase() }
|
|
15
|
-
const schema = /** @type {toa.schema.Schema} */ { validate: () => undefined }
|
|
16
|
-
|
|
17
|
-
/** @type {Provider} */
|
|
18
|
-
let provider
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
cleanEnv()
|
|
22
|
-
provider = new Provider(locator, schema)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('should replace secret values', async () => {
|
|
26
|
-
const configuration = { foo: '$FOO_SECRET' }
|
|
27
|
-
const secrets = { FOO_SECRET: generate() }
|
|
28
|
-
|
|
29
|
-
setEnv(configuration, secrets)
|
|
30
|
-
|
|
31
|
-
await provider.open()
|
|
32
|
-
const value = provider.source()
|
|
33
|
-
|
|
34
|
-
expect(value).toStrictEqual({ foo: secrets.FOO_SECRET })
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should throw if secret value is not set', async () => {
|
|
38
|
-
const configuration = { foo: '$FOO_SECRET' }
|
|
39
|
-
|
|
40
|
-
setEnv(configuration)
|
|
41
|
-
|
|
42
|
-
await expect(provider.open()).rejects.toThrow('FOO_SECRET is not set')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('should replace nested secrets', async () => {
|
|
46
|
-
const configuration = { foo: { bar: '$BAR' } }
|
|
47
|
-
const secrets = { BAR: generate() }
|
|
48
|
-
|
|
49
|
-
setEnv(configuration, secrets)
|
|
50
|
-
|
|
51
|
-
await provider.open()
|
|
52
|
-
const value = provider.source()
|
|
53
|
-
|
|
54
|
-
expect(value).toStrictEqual({ foo: { bar: secrets.BAR } })
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('should replace placeholders', async () => {
|
|
58
|
-
const name = 'FOO_VALUE'
|
|
59
|
-
const configuration = { foo: { bar: 'foo_${' + name + '}' } }
|
|
60
|
-
const value = generate()
|
|
61
|
-
|
|
62
|
-
setEnv(configuration)
|
|
63
|
-
setVal(name, value)
|
|
64
|
-
|
|
65
|
-
await provider.open()
|
|
66
|
-
const source = provider.source()
|
|
67
|
-
|
|
68
|
-
expect(source).toStrictEqual({ foo: { bar: 'foo_' + value } })
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('should replace multiple placeholders', async () => {
|
|
72
|
-
const configuration = { foo: '${FOO} ${BAR}' }
|
|
73
|
-
|
|
74
|
-
setEnv(configuration)
|
|
75
|
-
setVal('FOO', 'hello')
|
|
76
|
-
setVal('BAR', 'world')
|
|
77
|
-
|
|
78
|
-
await provider.open()
|
|
79
|
-
const source = provider.source()
|
|
80
|
-
|
|
81
|
-
expect(source).toStrictEqual({ foo: 'hello world' })
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('should not replace if variable not set', async () => {
|
|
85
|
-
const configuration = { foo: '${FOO}' }
|
|
86
|
-
|
|
87
|
-
setEnv(configuration)
|
|
88
|
-
|
|
89
|
-
await provider.open()
|
|
90
|
-
const value = provider.source()
|
|
91
|
-
|
|
92
|
-
expect(value).toStrictEqual(configuration)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const usedVariables = []
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* @param {object} configuration
|
|
99
|
-
* @param {Record<string, string>} [secrets]
|
|
100
|
-
*/
|
|
101
|
-
function setEnv (configuration, secrets) {
|
|
102
|
-
const variable = PREFIX + locator.uppercase
|
|
103
|
-
const encoded = encode(configuration)
|
|
104
|
-
|
|
105
|
-
setVal(variable, encoded)
|
|
106
|
-
|
|
107
|
-
if (secrets !== undefined) {
|
|
108
|
-
for (const [key, value] of Object.entries(secrets)) {
|
|
109
|
-
const variable = PREFIX + '_' + key
|
|
110
|
-
|
|
111
|
-
process.env[variable] = value
|
|
112
|
-
usedVariables.push(variable)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function setVal (variable, value) {
|
|
118
|
-
process.env[variable] = value
|
|
119
|
-
usedVariables.push(variable)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function cleanEnv () {
|
|
123
|
-
for (const variable of usedVariables) {
|
|
124
|
-
delete process.env[variable]
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
usedVariables.length = 0
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const PREFIX = 'TOA_CONFIGURATION_'
|
package/source/secrets.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { map } = require('@toa.io/generic')
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {object} configuration
|
|
7
|
-
* @param {(variable: string, name?: string) => void} callback
|
|
8
|
-
* @returns {object}
|
|
9
|
-
*/
|
|
10
|
-
function secrets (configuration, callback) {
|
|
11
|
-
return map(configuration, (value) => {
|
|
12
|
-
if (typeof value !== 'string') return
|
|
13
|
-
|
|
14
|
-
const match = value.match(SECRET_RX)
|
|
15
|
-
|
|
16
|
-
if (match === null) return
|
|
17
|
-
|
|
18
|
-
const name = match.groups.variable
|
|
19
|
-
const variable = PREFIX + name
|
|
20
|
-
|
|
21
|
-
return callback(variable, name)
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const PREFIX = 'TOA_CONFIGURATION__'
|
|
26
|
-
const SECRET_RX = /^\$(?<variable>[A-Z0-9_]{1,32})$/
|
|
27
|
-
|
|
28
|
-
exports.secrets = secrets
|
package/source/secrets.test.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { secrets } = require('./secrets')
|
|
4
|
-
|
|
5
|
-
it('should be', async () => {
|
|
6
|
-
expect(secrets).toBeInstanceOf(Function)
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
it('should find secrets', async () => {
|
|
10
|
-
const configuration = {
|
|
11
|
-
foo: {
|
|
12
|
-
bar: '$BAR_VALUE'
|
|
13
|
-
},
|
|
14
|
-
baz: '$BAZ_VALUE'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const variables = new Set()
|
|
18
|
-
const names = new Set()
|
|
19
|
-
|
|
20
|
-
secrets(configuration, (variable, name) => {
|
|
21
|
-
variables.add(variable)
|
|
22
|
-
names.add(name)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
expect(variables.has('TOA_CONFIGURATION__BAR_VALUE')).toStrictEqual(true)
|
|
26
|
-
expect(variables.has('TOA_CONFIGURATION__BAZ_VALUE')).toStrictEqual(true)
|
|
27
|
-
|
|
28
|
-
expect(names.has('BAR_VALUE')).toStrictEqual(true)
|
|
29
|
-
expect(names.has('BAZ_VALUE')).toStrictEqual(true)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('should replace values', async () => {
|
|
33
|
-
const configuration = { foo: '$FOO' }
|
|
34
|
-
|
|
35
|
-
const output = secrets(configuration, (variable) => 'hello')
|
|
36
|
-
|
|
37
|
-
expect(output).toStrictEqual({ foo: 'hello' })
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('should allow numbers in secret names', async () => {
|
|
41
|
-
const configuration = { foo: '$HOST_0' }
|
|
42
|
-
const found = new Set()
|
|
43
|
-
|
|
44
|
-
secrets(configuration, (variable) => found.add(variable))
|
|
45
|
-
|
|
46
|
-
expect(found.has('TOA_CONFIGURATION__HOST_0')).toStrictEqual(true)
|
|
47
|
-
})
|
package/types/aspect.d.ts
DELETED
package/types/factory.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Component } from '@toa.io/norm/types'
|
|
2
|
-
|
|
3
|
-
import * as _extensions from '@toa.io/core/types/extensions'
|
|
4
|
-
import * as _provider from './provider'
|
|
5
|
-
|
|
6
|
-
declare namespace toa.extensions.configuration {
|
|
7
|
-
|
|
8
|
-
interface Factory extends _extensions.Factory {
|
|
9
|
-
provider(component: Component): _provider.Provider
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
}
|
package/types/provider.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Source } from '@toa.io/core/types/reflection'
|
|
2
|
-
import { Connector } from '@toa.io/core/types'
|
|
3
|
-
|
|
4
|
-
declare namespace toa.extensions.configuration {
|
|
5
|
-
|
|
6
|
-
interface Provider extends Connector {
|
|
7
|
-
source: Source
|
|
8
|
-
object: Object
|
|
9
|
-
key: string
|
|
10
|
-
|
|
11
|
-
set(key: string, value: any): void
|
|
12
|
-
|
|
13
|
-
unset(key: string): void
|
|
14
|
-
|
|
15
|
-
reset(): void
|
|
16
|
-
|
|
17
|
-
export(): string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type Provider = toa.extensions.configuration.Provider
|
|
File without changes
|