blokctl 1.1.0 → 1.2.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/dist/commands/create/project.d.ts +2 -0
- package/dist/commands/create/project.js +13 -10
- package/dist/commands/create/utils/Examples.d.ts +2 -2
- package/dist/commands/create/utils/Examples.js +69 -103
- package/dist/commands/create/workflow.js +8 -6
- package/dist/commands/dev/index.js +26 -0
- package/dist/commands/install/node.d.ts +1 -0
- package/dist/commands/install/node.js +3 -39
- package/dist/commands/migrate/index.js +25 -0
- package/dist/commands/migrate/nodesTs.d.ts +31 -0
- package/dist/commands/migrate/nodesTs.js +484 -0
- package/dist/commands/migrate/refs.d.ts +15 -0
- package/dist/commands/migrate/refs.js +706 -0
- package/dist/commands/nodes/index.js +10 -0
- package/dist/commands/nodes/listNodes.d.ts +2 -0
- package/dist/commands/nodes/listNodes.js +20 -15
- package/dist/commands/nodes/syncNodes.d.ts +7 -0
- package/dist/commands/nodes/syncNodes.js +138 -0
- package/dist/commands/nodes/syncNodes.test.d.ts +1 -0
- package/dist/commands/nodes/syncNodes.test.js +322 -0
- package/dist/commands/publish/workflow.d.ts +1 -0
- package/dist/commands/publish/workflow.js +9 -0
- package/dist/commands/publish/workflow.test.d.ts +1 -0
- package/dist/commands/publish/workflow.test.js +22 -0
- package/dist/services/observability-stack.real-prometheus.test.d.ts +1 -0
- package/dist/services/observability-stack.real-prometheus.test.js +95 -0
- package/dist/services/observability-stack.real-tempo.test.d.ts +1 -0
- package/dist/services/observability-stack.real-tempo.test.js +86 -0
- package/dist/studio-dist/assets/index-QEvKRE4T.js +42 -0
- package/dist/studio-dist/index.html +1 -1
- package/package.json +3 -2
- package/dist/studio-dist/assets/index-CnFqCRQe.js +0 -42
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
const RUN = process.env.BLOK_INTEGRATION_PROMETHEUS;
|
|
5
|
+
const d = RUN ? describe : describe.skip;
|
|
6
|
+
const PROMETHEUS_API = process.env.BLOK_PROMETHEUS_API_URL ?? "http://localhost:9090";
|
|
7
|
+
function run(command, args) {
|
|
8
|
+
const result = spawnSync(command, args, { encoding: "utf8" });
|
|
9
|
+
if (result.status !== 0)
|
|
10
|
+
throw new Error(result.stderr || result.stdout || `${command} ${args.join(" ")} failed`);
|
|
11
|
+
return `${result.stdout}\n${result.stderr}`;
|
|
12
|
+
}
|
|
13
|
+
function removeContainer(name) {
|
|
14
|
+
spawnSync("docker", ["rm", "-f", name], { encoding: "utf8" });
|
|
15
|
+
}
|
|
16
|
+
async function queryPrometheus(query) {
|
|
17
|
+
const response = await fetch(`${PROMETHEUS_API}/api/v1/query?query=${encodeURIComponent(query)}`);
|
|
18
|
+
expect(response.status).toBe(200);
|
|
19
|
+
return (await response.json());
|
|
20
|
+
}
|
|
21
|
+
async function activeTargets() {
|
|
22
|
+
const response = await fetch(`${PROMETHEUS_API}/api/v1/targets?state=active`);
|
|
23
|
+
expect(response.status).toBe(200);
|
|
24
|
+
const body = (await response.json());
|
|
25
|
+
return body.data?.activeTargets ?? [];
|
|
26
|
+
}
|
|
27
|
+
async function waitForTarget(instance) {
|
|
28
|
+
let target;
|
|
29
|
+
for (let i = 0; i < 30; i++) {
|
|
30
|
+
target = (await activeTargets()).find((candidate) => candidate.labels.instance === instance);
|
|
31
|
+
if (target?.health === "up")
|
|
32
|
+
return target;
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
34
|
+
}
|
|
35
|
+
return target;
|
|
36
|
+
}
|
|
37
|
+
async function waitForQuery(query) {
|
|
38
|
+
let last;
|
|
39
|
+
for (let i = 0; i < 30; i++) {
|
|
40
|
+
last = await queryPrometheus(query);
|
|
41
|
+
if ((last.data?.result?.length ?? 0) > 0)
|
|
42
|
+
return last;
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
44
|
+
}
|
|
45
|
+
return last ?? queryPrometheus(query);
|
|
46
|
+
}
|
|
47
|
+
d("observability stack - Prometheus live", () => {
|
|
48
|
+
it("scrapes the gRPC metrics target on the shared metrics port", async () => {
|
|
49
|
+
const name = `blok-prom-target-${randomBytes(4).toString("hex")}`;
|
|
50
|
+
const metricsServer = [
|
|
51
|
+
"from http.server import BaseHTTPRequestHandler, HTTPServer",
|
|
52
|
+
"class H(BaseHTTPRequestHandler):",
|
|
53
|
+
" def log_message(self, *args): pass",
|
|
54
|
+
" def do_GET(self):",
|
|
55
|
+
" body = b'# TYPE blok_prom_port_live_test_total counter\\nblok_prom_port_live_test_total 7\\n'",
|
|
56
|
+
" self.send_response(200)",
|
|
57
|
+
" self.send_header('Content-Type', 'text/plain; version=0.0.4')",
|
|
58
|
+
" self.send_header('Content-Length', str(len(body)))",
|
|
59
|
+
" self.end_headers()",
|
|
60
|
+
" self.wfile.write(body)",
|
|
61
|
+
"HTTPServer(('0.0.0.0', 9464), H).serve_forever()",
|
|
62
|
+
].join("\n");
|
|
63
|
+
try {
|
|
64
|
+
run("docker", [
|
|
65
|
+
"run",
|
|
66
|
+
"--rm",
|
|
67
|
+
"-d",
|
|
68
|
+
"--name",
|
|
69
|
+
name,
|
|
70
|
+
"--network",
|
|
71
|
+
"shared-network",
|
|
72
|
+
"--network-alias",
|
|
73
|
+
"blok-grpc",
|
|
74
|
+
"python:3.12-alpine",
|
|
75
|
+
"python",
|
|
76
|
+
"-c",
|
|
77
|
+
metricsServer,
|
|
78
|
+
]);
|
|
79
|
+
const target = await waitForTarget("blok-grpc:9464");
|
|
80
|
+
expect(target?.scrapeUrl).toBe("http://blok-grpc:9464/metrics");
|
|
81
|
+
expect(target?.health).toBe("up");
|
|
82
|
+
const up = await waitForQuery('up{job="blok-grpc",instance="blok-grpc:9464"} == 1');
|
|
83
|
+
expect(up.status).toBe("success");
|
|
84
|
+
expect(up.data?.result).toHaveLength(1);
|
|
85
|
+
expect(up.data?.result?.[0]?.value[1]).toBe("1");
|
|
86
|
+
const metric = await waitForQuery("blok_prom_port_live_test_total");
|
|
87
|
+
expect(metric.status).toBe("success");
|
|
88
|
+
expect(metric.data?.result).toHaveLength(1);
|
|
89
|
+
expect(metric.data?.result?.[0]?.value[1]).toBe("7");
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
removeContainer(name);
|
|
93
|
+
}
|
|
94
|
+
}, 60_000);
|
|
95
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
const RUN = process.env.BLOK_INTEGRATION_TEMPO;
|
|
7
|
+
const d = RUN ? describe : describe.skip;
|
|
8
|
+
const TEMPO_API = process.env.BLOK_TEMPO_API_URL ?? "http://localhost:3201";
|
|
9
|
+
const TEMPO_OTLP = process.env.BLOK_TEMPO_OTLP_URL ?? "http://localhost:4318/v1/traces";
|
|
10
|
+
function findRepoRoot() {
|
|
11
|
+
let dir = import.meta.dirname;
|
|
12
|
+
for (let i = 0; i < 8; i++) {
|
|
13
|
+
if (fs.existsSync(path.join(dir, "infra", "metrics", "tempo.yaml")))
|
|
14
|
+
return dir;
|
|
15
|
+
const up = path.dirname(dir);
|
|
16
|
+
if (up === dir)
|
|
17
|
+
break;
|
|
18
|
+
dir = up;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function hex(bytes) {
|
|
23
|
+
return randomBytes(bytes).toString("hex");
|
|
24
|
+
}
|
|
25
|
+
function dockerLogs(container) {
|
|
26
|
+
const result = spawnSync("docker", ["logs", container], { encoding: "utf8" });
|
|
27
|
+
if (result.status !== 0)
|
|
28
|
+
throw new Error(result.stderr || result.stdout || `docker logs ${container} failed`);
|
|
29
|
+
return `${result.stdout}\n${result.stderr}`;
|
|
30
|
+
}
|
|
31
|
+
async function waitForTrace(traceId) {
|
|
32
|
+
let last;
|
|
33
|
+
for (let i = 0; i < 20; i++) {
|
|
34
|
+
last = await fetch(`${TEMPO_API}/api/traces/${traceId}`);
|
|
35
|
+
if (last.ok)
|
|
36
|
+
return last;
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
38
|
+
}
|
|
39
|
+
return last ?? fetch(`${TEMPO_API}/api/traces/${traceId}`);
|
|
40
|
+
}
|
|
41
|
+
d("observability stack - Tempo live", () => {
|
|
42
|
+
it("boots without the dangling memcached cache and ingests an OTLP span", async () => {
|
|
43
|
+
const repo = findRepoRoot();
|
|
44
|
+
if (!repo)
|
|
45
|
+
throw new Error("repo root not found");
|
|
46
|
+
const tempoConfig = fs.readFileSync(path.join(repo, "infra", "metrics", "tempo.yaml"), "utf8");
|
|
47
|
+
expect(tempoConfig).not.toContain("memcached:11211");
|
|
48
|
+
const traceId = hex(16);
|
|
49
|
+
const spanId = hex(8);
|
|
50
|
+
const now = BigInt(Date.now()) * 1000000n;
|
|
51
|
+
const response = await fetch(TEMPO_OTLP, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: { "content-type": "application/json" },
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
resourceSpans: [
|
|
56
|
+
{
|
|
57
|
+
resource: { attributes: [{ key: "service.name", value: { stringValue: "blok-tempo-live-test" } }] },
|
|
58
|
+
scopeSpans: [
|
|
59
|
+
{
|
|
60
|
+
scope: { name: "blokctl-live-test" },
|
|
61
|
+
spans: [
|
|
62
|
+
{
|
|
63
|
+
traceId,
|
|
64
|
+
spanId,
|
|
65
|
+
name: "tempo-memcached-regression",
|
|
66
|
+
kind: 1,
|
|
67
|
+
startTimeUnixNano: now.toString(),
|
|
68
|
+
endTimeUnixNano: (now + 1000000n).toString(),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
expect(response.status).toBe(200);
|
|
78
|
+
const trace = await waitForTrace(traceId);
|
|
79
|
+
expect(trace.ok).toBe(true);
|
|
80
|
+
const traceBody = await trace.text();
|
|
81
|
+
expect(traceBody).toContain("blok-tempo-live-test");
|
|
82
|
+
expect(traceBody).toContain("tempo-memcached-regression");
|
|
83
|
+
const logs = dockerLogs("tempo");
|
|
84
|
+
expect(logs.toLowerCase()).not.toContain("memcached");
|
|
85
|
+
});
|
|
86
|
+
});
|