@toa.io/operations 1.0.0-alpha.7 → 1.0.0-alpha.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/operations",
3
- "version": "1.0.0-alpha.7",
3
+ "version": "1.0.0-alpha.72",
4
4
  "description": "Toa Deployment",
5
5
  "homepage": "https://toa.io",
6
6
  "author": {
@@ -16,23 +16,23 @@
16
16
  "url": "https://github.com/toa-io/toa/issues"
17
17
  },
18
18
  "engines": {
19
- "node": ">= 18.0.0"
19
+ "node": ">= 20.0.0"
20
20
  },
21
21
  "publishConfig": {
22
22
  "access": "public"
23
23
  },
24
24
  "main": "src/index.js",
25
- "types": "types/index.d.ts",
25
+ "types": "types/index.ts",
26
26
  "scripts": {
27
27
  "test": "echo \"Error: run tests from root\" && exit 1"
28
28
  },
29
29
  "dependencies": {
30
- "@toa.io/filesystem": "1.0.0-alpha.7",
31
- "@toa.io/generic": "1.0.0-alpha.7",
32
- "@toa.io/norm": "1.0.0-alpha.7",
33
- "@toa.io/yaml": "1.0.0-alpha.7",
30
+ "@toa.io/filesystem": "1.0.0-alpha.63",
31
+ "@toa.io/generic": "1.0.0-alpha.63",
32
+ "@toa.io/norm": "1.0.0-alpha.67",
33
+ "@toa.io/yaml": "1.0.0-alpha.63",
34
34
  "execa": "5.1.1",
35
35
  "fs-extra": "11.1.1"
36
36
  },
37
- "gitHead": "4f5ac0bc342d4b7bd469fbe5c74266f050b55c9f"
37
+ "gitHead": "919b587dfcdfab40d18f0f8bd24b34d8107f811a"
38
38
  }
@@ -1,10 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const { addVariables } = require('./variables')
4
+ const { addMounts } = require('./mounts')
4
5
 
