@toa.io/operations 1.0.0-alpha.33 → 1.0.0-alpha.331

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 (78) hide show
  1. package/CHANGELOG.md +378 -0
  2. package/image/runtime.Dockerfile +20 -0
  3. package/package.json +10 -10
  4. package/readme.md +224 -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 +129 -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 +160 -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.configmap.yaml +13 -0
  24. package/src/deployment/chart/templates/components.yaml +4 -3
  25. package/src/deployment/chart/templates/compositions.yaml +109 -4
  26. package/src/deployment/chart/templates/mono.yaml +195 -0
  27. package/src/deployment/chart/templates/services.yaml +97 -7
  28. package/src/deployment/chart/values.yaml +23 -1
  29. package/src/deployment/composition.js +8 -6
  30. package/src/deployment/deployment.js +175 -35
  31. package/src/deployment/drain.js +70 -0
  32. package/src/deployment/drain.test.js +64 -0
  33. package/src/deployment/factory.js +172 -37
  34. package/src/deployment/images/bundle.js +93 -0
  35. package/src/deployment/images/composition.Dockerfile +9 -17
  36. package/src/deployment/images/composition.js +9 -56
  37. package/src/deployment/images/dependencies.Dockerfile +23 -0
  38. package/src/deployment/images/dependencies.js +171 -0
  39. package/src/deployment/images/dependencies.test.js +271 -0
  40. package/src/deployment/images/factory.js +31 -15
  41. package/src/deployment/images/format.js +66 -0
  42. package/src/deployment/images/format.test.js +150 -0
  43. package/src/deployment/images/image.fixtures.js +17 -19
  44. package/src/deployment/images/image.js +79 -38
  45. package/src/deployment/images/image.test.js +11 -5
  46. package/src/deployment/images/index.js +1 -5
  47. package/src/deployment/images/mono.Dockerfile +11 -0
  48. package/src/deployment/images/mono.js +15 -0
  49. package/src/deployment/images/publish.js +91 -0
  50. package/src/deployment/images/runtime-base.test.js +97 -0
  51. package/src/deployment/images/service.Dockerfile +17 -5
  52. package/src/deployment/images/service.js +63 -14
  53. package/src/deployment/index.js +1 -5
  54. package/src/deployment/operator.js +25 -16
  55. package/src/deployment/operator.test.js +98 -7
  56. package/src/deployment/registry.js +210 -46
  57. package/src/deployment/registry.test.js +539 -0
  58. package/src/deployment/secrets.js +92 -0
  59. package/src/deployment/secrets.test.js +180 -0
  60. package/src/deployment/service.js +4 -6
  61. package/src/deployment/workspace.js +13 -8
  62. package/src/index.js +3 -2
  63. package/src/process.js +51 -13
  64. package/src/process.test.js +33 -0
  65. package/types/_deployment/composition.d.ts +2 -3
  66. package/types/_deployment/dependency.d.ts +15 -7
  67. package/types/_deployment/deployment.d.ts +10 -8
  68. package/types/_deployment/factory.d.ts +2 -4
  69. package/types/_deployment/images/factory.d.ts +6 -8
  70. package/types/_deployment/images/image.d.ts +16 -1
  71. package/types/_deployment/images/registry.d.ts +8 -11
  72. package/types/_deployment/operator.d.ts +10 -9
  73. package/types/_deployment/registry.d.ts +7 -4
  74. package/types/_deployment/service.d.ts +4 -3
  75. package/types/dependency.ts +79 -0
  76. package/types/index.ts +1 -0
  77. package/types/dependency.d.ts +0 -39
  78. package/types/index.d.ts +0 -1
