@toa.io/operations 1.0.0-alpha.0 → 1.0.0-alpha.102

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.0",
3
+ "version": "1.0.0-alpha.102",
4
4
  "description": "Toa Deployment",
5
5
  "homepage": "https://toa.io",
6
6
  "author": {
@@ -16,21 +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.0",
31
- "@toa.io/generic": "1.0.0-alpha.0",
32
- "@toa.io/yaml": "1.0.0-alpha.0",
33
- "execa": "5.1.1"
30
+ "@toa.io/filesystem": "1.0.0-alpha.93",
31
+ "@toa.io/generic": "1.0.0-alpha.93",
32
+ "@toa.io/norm": "1.0.0-alpha.102",
33
+ "@toa.io/yaml": "1.0.0-alpha.93",
34
+ "execa": "5.1.1",
35
+ "fs-extra": "11.1.1"
34
36
  },
35
- "gitHead": "06c64546f6292cc07c52f74b31415101037f7616"
37
+ "gitHead": "ce5b6e22c1adbec4cc5d4dc5dd8f6bafc41d93d0"
36
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 }}
@@ -8,5 +8,8 @@
8
8
  secretKeyRef:
9
9
  name: {{ .secret.name }}
10
10
  key: {{ .secret.key }}
11
+ {{- if .secret.optional }}
12
+ optional: {{ .secret.optional }}
13
+ {{- end }}
11
14
  {{- end }}
