@toa.io/operations 1.0.0-alpha.274 → 1.0.0-alpha.277
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 +20 -0
- package/package.json +8 -7
- package/src/deployment/.deployment/.describe/components.js +1 -5
- package/src/deployment/.deployment/.describe/compositions.js +3 -7
- package/src/deployment/.deployment/.describe/dependencies.js +1 -6
- package/src/deployment/.deployment/.describe/events.js +1 -5
- package/src/deployment/.deployment/.describe/index.js +4 -13
- package/src/deployment/.deployment/.describe/mounts.js +1 -5
- package/src/deployment/.deployment/.describe/resources.js +1 -5
- package/src/deployment/.deployment/.describe/services.js +2 -6
- package/src/deployment/.deployment/.describe/services.test.js +11 -11
- package/src/deployment/.deployment/.describe/variables.js +1 -5
- package/src/deployment/.deployment/declare.js +1 -5
- package/src/deployment/.deployment/describe.js +10 -10
- package/src/deployment/.deployment/index.js +3 -9
- package/src/deployment/.deployment/merge.js +1 -5
- package/src/deployment/.deployment/merge.test.js +10 -12
- package/src/deployment/chart/templates/mono.yaml +4 -0
- package/src/deployment/chart/templates/services.yaml +4 -0
- package/src/deployment/composition.js +1 -5
- package/src/deployment/deployment.js +10 -11
- package/src/deployment/factory.js +28 -22
- package/src/deployment/images/composition.Dockerfile +1 -1
- package/src/deployment/images/composition.js +11 -11
- package/src/deployment/images/factory.js +4 -8
- 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 +7 -18
- package/src/deployment/images/image.test.js +6 -5
- package/src/deployment/images/index.js +1 -5
- package/src/deployment/images/mono.Dockerfile +1 -1
- package/src/deployment/images/mono.js +13 -12
- package/src/deployment/images/runtime-base.test.js +15 -14
- package/src/deployment/images/service.js +8 -8
- package/src/deployment/index.js +1 -5
- package/src/deployment/operator.js +2 -6
- package/src/deployment/operator.test.js +8 -7
- package/src/deployment/registry.js +3 -7
- package/src/deployment/registry.test.js +39 -37
- package/src/deployment/service.js +1 -5
- package/src/deployment/workspace.js +4 -8
- package/src/index.js +1 -3
- package/src/process.js +2 -6
- package/types/_deployment/composition.d.ts +1 -1
- package/types/_deployment/dependency.d.ts +1 -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/registry.d.ts +2 -2
- package/types/_deployment/operator.d.ts +2 -2
- package/types/_deployment/registry.d.ts +2 -2
- package/types/_deployment/service.d.ts +4 -1
- package/types/dependency.ts +2 -0
- package/types/index.ts +1 -1
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { Composition } from './composition.js'
|
|
2
|
+
import { Service } from './service.js'
|
|
3
|
+
import { Mono } from './mono.js'
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
const { Service } = require('./service')
|
|
5
|
-
const { Mono } = require('./mono')
|
|
6
|
-
|
|
7
|
-
class Factory {
|
|
5
|
+
export class Factory {
|
|
8
6
|
/** @type {string} */
|
|
9
7
|
#scope
|
|
10
8
|
|
|
@@ -58,5 +56,3 @@ class Factory {
|
|
|
58
56
|
return instance
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
|
-
|
|
62
|
-
exports.Factory = Factory
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { writeFile } from 'node:fs/promises'
|
|
3
|
+
import { dirname, join, parse } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A component is copied into the image on its own, away from the package that
|
|
7
|
+
* declared what its files are. Node reads a `.js` as CommonJS unless a manifest
|
|
8
|
+
* beside it says otherwise, so the one the component was written under is
|
|
9
|
+
* restated where it lands.
|
|
10
|
+
*
|
|
11
|
+
* A component that ships its own manifest already says so, and is left alone.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} source the component in the workspace
|
|
14
|
+
* @param {string} target where it was copied
|
|
15
|
+
* @param {string} name
|
|
16
|
+
*/
|
|
17
|
+
export async function declare (source, target, name) {
|
|
18
|
+
if (existsSync(join(target, MANIFEST))) return
|
|
19
|
+
|
|
20
|
+
await writeFile(join(target, MANIFEST),
|
|
21
|
+
JSON.stringify({ name, private: true, type: format(source) }, null, 2) + '\n')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The module format a directory's files are read as, which the nearest manifest
|
|
26
|
+
* above it decides.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} directory
|
|
29
|
+
* @returns {'module' | 'commonjs'}
|
|
30
|
+
*/
|
|
31
|
+
export function format (directory) {
|
|
32
|
+
const { root } = parse(directory)
|
|
33
|
+
|
|
34
|
+
let current = directory
|
|
35
|
+
|
|
36
|
+
while (current !== root) {
|
|
37
|
+
const manifest = join(current, MANIFEST)
|
|
38
|
+
|
|
39
|
+
if (existsSync(manifest))
|
|
40
|
+
return JSON.parse(readFileSync(manifest, 'utf8')).type === 'module' ? 'module' : 'commonjs'
|
|
41
|
+
|
|
42
|
+
current = dirname(current)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return 'commonjs'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const MANIFEST = 'package.json'
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { it, describe, before, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import { declare, format } from './format.js'
|
|
8
|
+
|
|
9
|
+
let root
|
|
10
|
+
|
|
11
|
+
before(async () => {
|
|
12
|
+
root = await mkdtemp(join(tmpdir(), 'format-'))
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
after(async () => {
|
|
16
|
+
await rm(root, { recursive: true, force: true })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('format', () => {
|
|
20
|
+
it('should read the nearest manifest above', async () => {
|
|
21
|
+
const workspace = join(root, 'modules')
|
|
22
|
+
const component = join(workspace, 'components', 'one')
|
|
23
|
+
|
|
24
|
+
await mkdir(component, { recursive: true })
|
|
25
|
+
await writeFile(join(workspace, 'package.json'), JSON.stringify({ type: 'module' }))
|
|
26
|
+
|
|
27
|
+
assert.strictEqual(format(component), 'module')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should default to commonjs', async () => {
|
|
31
|
+
const workspace = join(root, 'scripts')
|
|
32
|
+
const component = join(workspace, 'components', 'one')
|
|
33
|
+
|
|
34
|
+
await mkdir(component, { recursive: true })
|
|
35
|
+
await writeFile(join(workspace, 'package.json'), JSON.stringify({ name: 'scripts' }))
|
|
36
|
+
|
|
37
|
+
assert.strictEqual(format(component), 'commonjs')
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('declare', () => {
|
|
42
|
+
it('should state the format the component was written under', async () => {
|
|
43
|
+
const workspace = join(root, 'stated')
|
|
44
|
+
const source = join(workspace, 'components', 'one')
|
|
45
|
+
const target = join(root, 'image', 'one')
|
|
46
|
+
|
|
47
|
+
await mkdir(source, { recursive: true })
|
|
48
|
+
await mkdir(target, { recursive: true })
|
|
49
|
+
await writeFile(join(workspace, 'package.json'), JSON.stringify({ type: 'module' }))
|
|
50
|
+
|
|
51
|
+
await declare(source, target, 'default-one')
|
|
52
|
+
|
|
53
|
+
const manifest = JSON.parse(await readFile(join(target, 'package.json'), 'utf8'))
|
|
54
|
+
|
|
55
|
+
assert.strictEqual(manifest.type, 'module')
|
|
56
|
+
assert.strictEqual(manifest.name, 'default-one')
|
|
57
|
+
assert.strictEqual(manifest.private, true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should leave a manifest the component ships alone', async () => {
|
|
61
|
+
const source = join(root, 'own', 'components', 'one')
|
|
62
|
+
const target = join(root, 'image', 'own')
|
|
63
|
+
|
|
64
|
+
await mkdir(source, { recursive: true })
|
|
65
|
+
await mkdir(target, { recursive: true })
|
|
66
|
+
|
|
67
|
+
const own = JSON.stringify({ name: 'own', dependencies: { matchacho: '0.6.0' } })
|
|
68
|
+
|
|
69
|
+
await writeFile(join(target, 'package.json'), own)
|
|
70
|
+
await declare(source, target, 'default-own')
|
|
71
|
+
|
|
72
|
+
assert.strictEqual(await readFile(join(target, 'package.json'), 'utf8'), own)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { Image } = require('./image')
|
|
4
|
-
const { generate } = require('randomstring')
|
|
1
|
+
import { Image } from './image.js'
|
|
2
|
+
import { generate } from 'randomstring'
|
|
5
3
|
|
|
6
4
|
const version = '168b04ff'
|
|
7
5
|
const name = generate()
|
|
@@ -29,10 +27,12 @@ const registry = {
|
|
|
29
27
|
base: 'node:alpine'
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
export const scope = generate()
|
|
31
|
+
|
|
32
|
+
// distinct from the module's own `version` above
|
|
33
|
+
const published = 'ba2409fc'
|
|
34
|
+
|
|
35
|
+
// the fixture stands in for the global process
|
|
36
|
+
const current = process
|
|
37
|
+
|
|
38
|
+
export { name, Class, runtime, registry, current as process, published as version }
|
|
@@ -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
|
|
@@ -127,7 +119,4 @@ function createArguments (variables) {
|
|
|
127
119
|
return args.join('\n')
|
|
128
120
|
}
|
|
129
121
|
|
|
130
|
-
const RUNTIME_IMAGE = 'ghcr.io/toa-io/runtime'
|
|
131
|
-
|
|
132
|
-
exports.Image = Image
|
|
133
|
-
exports.RUNTIME_IMAGE = RUNTIME_IMAGE
|
|
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
|
})
|
|
@@ -12,7 +12,7 @@ COPY --chown=node:node . /composition
|
|
|
12
12
|
{{build.run}}
|
|
13
13
|
|
|
14
14
|
RUN --mount=type=cache,target=/root/.npm \
|
|
15
|
-
for entry in *; do if
|
|
15
|
+
for entry in *; do if grep -qs '"dependencies"' "$entry/package.json"; then (cd $entry && npm i --omit=dev); fi; done
|
|
16
16
|
|
|
17
17
|
USER node
|
|
18
18
|
CMD toa mono *
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import fs from 'fs-extra'
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const { createHash } = require('node:crypto')
|
|
5
|
+
import { Image } from './image.js'
|
|
6
|
+
import { declare } from './format.js'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Mono extends Image {
|
|
10
|
-
dockerfile = join(__dirname, 'mono.Dockerfile')
|
|
8
|
+
export class Mono extends Image {
|
|
9
|
+
dockerfile = join(import.meta.dirname, 'mono.Dockerfile')
|
|
11
10
|
|
|
12
11
|
#image
|
|
13
12
|
#components
|
|
@@ -61,11 +60,13 @@ class Mono extends Image {
|
|
|
61
60
|
async prepare (root) {
|
|
62
61
|
const context = await super.prepare(root)
|
|
63
62
|
|
|
64
|
-
for (const component of this.#components)
|
|
65
|
-
|
|
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
|
+
}
|
|
66
69
|
|
|
67
70
|
return context
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
|
-
|
|
71
|
-
exports.Mono = Mono
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { mkdtemp, readFile } from 'node:fs/promises'
|
|
6
|
+
import { tmpdir } from 'node:os'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
import { Image, RUNTIME_IMAGE } from './image.js'
|
|
8
9
|
|
|
9
|
-
const compositionDockerfile = join(
|
|
10
|
-
const serviceDockerfile = join(
|
|
10
|
+
const compositionDockerfile = join(import.meta.dirname, 'composition.Dockerfile')
|
|
11
|
+
const serviceDockerfile = join(import.meta.dirname, 'service.Dockerfile')
|
|
11
12
|
|
|
12
13
|
class TestImage extends Image {
|
|
13
14
|
dockerfile
|
|
@@ -51,8 +52,8 @@ describe('runtime base image', () => {
|
|
|
51
52
|
const path = await image.prepare(root)
|
|
52
53
|
const dockerfile = await readFile(join(path, 'Dockerfile'), 'utf8')
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
assert.ok(dockerfile.includes(`FROM ${RUNTIME_IMAGE}:1.0.0-alpha.232`))
|
|
56
|
+
assert.doesNotMatch(dockerfile, /npm i -g @toa\.io\/runtime/)
|
|
56
57
|
})
|
|
57
58
|
|
|
58
59
|
it('should not install runtime in service Dockerfile template', async () => {
|
|
@@ -62,8 +63,8 @@ describe('runtime base image', () => {
|
|
|
62
63
|
const path = await image.prepare(root)
|
|
63
64
|
const dockerfile = await readFile(join(path, 'Dockerfile'), 'utf8')
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
assert.ok(dockerfile.includes(`FROM ${RUNTIME_IMAGE}:1.0.0-alpha.99`))
|
|
67
|
+
assert.doesNotMatch(dockerfile, /npm i -g @toa\.io\/runtime/)
|
|
67
68
|
})
|
|
68
69
|
|
|
69
70
|
it('should allow registry.build.image override', async () => {
|
|
@@ -74,8 +75,8 @@ describe('runtime base image', () => {
|
|
|
74
75
|
const path = await image.prepare(root)
|
|
75
76
|
const dockerfile = await readFile(join(path, 'Dockerfile'), 'utf8')
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
assert.ok(dockerfile.includes('FROM node:24.14.0-alpine3.22'))
|
|
79
|
+
assert.ok(!(dockerfile.includes(RUNTIME_IMAGE)))
|
|
79
80
|
})
|
|
80
81
|
|
|
81
82
|
it('should allow composition.image override via base', async () => {
|
|
@@ -85,6 +86,6 @@ describe('runtime base image', () => {
|
|
|
85
86
|
const path = await image.prepare(root)
|
|
86
87
|
const dockerfile = await readFile(join(path, 'Dockerfile'), 'utf8')
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
assert.ok(dockerfile.includes('FROM custom.example/base:1'))
|
|
89
90
|
})
|
|
90
91
|
})
|
|
@@ -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
|
|
|
@@ -58,5 +56,3 @@ class Operator {
|
|
|
58
56
|
|
|
59
57
|
/** @type {toa.deployment.installation.Options} */
|
|
60
58
|
const OPTIONS = { wait: false }
|
|
61
|
-
|
|
62
|
-
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,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { posix } = require('node:path')
|
|
4
|
-
const workspace = require('./workspace')
|
|
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 {
|
|
10
8
|
#scope
|
|
11
9
|
|
|
12
10
|
#registry
|
|
@@ -165,5 +163,3 @@ class Registry {
|
|
|
165
163
|
}
|
|
166
164
|
|
|
167
165
|
const BUILDER = 'toa'
|
|
168
|
-
|
|
169
|
-
exports.Registry = Registry
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { it, beforeEach, mock } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
import { Registry } from './registry.js'
|
|
4
6
|
|
|
5
7
|
/** @type {toa.operations.Process} */
|
|
6
8
|
let process
|
|
@@ -14,7 +16,7 @@ let images
|
|
|
14
16
|
beforeEach(() => {
|
|
15
17
|
images = []
|
|
16
18
|
process = /** @type {toa.operations.Process} */ {
|
|
17
|
-
execute:
|
|
19
|
+
execute: mock.fn(async (cmd, args) => {
|
|
18
20
|
if (args[0] === 'manifest')
|
|
19
21
|
throw new Error('manifest unknown')
|
|
20
22
|
|
|
@@ -39,25 +41,25 @@ it('should reuse named builder across images', async () => {
|
|
|
39
41
|
|
|
40
42
|
await registry.build()
|
|
41
43
|
|
|
42
|
-
const creates = process.execute.mock.calls.filter(([, args]) =>
|
|
44
|
+
const creates = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
|
|
43
45
|
args[0] === 'buildx' && args[1] === 'create')
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
assert.strictEqual(creates.length, 1)
|
|
48
|
+
assert.deepStrictEqual(creates[0].arguments[1], ['buildx', 'create', '--name', 'toa', '--bootstrap'])
|
|
47
49
|
|
|
48
|
-
const builds = process.execute.mock.calls.filter(([, args]) =>
|
|
50
|
+
const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
|
|
49
51
|
args[0] === '--context=default' && args[1] === 'buildx' && args[2] === 'build')
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
assert.strictEqual(builds.length, 2)
|
|
52
54
|
|
|
53
|
-
for (const [, args] of builds) {
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
for (const { arguments: [, args] } of builds) {
|
|
56
|
+
assert.ok(args.includes('--builder'))
|
|
57
|
+
assert.strictEqual(args[args.indexOf('--builder') + 1], 'toa')
|
|
56
58
|
}
|
|
57
59
|
})
|
|
58
60
|
|
|
59
61
|
it('should not create builder when it already exists', async () => {
|
|
60
|
-
process.execute =
|
|
62
|
+
process.execute = mock.fn(async (cmd, args) => {
|
|
61
63
|
if (args[0] === 'manifest')
|
|
62
64
|
throw new Error('manifest unknown')
|
|
63
65
|
|
|
@@ -70,10 +72,10 @@ it('should not create builder when it already exists', async () => {
|
|
|
70
72
|
|
|
71
73
|
await registry.build()
|
|
72
74
|
|
|
73
|
-
const creates = process.execute.mock.calls.filter(([, args]) =>
|
|
75
|
+
const creates = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
|
|
74
76
|
args[0] === 'buildx' && args[1] === 'create')
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
assert.strictEqual(creates.length, 0)
|
|
77
79
|
})
|
|
78
80
|
|
|
79
81
|
it('should add shared registry cache flags when base is set', async () => {
|
|
@@ -84,16 +86,16 @@ it('should add shared registry cache flags when base is set', async () => {
|
|
|
84
86
|
|
|
85
87
|
await registry.build()
|
|
86
88
|
|
|
87
|
-
const builds = process.execute.mock.calls.filter(([, args]) =>
|
|
89
|
+
const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
|
|
88
90
|
args[0] === '--context=default' && args[2] === 'build')
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
assert.strictEqual(builds.length, 2)
|
|
91
93
|
|
|
92
|
-
for (const [, args] of builds) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
for (const { arguments: [, args] } of builds) {
|
|
95
|
+
assert.ok(args.includes('--cache-from'))
|
|
96
|
+
assert.ok(args.includes('type=registry,ref=example.com/reg/acme/buildcache'))
|
|
97
|
+
assert.ok(args.includes('--cache-to'))
|
|
98
|
+
assert.ok(args.includes('type=registry,ref=example.com/reg/acme/buildcache,mode=max,image-manifest=true'))
|
|
97
99
|
}
|
|
98
100
|
})
|
|
99
101
|
|
|
@@ -104,15 +106,15 @@ it('should omit cache flags when base is not set', async () => {
|
|
|
104
106
|
|
|
105
107
|
await registry.build()
|
|
106
108
|
|
|
107
|
-
const builds = process.execute.mock.calls.filter(([, args]) =>
|
|
109
|
+
const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
|
|
108
110
|
args[0] === '--context=default' && args[2] === 'build')
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
assert.strictEqual(builds.length, 1)
|
|
111
113
|
|
|
112
|
-
const [, args] = builds[0]
|
|
114
|
+
const { arguments: [, args] } = builds[0]
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
assert.ok(!(args.includes('--cache-from')))
|
|
117
|
+
assert.ok(!(args.includes('--cache-to')))
|
|
116
118
|
})
|
|
117
119
|
|
|
118
120
|
it('should use default builder when platforms is null', async () => {
|
|
@@ -122,20 +124,20 @@ it('should use default builder when platforms is null', async () => {
|
|
|
122
124
|
|
|
123
125
|
await registry.build()
|
|
124
126
|
|
|
125
|
-
const builds = process.execute.mock.calls.filter(([, args]) =>
|
|
127
|
+
const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
|
|
126
128
|
args[0] === '--context=default' && args[2] === 'build')
|
|
127
129
|
|
|
128
|
-
|
|
130
|
+
assert.strictEqual(builds.length, 1)
|
|
129
131
|
|
|
130
|
-
const [, args] = builds[0]
|
|
132
|
+
const { arguments: [, args] } = builds[0]
|
|
131
133
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
assert.strictEqual(args[args.indexOf('--builder') + 1], 'default')
|
|
135
|
+
assert.ok(!(args.includes('--cache-from')))
|
|
136
|
+
assert.ok(!(args.includes('--platform')))
|
|
135
137
|
})
|
|
136
138
|
|
|
137
139
|
it('should skip build when image already exists', async () => {
|
|
138
|
-
process.execute =
|
|
140
|
+
process.execute = mock.fn(async () => '')
|
|
139
141
|
|
|
140
142
|
const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
|
|
141
143
|
|
|
@@ -143,11 +145,11 @@ it('should skip build when image already exists', async () => {
|
|
|
143
145
|
|
|
144
146
|
await registry.build()
|
|
145
147
|
|
|
146
|
-
const builds = process.execute.mock.calls.filter(([, args]) =>
|
|
148
|
+
const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
|
|
147
149
|
args[0] === '--context=default' && args[2] === 'build')
|
|
148
150
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
assert.strictEqual(builds.length, 0)
|
|
152
|
+
assert.ok(process.execute.mock.calls.some((call) => call.arguments.length === 3 && isDeepStrictEqual(call.arguments[0], 'docker') && isDeepStrictEqual(call.arguments[1], ['manifest', 'inspect', images[0].reference]) && isDeepStrictEqual(call.arguments[2], { silently: true })))
|
|
151
153
|
})
|
|
152
154
|
|
|
153
155
|
/**
|
|
@@ -166,7 +168,7 @@ function createImage (name) {
|
|
|
166
168
|
const image = /** @type {toa.deployment.images.Image} */ {
|
|
167
169
|
reference: `example.com/reg/acme/${name}:abcdef12`,
|
|
168
170
|
context: `/tmp/${name}`,
|
|
169
|
-
prepare:
|
|
171
|
+
prepare: mock.fn(async () => `/tmp/${name}`)
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
images.push(image)
|