@toa.io/operations 1.0.0-alpha.27 → 1.0.0-alpha.272

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/image/runtime.Dockerfile +4 -0
  3. package/package.json +8 -8
  4. package/readme.md +16 -0
  5. package/src/deployment/.deployment/.describe/compositions.js +7 -2
  6. package/src/deployment/.deployment/.describe/events.js +67 -0
  7. package/src/deployment/.deployment/.describe/mounts.js +24 -0
  8. package/src/deployment/.deployment/.describe/resources.js +37 -0
  9. package/src/deployment/.deployment/.describe/services.js +28 -1
  10. package/src/deployment/.deployment/.describe/services.test.js +61 -0
  11. package/src/deployment/.deployment/.describe/variables.js +6 -5
  12. package/src/deployment/.deployment/describe.js +130 -6
  13. package/src/deployment/.deployment/merge.js +45 -1
  14. package/src/deployment/.deployment/merge.test.js +74 -0
  15. package/src/deployment/chart/templates/compositions.yaml +70 -0
  16. package/src/deployment/chart/templates/mono.yaml +177 -0
  17. package/src/deployment/chart/templates/services.yaml +71 -4
  18. package/src/deployment/chart/values.yaml +11 -1
  19. package/src/deployment/composition.js +1 -0
  20. package/src/deployment/deployment.js +6 -2
  21. package/src/deployment/factory.js +24 -8
  22. package/src/deployment/images/composition.Dockerfile +2 -2
  23. package/src/deployment/images/composition.js +16 -11
  24. package/src/deployment/images/factory.js +12 -3
  25. package/src/deployment/images/image.js +16 -7
  26. package/src/deployment/images/mono.Dockerfile +18 -0
  27. package/src/deployment/images/mono.js +71 -0
  28. package/src/deployment/images/runtime-base.test.js +90 -0
  29. package/src/deployment/images/service.Dockerfile +3 -3
  30. package/src/deployment/operator.js +4 -0
  31. package/src/deployment/registry.js +64 -16
  32. package/src/deployment/registry.test.js +175 -0
  33. package/types/_deployment/dependency.d.ts +2 -0
  34. package/types/_deployment/images/image.d.ts +4 -2
  35. package/types/_deployment/registry.d.ts +9 -7
  36. package/types/dependency.ts +71 -0
  37. package/types/dependency.d.ts +0 -39
  38. /package/types/{index.d.ts → index.ts} +0 -0
@@ -13,14 +13,58 @@ const merge = (dependencies) => {
13
13
  /** @type {toa.deployment.dependency.Variables} */
14
14
  const variables = {}
15
15
 
16
+ /** event labels consumed by something other than a component's own receivers */
17
+ const events = []
18
+
19
+ const mounts = {}
20
+
21
+ /** @type {toa.deployment.dependency.Probe | false | undefined} */
22
+ let probe
23
+
16
24
  for (const dependency of dependencies) {
17
25
  if (dependency.references !== undefined) references.push(...dependency.references)
18
26
  if (dependency.services !== undefined) services.push(...dependency.services)
19
27
  if (dependency.proxies !== undefined) proxies.push(...dependency.proxies)
20
28
  if (dependency.variables !== undefined) append(variables, dependency.variables)
29
+ if (dependency.events !== undefined) events.push(...dependency.events)
30
+ if (dependency.mounts !== undefined) append(mounts, dependency.mounts)
31
+ if (dependency.probe !== undefined) probe = dependency.probe
21
32
  }
22
33
 
23
- return { references, services, proxies, variables }
34
+ reserve(services, probe)
35
+
36
+ return { references, services, proxies, variables, mounts, events, probe }
37
+ }
38
+
39
+ /**
40
+ * In Kubernetes these are separate pods, but `toa mono` and a local run put every
41
+ * service in one process — so a port may be claimed once and only once.
42
+ */
43
+ const reserve = (services, probe) => {
44
+ const claimed = new Map()
45
+
46
+ if (probe !== undefined && probe !== false)
47
+ claimed.set(probe.port, 'the readiness probe')
48
+
49
+ for (const service of services)
50
+ for (const [port, claimant] of ports(service)) {
51
+ const conflicting = claimed.get(port)
52
+
53
+ if (conflicting !== undefined)
54
+ throw new Error(`Port ${port} is claimed by both ${conflicting} and ${claimant}`)
55
+
56
+ claimed.set(port, claimant)
57
+ }
58
+ }
59
+
60
+ function * ports (service) {
61
+ const name = `'${service.group}-${service.name}'`
62
+
63
+ if (service.port !== undefined)
64
+ yield [service.port, name]
65
+
66
+ if (service.probe !== undefined && service.probe !== false && service.probe.port !== service.port)
67
+ yield [service.probe.port, `the readiness probe of ${name}`]
24
68
  }