12
15
  {{- 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
@@ -12,12 +16,12 @@ compositions:
12
16
  secret:
13
17
  name: secret-name
14
18
  key: secret-key
19
+ optional: false
15
20
  - name: users
16
21
  image: localhost:5000/composition-users:0.0.0
17
22
  replicas: 3
18
23
  components:
19
24
  - users-users
20
- # TODO: create component services only if sync binding is being used
21
25
  components:
22
26
  - todos-tasks
23
27
  - todos-stats
@@ -28,7 +32,8 @@ services:
28
32
  port: 8000
29
33
  replicas: 2
30
34
  ingress:
31
- host: dummies.toa.io
35
+ default: true
36
+ hosts: [dummies.toa.io]
32
37
  class: alb
33
38
  annotations:
34
39
  alb.ingress.kubernetes.io/scheme: internet-facing
@@ -40,6 +45,11 @@ services:
40
45
  secret:
41
46
  name: secret-name
42
47
  key: secret-key
48
+ probe:
49
+ port: 8000
50
+ path: /.ready
51
+ delay: 1
52
+
43
53
  proxies:
44
54
  - name: storage-proxy
45
55
  target: host.docker.internal
@@ -2,8 +2,8 @@
2
2
 
3
3
  const { join } = require('node:path')
4
4
  const { writeFile: write } = require('node:fs/promises')
5
- const { directory: { copy } } = require('@toa.io/filesystem')
6
5
  const { dump } = require('@toa.io/yaml')
6
+ const fs = require('fs-extra')
7
7
 
8
8
  const { merge, declare, describe } = require('./.deployment')
9
9
 
@@ -28,7 +28,7 @@ class Deployment {
28
28
  await Promise.all([
29
29
  write(join(target, 'Chart.yaml'), chart),
30
30
  write(join(target, 'values.yaml'), values),
31
- copy(TEMPLATES, join(target, 'templates'))
31
+ fs.copy(TEMPLATES, join(target, 'templates'))
32
32
  ])
33
33
 
34
34
  this.#target = target
@@ -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])
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { context: load } = require('@toa.io/norm')
3
4
  const { Process } = require('../process')
4
5
  const { Operator } = require('./operator')
5
6
  const { Factory: ImagesFactory } = require('./images')
@@ -14,7 +15,6 @@ class Factory {
14
15
  #dependencies
15
16
  #registry
16
17
  #process
17
- #extensionComponents = []
18
18
 
19
19
  constructor (context) {
20
20
  this.#context = context
@@ -85,6 +85,12 @@ class Factory {
85
85
 
86
86
  return new Service(service, image)
87
87
  }
88
+
89
+ static async create (path, environment) {
90
+ const context = await load(path, environment)
91
+
92
+ return new Factory(context)
93
+ }
88
94
  }
89
95
 
90
96
  exports.Factory = Factory
@@ -1,4 +1,4 @@
1
- FROM node:20.9.0-alpine3.18
1
+ FROM {{build.image}}
2
2
 
3
3
  {{build.arguments}}
4
4
 
@@ -1,30 +1,23 @@
1
1
  'use strict'
2
2
 
3
3
  const { join } = require('node:path')
4
- const { hash } = require('@toa.io/generic')
5
- const { directory: { copy } } = require('@toa.io/filesystem')
4
+ const fs = require('fs-extra')
5
+ const { createHash } = require('node:crypto')
6
6
 
7
7
  const { Image } = require('./image')
8
8
 
9
9
  class Composition extends Image {
10
10
  dockerfile = join(__dirname, 'composition.Dockerfile')
11
11
 
12
- /** @type {string} */
13
12
  #name
14
-
15
- /** @type {toa.norm.Component[]} */
13
+ #image
16
14
  #components
17
15
 
18
- /**
19
- * @param {string} scope
20
- * @param {toa.norm.context.Runtime} runtime
21
- * @param {toa.norm.context.Registry} registry
22
- * @param {toa.norm.context.Composition} composition
23
- */
24
16
  constructor (scope, runtime, registry, composition) {
25
17
  super(scope, runtime, registry)
26
18
 
27
19
  this.#name = composition.name
20
+ this.#image = composition.image
28
21
  this.#components = composition.components
29
22
  }
30
23
 
@@ -33,16 +26,41 @@ class Composition extends Image {
33
26
  }
34
27
 
35
28
  get version () {
36
- const tags = this.#components.map((component) => component.locator.id + ':' + component.version)
29
+ const hash = createHash('sha256')
30
+
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)
37
+ }
38
+
39
+ get base () {
40
+ if (this.#image !== undefined) {
41
+ return this.#image
42
+ }
43
+
44
+ let image = null
45
+
46
+ for (const component of this.#components) {
47
+ const value = component.build?.image
48
+
49
+ if (image !== null && image !== value) {
50
+ throw new Error(`Composition '${this.#name}' requires different base images for its components. Specify base image for the composition in the context.`)
51
+ }
52
+
53
+ image = value
54
+ }
37
55
 
38
- return hash(tags.join(';'))
56
+ return image ?? undefined
39
57
  }
40
58
 
41
59
  async prepare (root) {
42
60
  const context = await super.prepare(root)
43
61
 
44
62
  for (const component of this.#components) {
45
- await copy(component.path, join(context, component.locator.label))
63
+ await fs.copy(component.path, join(context, component.locator.label))
46
64
  }
47
65
 
48
66
  return 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
@@ -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 { directory } = require('@toa.io/filesystem')
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}
@@ -13,68 +20,52 @@ const { directory } = require('@toa.io/filesystem')
13
20
  class Image {
14
21
  context
15
22
  reference
16
-
17
- /**
18
- * @protected
19
- * @type {string}
20
- */
21
23
  dockerfile
22
24
 
23
- /** @type {string} */
24
25
  #scope
25
-
26
- /** @type {toa.norm.context.Registry} */
27
26
  #registry
28
-
29
- /** @type {toa.norm.context.Runtime} */
30
27
  #runtime
28
+ #values = {
29
+ build: {
30
+ image: 'node:20.9.0-alpine3.18'
31
+ }
32
+ }
31
33
 
32
- /** @type {{ runtime?: Partial<toa.norm.context.Runtime>, build: object }} */
33
- #values = { build: { command: 'echo hello' } }
34
-
35
- /**
36
- * @param {string} scope
37
- * @param {toa.norm.context.Runtime} runtime
38
- * @param {toa.norm.context.Registry} registry
39
- */
40
34
  constructor (scope, runtime, registry) {
41
35
  this.#scope = scope
42
36
  this.#registry = registry
43
37
  this.#runtime = runtime
44
-
45
- this.#setValues()
46
38
  }
47
39
 
48
40
  tag () {
49
- 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)
50
47
 
51
48
  this.reference = posix.join(this.#registry.base ?? '', this.#scope, `${this.name}:${tag}`)
52
49
  }
53
50
 
54
- /**
55
- * @abstract
56
- * @protected
57
- * @type {string}
58
- */
59
51
  get name () {}
60
52
 
61
- /**
62
- * @abstract
63
- * @protected
64
- * @type {string}
65
- */
66
53
  get version () {}
67
54
 
55
+ get base () {}
56
+
68
57
  async prepare (root) {
69
58
  if (this.dockerfile === undefined) throw new Error('Dockerfile isn\'t specified')
70
59
 
60
+ this.#setValues()
61
+
71
62
  const path = join(root, `${this.name}.${this.version}`)
72
63
 
73
- await directory.ensure(path)
64
+ await mkdir(path, { recursive: true })
74
65
 
75
66
  const template = await read(this.dockerfile, 'utf-8')
76
67
  const contents = template.replace(/{{(\S{1,32})}}/g, (_, key) => this.#value(key))
77
- const ignore = 'Dockerfile'
68
+ const ignore = ['Dockerfile', '**/node_modules'].join('\r\n')
78
69
 
79
70
  await write(join(path, 'Dockerfile'), contents)
80
71
  await write(join(path, '.dockerignore'), ignore)
@@ -88,6 +79,12 @@ class Image {
88
79
  this.#values.runtime = this.#runtime
89
80
  this.#values.build = overwrite(this.#values.build, this.#registry.build)
90
81
 
82
+ const image = this.base
83
+
84
+ if (image !== undefined) {
85
+ this.#values.build.image = image
86
+ }
87
+
91
88
  if (this.#values.build.arguments !== undefined) this.#values.build.arguments = createArguments(this.#values.build.arguments)
92
89
  if (this.#values.build.run !== undefined) this.#values.build.run = createRunCommands(this.#values.build.run)
93
90
  }
@@ -3,7 +3,7 @@
3
3
  const { join, dirname } = require('node:path')
4
4
 
5
5
  const { Image } = require('./image')
6
- const { directory: { copy } } = require('@toa.io/filesystem')
6
+ const fs = require('fs-extra')
7
7
 
8
8
  class Service extends Image {
9
9
  dockerfile = join(__dirname, 'service.Dockerfile')
@@ -57,7 +57,7 @@ class Service extends Image {
57
57
  async prepare (root) {
58
58
  const context = await super.prepare(root)
59
59
 
60
- await copy(this.#path, context)
60
+ await fs.copy(this.#path, context)
61
61
 
62
62
  return context
63
63
  }
@@ -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,28 +1,20 @@
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}
7
8
  */
8
9
  class Registry {
9
- /** @type {toa.norm.context.Registry} */
10
10
  #registry
11
11
 
12
- /** @type {toa.deployment.images.Factory} */
13
12
  #factory
14
13
 
15
- /** @type {toa.operations.Process} */
16
14
  #process
17
15
 
18
- /** @type {toa.deployment.images.Image[]} */
19
16
  #images = []
20
17
 
21
- /**
22
- * @param {toa.norm.context.Registry} registry
23
- * @param {toa.deployment.images.Factory} factory
24
- * @param {toa.operations.Process} process
25
- */
26
18
  constructor (registry, factory, process) {
27
19
  this.#registry = registry
28
20
  this.#factory = factory
@@ -48,7 +40,9 @@ class Registry {
48
40
  async build () {
49
41
  await this.prepare()
50
42
 
51
- for (const image of this.#images) await this.#build(image)
43
+ for (const image of this.#images) {
44
+ await this.#build(image)
45
+ }
52
46
  }
53
47
 
54
48
  async push () {
@@ -57,8 +51,12 @@ class Registry {
57
51
  for (const image of this.#images) await this.#push(image)
58
52
  }
59
53
 
54
+ tags () {
55
+ return this.#images.map((image) => image.reference)
56
+ }
57
+
60
58
  /**
61
- * @param {"composition" | "service"} type
59
+ * @param {'composition' | 'service'} type
62
60
  * @param {...any} args
63
61
  * @returns {toa.deployment.images.Image}
64
62
  */
@@ -76,9 +74,18 @@ class Registry {
76
74
  * @returns {Promise<void>}
77
75
  */
78
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
+
79
82
  const args = ['--context=default', 'buildx', 'build']
80
83
 
81
- if (push) args.push('--push')
84
+ if (push) {
85
+ args.push('--push')
86
+ } else {
87
+ args.push('--load')
88
+ }
82
89
 
83
90
  args.push('--tag', image.reference, image.context)
84
91
 
@@ -90,12 +97,15 @@ class Registry {
90
97
 
91
98
  if (multiarch) {
92
99
  const platform = this.#registry.platforms.join(',')
100
+ const builder = await this.#createBuilder()
93
101
 
94
102
  args.push('--platform', platform)
95
- args.push('--builder', BUILDER)
103
+ args.push('--builder', builder)
104
+ } else {
105
+ args.push('--builder', 'default')
106
+ }
96
107
 
97
- await this.#ensureBuilder()
98
- } else args.push('--builder', 'default')
108
+ args.push('--progress', 'plain')
99
109
 
100
110
  await this.#process.execute('docker', args)
101
111
  }
@@ -104,20 +114,27 @@ class Registry {
104
114
  await this.#build(image, true)
105
115
  }
106
116
 
107
- async #ensureBuilder () {
108
- const ls = 'buildx ls'.split(' ')
109
- const output = await this.#process.execute('docker', ls, { silently: true })
110
- const exists = output.split('\n').findIndex((line) => line.startsWith('toa '))
117
+ async exists (tag) {
118
+ const args = ['manifest', 'inspect', tag]
119
+
120
+ try {
121
+ await this.#process.execute('docker', args, { silently: true })
122
+ } catch (error) {
123
+ console.log(error.message)
111
124
 
112
- if (exists === -1) await this.#createBuilder()
125
+ return false
126
+ }
127
+
128
+ return true
113
129
  }
114
130
 
115
131
  async #createBuilder () {
116
- const create = `buildx create --name ${BUILDER} --use`.split(' ')
117
- const bootstrap = 'buildx inspect --bootstrap'.split(' ')
132
+ const name = `toa-${newid()}`
133
+ const create = `buildx create --name ${name} --bootstrap --use`.split(' ')
118
134
 
119
135
  await this.#process.execute('docker', create)
120
- await this.#process.execute('docker', bootstrap)
136
+
137
+ return name
121
138
  }
122
139
  }
123
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,29 +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
- }
22
- }
23
-
24
- export type Variables = Record<'global' | string, Variable[]>
25
-
26
- export type Dependency = {
27
- services?: Service[]
28
- variables?: Variables
29
- }
File without changes