@toa.io/operations 1.0.0-alpha.284 → 1.0.0-alpha.287
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 +1 -224
- package/package.json +4 -4
- package/readme.md +43 -0
- package/src/deployment/.deployment/.describe/compositions.js +7 -5
- package/src/deployment/.deployment/.describe/events.js +7 -7
- package/src/deployment/.deployment/.describe/fold.js +10 -3
- package/src/deployment/.deployment/.describe/fold.test.js +30 -14
- package/src/deployment/.deployment/.describe/mounts.js +4 -7
- package/src/deployment/.deployment/.describe/resources.js +7 -5
- package/src/deployment/.deployment/.describe/resources.test.js +13 -6
- package/src/deployment/.deployment/.describe/services.js +10 -9
- package/src/deployment/.deployment/.describe/services.test.js +22 -5
- package/src/deployment/.deployment/.describe/variables.js +2 -3
- package/src/deployment/.deployment/describe.js +11 -7
- package/src/deployment/.deployment/merge.js +11 -6
- package/src/deployment/.deployment/merge.test.js +41 -10
- package/src/deployment/composition.js +1 -1
- package/src/deployment/deployment.js +35 -16
- package/src/deployment/factory.js +39 -19
- package/src/deployment/images/composition.Dockerfile +1 -1
- package/src/deployment/images/composition.js +10 -9
- package/src/deployment/images/factory.js +17 -6
- package/src/deployment/images/format.js +9 -5
- package/src/deployment/images/image.fixtures.js +2 -2
- package/src/deployment/images/image.js +38 -21
- package/src/deployment/images/image.test.js +7 -2
- package/src/deployment/images/mono.Dockerfile +1 -1
- package/src/deployment/images/mono.js +10 -9
- package/src/deployment/images/publish.js +98 -0
- package/src/deployment/images/runtime-base.test.js +12 -6
- package/src/deployment/images/service.Dockerfile +1 -1
- package/src/deployment/images/service.js +4 -4
- package/src/deployment/operator.js +8 -8
- package/src/deployment/registry.js +89 -61
- package/src/deployment/registry.test.js +117 -43
- package/src/deployment/service.js +3 -1
- package/src/deployment/workspace.js +1 -1
- package/src/process.js +1 -1
- package/src/process.test.js +8 -4
- package/types/_deployment/composition.d.ts +0 -2
- package/types/_deployment/dependency.d.ts +10 -6
- package/types/_deployment/deployment.d.ts +0 -4
- package/types/_deployment/factory.d.ts +0 -2
- package/types/_deployment/images/factory.d.ts +4 -6
- package/types/_deployment/images/image.d.ts +2 -4
- package/types/_deployment/images/registry.d.ts +7 -10
- package/types/_deployment/operator.d.ts +5 -7
- package/types/_deployment/registry.d.ts +6 -8
- package/types/_deployment/service.d.ts +0 -2
- package/types/dependency.ts +3 -0
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { posix } from 'node:path'
|
|
2
1
|
import * as workspace from './workspace.js'
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* @implements {toa.deployment.Registry}
|
|
6
5
|
*/
|
|
7
6
|
export class Registry {
|
|
8
|
-
#scope
|
|
9
|
-
|
|
10
7
|
#registry
|
|
11
8
|
|
|
12
9
|
#factory
|
|
@@ -15,29 +12,28 @@ export class Registry {
|
|
|
15
12
|
|
|
16
13
|
#images = []
|
|
17
14
|
|
|
18
|
-
/** @type {string | undefined} */
|
|
15
|
+
/** @type {Promise<string> | undefined} */
|
|
19
16
|
#builder
|
|
20
17
|
|
|
21
|
-
constructor
|
|
22
|
-
this.#scope = scope
|
|
18
|
+
constructor(registry, factory, process) {
|
|
23
19
|
this.#registry = registry
|
|
24
20
|
this.#factory = factory
|
|
25
21
|
this.#process = process
|
|
26
22
|
}
|
|
27
23
|
|
|
28
|
-
composition
|
|
24
|
+
composition(composition) {
|
|
29
25
|
return this.#create('composition', composition)
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
service
|
|
28
|
+
service(path, service) {
|
|
33
29
|
return this.#create('service', path, service)
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
mono
|
|
32
|
+
mono(composition) {
|
|
37
33
|
return this.#create('mono', composition)
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
async prepare
|
|
36
|
+
async prepare(root) {
|
|
41
37
|
const path = await workspace.create('images', root)
|
|
42
38
|
|
|
43
39
|
await Promise.all(this.#images.map((image) => image.prepare(path)))
|
|
@@ -45,20 +41,17 @@ export class Registry {
|
|
|
45
41
|
return path
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
async build
|
|
44
|
+
async build() {
|
|
49
45
|
await this.prepare()
|
|
50
|
-
|
|
51
|
-
for (const image of this.#images)
|
|
52
|
-
await this.#build(image)
|
|
46
|
+
await this.#run(false)
|
|
53
47
|
}
|
|
54
48
|
|
|
55
|
-
async push
|
|
49
|
+
async push() {
|
|
56
50
|
await this.prepare()
|
|
57
|
-
|
|
58
|
-
for (const image of this.#images) await this.#push(image)
|
|
51
|
+
await this.#run(true)
|
|
59
52
|
}
|
|
60
53
|
|
|
61
|
-
tags
|
|
54
|
+
tags() {
|
|
62
55
|
return this.#images.map((image) => image.reference)
|
|
63
56
|
}
|
|
64
57
|
|
|
@@ -67,7 +60,7 @@ export class Registry {
|
|
|
67
60
|
* @param {...any} args
|
|
68
61
|
* @returns {toa.deployment.images.Image}
|
|
69
62
|
*/
|
|
70
|
-
#create
|
|
63
|
+
#create(type, ...args) {
|
|
71
64
|
const image = this.#factory[type](...args)
|
|
72
65
|
|
|
73
66
|
this.#images.push(image)
|
|
@@ -75,54 +68,65 @@ export class Registry {
|
|
|
75
68
|
return image
|
|
76
69
|
}
|
|
77
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Every image is probed at once, and what is missing is built concurrently: a build is
|
|
73
|
+
* bound by the registry it uploads to, not by the runner, so one at a time leaves both idle.
|
|
74
|
+
*
|
|
75
|
+
* @param {boolean} push
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
async #run(push) {
|
|
79
|
+
const existing = await Promise.all(
|
|
80
|
+
this.#images.map((image) => this.exists(image.reference))
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const missing = this.#images.filter((image, index) => {
|
|
84
|
+
if (existing[index]) console.log('Image already exists, skipping:', image.reference)
|
|
85
|
+
|
|
86
|
+
return !existing[index]
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await pool(missing, (image) => this.#build(image, push))
|
|
90
|
+
}
|
|
91
|
+
|
|
78
92
|
/**
|
|
79
93
|
* @param {toa.deployment.images.Image} image
|
|
80
94
|
* @param {boolean} [push]
|
|
81
95
|
* @returns {Promise<void>}
|
|
82
96
|
*/
|
|
83
|
-
async #build
|
|
84
|
-
if (await this.exists(image.reference)) {
|
|
85
|
-
console.log('Image already exists, skipping:', image.reference)
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
|
|
97
|
+
async #build(image, push = false) {
|
|
89
98
|
const args = ['--context=default', 'buildx', 'build']
|
|
90
99
|
|
|
91
|
-
if (push)
|
|
92
|
-
|
|
93
|
-
else
|
|
94
|
-
args.push('--load')
|
|
100
|
+
if (push) args.push('--push')
|
|
101
|
+
else args.push('--load')
|
|
95
102
|
|
|
96
103
|
args.push('--tag', image.reference, image.context)
|
|
97
104
|
|
|
98
|
-
const multiarch = this.#registry.platforms !== null
|
|
99
|
-
|
|
100
105
|
if (this.#registry.build?.arguments !== undefined) {
|
|
101
|
-
for (const arg of this.#registry.build.arguments)
|
|
106
|
+
for (const arg of this.#registry.build.arguments)
|
|
107
|
+
args.push('--build-arg', `${arg}=${process.env[arg]}`)
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
const platforms = this.#registry.platforms
|
|
111
|
+
|
|
112
|
+
if (platforms !== null) args.push('--platform', platforms.join(','))
|
|
107
113
|
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
// the container driver is what builds for a platform the runner is not; for a single
|
|
115
|
+
// platform it costs a buildkit image to pull and buys nothing
|
|
116
|
+
if (platforms !== null && platforms.length > 1) {
|
|
117
|
+
args.push('--builder', await this.#ensureBuilder())
|
|
110
118
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
// the driver that produces attestations is this one, and nothing reads them;
|
|
120
|
+
// each is a manifest list to export and push per image
|
|
121
|
+
args.push('--provenance=false')
|
|
122
|
+
} else args.push('--builder', 'default')
|
|
115
123
|
|
|
116
124
|
args.push('--progress', 'plain')
|
|
117
125
|
|
|
118
126
|
await this.#process.execute('docker', args)
|
|
119
127
|
}
|
|
120
128
|
|
|
121
|
-
async
|
|
122
|
-
await this.#build(image, true)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async exists (tag) {
|
|
129
|
+
async exists(tag) {
|
|
126
130
|
const args = ['manifest', 'inspect', tag]
|
|
127
131
|
|
|
128
132
|
try {
|
|
@@ -136,30 +140,54 @@ export class Registry {
|
|
|
136
140
|
return true
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
/** @returns {Promise<string>} */
|
|
144
|
+
#ensureBuilder() {
|
|
145
|
+
// the promise is what is memoized, not its value: concurrent builds ask before any answers
|
|
146
|
+
this.#builder ??= this.#createBuilder()
|
|
147
|
+
|
|
148
|
+
return this.#builder
|
|
149
|
+
}
|
|
142
150
|
|
|
151
|
+
/** @returns {Promise<string>} */
|
|
152
|
+
async #createBuilder() {
|
|
143
153
|
try {
|
|
144
|
-
await this.#process.execute('docker', ['buildx', 'inspect', BUILDER], {
|
|
154
|
+
await this.#process.execute('docker', ['buildx', 'inspect', BUILDER], {
|
|
155
|
+
silently: true
|
|
156
|
+
})
|
|
145
157
|
} catch {
|
|
146
|
-
await this.#process.execute('docker', [
|
|
158
|
+
await this.#process.execute('docker', [
|
|
159
|
+
'buildx',
|
|
160
|
+
'create',
|
|
161
|
+
'--name',
|
|
162
|
+
BUILDER,
|
|
163
|
+
'--bootstrap'
|
|
164
|
+
])
|
|
147
165
|
}
|
|
148
166
|
|
|
149
|
-
this.#builder = BUILDER
|
|
150
|
-
|
|
151
167
|
return BUILDER
|
|
152
168
|
}
|
|
169
|
+
}
|
|
153
170
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
/**
|
|
172
|
+
* @template T
|
|
173
|
+
* @param {T[]} items
|
|
174
|
+
* @param {(item: T) => Promise<void>} work
|
|
175
|
+
* @returns {Promise<void>}
|
|
176
|
+
*/
|
|
177
|
+
async function pool(items, work) {
|
|
178
|
+
const queue = items.slice()
|
|
159
179
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
180
|
+
const workers = Array.from(
|
|
181
|
+
{ length: Math.min(CONCURRENCY, queue.length) },
|
|
182
|
+
async () => {
|
|
183
|
+
while (queue.length > 0) await work(queue.shift())
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
await Promise.all(workers)
|
|
163
188
|
}
|
|
164
189
|
|
|
165
190
|
const BUILDER = 'toa'
|
|
191
|
+
|
|
192
|
+
/** How many builds run at once. Past this the uploads contend for the same uplink. */
|
|
193
|
+
const CONCURRENCY = 3
|
|
@@ -17,8 +17,7 @@ beforeEach(() => {
|
|
|
17
17
|
images = []
|
|
18
18
|
process = /** @type {toa.operations.Process} */ {
|
|
19
19
|
execute: mock.fn(async (cmd, args) => {
|
|
20
|
-
if (args[0] === 'manifest')
|
|
21
|
-
throw new Error('manifest unknown')
|
|
20
|
+
if (args[0] === 'manifest') throw new Error('manifest unknown')
|
|
22
21
|
|
|
23
22
|
if (args[0] === 'buildx' && args[1] === 'inspect')
|
|
24
23
|
throw new Error('builder not found')
|
|
@@ -29,30 +28,45 @@ beforeEach(() => {
|
|
|
29
28
|
|
|
30
29
|
factory = /** @type {toa.deployment.images.Factory} */ {
|
|
31
30
|
composition: () => createImage('composition-mono'),
|
|
32
|
-
service: () => createImage('extension-realtime')
|
|
31
|
+
service: () => createImage('extension-realtime'),
|
|
32
|
+
mono: () => createImage('mono')
|
|
33
33
|
}
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
it('should reuse named builder across images', async () => {
|
|
37
|
-
const registry = createRegistry({
|
|
37
|
+
const registry = createRegistry({
|
|
38
|
+
base: 'example.com/reg',
|
|
39
|
+
platforms: ['linux/amd64', 'linux/arm64']
|
|
40
|
+
})
|
|
38
41
|
|
|
39
42
|
registry.composition(/** @type {any} */ ({}))
|
|
40
43
|
registry.service('.', /** @type {any} */ ({}))
|
|
41
44
|
|
|
42
45
|
await registry.build()
|
|
43
46
|
|
|
44
|
-
const creates = process.execute.mock.calls.filter(
|
|
45
|
-
args[0] === 'buildx' && args[1] === 'create'
|
|
47
|
+
const creates = process.execute.mock.calls.filter(
|
|
48
|
+
({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
|
|
49
|
+
)
|
|
46
50
|
|
|
47
51
|
assert.strictEqual(creates.length, 1)
|
|
48
|
-
assert.deepStrictEqual(creates[0].arguments[1], [
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
assert.deepStrictEqual(creates[0].arguments[1], [
|
|
53
|
+
'buildx',
|
|
54
|
+
'create',
|
|
55
|
+
'--name',
|
|
56
|
+
'toa',
|
|
57
|
+
'--bootstrap'
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
const builds = process.execute.mock.calls.filter(
|
|
61
|
+
({ arguments: [, args] }) =>
|
|
62
|
+
args[0] === '--context=default' && args[1] === 'buildx' && args[2] === 'build'
|
|
63
|
+
)
|
|
52
64
|
|
|
53
65
|
assert.strictEqual(builds.length, 2)
|
|
54
66
|
|
|
55
|
-
for (const {
|
|
67
|
+
for (const {
|
|
68
|
+
arguments: [, args]
|
|
69
|
+
} of builds) {
|
|
56
70
|
assert.ok(args.includes('--builder'))
|
|
57
71
|
assert.strictEqual(args[args.indexOf('--builder') + 1], 'toa')
|
|
58
72
|
}
|
|
@@ -60,25 +74,28 @@ it('should reuse named builder across images', async () => {
|
|
|
60
74
|
|
|
61
75
|
it('should not create builder when it already exists', async () => {
|
|
62
76
|
process.execute = mock.fn(async (cmd, args) => {
|
|
63
|
-
if (args[0] === 'manifest')
|
|
64
|
-
throw new Error('manifest unknown')
|
|
77
|
+
if (args[0] === 'manifest') throw new Error('manifest unknown')
|
|
65
78
|
|
|
66
79
|
return ''
|
|
67
80
|
})
|
|
68
81
|
|
|
69
|
-
const registry = createRegistry({
|
|
82
|
+
const registry = createRegistry({
|
|
83
|
+
base: 'example.com/reg',
|
|
84
|
+
platforms: ['linux/amd64', 'linux/arm64']
|
|
85
|
+
})
|
|
70
86
|
|
|
71
87
|
registry.composition(/** @type {any} */ ({}))
|
|
72
88
|
|
|
73
89
|
await registry.build()
|
|
74
90
|
|
|
75
|
-
const creates = process.execute.mock.calls.filter(
|
|
76
|
-
args[0] === 'buildx' && args[1] === 'create'
|
|
91
|
+
const creates = process.execute.mock.calls.filter(
|
|
92
|
+
({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
|
|
93
|
+
)
|
|
77
94
|
|
|
78
95
|
assert.strictEqual(creates.length, 0)
|
|
79
96
|
})
|
|
80
97
|
|
|
81
|
-
it('should
|
|
98
|
+
it('should not export a build cache', async () => {
|
|
82
99
|
const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
|
|
83
100
|
|
|
84
101
|
registry.composition(/** @type {any} */ ({}))
|
|
@@ -86,35 +103,77 @@ it('should add shared registry cache flags when base is set', async () => {
|
|
|
86
103
|
|
|
87
104
|
await registry.build()
|
|
88
105
|
|
|
89
|
-
const builds = process.execute.mock.calls.filter(
|
|
90
|
-
args[0] === '--context=default' && args[2] === 'build'
|
|
106
|
+
const builds = process.execute.mock.calls.filter(
|
|
107
|
+
({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
|
|
108
|
+
)
|
|
91
109
|
|
|
92
110
|
assert.strictEqual(builds.length, 2)
|
|
93
111
|
|
|
94
|
-
for (const {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
assert.ok(args.includes('--cache-
|
|
98
|
-
assert.ok(args.includes('
|
|
112
|
+
for (const {
|
|
113
|
+
arguments: [, args]
|
|
114
|
+
} of builds) {
|
|
115
|
+
assert.ok(!args.includes('--cache-from'))
|
|
116
|
+
assert.ok(!args.includes('--cache-to'))
|
|
99
117
|
}
|
|
100
118
|
})
|
|
101
119
|
|
|
102
|
-
it('should
|
|
103
|
-
const registry = createRegistry({ platforms: ['linux/amd64'] })
|
|
120
|
+
it('should use the default builder for a single platform', async () => {
|
|
121
|
+
const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
|
|
104
122
|
|
|
105
123
|
registry.composition(/** @type {any} */ ({}))
|
|
106
124
|
|
|
107
125
|
await registry.build()
|
|
108
126
|
|
|
109
|
-
const
|
|
110
|
-
args[0] === '
|
|
127
|
+
const creates = process.execute.mock.calls.filter(
|
|
128
|
+
({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
|
|
129
|
+
)
|
|
111
130
|
|
|
112
|
-
assert.strictEqual(
|
|
131
|
+
assert.strictEqual(creates.length, 0)
|
|
132
|
+
|
|
133
|
+
const {
|
|
134
|
+
arguments: [, args]
|
|
135
|
+
} = process.execute.mock.calls.find(
|
|
136
|
+
({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
assert.strictEqual(args[args.indexOf('--builder') + 1], 'default')
|
|
140
|
+
assert.ok(args.includes('--platform'))
|
|
141
|
+
assert.strictEqual(args[args.indexOf('--platform') + 1], 'linux/amd64')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should create the builder once when images build concurrently', async () => {
|
|
145
|
+
const registry = createRegistry({
|
|
146
|
+
base: 'example.com/reg',
|
|
147
|
+
platforms: ['linux/amd64', 'linux/arm64']
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
registry.composition(/** @type {any} */ ({}))
|
|
151
|
+
registry.service('.', /** @type {any} */ ({}))
|
|
152
|
+
registry.mono(/** @type {any} */ ({}))
|
|
153
|
+
|
|
154
|
+
await registry.build()
|
|
155
|
+
|
|
156
|
+
const creates = process.execute.mock.calls.filter(
|
|
157
|
+
({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
assert.strictEqual(creates.length, 1)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should probe every image before building any', async () => {
|
|
164
|
+
const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
|
|
165
|
+
|
|
166
|
+
registry.composition(/** @type {any} */ ({}))
|
|
167
|
+
registry.service('.', /** @type {any} */ ({}))
|
|
168
|
+
|
|
169
|
+
await registry.build()
|
|
113
170
|
|
|
114
|
-
const { arguments: [, args] }
|
|
171
|
+
const calls = process.execute.mock.calls.map(({ arguments: [, args] }) => args[0])
|
|
172
|
+
const build = calls.indexOf('--context=default')
|
|
173
|
+
const probed = calls.slice(0, build).filter((argument) => argument === 'manifest')
|
|
115
174
|
|
|
116
|
-
|
|
117
|
-
assert.
|
|
175
|
+
// both probes are spent before the first build starts, rather than one before each
|
|
176
|
+
assert.strictEqual(probed.length, 2)
|
|
118
177
|
})
|
|
119
178
|
|
|
120
179
|
it('should use default builder when platforms is null', async () => {
|
|
@@ -124,16 +183,18 @@ it('should use default builder when platforms is null', async () => {
|
|
|
124
183
|
|
|
125
184
|
await registry.build()
|
|
126
185
|
|
|
127
|
-
const builds = process.execute.mock.calls.filter(
|
|
128
|
-
args[0] === '--context=default' && args[2] === 'build'
|
|
186
|
+
const builds = process.execute.mock.calls.filter(
|
|
187
|
+
({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
|
|
188
|
+
)
|
|
129
189
|
|
|
130
190
|
assert.strictEqual(builds.length, 1)
|
|
131
191
|
|
|
132
|
-
const {
|
|
192
|
+
const {
|
|
193
|
+
arguments: [, args]
|
|
194
|
+
} = builds[0]
|
|
133
195
|
|
|
134
196
|
assert.strictEqual(args[args.indexOf('--builder') + 1], 'default')
|
|
135
|
-
assert.ok(!
|
|
136
|
-
assert.ok(!(args.includes('--platform')))
|
|
197
|
+
assert.ok(!args.includes('--platform'))
|
|
137
198
|
})
|
|
138
199
|
|
|
139
200
|
it('should skip build when image already exists', async () => {
|
|
@@ -145,26 +206,39 @@ it('should skip build when image already exists', async () => {
|
|
|
145
206
|
|
|
146
207
|
await registry.build()
|
|
147
208
|
|
|
148
|
-
const builds = process.execute.mock.calls.filter(
|
|
149
|
-
args[0] === '--context=default' && args[2] === 'build'
|
|
209
|
+
const builds = process.execute.mock.calls.filter(
|
|
210
|
+
({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
|
|
211
|
+
)
|
|
150
212
|
|
|
151
213
|
assert.strictEqual(builds.length, 0)
|
|
152
|
-
assert.ok(
|
|
214
|
+
assert.ok(
|
|
215
|
+
process.execute.mock.calls.some(
|
|
216
|
+
(call) =>
|
|
217
|
+
call.arguments.length === 3 &&
|
|
218
|
+
isDeepStrictEqual(call.arguments[0], 'docker') &&
|
|
219
|
+
isDeepStrictEqual(call.arguments[1], [
|
|
220
|
+
'manifest',
|
|
221
|
+
'inspect',
|
|
222
|
+
images[0].reference
|
|
223
|
+
]) &&
|
|
224
|
+
isDeepStrictEqual(call.arguments[2], { silently: true })
|
|
225
|
+
)
|
|
226
|
+
)
|
|
153
227
|
})
|
|
154
228
|
|
|
155
229
|
/**
|
|
156
230
|
* @param {object} [registry]
|
|
157
231
|
* @returns {Registry}
|
|
158
232
|
*/
|
|
159
|
-
function createRegistry
|
|
160
|
-
return new Registry(
|
|
233
|
+
function createRegistry(registry = {}) {
|
|
234
|
+
return new Registry(registry, factory, process)
|
|
161
235
|
}
|
|
162
236
|
|
|
163
237
|
/**
|
|
164
238
|
* @param {string} name
|
|
165
239
|
* @returns {toa.deployment.images.Image}
|
|
166
240
|
*/
|
|
167
|
-
function createImage
|
|
241
|
+
function createImage(name) {
|
|
168
242
|
const image = /** @type {toa.deployment.images.Image} */ {
|
|
169
243
|
reference: `example.com/reg/acme/${name}:abcdef12`,
|
|
170
244
|
context: `/tmp/${name}`,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// a service is what a deployment carries, and it is constructed
|
|
2
|
+
// oxlint-disable-next-line typescript/no-extraneous-class
|
|
1
3
|
export class Service {
|
|
2
|
-
constructor
|
|
4
|
+
constructor(service, image) {
|
|
3
5
|
Object.assign(this, service)
|
|
4
6
|
|
|
5
7
|
this.name = service.group + '-' + service.name
|
|
@@ -7,7 +7,7 @@ import { tmpdir } from 'node:os'
|
|
|
7
7
|
* @param {string} [path]
|
|
8
8
|
* @return {Promise<string>}
|
|
9
9
|
*/
|
|
10
|
-
export async function create
|
|
10
|
+
export async function create(type, path) {
|
|
11
11
|
if (path === undefined) return await mkdtemp(join(tmpdir(), 'toa-' + type))
|
|
12
12
|
|
|
13
13
|
path = resolve(path)
|
package/src/process.js
CHANGED
|
@@ -4,7 +4,7 @@ import { execa } from 'execa'
|
|
|
4
4
|
* @implements {toa.operations.Process}
|
|
5
5
|
*/
|
|
6
6
|
export class Process {
|
|
7
|
-
async execute
|
|
7
|
+
async execute(cmd, args, options = {}) {
|
|
8
8
|
console.log('toa>', cmd, args.join(' '))
|
|
9
9
|
|
|
10
10
|
const command = execa(cmd, args)
|
package/src/process.test.js
CHANGED
|
@@ -7,14 +7,18 @@ describe('execute', () => {
|
|
|
7
7
|
it('should answer what the command wrote', async () => {
|
|
8
8
|
// `execa` is a named export, and calling the module namespace instead answers
|
|
9
9
|
// `execa is not a function` — where only a deployment would have found out
|
|
10
|
-
const output = await new Process().execute(
|
|
11
|
-
|
|
10
|
+
const output = await new Process().execute(
|
|
11
|
+
'node',
|
|
12
|
+
['-e', 'process.stdout.write("ok")'],
|
|
13
|
+
{ silently: true }
|
|
14
|
+
)
|
|
12
15
|
|
|
13
16
|
assert.strictEqual(output, 'ok')
|
|
14
17
|
})
|
|
15
18
|
|
|
16
19
|
it('should reject what the command failed at', async () => {
|
|
17
|
-
await assert.rejects(
|
|
18
|
-
{ silently: true })
|
|
20
|
+
await assert.rejects(
|
|
21
|
+
new Process().execute('node', ['-e', 'process.exit(1)'], { silently: true })
|
|
22
|
+
)
|
|
19
23
|
})
|
|
20
24
|
})
|
|
@@ -2,10 +2,11 @@ import * as _service from './service.js'
|
|
|
2
2
|
import { dependencies } from '@toa.io/norm/types/context'
|
|
3
3
|
|
|
4
4
|
declare namespace toa.deployment {
|
|
5
|
-
|
|
6
5
|
namespace dependency {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
type Constructor = (
|
|
7
|
+
instances: dependencies.Instance[],
|
|
8
|
+
annotation: any
|
|
9
|
+
) => Declaration
|
|
9
10
|
|
|
10
11
|
type Reference = {
|
|
11
12
|
name: string
|
|
@@ -19,6 +20,11 @@ declare namespace toa.deployment {
|
|
|
19
20
|
group: string
|
|
20
21
|
name: string
|
|
21
22
|
version: string
|
|
23
|
+
|
|
24
|
+
/** The repository the extension publishes this service's image to, without a tag.
|
|
25
|
+
* Stating it lets an application take the published image instead of building one. */
|
|
26
|
+
image?: string
|
|
27
|
+
|
|
22
28
|
port: number
|
|
23
29
|
ingress?: {
|
|
24
30
|
host: string
|
|
@@ -36,7 +42,7 @@ declare namespace toa.deployment {
|
|
|
36
42
|
name: string
|
|
37
43
|
value?: string | number
|
|
38
44
|
secret?: {
|
|
39
|
-
name: string
|
|
45
|
+
name: string
|
|
40
46
|
key: string
|
|
41
47
|
}
|
|
42
48
|
}
|
|
@@ -52,7 +58,6 @@ declare namespace toa.deployment {
|
|
|
52
58
|
variables?: Variables
|
|
53
59
|
events?: string[]
|
|
54
60
|
}
|
|
55
|
-
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
type Dependency = {
|
|
@@ -62,7 +67,6 @@ declare namespace toa.deployment {
|
|
|
62
67
|
variables?: dependency.Variables
|
|
63
68
|
events?: string[]
|
|
64
69
|
}
|
|
65
|
-
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
export type Declaration = toa.deployment.dependency.Declaration
|
|
@@ -3,7 +3,6 @@ import type * as _service from './service.js'
|
|
|
3
3
|
import type * as _dependency from './dependency.js'
|
|
4
4
|
|
|
5
5
|
declare namespace toa.deployment {
|
|
6
|
-
|
|
7
6
|
interface Declaration {
|
|
8
7
|
apiVersion: string
|
|
9
8
|
type: string
|
|
@@ -25,13 +24,11 @@ declare namespace toa.deployment {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
namespace installation {
|
|
28
|
-
|
|
29
27
|
interface Options {
|
|
30
28
|
wait?: boolean
|
|
31
29
|
target?: string
|
|
32
30
|
namespace?: string
|
|
33
31
|
}
|
|
34
|
-
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
namespace template {
|
|
@@ -54,7 +51,6 @@ declare namespace toa.deployment {
|
|
|
54
51
|
|
|
55
52
|
variables(): _dependency.Variables
|
|
56
53
|
}
|
|
57
|
-
|
|
58
54
|
}
|
|
59
55
|
|
|
60
56
|
export namespace installation {
|
|
@@ -2,13 +2,11 @@ import type * as _operator from './operator.js'
|
|
|
2
2
|
import * as _registry from './registry.js'
|
|
3
3
|
|
|
4
4
|
declare namespace toa.deployment {
|
|
5
|
-
|
|
6
5
|
interface Factory {
|
|
7
6
|
operator(): _operator.Operator
|
|
8
7
|
|
|
9
8
|
registry(): _registry.Registry
|
|
10
9
|
}
|
|
11
|
-
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
export type Factory = toa.deployment.Factory
|