@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/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.configuration",
|
|
3
|
-
"version": "0.20.0-dev.
|
|
3
|
+
"version": "0.20.0-dev.35",
|
|
4
4
|
"description": "Toa Configuration",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
7
|
-
"main": "
|
|
7
|
+
"main": "transpiled/index.js",
|
|
8
|
+
"types": "transpiled/index.d.ts",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
10
11
|
"url": "git+https://github.com/toa-io/toa.git"
|
|
@@ -15,12 +16,18 @@
|
|
|
15
16
|
"publishConfig": {
|
|
16
17
|
"access": "public"
|
|
17
18
|
},
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
19
|
+
"scripts": {
|
|
20
|
+
"prepublishOnly": "npm run transpile",
|
|
21
|
+
"transpile": "tsc"
|
|
22
|
+
},
|
|
23
|
+
"jest": {
|
|
24
|
+
"preset": "ts-jest",
|
|
25
|
+
"testEnvironment": "node"
|
|
24
26
|
},
|
|
25
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "04d3317272256b98003a414ee1eb2555b65163a5",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@toa.io/core": "0.20.0-dev.35",
|
|
30
|
+
"@toa.io/generic": "0.20.0-dev.35",
|
|
31
|
+
"@toa.io/schemas": "0.20.0-dev.35"
|
|
32
|
+
}
|
|
26
33
|
}
|
package/readme.md
CHANGED
|
@@ -49,9 +49,9 @@ $ toa conceal configuration BAZ_VALUE=$ecr3t
|
|
|
49
49
|
|
|
50
50
|
## Problem Definition
|
|
51
51
|
|
|
52
|
-
- Components
|
|
53
|
-
|
|
54
|
-
-
|
|
52
|
+
- Components should be runnable in different deployment environments.
|
|
53
|
+
- Some algorithm's parameters should be deployed secretly.
|
|
54
|
+
- Components should be reusable in different contexts.
|
|
55
55
|
|
|
56
56
|
## Manifest
|
|
57
57
|
|
|
@@ -77,6 +77,18 @@ configuration:
|
|
|
77
77
|
> compatibility with existing contexts and deployment environments.
|
|
78
78
|
> Therefore, configuration schema changes are subject to component versioning.
|
|
79
79
|
|
|
80
|
+
If `configuration` object doesn't contain property `schema`, then it is considered to be schema.
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
# manifest.toa.yaml
|
|
84
|
+
name: dummy
|
|
85
|
+
namespace: dummies
|
|
86
|
+
|
|
87
|
+
configuration:
|
|
88
|
+
foo: string
|
|
89
|
+
bar: number
|
|
90
|
+
```
|
|
91
|
+
|
|
80
92
|
### Defaults
|
|
81
93
|
|
|
82
94
|
The default configuration value can be provided using the `defaults` property, which should conform
|
|
@@ -126,7 +138,7 @@ configuration:
|
|
|
126
138
|
|
|
127
139
|
## Secrets
|
|
128
140
|
|
|
129
|
-
Configuration annotation values which are uppercase strings prefixed with `$` considered as secrets.
|
|
141
|
+
Configuration annotation top-level values which are uppercase strings prefixed with `$` considered as secrets.
|
|
130
142
|
|
|
131
143
|
```yaml
|
|
132
144
|
# context.toa.yaml
|
|
@@ -136,8 +148,8 @@ configuration:
|
|
|
136
148
|
```
|
|
137
149
|
|
|
138
150
|
Secrets are not being deployed with context
|
|
139
|
-
deployment ([`toa deploy`](/runtime/cli/readme.md#deploy)),
|
|
140
|
-
|
|
151
|
+
deployment ([`toa deploy`](/runtime/cli/readme.md#deploy)), thus must be deployed separately at
|
|
152
|
+
least once for each deployment environment
|
|
141
153
|
manually ([`toa conceal`](/runtime/cli/readme.md#conceal)).
|
|
142
154
|
|
|
143
155
|
Deployed kubernetes secret's name is predefined as `configuration`.
|
|
@@ -148,7 +160,7 @@ $ toa conceal configuration STRIPE_API_KEY=xxxxxxxx
|
|
|
148
160
|
|
|
149
161
|
## Aspect
|
|
150
162
|
|
|
151
|
-
Component's configuration
|
|
163
|
+
Component's configuration values are available as a well-known Aspect `configuration`.
|
|
152
164
|
|
|
153
165
|
```javascript
|
|
154
166
|
function transition (input, entity, context) {
|
|
@@ -157,23 +169,3 @@ function transition (input, entity, context) {
|
|
|
157
169
|
// ...
|
|
158
170
|
}
|
|
159
171
|
```
|
|
160
|
-
|
|
161
|
-
### Local environment placeholders
|
|
162
|
-
|
|
163
|
-
Configuration annotation values may contain placeholders that reference environment variables.
|
|
164
|
-
Placeholders are replaced with values if the corresponding environment variables are set.
|
|
165
|
-
|
|
166
|
-
> Placeholders can only be used with local environment (exported
|
|
167
|
-
> by [`toa env`](/runtime/cli/readme.md#env)), as these values are not deployed.
|
|
168
|
-
|
|
169
|
-
```yaml
|
|
170
|
-
# context.toa.yaml
|
|
171
|
-
configuration:
|
|
172
|
-
dummies.dummy:
|
|
173
|
-
url@local: https://stage${STAGE}.intranet/
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
```dotenv
|
|
177
|
-
# .env
|
|
178
|
-
STAGE=82
|
|
179
|
-
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<object>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Aspect } from './Aspect'
|
|
2
|
+
|
|
3
|
+
it('should return value', async () => {
|
|
4
|
+
const configuration = {
|
|
5
|
+
foo: 'bar',
|
|
6
|
+
bar: {
|
|
7
|
+
baz: 'quux'
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const aspect = new Aspect(configuration)
|
|
12
|
+
|
|
13
|
+
expect(aspect.invoke(['foo'])).toStrictEqual(configuration.foo)
|
|
14
|
+
expect(aspect.invoke(['bar', 'baz'])).toStrictEqual(configuration.bar.baz)
|
|
15
|
+
})
|
package/source/Aspect.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Connector, type extensions } from '@toa.io/core'
|
|
2
|
+
|
|
3
|
+
export class Aspect extends Connector implements extensions.Aspect {
|
|
4
|
+
public readonly name = 'configuration'
|
|
5
|
+
|
|
6
|
+
private readonly value: object
|
|
7
|
+
|
|
8
|
+
public constructor (value: object) {
|
|
9
|
+
super()
|
|
10
|
+
|
|
11
|
+
this.value = value
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public invoke (path: string[]): any {
|
|
15
|
+
let cursor: any = this.value
|
|
16
|
+
|
|
17
|
+
if (path !== undefined)
|
|
18
|
+
for (const segment of path)
|
|
19
|
+
cursor = cursor[segment]
|
|
20
|
+
|
|
21
|
+
return cursor
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Locator, type extensions } from '@toa.io/core'
|
|
2
|
+
import { Aspect } from './Aspect'
|
|
3
|
+
import { type Manifest } from './manifest'
|
|
4
|
+
import { get } from './configuration'
|
|
5
|
+
|
|
6
|
+
export class Factory implements extensions.Factory {
|
|
7
|
+
public aspect (locator: Locator, manifest: Manifest): extensions.Aspect {
|
|
8
|
+
const value = get(locator, manifest)
|
|
9
|
+
|
|
10
|
+
return new Aspect(value)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { encode } from '@toa.io/generic'
|
|
2
|
+
import { Locator } from '@toa.io/core'
|
|
3
|
+
import { generate } from 'randomstring'
|
|
4
|
+
import { get } from './configuration'
|
|
5
|
+
import { type Manifest } from './manifest'
|
|
6
|
+
|
|
7
|
+
let locator: Locator
|
|
8
|
+
let manifest: Manifest
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
locator = new Locator(generate(), generate())
|
|
12
|
+
manifest = { schema: { foo: 'string' } }
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
for (const name of used)
|
|
17
|
+
process.env[name] = undefined
|
|
18
|
+
|
|
19
|
+
used = []
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should read value', async () => {
|
|
23
|
+
manifest.schema = { foo: 'string' }
|
|
24
|
+
const value: object = { foo: generate() }
|
|
25
|
+
|
|
26
|
+
set(value)
|
|
27
|
+
|
|
28
|
+
const result = get(locator, manifest)
|
|
29
|
+
|
|
30
|
+
expect(result).toStrictEqual(value)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should return empty object if no value set', async () => {
|
|
34
|
+
expect(get(locator, manifest)).toStrictEqual({})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should substitute secrets', async () => {
|
|
38
|
+
const value: object = { foo: '$BAR' }
|
|
39
|
+
|
|
40
|
+
set(value)
|
|
41
|
+
set('bar', '_BAR')
|
|
42
|
+
|
|
43
|
+
const result = get(locator, manifest)
|
|
44
|
+
|
|
45
|
+
expect(result).toStrictEqual({ foo: 'bar' })
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should use defaults', async () => {
|
|
49
|
+
manifest.schema = { foo: 'string', bar: ['number'], 'baz?': 'string' }
|
|
50
|
+
manifest.defaults = { foo: 'bar', bar: [1] }
|
|
51
|
+
|
|
52
|
+
const values = { bar: [2], baz: 'foo' }
|
|
53
|
+
|
|
54
|
+
set(values)
|
|
55
|
+
|
|
56
|
+
const result = get(locator, manifest)
|
|
57
|
+
|
|
58
|
+
expect(result).toStrictEqual({
|
|
59
|
+
foo: 'bar',
|
|
60
|
+
bar: [2],
|
|
61
|
+
baz: 'foo'
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should validate', async () => {
|
|
66
|
+
manifest.schema = { foo: 'hello', bar: 'number' }
|
|
67
|
+
|
|
68
|
+
const values = { bar: 5 }
|
|
69
|
+
|
|
70
|
+
set(values)
|
|
71
|
+
|
|
72
|
+
const result = get(locator, manifest)
|
|
73
|
+
|
|
74
|
+
expect(result).toStrictEqual({
|
|
75
|
+
foo: 'hello',
|
|
76
|
+
bar: 5
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
function set (value: object | string, key = locator.uppercase): void {
|
|
81
|
+
const string = typeof value === 'string' ? value : encode(value)
|
|
82
|
+
const name = 'TOA_CONFIGURATION_' + key
|
|
83
|
+
|
|
84
|
+
process.env[name] = string
|
|
85
|
+
|
|
86
|
+
used.push(name)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let used: string[] = []
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type Locator } from '@toa.io/core'
|
|
2
|
+
import { decode, add } from '@toa.io/generic'
|
|
3
|
+
import * as schemas from '@toa.io/schemas'
|
|
4
|
+
import { PREFIX, SECRET_RX } from './deployment'
|
|
5
|
+
import { type Manifest } from './manifest'
|
|
6
|
+
|
|
7
|
+
export function get (locator: Locator, manifest: Manifest): Configuration {
|
|
8
|
+
const values = getConfiguration(locator.uppercase)
|
|
9
|
+
|
|
10
|
+
substituteSecrets(values)
|
|
11
|
+
|
|
12
|
+
if (manifest.defaults !== undefined) add(values, manifest.defaults)
|
|
13
|
+
|
|
14
|
+
const schema = schemas.schema(manifest.schema)
|
|
15
|
+
|
|
16
|
+
schema.validate(values)
|
|
17
|
+
|
|
18
|
+
return values
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getConfiguration (suffix: string): Configuration {
|
|
22
|
+
const variable = PREFIX + suffix
|
|
23
|
+
const string = process.env[variable]
|
|
24
|
+
|
|
25
|
+
if (string === undefined) return {}
|
|
26
|
+
else return decode(string)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function substituteSecrets (configuration: Configuration): void {
|
|
30
|
+
for (const [key, value] of Object.entries(configuration)) {
|
|
31
|
+
if (typeof value !== 'string') continue
|
|
32
|
+
|
|
33
|
+
const match = value.match(SECRET_RX)
|
|
34
|
+
|
|
35
|
+
if (match === null) continue
|
|
36
|
+
|
|
37
|
+
const name = match.groups?.variable as string
|
|
38
|
+
|
|
39
|
+
configuration[key] = getSecret(name)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getSecret (name: string): string {
|
|
44
|
+
const variable = PREFIX + '_' + name
|
|
45
|
+
const value = process.env[variable]
|
|
46
|
+
|
|
47
|
+
if (value === undefined) throw new Error(`${variable} is not set.`)
|
|
48
|
+
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type Configuration = Record<string, any>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Annotation, deployment, type Instance } from './deployment'
|
|
2
|
+
import { type Manifest } from './manifest'
|
|
3
|
+
|
|
4
|
+
it('should validate annotation', async () => {
|
|
5
|
+
const wrongType = 'not ok' as unknown as Annotation
|
|
6
|
+
|
|
7
|
+
expect(() => deployment([], wrongType)).toThrow('object')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('should validate values', async () => {
|
|
11
|
+
const manifest: Manifest = {
|
|
12
|
+
schema: { foo: 'string', bar: 'number' },
|
|
13
|
+
defaults: { foo: 'ok', bar: 0 }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const locator = { id: 'component' }
|
|
17
|
+
const instances = [{ manifest, locator }] as unknown as Instance[]
|
|
18
|
+
const annotation: Annotation = { [locator.id]: { bar: 'not a number' } }
|
|
19
|
+
|
|
20
|
+
expect(() => deployment(instances, annotation)).toThrow('number')
|
|
21
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Dependency, type Variable, type Variables } from '@toa.io/operations'
|
|
2
|
+
import * as schemas from '@toa.io/schemas'
|
|
3
|
+
import { encode, overwrite } from '@toa.io/generic'
|
|
4
|
+
import { type Manifest } from './manifest'
|
|
5
|
+
import * as validators from './schemas'
|
|
6
|
+
import type { context } from '@toa.io/norm'
|
|
7
|
+
|
|
8
|
+
export function deployment (instances: Instance[], annotation: Annotation): Dependency {
|
|
9
|
+
validators.annotation.validate(annotation)
|
|
10
|
+
|
|
11
|
+
const variables: Variables = {}
|
|
12
|
+
|
|
13
|
+
for (const instance of instances) {
|
|
14
|
+
const values = annotation[instance.locator.id]
|
|
15
|
+
|
|
16
|
+
if (values === undefined) continue
|
|
17
|
+
|
|
18
|
+
validate(instance, values)
|
|
19
|
+
|
|
20
|
+
variables[instance.locator.label] = [{
|
|
21
|
+
name: PREFIX + instance.locator.uppercase,
|
|
22
|
+
value: encode(values)
|
|
23
|
+
}]
|
|
24
|
+
|
|
25
|
+
const secrets = createSecrets(values)
|
|
26
|
+
|
|
27
|
+
variables[instance.locator.label].push(...secrets)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { variables }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createSecrets (values: object): Variable[] {
|
|
34
|
+
const secrets: Variable[] = []
|
|
35
|
+
|
|
36
|
+
for (const value of Object.values(values)) {
|
|
37
|
+
if (typeof value !== 'string') continue
|
|
38
|
+
|
|
39
|
+
const match = value.match(SECRET_RX)
|
|
40
|
+
|
|
41
|
+
if (match === null) continue
|
|
42
|
+
|
|
43
|
+
const name = match.groups?.variable as string
|
|
44
|
+
|
|
45
|
+
secrets.push({
|
|
46
|
+
name: PREFIX + '_' + name,
|
|
47
|
+
secret: {
|
|
48
|
+
name: 'toa-configuration',
|
|
49
|
+
key: name
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return secrets
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validate (instace: Instance, values: object): void {
|
|
58
|
+
const defaults = instace.manifest.defaults ?? {}
|
|
59
|
+
const configuration = overwrite(defaults, values)
|
|
60
|
+
const schema = schemas.schema(instace.manifest.schema)
|
|
61
|
+
|
|
62
|
+
schema.validate(configuration)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const SECRET_RX = /^\$(?<variable>[A-Z0-9_]{1,32})$/
|
|
66
|
+
export const PREFIX = 'TOA_CONFIGURATION_'
|
|
67
|
+
|
|
68
|
+
export type Annotation = Record<string, object>
|
|
69
|
+
export type Instance = context.Dependency<Manifest>
|
package/source/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Manifest, manifest } from './manifest'
|
|
2
|
+
|
|
3
|
+
it('should validate', async () => {
|
|
4
|
+
const additional = { schema: {}, foo: 'bar' } as unknown as Manifest
|
|
5
|
+
|
|
6
|
+
expect(() => {
|
|
7
|
+
manifest(additional)
|
|
8
|
+
}).toThrow('additional')
|
|
9
|
+
|
|
10
|
+
const wrongType = { schema: 'not ok' } as unknown as Manifest
|
|
11
|
+
|
|
12
|
+
expect(() => {
|
|
13
|
+
manifest(wrongType)
|
|
14
|
+
}).toThrow('object')
|
|
15
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as schemas from './schemas'
|
|
2
|
+
import { type Configuration } from './configuration'
|
|
3
|
+
|
|
4
|
+
export function manifest (manifest: Manifest): Manifest {
|
|
5
|
+
if (manifest.schema === undefined) manifest = { schema: manifest }
|
|
6
|
+
|
|
7
|
+
schemas.manifest.validate(manifest)
|
|
8
|
+
|
|
9
|
+
return manifest
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Manifest {
|
|
13
|
+
schema: object
|
|
14
|
+
defaults?: Configuration
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import * as schemas from '@toa.io/schemas'
|
|
3
|
+
|
|
4
|
+
const path = resolve(__dirname, '../schemas')
|
|
5
|
+
const namespace = schemas.namespace(path)
|
|
6
|
+
|
|
7
|
+
export const manifest = namespace.schema('manifest')
|
|
8
|
+
export const annotation = namespace.schema('annotation')
|
package/tsconfig.json
ADDED
package/docs/discussion.md
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# Discussion
|
|
2
|
-
|
|
3
|
-
## Change Requests
|
|
4
|
-
|
|
5
|
-
- [x] feat(configuration): add configuration extension
|
|
6
|
-
- manifest (schema) validation
|
|
7
|
-
- context extension
|
|
8
|
-
- [x] feat(formation): add well-known extension 'configuration'
|
|
9
|
-
- component
|
|
10
|
-
- context
|
|
11
|
-
- [x] feat(node): add well-known context extension 'configuration'
|
|
12
|
-
- [x] feat(configuration): add concise declarations
|
|
13
|
-
- [x] feat(configuration): add runtime configuration resolution
|
|
14
|
-
- [x] feat(cli): add `toa configure <key> <value> --reset`
|
|
15
|
-
- validate type
|
|
16
|
-
- [x] feat(operations): add configuration deployment
|
|
17
|
-
- annotations (values) validation
|
|
18
|
-
- [ ] feat(configuration): add secrets resolution
|
|
19
|
-
- [ ] feat(operations): add secrets deployment
|
|
20
|
-
- [ ] feat(cli): add `toa conceal`
|
|
21
|
-
- validate type
|
|
22
|
-
- [ ] feat(cli): add `toa configure`
|
|
23
|
-
- prompt required values
|
|
24
|
-
- use JSON Schema title
|
|
25
|
-
|
|
26
|
-
## Statements
|
|
27
|
-
|
|
28
|
-
### Common
|
|
29
|
-
|
|
30
|
-
- Secrets are being deployed separately by `toa conceal` command
|
|
31
|
-
|
|
32
|
-
### 1: Environment variables
|
|
33
|
-
|
|
34
|
-
- Configuration values and secrets are mapped as environment variables to composition deployments
|
|
35
|
-
- Extensions may expose *deployment mutators*, which are able to modify deployment declaration
|
|
36
|
-
- Configuration context extension reads environment variables to resolve configuration and secrets
|
|
37
|
-
|
|
38
|
-
### 2: Dedicated Components
|
|
39
|
-
|
|
40
|
-
- Hot updates
|
|
41
|
-
- [Configuration consistency](consistency.md)
|
|
42
|
-
|
|
43
|
-
## Questions
|
|
44
|
-
|
|
45
|
-
### Where are values comes from?
|
|
46
|
-
|
|
47
|
-
Environment variables.
|
|
48
|
-
|
|
49
|
-
### Is there a configuration service or configuration component?
|
|
50
|
-
|
|
51
|
-
No. It will be implemented later as a part of [consistent configuration](consistency.md).
|
|
52
|
-
|
|
53
|
-
### How are configuration values being stored?
|
|
54
|
-
|
|
55
|
-
As a kubernetes secrets mapped as environment variables.
|
|
56
|
-
|
|
57
|
-
### Where are secrets being stored and how do they resolve to value?
|
|
58
|
-
|
|
59
|
-
As a kubernetes secrets mapped as environment variables.
|
|
60
|
-
|
|
61
|
-
### Is configuration a single environment variable or a set (one per component)?
|
|
62
|
-
|
|
63
|
-
#### Context Configuration
|
|
64
|
-
|
|
65
|
-
In later versions, context extension will resolve configuration values by component locator. Given
|
|
66
|
-
that it is yet
|
|
67
|
-
unknown when this will happen, a certain context might have appeared which configuration is big
|
|
68
|
-
enough to not fit the
|
|
69
|
-
environment variable limitations.
|
|
70
|
-
|
|
71
|
-
That is, Context Configuration must be mapped as a set of environment variables (one per component).
|
|
72
|
-
Values are
|
|
73
|
-
serialized Configuration Objects.
|
|
74
|
-
|
|
75
|
-
> This will also allow to configure local environment per component.
|
|
76
|
-
|
|
77
|
-
#### Secrets
|
|
78
|
-
|
|
79
|
-
Secrets are mapped per secret as they are not bound to components.
|
|
80
|
-
|
|
81
|
-
### Is configuration a single kubernetes secret or a set (one per component)?
|
|
82
|
-
|
|
83
|
-
#### Configuration
|
|
84
|
-
|
|
85
|
-
Single secret with a set of values per component.
|
|
86
|
-
|
|
87
|
-
#### Secrets
|
|
88
|
-
|
|
89
|
-
Once kubernetes secret per configuration secret.
|
|
90
|
-
|
|
91
|
-
### Is there an option to configure local environment?
|
|
92
|
-
|
|
93
|
-
<dl>
|
|
94
|
-
<dt><code>toa configure <component></code></dt>
|
|
95
|
-
<dd>Create local environment configuration values</dd>
|
|
96
|
-
</dl>
|
|
97
|
-
|
|
98
|
-
### Whose responsibility is to call annotations?
|
|
99
|
-
|
|
100
|
-
- norm
|
|
101
|
-
- deployment
|
|
102
|
-
|
|
103
|
-
`toa export context` should throw errors if context has invalid annotations, and it's not a part of
|
|
104
|
-
the deployment.
|
|
105
|
-
|
|
106
|
-
## References
|
|
107
|
-
|
|
108
|
-
- [#125](https://github.com/toa-io/toa/issues/125)
|
|
109
|
-
- [#132](https://github.com/toa-io/toa/issues/132)
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const find = require('../secrets')
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {toa.norm.context.dependencies.Instance[]} components
|
|
7
|
-
* @param {object} annotations
|
|
8
|
-
* @return {toa.deployment.dependency.Variables}
|
|
9
|
-
*/
|
|
10
|
-
function secrets (components, annotations) {
|
|
11
|
-
if (annotations === undefined) return {}
|
|
12
|
-
|
|
13
|
-
/** @type {toa.deployment.dependency.Variables} */
|
|
14
|
-
const variables = {}
|
|
15
|
-
|
|
16
|
-
for (const [id, annotation] of Object.entries(annotations)) {
|
|
17
|
-
const component = components.find((component) => component.locator.id === id)
|
|
18
|
-
const label = component.locator.label
|
|
19
|
-
|
|
20
|
-
find.secrets(annotation, (variable, key) => {
|
|
21
|
-
if (variables[label] === undefined) variables[label] = []
|
|
22
|
-
|
|
23
|
-
variables[label].push({
|
|
24
|
-
name: variable,
|
|
25
|
-
secret: { name: SECRET_NAME, key }
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return variables
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const SECRET_NAME = 'toa-configuration'
|
|
34
|
-
|
|
35
|
-
exports.secrets = secrets
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { encode } = require('@toa.io/generic')
|
|
4
|
-
|
|
5
|
-
function variables (components, annotations) {
|
|
6
|
-
if (annotations === undefined) return {}
|
|
7
|
-
|
|
8
|
-
/** @type {toa.deployment.dependency.Variables} */
|
|
9
|
-
const variables = {}
|
|
10
|
-
|
|
11
|
-
for (const [id, annotation] of Object.entries(annotations)) {
|
|
12
|
-
const component = components.find((component) => component.locator.id === id)
|
|
13
|
-
|
|
14
|
-
variables[component.locator.label] = [{
|
|
15
|
-
name: 'TOA_CONFIGURATION_' + component.locator.uppercase,
|
|
16
|
-
value: encode(annotation)
|
|
17
|
-
}]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return variables
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
exports.variables = variables
|