@toa.io/operations 1.0.0-alpha.29 → 1.0.0-alpha.291

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 (75) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/image/runtime.Dockerfile +4 -0
  3. package/package.json +8 -10
  4. package/readme.md +93 -0
  5. package/src/deployment/.deployment/.describe/components.js +1 -5
  6. package/src/deployment/.deployment/.describe/compositions.js +26 -7
  7. package/src/deployment/.deployment/.describe/dependencies.js +1 -6
  8. package/src/deployment/.deployment/.describe/events.js +63 -0
  9. package/src/deployment/.deployment/.describe/fold.js +42 -0
  10. package/src/deployment/.deployment/.describe/fold.test.js +108 -0
  11. package/src/deployment/.deployment/.describe/index.js +4 -13
  12. package/src/deployment/.deployment/.describe/mounts.js +17 -0
  13. package/src/deployment/.deployment/.describe/resources.js +103 -0
  14. package/src/deployment/.deployment/.describe/resources.test.js +129 -0
  15. package/src/deployment/.deployment/.describe/services.js +32 -6
  16. package/src/deployment/.deployment/.describe/services.test.js +78 -0
  17. package/src/deployment/.deployment/.describe/variables.js +5 -9
  18. package/src/deployment/.deployment/declare.js +1 -5
  19. package/src/deployment/.deployment/describe.js +125 -10
  20. package/src/deployment/.deployment/index.js +3 -9
  21. package/src/deployment/.deployment/merge.js +66 -6
  22. package/src/deployment/.deployment/merge.test.js +133 -0
  23. package/src/deployment/chart/templates/components.yaml +1 -1
  24. package/src/deployment/chart/templates/compositions.yaml +96 -4
  25. package/src/deployment/chart/templates/mono.yaml +182 -0
  26. package/src/deployment/chart/templates/services.yaml +82 -7
  27. package/src/deployment/chart/values.yaml +23 -1
  28. package/src/deployment/composition.js +6 -6
  29. package/src/deployment/deployment.js +55 -25
  30. package/src/deployment/drain.js +70 -0
  31. package/src/deployment/drain.test.js +64 -0
  32. package/src/deployment/factory.js +121 -32
  33. package/src/deployment/images/bundle.js +82 -0
  34. package/src/deployment/images/composition.Dockerfile +8 -16
  35. package/src/deployment/images/composition.js +9 -56
  36. package/src/deployment/images/dependencies.Dockerfile +16 -0
  37. package/src/deployment/images/dependencies.js +141 -0
  38. package/src/deployment/images/dependencies.test.js +214 -0
  39. package/src/deployment/images/factory.js +31 -15
  40. package/src/deployment/images/format.js +52 -0
  41. package/src/deployment/images/format.test.js +74 -0
  42. package/src/deployment/images/image.fixtures.js +13 -13
  43. package/src/deployment/images/image.js +77 -38
  44. package/src/deployment/images/image.test.js +11 -5
  45. package/src/deployment/images/index.js +1 -5
  46. package/src/deployment/images/mono.Dockerfile +11 -0
  47. package/src/deployment/images/mono.js +15 -0
  48. package/src/deployment/images/publish.js +93 -0
  49. package/src/deployment/images/runtime-base.test.js +97 -0
  50. package/src/deployment/images/service.Dockerfile +5 -4
  51. package/src/deployment/images/service.js +13 -13
  52. package/src/deployment/index.js +1 -5
  53. package/src/deployment/operator.js +13 -13
  54. package/src/deployment/operator.test.js +8 -7
  55. package/src/deployment/registry.js +165 -47
  56. package/src/deployment/registry.test.js +407 -0
  57. package/src/deployment/service.js +4 -6
  58. package/src/deployment/workspace.js +13 -8
  59. package/src/index.js +1 -3
  60. package/src/process.js +51 -13
  61. package/src/process.test.js +33 -0
  62. package/types/_deployment/composition.d.ts +1 -3
  63. package/types/_deployment/dependency.d.ts +13 -7
  64. package/types/_deployment/deployment.d.ts +3 -7
  65. package/types/_deployment/factory.d.ts +2 -4
  66. package/types/_deployment/images/factory.d.ts +6 -8
  67. package/types/_deployment/images/image.d.ts +16 -1
  68. package/types/_deployment/images/registry.d.ts +8 -11
  69. package/types/_deployment/operator.d.ts +7 -9
  70. package/types/_deployment/registry.d.ts +4 -4
  71. package/types/_deployment/service.d.ts +4 -3
  72. package/types/dependency.ts +79 -0
  73. package/types/index.ts +1 -0
  74. package/types/dependency.d.ts +0 -39
  75. package/types/index.d.ts +0 -1