25
69
 
26
70
  const append = (merged, variables) => {
@@ -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
+ })
@@ -5,6 +5,9 @@ metadata:
5
5
  name: composition-{{ required "composition name is required" .name }}
6
6
  spec:
7
7
  replicas: {{ .replicas | default 2 }}
8
+ # A backstop for stuck rollouts only. Must stay above the `helm upgrade --timeout`
9
+ # the deploy runs with, so a slow but progressing rollout is not marked failed.
10
+ progressDeadlineSeconds: 900
8
11
  selector:
9
12
  matchLabels:
10
13
  toa.io/composition: {{ .name }}
@@ -19,15 +22,82 @@ spec:
19
22
  containers:
20
23
  - name: {{ .name }}
21
24
  image: {{ .image }}
25
+ {{- if .resources }}
26
+ resources:
27
+ {{- if or .resources.cpu .resources.memory }}
28
+ requests:
29
+ {{- if .resources.cpu }}
30
+ cpu: {{ index .resources.cpu 0 }}
31
+ {{- end }}
32
+ {{- if .resources.memory }}
33
+ memory: {{ index .resources.memory 0 }}
34
+ {{- end }}
35
+ limits:
36
+ {{- if .resources.cpu }}
37
+ cpu: {{ index .resources.cpu 1 }}
38
+ {{- end }}
39
+ {{- if .resources.memory }}
40
+ memory: {{ index .resources.memory 1 }}
41
+ {{- end }}
42
+ {{- end }}
43
+ {{- end }}
22
44
  {{- if .variables }}
23
45
  env:
24
46
  {{- range .variables }}
25
47
  {{- include "env.var" . | indent 12 }}
26
48
  {{- end }}
27
49
  {{- end }}
50
+ {{- if .probe }}
51
+ ports:
52
+ - containerPort: {{ .probe.port }}
53
+ startupProbe:
54
+ httpGet:
55
+ path: {{ .probe.path }}
56
+ port: {{ .probe.port }}
57
+ {{- if .probe.delay }}
58
+ initialDelaySeconds: {{ .probe.delay }}
59
+ {{- end }}
60
+ periodSeconds: 2
61
+ timeoutSeconds: 3
62
+ failureThreshold: 150
63
+ readinessProbe:
64
+ httpGet:
65
+ path: {{ .probe.path }}
66
+ port: {{ .probe.port }}
67
+ periodSeconds: 10
68
+ timeoutSeconds: 3
69
+ failureThreshold: 3
70
+ {{- end }}
71
+ {{- if .mounts }}
72
+ volumeMounts:
73
+ {{- range .mounts }}
74
+ - name: {{ .name }}
75
+ mountPath: {{ .path }}
76
+ {{- end }}
77
+ {{- end }}
78
+ lifecycle:
79
+ preStop:
80
+ exec:
81
+ command: [sleep, "5"]
82
+ terminationGracePeriodSeconds: 45
28
83
  {{- if $.Values.credentials }}
29
84
  imagePullSecrets:
30
85
  - name: {{ $.Values.credentials }}
31
86
  {{- end }}
87
+ {{- if .mounts }}
88
+ volumes:
89
+ {{- range .mounts }}
90
+ - name: {{ .name }}
91
+ persistentVolumeClaim:
92
+ claimName: {{ .claim }}
93
+ {{- end }}
94
+ {{- end }}
95
+ topologySpreadConstraints:
96
+ - maxSkew: 1
97
+ topologyKey: kubernetes.io/hostname
98
+ whenUnsatisfiable: ScheduleAnyway
99
+ labelSelector:
100
+ matchLabels:
101
+ toa.io/composition: {{ .name }}
32
102
  ---
33
103
  {{- end }}
