build-strap 3.0.0-dev.42 → 4.0.0
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/LICENSE +1 -1
- package/README.md +3 -34
- package/dist/src/ant.d.ts +19 -0
- package/dist/src/ant.js +18 -0
- package/dist/src/cert.d.ts +12 -0
- package/dist/src/cert.js +91 -0
- package/dist/src/clean.d.ts +4 -0
- package/dist/src/clean.js +11 -0
- package/dist/src/copy.d.ts +7 -0
- package/dist/src/copy.js +53 -0
- package/dist/src/cp.d.ts +17 -0
- package/dist/src/cp.js +88 -0
- package/dist/src/docker.container.d.ts +73 -0
- package/dist/src/docker.container.js +247 -0
- package/dist/src/docker.d.ts +68 -0
- package/dist/src/docker.js +188 -0
- package/dist/src/docker.network.d.ts +42 -0
- package/dist/src/docker.network.js +85 -0
- package/dist/src/docker.volume.d.ts +26 -0
- package/dist/src/docker.volume.js +62 -0
- package/dist/src/dockerCleanup.d.ts +38 -0
- package/dist/src/dockerCleanup.js +77 -0
- package/dist/src/dockerCompose.d.ts +154 -0
- package/dist/src/dockerCompose.js +439 -0
- package/dist/src/fs.d.ts +5 -0
- package/dist/src/fs.js +38 -0
- package/dist/src/git.d.ts +9 -0
- package/dist/src/git.js +24 -0
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.js +20 -0
- package/dist/src/maps.d.ts +33 -0
- package/dist/src/maps.js +41 -0
- package/dist/src/npm.d.ts +35 -0
- package/dist/src/npm.js +104 -0
- package/dist/src/pkg.d.ts +33 -0
- package/dist/src/pkg.js +33 -0
- package/dist/src/publish.d.ts +27 -0
- package/dist/src/publish.js +57 -0
- package/dist/src/run.d.ts +13 -0
- package/dist/src/run.js +44 -0
- package/dist/src/tgz.d.ts +13 -0
- package/dist/src/tgz.js +67 -0
- package/dist/src/version.d.ts +34 -0
- package/dist/src/version.js +101 -0
- package/dist/src/yarn.d.ts +26 -0
- package/dist/src/yarn.js +75 -0
- package/package.json +74 -20
- package/ant.js +0 -43
- package/ant.js.flow +0 -42
- package/artifactory.js +0 -617
- package/artifactory.js.flow +0 -616
- package/cert.conf +0 -20
- package/cert.js +0 -153
- package/cert.js.flow +0 -152
- package/clean.js +0 -20
- package/clean.js.flow +0 -19
- package/copy.js +0 -80
- package/copy.js.flow +0 -79
- package/cp.js +0 -173
- package/cp.js.flow +0 -172
- package/docker.container.js +0 -390
- package/docker.container.js.flow +0 -389
- package/docker.js +0 -368
- package/docker.js.flow +0 -367
- package/docker.network.js +0 -160
- package/docker.network.js.flow +0 -159
- package/docker.volume.js +0 -136
- package/docker.volume.js.flow +0 -136
- package/dockerCleanup.js +0 -152
- package/dockerCleanup.js.flow +0 -151
- package/dockerCompose.js +0 -766
- package/dockerCompose.js.flow +0 -765
- package/flow.js +0 -21
- package/flow.js.flow +0 -20
- package/fs.js +0 -95
- package/fs.js.flow +0 -96
- package/git.js +0 -35
- package/git.js.flow +0 -34
- package/hg.js +0 -35
- package/hg.js.flow +0 -34
- package/index.js +0 -27
- package/index.js.flow +0 -26
- package/inline-resources.js +0 -158
- package/inline-resources.js.flow +0 -157
- package/maps.js +0 -112
- package/maps.js.flow +0 -111
- package/npm.js +0 -145
- package/npm.js.flow +0 -144
- package/pkg.js +0 -81
- package/pkg.js.flow +0 -80
- package/publish.js +0 -145
- package/publish.js.flow +0 -144
- package/run.js +0 -90
- package/run.js.flow +0 -89
- package/src/ant.js +0 -42
- package/src/artifactory.js +0 -616
- package/src/cert.conf +0 -20
- package/src/cert.js +0 -152
- package/src/clean.js +0 -19
- package/src/copy.js +0 -79
- package/src/cp.js +0 -172
- package/src/docker.container.js +0 -389
- package/src/docker.js +0 -367
- package/src/docker.network.js +0 -159
- package/src/docker.volume.js +0 -136
- package/src/dockerCleanup.js +0 -151
- package/src/dockerCompose.js +0 -765
- package/src/flow.js +0 -20
- package/src/fs.js +0 -96
- package/src/git.js +0 -34
- package/src/hg.js +0 -34
- package/src/index.js +0 -26
- package/src/inline-resources.js +0 -157
- package/src/maps.js +0 -111
- package/src/npm.js +0 -144
- package/src/pkg.js +0 -80
- package/src/publish.js +0 -144
- package/src/run.js +0 -89
- package/src/test.js +0 -55
- package/src/tgz.js +0 -101
- package/src/version.js +0 -141
- package/src/yarn.js +0 -123
- package/test.js +0 -56
- package/test.js.flow +0 -55
- package/tgz.js +0 -102
- package/tgz.js.flow +0 -101
- package/version.js +0 -142
- package/version.js.flow +0 -141
- package/yarn.js +0 -124
- package/yarn.js.flow +0 -123
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import bytes from 'bytes';
|
|
2
|
+
import { throttle } from 'lodash-es';
|
|
3
|
+
import { spawn } from './cp.js';
|
|
4
|
+
import { parseDockerDate } from './docker.js';
|
|
5
|
+
import { buildLog } from './run.js';
|
|
6
|
+
// calling this function in rapid succession can lead to errors.
|
|
7
|
+
// prefer throttled version
|
|
8
|
+
export async function unthrottledDockerContainerLs({ all, filter, } = {}) {
|
|
9
|
+
return (await spawn('docker', [
|
|
10
|
+
'container',
|
|
11
|
+
'ls',
|
|
12
|
+
...(all ? ['--all'] : []),
|
|
13
|
+
'--no-trunc',
|
|
14
|
+
'--format',
|
|
15
|
+
'{{json .}}',
|
|
16
|
+
], { captureOutput: true })).output
|
|
17
|
+
.split('\n')
|
|
18
|
+
.filter((line) => !!line.trim())
|
|
19
|
+
.map((line) => {
|
|
20
|
+
const { Command: command, CreatedAt, ID: id, Image: image, Labels, Mounts, Names, Networks, Ports, RunningFor: runningFor, Size, Status: status, } = JSON.parse(line.trim());
|
|
21
|
+
const names = Names.split(',').map((n) => n.trim());
|
|
22
|
+
const [name] = names;
|
|
23
|
+
return {
|
|
24
|
+
command,
|
|
25
|
+
created: parseDockerDate(CreatedAt),
|
|
26
|
+
async exists() {
|
|
27
|
+
try {
|
|
28
|
+
return !!(await this.inspect());
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
exited: /^Exited/.test(status),
|
|
35
|
+
id,
|
|
36
|
+
image,
|
|
37
|
+
inspect: async () => (await dockerContainerInspect(id))?.[0],
|
|
38
|
+
async isRunning() {
|
|
39
|
+
try {
|
|
40
|
+
return (await this.inspect())?.State?.Running || false;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
kill: async (ignoreError = true) => dockerContainerKill(id, ignoreError),
|
|
47
|
+
labels: Labels.split(',').map((l) => l.trim()),
|
|
48
|
+
mounts: Mounts.split(',').map((m) => m.trim()),
|
|
49
|
+
name,
|
|
50
|
+
names,
|
|
51
|
+
networks: Networks.split(',').map((n) => n.trim()),
|
|
52
|
+
ports: Ports.split(',').map((p) => {
|
|
53
|
+
const [, iface, src, dest] = p.match(/^([^:]+):(\d+)-\\u003e(\d+)/i) || [];
|
|
54
|
+
return {
|
|
55
|
+
dest: parseInt(dest, 10),
|
|
56
|
+
iface,
|
|
57
|
+
src: parseInt(src, 10),
|
|
58
|
+
};
|
|
59
|
+
}),
|
|
60
|
+
restart: async () => dockerContainerRestart(id),
|
|
61
|
+
rm: async (ignoreError = true) => dockerContainerRm(id, ignoreError),
|
|
62
|
+
runningFor,
|
|
63
|
+
size: bytes.parse(Size),
|
|
64
|
+
start: async () => dockerContainerStart(id),
|
|
65
|
+
status,
|
|
66
|
+
stop: async (ignoreError = true) => dockerContainerStop(id, ignoreError),
|
|
67
|
+
async teardown(verbose = true) {
|
|
68
|
+
const log = (msg) => verbose && buildLog(msg);
|
|
69
|
+
if (await this.isRunning()) {
|
|
70
|
+
try {
|
|
71
|
+
log(`Stopping container: <${this.name}>...`);
|
|
72
|
+
await this.stop(false);
|
|
73
|
+
log(`<${this.name}> container stopped.`);
|
|
74
|
+
}
|
|
75
|
+
catch (e1) {
|
|
76
|
+
log(`Failed to stop <${this.name}> container: ${e1.message}`);
|
|
77
|
+
try {
|
|
78
|
+
log(`Killing container: <${this.name}>...`);
|
|
79
|
+
await this.kill(false);
|
|
80
|
+
log(`<${this.name}> container killed.`);
|
|
81
|
+
}
|
|
82
|
+
catch (e2) {
|
|
83
|
+
log(`Failed to kill <${this.name}> container: ${e2.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
log(`<${this.name}> container not running, skipping stop.`);
|
|
89
|
+
}
|
|
90
|
+
if (await this.exists()) {
|
|
91
|
+
await this.rm(true);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
log(`<${this.name}> container does not exist, skipping rm.`);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
})
|
|
99
|
+
.filter((c) => c.id && (typeof filter === 'function' ? filter(c) : true));
|
|
100
|
+
}
|
|
101
|
+
export const dockerContainerLs = throttle(unthrottledDockerContainerLs, 500, {
|
|
102
|
+
trailing: false,
|
|
103
|
+
});
|
|
104
|
+
export async function dockerContainerFind(search, options) {
|
|
105
|
+
return ((await dockerContainerLs(options))?.find((c) => c.name === search ||
|
|
106
|
+
c.id.startsWith(search) ||
|
|
107
|
+
c.names.includes(search)) || null);
|
|
108
|
+
}
|
|
109
|
+
export async function dockerContainerStop(id, ignoreErrors = true) {
|
|
110
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
111
|
+
try {
|
|
112
|
+
await spawn('docker', ['container', 'stop', ...ids]);
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
if (!ignoreErrors) {
|
|
116
|
+
throw new Error(`Failed to stop container(s): ${e.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export async function dockerContainerKill(id, ignoreErrors = true) {
|
|
121
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
122
|
+
try {
|
|
123
|
+
await spawn('docker', ['container', 'kill', ...ids]);
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
if (!ignoreErrors) {
|
|
127
|
+
throw new Error(`Failed to kill container(s): ${e.message}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export async function dockerTryStopContainer(id, name = '') {
|
|
132
|
+
if (id) {
|
|
133
|
+
try {
|
|
134
|
+
await dockerContainerStop(id, false);
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
buildLog(`Failed to stop ${name} container: ${e.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export async function dockerContainerRm(id, ignoreErrors = true) {
|
|
142
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
143
|
+
try {
|
|
144
|
+
await spawn('docker', ['container', 'rm', ...ids]);
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
if (ignoreErrors) {
|
|
148
|
+
buildLog(`Warning (ignored Error): Failed to remove container(s): ${e.message}`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
throw new Error(`Failed to remove container(s): ${e.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export async function dockerContainerRestart(id) {
|
|
156
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
157
|
+
await spawn('docker', ['container', 'restart', ...ids]);
|
|
158
|
+
}
|
|
159
|
+
export async function dockerContainerStart(id) {
|
|
160
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
161
|
+
await spawn('docker', ['container', 'start', ...ids]);
|
|
162
|
+
}
|
|
163
|
+
export async function unthrottledDockerContainerInspect(id) {
|
|
164
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
165
|
+
return ((await spawn('docker', ['container', 'inspect', '--format', '{{json .}}', ...ids], { captureOutput: true })).output
|
|
166
|
+
.split('\n')
|
|
167
|
+
.filter((line) => !!line.trim())
|
|
168
|
+
.map((line) => JSON.parse(line.trim())) || []);
|
|
169
|
+
}
|
|
170
|
+
export const dockerContainerInspect = throttle(unthrottledDockerContainerInspect, 500, {
|
|
171
|
+
trailing: false,
|
|
172
|
+
});
|
|
173
|
+
export async function dockerContainerRun({ image, runArgs = [], cmd = [], spawnOptions, }) {
|
|
174
|
+
return (await spawn('docker', ['container', 'run', ...runArgs, image, ...cmd], {
|
|
175
|
+
pipeOutput: true,
|
|
176
|
+
stdio: 'inherit',
|
|
177
|
+
...spawnOptions,
|
|
178
|
+
})).output;
|
|
179
|
+
}
|
|
180
|
+
export async function dockerContainerRunDaemon({ image, runArgs = [], cmd = [], waitAttempts = 10, waitDuration = 501, }) {
|
|
181
|
+
const id = (await dockerContainerRun({
|
|
182
|
+
cmd,
|
|
183
|
+
image,
|
|
184
|
+
runArgs: ['-d', ...runArgs],
|
|
185
|
+
spawnOptions: {
|
|
186
|
+
captureOutput: true,
|
|
187
|
+
pipeOutput: false,
|
|
188
|
+
stdio: 'pipe',
|
|
189
|
+
},
|
|
190
|
+
})).trim();
|
|
191
|
+
let container = await dockerContainerFind(id);
|
|
192
|
+
let attempts = 0;
|
|
193
|
+
// containers don't always show up in the list right away
|
|
194
|
+
/* eslint-disable no-await-in-loop */
|
|
195
|
+
while (!container && attempts < waitAttempts) {
|
|
196
|
+
await new Promise((resolve) => {
|
|
197
|
+
setTimeout(resolve, waitDuration);
|
|
198
|
+
});
|
|
199
|
+
container = await dockerContainerFind(id);
|
|
200
|
+
attempts += 1;
|
|
201
|
+
}
|
|
202
|
+
/* eslint-enable no-await-in-loop */
|
|
203
|
+
if (!container) {
|
|
204
|
+
throw new Error(`Failed to find newly created container: ${id}`);
|
|
205
|
+
}
|
|
206
|
+
return container;
|
|
207
|
+
}
|
|
208
|
+
const indicators = ['.', ':', '*', '+', '-', '=', '%', '$', '@', '^', '&'];
|
|
209
|
+
let curIndicator = 0;
|
|
210
|
+
/**
|
|
211
|
+
* Waits for the given container to be fully started. Checks that the container is
|
|
212
|
+
* running, and test with the provided testFn every second until it returns true.
|
|
213
|
+
* @param {DockerContainer} container The container to test
|
|
214
|
+
* @param {Function} testFn A function that validates that the container is ready (e.g. by calling an API on the container)
|
|
215
|
+
* @param {number} timeoutMs The amount of time to wait between attempts.
|
|
216
|
+
* @param {number} maxAttempts The maximum number of attempts before giving up (rejects promise)
|
|
217
|
+
*/
|
|
218
|
+
export const dockerContainerWaitForStart = async (container, testFn, timeoutMs = 1000, maxAttempts = 1200) => {
|
|
219
|
+
// claim an indicator
|
|
220
|
+
const indicator = indicators[curIndicator] || '.';
|
|
221
|
+
curIndicator = (curIndicator + 1) % indicators.length;
|
|
222
|
+
const { name } = container;
|
|
223
|
+
buildLog(`Waiting for ${name} to fully start... ${indicator}`);
|
|
224
|
+
await new Promise((resolve, reject) => {
|
|
225
|
+
const check = (tries = 0) => {
|
|
226
|
+
process.stdout.write(indicator);
|
|
227
|
+
setTimeout(async () => {
|
|
228
|
+
try {
|
|
229
|
+
if (!(await container.isRunning())) {
|
|
230
|
+
return reject(new Error(`The '${name}' container is no longer running.`));
|
|
231
|
+
}
|
|
232
|
+
if (await testFn(container)) {
|
|
233
|
+
return resolve();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
// ignore
|
|
238
|
+
}
|
|
239
|
+
return tries < maxAttempts
|
|
240
|
+
? check(tries + 1)
|
|
241
|
+
: reject(new Error(`Timeout waiting for '${name}' container to start.`));
|
|
242
|
+
}, timeoutMs);
|
|
243
|
+
};
|
|
244
|
+
check();
|
|
245
|
+
});
|
|
246
|
+
process.stdout.write('\n');
|
|
247
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface DockerConfig {
|
|
2
|
+
name?: string;
|
|
3
|
+
registry?: string;
|
|
4
|
+
repository?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getDockerConfig(): DockerConfig;
|
|
7
|
+
export declare const parseDockerDate: (date: string) => Date;
|
|
8
|
+
export declare function dockerBuild(tags?: string[], buildArgs?: string[], target?: string | null, extraArgs?: string[], repo?: string, dockerFile?: string, workDir?: string): Promise<string>;
|
|
9
|
+
export declare function dockerTag(imageId: string, tags?: string[], repo?: string): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Apply the standard convention for tagging the docker image for the project,
|
|
12
|
+
* based on the source control branch being built.
|
|
13
|
+
* For releases, applies `latest`, `M.m.p`, `M.m`, `M`
|
|
14
|
+
* For release candidates, applies `latest-rc`
|
|
15
|
+
* For feature branches, applies `latest-feature`
|
|
16
|
+
* For development branch, applies `latest-dev`
|
|
17
|
+
* All other branches, no tag is applies
|
|
18
|
+
* @param {string} imageId id of the docker image to which to apply the tags
|
|
19
|
+
* @param {string} repo optional override of the docker repo url to use (derived from package.json by default)
|
|
20
|
+
*/
|
|
21
|
+
export declare function dockerTagVersion(imageId: string, repo?: string): Promise<void>;
|
|
22
|
+
export interface DockerImage {
|
|
23
|
+
created: Date;
|
|
24
|
+
digest: string;
|
|
25
|
+
id: string;
|
|
26
|
+
repository: string;
|
|
27
|
+
size: number;
|
|
28
|
+
tag: string;
|
|
29
|
+
}
|
|
30
|
+
export declare type DockerImageFilter = (i: DockerImage) => boolean;
|
|
31
|
+
export declare function dockerImages(repo?: null | string, filter?: null | DockerImageFilter): Promise<DockerImage[]>;
|
|
32
|
+
export declare function getDockerId(tag?: string, repo?: null | string): Promise<string>;
|
|
33
|
+
export declare function getUntaggedDockerIds(repo?: string, filter?: null | DockerImageFilter): Promise<string[]>;
|
|
34
|
+
export declare function getDockerTags(id?: null | string, repo?: null | string, fullName?: boolean, filter?: null | DockerImageFilter): Promise<string[]>;
|
|
35
|
+
export declare function getDockerDigest(imageId: string, repo?: string): Promise<string>;
|
|
36
|
+
interface DockerLoginOptions {
|
|
37
|
+
password?: string;
|
|
38
|
+
registry?: string;
|
|
39
|
+
user?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function dockerLogin({ password, registry, user, }?: DockerLoginOptions): Promise<void>;
|
|
42
|
+
export declare function dockerPush(tags?: string[], repo?: string): Promise<void>;
|
|
43
|
+
export interface DockerPullOptions {
|
|
44
|
+
image: string;
|
|
45
|
+
offline?: boolean;
|
|
46
|
+
testUrl?: string | null;
|
|
47
|
+
}
|
|
48
|
+
export declare function dockerPull({ image, offline, testUrl, }: DockerPullOptions): Promise<void>;
|
|
49
|
+
export declare function dockerRmi(images?: string[], ignoreErrors?: boolean): Promise<void>;
|
|
50
|
+
export declare function getDockerRepo(): string;
|
|
51
|
+
export interface PullAndRunContainerOptions extends DockerPullOptions {
|
|
52
|
+
alias?: string | null;
|
|
53
|
+
cmd?: string[];
|
|
54
|
+
network?: string | null;
|
|
55
|
+
runArgs?: string[];
|
|
56
|
+
}
|
|
57
|
+
export declare function dockerPullAndRunContainer(options: PullAndRunContainerOptions): Promise<string>;
|
|
58
|
+
export interface CopyFilePathConfig {
|
|
59
|
+
from: string;
|
|
60
|
+
to: string;
|
|
61
|
+
}
|
|
62
|
+
export interface CopyFilesFromDockerImageConfig {
|
|
63
|
+
filePaths: CopyFilePathConfig[];
|
|
64
|
+
ignoreErrors?: boolean;
|
|
65
|
+
imageId: string;
|
|
66
|
+
}
|
|
67
|
+
export declare function copyFilesFromDockerImage({ filePaths, ignoreErrors, imageId, }: CopyFilesFromDockerImageConfig): Promise<any>;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import bytes from 'bytes';
|
|
2
|
+
import isReachable from 'is-reachable';
|
|
3
|
+
import moment from 'moment';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { spawn } from './cp.js';
|
|
6
|
+
import { dockerContainerKill, dockerContainerRunDaemon, } from './docker.container.js';
|
|
7
|
+
import { dockerNetworkConnect } from './docker.network.js';
|
|
8
|
+
import { getCfg, getPkgName, getPkgScope } from './pkg.js';
|
|
9
|
+
import { buildLog } from './run.js';
|
|
10
|
+
import { getDevBranch, getVersion } from './version.js';
|
|
11
|
+
export function getDockerConfig() {
|
|
12
|
+
return getCfg().docker || {};
|
|
13
|
+
}
|
|
14
|
+
export const parseDockerDate = (date) => moment(date, 'YYYY-MM-DD HH:mm:ss ZZ').toDate();
|
|
15
|
+
export async function dockerBuild(tags = ['latest'], buildArgs = [], target = null, extraArgs = [], repo = getDockerRepo(), dockerFile = './Dockerfile', workDir = '.') {
|
|
16
|
+
const args = [
|
|
17
|
+
'build',
|
|
18
|
+
...['-f', path.resolve(dockerFile)],
|
|
19
|
+
...(target ? ['--target', target] : []),
|
|
20
|
+
...tags.reduce((ar, t) => [...ar, '-t', `${repo}:${t}`], []),
|
|
21
|
+
...buildArgs.reduce((ar, a) => [...ar, '--build-arg', a], []),
|
|
22
|
+
...(extraArgs || []),
|
|
23
|
+
path.resolve(workDir),
|
|
24
|
+
];
|
|
25
|
+
return (await spawn('docker', args, { stdio: 'inherit' })).output;
|
|
26
|
+
}
|
|
27
|
+
export async function dockerTag(imageId, tags = ['latest'], repo = getDockerRepo()) {
|
|
28
|
+
await Promise.all(tags.map(async (t) => {
|
|
29
|
+
await spawn('docker', ['tag', imageId, `${repo}:${t}`]);
|
|
30
|
+
buildLog(`Tagged image ${imageId} as ${repo}:${t}`);
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Apply the standard convention for tagging the docker image for the project,
|
|
35
|
+
* based on the source control branch being built.
|
|
36
|
+
* For releases, applies `latest`, `M.m.p`, `M.m`, `M`
|
|
37
|
+
* For release candidates, applies `latest-rc`
|
|
38
|
+
* For feature branches, applies `latest-feature`
|
|
39
|
+
* For development branch, applies `latest-dev`
|
|
40
|
+
* All other branches, no tag is applies
|
|
41
|
+
* @param {string} imageId id of the docker image to which to apply the tags
|
|
42
|
+
* @param {string} repo optional override of the docker repo url to use (derived from package.json by default)
|
|
43
|
+
*/
|
|
44
|
+
export async function dockerTagVersion(imageId, repo = getDockerRepo()) {
|
|
45
|
+
const { branch, isRelease, major, minor, patch } = await getVersion();
|
|
46
|
+
// determine what tags to apply
|
|
47
|
+
if (isRelease) {
|
|
48
|
+
await dockerTag(imageId, ['latest', `${major}`, `${major}.${minor}`, `${major}.${minor}.${patch}`], repo);
|
|
49
|
+
}
|
|
50
|
+
else if (branch === (await getDevBranch())) {
|
|
51
|
+
await dockerTag(imageId, ['latest-dev'], repo);
|
|
52
|
+
}
|
|
53
|
+
else if (branch.match(/^(release|patch)-/)) {
|
|
54
|
+
await dockerTag(imageId, ['latest-rc'], repo);
|
|
55
|
+
}
|
|
56
|
+
else if (branch.match(/^feature-/)) {
|
|
57
|
+
await dockerTag(imageId, ['latest-feature'], repo);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function dockerImages(repo = getDockerRepo(), filter = null) {
|
|
61
|
+
return (await spawn('docker', ['images', '--format', `{{json .}}`, ...(repo ? [repo] : [])], { captureOutput: true })).output
|
|
62
|
+
.split('\n')
|
|
63
|
+
.filter((line) => !!line.trim())
|
|
64
|
+
.map((l) => {
|
|
65
|
+
const { CreatedAt, Digest: digest, ID: id, Repository: repository, Size, Tag: tag, } = JSON.parse(l.trim());
|
|
66
|
+
return {
|
|
67
|
+
created: parseDockerDate(CreatedAt),
|
|
68
|
+
digest,
|
|
69
|
+
id,
|
|
70
|
+
repository,
|
|
71
|
+
size: bytes.parse(Size),
|
|
72
|
+
tag,
|
|
73
|
+
};
|
|
74
|
+
})
|
|
75
|
+
.filter((m) => (m && m.id) != null &&
|
|
76
|
+
(typeof filter === 'function' ? filter(m) : true));
|
|
77
|
+
}
|
|
78
|
+
export async function getDockerId(tag = 'latest', repo = null) {
|
|
79
|
+
return (await dockerImages(repo, (m) => m.tag === tag)).map((m) => m.id)[0];
|
|
80
|
+
}
|
|
81
|
+
export async function getUntaggedDockerIds(repo, filter = null) {
|
|
82
|
+
return (await dockerImages(repo, (m) => m.tag === '<none>' && (typeof filter === 'function' ? filter(m) : true))).map((m) => m.id);
|
|
83
|
+
}
|
|
84
|
+
export async function getDockerTags(id, repo, fullName = false, filter = null) {
|
|
85
|
+
return (await dockerImages(repo, (m) => (!id || m.id === id) &&
|
|
86
|
+
(typeof filter === 'function' ? filter(m) : true))).map((m) => (fullName ? `${m.repository}:${m.tag}` : m.tag));
|
|
87
|
+
}
|
|
88
|
+
export async function getDockerDigest(imageId, repo) {
|
|
89
|
+
return (await dockerImages(repo, (m) => m.id === imageId)).map((m) => m.digest)[0];
|
|
90
|
+
}
|
|
91
|
+
export async function dockerLogin({ password = process.env.DOCKER_PASSWORD, registry = process.env.DOCKER_REGISTRY, user = process.env.DOCKER_PASSWORD, } = {}) {
|
|
92
|
+
const reg = registry || getDockerConfig().registry;
|
|
93
|
+
await spawn('docker', ['login', '-u', user || '', '-p', password || '', ...(reg ? [reg] : [])], { stdio: 'inherit' });
|
|
94
|
+
}
|
|
95
|
+
export async function dockerPush(tags = ['latest'], repo = getDockerRepo()) {
|
|
96
|
+
// do this sequentially, so you don't push the same image simultaneously
|
|
97
|
+
await tags.reduce(async (prev, tag) => {
|
|
98
|
+
await prev;
|
|
99
|
+
await spawn('docker', ['push', `${repo}:${tag}`], { stdio: 'inherit' });
|
|
100
|
+
}, Promise.resolve());
|
|
101
|
+
}
|
|
102
|
+
export async function dockerPull({ image, offline = process.argv.includes('--offline'), testUrl, }) {
|
|
103
|
+
if (offline || !(await isReachable(testUrl || 'https://hub.docker.com'))) {
|
|
104
|
+
if (offline === false) {
|
|
105
|
+
throw new Error('Offline, cannot docker pull');
|
|
106
|
+
}
|
|
107
|
+
buildLog('It looks like you are offline, skipping docker pull');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// throws on failure
|
|
111
|
+
await spawn('docker', ['pull', image], { stdio: 'inherit' });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export async function dockerRmi(images = [], ignoreErrors = true) {
|
|
115
|
+
// must do these sequentially, or they will interfere with each other
|
|
116
|
+
await images.reduce(async (prev, image) => {
|
|
117
|
+
await prev;
|
|
118
|
+
await spawn('docker', ['rmi', image], { stdio: 'inherit' }).catch((err) => {
|
|
119
|
+
if (ignoreErrors) {
|
|
120
|
+
buildLog(`Warning (ignored Error): Failed to rm image(s): ${err.message}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}, Promise.resolve());
|
|
127
|
+
}
|
|
128
|
+
export function getDockerRepo() {
|
|
129
|
+
const { name: dkrName, registry, repository: dkrRepo } = getDockerConfig();
|
|
130
|
+
const repository = dkrRepo || getPkgScope();
|
|
131
|
+
const name = dkrName || getPkgName(false);
|
|
132
|
+
if (!repository || !name) {
|
|
133
|
+
throw new Error('Docker configuration is missing or incomplete in package.json');
|
|
134
|
+
}
|
|
135
|
+
return `${registry ? `${registry}/` : ''}${repository}/${name}`;
|
|
136
|
+
}
|
|
137
|
+
export async function dockerPullAndRunContainer(options) {
|
|
138
|
+
const { image, runArgs = [], cmd = [], network, alias, offline = process.argv.includes('--offline'), testUrl, } = options;
|
|
139
|
+
try {
|
|
140
|
+
await dockerPull({ image, offline, testUrl });
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
buildLog(`docker pull failed (${err.message}), attempting to continue...`);
|
|
144
|
+
}
|
|
145
|
+
const { id } = await dockerContainerRunDaemon({ cmd, image, runArgs });
|
|
146
|
+
if (network) {
|
|
147
|
+
await dockerNetworkConnect(network, id, alias);
|
|
148
|
+
}
|
|
149
|
+
return id;
|
|
150
|
+
}
|
|
151
|
+
export async function copyFilesFromDockerImage({ filePaths, ignoreErrors, imageId, }) {
|
|
152
|
+
const container = await dockerContainerRunDaemon({
|
|
153
|
+
cmd: ['read', '-p', 'pause'],
|
|
154
|
+
image: imageId,
|
|
155
|
+
runArgs: ['-it', '--rm', '--entrypoint=/bin/bash'],
|
|
156
|
+
});
|
|
157
|
+
buildLog(`Copying Files from Docker Image - imageId (${imageId})`);
|
|
158
|
+
const spawnOptions = {
|
|
159
|
+
captureOutput: false,
|
|
160
|
+
pipeOutput: true,
|
|
161
|
+
stdio: 'inherit',
|
|
162
|
+
};
|
|
163
|
+
const copyErrors = [];
|
|
164
|
+
try {
|
|
165
|
+
filePaths.forEach(async (fp) => {
|
|
166
|
+
// Try to copy from the Debug Folder
|
|
167
|
+
try {
|
|
168
|
+
await spawn('docker', ['cp', `${container.id}:${fp.from}`, fp.to], spawnOptions);
|
|
169
|
+
buildLog(`Copied ${fp.from} => ${fp.to}`);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
copyErrors.push(error);
|
|
173
|
+
buildLog(`Error copying file ${fp.from}`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
try {
|
|
179
|
+
await dockerContainerKill(container.id);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
copyErrors.push(error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (copyErrors.length > 0 && ignoreErrors !== true) {
|
|
186
|
+
throw new Error('Some files failed to copy');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface DockerNetwork {
|
|
2
|
+
IPv6: boolean;
|
|
3
|
+
created: Date;
|
|
4
|
+
driver: string;
|
|
5
|
+
exists: () => Promise<boolean>;
|
|
6
|
+
id: string;
|
|
7
|
+
labels: string[];
|
|
8
|
+
name: string;
|
|
9
|
+
rm: () => Promise<void>;
|
|
10
|
+
scope: string;
|
|
11
|
+
}
|
|
12
|
+
interface DockerNetworkLsOptions {
|
|
13
|
+
filter?: (network: DockerNetwork) => boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function unthrottledDockerNetworkLs({ filter, }?: DockerNetworkLsOptions): Promise<DockerNetwork[]>;
|
|
16
|
+
export declare const dockerNetworkLs: (options?: DockerNetworkLsOptions) => Promise<DockerNetwork[]> | undefined;
|
|
17
|
+
export declare function dockerNetworkFind(networkName: string, options?: DockerNetworkLsOptions): Promise<DockerNetwork | null>;
|
|
18
|
+
export interface DockerNetworkCreateOptions {
|
|
19
|
+
auxAddresses?: string[];
|
|
20
|
+
driver?: string;
|
|
21
|
+
driverOpts?: Record<string, string>;
|
|
22
|
+
gateways?: string[];
|
|
23
|
+
ingress?: boolean;
|
|
24
|
+
internal?: boolean;
|
|
25
|
+
ipRange?: string;
|
|
26
|
+
ipv6?: boolean;
|
|
27
|
+
labels?: string[];
|
|
28
|
+
subnets?: string[];
|
|
29
|
+
}
|
|
30
|
+
export declare function dockerNetworkCreate(networkName: string, { driver, driverOpts, ipRange, gateways, subnets, auxAddresses, ingress, internal, ipv6, labels, }?: DockerNetworkCreateOptions): Promise<DockerNetwork | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Removes a network by id
|
|
33
|
+
* @param {string} id The id of the network
|
|
34
|
+
*/
|
|
35
|
+
export declare function dockerNetworkRm(id: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Removes all networks with the given name
|
|
38
|
+
* @param {string} networkName The name of the network
|
|
39
|
+
*/
|
|
40
|
+
export declare function dockerNetworkDelete(networkName: string): Promise<void>;
|
|
41
|
+
export declare function dockerNetworkConnect(networkName: string, containerId: string, alias?: string | null): Promise<void>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { throttle } from 'lodash-es';
|
|
2
|
+
import { spawn } from './cp.js';
|
|
3
|
+
import { parseDockerDate } from './docker.js';
|
|
4
|
+
// calling this function in rapid succession can lead to errors.
|
|
5
|
+
// prefer throttled version
|
|
6
|
+
export async function unthrottledDockerNetworkLs({ filter, } = {}) {
|
|
7
|
+
return (await spawn('docker', ['network', 'ls', '--format', '{{json .}}'], {
|
|
8
|
+
captureOutput: true,
|
|
9
|
+
})).output
|
|
10
|
+
.split('\n')
|
|
11
|
+
.filter((line) => !!line.trim())
|
|
12
|
+
.map((line) => {
|
|
13
|
+
const { CreatedAt, Driver: driver, ID: id, IPv6, Labels, Name: name, Scope: scope, } = JSON.parse(line.trim());
|
|
14
|
+
return {
|
|
15
|
+
IPv6: IPv6 === 'true',
|
|
16
|
+
created: parseDockerDate(CreatedAt),
|
|
17
|
+
driver,
|
|
18
|
+
exists: async () => !!(await dockerNetworkFind(id)),
|
|
19
|
+
id,
|
|
20
|
+
labels: Labels.split(',').map((l) => l.trim()),
|
|
21
|
+
name,
|
|
22
|
+
rm: async () => dockerNetworkRm(id),
|
|
23
|
+
scope,
|
|
24
|
+
};
|
|
25
|
+
})
|
|
26
|
+
.filter((n) => n.id && (typeof filter === 'function' ? filter(n) : true));
|
|
27
|
+
}
|
|
28
|
+
export const dockerNetworkLs = throttle(unthrottledDockerNetworkLs, 500, {
|
|
29
|
+
trailing: false,
|
|
30
|
+
});
|
|
31
|
+
export async function dockerNetworkFind(networkName, options) {
|
|
32
|
+
return ((await dockerNetworkLs(options))?.find((n) => n.name === networkName || n.id === networkName) || null);
|
|
33
|
+
}
|
|
34
|
+
export async function dockerNetworkCreate(networkName, { driver, driverOpts = {}, ipRange, gateways = [], subnets = [], auxAddresses = [], ingress, internal, ipv6, labels = [], } = {}) {
|
|
35
|
+
const existing = await dockerNetworkFind(networkName);
|
|
36
|
+
if (existing)
|
|
37
|
+
return existing;
|
|
38
|
+
await spawn('docker', [
|
|
39
|
+
'network',
|
|
40
|
+
'create',
|
|
41
|
+
...labels.flatMap((l) => ['--label', l]),
|
|
42
|
+
...gateways.flatMap((g) => ['--gateway', g]),
|
|
43
|
+
...subnets.flatMap((s) => ['--subnet', s]),
|
|
44
|
+
...auxAddresses.flatMap((a) => ['--aux-address', a]),
|
|
45
|
+
...(ipRange ? ['--ip-range', ipRange] : []),
|
|
46
|
+
...(ingress ? ['--ingress'] : []),
|
|
47
|
+
...(internal ? ['--internal'] : []),
|
|
48
|
+
...(ipv6 ? ['--ipv6'] : []),
|
|
49
|
+
...(driver ? ['-d', driver] : []),
|
|
50
|
+
...Object.entries(driverOpts).flatMap(([k, v]) => ['-o', `${k}=${v}`]),
|
|
51
|
+
networkName,
|
|
52
|
+
]);
|
|
53
|
+
return dockerNetworkFind(networkName);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Removes a network by id
|
|
57
|
+
* @param {string} id The id of the network
|
|
58
|
+
*/
|
|
59
|
+
export async function dockerNetworkRm(id) {
|
|
60
|
+
await spawn('docker', ['network', 'rm', id]);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Removes all networks with the given name
|
|
64
|
+
* @param {string} networkName The name of the network
|
|
65
|
+
*/
|
|
66
|
+
export async function dockerNetworkDelete(networkName) {
|
|
67
|
+
let existing;
|
|
68
|
+
// eslint-disable-next-line no-cond-assign, no-await-in-loop
|
|
69
|
+
while ((existing = await dockerNetworkFind(networkName))) {
|
|
70
|
+
// eslint-disable-next-line no-await-in-loop
|
|
71
|
+
await existing.rm();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function dockerNetworkConnect(networkName, containerId, alias) {
|
|
75
|
+
const net = await dockerNetworkCreate(networkName);
|
|
76
|
+
if (!net)
|
|
77
|
+
throw new Error('Failed to create docker network');
|
|
78
|
+
await spawn('docker', [
|
|
79
|
+
'network',
|
|
80
|
+
'connect',
|
|
81
|
+
...(alias ? ['--alias', alias] : []),
|
|
82
|
+
net.id,
|
|
83
|
+
containerId,
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
declare type DockerVolumeInspectOutput = Record<string, any>;
|
|
2
|
+
export interface DockerVolume {
|
|
3
|
+
driver: string;
|
|
4
|
+
inspect: () => Promise<null | DockerVolumeInspectOutput>;
|
|
5
|
+
labels: string[];
|
|
6
|
+
mountPoint: string;
|
|
7
|
+
name: string;
|
|
8
|
+
rm: () => Promise<void>;
|
|
9
|
+
scope: string;
|
|
10
|
+
}
|
|
11
|
+
interface DockerVolumeLsOptions {
|
|
12
|
+
filter?: (vol: DockerVolume) => boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function unthrottledDockerVolumeLs({ filter, }?: DockerVolumeLsOptions): Promise<DockerVolume[]>;
|
|
15
|
+
export declare const dockerVolumeLs: (options?: DockerVolumeLsOptions) => undefined | Promise<DockerVolume[]>;
|
|
16
|
+
export declare function dockerVolumeFind(search: string, options?: DockerVolumeLsOptions): Promise<null | DockerVolume>;
|
|
17
|
+
export interface DockerVolumeCreateOptions {
|
|
18
|
+
driver?: string;
|
|
19
|
+
driverOpts?: Record<string, string>;
|
|
20
|
+
labels?: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function dockerVolumeCreate(name: string, { labels, driver, driverOpts }?: DockerVolumeCreateOptions): Promise<null | DockerVolume>;
|
|
23
|
+
export declare function dockerVolumeRm(id: string | string[], ignoreErrors?: boolean): Promise<void>;
|
|
24
|
+
export declare function unthrottledDockerVolumeInspect(id: string | string[]): Promise<DockerVolumeInspectOutput[]>;
|
|
25
|
+
export declare const dockerVolumeInspect: (id: string | string[]) => undefined | Promise<DockerVolumeInspectOutput[]>;
|
|
26
|
+
export {};
|