5
- function compositions (compositions, variables) {
6
+ function compositions (compositions, dependency) {
6
7
  for (const composition of compositions) {
7
- addVariables(composition, variables)
8
+ addVariables(composition, dependency.variables)
9
+ addMounts(composition, dependency.mounts)
8
10
  }
9
11
  }
10
12
 
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ function addMounts (composition, mounts) {
4
+ if (mounts === undefined)
5
+ return
6
+
7
+ const used = new Set()
8
+
9
+ for (const [key, mount] of Object.entries(mounts)) {
10
+ if (key !== 'global' && !composition.components?.includes(key))
11
+ continue
12
+
13
+ for (const { name, path, claim } of mount) {
14
+ if (used.has(name))
15
+ continue
16
+
17
+ composition.mounts ??= []
18
+ composition.mounts.push({ name, path, claim })
19
+ used.add(name)
20
+ }
21
+ }
22
+ }
23
+
24
+ exports.addMounts = addMounts
@@ -1,17 +1,18 @@
1
1
  'use strict'
2
2
 
3
- function addVariables (deployment, variables) {
3
+ function addVariables (composition, variables) {
4
4
  const used = new Set()
5
5
 
6
- deployment.variables ??= []
6
+ composition.variables ??= []
7
7
 
8
8
  for (const [key, set] of Object.entries(variables)) {
9
- if (key !== 'global' && !deployment.components?.includes(key)) continue
9
+ if (key !== 'global' && !composition.components?.includes(key))
10
+ continue
10
11
 
11
12
  for (const variable of set) {
12
13
  if (used.has(variable.name)) continue
13
14
 
14
- deployment.variables.push(variable)
15
+ composition.variables.push(variable)
15
16
  used.add(variable.name)
16
17
  }
17
18
  }
@@ -1,24 +1,33 @@
1
1
  'use strict'
2
2
 
3
- const get = require('./.describe')
3
+ const desc = require('./.describe')
4
4
 
5
5
  const describe = (context, compositions, dependency) => {
6
6
  const { services } = dependency
7
7
 
8
8
  dependency.variables.global ??= []
9
- dependency.variables.global.unshift({ name: 'TOA_ENV', value: context.environment })
10
9
 
11
- const components = get.components(compositions)
10
+ dependency.variables.global.unshift(
11
+ {
12
+ name: 'TOA_CONTEXT',
13
+ value: context.name
14
+ }, {
15
+ name: 'TOA_ENV',
16
+ value: context.environment
17
+ }
18
+ )
19
+
20
+ const components = desc.components(compositions)
12
21
  const credentials = context.registry?.credentials
13
22
 
14
- get.compositions(compositions, dependency.variables, context.environment)
15
- get.services(services, dependency.variables)
23
+ desc.compositions(compositions, dependency)
24
+ desc.services(services, dependency.variables)
16
25
 
17
26
  return {
18
27
  compositions,
19
28
  components,
20
29
  services,
21
- credentials,
30
+ credentials
22
31
  }
23
32
  }
24
33
 
@@ -13,14 +13,17 @@ const merge = (dependencies) => {
13
13
  /** @type {toa.deployment.dependency.Variables} */
14
14
  const variables = {}
15
15
 
16
+ const mounts = {}
17
+
16
18
  for (const dependency of dependencies) {
17
19
  if (dependency.references !== undefined) references.push(...dependency.references)
18
20
  if (dependency.services !== undefined) services.push(...dependency.services)
19
21
  if (dependency.proxies !== undefined) proxies.push(...dependency.proxies)
20
22
  if (dependency.variables !== undefined) append(variables, dependency.variables)
23
+ if (dependency.mounts !== undefined) append(mounts, dependency.mounts)
21
24
  }
22
25
 
23
- return { references, services, proxies, variables }
26
+ return { references, services, proxies, variables, mounts }
24
27
  }
25
28
 
26
29
  const append = (merged, variables) => {
@@ -25,9 +25,24 @@ spec:
25
25
  {{- include "env.var" . | indent 12 }}
26
26
  {{- end }}
27
27
  {{- end }}
28
+ {{- if .mounts }}
29
+ volumeMounts:
30
+ {{- range .mounts }}
31
+ - name: {{ .name }}
32
+ mountPath: {{ .path }}
33
+ {{- end }}
34
+ {{- end }}
28
35
  {{- if $.Values.credentials }}
29
36
  imagePullSecrets:
30
37
  - name: {{ $.Values.credentials }}
31
38
  {{- end }}
39
+ {{- if .mounts }}
40
+ volumes:
41
+ {{- range .mounts }}
42
+ - name: {{ .name }}
43
+ persistentVolumeClaim:
44
+ claimName: {{ .claim }}
45
+ {{- end }}
46
+ {{- end }}
32
47
  ---
33
48
  {{- end }}
@@ -22,6 +22,16 @@ spec:
22
22
  {{- include "env.var" . | indent 12 }}
23
23
  {{- end }}
24
24
  {{- end }}
25
+ {{- if .probe }}
26
+ readinessProbe:
27
+ httpGet:
28
+ path: {{ .probe.path }}
29
+ port: {{ .probe.port }}
30
+ {{- if .probe.delay }}
31
+ initialDelaySeconds: {{ .probe.delay }}
32
+ {{- end }}
33
+ {{- end }}
34
+ {{- if .port }}
25
35
  ---
26
36
  apiVersion: v1
27
37
  kind: Service
@@ -36,8 +46,10 @@ spec:
36
46
  protocol: TCP
37
47
  port: {{ .port }}
38
48
  targetPort: {{ .port }}
39
- ---
49
+ {{- end }}
40
50
  {{- if .ingress }}
51
+ {{- $service := .name }}
52
+ ---
41
53
  apiVersion: networking.k8s.io/v1
42
54
  kind: Ingress
43
55
  metadata:
@@ -51,15 +63,18 @@ spec:
51
63
  ingressClassName: {{ .ingress.class }}
52
64
  {{- end }}
53
65
  rules:
54
- - host: {{ required "ingress.host is required" .ingress.host }}
66
+ {{- range .ingress.hosts }}
67
+ - host: {{ . }}
55
68
  http:
56
69
  paths:
57
70
  - path: /
58
71
  pathType: Prefix
59
72
  backend:
60
73
  service:
61
- name: extension-{{ .name }}
74
+ name: extension-{{ $service }}
62
75
  port:
63
76
  number: 8000
77
+ {{- end }}
64
78
  {{- end }}
79
+ ---
65
80
  {{- end }}
@@ -3,6 +3,10 @@ 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
@@ -18,7 +22,6 @@ compositions:
18
22
  replicas: 3
19
23
  components:
20
24
  - users-users
21
- # TODO: create component services only if sync binding is being used
22
25
  components:
23
26
  - todos-tasks
24
27
  - todos-stats
@@ -29,7 +32,7 @@ services:
29
32
  port: 8000
30
33
  replicas: 2
31
34
  ingress:
32
- host: dummies.toa.io
35
+ hosts: [dummies.toa.io]
33
36
  class: alb
34
37
  annotations:
35
38
  alb.ingress.kubernetes.io/scheme: internet-facing
@@ -41,6 +44,11 @@ services:
41
44
  secret:
42
45
  name: secret-name
43
46
  key: secret-key
47
+ probe:
48
+ port: 8000
49
+ path: /.ready
50
+ delay: 1
51
+
44
52
  proxies:
45
53
  - name: storage-proxy
46
54
  target: host.docker.internal
@@ -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])
@@ -15,7 +15,6 @@ class Factory {
15
15
  #dependencies
16
16
  #registry
17
17
  #process
18
- #extensionComponents = []
19
18
 
20
19
  constructor (context) {
21
20
  this.#context = context
@@ -3,9 +3,6 @@
3
3
  const { Composition } = require('./composition')
4
4
  const { Service } = require('./service')
5
5
 
6
- /**
7
- * @implements {toa.deployment.images.Factory}
8
- */
9
6
  class Factory {
10
7
  /** @type {string} */
11
8
  #scope
@@ -65,7 +65,7 @@ class Image {
65
65
 
66
66
  const template = await read(this.dockerfile, 'utf-8')
67
67
  const contents = template.replace(/{{(\S{1,32})}}/g, (_, key) => this.#value(key))
68
- const ignore = 'Dockerfile'
68
+ const ignore = ['Dockerfile', '**/node_modules'].join('\r\n')
69
69
 
70
70
  await write(join(path, 'Dockerfile'), contents)
71
71
  await write(join(path, '.dockerignore'), ignore)
@@ -50,6 +50,10 @@ class Operator {
50
50
  variables () {
51
51
  return this.#deployment.variables()
52
52
  }
53
+
54
+ tags () {
55
+ return this.#registry.tags()
56
+ }
53
57
  }
54
58
 
55
59
  /** @type {toa.deployment.installation.Options} */
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const workspace = require('./workspace')
4
+ const { newid } = require('@toa.io/generic')
4
5
 
5
6
  /**
6
7
  * @implements {toa.deployment.Registry}
@@ -39,7 +40,9 @@ class Registry {
39
40
  async build () {
40
41
  await this.prepare()
41
42
 
42
- for (const image of this.#images) await this.#build(image)
43
+ for (const image of this.#images) {
44
+ await this.#build(image)
45
+ }
43
46
  }
44
47
 
45
48
  async push () {
@@ -48,6 +51,10 @@ class Registry {
48
51
  for (const image of this.#images) await this.#push(image)
49
52
  }
50
53
 
54
+ tags () {
55
+ return this.#images.map((image) => image.reference)
56
+ }
57
+
51
58
  /**
52
59
  * @param {'composition' | 'service'} type
53
60
  * @param {...any} args
@@ -67,9 +74,18 @@ class Registry {
67
74
  * @returns {Promise<void>}
68
75
  */
69
76
  async #build (image, push = false) {
77
+ if (await this.exists(image.reference)) {
78
+ console.log('Image already exists, skipping:', image.reference)
79
+ return
80
+ }
81
+
70
82
  const args = ['--context=default', 'buildx', 'build']
71
83
 
72
- if (push) args.push('--push')
84
+ if (push) {
85
+ args.push('--push')
86
+ } else {
87
+ args.push('--load')
88
+ }
73
89
 
74
90
  args.push('--tag', image.reference, image.context)
75
91
 
@@ -81,12 +97,13 @@ class Registry {
81
97
 
82
98
  if (multiarch) {
83
99
  const platform = this.#registry.platforms.join(',')
100
+ const builder = await this.#createBuilder()
84
101
 
85
102
  args.push('--platform', platform)
86
- args.push('--builder', BUILDER)
87
-
88
- await this.#ensureBuilder()
89
- } else args.push('--builder', 'default')
103
+ args.push('--builder', builder)
104
+ } else {
105
+ args.push('--builder', 'default')
106
+ }
90
107
 
91
108
  args.push('--progress', 'plain')
92
109
 
@@ -97,20 +114,25 @@ class Registry {
97
114
  await this.#build(image, true)
98
115
  }
99
116
 
100
- async #ensureBuilder () {
101
- const ls = 'buildx ls'.split(' ')
102
- const output = await this.#process.execute('docker', ls, { silently: true })
103
- const exists = output.split('\n').findIndex((line) => line.startsWith('toa '))
117
+ async exists (tag) {
118
+ const args = ['inspect', tag]
104
119
 
105
- if (exists === -1) await this.#createBuilder()
120
+ try {
121
+ await this.#process.execute('docker', args, { silently: true })
122
+ } catch {
123
+ return false
124
+ }
125
+
126
+ return true
106
127
  }
107
128
 
108
129
  async #createBuilder () {
109
- const create = `buildx create --name ${BUILDER} --use`.split(' ')
110
- const bootstrap = 'buildx inspect --bootstrap'.split(' ')
130
+ const name = `toa-${newid()}`
131
+ const create = `buildx create --name ${name} --bootstrap --use`.split(' ')
111
132
 
112
133
  await this.#process.execute('docker', create)
113
- await this.#process.execute('docker', bootstrap)
134
+
135
+ return name
114
136
  }
115
137
  }
116
138
 
@@ -4,9 +4,11 @@ declare namespace toa.deployment.images {
4
4
  readonly reference: string
5
5
  readonly context: string
6
6
 
7
- tag(): void
7
+ name: string
8
8
 
9
- prepare(root: string): Promise<string>
9
+ tag (): void
10
+
11
+ prepare (root: string): Promise<string>
10
12
  }
11
13
 
12
14
  }
@@ -1,19 +1,21 @@
1
1
  import type * as _norm from '@toa.io/norm/types'
2
2
  import type * as _dependency from './dependency'
3
- import type * as _image from "./images/image"
3
+ import type * as _image from './images/image'
4
4
 
5
5
  declare namespace toa.deployment {
6
-
6
+
7
7
  interface Registry {
8
- composition(composition: _norm.Composition): _image.Image
8
+ composition (composition: _norm.Composition): _image.Image
9
+
10
+ service (path: string, service: _dependency.Service): _image.Image
9
11
 
10
- service(path: string, service: _dependency.Service): _image.Image
12
+ prepare (path: string): Promise<string>
11
13
 
12
- prepare(path: string): Promise<string>
14
+ build (): Promise<void>
13
15
 
14
- build(): Promise<void>
16
+ push (): Promise<void>
15
17
 
16
- push(): Promise<void>
18
+ tags (): string[]
17
19
  }
18
20
 
19
21
  }
@@ -0,0 +1,58 @@
1
+ import type { Manifest } from '@toa.io/norm'
2
+ import type { Locator } from '@toa.io/core'
3
+
4
+ export interface Service {
5
+ group: string
6
+ name: string
7
+ version: string
8
+ port?: number
9
+ ingress?: Ingress
10
+ variables?: Variable[]
11
+ components?: string[]
12
+ probe?: Probe
13
+ }
14
+
15
+ export interface Variable {
16
+ name: string
17
+ value?: string
18
+ secret?: {
19
+ name: string
20
+ key: string
21
+ optional?: boolean
22
+ }
23
+ }
24
+
25
+ export interface Instance<T> {
26
+ locator: Locator
27
+ manifest: T
28
+ component: Manifest
29
+ }
30
+
31
+ export type Instances<T> = Array<Instance<T>>
32
+
33
+ export type Variables = Record<'global' | string, Variable[]>
34
+ export type Mounts = Record<'global' | string, Mount[]>
35
+
36
+ export interface Dependency {
37
+ services?: Service[]
38
+ variables?: Variables
39
+ mounts?: Mounts
40
+ }
41
+
42
+ interface Ingress {
43
+ hosts: string[]
44
+ class?: string
45
+ annotations?: object
46
+ }
47
+
48
+ interface Probe {
49
+ port: number
50
+ path: string
51
+ delay?: number
52
+ }
53
+
54
+ interface Mount {
55
+ name: string
56
+ path: string
57
+ claim: string
58
+ }
@@ -1,30 +0,0 @@
1
- export type Service = {
2
- group: string
3
- name: string
4
- version: string
5
- port: number
6
- ingress?: {
7
- host: string
8
- class?: string
9
- annotations?: object
10
- }
11
- variables: Variable[]
12
- components?: string[]
13
- }
14
-
15
- export type Variable = {
16
- name: string
17
- value?: string
18
- secret?: {
19
- name: string,
20
- key: string
21
- optional?: boolean
22
- }
23
- }
24
-
25
- export type Variables = Record<'global' | string, Variable[]>
26
-
27
- export type Dependency = {
28
- services?: Service[]
29
- variables?: Variables
30
- }
File without changes