@@ -0,0 +1,177 @@
1
+ {{- if .Values.mono }}
2
+ {{- with .Values.mono }}
3
+ apiVersion: apps/v1
4
+ kind: Deployment
5
+ metadata:
6
+ name: mono
7
+ spec:
8
+ replicas: {{ .replicas | default 2 }}
9
+ progressDeadlineSeconds: 900
10
+ strategy:
11
+ type: RollingUpdate
12
+ rollingUpdate:
13
+ maxUnavailable: 0
14
+ maxSurge: 50%
15
+ selector:
16
+ matchLabels:
17
+ toa.io/composition: mono
18
+ template:
19
+ metadata:
20
+ labels:
21
+ toa.io/composition: mono
22
+ {{- range .components }}
23
+ {{ . }}: "1"
24
+ {{- end }}
25
+ spec:
26
+ containers:
27
+ - name: mono
28
+ image: {{ .image }}
29
+ {{- if or .backends .probe }}
30
+ ports:
31
+ {{- range .backends }}
32
+ - containerPort: {{ .port }}
33
+ {{- end }}
34
+ {{- if .probe }}
35
+ - containerPort: {{ .probe.port }}
36
+ {{- end }}
37
+ {{- end }}
38
+ {{- if .resources }}
39
+ resources:
40
+ {{- if or .resources.cpu .resources.memory }}
41
+ requests:
42
+ {{- if .resources.cpu }}
43
+ cpu: {{ index .resources.cpu 0 }}
44
+ {{- end }}
45
+ {{- if .resources.memory }}
46
+ memory: {{ index .resources.memory 0 }}
47
+ {{- end }}
48
+ limits:
49
+ {{- if .resources.cpu }}
50
+ cpu: {{ index .resources.cpu 1 }}
51
+ {{- end }}
52
+ {{- if .resources.memory }}
53
+ memory: {{ index .resources.memory 1 }}
54
+ {{- end }}
55
+ {{- end }}
56
+ {{- end }}
57
+ {{- if .variables }}
58
+ env:
59
+ {{- range .variables }}
60
+ {{- include "env.var" . | indent 12 }}
61
+ {{- end }}
62
+ {{- end }}
63
+ {{- if .probe }}
64
+ startupProbe:
65
+ httpGet:
66
+ path: {{ .probe.path }}
67
+ port: {{ .probe.port }}
68
+ {{- if .probe.delay }}
69
+ initialDelaySeconds: {{ .probe.delay }}
70
+ {{- end }}
71
+ periodSeconds: 2
72
+ timeoutSeconds: 3
73
+ failureThreshold: 150
74
+ readinessProbe:
75
+ httpGet:
76
+ path: {{ .probe.path }}
77
+ port: {{ .probe.port }}
78
+ periodSeconds: 10
79
+ timeoutSeconds: 3
80
+ failureThreshold: 3
81
+ {{- end }}
82
+ {{- if .mounts }}
83
+ volumeMounts:
84
+ {{- range .mounts }}
85
+ - name: {{ .name }}
86
+ mountPath: {{ .path }}
87
+ {{- end }}
88
+ {{- end }}
89
+ lifecycle:
90
+ preStop:
91
+ exec:
92
+ command: [sleep, "5"]
93
+ terminationGracePeriodSeconds: 45
94
+ {{- if $.Values.credentials }}
95
+ imagePullSecrets:
96
+ - name: {{ $.Values.credentials }}
97
+ {{- end }}
98
+ {{- if .mounts }}
99
+ volumes:
100
+ {{- range .mounts }}
101
+ - name: {{ .name }}
102
+ persistentVolumeClaim:
103
+ claimName: {{ .claim }}
104
+ {{- end }}
105
+ {{- end }}
106
+ topologySpreadConstraints:
107
+ - maxSkew: 1
108
+ topologyKey: kubernetes.io/hostname
109
+ whenUnsatisfiable: ScheduleAnyway
110
+ labelSelector:
111
+ matchLabels:
112
+ toa.io/composition: mono
113
+ {{- if .backends }}
114
+ ---
115
+ apiVersion: v1
116
+ kind: Service
117
+ metadata:
118
+ name: mono
119
+ spec:
120
+ type: ClusterIP
121
+ selector:
122
+ toa.io/composition: mono
123
+ ports:
124
+ {{- range .backends }}
125
+ - name: port-{{ .port }}
126
+ protocol: TCP
127
+ port: {{ .port }}
128
+ targetPort: {{ .port }}
129
+ {{- end }}
130
+ {{- end }}
131
+ {{- if .ingress }}
132
+ ---
133
+ apiVersion: networking.k8s.io/v1
134
+ kind: Ingress
135
+ metadata:
136
+ name: mono
137
+ {{- if .ingress.annotations }}
138
+ annotations:
139
+ {{ toYaml .ingress.annotations | indent 4 }}
140
+ {{- end }}
141
+ spec:
142
+ {{- if .ingress.class }}
143
+ ingressClassName: {{ .ingress.class }}
144
+ {{- end }}
145
+ {{- $backends := .backends }}
146
+ rules:
147
+ {{- range .ingress.hosts }}
148
+ - host: {{ . }}
149
+ http:
150
+ paths:
151
+ {{- range $backends }}
152
+ - path: {{ .path }}
153
+ pathType: Prefix
154
+ backend:
155
+ service:
156
+ name: mono
157
+ port:
158
+ number: {{ .port }}
159
+ {{- end }}
160
+ {{- end }}
161
+ {{- if .ingress.default }}
162
+ - http:
163
+ paths:
164
+ {{- range $backends }}
165
+ - path: {{ .path }}
166
+ pathType: Prefix
167
+ backend:
168
+ service:
169
+ name: mono
170
+ port:
171
+ number: {{ .port }}
172
+ {{- end }}
173
+ {{- end }}
174
+ {{- end }}
175
+ ---
176
+ {{- end }}
177
+ {{- end }}
@@ -5,6 +5,16 @@ metadata:
5
5
  name: extension-{{ required "deployment name is required" .name }}
