@toa.io/extensions.origins 0.7.1 → 0.8.0-dev.0
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 +9 -8
- package/readme.md +57 -0
- package/source/.deployment/index.js +5 -0
- package/source/.deployment/uris.js +35 -0
- package/source/.test/constants.js +3 -0
- package/source/.test/deployment.fixtures.js +20 -0
- package/source/.test/factory.fixtures.js +13 -0
- package/source/constants.js +3 -0
- package/source/deployment.js +28 -0
- package/source/deployment.test.js +159 -0
- package/source/env.js +50 -0
- package/source/factory.js +34 -0
- package/source/factory.test.js +122 -0
- package/{src → source}/index.js +2 -0
- package/source/manifest.js +23 -0
- package/source/manifest.test.js +51 -0
- package/source/protocols/amqp/.test/aspect.fixtures.js +15 -0
- package/source/protocols/amqp/.test/mock.comq.js +13 -0
- package/source/protocols/amqp/aspect.js +75 -0
- package/source/protocols/amqp/aspect.test.js +119 -0
- package/source/protocols/amqp/deployment.js +57 -0
- package/source/protocols/amqp/index.js +9 -0
- package/source/protocols/amqp/protocols.js +3 -0
- package/{test → source/protocols/http/.test}/aspect.fixtures.js +6 -6
- package/{src → source/protocols/http}/aspect.js +13 -20
- package/{test → source/protocols/http}/aspect.test.js +11 -11
- package/source/protocols/http/index.js +7 -0
- package/source/protocols/http/protocols.js +3 -0
- package/source/protocols/index.js +6 -0
- package/source/schemas/annotations.cos.yaml +1 -0
- package/source/schemas/index.js +8 -0
- package/source/schemas/manifest.cos.yaml +1 -0
- package/types/amqp.ts +9 -0
- package/types/deployment.d.ts +7 -0
- package/types/{aspect.ts → http.ts} +1 -2
- package/src/.manifest/index.js +0 -7
- package/src/.manifest/normalize.js +0 -17
- package/src/.manifest/schema.yaml +0 -13
- package/src/.manifest/validate.js +0 -13
- package/src/factory.js +0 -14
- package/src/manifest.js +0 -13
- package/test/factory.fixtures.js +0 -5
- package/test/factory.test.js +0 -22
- package/test/manifest.fixtures.js +0 -11
- package/test/manifest.test.js +0 -58
- package/types/declaration.d.ts +0 -11
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.origins",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Toa
|
|
3
|
+
"version": "0.8.0-dev.0",
|
|
4
|
+
"description": "Toa Origins",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
7
|
-
"main": "
|
|
7
|
+
"main": "source/index.js",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/toa-io/toa.git"
|
|
@@ -19,14 +19,15 @@
|
|
|
19
19
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@toa.io/core": "0.
|
|
23
|
-
"@toa.io/generic": "0.
|
|
24
|
-
"@toa.io/
|
|
25
|
-
"@toa.io/yaml": "0.7.1",
|
|
22
|
+
"@toa.io/core": "0.8.0-dev.0",
|
|
23
|
+
"@toa.io/generic": "0.8.0-dev.0",
|
|
24
|
+
"@toa.io/schemas": "0.8.0-dev.0",
|
|
25
|
+
"@toa.io/yaml": "0.7.2-dev.1",
|
|
26
|
+
"comq": "0.6.0",
|
|
26
27
|
"node-fetch": "2.6.7"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node-fetch": "2.6.2"
|
|
30
31
|
},
|
|
31
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "5f1bfd167bb7919492d249f78bbcc6bd9fc645a4"
|
|
32
33
|
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Toa Origins
|
|
2
|
+
|
|
3
|
+
Origins extension enables external communications over supported protocols (`HTTP` and `AMQP`).
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
# manifest.toa.yaml
|
|
9
|
+
name: dummy
|
|
10
|
+
namespace: dummies
|
|
11
|
+
|
|
12
|
+
origins:
|
|
13
|
+
website: http://www.domain.com/docs/
|
|
14
|
+
messages: amqps://amqp.amazon.com
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// Node.js bridge
|
|
19
|
+
async function transition (input, object, context) {
|
|
20
|
+
await context.http.example.get() // GET http://www.domain.com/docs/example
|
|
21
|
+
await context.amqp.emit('something_happened', { really: true })
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
# context.toa.yaml
|
|
27
|
+
origins:
|
|
28
|
+
dummies.dummy:
|
|
29
|
+
messages: amqps://amqp.azure.com
|
|
30
|
+
messages@staging: amqp://amqp.stage
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Declaration
|
|
34
|
+
|
|
35
|
+
Origins extension declaration is a [Pointer](/libraries/pointer). Declarations can be overridden by
|
|
36
|
+
the context annotations.
|
|
37
|
+
|
|
38
|
+
## HTTP
|
|
39
|
+
|
|
40
|
+
Uses [node-fetch](https://github.com/node-fetch/node-fetch) and returns its result.
|
|
41
|
+
|
|
42
|
+
## AMQP
|
|
43
|
+
|
|
44
|
+
Uses [ComQ](https://github.com/toa-io/comq), thus, provides interface of `comq.IO` restricted
|
|
45
|
+
to `emit` and `request` methods.
|
|
46
|
+
|
|
47
|
+
AMQP origins require credential secrets to be deployed. Secret's name must
|
|
48
|
+
follow `toa-origins-{namespace}-{component}-{origin}` and it must have keys `username`
|
|
49
|
+
and `password`.
|
|
50
|
+
|
|
51
|
+
### Example
|
|
52
|
+
|
|
53
|
+
```shell
|
|
54
|
+
# deploy credentials to the current kubectl context
|
|
55
|
+
$ toa conceal toa-origins-dummies-dummiy-messages username developer
|
|
56
|
+
$ toa conceal toa-origins-dummies-dummiy-messages password secret
|
|
57
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { PREFIX } = require('../constants')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {toa.norm.context.dependencies.Instance[]} instances
|
|
7
|
+
* @param {toa.origins.Annotations} annotations
|
|
8
|
+
* @returns {toa.deployment.dependency.Variables}
|
|
9
|
+
*/
|
|
10
|
+
function uris (instances, annotations) {
|
|
11
|
+
const variables = {}
|
|
12
|
+
|
|
13
|
+
for (const [id, annotation] of Object.entries(annotations)) {
|
|
14
|
+
const component = instances.find((instance) => instance.locator.id === id)
|
|
15
|
+
|
|
16
|
+
if (component === undefined) throw new Error(`Origins annotations error: component '${id}' is not found`)
|
|
17
|
+
|
|
18
|
+
for (const origin of Object.keys(annotation)) {
|
|
19
|
+
if (!(origin in component.manifest)) {
|
|
20
|
+
throw new Error(`Origins annotations error: component '${id}' doesn't have '${origin}' origin`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const name = PREFIX + component.locator.uppercase
|
|
25
|
+
const json = JSON.stringify(annotation)
|
|
26
|
+
const value = btoa(json)
|
|
27
|
+
const variable = { name, value }
|
|
28
|
+
|
|
29
|
+
variables[component.locator.label] = [variable]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return variables
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
exports.uris = uris
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { Locator } = require('@toa.io/core')
|
|
5
|
+
const { random, sample } = require('@toa.io/generic')
|
|
6
|
+
|
|
7
|
+
const { PROTOCOLS } = require('./constants')
|
|
8
|
+
|
|
9
|
+
const component = () => ({
|
|
10
|
+
locator: new Locator(generate(), generate()),
|
|
11
|
+
manifest: { [generate()]: sample(PROTOCOLS) + '//' + generate() }
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const components = () => {
|
|
15
|
+
const length = random(20) + 10
|
|
16
|
+
|
|
17
|
+
return Array.from({ length }, component)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
exports.components = components
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
|
|
5
|
+
/** @type {Record<string, string>} */
|
|
6
|
+
const manifest = {
|
|
7
|
+
[generate()]: 'https://toa.io',
|
|
8
|
+
[generate()]: 'https://api.domain.com',
|
|
9
|
+
[generate()]: 'amqp://localhost',
|
|
10
|
+
[generate()]: 'amqps://localhost'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.manifest = manifest
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { merge } = require('@toa.io/generic')
|
|
4
|
+
const schemas = require('./schemas')
|
|
5
|
+
const protocols = require('./protocols')
|
|
6
|
+
const create = require('./.deployment')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {toa.norm.context.dependencies.Instance[]} instances
|
|
10
|
+
* @param {toa.origins.Annotations} annotations
|
|
11
|
+
* @returns {toa.deployment.dependency.Declaration}
|
|
12
|
+
*/
|
|
13
|
+
function deployment (instances, annotations = {}) {
|
|
14
|
+
schemas.annotations.validate(annotations)
|
|
15
|
+
|
|
16
|
+
const uris = create.uris(instances, annotations)
|
|
17
|
+
const variables = { ...uris }
|
|
18
|
+
|
|
19
|
+
protocols.reduce((variables, provider) => {
|
|
20
|
+
const specifics = provider.deployment?.(instances)
|
|
21
|
+
|
|
22
|
+
return merge(variables, specifics)
|
|
23
|
+
}, variables)
|
|
24
|
+
|
|
25
|
+
return { variables }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.deployment = deployment
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { sample, letters: { up } } = require('@toa.io/generic')
|
|
5
|
+
|
|
6
|
+
const fixtures = require('./.test/deployment.fixtures')
|
|
7
|
+
const { deployment } = require('../')
|
|
8
|
+
|
|
9
|
+
it('should be', async () => {
|
|
10
|
+
expect(deployment).toBeInstanceOf(Function)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
/** @type {toa.norm.context.dependencies.Instance[]} */
|
|
14
|
+
let components
|
|
15
|
+
|
|
16
|
+
/** @type {string} */
|
|
17
|
+
let origin
|
|
18
|
+
|
|
19
|
+
/** @type {toa.norm.context.dependencies.Instance} */
|
|
20
|
+
let component
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
components = fixtures.components()
|
|
24
|
+
|
|
25
|
+
component = sample(components)
|
|
26
|
+
|
|
27
|
+
const origins = Object.keys(component.manifest)
|
|
28
|
+
|
|
29
|
+
origin = sample(origins)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('validation', () => {
|
|
33
|
+
it('should throw on annotation component mismatch', async () => {
|
|
34
|
+
const id = generate()
|
|
35
|
+
|
|
36
|
+
const annotations = {
|
|
37
|
+
[id]: {
|
|
38
|
+
[origin]: 'dev://' + generate()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
expect(() => deployment(components, annotations))
|
|
43
|
+
.toThrow(`Origins annotations error: component '${id}' is not found`)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should throw on annotation origin mismatch', async () => {
|
|
47
|
+
const id = component.locator.id
|
|
48
|
+
const origin = generate()
|
|
49
|
+
|
|
50
|
+
const annotations = {
|
|
51
|
+
[id]: {
|
|
52
|
+
[origin]: 'dev://' + generate()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
expect(() => deployment(components, annotations))
|
|
57
|
+
.toThrow(`Origins annotations error: component '${id}' doesn't have '${origin}' origin`)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should throw if annotation is not valid', async () => {
|
|
61
|
+
const annotations = /** @type {toa.origins.Annotations} */ { [component.locator.id]: generate() }
|
|
62
|
+
|
|
63
|
+
expect(() => deployment(components, annotations)).toThrow('must be object')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should throw if annotation is URI is not valid', async () => {
|
|
67
|
+
const annotations = {
|
|
68
|
+
[component.locator.id]: {
|
|
69
|
+
[origin]: 'hello!'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
expect(() => deployment(components, annotations)).toThrow('must match format')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should create variables', () => {
|
|
78
|
+
const value = 'dev://' + generate()
|
|
79
|
+
|
|
80
|
+
/** @type {toa.origins.Annotations} */
|
|
81
|
+
const annotations = {
|
|
82
|
+
[component.locator.id]: {
|
|
83
|
+
[origin]: value
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const output = deployment(components, annotations)
|
|
88
|
+
|
|
89
|
+
expect(output.variables).not.toBeUndefined()
|
|
90
|
+
|
|
91
|
+
const variables = output.variables[component.locator.label]
|
|
92
|
+
const varName = 'TOA_ORIGINS_' + component.locator.uppercase
|
|
93
|
+
const variable = findVariable(variables, varName)
|
|
94
|
+
|
|
95
|
+
expect(variable).toBeDefined()
|
|
96
|
+
|
|
97
|
+
const json = JSON.stringify(annotations[component.locator.id])
|
|
98
|
+
const base64 = btoa(json)
|
|
99
|
+
|
|
100
|
+
expect(variable.value).toStrictEqual(base64)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('amqp', () => {
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
const amqpComponents = components.filter(
|
|
106
|
+
(component) => {
|
|
107
|
+
const origin = Object.keys(component.manifest)
|
|
108
|
+
const url = new URL(component.manifest[origin])
|
|
109
|
+
|
|
110
|
+
return url.protocol === 'amqp:' || url.protocol === 'amqps:'
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
component = sample(amqpComponents)
|
|
115
|
+
|
|
116
|
+
const origins = Object.keys(component.manifest)
|
|
117
|
+
|
|
118
|
+
origin = sample(origins)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should create credential secrets', () => {
|
|
122
|
+
/** @type {toa.origins.Annotations} */
|
|
123
|
+
const annotations = {
|
|
124
|
+
[component.locator.id]: {
|
|
125
|
+
[origin]: 'amqps://whatever'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const output = deployment(components, annotations)
|
|
130
|
+
const variables = output.variables[component.locator.label]
|
|
131
|
+
|
|
132
|
+
expect(variables).toBeDefined()
|
|
133
|
+
|
|
134
|
+
const envPrefix = `TOA_ORIGINS_${component.locator.uppercase}_`
|
|
135
|
+
const secPrefix = `toa-origins-${component.locator.label}-`
|
|
136
|
+
|
|
137
|
+
for (const property of ['username', 'password']) {
|
|
138
|
+
const variableName = envPrefix + up(property)
|
|
139
|
+
const secretName = secPrefix + property
|
|
140
|
+
const variable = findVariable(variables, variableName)
|
|
141
|
+
|
|
142
|
+
expect(variable).toBeDefined()
|
|
143
|
+
|
|
144
|
+
expect(variable.secret).toStrictEqual({
|
|
145
|
+
name: secretName,
|
|
146
|
+
key: property
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {toa.deployment.dependency.Variable[]} variables
|
|
154
|
+
* @param {string} name
|
|
155
|
+
* @returns {toa.deployment.dependency.Variable}
|
|
156
|
+
*/
|
|
157
|
+
function findVariable (variables, name) {
|
|
158
|
+
return variables.find((variable) => variable.name === name)
|
|
159
|
+
}
|
package/source/env.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { PREFIX } = require('./constants')
|
|
4
|
+
const { overwrite, remap, letters: { up } } = require('@toa.io/generic')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {toa.core.Locator} locator
|
|
8
|
+
* @param {toa.origins.Manifest} manifest
|
|
9
|
+
*/
|
|
10
|
+
function apply (locator, manifest) {
|
|
11
|
+
const variable = PREFIX + locator.uppercase
|
|
12
|
+
const envValue = readEnv(variable)
|
|
13
|
+
|
|
14
|
+
overwrite(manifest, envValue)
|
|
15
|
+
addCredentials(manifest, variable)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readEnv (variable) {
|
|
19
|
+
if (!(variable in process.env)) return
|
|
20
|
+
|
|
21
|
+
const base64 = process.env[variable]
|
|
22
|
+
const json = atob(base64)
|
|
23
|
+
|
|
24
|
+
return JSON.parse(json)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {toa.origins.Manifest} manifest
|
|
29
|
+
* @param {string} variable
|
|
30
|
+
*/
|
|
31
|
+
function addCredentials (manifest, variable) {
|
|
32
|
+
const prefix = variable + '_'
|
|
33
|
+
|
|
34
|
+
remap(manifest, (reference, origin) => {
|
|
35
|
+
const originPrefix = prefix + up(origin)
|
|
36
|
+
const username = process.env[originPrefix + '_USERNAME']
|
|
37
|
+
const password = process.env[originPrefix + '_PASSWORD']
|
|
38
|
+
|
|
39
|
+
if (username === undefined && password === undefined) return
|
|
40
|
+
|
|
41
|
+
const url = new URL(reference)
|
|
42
|
+
|
|
43
|
+
url.username = username
|
|
44
|
+
url.password = password
|
|
45
|
+
|
|
46
|
+
manifest[origin] = url.href
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
exports.apply = apply
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const protocols = require('./protocols')
|
|
4
|
+
const env = require('./env')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @implements {toa.core.extensions.Factory}
|
|
8
|
+
*/
|
|
9
|
+
class Factory {
|
|
10
|
+
aspect (locator, manifest) {
|
|
11
|
+
env.apply(locator, /** @type {toa.origins.Manifest} */ manifest)
|
|
12
|
+
|
|
13
|
+
return protocols.map((protocol) => this.#createAspect(protocol, manifest))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} protocol
|
|
18
|
+
* @param {toa.origins.Manifest} manifest
|
|
19
|
+
* @return {toa.core.extensions.Aspect}
|
|
20
|
+
*/
|
|
21
|
+
#createAspect (protocol, manifest) {
|
|
22
|
+
const protocolManifest = {}
|
|
23
|
+
|
|
24
|
+
for (const [origin, reference] of Object.entries(manifest)) {
|
|
25
|
+
const url = new URL(reference)
|
|
26
|
+
|
|
27
|
+
if (protocol.protocols.includes(url.protocol)) protocolManifest[origin] = reference
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return protocol.create(protocolManifest)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
exports.Factory = Factory
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { Locator } = require('@toa.io/core')
|
|
5
|
+
const { sample, overwrite, letters: { up } } = require('@toa.io/generic')
|
|
6
|
+
|
|
7
|
+
jest.mock('./protocols/http/aspect')
|
|
8
|
+
jest.mock('./protocols/amqp/aspect')
|
|
9
|
+
|
|
10
|
+
const http = require('./protocols/http/aspect')
|
|
11
|
+
const amqp = require('./protocols/amqp/aspect')
|
|
12
|
+
|
|
13
|
+
const fixtures = require('./.test/factory.fixtures')
|
|
14
|
+
const { Factory } = require('../')
|
|
15
|
+
|
|
16
|
+
/** @type {toa.core.extensions.Factory} */
|
|
17
|
+
let factory
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks()
|
|
21
|
+
|
|
22
|
+
factory = new Factory()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should create aspects', () => {
|
|
26
|
+
factory.aspect(new Locator(generate(), generate()), fixtures.manifest)
|
|
27
|
+
|
|
28
|
+
const httpManifest = filterManifest(fixtures.manifest, 'http')
|
|
29
|
+
const amqpManifest = filterManifest(fixtures.manifest, 'amqp')
|
|
30
|
+
|
|
31
|
+
expect(http.create).toHaveBeenCalledWith(httpManifest)
|
|
32
|
+
expect(amqp.create).toHaveBeenCalledWith(amqpManifest)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('env', () => {
|
|
36
|
+
it('should overwrites URLs from environment', async () => {
|
|
37
|
+
const httpManifest = filterManifest(fixtures.manifest, 'http')
|
|
38
|
+
const key = sample(Object.keys(httpManifest))
|
|
39
|
+
const override = { [key]: 'http://' + generate() }
|
|
40
|
+
const json = JSON.stringify(override)
|
|
41
|
+
const base64 = btoa(json)
|
|
42
|
+
const locator = new Locator(generate(), generate())
|
|
43
|
+
|
|
44
|
+
process.env['TOA_ORIGINS_' + locator.uppercase] = base64
|
|
45
|
+
|
|
46
|
+
factory.aspect(locator, fixtures.manifest)
|
|
47
|
+
|
|
48
|
+
const expected = overwrite(httpManifest, override)
|
|
49
|
+
|
|
50
|
+
expect(http.create).toHaveBeenCalledWith(expected)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('amqp', () => {
|
|
54
|
+
/** @type {toa.origins.Manifest} */
|
|
55
|
+
let amqpManifest
|
|
56
|
+
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
amqpManifest = filterManifest(fixtures.manifest, 'amqp')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should add credentials from environment', async () => {
|
|
62
|
+
const key = sample(Object.keys(amqpManifest))
|
|
63
|
+
const locator = new Locator(generate(), generate())
|
|
64
|
+
const envPrefix = 'TOA_ORIGINS_' + locator.uppercase + '_' + up(key) + '_'
|
|
65
|
+
const username = generate()
|
|
66
|
+
const password = generate()
|
|
67
|
+
|
|
68
|
+
process.env[envPrefix + 'USERNAME'] = username
|
|
69
|
+
process.env[envPrefix + 'PASSWORD'] = password
|
|
70
|
+
|
|
71
|
+
factory.aspect(locator, amqpManifest)
|
|
72
|
+
|
|
73
|
+
const manifest = amqp.create.mock.calls[0][0]
|
|
74
|
+
const origin = manifest[key]
|
|
75
|
+
const url = new URL(origin)
|
|
76
|
+
|
|
77
|
+
expect(url.username).toStrictEqual(username)
|
|
78
|
+
expect(url.password).toStrictEqual(password)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should add credentials to URLs from environment', async () => {
|
|
82
|
+
const key = sample(Object.keys(amqpManifest))
|
|
83
|
+
const hostname = generate().toLowerCase()
|
|
84
|
+
const override = { [key]: 'amqp://' + hostname }
|
|
85
|
+
const json = JSON.stringify(override)
|
|
86
|
+
const base64 = btoa(json)
|
|
87
|
+
const locator = new Locator(generate(), generate())
|
|
88
|
+
const envPrefix = 'TOA_ORIGINS_' + locator.uppercase + '_' + up(key) + '_'
|
|
89
|
+
const username = generate()
|
|
90
|
+
const password = generate()
|
|
91
|
+
|
|
92
|
+
process.env['TOA_ORIGINS_' + locator.uppercase] = base64
|
|
93
|
+
process.env[envPrefix + 'USERNAME'] = username
|
|
94
|
+
process.env[envPrefix + 'PASSWORD'] = password
|
|
95
|
+
|
|
96
|
+
factory.aspect(locator, fixtures.manifest)
|
|
97
|
+
|
|
98
|
+
const manifest = amqp.create.mock.calls[0][0]
|
|
99
|
+
const origin = manifest[key]
|
|
100
|
+
const url = new URL(origin)
|
|
101
|
+
|
|
102
|
+
expect(url.hostname).toStrictEqual(hostname)
|
|
103
|
+
expect(url.username).toStrictEqual(username)
|
|
104
|
+
expect(url.password).toStrictEqual(password)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {toa.origins.Manifest} manifest
|
|
111
|
+
* @param {string} protocol
|
|
112
|
+
* @return {toa.origins.Manifest}
|
|
113
|
+
*/
|
|
114
|
+
function filterManifest (manifest, protocol) {
|
|
115
|
+
const result = {}
|
|
116
|
+
|
|
117
|
+
for (const [origin, reference] of Object.entries(manifest)) {
|
|
118
|
+
if (reference.slice(0, protocol.length) === protocol) result[origin] = reference
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result
|
|
122
|
+
}
|
package/{src → source}/index.js
RENAMED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const schemas = require('./schemas')
|
|
4
|
+
const protocols = require('./protocols')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {toa.origins.Manifest} manifest
|
|
8
|
+
* @returns {toa.origins.Manifest}
|
|
9
|
+
*/
|
|
10
|
+
function manifest (manifest) {
|
|
11
|
+
schemas.manifest.validate(manifest)
|
|
12
|
+
|
|
13
|
+
for (const uri of Object.values(manifest)) {
|
|
14
|
+
const protocol = new URL(uri).protocol
|
|
15
|
+
const supported = protocols.find((provider) => provider.protocols.includes(protocol))
|
|
16
|
+
|
|
17
|
+
if (supported === undefined) throw new Error(`'${protocol}' protocol is not supported`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return manifest
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
exports.manifest = manifest
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { PROTOCOLS } = require('./.test/constants')
|
|
5
|
+
|
|
6
|
+
const { manifest } = require('../')
|
|
7
|
+
|
|
8
|
+
it('should be', async () => {
|
|
9
|
+
expect(manifest).toBeInstanceOf(Function)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should return manifest', async () => {
|
|
13
|
+
const input = { [generate()]: 'http://' + generate() }
|
|
14
|
+
const output = manifest(input)
|
|
15
|
+
|
|
16
|
+
expect(output).toStrictEqual(input)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should fail if not Record<string, string>', async () => {
|
|
20
|
+
const input = /** @type {toa.origins.Manifest} */ {
|
|
21
|
+
foo: {
|
|
22
|
+
bar: 'dev://null'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
expect(() => manifest(input)).toThrow()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should pass if valid', async () => {
|
|
30
|
+
const input = { foo: 'amqp://' + generate() }
|
|
31
|
+
|
|
32
|
+
expect(() => manifest(input)).not.toThrow()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should fail if not uri', async () => {
|
|
36
|
+
const input = { [generate()]: generate() }
|
|
37
|
+
|
|
38
|
+
expect(() => manifest(input)).toThrow('must match format')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should throw if protocol is not supported', async () => {
|
|
42
|
+
const input = { foo: 'wat://' + generate() }
|
|
43
|
+
|
|
44
|
+
expect(() => manifest(input)).toThrow('is not supported')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it.each(PROTOCOLS)('should support %s protocol', async (protocol) => {
|
|
48
|
+
const input = { foo: protocol + '//' + generate() }
|
|
49
|
+
|
|
50
|
+
expect(() => manifest(input)).not.toThrow()
|
|
51
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { random } = require('@toa.io/generic')
|
|
5
|
+
|
|
6
|
+
const manifest = {}
|
|
7
|
+
const originCount = random(5) + 2
|
|
8
|
+
|
|
9
|
+
for (let j = 0; j < originCount; j++) {
|
|
10
|
+
const origin = generate()
|
|
11
|
+
|
|
12
|
+
manifest[origin] = 'amqp://' + generate()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.manifest = manifest
|