@@ -6,7 +6,7 @@ metadata:
6
6
  spec:
7
7
  type: ClusterIP
8
8
  selector:
9
- {{ . }}: "1"
9
+ toa/component-{{ . }}: "1"
10
10
  ports:
11
11
  - name: http-binding
12
12
  protocol: TCP
@@ -5,29 +5,121 @@ 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
11
+ strategy:
12
+ type: RollingUpdate
13
+ rollingUpdate:
14
+ # a replacement must pass readiness before a running replica is taken out, and every
15
+ # replacement starts at once: the rollout takes one start-up, not one per replica
16
+ maxUnavailable: 0
17
+ maxSurge: 100%
8
18
  selector:
9
19
  matchLabels:
10
- toa.io/composition: {{ .name }}
20
+ toa/composition: {{ .name }}
11
21
  template:
12
22
  metadata:
13
23
  labels:
14
- toa.io/composition: {{ .name }}
24
+ toa/composition: {{ .name }}
15
25
  {{- range .components }}
16
- {{ . }}: "1"
26
+ toa/component-{{ . }}: "1"
27
+ {{- end }}
28
+ {{- range .hosted }}
29
+ toa/service-{{ . }}: "1"
17
30
  {{- end }}
18
31
  spec:
19
32
  containers:
20
33
  - name: {{ .name }}
21
34
  image: {{ .image }}
22
- {{- if .variables }}
35
+ {{- if .resources }}
36
+ resources:
37
+ {{- if or .resources.cpu .resources.memory }}
38
+ requests:
39
+ {{- if .resources.cpu }}
40
+ cpu: {{ index .resources.cpu 0 }}
41
+ {{- end }}
42
+ {{- if .resources.memory }}
43
+ memory: {{ index .resources.memory 0 }}
44
+ {{- end }}
45
+ limits:
46
+ {{- if .resources.cpu }}
47
+ cpu: {{ index .resources.cpu 1 }}
48
+ {{- end }}
49
+ {{- if .resources.memory }}
50
+ memory: {{ index .resources.memory 1 }}
51
+ {{- end }}
52
+ {{- end }}
53
+ {{- end }}
54
+ {{- if or .variables .services }}
23
55
  env:
56
+ {{- if .services }}
57
+ - name: TOA_SERVICES
58
+ value: {{ join " " .services | quote }}
59
+ {{- end }}
24
60
  {{- range .variables }}
25
61
  {{- include "env.var" . | indent 12 }}
26
62
  {{- end }}
27
63
  {{- end }}
64
+ {{- if or .backends .probe }}
65
+ ports:
66
+ {{- range .backends }}
67
+ - containerPort: {{ .port }}
68
+ {{- end }}
69
+ {{- if .probe }}
70
+ - containerPort: {{ .probe.port }}
71
+ {{- end }}
72
+ {{- end }}
73
+ {{- if .probe }}
74
+ startupProbe:
75
+ httpGet:
76
+ path: {{ .probe.path }}
77
+ port: {{ .probe.port }}
78
+ {{- if .probe.delay }}
79
+ initialDelaySeconds: {{ .probe.delay }}
80
+ {{- end }}
81
+ periodSeconds: 2
82
+ timeoutSeconds: 3
83
+ failureThreshold: 150
84
+ readinessProbe:
85
+ httpGet:
86
+ path: {{ .probe.path }}
87
+ port: {{ .probe.port }}
88
+ # a replica is ready when it says so, not at the next tick of a slow clock
89
+ periodSeconds: 2
90
+ timeoutSeconds: 3
91
+ failureThreshold: 5
92
+ {{- end }}
93
+ {{- if .mounts }}
94
+ volumeMounts:
95
+ {{- range .mounts }}
96
+ - name: {{ .name }}
97
+ mountPath: {{ .path }}
98
+ {{- end }}
99
+ {{- end }}
100
+ lifecycle:
101
+ preStop:
102
+ exec:
103
+ command: [sleep, "5"]
104
+ terminationGracePeriodSeconds: 45
28
105
  {{- if $.Values.credentials }}