6
6
  spec:
7
7
  replicas: {{ .replicas | default 2 }}
8
+ # A backstop for stuck rollouts only. Must stay above the `helm upgrade --timeout`
9
+ # the deploy runs with, so a slow but progressing rollout is not marked failed.
10
+ progressDeadlineSeconds: 900
11
+ strategy:
12
+ type: RollingUpdate
13
+ rollingUpdate:
14
+ # a replacement must pass readiness before a running replica is taken out,
15
+ # otherwise a single pod serves all traffic while the others start up
16
+ maxUnavailable: 0
17
+ maxSurge: 50%
8
18
  selector:
9
19
  matchLabels:
10
20
  toa.io/service: extension-{{ .name }}
@@ -16,6 +26,25 @@ spec:
16
26
  containers:
17
27
  - name: extension-{{ .name }}
18
28
  image: {{ .image }}
29
+ {{- if .resources }}
30
+ resources:
31
+ {{- if or .resources.cpu .resources.memory }}
32
+ requests:
33
+ {{- if .resources.cpu }}
34
+ cpu: {{ index .resources.cpu 0 }}
35
+ {{- end }}
36
+ {{- if .resources.memory }}
37
+ memory: {{ index .resources.memory 0 }}
38
+ {{- end }}
39
+ limits:
40
+ {{- if .resources.cpu }}
41
+ cpu: {{ index .resources.cpu 1 }}
42
+ {{- end }}
43
+ {{- if .resources.memory }}
44
+ memory: {{ index .resources.memory 1 }}
45
+ {{- end }}
46
+ {{- end }}
47
+ {{- end }}
19
48
  {{- if .variables }}
20
49
  env:
21
50
  {{- range .variables }}
@@ -23,14 +52,37 @@ spec:
23
52
  {{- end }}
24
53
  {{- end }}
25
54
  {{- if .probe }}
26
- readinessProbe:
55
+ startupProbe:
27
56
  httpGet:
28
57
  path: {{ .probe.path }}
29
58
  port: {{ .probe.port }}
30
59
  {{- if .probe.delay }}
31
60
  initialDelaySeconds: {{ .probe.delay }}
32
61
  {{- end }}
62
+ periodSeconds: 2
63
+ timeoutSeconds: 3
64
+ failureThreshold: 150
65
+ readinessProbe:
66
+ httpGet:
67
+ path: {{ .probe.path }}
68
+ port: {{ .probe.port }}
69
+ periodSeconds: 10
70
+ timeoutSeconds: 3
71
+ failureThreshold: 3
33
72
  {{- end }}
