@toa.io/operations 1.0.0-alpha.28 → 1.0.0-alpha.280
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/CHANGELOG.md +204 -0
- package/image/runtime.Dockerfile +4 -0
- package/package.json +10 -10
- package/readme.md +16 -0
- package/src/deployment/.deployment/.describe/components.js +1 -5
- package/src/deployment/.deployment/.describe/compositions.js +8 -7
- package/src/deployment/.deployment/.describe/dependencies.js +1 -6
- package/src/deployment/.deployment/.describe/events.js +63 -0
- package/src/deployment/.deployment/.describe/index.js +4 -13
- package/src/deployment/.deployment/.describe/mounts.js +20 -0
- package/src/deployment/.deployment/.describe/resources.js +33 -0
- package/src/deployment/.deployment/.describe/services.js +28 -5
- package/src/deployment/.deployment/.describe/services.test.js +61 -0
- package/src/deployment/.deployment/.describe/variables.js +6 -9
- package/src/deployment/.deployment/declare.js +1 -5
- package/src/deployment/.deployment/describe.js +133 -9
- package/src/deployment/.deployment/index.js +3 -9
- package/src/deployment/.deployment/merge.js +46 -6
- package/src/deployment/.deployment/merge.test.js +72 -0
- package/src/deployment/chart/templates/compositions.yaml +70 -0
- package/src/deployment/chart/templates/mono.yaml +181 -0
- package/src/deployment/chart/templates/services.yaml +75 -4
- package/src/deployment/chart/values.yaml +11 -1
- package/src/deployment/composition.js +2 -5
- package/src/deployment/deployment.js +19 -12
- package/src/deployment/factory.js +50 -28
- package/src/deployment/images/composition.Dockerfile +2 -2
- package/src/deployment/images/composition.js +26 -22
- package/src/deployment/images/factory.js +15 -10
- package/src/deployment/images/format.js +48 -0
- package/src/deployment/images/format.test.js +74 -0
- package/src/deployment/images/image.fixtures.js +11 -11
- package/src/deployment/images/image.js +20 -22
- package/src/deployment/images/image.test.js +6 -5
- package/src/deployment/images/index.js +1 -5
- package/src/deployment/images/mono.Dockerfile +18 -0
- package/src/deployment/images/mono.js +72 -0
- package/src/deployment/images/runtime-base.test.js +91 -0
- package/src/deployment/images/service.Dockerfile +3 -3
- package/src/deployment/images/service.js +8 -8
- package/src/deployment/index.js +1 -5
- package/src/deployment/operator.js +6 -6
- package/src/deployment/operator.test.js +8 -7
- package/src/deployment/registry.js +66 -22
- package/src/deployment/registry.test.js +177 -0
- package/src/deployment/service.js +1 -5
- package/src/deployment/workspace.js +13 -8
- package/src/index.js +1 -3
- package/src/process.js +2 -8
- package/src/process.test.js +20 -0
- package/types/_deployment/composition.d.ts +1 -1
- package/types/_deployment/dependency.d.ts +3 -1
- package/types/_deployment/deployment.d.ts +3 -3
- package/types/_deployment/factory.d.ts +2 -2
- package/types/_deployment/images/factory.d.ts +2 -2
- package/types/_deployment/images/image.d.ts +4 -2
- package/types/_deployment/images/registry.d.ts +2 -2
- package/types/_deployment/operator.d.ts +2 -2
- package/types/_deployment/registry.d.ts +10 -8
- package/types/_deployment/service.d.ts +4 -1
- package/types/dependency.ts +73 -0
- package/types/index.ts +1 -0
- package/types/dependency.d.ts +0 -39
- package/types/index.d.ts +0 -1
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { join, posix } from 'node:path'
|
|
2
|
+
import { readFile as read, writeFile as write } from 'node:fs/promises'
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
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')
|
|
5
|
+
import { overwrite } from '@toa.io/generic'
|
|
6
|
+
import { mkdir } from 'node:fs/promises'
|
|
15
7
|
|
|
16
8
|
/**
|
|
17
9
|
* @implements {toa.deployment.images.Image}
|
|
18
10
|
* @abstract
|
|
19
11
|
*/
|
|
20
|
-
class Image {
|
|
12
|
+
export class Image {
|
|
21
13
|
context
|
|
22
14
|
reference
|
|
23
15
|
dockerfile
|
|
@@ -26,9 +18,7 @@ class Image {
|
|
|
26
18
|
#registry
|
|
27
19
|
#runtime
|
|
28
20
|
#values = {
|
|
29
|
-
build: {
|
|
30
|
-
image: 'node:20.9.0-alpine3.18'
|
|
31
|
-
}
|
|
21
|
+
build: {}
|
|
32
22
|
}
|
|
33
23
|
|
|
34
24
|
constructor (scope, runtime, registry) {
|
|
@@ -54,6 +44,8 @@ class Image {
|
|
|
54
44
|
|
|
55
45
|
get base () {}
|
|
56
46
|
|
|
47
|
+
get run () {}
|
|
48
|
+
|
|
57
49
|
async prepare (root) {
|
|
58
50
|
if (this.dockerfile === undefined) throw new Error('Dockerfile isn\'t specified')
|
|
59
51
|
|
|
@@ -65,7 +57,7 @@ class Image {
|
|
|
65
57
|
|
|
66
58
|
const template = await read(this.dockerfile, 'utf-8')
|
|
67
59
|
const contents = template.replace(/{{(\S{1,32})}}/g, (_, key) => this.#value(key))
|
|
68
|
-
const ignore = 'Dockerfile'
|
|
60
|
+
const ignore = ['Dockerfile', '**/node_modules'].join('\r\n')
|
|
69
61
|
|
|
70
62
|
await write(join(path, 'Dockerfile'), contents)
|
|
71
63
|
await write(join(path, '.dockerignore'), ignore)
|
|
@@ -77,13 +69,19 @@ class Image {
|
|
|
77
69
|
|
|
78
70
|
#setValues () {
|
|
79
71
|
this.#values.runtime = this.#runtime
|
|
80
|
-
this.#values.build = overwrite(
|
|
72
|
+
this.#values.build = overwrite({
|
|
73
|
+
image: `${RUNTIME_IMAGE}:${this.#runtime.version}`
|
|
74
|
+
}, this.#registry.build)
|
|
81
75
|
|
|
82
76
|
const image = this.base
|
|
83
77
|
|
|
84
|
-
if (image !== undefined)
|
|
78
|
+
if (image !== undefined)
|
|
85
79
|
this.#values.build.image = image
|
|
86
|
-
|
|
80
|
+
|
|
81
|
+
const run = this.run
|
|
82
|
+
|
|
83
|
+
if (run !== undefined)
|
|
84
|
+
this.#values.build.run = (this.#values.build.run === undefined ? '' : this.#values.build.run + '\n') + run
|
|
87
85
|
|
|
88
86
|
if (this.#values.build.arguments !== undefined) this.#values.build.arguments = createArguments(this.#values.build.arguments)
|
|
89
87
|
if (this.#values.build.run !== undefined) this.#values.build.run = createRunCommands(this.#values.build.run)
|
|
@@ -121,4 +119,4 @@ function createArguments (variables) {
|
|
|
121
119
|
return args.join('\n')
|
|
122
120
|
}
|
|
123
121
|
|
|
124
|
-
|
|
122
|
+
export const RUNTIME_IMAGE = 'ghcr.io/toa-io/runtime'
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
import * as fixtures from './image.fixtures.js'
|
|
5
|
+
import { generate } from 'randomstring'
|
|
5
6
|
|
|
6
7
|
/** @type {toa.deployment.images.Image} */
|
|
7
8
|
let instance
|
|
@@ -13,11 +14,11 @@ beforeEach(() => {
|
|
|
13
14
|
it('should assign url', () => {
|
|
14
15
|
instance.tag()
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
assert.deepStrictEqual(instance.reference, `${fixtures.registry.base}/${fixtures.scope}/${fixtures.name}:${fixtures.version}`)
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
describe('prepare', () => {
|
|
20
21
|
it('should throw error if no dockerfile specified', async () => {
|
|
21
|
-
await
|
|
22
|
+
await assert.rejects(instance.prepare(generate()), (error) => /Dockerfile isn't specified/.test(error.message))
|
|
22
23
|
})
|
|
23
24
|
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
FROM {{build.image}}
|
|
2
|
+
|
|
3
|
+
{{build.arguments}}
|
|
4
|
+
|
|
5
|
+
ENV NODE_ENV=production
|
|
6
|
+
RUN if [ "{{runtime.registry}}" != "" ]; then npm set registry {{runtime.registry}}; fi
|
|
7
|
+
RUN if [ "{{runtime.proxy}}" != "" ]; then npm set proxy {{runtime.proxy}}; fi
|
|
8
|
+
|
|
9
|
+
WORKDIR /composition
|
|
10
|
+
COPY --chown=node:node . /composition
|
|
11
|
+
|
|
12
|
+
{{build.run}}
|
|
13
|
+
|
|
14
|
+
RUN --mount=type=cache,target=/root/.npm \
|
|
15
|
+
for entry in *; do if grep -qs '"dependencies"' "$entry/package.json"; then (cd $entry && npm i --omit=dev); fi; done
|
|
16
|
+
|
|
17
|
+
USER node
|
|
18
|
+
CMD toa mono *
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import fs from 'fs-extra'
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
|
|
5
|
+
import { Image } from './image.js'
|
|
6
|
+
import { declare } from './format.js'
|
|
7
|
+
|
|
8
|
+
export class Mono extends Image {
|
|
9
|
+
dockerfile = join(import.meta.dirname, 'mono.Dockerfile')
|
|
10
|
+
|
|
11
|
+
#image
|
|
12
|
+
#components
|
|
13
|
+
|
|
14
|
+
constructor (scope, runtime, registry, composition) {
|
|
15
|
+
super(scope, runtime, registry)
|
|
16
|
+
|
|
17
|
+
this.#image = composition.image
|
|
18
|
+
this.#components = composition.components
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get name () {
|
|
22
|
+
return 'mono'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get version () {
|
|
26
|
+
const hash = createHash('sha256')
|
|
27
|
+
|
|
28
|
+
for (const component of this.#components) {
|
|
29
|
+
hash.update(component.locator.id)
|
|
30
|
+
hash.update(component.version)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return hash.digest('hex').slice(0, 8)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get base () {
|
|
37
|
+
if (this.#image !== undefined) return this.#image
|
|
38
|
+
|
|
39
|
+
const images = new Set(this.#components.map((component) => component.build?.image))
|
|
40
|
+
|
|
41
|
+
if (images.size > 1)
|
|
42
|
+
throw new Error('Mono deployment requires different base images for its components. Specify base image for the composition in the context.')
|
|
43
|
+
|
|
44
|
+
return images.values().next().value
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get run () {
|
|
48
|
+
const commands = []
|
|
49
|
+
|
|
50
|
+
for (const component of this.#components) {
|
|
51
|
+
const run = component.build?.run
|
|
52
|
+
|
|
53
|
+
if (run !== undefined)
|
|
54
|
+
commands.push(run)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return commands.join('\n')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async prepare (root) {
|
|
61
|
+
const context = await super.prepare(root)
|
|
62
|
+
|
|
63
|
+
for (const component of this.#components) {
|
|
64
|
+
const target = join(context, component.locator.label)
|
|
65
|
+
|
|
66
|
+
await fs.copy(component.path, target)
|
|
67
|
+
await declare(component.path, target, component.locator.label)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return context
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { mkdtemp, readFile } from 'node:fs/promises'
|
|
6
|
+
import { tmpdir } from 'node:os'
|
|
7
|
+
|
|
8
|
+
import { Image, RUNTIME_IMAGE } from './image.js'
|
|
9
|
+
|
|
10
|
+
const compositionDockerfile = join(import.meta.dirname, 'composition.Dockerfile')
|
|
11
|
+
const serviceDockerfile = join(import.meta.dirname, 'service.Dockerfile')
|
|
12
|
+
|
|
13
|
+
class TestImage extends Image {
|
|
14
|
+
dockerfile
|
|
15
|
+
|
|
16
|
+
#name
|
|
17
|
+
#base
|
|
18
|
+
|
|
19
|
+
constructor (runtime, registry, dockerfile, name = 'test', base) {
|
|
20
|
+
super('acme', runtime, registry)
|
|
21
|
+
|
|
22
|
+
this.dockerfile = dockerfile
|
|
23
|
+
this.#name = name
|
|
24
|
+
this.#base = base
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get name () {
|
|
28
|
+
return this.#name
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get version () {
|
|
32
|
+
return 'abcdef12'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get base () {
|
|
36
|
+
return this.#base
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('runtime base image', () => {
|
|
41
|
+
/** @type {string} */
|
|
42
|
+
let root
|
|
43
|
+
|
|
44
|
+
beforeEach(async () => {
|
|
45
|
+
root = await mkdtemp(join(tmpdir(), 'toa-runtime-base-test'))
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should default build.image to version-pinned GHCR runtime image', async () => {
|
|
49
|
+
const runtime = { version: '1.0.0-alpha.232' }
|
|
50
|
+
const image = new TestImage(runtime, {}, compositionDockerfile)
|
|
51
|
+
|
|
52
|
+
const path = await image.prepare(root)
|
|
53
|
+
const dockerfile = await readFile(join(path, 'Dockerfile'), 'utf8')
|
|
54
|
+
|
|
55
|
+
assert.ok(dockerfile.includes(`FROM ${RUNTIME_IMAGE}:1.0.0-alpha.232`))
|
|
56
|
+
assert.doesNotMatch(dockerfile, /npm i -g @toa\.io\/runtime/)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should not install runtime in service Dockerfile template', async () => {
|
|
60
|
+
const runtime = { version: '1.0.0-alpha.99' }
|
|
61
|
+
const image = new TestImage(runtime, {}, serviceDockerfile, 'service-test')
|
|
62
|
+
|
|
63
|
+
const path = await image.prepare(root)
|
|
64
|
+
const dockerfile = await readFile(join(path, 'Dockerfile'), 'utf8')
|
|
65
|
+
|
|
66
|
+
assert.ok(dockerfile.includes(`FROM ${RUNTIME_IMAGE}:1.0.0-alpha.99`))
|
|
67
|
+
assert.doesNotMatch(dockerfile, /npm i -g @toa\.io\/runtime/)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should allow registry.build.image override', async () => {
|
|
71
|
+
const runtime = { version: '1.0.0-alpha.232' }
|
|
72
|
+
const registry = { build: { image: 'node:24.14.0-alpine3.22' } }
|
|
73
|
+
const image = new TestImage(runtime, registry, compositionDockerfile)
|
|
74
|
+
|
|
75
|
+
const path = await image.prepare(root)
|
|
76
|
+
const dockerfile = await readFile(join(path, 'Dockerfile'), 'utf8')
|
|
77
|
+
|
|
78
|
+
assert.ok(dockerfile.includes('FROM node:24.14.0-alpine3.22'))
|
|
79
|
+
assert.ok(!(dockerfile.includes(RUNTIME_IMAGE)))
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should allow composition.image override via base', async () => {
|
|
83
|
+
const runtime = { version: '1.0.0-alpha.232' }
|
|
84
|
+
const image = new TestImage(runtime, {}, compositionDockerfile, 'mono', 'custom.example/base:1')
|
|
85
|
+
|
|
86
|
+
const path = await image.prepare(root)
|
|
87
|
+
const dockerfile = await readFile(join(path, 'Dockerfile'), 'utf8')
|
|
88
|
+
|
|
89
|
+
assert.ok(dockerfile.includes('FROM custom.example/base:1'))
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
FROM
|
|
1
|
+
FROM {{build.image}}
|
|
2
2
|
|
|
3
3
|
ENV NODE_ENV=production
|
|
4
4
|
RUN if [ "{{runtime.registry}}" != "" ]; then npm set registry {{runtime.registry}}; fi
|
|
5
5
|
RUN if [ "{{runtime.proxy}}" != "" ]; then npm set proxy {{runtime.proxy}}; fi
|
|
6
|
-
RUN npm i -g @toa.io/runtime@{{runtime.version}} --omit=dev
|
|
7
6
|
|
|
8
7
|
WORKDIR /service
|
|
9
8
|
COPY --chown=node:node . /service
|
|
10
9
|
|
|
11
|
-
RUN npm
|
|
10
|
+
RUN --mount=type=cache,target=/root/.npm \
|
|
11
|
+
npm i --omit=dev
|
|
12
12
|
|
|
13
13
|
USER node
|
|
14
14
|
CMD toa serve .
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { join, dirname } from 'node:path'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import { Image } from './image.js'
|
|
5
|
+
import fs from 'fs-extra'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
+
// a service is named the way a package is, and its directory is where it lives
|
|
8
|
+
const require = createRequire(import.meta.url)
|
|
7
9
|
|
|
8
|
-
class Service extends Image {
|
|
9
|
-
dockerfile = join(
|
|
10
|
+
export class Service extends Image {
|
|
11
|
+
dockerfile = join(import.meta.dirname, 'service.Dockerfile')
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Used by Dockerfile
|
|
@@ -70,5 +72,3 @@ class Service extends Image {
|
|
|
70
72
|
const find = (reference) => {
|
|
71
73
|
return dirname(require.resolve(join(reference, 'package.json')))
|
|
72
74
|
}
|
|
73
|
-
|
|
74
|
-
exports.Service = Service
|
package/src/deployment/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import * as workspace from './workspace.js'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Operator {
|
|
3
|
+
export class Operator {
|
|
6
4
|
/** @type {toa.deployment.Deployment} */
|
|
7
5
|
#deployment
|
|
8
6
|
|
|
@@ -50,9 +48,11 @@ class Operator {
|
|
|
50
48
|
variables () {
|
|
51
49
|
return this.#deployment.variables()
|
|
52
50
|
}
|
|
51
|
+
|
|
52
|
+
tags () {
|
|
53
|
+
return this.#registry.tags()
|
|
54
|
+
}
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/** @type {toa.deployment.installation.Options} */
|
|
56
58
|
const OPTIONS = { wait: false }
|
|
57
|
-
|
|
58
|
-
exports.Operator = Operator
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, it, beforeEach, mock } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
import { Operator } from '../../src/deployment/operator.js'
|
|
5
|
+
import { generate } from 'randomstring'
|
|
5
6
|
|
|
6
7
|
it('should be', async () => {
|
|
7
|
-
|
|
8
|
+
assert.ok(Operator instanceof Function)
|
|
8
9
|
})
|
|
9
10
|
|
|
10
11
|
/** @type {toa.deployment.Operator} */
|
|
@@ -25,7 +26,7 @@ describe('env', () => {
|
|
|
25
26
|
it('should be', async () => {
|
|
26
27
|
operator = new Operator(deployment, registry)
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
assert.ok(operator.variables instanceof Function)
|
|
29
30
|
})
|
|
30
31
|
|
|
31
32
|
it('should return variables', async () => {
|
|
@@ -33,12 +34,12 @@ describe('env', () => {
|
|
|
33
34
|
|
|
34
35
|
deployment.variables =
|
|
35
36
|
/** @type {typeof toa.deployment.Operator.variables} */
|
|
36
|
-
|
|
37
|
+
mock.fn(() => variables)
|
|
37
38
|
|
|
38
39
|
operator = new Operator(deployment, registry)
|
|
39
40
|
|
|
40
41
|
const output = operator.variables()
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
assert.deepStrictEqual(output, variables)
|
|
43
44
|
})
|
|
44
45
|
})
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const workspace = require('./workspace')
|
|
4
|
-
const { newid } = require('@toa.io/generic')
|
|
1
|
+
import { posix } from 'node:path'
|
|
2
|
+
import * as workspace from './workspace.js'
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* @implements {toa.deployment.Registry}
|
|
8
6
|
*/
|
|
9
|
-
class Registry {
|
|
7
|
+
export class Registry {
|
|
8
|
+
#scope
|
|
9
|
+
|
|
10
10
|
#registry
|
|
11
11
|
|
|
12
12
|
#factory
|
|
@@ -15,7 +15,11 @@ class Registry {
|
|
|
15
15
|
|
|
16
16
|
#images = []
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
/** @type {string | undefined} */
|
|
19
|
+
#builder
|
|
20
|
+
|
|
21
|
+
constructor (scope, registry, factory, process) {
|
|
22
|
+
this.#scope = scope
|
|
19
23
|
this.#registry = registry
|
|
20
24
|
this.#factory = factory
|
|
21
25
|
this.#process = process
|
|
@@ -29,6 +33,10 @@ class Registry {
|
|
|
29
33
|
return this.#create('service', path, service)
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
mono (composition) {
|
|
37
|
+
return this.#create('mono', composition)
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
async prepare (root) {
|
|
33
41
|
const path = await workspace.create('images', root)
|
|
34
42
|
|
|
@@ -40,9 +48,8 @@ class Registry {
|
|
|
40
48
|
async build () {
|
|
41
49
|
await this.prepare()
|
|
42
50
|
|
|
43
|
-
for (const image of this.#images)
|
|
51
|
+
for (const image of this.#images)
|
|
44
52
|
await this.#build(image)
|
|
45
|
-
}
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
async push () {
|
|
@@ -51,8 +58,12 @@ class Registry {
|
|
|
51
58
|
for (const image of this.#images) await this.#push(image)
|
|
52
59
|
}
|
|
53
60
|
|
|
61
|
+
tags () {
|
|
62
|
+
return this.#images.map((image) => image.reference)
|
|
63
|
+
}
|
|
64
|
+
|
|
54
65
|
/**
|
|
55
|
-
* @param {'composition' | 'service'} type
|
|
66
|
+
* @param {'composition' | 'service' | 'mono'} type
|
|
56
67
|
* @param {...any} args
|
|
57
68
|
* @returns {toa.deployment.images.Image}
|
|
58
69
|
*/
|
|
@@ -70,13 +81,17 @@ class Registry {
|
|
|
70
81
|
* @returns {Promise<void>}
|
|
71
82
|
*/
|
|
72
83
|
async #build (image, push = false) {
|
|
84
|
+
if (await this.exists(image.reference)) {
|
|
85
|
+
console.log('Image already exists, skipping:', image.reference)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
const args = ['--context=default', 'buildx', 'build']
|
|
74
90
|
|
|
75
|
-
if (push)
|
|
91
|
+
if (push)
|
|
76
92
|
args.push('--push')
|
|
77
|
-
|
|
93
|
+
else
|
|
78
94
|
args.push('--load')
|
|
79
|
-
}
|
|
80
95
|
|
|
81
96
|
args.push('--tag', image.reference, image.context)
|
|
82
97
|
|
|
@@ -88,14 +103,15 @@ class Registry {
|
|
|
88
103
|
|
|
89
104
|
if (multiarch) {
|
|
90
105
|
const platform = this.#registry.platforms.join(',')
|
|
91
|
-
const builder = await this.#
|
|
106
|
+
const builder = await this.#ensureBuilder()
|
|
92
107
|
|
|
93
108
|
args.push('--platform', platform)
|
|
94
109
|
args.push('--builder', builder)
|
|
95
110
|
|
|
96
|
-
|
|
111
|
+
if (this.#registry.base !== undefined)
|
|
112
|
+
this.#appendCache(args)
|
|
113
|
+
} else
|
|
97
114
|
args.push('--builder', 'default')
|
|
98
|
-
}
|
|
99
115
|
|
|
100
116
|
args.push('--progress', 'plain')
|
|
101
117
|
|
|
@@ -106,16 +122,44 @@ class Registry {
|
|
|
106
122
|
await this.#build(image, true)
|
|
107
123
|
}
|
|
108
124
|
|
|
109
|
-
async
|
|
110
|
-
const
|
|
111
|
-
|
|
125
|
+
async exists (tag) {
|
|
126
|
+
const args = ['manifest', 'inspect', tag]
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
await this.#process.execute('docker', args, { silently: true })
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.log(error.message)
|
|
132
|
+
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async #ensureBuilder () {
|
|
140
|
+
if (this.#builder !== undefined)
|
|
141
|
+
return this.#builder
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await this.#process.execute('docker', ['buildx', 'inspect', BUILDER], { silently: true })
|
|
145
|
+
} catch {
|
|
146
|
+
await this.#process.execute('docker', ['buildx', 'create', '--name', BUILDER, '--bootstrap'])
|
|
147
|
+
}
|
|
112
148
|
|
|
113
|
-
|
|
149
|
+
this.#builder = BUILDER
|
|
114
150
|
|
|
115
|
-
return
|
|
151
|
+
return BUILDER
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {string[]} args
|
|
156
|
+
*/
|
|
157
|
+
#appendCache (args) {
|
|
158
|
+
const ref = posix.join(this.#registry.base, this.#scope, 'buildcache')
|
|
159
|
+
|
|
160
|
+
args.push('--cache-from', `type=registry,ref=${ref}`)
|
|
161
|
+
args.push('--cache-to', `type=registry,ref=${ref},mode=max,image-manifest=true`)
|
|
116
162
|
}
|
|
117
163
|
}
|
|
118
164
|
|
|
119
165
|
const BUILDER = 'toa'
|
|
120
|
-
|
|
121
|
-
exports.Registry = Registry
|