@toa.io/operations 1.0.0-alpha.34 → 1.0.0-alpha.340
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 +449 -0
- package/image/runtime.Dockerfile +20 -0
- package/package.json +10 -10
- package/readme.md +224 -0
- package/src/deployment/.deployment/.describe/components.js +1 -5
- package/src/deployment/.deployment/.describe/compositions.js +26 -7
- package/src/deployment/.deployment/.describe/dependencies.js +1 -6
- package/src/deployment/.deployment/.describe/events.js +63 -0
- package/src/deployment/.deployment/.describe/fold.js +42 -0
- package/src/deployment/.deployment/.describe/fold.test.js +108 -0
- package/src/deployment/.deployment/.describe/index.js +4 -13
- package/src/deployment/.deployment/.describe/mounts.js +17 -0
- package/src/deployment/.deployment/.describe/resources.js +129 -0
- package/src/deployment/.deployment/.describe/resources.test.js +129 -0
- package/src/deployment/.deployment/.describe/services.js +32 -6
- package/src/deployment/.deployment/.describe/services.test.js +78 -0
- package/src/deployment/.deployment/.describe/variables.js +5 -9
- package/src/deployment/.deployment/declare.js +1 -5
- package/src/deployment/.deployment/describe.js +168 -10
- package/src/deployment/.deployment/index.js +3 -9
- package/src/deployment/.deployment/merge.js +66 -6
- package/src/deployment/.deployment/merge.test.js +133 -0
- package/src/deployment/chart/templates/components.configmap.yaml +13 -0
- package/src/deployment/chart/templates/components.yaml +4 -3
- package/src/deployment/chart/templates/compositions.yaml +109 -4
- package/src/deployment/chart/templates/mono.yaml +199 -0
- package/src/deployment/chart/templates/services.yaml +97 -7
- package/src/deployment/chart/values.yaml +23 -1
- package/src/deployment/composition.js +8 -6
- package/src/deployment/deployment.js +175 -35
- package/src/deployment/drain.js +70 -0
- package/src/deployment/drain.test.js +64 -0
- package/src/deployment/factory.js +178 -37
- package/src/deployment/images/bundle.js +93 -0
- package/src/deployment/images/composition.Dockerfile +9 -17
- package/src/deployment/images/composition.js +9 -56
- package/src/deployment/images/dependencies.Dockerfile +23 -0
- package/src/deployment/images/dependencies.js +171 -0
- package/src/deployment/images/dependencies.test.js +271 -0
- package/src/deployment/images/factory.js +31 -15
- package/src/deployment/images/format.js +66 -0
- package/src/deployment/images/format.test.js +150 -0
- package/src/deployment/images/image.fixtures.js +17 -19
- package/src/deployment/images/image.js +79 -38
- package/src/deployment/images/image.test.js +11 -5
- package/src/deployment/images/index.js +1 -5
- package/src/deployment/images/mono.Dockerfile +11 -0
- package/src/deployment/images/mono.js +15 -0
- package/src/deployment/images/publish.js +91 -0
- package/src/deployment/images/runtime-base.test.js +97 -0
- package/src/deployment/images/service.Dockerfile +17 -5
- package/src/deployment/images/service.js +63 -14
- package/src/deployment/index.js +1 -5
- package/src/deployment/operator.js +25 -16
- package/src/deployment/operator.test.js +98 -7
- package/src/deployment/registry.js +210 -46
- package/src/deployment/registry.test.js +539 -0
- package/src/deployment/secrets.js +92 -0
- package/src/deployment/secrets.test.js +180 -0
- package/src/deployment/service.js +4 -6
- package/src/deployment/workspace.js +13 -8
- package/src/index.js +3 -2
- package/src/process.js +51 -13
- package/src/process.test.js +33 -0
- package/types/_deployment/composition.d.ts +2 -3
- package/types/_deployment/dependency.d.ts +15 -7
- package/types/_deployment/deployment.d.ts +10 -8
- package/types/_deployment/factory.d.ts +2 -4
- package/types/_deployment/images/factory.d.ts +6 -8
- package/types/_deployment/images/image.d.ts +16 -1
- package/types/_deployment/images/registry.d.ts +8 -11
- package/types/_deployment/operator.d.ts +10 -9
- package/types/_deployment/registry.d.ts +7 -4
- package/types/_deployment/service.d.ts +4 -3
- package/types/dependency.ts +79 -0
- package/types/index.ts +1 -0
- package/types/dependency.d.ts +0 -39
- package/types/index.d.ts +0 -1
|
@@ -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,102 @@ 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)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('install', () => {
|
|
48
|
+
/** @type {toa.deployment.Deployment} */
|
|
49
|
+
let deployment
|
|
50
|
+
|
|
51
|
+
/** @type {toa.deployment.Registry} */
|
|
52
|
+
let registry
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
deployment = /** @type {toa.deployment.Deployment} */ {
|
|
56
|
+
export: mock.fn(async () => {}),
|
|
57
|
+
verify: mock.fn(async () => {}),
|
|
58
|
+
install: mock.fn(async () => {})
|
|
59
|
+
}
|
|
60
|
+
registry = /** @type {toa.deployment.Registry} */ {
|
|
61
|
+
push: mock.fn(async () => {}),
|
|
62
|
+
alias: mock.fn(async () => {})
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should verify the secrets before anything is built', async () => {
|
|
67
|
+
const order = []
|
|
68
|
+
|
|
69
|
+
deployment.verify = mock.fn(async () => {
|
|
70
|
+
order.push('verify')
|
|
71
|
+
})
|
|
72
|
+
deployment.export = mock.fn(async () => {
|
|
73
|
+
order.push('export')
|
|
74
|
+
})
|
|
75
|
+
registry.push = mock.fn(async () => {
|
|
76
|
+
order.push('push')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
operator = new Operator(deployment, registry, 'production')
|
|
80
|
+
|
|
81
|
+
await operator.install({ namespace: 'acme' })
|
|
82
|
+
|
|
83
|
+
// export and push race each other, and both wait for the cluster to have been read
|
|
84
|
+
assert.strictEqual(order[0], 'verify')
|
|
85
|
+
assert.deepStrictEqual(order.slice(1).sort(), ['export', 'push'])
|
|
86
|
+
assert.deepStrictEqual(deployment.verify.mock.calls[0].arguments, [
|
|
87
|
+
{ wait: false, namespace: 'acme' }
|
|
88
|
+
])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should build nothing where the secrets are not deployed', async () => {
|
|
92
|
+
deployment.verify = mock.fn(async () => {
|
|
93
|
+
throw new Error('Secrets are not deployed: toa-mongodb.default/username')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
operator = new Operator(deployment, registry, 'production')
|
|
97
|
+
|
|
98
|
+
await assert.rejects(operator.install(), /Secrets are not deployed/)
|
|
99
|
+
|
|
100
|
+
assert.strictEqual(deployment.export.mock.callCount(), 0)
|
|
101
|
+
assert.strictEqual(registry.push.mock.callCount(), 0)
|
|
102
|
+
assert.strictEqual(registry.alias.mock.callCount(), 0)
|
|
103
|
+
assert.strictEqual(deployment.install.mock.callCount(), 0)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should tag the environment after a push', async () => {
|
|
107
|
+
const order = []
|
|
108
|
+
|
|
109
|
+
registry.push = mock.fn(async () => {
|
|
110
|
+
order.push('push')
|
|
111
|
+
})
|
|
112
|
+
registry.alias = mock.fn(async () => {
|
|
113
|
+
order.push('alias')
|
|
114
|
+
})
|
|
115
|
+
deployment.install = mock.fn(async () => {
|
|
116
|
+
order.push('install')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
operator = new Operator(deployment, registry, 'production')
|
|
120
|
+
|
|
121
|
+
await operator.install()
|
|
122
|
+
|
|
123
|
+
assert.deepStrictEqual(order, ['push', 'alias', 'install'])
|
|
124
|
+
assert.deepStrictEqual(registry.alias.mock.calls[0].arguments, ['production'])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should not tag the environment on push', async () => {
|
|
128
|
+
operator = new Operator(deployment, registry, 'production')
|
|
129
|
+
|
|
130
|
+
await operator.push()
|
|
131
|
+
|
|
132
|
+
assert.strictEqual(registry.push.mock.callCount(), 1)
|
|
133
|
+
assert.strictEqual(registry.alias.mock.callCount(), 0)
|
|
43
134
|
})
|
|
44
135
|
})
|
|
@@ -1,62 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const workspace = require('./workspace')
|
|
4
|
-
const { newid } = require('@toa.io/generic')
|
|
1
|
+
import * as workspace from './workspace.js'
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* @implements {toa.deployment.Registry}
|
|
8
5
|
*/
|
|
9
|
-
class Registry {
|
|
6
|
+
export class Registry {
|
|
10
7
|
#registry
|
|
11
8
|
|
|
12
9
|
#factory
|
|
13
10
|
|
|
14
11
|
#process
|
|
15
12
|
|
|
13
|
+
/** @type {toa.deployment.images.Image[]} */
|
|
16
14
|
#images = []
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
/** @type {Promise<string> | undefined} */
|
|
17
|
+
#builder
|
|
18
|
+
|
|
19
|
+
constructor(registry, factory, process) {
|
|
19
20
|
this.#registry = registry
|
|
20
21
|
this.#factory = factory
|
|
21
22
|
this.#process = process
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
composition
|
|
25
|
+
composition(composition) {
|
|
25
26
|
return this.#create('composition', composition)
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
service
|
|
29
|
+
service(path, service) {
|
|
29
30
|
return this.#create('service', path, service)
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
mono(composition) {
|
|
34
|
+
return this.#create('mono', composition)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async prepare(root) {
|
|
33
38
|
const path = await workspace.create('images', root)
|
|
34
39
|
|
|
35
|
-
await Promise.all(this.#
|
|
40
|
+
await Promise.all(this.#all().map((image) => image.prepare(path)))
|
|
36
41
|
|
|
37
42
|
return path
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
async build
|
|
41
|
-
await this
|
|
45
|
+
async build() {
|
|
46
|
+
await this.#run(false)
|
|
47
|
+
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
49
|
+
async push() {
|
|
50
|
+
await this.#run(true)
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
/**
|
|
54
|
+
* A second tag on each workload image, the environment, so a retention job can see what
|
|
55
|
+
* a deploy left running. `deps-*` is not in `#images`.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} [environment]
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
async alias(environment) {
|
|
61
|
+
if (
|
|
62
|
+
typeof environment !== 'string' ||
|
|
63
|
+
environment === '' ||
|
|
64
|
+
CONTENT_TAG.test(environment)
|
|
65
|
+
)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
await Promise.all(this.#images.map((image) => this.#alias(image, environment)))
|
|
69
|
+
}
|
|
50
70
|
|
|
51
|
-
|
|
71
|
+
tags() {
|
|
72
|
+
return this.#images.map((image) => image.reference)
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
/**
|
|
55
|
-
* @param {'composition' | 'service'} type
|
|
76
|
+
* @param {'composition' | 'service' | 'mono'} type
|
|
56
77
|
* @param {...any} args
|
|
57
78
|
* @returns {toa.deployment.images.Image}
|
|
58
79
|
*/
|
|
59
|
-
#create
|
|
80
|
+
#create(type, ...args) {
|
|
60
81
|
const image = this.#factory[type](...args)
|
|
61
82
|
|
|
62
83
|
this.#images.push(image)
|
|
@@ -64,58 +85,201 @@ class Registry {
|
|
|
64
85
|
return image
|
|
65
86
|
}
|
|
66
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Every image and what it is laid over.
|
|
90
|
+
*
|
|
91
|
+
* @returns {toa.deployment.images.Image[]}
|
|
92
|
+
*/
|
|
93
|
+
#all() {
|
|
94
|
+
return this.#images.flatMap((image) =>
|
|
95
|
+
image.dependencies === undefined ? [image] : [image.dependencies, image]
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Every image is probed at once, and what is missing is prepared and built concurrently:
|
|
101
|
+
* a build is bound by the registry it uploads to, not by the runner, so one at a time
|
|
102
|
+
* leaves both idle. What an image is laid over is built before it.
|
|
103
|
+
*
|
|
104
|
+
* @param {boolean} push
|
|
105
|
+
* @returns {Promise<void>}
|
|
106
|
+
*/
|
|
107
|
+
async #run(push) {
|
|
108
|
+
// a push builds on the container driver, whose bootstrap is spent while the probes are out
|
|
109
|
+
if (push) this.#ensureBuilder().catch(() => undefined)
|
|
110
|
+
|
|
111
|
+
const images = this.#all()
|
|
112
|
+
|
|
113
|
+
const existing = await Promise.all(
|
|
114
|
+
images.map((image) => this.exists(image.reference))
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const missing = images.filter((image, index) => {
|
|
118
|
+
if (existing[index]) console.log('Image already exists, skipping:', image.reference)
|
|
119
|
+
|
|
120
|
+
return !existing[index]
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
if (missing.length === 0) return
|
|
124
|
+
|
|
125
|
+
const path = await workspace.create('images')
|
|
126
|
+
|
|
127
|
+
await Promise.all(missing.map((image) => image.prepare(path)))
|
|
128
|
+
|
|
129
|
+
const bases = new Set(this.#images.map((image) => image.dependencies))
|
|
130
|
+
const build = (image) => this.#build(image, push)
|
|
131
|
+
|
|
132
|
+
await pool(
|
|
133
|
+
missing.filter((image) => bases.has(image)),
|
|
134
|
+
build
|
|
135
|
+
)
|
|
136
|
+
await pool(
|
|
137
|
+
missing.filter((image) => !bases.has(image)),
|
|
138
|
+
build
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
67
142
|
/**
|
|
68
143
|
* @param {toa.deployment.images.Image} image
|
|
69
144
|
* @param {boolean} [push]
|
|
70
145
|
* @returns {Promise<void>}
|
|
71
146
|
*/
|
|
72
|
-
async #build
|
|
147
|
+
async #build(image, push = false) {
|
|
73
148
|
const args = ['--context=default', 'buildx', 'build']
|
|
74
149
|
|
|
75
|
-
if (push)
|
|
76
|
-
|
|
77
|
-
} else {
|
|
78
|
-
args.push('--load')
|
|
79
|
-
}
|
|
150
|
+
if (push) args.push('--push')
|
|
151
|
+
else args.push('--load')
|
|
80
152
|
|
|
81
153
|
args.push('--tag', image.reference, image.context)
|
|
82
154
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
for (const arg of this.#registry.build.arguments) args.push('--build-arg', `${arg}=${process.env[arg]}`)
|
|
155
|
+
if (image.arguments && this.#registry.build?.arguments !== undefined) {
|
|
156
|
+
for (const arg of this.#registry.build.arguments)
|
|
157
|
+
args.push('--build-arg', `${arg}=${process.env[arg]}`)
|
|
87
158
|
}
|
|
88
159
|
|
|
89
|
-
|
|
90
|
-
const platform = this.#registry.platforms.join(',')
|
|
91
|
-
const builder = await this.#createBuilder()
|
|
160
|
+
const platforms = this.#registry.platforms
|
|
92
161
|
|
|
93
|
-
|
|
94
|
-
args.push('--builder', builder)
|
|
162
|
+
if (platforms !== null) args.push('--platform', platforms.join(','))
|
|
95
163
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
164
|
+
// a push takes the container driver: its registry exporter is what lays an image over a
|
|
165
|
+
// base it never pulled. A load takes the daemon's own, unless it builds for a platform
|
|
166
|
+
// the runner is not, which the daemon's cannot
|
|
167
|
+
if (push || (platforms !== null && platforms.length > 1)) {
|
|
168
|
+
args.push('--builder', await this.#ensureBuilder())
|
|
169
|
+
|
|
170
|
+
// the driver that produces attestations is this one, and nothing reads them;
|
|
171
|
+
// each is a manifest list to export and push per image
|
|
172
|
+
args.push('--provenance=false')
|
|
173
|
+
} else args.push('--builder', 'default')
|
|
99
174
|
|
|
100
175
|
args.push('--progress', 'plain')
|
|
101
176
|
|
|
102
177
|
await this.#process.execute('docker', args)
|
|
103
178
|
}
|
|
104
179
|
|
|
105
|
-
|
|
106
|
-
|
|
180
|
+
/**
|
|
181
|
+
* @param {toa.deployment.images.Image} image
|
|
182
|
+
* @param {string} environment
|
|
183
|
+
* @returns {Promise<void>}
|
|
184
|
+
*/
|
|
185
|
+
async #alias(image, environment) {
|
|
186
|
+
const target = retag(image.reference, environment)
|
|
187
|
+
const args = ['buildx', 'imagetools', 'create', '--tag', target]
|
|
188
|
+
|
|
189
|
+
if (LOCAL.test(image.reference)) args.push('--insecure')
|
|
190
|
+
|
|
191
|
+
args.push(image.reference)
|
|
192
|
+
|
|
193
|
+
await this.#process.execute('docker', args)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async exists(tag) {
|
|
197
|
+
const args = ['manifest', 'inspect']
|
|
198
|
+
|
|
199
|
+
// a registry on this machine speaks plain HTTP, which the probe has to be told
|
|
200
|
+
if (LOCAL.test(tag)) args.push('--insecure')
|
|
201
|
+
|
|
202
|
+
args.push(tag)
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
await this.#process.execute('docker', args, { silently: true })
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.log(error.message)
|
|
208
|
+
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return true
|
|
107
213
|
}
|
|
108
214
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
215
|
+
/** @returns {Promise<string>} */
|
|
216
|
+
#ensureBuilder() {
|
|
217
|
+
// the promise is what is memoized, not its value: concurrent builds ask before any answers
|
|
218
|
+
this.#builder ??= this.#createBuilder()
|
|
112
219
|
|
|
113
|
-
|
|
220
|
+
return this.#builder
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** @returns {Promise<string>} */
|
|
224
|
+
async #createBuilder() {
|
|
225
|
+
try {
|
|
226
|
+
await this.#process.execute('docker', ['buildx', 'inspect', BUILDER], {
|
|
227
|
+
silently: true
|
|
228
|
+
})
|
|
229
|
+
} catch {
|
|
230
|
+
// on the host's network, a registry the host reaches as `localhost` is reached
|
|
231
|
+
await this.#process.execute('docker', [
|
|
232
|
+
'buildx',
|
|
233
|
+
'create',
|
|
234
|
+
'--name',
|
|
235
|
+
BUILDER,
|
|
236
|
+
'--driver',
|
|
237
|
+
'docker-container',
|
|
238
|
+
'--driver-opt',
|
|
239
|
+
'network=host',
|
|
240
|
+
'--bootstrap'
|
|
241
|
+
])
|
|
242
|
+
}
|
|
114
243
|
|
|
115
|
-
return
|
|
244
|
+
return BUILDER
|
|
116
245
|
}
|
|
117
246
|
}
|
|
118
247
|
|
|
248
|
+
/**
|
|
249
|
+
* @template T
|
|
250
|
+
* @param {T[]} items
|
|
251
|
+
* @param {(item: T) => Promise<void>} work
|
|
252
|
+
* @returns {Promise<void>}
|
|
253
|
+
*/
|
|
254
|
+
async function pool(items, work) {
|
|
255
|
+
const queue = items.slice()
|
|
256
|
+
|
|
257
|
+
const workers = Array.from(
|
|
258
|
+
{ length: Math.min(CONCURRENCY, queue.length) },
|
|
259
|
+
async () => {
|
|
260
|
+
while (queue.length > 0) await work(queue.shift())
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
await Promise.all(workers)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {string} reference
|
|
269
|
+
* @param {string} tag
|
|
270
|
+
* @returns {string}
|
|
271
|
+
*/
|
|
272
|
+
function retag(reference, tag) {
|
|
273
|
+
return reference.slice(0, reference.lastIndexOf(':') + 1) + tag
|
|
274
|
+
}
|
|
275
|
+
|
|
119
276
|
const BUILDER = 'toa'
|
|
120
277
|
|
|
121
|
-
|
|
278
|
+
/** An environment named this way is a content tag, and must not be moved as one. */
|
|
279
|
+
const CONTENT_TAG = /^(deps-)?[0-9a-f]{8}$/
|
|
280
|
+
|
|
281
|
+
/** A reference into a registry on this machine, by any of the names it goes by. */
|
|
282
|
+
const LOCAL = /^(localhost|127\.\d+\.\d+\.\d+|host\.docker\.internal)(:\d+)?\//
|
|
283
|
+
|
|
284
|
+
/** How many builds run at once. Past this the uploads contend for the same uplink. */
|
|
285
|
+
const CONCURRENCY = 3
|