73
+ lifecycle:
74
+ preStop:
75
+ exec:
76
+ command: [sleep, "5"]
77
+ terminationGracePeriodSeconds: 45
78
+ topologySpreadConstraints:
79
+ - maxSkew: 1
80
+ topologyKey: kubernetes.io/hostname
81
+ whenUnsatisfiable: ScheduleAnyway
82
+ labelSelector:
83
+ matchLabels:
84
+ toa.io/service: extension-{{ .name }}
85
+ {{- if .port }}
34
86
  ---
35
87
  apiVersion: v1
36
88
  kind: Service
@@ -45,9 +97,12 @@ spec:
45
97
  protocol: TCP
46
98
  port: {{ .port }}
47
99
  targetPort: {{ .port }}
48
- ---
100
+ {{- end }}
49
101
  {{- if .ingress }}
50
102
  {{- $service := .name }}
103
+ {{- $port := .port }}
104
+ {{- $path := .ingress.path | default "/" }}
105
+ ---
51
106
  apiVersion: networking.k8s.io/v1
52
107
  kind: Ingress
53
108
  metadata:
@@ -65,13 +120,25 @@ spec:
65
120
  - host: {{ . }}
66
121
  http:
67
122
  paths:
68
- - path: /
123
+ - path: {{ $path }}
69
124
  pathType: Prefix
70
125
  backend:
71
126
  service:
72
127
  name: extension-{{ $service }}
73
128
  port:
74
- number: 8000
129
+ number: {{ $port }}
130
+ {{- end }}
131
+ {{- if .ingress.default }}
132
+ - http:
133
+ paths:
134
+ - path: {{ $path }}
135
+ pathType: Prefix
136
+ backend:
137
+ service:
138
+ name: extension-{{ $service }}
139
+ port:
140
+ number: {{ $port }}
75
141
  {{- end }}
76
142
  {{- end }}
143
+ ---
77
144
  {{- end }}
@@ -3,9 +3,16 @@ compositions:
3
3
  - name: todos
4
4
  image: localhost:5000/composition-todos:0.0.0
5
5
  replicas: 2
6
+ mounts:
7
+ - name: mount-name
8
+ path: /storage
9
+ claim: storage-pvc
6
10
  components:
7
11
  - todos-tasks
8
12
  - todos-stats
13
+ resources:
14
+ cpu: [100m, 1]
15
+ memory: [100Mi, 1Gi]
9
16
  variables:
10
17
  - name: TOA_CONFIGURATION_TODOS_TASKS
11
18
  value: foo
@@ -18,7 +25,6 @@ compositions:
18
25
  replicas: 3
19
26
  components:
20
27
  - users-users
21
- # TODO: create component services only if sync binding is being used
22
28
  components:
23
29
  - todos-tasks
24
30
  - todos-stats
@@ -28,7 +34,11 @@ services:
28
34
  image: localhost:5000/resources-gateway:0.0.0
29
35
  port: 8000
30
36
  replicas: 2
37
+ resources:
38
+ cpu: [100m, 1]
39
+ memory: [100Mi, 1Gi]
31
40
  ingress:
41
+ default: true
32
42
  hosts: [dummies.toa.io]
33
43
  class: alb
34
44
  annotations:
@@ -10,6 +10,7 @@ class Composition {
10
10
  this.name = composition.name
11
11
  this.image = image.reference
12
12
  this.components = composition.components.map(component)
13
+ this.resources = composition.resources
13
14
  }
14
15
  }
15
16
 
