@bizone-ai/cli 0.1.4 → 0.1.5
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/README.md +7 -1
- package/bin/bizone.js +1 -1
- package/package.json +1 -1
- package/src/cli.js +90 -57
- package/src/colors.js +4 -4
- package/src/commands/configuration.js +16 -14
- package/src/commands/database.js +23 -13
- package/src/commands/environment.js +31 -19
- package/src/commands/images.js +22 -14
- package/src/commands/resources.js +16 -14
- package/src/commands/start.js +48 -34
- package/src/commands/stop.js +15 -9
- package/src/config.js +13 -13
- package/src/containers/_commons.js +15 -15
- package/src/containers/cloud-tools.js +22 -22
- package/src/containers/mysql.js +17 -17
- package/src/containers/orchestrator.js +28 -28
- package/src/containers/resource-manager.js +20 -20
- package/src/containers/resources-job.js +7 -7
- package/src/containers/state-store.js +22 -22
- package/src/containers/ui.js +14 -14
- package/src/defaults.js +32 -24
- package/src/docker.js +112 -53
- package/src/http.js +20 -10
- package/src/resources-loader.js +6 -6
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import {BIZONE_SERVICE_ENV} from "./_commons.js";
|
|
1
|
+
import { BIZONE_SERVICE_ENV } from "./_commons.js";
|
|
2
2
|
|
|
3
3
|
export const STATE_STORE_SERVICE = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
type: "state-store",
|
|
5
|
+
image: "699453144787.dkr.ecr.us-east-1.amazonaws.com/state-store:latest",
|
|
6
|
+
containerName: "state-store",
|
|
7
|
+
containerPort: 80,
|
|
8
|
+
portKey: "state_store_port",
|
|
9
|
+
defaultPort: "8088",
|
|
10
|
+
enableKey: "state_store_enable",
|
|
11
|
+
readyPath: "/.system/status",
|
|
12
|
+
readyCheck: "systemStatusOk",
|
|
13
|
+
defaultEnv: {
|
|
14
|
+
"http.port": "80",
|
|
15
|
+
"transport.config.endpoint": "http://bizone-res-manager/",
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
"storage.type": "mysql",
|
|
18
|
+
"storage.supported": "memory,mysql",
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
"mysql.storage": "jdbc:mysql://bizone-mysql:3306",
|
|
21
|
+
"mysql.storage.user": "root",
|
|
22
|
+
"mysql.storage.password.secret": "mysql/password",
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
"secret.mysql.password": "root",
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
26
|
+
...BIZONE_SERVICE_ENV,
|
|
27
|
+
},
|
|
28
|
+
};
|
package/src/containers/ui.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export const UI_SERVICE = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
2
|
+
type: "ui",
|
|
3
|
+
image: "699453144787.dkr.ecr.us-east-1.amazonaws.com/bizone-ui:latest",
|
|
4
|
+
containerName: "bizone-ui",
|
|
5
|
+
containerPort: 7006,
|
|
6
|
+
portKey: "ui_port",
|
|
7
|
+
defaultPort: "7006",
|
|
8
|
+
enableKey: null,
|
|
9
|
+
readyPath: "/actuator/health/readiness",
|
|
10
|
+
readyCheck: "uiReadinessUp",
|
|
11
|
+
defaultEnv: {
|
|
12
|
+
"resource-manager.url": "http://bizone-res-manager/",
|
|
13
|
+
"auth.type": "none",
|
|
14
|
+
},
|
|
15
|
+
};
|
package/src/defaults.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
// Static defaults and platform topology for the Bizone CLI.
|
|
2
2
|
|
|
3
|
-
import {ORCHESTRATOR_SERVICE} from "./containers/orchestrator.js";
|
|
4
|
-
import {RESOURCE_MANAGER_SERVICE} from "./containers/resource-manager.js";
|
|
5
|
-
import {STATE_STORE_SERVICE} from "./containers/state-store.js";
|
|
6
|
-
import {MYSQL_SERVICE} from "./containers/mysql.js";
|
|
7
|
-
import {CLOUD_TOOLS_SERVICE} from "./containers/cloud-tools.js";
|
|
8
|
-
import {UI_SERVICE} from "./containers/ui.js";
|
|
9
|
-
import {RESOURCES_JOB} from "./containers/resources-job.js";
|
|
3
|
+
import { ORCHESTRATOR_SERVICE } from "./containers/orchestrator.js";
|
|
4
|
+
import { RESOURCE_MANAGER_SERVICE } from "./containers/resource-manager.js";
|
|
5
|
+
import { STATE_STORE_SERVICE } from "./containers/state-store.js";
|
|
6
|
+
import { MYSQL_SERVICE } from "./containers/mysql.js";
|
|
7
|
+
import { CLOUD_TOOLS_SERVICE } from "./containers/cloud-tools.js";
|
|
8
|
+
import { UI_SERVICE } from "./containers/ui.js";
|
|
9
|
+
import { RESOURCES_JOB } from "./containers/resources-job.js";
|
|
10
10
|
|
|
11
11
|
export const CONTAINERS = {
|
|
12
12
|
mysql: MYSQL_SERVICE,
|
|
13
|
-
|
|
13
|
+
"resource-manager": RESOURCE_MANAGER_SERVICE,
|
|
14
14
|
orchestrator: ORCHESTRATOR_SERVICE,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
"state-store": STATE_STORE_SERVICE,
|
|
16
|
+
"cloud-tools": CLOUD_TOOLS_SERVICE,
|
|
17
17
|
ui: UI_SERVICE,
|
|
18
|
-
resources: RESOURCES_JOB
|
|
18
|
+
resources: RESOURCES_JOB,
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
// Valid image / environment "types" (a.k.a. container types).
|
|
@@ -26,39 +26,47 @@ export function defaultEnvFor(type) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Shared docker network name (default; overridable via the `network_name` config key).
|
|
29
|
-
export const NETWORK_NAME =
|
|
29
|
+
export const NETWORK_NAME = "bizone-local";
|
|
30
30
|
|
|
31
31
|
// Defaults for general `config` keys.
|
|
32
32
|
export const DEFAULT_CONFIG = {
|
|
33
|
-
forward_aws_env:
|
|
33
|
+
forward_aws_env: "true",
|
|
34
|
+
aws_profile: "",
|
|
34
35
|
network_name: NETWORK_NAME,
|
|
35
36
|
mysql_port: CONTAINERS.mysql.defaultPort,
|
|
36
37
|
orchestrator_port: CONTAINERS.orchestrator.defaultPort,
|
|
37
38
|
resource_manager_port: CONTAINERS["resource-manager"].defaultPort,
|
|
38
39
|
state_store_port: CONTAINERS["state-store"].defaultPort,
|
|
39
|
-
state_store_enable:
|
|
40
|
+
state_store_enable: "true",
|
|
40
41
|
cloud_tools_port: CONTAINERS["cloud-tools"].defaultPort,
|
|
41
|
-
cloud_tools_enable:
|
|
42
|
+
cloud_tools_enable: "false",
|
|
42
43
|
ui_port: CONTAINERS.ui.defaultPort,
|
|
43
|
-
timeout_sec:
|
|
44
|
+
timeout_sec: "300",
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
// Known config keys (used for validation / help).
|
|
47
48
|
export const CONFIG_KEYS = Object.keys(DEFAULT_CONFIG);
|
|
48
49
|
|
|
49
50
|
// AWS env vars forwarded into the orchestrator runtime when forward_aws_env=true.
|
|
50
|
-
export const AWS_ENV_VARS = [
|
|
51
|
+
export const AWS_ENV_VARS = [
|
|
52
|
+
"AWS_REGION",
|
|
53
|
+
"AWS_ACCESS_KEY_ID",
|
|
54
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
55
|
+
];
|
|
51
56
|
|
|
52
57
|
// Predefined secret env-var names. When showing env (`environment get`),
|
|
53
58
|
// values for these keys are masked. Matching is case-insensitive and also
|
|
54
59
|
// triggers on any key containing one of the SECRET_KEY_SUBSTRINGS below.
|
|
55
|
-
export const SECRET_ENV_KEYS = [
|
|
56
|
-
'AWS_SECRET_ACCESS_KEY',
|
|
57
|
-
'AWS_ACCESS_KEY_ID',
|
|
58
|
-
];
|
|
60
|
+
export const SECRET_ENV_KEYS = ["AWS_SECRET_ACCESS_KEY", "AWS_ACCESS_KEY_ID"];
|
|
59
61
|
|
|
60
|
-
export const SECRET_KEY_SUBSTRINGS = [
|
|
62
|
+
export const SECRET_KEY_SUBSTRINGS = [
|
|
63
|
+
"password",
|
|
64
|
+
"secret",
|
|
65
|
+
"token",
|
|
66
|
+
"apikey",
|
|
67
|
+
"api_key",
|
|
68
|
+
];
|
|
61
69
|
|
|
62
70
|
// Importing procedure constants.
|
|
63
|
-
export const IMPORT_USER =
|
|
64
|
-
export const IMPORT_MESSAGE =
|
|
71
|
+
export const IMPORT_USER = "bizone-cli";
|
|
72
|
+
export const IMPORT_MESSAGE = "resource initialization";
|
package/src/docker.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Docker helpers. All commands shell out to the local `docker` binary.
|
|
2
2
|
|
|
3
|
-
import { spawnSync, spawn } from
|
|
3
|
+
import { spawnSync, spawn } from "node:child_process";
|
|
4
4
|
|
|
5
5
|
function run(args, opts = {}) {
|
|
6
|
-
return spawnSync(
|
|
6
|
+
return spawnSync("docker", args, { encoding: "utf8", ...opts });
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
// --- AWS ECR auto-login ---------------------------------------------------
|
|
@@ -28,27 +28,35 @@ function parseEcr(image) {
|
|
|
28
28
|
|
|
29
29
|
// Authenticate docker to the ECR registry backing `image`, using the AWS CLI.
|
|
30
30
|
// No-op (returns false) for non-ECR images. Throws on failure.
|
|
31
|
-
function ecrLogin(ecr) {
|
|
31
|
+
function ecrLogin(ecr, profile) {
|
|
32
32
|
if (!ecr) return false;
|
|
33
33
|
if (loggedInRegistries.has(ecr.registry)) return true;
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const awsArgs = ["ecr", "get-login-password", "--region", ecr.region];
|
|
36
|
+
if (profile) awsArgs.push("--profile", profile);
|
|
37
|
+
|
|
38
|
+
const pw = spawnSync("aws", awsArgs, { encoding: "utf8" });
|
|
38
39
|
if (pw.status !== 0) {
|
|
40
|
+
const profileNote = profile ? ` --profile ${profile}` : "";
|
|
39
41
|
throw new Error(
|
|
40
|
-
`Failed to obtain ECR password (aws ecr get-login-password --region ${ecr.region}): ` +
|
|
41
|
-
`${(
|
|
42
|
+
`Failed to obtain ECR password (aws ecr get-login-password --region ${ecr.region}${profileNote}): ` +
|
|
43
|
+
`${(
|
|
44
|
+
pw.stderr ||
|
|
45
|
+
pw.error?.message ||
|
|
46
|
+
""
|
|
47
|
+
).trim()}. Is the AWS CLI installed and configured?`,
|
|
42
48
|
);
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
const login = spawnSync(
|
|
46
|
-
|
|
47
|
-
[
|
|
48
|
-
{ input: pw.stdout.trim(), encoding:
|
|
52
|
+
"docker",
|
|
53
|
+
["login", "--username", "AWS", "--password-stdin", ecr.registry],
|
|
54
|
+
{ input: pw.stdout.trim(), encoding: "utf8" },
|
|
49
55
|
);
|
|
50
56
|
if (login.status !== 0) {
|
|
51
|
-
throw new Error(
|
|
57
|
+
throw new Error(
|
|
58
|
+
`docker login to ${ecr.registry} failed: ${(login.stderr || "").trim()}`,
|
|
59
|
+
);
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
loggedInRegistries.add(ecr.registry);
|
|
@@ -59,62 +67,97 @@ function ecrLogin(ecr) {
|
|
|
59
67
|
// against an ECR image, logs in and retries once. Non-auth pull failures are
|
|
60
68
|
// ignored here (the image may already exist locally) and surfaced later by the
|
|
61
69
|
// actual `docker run`.
|
|
62
|
-
export function ensureImage(
|
|
70
|
+
export function ensureImage(
|
|
71
|
+
image,
|
|
72
|
+
{ onBeforePull, onBeforeLogin, awsProfile } = {},
|
|
73
|
+
) {
|
|
63
74
|
if (onBeforePull) onBeforePull(image);
|
|
64
|
-
let r = run([
|
|
75
|
+
let r = run(["pull", image]);
|
|
65
76
|
if (r.status === 0) return;
|
|
66
77
|
|
|
67
|
-
const out = `${r.stdout ||
|
|
78
|
+
const out = `${r.stdout || ""}${r.stderr || ""}`;
|
|
68
79
|
if (AUTH_ERR_RE.test(out)) {
|
|
69
80
|
const ecr = parseEcr(image);
|
|
70
81
|
if (ecr) {
|
|
71
82
|
if (onBeforeLogin) onBeforeLogin(ecr.registry);
|
|
72
|
-
ecrLogin(ecr);
|
|
83
|
+
ecrLogin(ecr, awsProfile);
|
|
73
84
|
if (onBeforePull) onBeforePull(image);
|
|
74
|
-
r = run([
|
|
85
|
+
r = run(["pull", image]);
|
|
75
86
|
if (r.status !== 0) {
|
|
76
|
-
throw new Error(
|
|
87
|
+
throw new Error(
|
|
88
|
+
`docker pull ${image} failed after ECR login: ${(
|
|
89
|
+
r.stderr || ""
|
|
90
|
+
).trim()}`,
|
|
91
|
+
);
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
94
|
}
|
|
80
95
|
}
|
|
81
96
|
|
|
82
97
|
export function dockerAvailable() {
|
|
83
|
-
const r = run([
|
|
98
|
+
const r = run(["version", "--format", "{{.Server.Version}}"]);
|
|
84
99
|
return r.status === 0;
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
// Create the shared network if it does not already exist. Never fails if present.
|
|
88
103
|
export function ensureNetwork(network) {
|
|
89
|
-
const ls = run([
|
|
90
|
-
|
|
104
|
+
const ls = run([
|
|
105
|
+
"network",
|
|
106
|
+
"ls",
|
|
107
|
+
"--filter",
|
|
108
|
+
`name=^${network}$`,
|
|
109
|
+
"--format",
|
|
110
|
+
"{{.Name}}",
|
|
111
|
+
]);
|
|
112
|
+
if (ls.status === 0 && ls.stdout.split("\n").includes(network)) {
|
|
91
113
|
return false; // already existed
|
|
92
114
|
}
|
|
93
|
-
const create = run([
|
|
94
|
-
if (create.status !== 0 && !/already exists/i.test(create.stderr ||
|
|
95
|
-
throw new Error(
|
|
115
|
+
const create = run(["network", "create", network]);
|
|
116
|
+
if (create.status !== 0 && !/already exists/i.test(create.stderr || "")) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Failed to create network ${network}: ${create.stderr.trim()}`,
|
|
119
|
+
);
|
|
96
120
|
}
|
|
97
121
|
return true; // created
|
|
98
122
|
}
|
|
99
123
|
|
|
100
124
|
// Run a container detached. Returns the full container id.
|
|
101
|
-
export function runDetached({
|
|
102
|
-
|
|
125
|
+
export function runDetached({
|
|
126
|
+
name,
|
|
127
|
+
hostPort,
|
|
128
|
+
containerPort,
|
|
129
|
+
image,
|
|
130
|
+
env = {},
|
|
131
|
+
volume = null,
|
|
132
|
+
network,
|
|
133
|
+
}) {
|
|
134
|
+
const args = [
|
|
135
|
+
"run",
|
|
136
|
+
"-d",
|
|
137
|
+
"--name",
|
|
138
|
+
name,
|
|
139
|
+
"-h",
|
|
140
|
+
name,
|
|
141
|
+
"--network",
|
|
142
|
+
network,
|
|
143
|
+
"--network-alias",
|
|
144
|
+
name,
|
|
145
|
+
];
|
|
103
146
|
if (hostPort != null && containerPort != null) {
|
|
104
|
-
args.push(
|
|
147
|
+
args.push("-p", `${hostPort}:${containerPort}`);
|
|
105
148
|
}
|
|
106
149
|
if (volume && volume.name && volume.mount) {
|
|
107
|
-
args.push(
|
|
150
|
+
args.push("-v", `${volume.name}:${volume.mount}`);
|
|
108
151
|
}
|
|
109
152
|
for (const [k, v] of Object.entries(env)) {
|
|
110
|
-
if (v === undefined || v === null || v ===
|
|
111
|
-
args.push(
|
|
153
|
+
if (v === undefined || v === null || v === "") continue;
|
|
154
|
+
args.push("-e", `${k}=${v}`);
|
|
112
155
|
}
|
|
113
156
|
args.push(image);
|
|
114
157
|
|
|
115
158
|
const r = run(args);
|
|
116
159
|
if (r.status !== 0) {
|
|
117
|
-
throw new Error(`docker run ${name} failed: ${(r.stderr ||
|
|
160
|
+
throw new Error(`docker run ${name} failed: ${(r.stderr || "").trim()}`);
|
|
118
161
|
}
|
|
119
162
|
return r.stdout.trim();
|
|
120
163
|
}
|
|
@@ -122,34 +165,47 @@ export function runDetached({ name, hostPort, containerPort, image, env = {}, vo
|
|
|
122
165
|
// Run a container in the foreground (blocking), removing it on exit.
|
|
123
166
|
export function runBlocking({ name, image, env = {}, network }) {
|
|
124
167
|
return new Promise((resolve, reject) => {
|
|
125
|
-
const args = [
|
|
168
|
+
const args = ["run", "--rm", "--name", name, "--network", network];
|
|
126
169
|
for (const [k, v] of Object.entries(env)) {
|
|
127
|
-
if (v === undefined || v === null || v ===
|
|
128
|
-
args.push(
|
|
170
|
+
if (v === undefined || v === null || v === "") continue;
|
|
171
|
+
args.push("-e", `${k}=${v}`);
|
|
129
172
|
}
|
|
130
173
|
args.push(image);
|
|
131
174
|
|
|
132
|
-
const child = spawn(
|
|
133
|
-
child.on(
|
|
134
|
-
child.on(
|
|
175
|
+
const child = spawn("docker", args, { stdio: "inherit" });
|
|
176
|
+
child.on("error", reject);
|
|
177
|
+
child.on("close", (code) => {
|
|
135
178
|
if (code === 0) resolve();
|
|
136
|
-
else
|
|
179
|
+
else
|
|
180
|
+
reject(
|
|
181
|
+
new Error(`Resources init job (${name}) exited with code ${code}`),
|
|
182
|
+
);
|
|
137
183
|
});
|
|
138
184
|
});
|
|
139
185
|
}
|
|
140
186
|
|
|
141
187
|
// Return the set of running container ids (full ids).
|
|
142
188
|
export function runningContainerIds() {
|
|
143
|
-
const r = run([
|
|
189
|
+
const r = run(["ps", "--no-trunc", "--format", "{{.ID}}"]);
|
|
144
190
|
if (r.status !== 0) return new Set();
|
|
145
|
-
return new Set(
|
|
191
|
+
return new Set(
|
|
192
|
+
r.stdout
|
|
193
|
+
.split("\n")
|
|
194
|
+
.map((s) => s.trim())
|
|
195
|
+
.filter(Boolean),
|
|
196
|
+
);
|
|
146
197
|
}
|
|
147
198
|
|
|
148
199
|
// Return the set of running container names.
|
|
149
200
|
export function runningContainerNames() {
|
|
150
|
-
const r = run([
|
|
201
|
+
const r = run(["ps", "--format", "{{.Names}}"]);
|
|
151
202
|
if (r.status !== 0) return new Set();
|
|
152
|
-
return new Set(
|
|
203
|
+
return new Set(
|
|
204
|
+
r.stdout
|
|
205
|
+
.split("\n")
|
|
206
|
+
.map((s) => s.trim())
|
|
207
|
+
.filter(Boolean),
|
|
208
|
+
);
|
|
153
209
|
}
|
|
154
210
|
|
|
155
211
|
export function isContainerRunning(name) {
|
|
@@ -158,33 +214,36 @@ export function isContainerRunning(name) {
|
|
|
158
214
|
|
|
159
215
|
// Remove a named docker volume. Returns true on success.
|
|
160
216
|
export function removeVolume(name) {
|
|
161
|
-
return run([
|
|
217
|
+
return run(["volume", "rm", name]).status === 0;
|
|
162
218
|
}
|
|
163
219
|
|
|
164
220
|
// Stop & remove a container by id or name. Ignores "no such container".
|
|
165
221
|
export function stopContainer(idOrName) {
|
|
166
|
-
const stop = run([
|
|
167
|
-
run([
|
|
222
|
+
const stop = run(["stop", idOrName]);
|
|
223
|
+
run(["rm", "-f", idOrName]); // best-effort cleanup
|
|
168
224
|
return stop.status === 0;
|
|
169
225
|
}
|
|
170
226
|
|
|
171
227
|
// Remove a container if it already exists (cleanup before re-running).
|
|
172
228
|
export function removeIfExists(name) {
|
|
173
|
-
run([
|
|
229
|
+
run(["rm", "-f", name]);
|
|
174
230
|
}
|
|
175
231
|
|
|
176
232
|
// Whether the MySQL server inside a container accepts connections.
|
|
177
|
-
export function mysqlReady(
|
|
233
|
+
export function mysqlReady(
|
|
234
|
+
containerName,
|
|
235
|
+
{ user = "root", password = "root" } = {},
|
|
236
|
+
) {
|
|
178
237
|
const r = run([
|
|
179
|
-
|
|
238
|
+
"exec",
|
|
180
239
|
containerName,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
240
|
+
"mysqladmin",
|
|
241
|
+
"ping",
|
|
242
|
+
"-h",
|
|
243
|
+
"127.0.0.1",
|
|
185
244
|
`-u${user}`,
|
|
186
245
|
`-p${password}`,
|
|
187
|
-
|
|
246
|
+
"--silent",
|
|
188
247
|
]);
|
|
189
248
|
return r.status === 0;
|
|
190
249
|
}
|
package/src/http.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// HTTP helpers: generic readiness waiting + resource import.
|
|
2
2
|
|
|
3
|
-
import { IMPORT_USER, IMPORT_MESSAGE } from
|
|
3
|
+
import { IMPORT_USER, IMPORT_MESSAGE } from "./defaults.js";
|
|
4
4
|
|
|
5
5
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6
6
|
|
|
7
7
|
// Readiness predicates keyed by name (see SERVICES[].readyCheck).
|
|
8
8
|
const CHECKS = {
|
|
9
|
-
systemStatusOk: (body) => body && body.status ===
|
|
10
|
-
uiReadinessUp: (body) => body && body.status && body.status.code ===
|
|
9
|
+
systemStatusOk: (body) => body && body.status === "OK",
|
|
10
|
+
uiReadinessUp: (body) => body && body.status && body.status.code === "UP",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
async function probe(url) {
|
|
14
14
|
try {
|
|
15
|
-
const res = await fetch(url, { method:
|
|
15
|
+
const res = await fetch(url, { method: "GET" });
|
|
16
16
|
if (!res.ok) return null;
|
|
17
17
|
const text = await res.text();
|
|
18
18
|
try {
|
|
@@ -27,7 +27,13 @@ async function probe(url) {
|
|
|
27
27
|
|
|
28
28
|
// Generic wait-for-ready. Polls `url`, applying the named check, until it
|
|
29
29
|
// passes or the timeout elapses. Returns true on success, throws on timeout.
|
|
30
|
-
export async function waitForReady({
|
|
30
|
+
export async function waitForReady({
|
|
31
|
+
url,
|
|
32
|
+
checkName,
|
|
33
|
+
timeoutSec,
|
|
34
|
+
label,
|
|
35
|
+
onTick,
|
|
36
|
+
}) {
|
|
31
37
|
const check = CHECKS[checkName];
|
|
32
38
|
if (!check) throw new Error(`Unknown readiness check: ${checkName}`);
|
|
33
39
|
|
|
@@ -40,7 +46,9 @@ export async function waitForReady({ url, checkName, timeoutSec, label, onTick }
|
|
|
40
46
|
if (onTick) onTick(attempt);
|
|
41
47
|
await sleep(1500);
|
|
42
48
|
}
|
|
43
|
-
throw new Error(
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Timed out waiting for ${label || url} to become ready (${timeoutSec}s)`,
|
|
51
|
+
);
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
// Import a set of items into a resource-manager namespace.
|
|
@@ -48,13 +56,15 @@ export async function importNamespace({ port, namespace, items }) {
|
|
|
48
56
|
const url = `http://localhost:${port}/admin/${namespace}/status`;
|
|
49
57
|
const payload = { user: IMPORT_USER, message: IMPORT_MESSAGE, items };
|
|
50
58
|
const res = await fetch(url, {
|
|
51
|
-
method:
|
|
52
|
-
headers: {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { "content-type": "application/json" },
|
|
53
61
|
body: JSON.stringify(payload),
|
|
54
62
|
});
|
|
55
63
|
if (!res.ok) {
|
|
56
|
-
const body = await res.text().catch(() =>
|
|
57
|
-
throw new Error(
|
|
64
|
+
const body = await res.text().catch(() => "");
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Import of namespace "${namespace}" failed: HTTP ${res.status} ${body}`,
|
|
67
|
+
);
|
|
58
68
|
}
|
|
59
69
|
return true;
|
|
60
70
|
}
|
package/src/resources-loader.js
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
// - single file with an "items" field: use that array
|
|
6
6
|
// - single file otherwise: the file contents become the sole item
|
|
7
7
|
|
|
8
|
-
import { readFileSync, statSync, readdirSync } from
|
|
9
|
-
import { join, isAbsolute, resolve } from
|
|
8
|
+
import { readFileSync, statSync, readdirSync } from "node:fs";
|
|
9
|
+
import { join, isAbsolute, resolve } from "node:path";
|
|
10
10
|
|
|
11
|
-
import { BIZONE_DIR } from
|
|
11
|
+
import { BIZONE_DIR } from "./config.js";
|
|
12
12
|
|
|
13
13
|
function readJson(path) {
|
|
14
|
-
const raw = readFileSync(path,
|
|
14
|
+
const raw = readFileSync(path, "utf8");
|
|
15
15
|
return JSON.parse(raw);
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -20,7 +20,7 @@ function collectJsonFiles(dir, out = []) {
|
|
|
20
20
|
const full = join(dir, entry.name);
|
|
21
21
|
if (entry.isDirectory()) {
|
|
22
22
|
collectJsonFiles(full, out);
|
|
23
|
-
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(
|
|
23
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".json")) {
|
|
24
24
|
out.push(full);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -43,7 +43,7 @@ export function loadItems(configuredPath) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const content = readJson(path);
|
|
46
|
-
if (content && typeof content ===
|
|
46
|
+
if (content && typeof content === "object" && Array.isArray(content.items)) {
|
|
47
47
|
return content.items;
|
|
48
48
|
}
|
|
49
49
|
return [content];
|