@toa.io/operations 1.0.0-alpha.6 → 1.0.0-alpha.63

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.6",
3
+ "version": "1.0.0-alpha.63",
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.6",
31
- "@toa.io/generic": "1.0.0-alpha.6",
32
- "@toa.io/norm": "1.0.0-alpha.6",
33
- "@toa.io/yaml": "1.0.0-alpha.6",
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.63",
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": "f28d629a9477646e267a8af8479cc1bb10d62c80"
37
+ "gitHead": "9acf69d3135ea69bb9080c4f2f654d119f7b8a82"
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,15 @@ 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 }}
25
34
  ---
26
35
  apiVersion: v1
27
36
  kind: Service
@@ -38,6 +47,7 @@ spec:
38
47
  targetPort: {{ .port }}
39
48
  ---
40
49
  {{- if .ingress }}
50
+ {{- $service := .name }}
41
51
  apiVersion: networking.k8s.io/v1
42
52
  kind: Ingress
43
53
  metadata:
@@ -51,15 +61,17 @@ spec:
51
61
  ingressClassName: {{ .ingress.class }}
52
62
  {{- end }}
53
63
  rules:
54
- - host: {{ required "ingress.host is required" .ingress.host }}
64
+ {{- range .ingress.hosts }}
65
+ - host: {{ . }}
55
66
  http:
56
67
  paths:
57
68
  - path: /
58
69
  pathType: Prefix
59
70
  backend:
60
71
  service:
61
- name: extension-{{ .name }}
72
+ name: extension-{{ $service }}
62
73
  port:
63
74
  number: 8000
75
+ {{- end }}
64
76
  {{- end }}
