postgresai 0.17.0-rc.3 → 0.17.0-rc.4
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/dist/bin/postgres-ai.js +269 -17
- package/package.json +1 -1
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -13530,7 +13530,7 @@ var {
|
|
|
13530
13530
|
// package.json
|
|
13531
13531
|
var package_default = {
|
|
13532
13532
|
name: "postgresai",
|
|
13533
|
-
version: "0.17.0-rc.
|
|
13533
|
+
version: "0.17.0-rc.4",
|
|
13534
13534
|
description: "postgres_ai CLI",
|
|
13535
13535
|
license: "Apache-2.0",
|
|
13536
13536
|
private: false,
|
|
@@ -16890,7 +16890,7 @@ var Result = import_lib.default.Result;
|
|
|
16890
16890
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
16891
16891
|
var defaults = import_lib.default.defaults;
|
|
16892
16892
|
// package.json
|
|
16893
|
-
var version = "0.17.0-rc.
|
|
16893
|
+
var version = "0.17.0-rc.4";
|
|
16894
16894
|
var package_default2 = {
|
|
16895
16895
|
name: "postgresai",
|
|
16896
16896
|
version,
|
|
@@ -17223,6 +17223,22 @@ var TEXT_SENSITIVE_PAIR = /((?:password|passwd|db[-_]?pass|connstr|secret|token|
|
|
|
17223
17223
|
function redactTextSecrets(text) {
|
|
17224
17224
|
return text.replace(TEXT_URL_USERINFO, "$1:[REDACTED]@").replace(TEXT_SENSITIVE_PAIR, "$1[REDACTED]");
|
|
17225
17225
|
}
|
|
17226
|
+
function isControlCodePoint(cp) {
|
|
17227
|
+
return cp < 32 || cp === 127 || cp >= 128 && cp <= 159 || cp === 8232 || cp === 8233;
|
|
17228
|
+
}
|
|
17229
|
+
function stripControlsToSpace(text) {
|
|
17230
|
+
let out = "";
|
|
17231
|
+
for (const ch of text) {
|
|
17232
|
+
const cp = ch.codePointAt(0);
|
|
17233
|
+
out += cp !== undefined && isControlCodePoint(cp) ? " " : ch;
|
|
17234
|
+
}
|
|
17235
|
+
return out;
|
|
17236
|
+
}
|
|
17237
|
+
function whitespaceTolerantSecretPattern(secret) {
|
|
17238
|
+
const chars = Array.from(secret.replace(/\s+/g, ""));
|
|
17239
|
+
const body = chars.map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s*");
|
|
17240
|
+
return new RegExp(body, "g");
|
|
17241
|
+
}
|
|
17226
17242
|
function normalizeBaseUrl(value) {
|
|
17227
17243
|
const trimmed = (value || "").replace(/\/$/, "");
|
|
17228
17244
|
try {
|
|
@@ -30566,6 +30582,38 @@ async function resolveDatasourceId(adminPassword, debug = false) {
|
|
|
30566
30582
|
return null;
|
|
30567
30583
|
}
|
|
30568
30584
|
}
|
|
30585
|
+
function aasSuccessMessage(r) {
|
|
30586
|
+
const armed = r.armedFor === "pull" ? " (armed for pull: the platform will query this instance's Grafana)" : r.armedFor === "jobs" ? " (armed for the job channel: this instance answers its own queries)" : "";
|
|
30587
|
+
const base = `AAS auto-collection registered${armed}`;
|
|
30588
|
+
if (r.vcpus === 0) {
|
|
30589
|
+
return `${base} — collection stays OFF until a source-DB vCPU count is known (the platform stamps it at provision time; --vcpus <n> sets it for a manual install)`;
|
|
30590
|
+
}
|
|
30591
|
+
if (r.urlPending) {
|
|
30592
|
+
return `${base} — collection starts once this deploy reports the Grafana url to the platform`;
|
|
30593
|
+
}
|
|
30594
|
+
return base;
|
|
30595
|
+
}
|
|
30596
|
+
var PLATFORM_ERROR_MAX = 300;
|
|
30597
|
+
function formatPlatformError(body, secrets = []) {
|
|
30598
|
+
if (!body || typeof body !== "object")
|
|
30599
|
+
return "";
|
|
30600
|
+
const b = body;
|
|
30601
|
+
const str = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : "";
|
|
30602
|
+
const text = str(b.details) || str(b.message);
|
|
30603
|
+
if (!text)
|
|
30604
|
+
return "";
|
|
30605
|
+
const code = str(b.code);
|
|
30606
|
+
let out = stripControlsToSpace(code ? `${code}: ${text}` : text).replace(/\s+/g, " ").trim();
|
|
30607
|
+
for (const secret of secrets) {
|
|
30608
|
+
const compact = secret.replace(/\s+/g, "");
|
|
30609
|
+
if (compact.length < 8)
|
|
30610
|
+
continue;
|
|
30611
|
+
out = out.replace(whitespaceTolerantSecretPattern(compact), "[redacted]");
|
|
30612
|
+
}
|
|
30613
|
+
out = redactTextSecrets(out);
|
|
30614
|
+
out = out.replace(/glsa_[A-Za-z0-9_-]+/g, "[redacted]");
|
|
30615
|
+
return out.length > PLATFORM_ERROR_MAX ? `${out.slice(0, PLATFORM_ERROR_MAX - 1).trimEnd()}…` : out;
|
|
30616
|
+
}
|
|
30569
30617
|
async function registerAasCollection(apiKey, instanceId, opts) {
|
|
30570
30618
|
const debug = !!opts.debug;
|
|
30571
30619
|
try {
|
|
@@ -30615,11 +30663,19 @@ async function registerAasCollection(apiKey, instanceId, opts) {
|
|
|
30615
30663
|
})
|
|
30616
30664
|
});
|
|
30617
30665
|
if (!res.ok) {
|
|
30666
|
+
const detail = formatPlatformError(await res.json().catch(() => null), [apiKey, saToken]);
|
|
30667
|
+
const suffix = detail ? ` — ${detail}` : "";
|
|
30618
30668
|
if (debug)
|
|
30619
|
-
console.error(`Debug: AAS register failed: HTTP ${res.status}`);
|
|
30620
|
-
return { ok: false, reason: `platform returned HTTP ${res.status}` };
|
|
30669
|
+
console.error(`Debug: AAS register failed: HTTP ${res.status}${suffix}`);
|
|
30670
|
+
return { ok: false, reason: `platform returned HTTP ${res.status}${suffix}` };
|
|
30621
30671
|
}
|
|
30622
|
-
|
|
30672
|
+
const reply = await res.json().catch(() => null);
|
|
30673
|
+
return {
|
|
30674
|
+
ok: true,
|
|
30675
|
+
vcpus: typeof reply?.vcpus === "number" ? reply.vcpus : undefined,
|
|
30676
|
+
urlPending: typeof reply?.url_pending === "boolean" ? reply.url_pending : undefined,
|
|
30677
|
+
armedFor: typeof reply?.armed_for === "string" ? reply.armed_for : undefined
|
|
30678
|
+
};
|
|
30623
30679
|
} catch (err) {
|
|
30624
30680
|
if (debug)
|
|
30625
30681
|
console.error(`Debug: AAS register error: ${err.message}`);
|
|
@@ -38950,8 +39006,41 @@ var LOCAL_INSTALL_MANAGED_ENV_KEYS = [
|
|
|
38950
39006
|
"VM_FORCE_MERGE_AUTH_KEY",
|
|
38951
39007
|
"VM_PPROF_AUTH_KEY",
|
|
38952
39008
|
"PGAI_INSTANCE_ID",
|
|
38953
|
-
"INSTANCE_JOBS_USER"
|
|
39009
|
+
"INSTANCE_JOBS_USER",
|
|
39010
|
+
"COMPOSE_PROFILES"
|
|
38954
39011
|
];
|
|
39012
|
+
var INSTANCE_JOBS_PROFILE = "instance-jobs";
|
|
39013
|
+
function composeProfilesValue(existing, want) {
|
|
39014
|
+
const profiles = [];
|
|
39015
|
+
for (const raw of (existing ?? "").split(",")) {
|
|
39016
|
+
const token = raw.trim();
|
|
39017
|
+
if (token && !profiles.includes(token))
|
|
39018
|
+
profiles.push(token);
|
|
39019
|
+
}
|
|
39020
|
+
const at = profiles.indexOf(INSTANCE_JOBS_PROFILE);
|
|
39021
|
+
if (want === true && at === -1)
|
|
39022
|
+
profiles.push(INSTANCE_JOBS_PROFILE);
|
|
39023
|
+
if (want === false && at !== -1)
|
|
39024
|
+
profiles.splice(at, 1);
|
|
39025
|
+
return profiles.length > 0 ? profiles.join(",") : null;
|
|
39026
|
+
}
|
|
39027
|
+
function instanceJobsProfileEnabled(composeProfiles) {
|
|
39028
|
+
return (composeProfiles ?? "").split(",").map((p) => p.trim()).includes(INSTANCE_JOBS_PROFILE);
|
|
39029
|
+
}
|
|
39030
|
+
function resolveInstanceJobsRequest(flag, envValue) {
|
|
39031
|
+
if (flag !== undefined)
|
|
39032
|
+
return { enabled: flag };
|
|
39033
|
+
const value = (envValue ?? "").trim().toLowerCase();
|
|
39034
|
+
if (value === "")
|
|
39035
|
+
return {};
|
|
39036
|
+
if (["1", "true", "yes", "on"].includes(value))
|
|
39037
|
+
return { enabled: true };
|
|
39038
|
+
if (["0", "false", "no", "off"].includes(value))
|
|
39039
|
+
return { enabled: false };
|
|
39040
|
+
return {
|
|
39041
|
+
error: `PGAI_INSTANCE_JOBS=${envValue} is not a boolean ` + "(use true/false, 1/0, yes/no, on/off, or leave it empty to keep the current setting)"
|
|
39042
|
+
};
|
|
39043
|
+
}
|
|
38955
39044
|
function statOwner(file) {
|
|
38956
39045
|
if (!file)
|
|
38957
39046
|
return;
|
|
@@ -38970,7 +39059,7 @@ function processOwner() {
|
|
|
38970
39059
|
return null;
|
|
38971
39060
|
return `${process.getuid()}:${process.getgid()}`;
|
|
38972
39061
|
}
|
|
38973
|
-
function buildLocalInstallEnv(existingEnv, imageTag, instanceId, credentialFile) {
|
|
39062
|
+
function buildLocalInstallEnv(existingEnv, imageTag, instanceId, credentialFile, instanceJobs) {
|
|
38974
39063
|
const readRaw = (key) => {
|
|
38975
39064
|
const re = new RegExp(`^[ ]*(?:export[ ]+)?${key}=(.*)$`, "gm");
|
|
38976
39065
|
let value = null;
|
|
@@ -39002,6 +39091,10 @@ function buildLocalInstallEnv(existingEnv, imageTag, instanceId, credentialFile)
|
|
|
39002
39091
|
const credentialOwner = owner === undefined ? processOwner() : owner;
|
|
39003
39092
|
if (credentialOwner)
|
|
39004
39093
|
envLines.push(`INSTANCE_JOBS_USER=${credentialOwner}`);
|
|
39094
|
+
const existingProfiles = readRaw("COMPOSE_PROFILES");
|
|
39095
|
+
const composeProfiles = composeProfilesValue(existingProfiles === null ? null : stripMatchingQuotes(existingProfiles), instanceJobs);
|
|
39096
|
+
if (composeProfiles)
|
|
39097
|
+
envLines.push(`COMPOSE_PROFILES=${composeProfiles}`);
|
|
39005
39098
|
for (const key of VM_ADMIN_AUTH_KEYS) {
|
|
39006
39099
|
const existing = readRaw(key);
|
|
39007
39100
|
envLines.push(`${key}=${existing ? stripMatchingQuotes(existing) : crypto2.randomBytes(32).toString("hex")}`);
|
|
@@ -41053,7 +41146,7 @@ async function applyMonitoringTargetsConfig() {
|
|
|
41053
41146
|
console.log("Restarting pgwatch collectors to pick up target changes...");
|
|
41054
41147
|
return runCompose(["up", "-d", "--force-recreate", "pgwatch-prometheus", "pgwatch-postgres"]);
|
|
41055
41148
|
}
|
|
41056
|
-
async function runCompose(args, grafanaPassword) {
|
|
41149
|
+
async function runCompose(args, grafanaPassword, extraEnv) {
|
|
41057
41150
|
let composeFile;
|
|
41058
41151
|
let projectDir;
|
|
41059
41152
|
try {
|
|
@@ -41117,6 +41210,8 @@ async function runCompose(args, grafanaPassword) {
|
|
|
41117
41210
|
}
|
|
41118
41211
|
}
|
|
41119
41212
|
}
|
|
41213
|
+
if (extraEnv)
|
|
41214
|
+
Object.assign(env, extraEnv);
|
|
41120
41215
|
const finalArgs = [...args];
|
|
41121
41216
|
if (process.platform === "darwin" && shouldScaleOutNodeExporter(args)) {
|
|
41122
41217
|
finalArgs.push("--scale", "self-node-exporter=0");
|
|
@@ -41175,6 +41270,127 @@ Applying sink-prometheus config (VictoriaMetrics admin keys, #359)...`);
|
|
|
41175
41270
|
console.log("\u2713 sink-prometheus is running the current config");
|
|
41176
41271
|
return true;
|
|
41177
41272
|
}
|
|
41273
|
+
function readComposeProfiles(knownProjectDir) {
|
|
41274
|
+
const fromProcess = process.env.COMPOSE_PROFILES;
|
|
41275
|
+
if (fromProcess !== undefined && fromProcess.trim() !== "")
|
|
41276
|
+
return fromProcess;
|
|
41277
|
+
let projectDir;
|
|
41278
|
+
if (knownProjectDir) {
|
|
41279
|
+
projectDir = knownProjectDir;
|
|
41280
|
+
} else {
|
|
41281
|
+
try {
|
|
41282
|
+
({ projectDir } = resolvePaths());
|
|
41283
|
+
} catch {
|
|
41284
|
+
projectDir = getDefaultMonitoringProjectDir();
|
|
41285
|
+
}
|
|
41286
|
+
}
|
|
41287
|
+
const envFile = path7.resolve(projectDir, ".env");
|
|
41288
|
+
let content;
|
|
41289
|
+
try {
|
|
41290
|
+
content = fs9.readFileSync(envFile, "utf8");
|
|
41291
|
+
} catch (err) {
|
|
41292
|
+
if (err?.code !== "ENOENT") {
|
|
41293
|
+
console.error(`\u26A0 Could not read ${envFile}: ${err instanceof Error ? err.message : String(err)}`);
|
|
41294
|
+
console.error(" Treating the instance-jobs profile as disabled; if it is in fact enabled,");
|
|
41295
|
+
console.error(" an absent container will be reported as '- not enabled' rather than a fault.");
|
|
41296
|
+
}
|
|
41297
|
+
return null;
|
|
41298
|
+
}
|
|
41299
|
+
let value = null;
|
|
41300
|
+
for (const m of content.matchAll(/^[ \t]*(?:export[ \t]+)?COMPOSE_PROFILES=(.*)$/gm)) {
|
|
41301
|
+
value = stripMatchingQuotes(m[1].trim());
|
|
41302
|
+
}
|
|
41303
|
+
return value;
|
|
41304
|
+
}
|
|
41305
|
+
function instanceJobsRemovalOutcome(before, after, removeExitCode) {
|
|
41306
|
+
if (before === "unknown" || after === "unknown") {
|
|
41307
|
+
return {
|
|
41308
|
+
ok: false,
|
|
41309
|
+
message: `\u2717 Could not determine whether the instance-jobs container is present.
|
|
41310
|
+
` + " The profile is off in .env, but a container left behind keeps polling."
|
|
41311
|
+
};
|
|
41312
|
+
}
|
|
41313
|
+
if (before === "absent") {
|
|
41314
|
+
return { ok: true, message: "\u2713 instance-jobs is disabled (no container was present)" };
|
|
41315
|
+
}
|
|
41316
|
+
if (removeExitCode !== 0 || after !== "absent") {
|
|
41317
|
+
return {
|
|
41318
|
+
ok: false,
|
|
41319
|
+
message: `\u2717 Could not remove the instance-jobs container.
|
|
41320
|
+
` + ` The profile is off in .env, but the container is still there and still polling. To retry:
|
|
41321
|
+
` + ` COMPOSE_PROFILES=${INSTANCE_JOBS_PROFILE} docker compose rm -sf ${INSTANCE_JOBS_PROFILE}`
|
|
41322
|
+
};
|
|
41323
|
+
}
|
|
41324
|
+
return before === "running" ? {
|
|
41325
|
+
ok: true,
|
|
41326
|
+
message: "\u26A0 instance-jobs was RUNNING and has been removed \u2014 outbound collection on this machine is now OFF"
|
|
41327
|
+
} : { ok: true, message: "\u2713 instance-jobs container removed (it was not running)" };
|
|
41328
|
+
}
|
|
41329
|
+
function instanceJobsStartSkipReason(storeRunning) {
|
|
41330
|
+
if (storeRunning === true)
|
|
41331
|
+
return null;
|
|
41332
|
+
return storeRunning === false ? "the stack is not running, so instance-jobs was not started - it comes up with the rest on `mon start`" : "could not tell whether the stack is running, so instance-jobs was not started - run `mon start`, then `mon health`";
|
|
41333
|
+
}
|
|
41334
|
+
async function composeServiceRunning(projectDir, service) {
|
|
41335
|
+
const composeCmd = getComposeCmd();
|
|
41336
|
+
if (!composeCmd)
|
|
41337
|
+
return null;
|
|
41338
|
+
try {
|
|
41339
|
+
const { stdout } = await execFilePromise(composeCmd[0], [...composeCmd.slice(1), "-f", path7.resolve(projectDir, "docker-compose.yml"), "ps", "-q", service], { cwd: projectDir });
|
|
41340
|
+
return stdout.trim().length > 0;
|
|
41341
|
+
} catch {
|
|
41342
|
+
return null;
|
|
41343
|
+
}
|
|
41344
|
+
}
|
|
41345
|
+
async function applyInstanceJobsProfile(projectDir, enabled) {
|
|
41346
|
+
if (enabled === undefined)
|
|
41347
|
+
return true;
|
|
41348
|
+
if (enabled) {
|
|
41349
|
+
const skip = instanceJobsStartSkipReason(await composeServiceRunning(projectDir, "sink-prometheus"));
|
|
41350
|
+
if (skip) {
|
|
41351
|
+
console.log(`
|
|
41352
|
+
(${skip})`);
|
|
41353
|
+
return true;
|
|
41354
|
+
}
|
|
41355
|
+
console.log(`
|
|
41356
|
+
Starting the instance-jobs container (compose profile enabled)...`);
|
|
41357
|
+
const code = await runCompose(["up", "-d", "--no-deps", INSTANCE_JOBS_PROFILE]);
|
|
41358
|
+
if (code !== 0) {
|
|
41359
|
+
console.error("\u2717 Could not start the instance-jobs container.");
|
|
41360
|
+
console.error(" The channel is enabled in .env but nothing is polling. To retry:");
|
|
41361
|
+
console.error(` docker compose up -d --no-deps ${INSTANCE_JOBS_PROFILE}`);
|
|
41362
|
+
return false;
|
|
41363
|
+
}
|
|
41364
|
+
console.log("\u2713 instance-jobs is running");
|
|
41365
|
+
return true;
|
|
41366
|
+
}
|
|
41367
|
+
const before = await inspectInstanceJobsState();
|
|
41368
|
+
if (before === "absent" || before === "unknown") {
|
|
41369
|
+
const verdict = instanceJobsRemovalOutcome(before, before, 0);
|
|
41370
|
+
(verdict.ok ? console.log : console.error)(`
|
|
41371
|
+
${verdict.message}`);
|
|
41372
|
+
return verdict.ok;
|
|
41373
|
+
}
|
|
41374
|
+
const code = await runCompose(["rm", "-sf", INSTANCE_JOBS_PROFILE], undefined, { COMPOSE_PROFILES: INSTANCE_JOBS_PROFILE });
|
|
41375
|
+
const verdict = instanceJobsRemovalOutcome(before, await inspectInstanceJobsState(), code);
|
|
41376
|
+
(verdict.ok ? console.log : console.error)(`
|
|
41377
|
+
${verdict.message}`);
|
|
41378
|
+
return verdict.ok;
|
|
41379
|
+
}
|
|
41380
|
+
async function inspectInstanceJobsState() {
|
|
41381
|
+
try {
|
|
41382
|
+
const { stdout } = await execFilePromise("docker", [
|
|
41383
|
+
"inspect",
|
|
41384
|
+
"-f",
|
|
41385
|
+
"{{.State.Running}}",
|
|
41386
|
+
INSTANCE_JOBS_PROFILE
|
|
41387
|
+
]);
|
|
41388
|
+
return stdout.trim() === "true" ? "running" : "stopped";
|
|
41389
|
+
} catch (error) {
|
|
41390
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
41391
|
+
return /no such (object|container)/i.test(text) ? "absent" : "unknown";
|
|
41392
|
+
}
|
|
41393
|
+
}
|
|
41178
41394
|
program2.command("help").description("show help").action(() => {
|
|
41179
41395
|
program2.outputHelp();
|
|
41180
41396
|
});
|
|
@@ -41188,7 +41404,7 @@ mon.command("local-install").description("install local monitoring stack (genera
|
|
|
41188
41404
|
" PGAI_ENABLE_IPV6=false (accepted: true|false|yes|no, lowercase)",
|
|
41189
41405
|
""
|
|
41190
41406
|
].join(`
|
|
41191
|
-
`)).option("--demo", "demo mode with sample database", false).option("--api-key <key>", "Postgres AI API key for automated report uploads").option("--org <alias>", `organization alias (or ${ORG_ENV}); required to register with a global token`).option("--org-id <id>", `organization id (or ${ORG_ID_ENV}); alternative to --org`).option("--db-url <url>", "PostgreSQL connection URL to monitor").option("--tag <tag>", "Docker image tag to use (e.g., 0.14.0, 0.14.0-dev.33)").option("--project <name>", "Docker Compose project name (default: postgres_ai)").option("--instance-id <uuid>", "adopt a console-provisioned monitoring instance instead of self-registering a new one (set automatically by the provisioning flow; PGAI_INSTANCE_ID env also works)").option("--vcpus <n>", "source DB vCPU count used for AAS zone thresholds (PGAI_VCPUS env also works). Omit or 0 = unknown: the platform keeps the value it stamped at provision time. An explicit non-zero value overwrites the stored one; changing it re-arms the AAS backfill.").option("-y, --yes", "accept all defaults and skip interactive prompts", false).action(async (opts) => {
|
|
41407
|
+
`)).option("--demo", "demo mode with sample database", false).option("--api-key <key>", "Postgres AI API key for automated report uploads").option("--org <alias>", `organization alias (or ${ORG_ENV}); required to register with a global token`).option("--org-id <id>", `organization id (or ${ORG_ID_ENV}); alternative to --org`).option("--db-url <url>", "PostgreSQL connection URL to monitor").option("--tag <tag>", "Docker image tag to use (e.g., 0.14.0, 0.14.0-dev.33)").option("--project <name>", "Docker Compose project name (default: postgres_ai)").option("--instance-id <uuid>", "adopt a console-provisioned monitoring instance instead of self-registering a new one (set automatically by the provisioning flow; PGAI_INSTANCE_ID env also works)").option("--vcpus <n>", "source DB vCPU count used for AAS zone thresholds (PGAI_VCPUS env also works). Omit or 0 = unknown: the platform keeps the value it stamped at provision time. An explicit non-zero value overwrites the stored one; changing it re-arms the AAS backfill.").option("--instance-jobs", "enable the instance-jobs compose profile (outbound collection channel; PGAI_INSTANCE_JOBS env also works). Writes COMPOSE_PROFILES into the stack .env, so every later mon command covers the container").option("--no-instance-jobs", "disable the instance-jobs compose profile and remove its container").option("-y, --yes", "accept all defaults and skip interactive prompts", false).action(async (opts) => {
|
|
41192
41408
|
const globalOpts = program2.opts();
|
|
41193
41409
|
let apiKey = opts.apiKey || globalOpts.apiKey;
|
|
41194
41410
|
console.log(`
|
|
@@ -41210,7 +41426,14 @@ mon.command("local-install").description("install local monitoring stack (genera
|
|
|
41210
41426
|
const previousTagMatch = existingEnv.match(/^PGAI_TAG=(.+)$/m);
|
|
41211
41427
|
const previousTag = previousTagMatch ? previousTagMatch[1].trim() : null;
|
|
41212
41428
|
const imageTag = opts.tag || package_default.version;
|
|
41213
|
-
const
|
|
41429
|
+
const instanceJobsRequest = resolveInstanceJobsRequest(opts.instanceJobs, process.env.PGAI_INSTANCE_JOBS);
|
|
41430
|
+
if (instanceJobsRequest.error) {
|
|
41431
|
+
console.error(`\u2717 ${instanceJobsRequest.error}`);
|
|
41432
|
+
process.exitCode = 1;
|
|
41433
|
+
return;
|
|
41434
|
+
}
|
|
41435
|
+
const instanceJobs = instanceJobsRequest.enabled;
|
|
41436
|
+
const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag, opts.instanceId || process.env.PGAI_INSTANCE_ID || null, path7.resolve(projectDir, ".pgwatch-config"), instanceJobs);
|
|
41214
41437
|
writeEnvFile(envFile, envContent);
|
|
41215
41438
|
if (preservedKeys.length > 0) {
|
|
41216
41439
|
console.log(`Preserved existing .env settings: ${preservedKeys.join(", ")}
|
|
@@ -41241,6 +41464,8 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
|
|
|
41241
41464
|
console.error(`\u26A0 Monitoring services are already running: ${containers.join(", ")}`);
|
|
41242
41465
|
console.log(`Use 'postgres-ai mon restart' to restart them
|
|
41243
41466
|
`);
|
|
41467
|
+
if (!await applyInstanceJobsProfile(projectDir, instanceJobs))
|
|
41468
|
+
process.exitCode = 1;
|
|
41244
41469
|
return;
|
|
41245
41470
|
}
|
|
41246
41471
|
if (!opts.demo) {
|
|
@@ -41530,6 +41755,9 @@ Searched: ${demoCandidates.join(", ")}
|
|
|
41530
41755
|
}
|
|
41531
41756
|
console.log(`\u2713 Services started
|
|
41532
41757
|
`);
|
|
41758
|
+
const instanceJobsApplied = await applyInstanceJobsProfile(projectDir, instanceJobs);
|
|
41759
|
+
if (!instanceJobsApplied)
|
|
41760
|
+
process.exitCode = 1;
|
|
41533
41761
|
if (apiKey && !opts.demo) {
|
|
41534
41762
|
let regOrgScope;
|
|
41535
41763
|
if (isGlobalTokenValue(apiKey)) {
|
|
@@ -41571,7 +41799,7 @@ Searched: ${demoCandidates.join(", ")}
|
|
|
41571
41799
|
debug: !!process.env.DEBUG
|
|
41572
41800
|
});
|
|
41573
41801
|
if (aas.ok) {
|
|
41574
|
-
console.log(`\u2713
|
|
41802
|
+
console.log(`\u2713 ${aasSuccessMessage(aas)}
|
|
41575
41803
|
`);
|
|
41576
41804
|
} else {
|
|
41577
41805
|
console.error(`\u26A0 AAS auto-collection not registered (${aas.reason}); it can be enabled later by re-running 'postgresai mon local-install'
|
|
@@ -41590,9 +41818,15 @@ Searched: ${demoCandidates.join(", ")}
|
|
|
41590
41818
|
}
|
|
41591
41819
|
}
|
|
41592
41820
|
console.log("=================================");
|
|
41593
|
-
console.log(" Local install completed!");
|
|
41821
|
+
console.log(instanceJobsApplied ? " Local install completed!" : " Local install completed WITH ONE FAILURE");
|
|
41594
41822
|
console.log(`=================================
|
|
41595
41823
|
`);
|
|
41824
|
+
if (!instanceJobsApplied) {
|
|
41825
|
+
console.log(instanceJobs ? `\u2717 The instance-jobs container did not start. Everything below is running; the outbound
|
|
41826
|
+
` + " collection channel is not. See the error above, then `postgres-ai mon health`.\n" : `\u2717 The instance-jobs container could not be removed. Everything below is running, and so
|
|
41827
|
+
` + ` is a container that keeps polling. See the error above.
|
|
41828
|
+
`);
|
|
41829
|
+
}
|
|
41596
41830
|
console.log("What's running:");
|
|
41597
41831
|
if (opts.demo) {
|
|
41598
41832
|
console.log(" \u2705 Demo PostgreSQL database (monitoring target)");
|
|
@@ -41707,7 +41941,7 @@ mon.command("logs [service]").option("-f, --follow", "follow logs", false).optio
|
|
|
41707
41941
|
});
|
|
41708
41942
|
function judgeServiceHealth(service, present, status, health) {
|
|
41709
41943
|
if (!present) {
|
|
41710
|
-
return service.optional ? { ok: true, mark: "-", label: "not enabled" } : { ok: false, mark: "\u2717", label: "unreachable" };
|
|
41944
|
+
return service.optional ? { ok: true, mark: "-", label: "not enabled" } : { ok: false, mark: "\u2717", label: service.absentLabel ?? "unreachable" };
|
|
41711
41945
|
}
|
|
41712
41946
|
if (status !== "running") {
|
|
41713
41947
|
return { ok: false, mark: "\u2717", label: `unhealthy (status: ${status})` };
|
|
@@ -41717,7 +41951,7 @@ function judgeServiceHealth(service, present, status, health) {
|
|
|
41717
41951
|
}
|
|
41718
41952
|
return { ok: true, mark: "\u2713", label: "healthy" };
|
|
41719
41953
|
}
|
|
41720
|
-
function monitoringHealthServices() {
|
|
41954
|
+
function monitoringHealthServices(instanceJobsEnabled = false) {
|
|
41721
41955
|
return [
|
|
41722
41956
|
{ name: "Grafana", container: "grafana-with-datasources" },
|
|
41723
41957
|
{ name: "Prometheus", container: "sink-prometheus" },
|
|
@@ -41725,11 +41959,17 @@ function monitoringHealthServices() {
|
|
|
41725
41959
|
{ name: "PGWatch (Prometheus)", container: "pgwatch-prometheus" },
|
|
41726
41960
|
{ name: "Target DB", container: "target-db" },
|
|
41727
41961
|
{ name: "Sink Postgres", container: "sink-postgres" },
|
|
41728
|
-
{
|
|
41962
|
+
{
|
|
41963
|
+
name: "Instance jobs",
|
|
41964
|
+
container: "instance-jobs",
|
|
41965
|
+
optional: !instanceJobsEnabled,
|
|
41966
|
+
readHealth: true,
|
|
41967
|
+
absentLabel: "enabled but not running"
|
|
41968
|
+
}
|
|
41729
41969
|
];
|
|
41730
41970
|
}
|
|
41731
41971
|
mon.command("health").description("health check for monitoring services").option("--wait <seconds>", "wait time in seconds for services to become healthy", parseInt, 0).action(async (opts) => {
|
|
41732
|
-
const services = monitoringHealthServices();
|
|
41972
|
+
const services = monitoringHealthServices(instanceJobsProfileEnabled(readComposeProfiles()));
|
|
41733
41973
|
const waitTime = opts.wait || 0;
|
|
41734
41974
|
const maxAttempts = waitTime > 0 ? Math.ceil(waitTime / 5) : 1;
|
|
41735
41975
|
console.log(`Checking service health...
|
|
@@ -41863,6 +42103,12 @@ Updating Docker images...`);
|
|
|
41863
42103
|
process.exitCode = 1;
|
|
41864
42104
|
return;
|
|
41865
42105
|
}
|
|
42106
|
+
if (instanceJobsProfileEnabled(readComposeProfiles(projectDir))) {
|
|
42107
|
+
if (!await applyInstanceJobsProfile(projectDir, true)) {
|
|
42108
|
+
process.exitCode = 1;
|
|
42109
|
+
return;
|
|
42110
|
+
}
|
|
42111
|
+
}
|
|
41866
42112
|
console.log(`
|
|
41867
42113
|
\u2713 Update completed successfully`);
|
|
41868
42114
|
console.log(`
|
|
@@ -42617,7 +42863,7 @@ withOrgOptions(program2.command("promql")).description("run a PromQL query on a
|
|
|
42617
42863
|
});
|
|
42618
42864
|
if (!isTerminal2(res.status)) {
|
|
42619
42865
|
console.error(`Timed out after ${timeoutS}s. Job ${jobId} is still ${res.status}.`);
|
|
42620
|
-
console.error(res.status === "queued" ? "Nothing has claimed it: check the instance-jobs container is running (it is behind " + "
|
|
42866
|
+
console.error(res.status === "queued" ? "Nothing has claimed it: check the instance-jobs container is running on that instance " + "(`postgresai mon health`; it is behind a compose profile, enabled with " + "`mon local-install --instance-jobs`), or wait \u2014 a first collection can queue " + "~20 periods ahead of it." : "The instance is working on it \u2014 wait, or re-run with a longer --timeout.");
|
|
42621
42867
|
process.exitCode = 1;
|
|
42622
42868
|
return;
|
|
42623
42869
|
}
|
|
@@ -43927,17 +44173,23 @@ export {
|
|
|
43927
44173
|
applyApiKey,
|
|
43928
44174
|
applyProjectName,
|
|
43929
44175
|
buildLocalInstallEnv,
|
|
44176
|
+
composeProfilesValue,
|
|
43930
44177
|
instanceIdToPersist,
|
|
44178
|
+
instanceJobsProfileEnabled,
|
|
44179
|
+
instanceJobsRemovalOutcome,
|
|
44180
|
+
instanceJobsStartSkipReason,
|
|
43931
44181
|
isValidComposeYaml,
|
|
43932
44182
|
judgeServiceHealth,
|
|
43933
44183
|
monitoringHealthServices,
|
|
43934
44184
|
persistInstanceId,
|
|
43935
44185
|
planMonitoringRegistration,
|
|
43936
44186
|
program2 as program,
|
|
44187
|
+
readComposeProfiles,
|
|
43937
44188
|
readDeployedTag,
|
|
43938
44189
|
refreshBundledComposeIfStale,
|
|
43939
44190
|
registerMonitoringInstance,
|
|
43940
44191
|
resolveAdoptedProject,
|
|
44192
|
+
resolveInstanceJobsRequest,
|
|
43941
44193
|
selfRegisterAndPersist,
|
|
43942
44194
|
shouldScaleOutNodeExporter,
|
|
43943
44195
|
statOwner,
|