forked from testcontainers/testcontainers-node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompose-client.ts
More file actions
132 lines (118 loc) · 4.42 KB
/
compose-client.ts
File metadata and controls
132 lines (118 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import compose from "docker-compose";
import { log, pullLog, toSeconds } from "../../../common";
import { defaultComposeOptions } from "./default-compose-options";
import { ComposeDownOptions, ComposeOptions } from "./types";
export interface ComposeClient {
version: string;
up(options: ComposeOptions, services?: Array<string>): Promise<void>;
pull(options: ComposeOptions, services?: Array<string>): Promise<void>;
stop(options: ComposeOptions): Promise<void>;
down(options: ComposeOptions, downOptions: ComposeDownOptions): Promise<void>;
}
export async function getComposeClient(environment: NodeJS.ProcessEnv): Promise<ComposeClient> {
try {
const version = (await compose.version()).data.version;
return new DockerComposeClient(version, environment);
} catch (err) {
return new MissingComposeClient("N/A");
}
}
class DockerComposeClient implements ComposeClient {
constructor(
public readonly version: string,
private readonly environment: NodeJS.ProcessEnv
) {}
async up(options: ComposeOptions, services: Array<string> | undefined): Promise<void> {
try {
if (services) {
log.info(`Upping Compose environment services ${services.join(", ")}...`);
await compose.upMany(services, defaultComposeOptions(this.environment, options));
} else {
log.info(`Upping Compose environment...`);
await compose.upAll(defaultComposeOptions(this.environment, options));
}
log.info(`Upped Compose environment`);
} catch (err) {
await handleAndRethrow(err, async (error: Error) => {
try {
log.error(`Failed to up Compose environment: ${error.message}`);
await this.down(options, { removeVolumes: true, timeout: 0 });
} catch {
log.error(`Failed to down Compose environment after failed up`);
}
});
}
}
async pull(options: ComposeOptions, services: Array<string> | undefined): Promise<void> {
try {
if (services) {
log.info(`Pulling Compose environment images "${services.join('", "')}"...`);
await compose.pullMany(services, defaultComposeOptions(this.environment, { ...options, logger: pullLog }));
} else {
log.info(`Pulling Compose environment images...`);
await compose.pullAll(defaultComposeOptions(this.environment, { ...options, logger: pullLog }));
}
log.info(`Pulled Compose environment`);
} catch (err) {
await handleAndRethrow(err, async (error: Error) =>
log.error(`Failed to pull Compose environment images: ${error.message}`)
);
}
}
async stop(options: ComposeOptions): Promise<void> {
try {
log.info(`Stopping Compose environment...`);
await compose.stop(defaultComposeOptions(this.environment, options));
log.info(`Stopped Compose environment`);
} catch (err) {
await handleAndRethrow(err, async (error: Error) =>
log.error(`Failed to stop Compose environment: ${error.message}`)
);
}
}
async down(options: ComposeOptions, downOptions: ComposeDownOptions): Promise<void> {
try {
log.info(`Downing Compose environment...`);
await compose.down({
...defaultComposeOptions(this.environment, options),
commandOptions: composeDownCommandOptions(downOptions),
});
log.info(`Downed Compose environment`);
} catch (err) {
await handleAndRethrow(err, async (error: Error) =>
log.error(`Failed to down Compose environment: ${error.message}`)
);
}
}
}
class MissingComposeClient implements ComposeClient {
constructor(public readonly version: string) {}
up(): Promise<void> {
throw new Error("Compose is not installed");
}
pull(): Promise<void> {
throw new Error("Compose is not installed");
}
stop(): Promise<void> {
throw new Error("Compose is not installed");
}
down(): Promise<void> {
throw new Error("Compose is not installed");
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function handleAndRethrow(err: any, handle: (error: Error) => Promise<void>): Promise<never> {
const error = err instanceof Error ? err : new Error(err.err.trim());
await handle(error);
throw error;
}
function composeDownCommandOptions(options: ComposeDownOptions): string[] {
const result: string[] = [];
if (options.removeVolumes) {
result.push("-v");
}
if (options.timeout) {
result.push("-t", `${toSeconds(options.timeout)}`);
}
return result;
}