29
106
  imagePullSecrets:
30
107
  - name: {{ $.Values.credentials }}
31
108
  {{- end }}
109
+ {{- if .mounts }}
110
+ volumes:
111
+ {{- range .mounts }}
112
+ - name: {{ .name }}
113
+ persistentVolumeClaim:
114
+ claimName: {{ .claim }}
115
+ {{- end }}
116
+ {{- end }}
117
+ topologySpreadConstraints:
118
+ - maxSkew: 1
119
+ topologyKey: kubernetes.io/hostname
120
+ whenUnsatisfiable: ScheduleAnyway
121
+ labelSelector:
122
+ matchLabels:
123
+ toa/composition: {{ .name }}
32
124
  ---
33
125
  {{- end }}
@@ -0,0 +1,182 @@
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: 100%
15
+ selector:
16
+ matchLabels:
17
+ toa/composition: mono
18
+ template:
19
+ metadata:
20
+ labels:
21
+ toa/composition: mono
22
+ {{- range .components }}
23
+ toa/component-{{ . }}: "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
+ # a replica is ready when it says so, not at the next tick of a slow clock
79
+ periodSeconds: 2
80
+ timeoutSeconds: 3
81
+ failureThreshold: 5
82
+ {{- end }}
83
+ {{- if .mounts }}
84
+ volumeMounts:
85
+ {{- range .mounts }}
86
+ - name: {{ .name }}
87
+ mountPath: {{ .path }}
88
+ {{- end }}
89
+ {{- end }}
90
+ lifecycle:
91
+ preStop:
92
+ exec:
93
+ command: [sleep, "5"]
94
+ terminationGracePeriodSeconds: 45
95
+ {{- if $.Values.credentials }}
96
+ imagePullSecrets:
97
+ - name: {{ $.Values.credentials }}
98
+ {{- end }}
99
+ {{- if .mounts }}
100
+ volumes:
101
+ {{- range .mounts }}
102
+ - name: {{ .name }}
103
+ persistentVolumeClaim:
104
+ claimName: {{ .claim }}
105
+ {{- end }}
106
+ {{- end }}
107
+ topologySpreadConstraints:
108
+ - maxSkew: 1
109
+ topologyKey: kubernetes.io/hostname
110
+ whenUnsatisfiable: ScheduleAnyway
111
+ labelSelector:
112
+ matchLabels:
113
+ toa/composition: mono
114
+ {{- if .backends }}
115
+ ---
116
+ apiVersion: v1
117
+ kind: Service
118
+ metadata:
119
+ name: mono
120
+ {{- if .annotations }}
121
+ annotations:
122
+ {{ toYaml .annotations | indent 4 }}
123
+ {{- end }}
124
+ spec:
125
+ type: ClusterIP
126
+ selector:
127
+ toa/composition: mono
128
+ ports:
129
+ {{- range .backends }}
130
+ - name: port-{{ .port }}
131
+ protocol: TCP
132
+ port: {{ .port }}
133
+ targetPort: {{ .port }}
134
+ {{- end }}
135
+ {{- end }}
136
+ {{- if .ingress }}
137
+ ---
138
+ apiVersion: networking.k8s.io/v1
139
+ kind: Ingress
140
+ metadata:
141
+ name: mono
142
+ {{- if .ingress.annotations }}
143
+ annotations:
144
+ {{ toYaml .ingress.annotations | indent 4 }}
145
+ {{- end }}
146
+ spec:
147
+ {{- if .ingress.class }}
148
+ ingressClassName: {{ .ingress.class }}
149
+ {{- end }}
150
+ {{- $backends := .backends }}
151
+ rules:
152
+ {{- range .ingress.hosts }}
153
+ - host: {{ . }}
154
+ http:
155
+ paths:
156
+ {{- range $backends }}
157
+ - path: {{ .path }}
158
+ pathType: Prefix
159
+ backend:
160
+ service:
161
+ name: mono
162
+ port:
163
+ number: {{ .port }}
164
+ {{- end }}
165
+ {{- end }}
166
+ {{- if .ingress.default }}
167
+ - http:
168
+ paths:
169
+ {{- range $backends }}
170
+ - path: {{ .path }}
171
+ pathType: Prefix
172
+ backend:
173
+ service:
174
+ name: mono
175
+ port:
176
+ number: {{ .port }}
177
+ {{- end }}
178
+ {{- end }}
179
+ {{- end }}
180
+ ---
181
+ {{- end }}
182
+ {{- end }}
@@ -1,21 +1,52 @@
1
1
  {{- range .Values.services }}