65
77
  {{- 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
@@ -1,8 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const { join } = require('node:path')
4
- const { hash } = require('@toa.io/generic')
5
4
  const fs = require('fs-extra')
5
+ const { createHash } = require('node:crypto')
6
6
 
7
7
  const { Image } = require('./image')
8
8
 
@@ -26,22 +26,29 @@ class Composition extends Image {
26
26
  }
27
27
 
28
28
  get version () {
29
- const tags = this.#components.map((component) => component.locator.id + ':' + component.version)
29
+ const hash = createHash('sha256')
30
30
 
31
- return hash(tags.join(';'))
31
+ for (const component of this.#components) {
32
+ hash.update(component.locator.id)
33
+ hash.update(component.version)
34
+ }
35
+
36
+ return hash.digest('hex').slice(0, 8)
32
37
  }
33
38
 
34
39
  get base () {
35
- if (this.#image !== undefined)
40
+ if (this.#image !== undefined) {
36
41
  return this.#image
42
+ }
37
43
 
38
44
  let image = null
39
45
 
40
46
  for (const component of this.#components) {
41
47
  const value = component.build?.image
42
48
 
43
- if (image !== null && image !== value)
49
+ if (image !== null && image !== value) {
44
50
  throw new Error(`Composition '${this.#name}' requires different base images for its components. Specify base image for the composition in the context.`)
51
+ }
45
52
 
46
53
  image = value
47
54
  }
@@ -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
@@ -2,9 +2,8 @@
2
2
 
3
3
  const { Image } = require('./image')
4
4
  const { generate } = require('randomstring')
5
- const { hash } = require('@toa.io/generic')
6
5
 
7
- const version = generate()
6
+ const version = '168b04ff'
8
7
  const name = generate()
9
8
 
10
9
  /**
@@ -22,17 +21,17 @@ class Class extends Image {
22
21
 
23
22
  /** @type {toa.norm.context.Runtime} */
24
23
  const runtime = {
25
- version: generate()
24
+ version: '0.0.0'
26
25
  }
27
26
 
28
27
  /** @type {toa.norm.context.Registry} */
29
28
  const registry = {
30
- base: generate()
29
+ base: 'node:alpine'
31
30
  }
32
31
 
33
32
  exports.scope = generate()
34
33
  exports.name = name
35
- exports.version = hash(runtime.version + ';' + version)
34
+ exports.version = 'ba2409fc'
36
35
  exports.Class = Class
37
36
  exports.runtime = runtime
38
37
  exports.registry = registry
@@ -1,10 +1,17 @@
1
1
  'use strict'
2
2
 
3
- const { join, posix } = require('node:path')
4
- const { readFile: read, writeFile: write } = require('node:fs/promises')
5
-
6
- const { hash, overwrite } = require('@toa.io/generic')
7
- const fs = require('fs-extra')
3
+ const {
4
+ join,
5
+ posix
6
+ } = require('node:path')
7
+ const {
8
+ readFile: read,
9
+ writeFile: write
10
+ } = require('node:fs/promises')
11
+ const { createHash } = require('node:crypto')
12
+
13
+ const { overwrite } = require('@toa.io/generic')
14
+ const { mkdir } = require('node:fs/promises')
8
15
 
9
16
  /**
10
17
  * @implements {toa.deployment.images.Image}
@@ -31,7 +38,12 @@ class Image {
31
38
  }
32
39
 
33
40
  tag () {
34
- const tag = hash(this.#runtime?.version + ';' + this.version)
41
+ const hash = createHash('sha256')
42
+
43
+ hash.update(this.#runtime.version)
44
+ hash.update(this.version)
45
+
46
+ const tag = hash.digest('hex').slice(0, 8)
35
47
 
36
48
  this.reference = posix.join(this.#registry.base ?? '', this.#scope, `${this.name}:${tag}`)
37
49
  }
@@ -49,11 +61,11 @@ class Image {
49
61
 
50
62
  const path = join(root, `${this.name}.${this.version}`)
51
63
 
52
- await fs.ensureDir(path)
64
+ await mkdir(path, { recursive: true })
53
65
 
54
66
  const template = await read(this.dockerfile, 'utf-8')
55
67
  const contents = template.replace(/{{(\S{1,32})}}/g, (_, key) => this.#value(key))
56
- const ignore = 'Dockerfile'
68
+ const ignore = ['Dockerfile', '**/node_modules'].join('\r\n')
57
69
 
58
70
  await write(join(path, 'Dockerfile'), contents)
59
71
  await write(join(path, '.dockerignore'), ignore)
@@ -69,8 +81,9 @@ class Image {
69
81
 
70
82
  const image = this.base
71
83
 
72
- if (image !== undefined)
84
+ if (image !== undefined) {
73
85
  this.#values.build.image = image
86
+ }
74
87
 
75
88
  if (this.#values.build.arguments !== undefined) this.#values.build.arguments = createArguments(this.#values.build.arguments)
76
89
  if (this.#values.build.run !== undefined) this.#values.build.run = createRunCommands(this.#values.build.run)
@@ -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
@@ -69,7 +76,11 @@ class Registry {
69
76
  async #build (image, push = false) {
70
77
  const args = ['--context=default', 'buildx', 'build']
71
78
 
72
- if (push) args.push('--push')
79
+ if (push) {
80
+ args.push('--push')
81
+ } else {
82
+ args.push('--load')
83
+ }
73
84
 
74
85
  args.push('--tag', image.reference, image.context)
75
86
 
@@ -81,12 +92,14 @@ class Registry {
81
92
 
82
93
  if (multiarch) {
83
94
  const platform = this.#registry.platforms.join(',')
95
+ const builder = await this.#createBuilder()
84
96
 
85
97
  args.push('--platform', platform)
86
- args.push('--builder', BUILDER)
98
+ args.push('--builder', builder)
87
99
 
88
- await this.#ensureBuilder()
89
- } else args.push('--builder', 'default')
100
+ } else {
101
+ args.push('--builder', 'default')
102
+ }
90
103
 
91
104
  args.push('--progress', 'plain')
92
105
 
@@ -97,20 +110,13 @@ class Registry {
97
110
  await this.#build(image, true)
98
111
  }
99
112
 
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 '))
104
-
105
- if (exists === -1) await this.#createBuilder()
106
- }
107
-
108
113
  async #createBuilder () {
109
- const create = `buildx create --name ${BUILDER} --use`.split(' ')
110
- const bootstrap = 'buildx inspect --bootstrap'.split(' ')
114
+ const name = `toa-${newid()}`
115
+ const create = `buildx create --name ${name} --bootstrap --use`.split(' ')
111
116
 
112
117
  await this.#process.execute('docker', create)
113
- await this.#process.execute('docker', bootstrap)
118
+
119
+ return name
114
120
  }
115
121
  }
116
122
 
@@ -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,47 @@
1
+ export interface Service {
2
+ group: string
3
+ name: string
4
+ version: string
5
+ port: number
6
+ ingress: Ingress
7
+ variables: Variable[]
8
+ components?: string[]
9
+ probe?: Probe
10
+ }
11
+
12
+ export interface Variable {
13
+ name: string
14
+ value?: string
15
+ secret?: {
16
+ name: string
17
+ key: string
18
+ optional?: boolean
19
+ }
20
+ }
21
+
22
+ export type Variables = Record<'global' | string, Variable[]>
23
+ export type Mounts = Record<'global' | string, Mount[]>
24
+
25
+ export interface Dependency {
26
+ services?: Service[]
27
+ variables?: Variables
28
+ mounts?: Mounts
29
+ }
30
+
31
+ interface Ingress {
32
+ hosts: string[]
33
+ class?: string
34
+ annotations?: object
35
+ }
36
+
37
+ interface Probe {
38
+ port: number
39
+ path: string
40
+ delay?: number
41
+ }
42
+
43
+ interface Mount {
44
+ name: string
45
+ path: string
46
+ claim: string
47
+ }
@@ -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