@toa.io/operations 1.0.0-alpha.261 → 1.0.0-alpha.263
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/CHANGELOG.md +36 -0
- package/package.json +3 -3
- package/src/deployment/.deployment/.describe/resources.js +37 -0
- package/src/deployment/.deployment/.describe/services.js +23 -1
- package/src/deployment/.deployment/.describe/services.test.js +61 -0
- package/src/deployment/.deployment/describe.js +29 -6
- package/src/deployment/.deployment/merge.js +33 -0
- package/src/deployment/.deployment/merge.test.js +74 -0
- package/src/deployment/chart/templates/mono.yaml +17 -8
- package/src/deployment/chart/templates/services.yaml +6 -4
- package/src/deployment/factory.js +2 -4
- package/src/deployment/images/factory.js +2 -2
- package/src/deployment/images/mono.Dockerfile +2 -2
- package/src/deployment/images/mono.js +2 -9
- package/src/deployment/registry.js +2 -2
- package/types/dependency.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,42 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.0.0-alpha.263](https://github.com/toa-io/toa/compare/v1.0.0-alpha.262...v1.0.0-alpha.263) (2026-08-29)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @toa.io/operations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [1.0.0-alpha.262](https://github.com/toa-io/toa/compare/v1.0.0-alpha.261...v1.0.0-alpha.262) (2026-08-28)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **cli:** boot toa mono from components and env, not context ([1518f72](https://github.com/toa-io/toa/commit/1518f729d7611ae88e0538f286231d283a7098ef))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
* feat(operations)!: require service ports to be unique ([b22cf87](https://github.com/toa-io/toa/commit/b22cf8770d24c555b5f355a5f117a9043a57799b))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
* **operations:** let a service claim a path prefix on the host ([02677d1](https://github.com/toa-io/toa/commit/02677d17fcd4222b07c3be458d93bcc496a016a8))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### BREAKING CHANGES
|
|
31
|
+
|
|
32
|
+
* an application whose own extension runs a service on a port
|
|
33
|
+
already taken by another — 8000 by the exposition gateway, 8001 by the telemetry
|
|
34
|
+
readiness probe — must move it.
|
|
35
|
+
|
|
36
|
+
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
6
42
|
# [1.0.0-alpha.261](https://github.com/toa-io/toa/compare/v1.0.0-alpha.260...v1.0.0-alpha.261) (2026-08-26)
|
|
7
43
|
|
|
8
44
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/operations",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.263",
|
|
4
4
|
"description": "Toa Deployment",
|
|
5
5
|
"homepage": "https://toa.io",
|
|
6
6
|
"author": {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@toa.io/filesystem": "1.0.0-alpha.254",
|
|
31
31
|
"@toa.io/generic": "1.0.0-alpha.254",
|
|
32
|
-
"@toa.io/norm": "1.0.0-alpha.
|
|
32
|
+
"@toa.io/norm": "1.0.0-alpha.263",
|
|
33
33
|
"@toa.io/yaml": "1.0.0-alpha.254",
|
|
34
34
|
"execa": "5.1.1",
|
|
35
35
|
"fs-extra": "11.1.1"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "165daa284bb7a75907a593fde359e3553b083417"
|
|
38
38
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Every deployment states what it may take. One that states nothing is `BestEffort`: the
|
|
5
|
+
* first evicted under memory pressure and the last given CPU under contention — which on
|
|
6
|
+
* a busy node is slow enough for its own startup probe to kill it, in a loop, while the
|
|
7
|
+
* chart reports nothing wrong. That is not a thing to find out in production.
|
|
8
|
+
*
|
|
9
|
+
* A deployment takes its own declaration, or the context's `resources` where it has none.
|
|
10
|
+
* Deploying without any is a decision rather than an omission, and it is spelled
|
|
11
|
+
* `resources: null` — at either place.
|
|
12
|
+
*/
|
|
13
|
+
function resources (context, values) {
|
|
14
|
+
for (const unit of units(values)) {
|
|
15
|
+
// `null` is an answer and `undefined` is not one, so the fallback reads only the latter
|
|
16
|
+
if (unit.deployment.resources === undefined)
|
|
17
|
+
unit.deployment.resources = context.resources
|
|
18
|
+
|
|
19
|
+
if (unit.deployment.resources === undefined)
|
|
20
|
+
throw new Error(`${unit.subject} declares no resources. ` +
|
|
21
|
+
'Declare them on it or as the context\'s \'resources\', ' +
|
|
22
|
+
'or \'resources: null\' to deploy it without any.')
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function * units (values) {
|
|
27
|
+
if (values.mono !== undefined)
|
|
28
|
+
yield { deployment: values.mono, subject: 'The mono deployment' }
|
|
29
|
+
|
|
30
|
+
for (const composition of values.compositions ?? [])
|
|
31
|
+
yield { deployment: composition, subject: `Composition '${composition.name}'` }
|
|
32
|
+
|
|
33
|
+
for (const service of values.services ?? [])
|
|
34
|
+
yield { deployment: service, subject: `Service '${service.name}'` }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
exports.resources = resources
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { addVariables } = require('./variables')
|
|
4
4
|
|
|
5
|
-
function services (services, variables, probe) {
|
|
5
|
+
function services (services, variables, probe, ingress) {
|
|
6
6
|
for (const service of services) {
|
|
7
7
|
addVariables(service, variables)
|
|
8
8
|
|
|
@@ -10,7 +10,29 @@ function services (services, variables, probe) {
|
|
|
10
10
|
delete service.probe
|
|
11
11
|
else if (service.probe === undefined && probe !== undefined && probe !== false)
|
|
12
12
|
service.probe = probe
|
|
13
|
+
|
|
14
|
+
if (service.ingress !== undefined)
|
|
15
|
+
expose(service, ingress)
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
/**
|
|
20
|
+
* A service declares only its own intent — a path, a port. Where that lands is
|
|
21
|
+
* the context's business, so everything else comes from its `ingress` section.
|
|
22
|
+
* What the service did declare wins.
|
|
23
|
+
*/
|
|
24
|
+
function expose (service, ingress = {}) {
|
|
25
|
+
const declared = Object.fromEntries(
|
|
26
|
+
Object.entries(service.ingress).filter(([, value]) => value !== undefined))
|
|
27
|
+
|
|
28
|
+
service.ingress = Object.assign({}, ingress, declared)
|
|
29
|
+
|
|
30
|
+
if (service.ingress.hosts === undefined)
|
|
31
|
+
throw new Error(`Service '${service.name}' declares an ingress, but no hosts are defined. ` +
|
|
32
|
+
"Declare them in the context's 'ingress' section.")
|
|
33
|
+
|
|
34
|
+
if (service.port === undefined)
|
|
35
|
+
throw new Error(`Service '${service.name}' declares an ingress, but no port.`)
|
|
36
|
+
}
|
|
37
|
+
|
|
16
38
|
exports.services = services
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { services } = require('./services')
|
|
4
|
+
|
|
5
|
+
const service = (extra = {}) => ({ group: 'group', name: 'group-one', version: '0', ...extra })
|
|
6
|
+
|
|
7
|
+
it('should leave a service without an ingress alone', () => {
|
|
8
|
+
const list = [service()]
|
|
9
|
+
|
|
10
|
+
services(list, {}, undefined, { hosts: ['api.dev'] })
|
|
11
|
+
|
|
12
|
+
expect(list[0].ingress).toBeUndefined()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* A service declares only where it wants to land; the cluster-level plumbing
|
|
17
|
+
* belongs to the context.
|
|
18
|
+
*/
|
|
19
|
+
it('should supply hosts, class and annotations from the context', () => {
|
|
20
|
+
const list = [service({ port: 8002, ingress: { path: '/.introspection' } })]
|
|
21
|
+
|
|
22
|
+
services(list, {}, undefined, { hosts: ['api.dev'], class: 'alb', annotations: { a: 'b' } })
|
|
23
|
+
|
|
24
|
+
expect(list[0].ingress).toStrictEqual({
|
|
25
|
+
path: '/.introspection',
|
|
26
|
+
hosts: ['api.dev'],
|
|
27
|
+
class: 'alb',
|
|
28
|
+
annotations: { a: 'b' }
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should keep what the service declared itself', () => {
|
|
33
|
+
const list = [service({ port: 8000, ingress: { path: '/', hosts: ['own.dev'] } })]
|
|
34
|
+
|
|
35
|
+
services(list, {}, undefined, { hosts: ['api.dev'], class: 'alb' })
|
|
36
|
+
|
|
37
|
+
expect(list[0].ingress.hosts).toStrictEqual(['own.dev'])
|
|
38
|
+
expect(list[0].ingress.class).toStrictEqual('alb')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should ignore properties the service left undefined', () => {
|
|
42
|
+
const list = [service({ port: 8000, ingress: { path: '/', class: undefined } })]
|
|
43
|
+
|
|
44
|
+
services(list, {}, undefined, { hosts: ['api.dev'], class: 'alb' })
|
|
45
|
+
|
|
46
|
+
expect(list[0].ingress.class).toStrictEqual('alb')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should reject an ingress with nowhere to land', () => {
|
|
50
|
+
const list = [service({ port: 8002, ingress: { path: '/.introspection' } })]
|
|
51
|
+
|
|
52
|
+
expect(() => services(list, {}, undefined, undefined))
|
|
53
|
+
.toThrow("Service 'group-one' declares an ingress, but no hosts are defined")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should reject an ingress without a port', () => {
|
|
57
|
+
const list = [service({ ingress: { path: '/.introspection' } })]
|
|
58
|
+
|
|
59
|
+
expect(() => services(list, {}, undefined, { hosts: ['api.dev'] }))
|
|
60
|
+
.toThrow("Service 'group-one' declares an ingress, but no port")
|
|
61
|
+
})
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const desc = require('./.describe')
|
|
4
4
|
const { addVariables } = require('./.describe/variables')
|
|
5
5
|
const { addMounts } = require('./.describe/mounts')
|
|
6
|
+
const { resources } = require('./.describe/resources')
|
|
6
7
|
|
|
7
8
|
const describe = (context, compositions, dependency, image) => {
|
|
8
9
|
const { services } = dependency
|
|
@@ -26,26 +27,34 @@ const describe = (context, compositions, dependency, image) => {
|
|
|
26
27
|
|
|
27
28
|
mono.image = image.reference
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
const values = {
|
|
30
31
|
compositions: [],
|
|
31
32
|
components: mono.components,
|
|
32
33
|
services: [],
|
|
33
34
|
credentials,
|
|
34
35
|
mono
|
|
35
36
|
}
|
|
37
|
+
|
|
38
|
+
resources(context, values)
|
|
39
|
+
|
|
40
|
+
return values
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
const components = desc.components(compositions)
|
|
39
44
|
|
|
40
45
|
desc.compositions(compositions, dependency)
|
|
41
|
-
desc.services(services, dependency.variables, dependency.probe)
|
|
46
|
+
desc.services(services, dependency.variables, dependency.probe, context.ingress)
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
const values = {
|
|
44
49
|
compositions,
|
|
45
50
|
components,
|
|
46
51
|
services,
|
|
47
52
|
credentials
|
|
48
53
|
}
|
|
54
|
+
|
|
55
|
+
resources(context, values)
|
|
56
|
+
|
|
57
|
+
return values
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
function unit (context, dependency) {
|
|
@@ -61,6 +70,9 @@ function unit (context, dependency) {
|
|
|
61
70
|
variables: []
|
|
62
71
|
}
|
|
63
72
|
|
|
73
|
+
if (context.ingress !== undefined)
|
|
74
|
+
mono.ingress = Object.assign({}, context.ingress)
|
|
75
|
+
|
|
64
76
|
addVariables(mono, variables, Object.keys(variables))
|
|
65
77
|
addMounts(mono, dependency.mounts, Object.keys(mounts))
|
|
66
78
|
|
|
@@ -70,11 +82,19 @@ function unit (context, dependency) {
|
|
|
70
82
|
if (!mono.variables.some((item) => item.name === variable.name))
|
|
71
83
|
mono.variables.push(variable)
|
|
72
84
|
|
|
85
|
+
// every declared port is bound by the single mono process, none is primary
|
|
73
86
|
if (service.port !== undefined)
|
|
74
|
-
mono.port
|
|
87
|
+
(mono.backends ??= []).push({ port: service.port, path: service.ingress?.path ?? '/' })
|
|
75
88
|
|
|
76
|
-
if (service.ingress !== undefined)
|
|
77
|
-
|
|
89
|
+
if (service.ingress !== undefined) {
|
|
90
|
+
const { path, hosts, ...ingress } = service.ingress
|
|
91
|
+
|
|
92
|
+
mono.ingress = Object.assign(mono.ingress ?? {}, ingress)
|
|
93
|
+
|
|
94
|
+
// one Ingress serves every path, so its hosts are the union, not the last word
|
|
95
|
+
if (hosts !== undefined)
|
|
96
|
+
mono.ingress.hosts = [...new Set([...(mono.ingress.hosts ?? []), ...hosts])]
|
|
97
|
+
}
|
|
78
98
|
|
|
79
99
|
if (service.probe !== undefined && service.probe !== false)
|
|
80
100
|
mono.probe = service.probe
|
|
@@ -83,6 +103,9 @@ function unit (context, dependency) {
|
|
|
83
103
|
if (mono.probe === undefined && dependency.probe !== undefined && dependency.probe !== false)
|
|
84
104
|
mono.probe = dependency.probe
|
|
85
105
|
|
|
106
|
+
// the more specific prefix must come first, whatever the controller's tie-break
|
|
107
|
+
mono.backends?.sort((a, b) => b.path.length - a.path.length)
|
|
108
|
+
|
|
86
109
|
return mono
|
|
87
110
|
}
|
|
88
111
|
|
|
@@ -27,9 +27,42 @@ const merge = (dependencies) => {
|
|
|
27
27
|
if (dependency.probe !== undefined) probe = dependency.probe
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
reserve(services, probe)
|
|
31
|
+
|
|
30
32
|
return { references, services, proxies, variables, mounts, probe }
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* In Kubernetes these are separate pods, but `toa mono` and a local run put every
|
|
37
|
+
* service in one process — so a port may be claimed once and only once.
|
|
38
|
+
*/
|
|
39
|
+
const reserve = (services, probe) => {
|
|
40
|
+
const claimed = new Map()
|
|
41
|
+
|
|
42
|
+
if (probe !== undefined && probe !== false)
|
|
43
|
+
claimed.set(probe.port, 'the readiness probe')
|
|
44
|
+
|
|
45
|
+
for (const service of services)
|
|
46
|
+
for (const [port, claimant] of ports(service)) {
|
|
47
|
+
const conflicting = claimed.get(port)
|
|
48
|
+
|
|
49
|
+
if (conflicting !== undefined)
|
|
50
|
+
throw new Error(`Port ${port} is claimed by both ${conflicting} and ${claimant}`)
|
|
51
|
+
|
|
52
|
+
claimed.set(port, claimant)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function * ports (service) {
|
|
57
|
+
const name = `'${service.group}-${service.name}'`
|
|
58
|
+
|
|
59
|
+
if (service.port !== undefined)
|
|
60
|
+
yield [service.port, name]
|
|
61
|
+
|
|
62
|
+
if (service.probe !== undefined && service.probe !== false && service.probe.port !== service.port)
|
|
63
|
+
yield [service.probe.port, `the readiness probe of ${name}`]
|
|
64
|
+
}
|
|
65
|
+
|
|
33
66
|
const append = (merged, variables) => {
|
|
34
67
|
for (const [component, vars] of Object.entries(variables)) {
|
|
35
68
|
if (merged[component] === undefined) merged[component] = []
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { merge } = require('./merge')
|
|
4
|
+
|
|
5
|
+
const service = (name, extra = {}) => ({ group: 'group', name, version: '0', ...extra })
|
|
6
|
+
|
|
7
|
+
it('should merge services of all dependencies', () => {
|
|
8
|
+
const merged = merge([
|
|
9
|
+
{ services: [service('one', { port: 8000 })] },
|
|
10
|
+
{ services: [service('two', { port: 8001 })] }
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
expect(merged.services).toHaveLength(2)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* In Kubernetes these are separate pods, but `toa mono` and a local run put every
|
|
18
|
+
* service in one process.
|
|
19
|
+
*/
|
|
20
|
+
describe('port reservation', () => {
|
|
21
|
+
it('should reject two services claiming one port', () => {
|
|
22
|
+
const dependencies = [
|
|
23
|
+
{ services: [service('one', { port: 8000 })] },
|
|
24
|
+
{ services: [service('two', { port: 8000 })] }
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
expect(() => merge(dependencies))
|
|
28
|
+
.toThrow("Port 8000 is claimed by both 'group-one' and 'group-two'")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should reject a service claiming the port of the readiness probe', () => {
|
|
32
|
+
const dependencies = [
|
|
33
|
+
{ probe: { path: '/.ready', port: 8001 } },
|
|
34
|
+
{ services: [service('one', { port: 8001 })] }
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
expect(() => merge(dependencies))
|
|
38
|
+
.toThrow("Port 8001 is claimed by both the readiness probe and 'group-one'")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should reject a probe claiming the port of another service', () => {
|
|
42
|
+
const dependencies = [
|
|
43
|
+
{ services: [service('one', { port: 8000 })] },
|
|
44
|
+
{ services: [service('two', { port: 8002, probe: { path: '/.ready', port: 8000 } })] }
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
expect(() => merge(dependencies))
|
|
48
|
+
.toThrow("Port 8000 is claimed by both 'group-one' and the readiness probe of 'group-two'")
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should allow a service to probe its own port', () => {
|
|
52
|
+
const dependencies = [
|
|
53
|
+
{ services: [service('one', { port: 8000, probe: { path: '/.ready', port: 8000 } })] }
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
expect(() => merge(dependencies)).not.toThrow()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should ignore services without a port', () => {
|
|
60
|
+
const dependencies = [
|
|
61
|
+
{ services: [service('one'), service('two')] }
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
expect(() => merge(dependencies)).not.toThrow()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should ignore a disabled probe', () => {
|
|
68
|
+
const dependencies = [
|
|
69
|
+
{ probe: false, services: [service('one', { port: 8000, probe: false })] }
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
expect(() => merge(dependencies)).not.toThrow()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -26,12 +26,14 @@ spec:
|
|
|
26
26
|
containers:
|
|
27
27
|
- name: mono
|
|
28
28
|
image: {{ .image }}
|
|
29
|
-
{{- if .
|
|
29
|
+
{{- if or .backends .probe }}
|
|
30
30
|
ports:
|
|
31
|
+
{{- range .backends }}
|
|
31
32
|
- containerPort: {{ .port }}
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
{{- end }}
|
|
34
|
+
{{- if .probe }}
|
|
34
35
|
- containerPort: {{ .probe.port }}
|
|
36
|
+
{{- end }}
|
|
35
37
|
{{- end }}
|
|
36
38
|
{{- if .resources }}
|
|
37
39
|
resources:
|
|
@@ -108,7 +110,7 @@ spec:
|
|
|
108
110
|
labelSelector:
|
|
109
111
|
matchLabels:
|
|
110
112
|
toa.io/composition: mono
|
|
111
|
-
{{- if .
|
|
113
|
+
{{- if .backends }}
|
|
112
114
|
---
|
|
113
115
|
apiVersion: v1
|
|
114
116
|
kind: Service
|
|
@@ -119,10 +121,12 @@ spec:
|
|
|
119
121
|
selector:
|
|
120
122
|
toa.io/composition: mono
|
|
121
123
|
ports:
|
|
124
|
+
{{- range .backends }}
|
|
122
125
|
- name: port-{{ .port }}
|
|
123
126
|
protocol: TCP
|
|
124
127
|
port: {{ .port }}
|
|
125
128
|
targetPort: {{ .port }}
|
|
129
|
+
{{- end }}
|
|
126
130
|
{{- end }}
|
|
127
131
|
{{- if .ingress }}
|
|
128
132
|
---
|
|
@@ -138,29 +142,34 @@ spec:
|
|
|
138
142
|
{{- if .ingress.class }}
|
|
139
143
|
ingressClassName: {{ .ingress.class }}
|
|
140
144
|
{{- end }}
|
|
145
|
+
{{- $backends := .backends }}
|
|
141
146
|
rules:
|
|
142
147
|
{{- range .ingress.hosts }}
|
|
143
148
|
- host: {{ . }}
|
|
144
149
|
http:
|
|
145
150
|
paths:
|
|
146
|
-
-
|
|
151
|
+
{{- range $backends }}
|
|
152
|
+
- path: {{ .path }}
|
|
147
153
|
pathType: Prefix
|
|
148
154
|
backend:
|
|
149
155
|
service:
|
|
150
156
|
name: mono
|
|
151
157
|
port:
|
|
152
|
-
number: {{
|
|
158
|
+
number: {{ .port }}
|
|
159
|
+
{{- end }}
|
|
153
160
|
{{- end }}
|
|
154
161
|
{{- if .ingress.default }}
|
|
155
162
|
- http:
|
|
156
163
|
paths:
|
|
157
|
-
-
|
|
164
|
+
{{- range $backends }}
|
|
165
|
+
- path: {{ .path }}
|
|
158
166
|
pathType: Prefix
|
|
159
167
|
backend:
|
|
160
168
|
service:
|
|
161
169
|
name: mono
|
|
162
170
|
port:
|
|
163
|
-
number: {{
|
|
171
|
+
number: {{ .port }}
|
|
172
|
+
{{- end }}
|
|
164
173
|
{{- end }}
|
|
165
174
|
{{- end }}
|
|
166
175
|
---
|
|
@@ -100,6 +100,8 @@ spec:
|
|
|
100
100
|
{{- end }}
|
|
101
101
|
{{- if .ingress }}
|
|
102
102
|
{{- $service := .name }}
|
|
103
|
+
{{- $port := .port }}
|
|
104
|
+
{{- $path := .ingress.path | default "/" }}
|
|
103
105
|
---
|
|
104
106
|
apiVersion: networking.k8s.io/v1
|
|
105
107
|
kind: Ingress
|
|
@@ -118,24 +120,24 @@ spec:
|
|
|
118
120
|
- host: {{ . }}
|
|
119
121
|
http:
|
|
120
122
|
paths:
|
|
121
|
-
- path:
|
|
123
|
+
- path: {{ $path }}
|
|
122
124
|
pathType: Prefix
|
|
123
125
|
backend:
|
|
124
126
|
service:
|
|
125
127
|
name: extension-{{ $service }}
|
|
126
128
|
port:
|
|
127
|
-
number:
|
|
129
|
+
number: {{ $port }}
|
|
128
130
|
{{- end }}
|
|
129
131
|
{{- if .ingress.default }}
|
|
130
132
|
- http:
|
|
131
133
|
paths:
|
|
132
|
-
- path:
|
|
134
|
+
- path: {{ $path }}
|
|
133
135
|
pathType: Prefix
|
|
134
136
|
backend:
|
|
135
137
|
service:
|
|
136
138
|
name: extension-{{ $service }}
|
|
137
139
|
port:
|
|
138
|
-
number:
|
|
140
|
+
number: {{ $port }}
|
|
139
141
|
{{- end }}
|
|
140
142
|
{{- end }}
|
|
141
143
|
---
|
|
@@ -11,7 +11,6 @@ const { Service } = require('./service')
|
|
|
11
11
|
|
|
12
12
|
class Factory {
|
|
13
13
|
#context
|
|
14
|
-
#root
|
|
15
14
|
#mono
|
|
16
15
|
#compositions
|
|
17
16
|
#dependencies
|
|
@@ -21,7 +20,6 @@ class Factory {
|
|
|
21
20
|
|
|
22
21
|
constructor (context, options = {}) {
|
|
23
22
|
this.#context = context
|
|
24
|
-
this.#root = options.root
|
|
25
23
|
this.#mono = options.mono === true
|
|
26
24
|
this.#process = new Process()
|
|
27
25
|
|
|
@@ -34,7 +32,7 @@ class Factory {
|
|
|
34
32
|
if (this.#mono)
|
|
35
33
|
this.#image = this.#registry.mono({
|
|
36
34
|
components: context.components
|
|
37
|
-
}
|
|
35
|
+
})
|
|
38
36
|
else
|
|
39
37
|
this.#compositions = context.compositions.map((composition) => this.#composition(composition))
|
|
40
38
|
}
|
|
@@ -108,7 +106,7 @@ class Factory {
|
|
|
108
106
|
static async create (path, environment, options = {}) {
|
|
109
107
|
const context = await load(path, environment)
|
|
110
108
|
|
|
111
|
-
return new Factory(context,
|
|
109
|
+
return new Factory(context, options)
|
|
112
110
|
}
|
|
113
111
|
}
|
|
114
112
|
|
|
@@ -50,8 +50,8 @@ class Factory {
|
|
|
50
50
|
/**
|
|
51
51
|
* @returns {Mono}
|
|
52
52
|
*/
|
|
53
|
-
mono (composition
|
|
54
|
-
const instance = new Mono(this.#scope, this.#runtime, this.#registry, composition
|
|
53
|
+
mono (composition) {
|
|
54
|
+
const instance = new Mono(this.#scope, this.#runtime, this.#registry, composition)
|
|
55
55
|
|
|
56
56
|
instance.tag()
|
|
57
57
|
|
|
@@ -12,7 +12,7 @@ COPY --chown=node:node . /composition
|
|
|
12
12
|
{{build.run}}
|
|
13
13
|
|
|
14
14
|
RUN --mount=type=cache,target=/root/.npm \
|
|
15
|
-
for entry in
|
|
15
|
+
for entry in *; do if [ -f "$entry/package.json" ]; then (cd $entry && npm i --omit=dev); fi; done
|
|
16
16
|
|
|
17
17
|
USER node
|
|
18
|
-
CMD toa mono
|
|
18
|
+
CMD toa mono *
|
|
@@ -9,14 +9,12 @@ const { Image } = require('./image')
|
|
|
9
9
|
class Mono extends Image {
|
|
10
10
|
dockerfile = join(__dirname, 'mono.Dockerfile')
|
|
11
11
|
|
|
12
|
-
#root
|
|
13
12
|
#image
|
|
14
13
|
#components
|
|
15
14
|
|
|
16
|
-
constructor (scope, runtime, registry, composition
|
|
15
|
+
constructor (scope, runtime, registry, composition) {
|
|
17
16
|
super(scope, runtime, registry)
|
|
18
17
|
|
|
19
|
-
this.#root = root
|
|
20
18
|
this.#image = composition.image
|
|
21
19
|
this.#components = composition.components
|
|
22
20
|
}
|
|
@@ -67,16 +65,11 @@ class Mono extends Image {
|
|
|
67
65
|
async prepare (root) {
|
|
68
66
|
const context = await super.prepare(root)
|
|
69
67
|
|
|
70
|
-
await fs.copy(join(this.#root, CONTEXT), join(context, CONTEXT))
|
|
71
|
-
await fs.ensureDir(join(context, 'components'))
|
|
72
|
-
|
|
73
68
|
for (const component of this.#components)
|
|
74
|
-
await fs.copy(component.path, join(context,
|
|
69
|
+
await fs.copy(component.path, join(context, component.locator.label))
|
|
75
70
|
|
|
76
71
|
return context
|
|
77
72
|
}
|
|
78
73
|
}
|
|
79
74
|
|
|
80
|
-
const CONTEXT = 'context.toa.yaml'
|
|
81
|
-
|
|
82
75
|
exports.Mono = Mono
|