2
+ {{- if not .workload }}
2
3
  apiVersion: apps/v1
3
4
  kind: Deployment
4
5
  metadata:
5
6
  name: extension-{{ required "deployment name is required" .name }}
6
7
  spec:
7
8
  replicas: {{ .replicas | default 2 }}
9
+ # A backstop for stuck rollouts only. Must stay above the `helm upgrade --timeout`
10
+ # the deploy runs with, so a slow but progressing rollout is not marked failed.
11
+ progressDeadlineSeconds: 900
12
+ strategy:
13
+ type: RollingUpdate
14
+ rollingUpdate:
15
+ # a replacement must pass readiness before a running replica is taken out, and every
16
+ # replacement starts at once: the rollout takes one start-up, not one per replica
17
+ maxUnavailable: 0
18
+ maxSurge: 100%
8
19
  selector:
9
20
  matchLabels:
10
- toa.io/service: extension-{{ .name }}
21
+ toa/service: extension-{{ .name }}
11
22
  template:
12
23
  metadata:
13
24
  labels:
14
- toa.io/service: extension-{{ .name }}
25
+ toa/service: extension-{{ .name }}
26
+ toa/service-{{ .name }}: "1"
15
27
  spec:
16
28
  containers:
17
29
  - name: extension-{{ .name }}
18
30
  image: {{ .image }}
31
+ {{- if .resources }}
32
+ resources:
33
+ {{- if or .resources.cpu .resources.memory }}
34
+ requests:
35
+ {{- if .resources.cpu }}
36
+ cpu: {{ index .resources.cpu 0 }}
37
+ {{- end }}
38
+ {{- if .resources.memory }}
39
+ memory: {{ index .resources.memory 0 }}
40
+ {{- end }}
41
+ limits:
42
+ {{- if .resources.cpu }}
43
+ cpu: {{ index .resources.cpu 1 }}
44
+ {{- end }}
45
+ {{- if .resources.memory }}
46
+ memory: {{ index .resources.memory 1 }}
47
+ {{- end }}
48
+ {{- end }}
49
+ {{- end }}
19
50
  {{- if .variables }}
20
51
  env:
21
52
  {{- range .variables }}
@@ -23,31 +54,63 @@ spec:
23
54
  {{- end }}
24
55
  {{- end }}
25
56
  {{- if .probe }}
26
- readinessProbe:
57
+ startupProbe:
27
58
  httpGet:
28
59
  path: {{ .probe.path }}
29
60
  port: {{ .probe.port }}
30
61
  {{- if .probe.delay }}
31
62
  initialDelaySeconds: {{ .probe.delay }}
32
63
  {{- end }}
64
+ periodSeconds: 2
65
+ timeoutSeconds: 3
66
+ failureThreshold: 150
67
+ readinessProbe:
68
+ httpGet:
69
+ path: {{ .probe.path }}
70
+ port: {{ .probe.port }}
71
+ # a replica is ready when it says so, not at the next tick of a slow clock
72
+ periodSeconds: 2
73
+ timeoutSeconds: 3
74
+ failureThreshold: 5
33
75
  {{- end }}
76
+ lifecycle:
77
+ preStop:
78
+ exec:
79
+ command: [sleep, "5"]
80
+ terminationGracePeriodSeconds: 45
81
+ topologySpreadConstraints:
82
+ - maxSkew: 1
83
+ topologyKey: kubernetes.io/hostname
84
+ whenUnsatisfiable: ScheduleAnyway
85
+ labelSelector:
86
+ matchLabels:
87
+ toa/service: extension-{{ .name }}
88
+ {{- end }}
89
+ {{- if .port }}
34
90
  ---
35
91
  apiVersion: v1
36
92
  kind: Service
37
93
  metadata:
38
94
  name: extension-{{ .name }}
95
+ {{- if .annotations }}
96
+ annotations:
97
+ {{ toYaml .annotations | indent 4 }}
98
+ {{- end }}
39
99
  spec:
40
100
  type: ClusterIP
41
101
  selector:
42
- toa.io/service: extension-{{ .name }}
102
+ toa/service-{{ .name }}: "1"
43
103
  ports:
44
104
  - name: port-{{ .port }}