@@ -1,20 +1,16 @@
1
- 'use strict'
1
+ export function addVariables(composition, variables, keys = composition.components) {
2
+ composition.variables ??= []
2
3
 
3
- function addVariables (deployment, variables) {
4
- const used = new Set()
5
-
6
- deployment.variables ??= []
4
+ const used = new Set(composition.variables.map((variable) => variable.name))
7
5
 
8
6
  for (const [key, set] of Object.entries(variables)) {
9
- if (key !== 'global' && !deployment.components?.includes(key)) continue
7
+ if (key !== 'global' && !keys?.includes(key)) continue
10
8
 
11
9
  for (const variable of set) {
12
10
  if (used.has(variable.name)) continue
13
11
 
14
- deployment.variables.push(variable)
12
+ composition.variables.push(variable)
15
13
  used.add(variable.name)
16
14
  }
17
15
  }
18
16
  }
19
-
20
- exports.addVariables = addVariables
@@ -1,11 +1,9 @@
1
- 'use strict'
2
-
3
1
  /**
4
2
  * @param {toa.norm.Context} context
5
3
  * @param {toa.deployment.Dependency} dependency
6
4
  * @returns {toa.deployment.Declaration}
7
5
  */
8
- const declare = (context, dependency) => {
6
+ export const declare = (context, dependency) => {
9
7
  const { references } = dependency
10
8
  const { name, description, version } = context
11
9
 
@@ -25,5 +23,3 @@ const DECLARATION = {
25
23
  apiVersion: 'v2',
26
24
  type: 'application'
27
25
  }
28
-
29
- exports.declare = declare
@@ -1,8 +1,15 @@
1
- 'use strict'
1
+ import { gzipSync } from 'node:zlib'
2
+ import { map } from '@toa.io/norm'
3
+ import { MAP_DIRECTORY, MAP_FILE } from '@toa.io/definitions'
2
4
 
3
- const get = require('./.describe')
5
+ import * as desc from './.describe/index.js'
6
+ import { addVariables } from './.describe/variables.js'
7
+ import { addMounts } from './.describe/mounts.js'
8
+ import { resources } from './.describe/resources.js'
9
+ import { events } from './.describe/events.js'
10
+ import { fold } from './.describe/fold.js'
4
11
 
5
- const describe = (context, compositions, dependency) => {
12
+ export const describe = (context, compositions, dependency, image) => {
6
13
  const { services } = dependency
7
14
 
8
15
  dependency.variables.global ??= []
@@ -11,24 +18,167 @@ const describe = (context, compositions, dependency) => {
11
18
  {
12
19
  name: 'TOA_CONTEXT',
13
20
  value: context.name
14
- }, {
21
+ },
22
+ {
15
23
  name: 'TOA_ENV',
16
24
  value: context.environment
17
25
  }
18
26
  )
19
27
 
20
- const components = get.components(compositions)
28
+ const atomicity = context.atomicity
29
+
30
+ if (atomicity?.redis !== undefined) {
31
+ const addresses = Array.isArray(atomicity.redis) ? atomicity.redis : [atomicity.redis]
32
+
33
+ // a lock is taken on `floor(n / 2) + 1` of them, so an even number tolerates no more
34
+ // losses than the odd number below it, and two tolerate fewer than one does
35
+ if (addresses.length % 2 === 0)
36
+ throw new Error(
37
+ `'atomicity.redis' takes an odd number of addresses, ` +
38
+ `${addresses.length} given`
39
+ )
40
+
41
+ dependency.variables.global.push({
42
+ name: 'TOA_ATOMICITY_REDIS',
43
+ value: addresses.join(' ')
44
+ })
45
+ }
46
+
47
+ if (atomicity?.interval !== undefined)
48
+ dependency.variables.global.push({
49
+ name: 'TOA_ATOMICITY_INTERVAL',
50
+ value: String(atomicity.interval)
51
+ })
52
+
53
+ const outbox = context.outbox
54
+
55
+ if (outbox?.interval !== undefined)
56
+ dependency.variables.global.push({
57
+ name: 'TOA_OUTBOX_INTERVAL',
58
+ value: String(outbox.interval)
59
+ })
60
+
61
+ if (outbox?.batch !== undefined)
62
+ dependency.variables.global.push({
63
+ name: 'TOA_OUTBOX_BATCH',
64
+ value: String(outbox.batch)
65
+ })
66
+
67
+ if (outbox?.retention !== undefined)
68
+ dependency.variables.global.push({
69
+ name: 'TOA_OUTBOX_RETENTION',
70
+ value: String(outbox.retention)
71
+ })
72
+
73
+ if (context.inbox?.retention !== undefined)
74
+ dependency.variables.global.push({
75
+ name: 'TOA_INBOX_RETENTION',
76
+ value: String(context.inbox.retention)
77
+ })
78
+
79
+ if (context.addressed?.timeout !== undefined)
80
+ dependency.variables.global.push({
81
+ name: 'TOA_ADDRESSED_TIMEOUT',
82
+ value: String(context.addressed.timeout)
83
+ })
84
+
85
+ events(context, dependency)
86
+
21
87
  const credentials = context.registry?.credentials
22
88
 
23
- get.compositions(compositions, dependency.variables, context.environment)
24
- get.services(services, dependency.variables)
89
+ if (image !== undefined) {
90
+ const mono = unit(context, dependency)
91
+
92
+ mono.image = image.reference
93
+
94
+ const values = {
95
+ compositions: [],
96
+ components: mono.components,
97
+ services: [],
98
+ credentials,
99
+ map: versions(context),
100
+ mono
101
+ }
102
+
103
+ resources(context, values)
104
+
105
+ return values
106
+ }
107
+
108
+ const components = desc.components(compositions)
25
109
 
26
- return {
110
+ desc.compositions(compositions, dependency)
111
+ desc.services(services, dependency.variables, dependency.probe, context.ingress)
112
+
113
+ const values = {
27
114
  compositions,
28
115
  components,
29
116
  services,
30
- credentials
117
+ credentials,
118
+ map: versions(context)
31
119
  }
120
+
121
+ resources(context, values)
122
+
123
+ return values
32
124
  }
33
125
 
34
- exports.describe = describe
126
+ function unit(context, dependency) {
127
+ // an evicted component is deployed by other means, so mono runs it nowhere
128
+ const components = (context.components ?? [])
129
+ .filter((component) => component.evicted !== true)
130
+ .map((component) => component.locator.label)
131
+
132
+ const variables = dependency.variables ?? {}
133
+ const mounts = dependency.mounts ?? {}
134
+
135
+ const mono = {
136
+ replicas: context.mono?.replicas,
137
+ resources: context.mono?.resources,
138
+ components,
139
+ variables: []
140
+ }
141
+
142
+ if (context.ingress !== undefined) mono.ingress = Object.assign({}, context.ingress)
143
+
144
+ addVariables(mono, variables, Object.keys(variables))
145
+ addMounts(mono, dependency.mounts, Object.keys(mounts))
146
+
147
+ fold(mono, dependency.services ?? [], dependency)
148
+
149
+ // mono is the one workload that fronts every service it runs under a single name,
150
+ // so it inherits how they are reached as well as what they run
151
+ for (const service of dependency.services ?? []) {
152
+ // one Service fronts every backend, so its annotations are the union
153
+ if (service.annotations !== undefined)
154
+ mono.annotations = Object.assign(mono.annotations ?? {}, service.annotations)
155
+
156
+ if (service.ingress !== undefined) {
157
+ const { path, hosts, ...ingress } = service.ingress
158
+
159
+ mono.ingress = Object.assign(mono.ingress ?? {}, ingress)
160
+
161
+ // one Ingress serves every path, so its hosts are the union, not the last word
162
+ if (hosts !== undefined)
163
+ mono.ingress.hosts = [...new Set([...(mono.ingress.hosts ?? []), ...hosts])]
164
+ }
165
+ }
166
+
167
+ return mono
168
+ }
169
+
170
+ /**
171
+ * The component map every workload mounts: which version of a component a process asks what that
172
+ * component provides. Named rather than found, because a ConfigMap mounts as a directory.
173
+ *
174
+ * Compressed here rather than in the chart, which renders no gzip, and carried as the bytes it is:
175
+ * a ConfigMap is capped at a megabyte, and this copy is read by a program. `gzipSync` writes a zero
176
+ * timestamp, so a deploy that changed nothing renders the same object.
177
+ *
178
+ * @param {toa.norm.Context} context
179
+ */
180
+ function versions(context) {
181
+ const gzip = gzipSync(Buffer.from(JSON.stringify(map(context)))).toString('base64')
182
+
183
+ return { directory: MAP_DIRECTORY, file: MAP_FILE, gzip }
184
+ }
@@ -1,9 +1,3 @@
1
- 'use strict'
2
-
3
- const { declare } = require('./declare')
4
- const { describe } = require('./describe')
5
- const { merge } = require('./merge')
6
-
7
- exports.declare = declare
8
- exports.describe = describe
9
- exports.merge = merge
1
+ export { declare } from './declare.js'
2
+ export { describe } from './describe.js'
3
+ export { merge } from './merge.js'
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- const merge = (dependencies) => {
1
+ export const merge = (dependencies) => {
4
2
  /** @type {toa.deployment.dependency.Reference[]} */
5
3
  const references = []
6
4
 
@@ -13,14 +11,78 @@ const merge = (dependencies) => {
13
11
  /** @type {toa.deployment.dependency.Variables} */
14
12
  const variables = {}
15
13
 
14
+ /** event labels consumed by something other than a component's own receivers */
15
+ const events = []
16
+
17
+ const mounts = {}
18
+
19
+ /** @type {toa.deployment.dependency.Probe | false | undefined} */
20
+ let probe
21
+
16
22
  for (const dependency of dependencies) {
17
23
  if (dependency.references !== undefined) references.push(...dependency.references)
18
24
  if (dependency.services !== undefined) services.push(...dependency.services)
19
25
  if (dependency.proxies !== undefined) proxies.push(...dependency.proxies)
20
26
  if (dependency.variables !== undefined) append(variables, dependency.variables)
27
+ if (dependency.events !== undefined) events.push(...dependency.events)
28
+ if (dependency.mounts !== undefined) append(mounts, dependency.mounts)
29
+ if (dependency.probe !== undefined) probe = dependency.probe
21
30
  }
22
31
 
23
- return { references, services, proxies, variables }
32
+ reserve(services, probe)
33
+
34
+ return { references, services, proxies, variables, mounts, events, probe }
35
+ }
36
+
37
+ /**
38
+ * A workload that hosts services puts them in one process — `mono`, a composition that
39
+ * declares them, a local run — so within one a port may be claimed once and only once.
40
+ * A service deploying on its own is a pod of its own, and shares a port with nobody.
41
+ */
42
+ const reserve = (services, probe) => {
43
+ /** @type {Map<string, Map<number, string>>} */
44
+ const workloads = new Map()
45
+
46
+ for (const service of services)
47
+ // a service several compositions run binds its ports in each of their pods
48
+ for (const workload of service.workload ?? [service.name]) {
49
+ let claimed = workloads.get(workload)
50
+
51
+ if (claimed === undefined) {
52
+ claimed = new Map()
53
+
54
+ if (probe !== undefined && probe !== false)
55
+ claimed.set(probe.port, 'the readiness probe')
56
+
57
+ workloads.set(workload, claimed)
58
+ }
59
+
60
+ for (const [port, claimant] of ports(service)) {
61
+ const conflicting = claimed.get(port)
62
+
63
+ if (conflicting !== undefined)
64
+ throw new Error(
65
+ `Port ${port} is claimed by both ${conflicting} and ${claimant}` +
66
+ (service.workload === undefined ? '' : ` in '${workload}'`)
67
+ )
68
+
69
+ claimed.set(port, claimant)
70
+ }
71
+ }
72
+ }
73
+
74
+ function* ports(service) {
75
+ // every service reaching here is named the way it is deployed, `<group>-<name>`
76
+ const name = `'${service.name}'`
77
+
78
+ if (service.port !== undefined) yield [service.port, name]
79
+
80
+ if (
81
+ service.probe !== undefined &&
82
+ service.probe !== false &&
83
+ service.probe.port !== service.port
84
+ )
85
+ yield [service.probe.port, `the readiness probe of ${name}`]
24
86
  }
25
87
 
26
88
  const append = (merged, variables) => {
@@ -30,5 +92,3 @@ const append = (merged, variables) => {
30
92
  merged[component].push(...vars)
31
93
  }
32
94
  }
33
-
34
- exports.merge = merge
@@ -0,0 +1,133 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { merge } from './merge.js'
5
+
6
+ // a service is named the way it is deployed by the time it reaches `merge`
7
+ const service = (name, extra = {}) => ({
8
+ group: 'group',
9
+ name: `group-${name}`,
10
+ version: '0',
11
+ ...extra
12
+ })
13
+
14
+ it('should merge services of all dependencies', () => {
15
+ const merged = merge([
16
+ { services: [service('one', { port: 8000 })] },
17
+ { services: [service('two', { port: 8001 })] }
18
+ ])
19
+
20
+ assert.strictEqual(merged.services.length, 2)
21
+ })
22
+
23
+ /*
24
+ * A workload that hosts services puts them in one process, so a port is claimed once
25
+ * within one. Services in separate pods share nothing.
26
+ */
27
+ describe('port reservation', () => {
28
+ const hosted = (name, extra = {}) => service(name, { workload: ['mono'], ...extra })
29
+
30
+ it('should reject two services of one workload claiming one port', () => {
31
+ const dependencies = [
32
+ { services: [hosted('one', { port: 8000 })] },
33
+ { services: [hosted('two', { port: 8000 })] }
34
+ ]
35
+
36
+ assert.throws(
37
+ () => merge(dependencies),
38
+ (error) =>
39
+ /Port 8000 is claimed by both 'group-one' and 'group-two' in 'mono'/.test(
40
+ error.message
41
+ )
42
+ )
43
+ })
44
+
45
+ it('should allow two workloads to claim one port', () => {
46
+ const dependencies = [
47
+ { services: [service('one', { port: 8000, workload: ['edge'] })] },
48
+ { services: [service('two', { port: 8000, workload: ['inner'] })] }
49
+ ]
50
+
51
+ assert.doesNotThrow(() => merge(dependencies))
52
+ })
53
+
54
+ it('should reserve the ports of a service in every workload running it', () => {
55
+ const dependencies = [
56
+ { services: [service('one', { port: 8000, workload: ['edge', 'inner'] })] },
57
+ { services: [service('two', { port: 8000, workload: ['inner'] })] }
58
+ ]
59
+
60
+ assert.throws(
61
+ () => merge(dependencies),
62
+ (error) =>
63
+ /Port 8000 is claimed by both 'group-one' and 'group-two' in 'inner'/.test(
64
+ error.message
65
+ )
66
+ )
67
+ })
68
+
69
+ it('should allow two services deployed on their own to claim one port', () => {
70
+ const dependencies = [
71
+ { services: [service('one', { port: 8000 })] },
72
+ { services: [service('two', { port: 8000 })] }
73
+ ]
74
+
75
+ assert.doesNotThrow(() => merge(dependencies))
76
+ })
77
+
78
+ it('should reject a service claiming the port of the readiness probe', () => {
79
+ const dependencies = [
80
+ { probe: { path: '/.ready', port: 8001 } },
81
+ { services: [service('one', { port: 8001 })] }
82
+ ]
83
+
84
+ assert.throws(
85
+ () => merge(dependencies),
86
+ (error) =>
87
+ /Port 8001 is claimed by both the readiness probe and 'group-one'/.test(
88
+ error.message
89
+ )
90
+ )
91
+ })
92
+
93
+ it('should reject a probe claiming the port of another service of the workload', () => {
94
+ const dependencies = [
95
+ { services: [hosted('one', { port: 8000 })] },
96
+ {
97
+ services: [hosted('two', { port: 8002, probe: { path: '/.ready', port: 8000 } })]
98
+ }
99
+ ]
100
+
101
+ assert.throws(
102
+ () => merge(dependencies),
103
+ (error) =>
104
+ /Port 8000 is claimed by both 'group-one' and the readiness probe of 'group-two' in 'mono'/.test(
105
+ error.message
106
+ )
107
+ )
108
+ })
109
+
110
+ it('should allow a service to probe its own port', () => {
111
+ const dependencies = [
112
+ {
113
+ services: [service('one', { port: 8000, probe: { path: '/.ready', port: 8000 } })]
114
+ }
115
+ ]
116
+
117
+ assert.doesNotThrow(() => merge(dependencies))
118
+ })
119
+
120
+ it('should ignore services without a port', () => {
121
+ const dependencies = [{ services: [service('one'), service('two')] }]
122
+
123
+ assert.doesNotThrow(() => merge(dependencies))
124
+ })
125
+
126
+ it('should ignore a disabled probe', () => {
127
+ const dependencies = [
128
+ { probe: false, services: [service('one', { port: 8000, probe: false })] }
129
+ ]
130
+
131
+ assert.doesNotThrow(() => merge(dependencies))
132
+ })
133
+ })
@@ -0,0 +1,13 @@
1
+ {{- if .Values.map }}
2
+ # What every component of the context provides, by the version this deployment runs of it.
3
+ # Mounted rather than rendered into a workload's environment: a variable would replace every pod
4
+ # of the context on every deployment, and a pod that came up between two of them could not be
5
+ # told. Carried gzipped, because what is read here is read by a program and a ConfigMap is capped
6
+ # at a megabyte: `kubectl exec <pod> -- zcat {{ .Values.map.directory }}/{{ .Values.map.file }}`.
7
+ apiVersion: v1
8
+ kind: ConfigMap
9
+ metadata:
10
+ name: components
11
+ binaryData:
12
+ {{ .Values.map.file }}: {{ .Values.map.gzip | quote }}
13
+ {{- end }}
@@ -6,11 +6,12 @@ metadata:
6
6
  spec:
7
7
  type: ClusterIP
8
8
  selector:
9
- {{ . }}: "1"
9
+ toa/component-{{ . }}: "1"
10
10
  ports:
11
+ # what a streamed call is dialled at; see documentation/ports.md
11
12
  - name: http-binding
12
13
  protocol: TCP
13
- port: 3000
14
- targetPort: 3000
14
+ port: 8005
15
+ targetPort: 8005
15
16
  ---
16
17
  {{- end }}
@@ -5,29 +5,134 @@ 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 or .mounts $.Values.map }}
94
+ volumeMounts:
95
+ {{- range .mounts }}
96
+ - name: {{ .name }}
97
+ mountPath: {{ .path }}
98
+ {{- end }}
99
+ {{- if $.Values.map }}
100
+ - name: components
101
+ mountPath: {{ $.Values.map.directory }}
102
+ readOnly: true
103
+ {{- end }}
104
+ {{- end }}
105
+ lifecycle:
106
+ preStop:
107
+ exec:
108
+ command: [sleep, "5"]
109
+ terminationGracePeriodSeconds: 45
28
110
  {{- if $.Values.credentials }}
29
111
  imagePullSecrets:
30
112
  - name: {{ $.Values.credentials }}
31
113
  {{- end }}
114
+ {{- if or .mounts $.Values.map }}
115
+ volumes:
116
+ {{- range .mounts }}
117
+ - name: {{ .name }}
118
+ persistentVolumeClaim:
119
+ claimName: {{ .claim }}
120
+ {{- end }}
121
+ {{- if $.Values.map }}
122
+ - name: components
123
+ configMap:
124
+ name: components
125
+ {{- end }}
126
+ {{- end }}
127
+ topologySpreadConstraints:
128
+ - maxSkew: 1
129
+ topologyKey: kubernetes.io/hostname
130
+ whenUnsatisfiable: ScheduleAnyway
131
+ labelSelector:
132
+ matchLabels:
133
+ toa/composition: {{ .name }}
134
+ # only this revision's pods: a rollout spreads the new ones, whatever the old ones occupy
135
+ matchLabelKeys:
136
+ - pod-template-hash
32
137
  ---
33
138
  {{- end }}