@toa.io/operations 1.0.0-alpha.9 → 1.0.0-alpha.92

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.9",
3
+ "version": "1.0.0-alpha.92",
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.9",
31
- "@toa.io/generic": "1.0.0-alpha.9",
32
- "@toa.io/norm": "1.0.0-alpha.9",
33
- "@toa.io/yaml": "1.0.0-alpha.9",
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.92",
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": "017a3fa22e8c60654c240f8e55908773d44d4ed1"
37
+ "gitHead": "975ee5af6194354efb6dd8c646b22a89c4d96f7a"
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,29 @@ 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 }}
78
+ {{- if .ingress.default }}
79
+ - http:
80
+ paths:
81
+ - path: /
82
+ pathType: Prefix
83
+ backend:
84
+ service:
85
+ name: extension-{{ $service }}
86
+ port:
87
+ number: 8000
88
+ {{- end }}
64
89
  {{- end }}
90
+ ---
65
91
  {{- 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,8 @@ services:
29
32
  port: 8000
30
33
  replicas: 2
31
34
  ingress:
32
- host: dummies.toa.io
35
+ default: true
36
+ hosts: [dummies.toa.io]
33
37
  class: alb
34
38
  annotations:
35
39
  alb.ingress.kubernetes.io/scheme: internet-facing
@@ -41,6 +45,11 @@ services:
41
45
  secret:
42
46
  name: secret-name
43
47
  key: secret-key
48
+ probe:
49
+ port: 8000
50
+ path: /.ready
51
+ delay: 1
52
+
44
53
  proxies:
45
54
  - name: storage-proxy
46
55
  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}
@@ -50,6 +51,10 @@ class Registry {
50
51
  for (const image of this.#images) await this.#push(image)
51
52
  }
52
53
 
54
+ tags () {
55
+ return this.#images.map((image) => image.reference)
56
+ }
57
+
53
58
  /**
54
59
  * @param {'composition' | 'service'} type
55
60
  * @param {...any} args
@@ -69,6 +74,11 @@ class Registry {
69
74
  * @returns {Promise<void>}
70
75
  */
71
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
+
72
82
  const args = ['--context=default', 'buildx', 'build']
73
83
 
74
84
  if (push) {
@@ -87,11 +97,10 @@ class Registry {
87
97
 
88
98
  if (multiarch) {
89
99
  const platform = this.#registry.platforms.join(',')
100
+ const builder = await this.#createBuilder()
90
101
 
91
102
  args.push('--platform', platform)
92
- args.push('--builder', BUILDER)
93
-
94
- await this.#ensureBuilder()
103
+ args.push('--builder', builder)
95
104
  } else {
96
105
  args.push('--builder', 'default')
97
106
  }
@@ -105,20 +114,27 @@ class Registry {
105
114
  await this.#build(image, true)
106
115
  }
107
116
 
108
- async #ensureBuilder () {
109
- const ls = 'buildx ls'.split(' ')
110
- const output = await this.#process.execute('docker', ls, { silently: true })
111
- const exists = output.split('\n').findIndex((line) => line.startsWith('toa '))
117
+ async exists (tag) {
118
+ const args = ['manifest', 'inspect', tag]
112
119
 
113
- if (exists === -1) await this.#createBuilder()
120
+ try {
121
+ await this.#process.execute('docker', args, { silently: true })
122
+ } catch (error) {
123
+ console.log(error.message)
124
+
125
+ return false
126
+ }
127
+
128
+ return true
114
129
  }
115
130
 
116
131
  async #createBuilder () {
117
- const create = `buildx create --name ${BUILDER} --use`.split(' ')
118
- const bootstrap = 'buildx inspect --bootstrap'.split(' ')
132
+ const name = `toa-${newid()}`
133
+ const create = `buildx create --name ${name} --bootstrap --use`.split(' ')
119
134
 
120
135
  await this.#process.execute('docker', create)
121
- await this.#process.execute('docker', bootstrap)
136
+
137
+ return name
122
138
  }
123
139
  }
124
140
 
@@ -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,59 @@
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
+ default?: boolean
44
+ hosts?: string[]
45
+ class?: string
46
+ annotations?: object
47
+ }
48
+
49
+ interface Probe {
50
+ port: number
51
+ path: string
52
+ delay?: number
53
+ }
54
+
55
+ interface Mount {
56
+ name: string
57
+ path: string
58
+ claim: string
59
+ }
@@ -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