@@ -13,11 +13,11 @@ class Deployment {
13
13
  #process
14
14
  #target
15
15
 
16
- constructor (context, compositions, dependencies, process) {
16
+ constructor (context, compositions, dependencies, process, image) {
17
17
  const dependency = merge(dependencies)
18
18
 
19
19
  this.#chart = declare(context, dependency)
20
- this.#values = describe(context, compositions, dependency)
20
+ this.#values = describe(context, compositions, dependency, image)
21
21
  this.#process = process
22
22
  }
23
23
 
@@ -42,6 +42,7 @@ class Deployment {
42
42
 
43
43
  if (options.namespace !== undefined) args.push('-n', options.namespace)
44
44
  if (options.wait === true) args.push('--wait')
45
+ if (options.timeout !== undefined) args.push('--timeout', options.timeout)
45
46
 
46
47
  await this.#process.execute('helm', ['dependency', 'update', this.#target])
47
48
  await this.#process.execute('helm', ['upgrade', this.#chart.name, '-i', ...args, this.#target])
@@ -68,6 +69,9 @@ class Deployment {
68
69
  addVariables(this.#values.compositions, variables, used)
69
70
  addVariables(this.#values.services, variables, used)
70
71
 
72
+ if (this.#values.mono !== undefined)
73
+ addVariables([this.#values.mono], variables, used)
74
+
71
75
  return variables
72
76
  }
73
77
  }
@@ -11,25 +11,40 @@ const { Service } = require('./service')
11
11
 
12
12
  class Factory {
13
13
  #context
14
+ #mono
14
15
  #compositions
15
16
  #dependencies
16
17
  #registry
17
18
  #process
18
- #extensionComponents = []
19
+ #image
19
20
 
20
- constructor (context) {
21
+ constructor (context, options = {}) {
21
22
  this.#context = context
23
+ this.#mono = options.mono === true
22
24
  this.#process = new Process()
23
25
 
24
26
  const imagesFactory = new ImagesFactory(context.name, context.runtime, context.registry)
25
27
 
26
- this.#registry = new Registry(context.registry, imagesFactory, this.#process)
27
- this.#compositions = context.compositions.map((composition) => this.#composition(composition))
28
+ this.#registry = new Registry(context.name, context.registry, imagesFactory, this.#process)
28
29
  this.#dependencies = this.#getDependencies()
30
+ this.#compositions = []
31
+
32
+ if (this.#mono)
33
+ this.#image = this.#registry.mono({
34
+ components: context.components
35
+ })
36
+ else
37
+ this.#compositions = context.compositions.map((composition) => this.#composition(composition))
29
38
  }
30
39
 
31
40
  operator () {
32
- const deployment = new Deployment(this.#context, this.#compositions, this.#dependencies, this.#process)
41
+ const deployment = new Deployment(
42
+ this.#context,
43
+ this.#compositions,
44
+ this.#dependencies,
45
+ this.#process,
46
+ this.#image
47
+ )
33
48
 
34
49
  return new Operator(deployment, this.#registry)
35
50
  }
@@ -71,7 +86,8 @@ class Factory {
71
86
  const dependency = module.deployment(instances, annotation)
72
87
 
73
88
  /** @type {toa.deployment.Service[]} */
74
- const services = dependency.services?.map((service) => this.#service(path, service))
89
+ const services = dependency.services?.map((service) =>
90
+ this.#mono ? service : this.#service(path, service))
75
91
 
76
92
  return { ...dependency, services }
77
93
  }
@@ -87,10 +103,10 @@ class Factory {
87
103
  return new Service(service, image)
88
104
  }
89
105
 
90
- static async create (path, environment) {
106
+ static async create (path, environment, options = {}) {
91
107
  const context = await load(path, environment)
92
108
 
93
- return new Factory(context)
109
+ return new Factory(context, options)
94
110
  }
95
111
  }
96
112
 
@@ -5,7 +5,6 @@ FROM {{build.image}}
5
5
  ENV NODE_ENV=production
6
6
  RUN if [ "{{runtime.registry}}" != "" ]; then npm set registry {{runtime.registry}}; fi
7
7
  RUN if [ "{{runtime.proxy}}" != "" ]; then npm set proxy {{runtime.proxy}}; fi
8
- RUN npm i -g @toa.io/runtime@{{runtime.version}} --omit=dev
9
8
 
10
9
  WORKDIR /composition
11
10
  COPY --chown=node:node . /composition
@@ -13,7 +12,8 @@ COPY --chown=node:node . /composition
13
12
  {{build.run}}
14
13
 
15
14
  # run 'npm i' in each component
16
- RUN for entry in *; do if [ -f "$entry/package.json" ]; then (cd $entry && npm i --omit=dev); fi; done
15
+ RUN --mount=type=cache,target=/root/.npm \
16
+ for entry in *; do if [ -f "$entry/package.json" ]; then (cd $entry && npm i --omit=dev); fi; done
17
17
 
18
18
  USER node
19
19
  CMD toa compose *