45
105
  protocol: TCP
46
106
  port: {{ .port }}
47
107
  targetPort: {{ .port }}
48
- ---
108
+ {{- end }}
49
109
  {{- if .ingress }}
50
110
  {{- $service := .name }}
111
+ {{- $port := .port }}
112
+ {{- $path := .ingress.path | default "/" }}
113
+ ---
51
114
  apiVersion: networking.k8s.io/v1
52
115
  kind: Ingress
53
116
  metadata:
@@ -65,13 +128,25 @@ spec:
65
128
  - host: {{ . }}
66
129
  http:
67
130
  paths:
68
- - path: /
131
+ - path: {{ $path }}
132
+ pathType: Prefix
133
+ backend:
134
+ service:
135
+ name: extension-{{ $service }}
136
+ port:
137
+ number: {{ $port }}
138
+ {{- end }}
139
+ {{- if .ingress.default }}
140
+ - http:
141
+ paths:
142
+ - path: {{ $path }}
69
143
  pathType: Prefix
70
144
  backend:
71
145
  service:
72
146
  name: extension-{{ $service }}
73
147
  port:
74
- number: 8000
148
+ number: {{ $port }}
75
149
  {{- end }}
76
150
  {{- end }}
151
+ ---
77
152
  {{- 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,17 +25,32 @@ compositions:
18
25
  replicas: 3
19
26
  components:
20
27
  - users-users
21
- # TODO: create component services only if sync binding is being used
28
+ # the extensions whose services this composition runs in its own pod
29
+ services:
30
+ - '@toa.io/extensions.exposition'
31
+ # each of those, as its own Service selects it
32
+ hosted:
33
+ - exposition-gateway
34
+ # the ports they bind
35
+ backends:
36
+ - port: 8000
37
+ path: /
22
38
  components:
23
39
  - todos-tasks
24
40
  - todos-stats
25
41
  - users-users
26
42
  services:
43
+ # a service a composition runs carries `workload`, and gets no deployment of its own —
44
+ # its Service and Ingress stay, and select that composition's pods
27
45
  - name: resources-gateway
28
46
  image: localhost:5000/resources-gateway:0.0.0
29
47
  port: 8000
30
48
  replicas: 2
49
+ resources:
50
+ cpu: [100m, 1]
51
+ memory: [100Mi, 1Gi]
31
52
  ingress:
53
+ default: true
32
54
  hosts: [dummies.toa.io]
33
55
  class: alb
34
56
  annotations:
@@ -1,18 +1,18 @@
1
- 'use strict'
2
-
3
- class Composition {
1
+ export class Composition {
4
2
  name
5
3
  image
6
4
  /** @type {string[]} */
7
5
  components
8
6
 
9
- constructor (composition, image) {
7
+ constructor(composition, image) {
10
8
  this.name = composition.name
11
9
  this.image = image.reference
12
10
  this.components = composition.components.map(component)
11
+ this.resources = composition.resources
12
+
13
+ // the extensions whose services this composition runs, as `TOA_SERVICES` for the process
14
+ if (composition.services !== undefined) this.services = composition.services
13
15
  }
14
16
  }
15
17
 
16
18
  const component = (component) => component.locator.label
17
-
18
- exports.Composition = Composition
@@ -1,78 +1,105 @@
1
- 'use strict'
1
+ import { join } from 'node:path'
2
+ import { writeFile as write } from 'node:fs/promises'
3
+ import { yaml as jsyaml } from '@toa.io/generic'
4
+ import { cp } from 'node:fs/promises'
2
5
 
3
- const { join } = require('node:path')
4
- const { writeFile: write } = require('node:fs/promises')
5
- const { dump } = require('@toa.io/yaml')
6
- const fs = require('fs-extra')
6
+ import { merge, declare, describe } from './.deployment/index.js'
7
+ import { drain } from './drain.js'
7
8
 
8
- const { merge, declare, describe } = require('./.deployment')
9
-
10
- class Deployment {
9
+ export class Deployment {
11
10
  #chart
12
11
  #values
13
12
  #process
14
13
  #target
15
14
 
16
- constructor (context, compositions, dependencies, process) {
15
+ constructor(context, compositions, dependencies, process, image) {
17
16
  const dependency = merge(dependencies)
18
17
 
19
18
  this.#chart = declare(context, dependency)
20
- this.#values = describe(context, compositions, dependency)
19
+ this.#values = describe(context, compositions, dependency, image)
21
20
  this.#process = process
22
21
  }
23
22
 
24
- async export (target) {
23
+ async export(target) {
25
24
  const chart = dump(this.#chart)
26
25
  const values = dump(this.#values)
27
26
 
28
27
  await Promise.all([
29
28
  write(join(target, 'Chart.yaml'), chart),
30
29
  write(join(target, 'values.yaml'), values),
31
- fs.copy(TEMPLATES, join(target, 'templates'))
30
+ cp(TEMPLATES, join(target, 'templates'), { recursive: true })
32
31
  ])
33
32
 
34
33
  this.#target = target
35
34
  }
36
35
 
37
- async install (options) {
36
+ async install(options) {
38
37
  if (options.target) this.#target = options.target
39
- if (this.#target === undefined) throw new Error('Deployment hasn\'t been exported')
38
+ if (this.#target === undefined) throw new Error("Deployment hasn't been exported")
40
39
 
41
40
  const args = []
42
41
 
43
42
  if (options.namespace !== undefined) args.push('-n', options.namespace)
44
43
  if (options.wait === true) args.push('--wait')
44
+ if (options.timeout !== undefined) args.push('--timeout', options.timeout)
45
+
46
+ await this.#update()
47
+ await this.#process.execute('helm', [
48
+ 'upgrade',
49
+ this.#chart.name,
50
+ '-i',
51
+ ...args,
52
+ this.#target
53
+ ])
45
54
 
46
- await this.#process.execute('helm', ['dependency', 'update', this.#target])
47
- await this.#process.execute('helm', ['upgrade', this.#chart.name, '-i', ...args, this.#target])
55
+ // ready is not done: the replicas replaced are still draining when helm answers
56
+ if (options.wait === true) await drain(this.#process, options)
48
57
  }
49
58
 
50
- async template (options) {
51
- if (this.#target === undefined) throw new Error('Deployment hasn\'t been exported')
59
+ async template(options) {
60
+ if (this.#target === undefined) throw new Error("Deployment hasn't been exported")
52
61
 
53
- await this.#process.execute('helm', ['dependency', 'update', this.#target], { silently: true })
62
+ await this.#update({ silently: true })
54
63
 
55
64
  const args = []
56
65
 
57
66
  if (options.namespace !== undefined) args.push('-n', options.namespace)
58
67
 
59
- return await this.#process.execute('helm',
68
+ return await this.#process.execute(
69
+ 'helm',
60
70
  ['template', this.#chart.name, ...args, this.#target],
61
- { silently: true })
71
+ { silently: true }
72
+ )
73
+ }
74
+
75
+ /**
76
+ * A chart with no subcharts has nothing to resolve, and the call still reaches out for
77
+ * the repositories it would have read.
78
+ *
79
+ * @param {object} [options]
80
+ * @returns {Promise<void>}
81
+ */
82
+ async #update(options = {}) {
83
+ if (this.#chart.dependencies.length === 0) return
84
+
85
+ await this.#process.execute('helm', ['dependency', 'update', this.#target], options)
62
86
  }
63
87
 
64
- variables () {
88
+ variables() {
65
89
  const variables = []
66
90
  const used = new Set()
67
91
 
68
92
  addVariables(this.#values.compositions, variables, used)
69
93
  addVariables(this.#values.services, variables, used)
70
94
 
95
+ if (this.#values.mono !== undefined)
96
+ addVariables([this.#values.mono], variables, used)
97
+
71
98
  return variables
72
99
  }
73
100
  }
74
101
 
75
- function addVariables (list, variables, used = new Set()) {
102
+ function addVariables(list, variables, used = new Set()) {
76
103
  for (const item of list) {
77
104
  if (item.variables === undefined) continue
78
105
 
@@ -85,6 +112,9 @@ function addVariables (list, variables, used = new Set()) {
85
112
  }
86
113
  }
87
114
 
88
- const TEMPLATES = join(__dirname, 'chart/templates')
115
+ const TEMPLATES = join(import.meta.dirname, 'chart/templates')
89
116
 
90
- exports.Deployment = Deployment
117
+ function dump(object) {
118
+ // js-yaml writes plain objects only, and the values carry locators and images
119
+ return jsyaml.dump(JSON.parse(JSON.stringify(object)), { noRefs: true, lineWidth: -1 })
120
+ }