k6ctl 1.0.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/cli.js +27 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/delete.d.ts +10 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +42 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/list.js +6 -6
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/logs.d.ts +7 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +39 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +64 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +26 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/services/kubernetes.service.d.ts +8 -2
- package/dist/services/kubernetes.service.d.ts.map +1 -1
- package/dist/services/kubernetes.service.js +71 -23
- package/dist/services/kubernetes.service.js.map +1 -1
- package/dist/types/lastRun.types.d.ts +8 -0
- package/dist/types/lastRun.types.d.ts.map +1 -0
- package/dist/types/lastRun.types.js +3 -0
- package/dist/types/lastRun.types.js.map +1 -0
- package/dist/types/testRunManifest.types.d.ts +2 -4
- package/dist/types/testRunManifest.types.d.ts.map +1 -1
- package/dist/utils/lastRunStore.d.ts +5 -0
- package/dist/utils/lastRunStore.d.ts.map +1 -0
- package/dist/utils/lastRunStore.js +41 -0
- package/dist/utils/lastRunStore.js.map +1 -0
- package/dist/utils/testRunManifestBuilder.d.ts +12 -0
- package/dist/utils/testRunManifestBuilder.d.ts.map +1 -1
- package/dist/utils/testRunManifestBuilder.js +36 -14
- package/dist/utils/testRunManifestBuilder.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +30 -3
- package/src/commands/delete.ts +52 -0
- package/src/commands/list.ts +7 -7
- package/src/commands/logs.ts +41 -0
- package/src/commands/run.ts +69 -0
- package/src/commands/status.ts +28 -0
- package/src/services/kubernetes.service.ts +77 -26
- package/src/types/lastRun.types.ts +7 -0
- package/src/types/testRunManifest.types.ts +3 -4
- package/src/utils/lastRunStore.ts +36 -0
- package/src/utils/testRunManifestBuilder.ts +41 -17
- package/test/integration/kubernetes.service.test.ts +23 -20
- package/test/unit/kubernetes.service.test.ts +3 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RunnerEnvVar } from "../utils/testRunManifestBuilder";
|
|
1
2
|
export interface TestRunManifest {
|
|
2
3
|
apiVersion: string;
|
|
3
4
|
kind: string;
|
|
@@ -13,10 +14,7 @@ export interface TestRunManifest {
|
|
|
13
14
|
separate?: boolean;
|
|
14
15
|
runner?: {
|
|
15
16
|
image?: string;
|
|
16
|
-
env?:
|
|
17
|
-
name: string;
|
|
18
|
-
value: string;
|
|
19
|
-
}>;
|
|
17
|
+
env?: RunnerEnvVar[];
|
|
20
18
|
resources?: {
|
|
21
19
|
limits: {
|
|
22
20
|
cpu: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testRunManifest.types.d.ts","sourceRoot":"","sources":["../../src/types/testRunManifest.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,MAAM,CAAC,EAAE;YACP,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,GAAG,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"testRunManifest.types.d.ts","sourceRoot":"","sources":["../../src/types/testRunManifest.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,MAAM,CAAC,EAAE;YACP,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC;YACrB,SAAS,CAAC,EAAE;gBACV,MAAM,EAAE;oBACN,GAAG,EAAE,MAAM,CAAA;oBACX,MAAM,EAAE,MAAM,CAAA;iBACf,CAAA;gBACD,QAAQ,EAAE;oBACR,GAAG,EAAE,MAAM,CAAA;oBACX,MAAM,EAAE,MAAM,CAAA;iBACf,CAAA;aACF,CAAA;SACF,CAAC;QACF,MAAM,EAAE;YACN,SAAS,CAAC,EAAE;gBACV,IAAI,EAAE,MAAM,CAAC;gBACb,IAAI,EAAE,MAAM,CAAC;aACd,CAAC;YACF,WAAW,CAAC,EAAE;gBACZ,IAAI,EAAE,MAAM,CAAC;gBACb,IAAI,EAAE,MAAM,CAAC;aACd,CAAA;SACF,CAAC;KACH,CAAC;CACH"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LastRunState } from '../types/lastRun.types';
|
|
2
|
+
export declare function saveLastRun(state: LastRunState): Promise<void>;
|
|
3
|
+
export declare function loadLastRun(): Promise<LastRunState | null>;
|
|
4
|
+
export declare function clearLastRun(): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=lastRunStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lastRunStore.d.ts","sourceRoot":"","sources":["../../src/utils/lastRunStore.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAQ3D,wBAAsB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAQhE;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAQlD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.saveLastRun = saveLastRun;
|
|
7
|
+
exports.loadLastRun = loadLastRun;
|
|
8
|
+
exports.clearLastRun = clearLastRun;
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
12
|
+
const LAST_RUN_FILE = '.k6ctl-last-run.json';
|
|
13
|
+
function getFilePath() {
|
|
14
|
+
return path_1.default.join(process.cwd(), LAST_RUN_FILE);
|
|
15
|
+
}
|
|
16
|
+
async function saveLastRun(state) {
|
|
17
|
+
const filePath = getFilePath();
|
|
18
|
+
await fs_1.promises.writeFile(filePath, JSON.stringify(state, null, 2), 'utf8');
|
|
19
|
+
logger_1.default.debug(`Last run state saved to ${filePath}`);
|
|
20
|
+
}
|
|
21
|
+
async function loadLastRun() {
|
|
22
|
+
const filePath = getFilePath();
|
|
23
|
+
try {
|
|
24
|
+
const content = await fs_1.promises.readFile(filePath, 'utf8');
|
|
25
|
+
return JSON.parse(content);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function clearLastRun() {
|
|
32
|
+
const filePath = getFilePath();
|
|
33
|
+
try {
|
|
34
|
+
await fs_1.promises.unlink(filePath);
|
|
35
|
+
logger_1.default.debug(`Last run state cleared (${filePath})`);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
logger_1.default.info('No last run state to clear.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=lastRunStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lastRunStore.js","sourceRoot":"","sources":["../../src/utils/lastRunStore.ts"],"names":[],"mappings":";;;;;AAWA,kCAIC;AAED,kCAQC;AAED,oCAQC;AAnCD,2BAAoC;AACpC,gDAAwB;AACxB,sDAA8B;AAG9B,MAAM,aAAa,GAAG,sBAAsB,CAAC;AAE7C,SAAS,WAAW;IAClB,OAAO,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AAEM,KAAK,UAAU,WAAW,CAAC,KAAmB;IACnD,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,aAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACrE,gBAAM,CAAC,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;AACtD,CAAC;AAEM,KAAK,UAAU,WAAW;IAC/B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,aAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAiB,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,YAAY;IAChC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,aAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,gBAAM,CAAC,KAAK,CAAC,2BAA2B,QAAQ,GAAG,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,gBAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { ArchivedFile, ConfigMapResult } from '../types/kubernetes.types';
|
|
2
2
|
import { K6Config } from '../types/config.types';
|
|
3
3
|
import { TestRunManifest } from '../types/testRunManifest.types';
|
|
4
|
+
export type RunnerEnvVar = {
|
|
5
|
+
name: string;
|
|
6
|
+
value: string;
|
|
7
|
+
} | {
|
|
8
|
+
name: string;
|
|
9
|
+
valueFrom: {
|
|
10
|
+
secretKeyRef: {
|
|
11
|
+
name: string;
|
|
12
|
+
key: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
4
16
|
export declare function buildTestRunManifest(configMapResult: ConfigMapResult, archiveOutput: ArchivedFile, cfg: K6Config, envFromLoader?: Record<string, string>): TestRunManifest;
|
|
5
17
|
//# sourceMappingURL=testRunManifestBuilder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testRunManifestBuilder.d.ts","sourceRoot":"","sources":["../../src/utils/testRunManifestBuilder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"testRunManifestBuilder.d.ts","sourceRoot":"","sources":["../../src/utils/testRunManifestBuilder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEjE,MAAM,MAAM,YAAY,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE;QAAE,YAAY,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAWnF,wBAAgB,oBAAoB,CAChC,eAAe,EAAE,eAAe,EAChC,aAAa,EAAE,YAAY,EAC3B,GAAG,EAAE,QAAQ,EACb,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACvC,eAAe,CAoCjB"}
|
|
@@ -50,22 +50,14 @@ function buildTestRunManifest(configMapResult, archiveOutput, cfg, envFromLoader
|
|
|
50
50
|
function buildRunnerEnv(cfg, envFromLoader) {
|
|
51
51
|
const envMap = new Map();
|
|
52
52
|
if (envFromLoader) {
|
|
53
|
-
for (const [key,
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
for (const [key, raw] of Object.entries(envFromLoader)) {
|
|
54
|
+
if (raw === undefined || raw === null)
|
|
55
|
+
continue;
|
|
56
|
+
addEnvToMap(key, raw, envMap);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (cfg.prometheus.trendStats?.length) {
|
|
62
|
-
envMap.set(K6_PROMETHEUS_RW_TREND_STATS, cfg.prometheus.trendStats.join(","));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const envArray = Array.from(envMap.entries()).map(([name, value]) => ({
|
|
66
|
-
name,
|
|
67
|
-
value,
|
|
68
|
-
}));
|
|
59
|
+
addPrometheusEnvVars(cfg, envMap);
|
|
60
|
+
const envArray = Array.from(envMap.values());
|
|
69
61
|
return envArray.length > 0 ? envArray : undefined;
|
|
70
62
|
}
|
|
71
63
|
function buildArgumentsString(args, cfg, name) {
|
|
@@ -86,4 +78,34 @@ function buildArgumentsString(args, cfg, name) {
|
|
|
86
78
|
}
|
|
87
79
|
return finalArgs.join(" ");
|
|
88
80
|
}
|
|
81
|
+
function parseSecretPlaceholder(value) {
|
|
82
|
+
const m = /^\{\{SECRETS\.([^.}]+)\.([^.}]+)\}\}$/.exec(value.trim());
|
|
83
|
+
if (!m)
|
|
84
|
+
return null;
|
|
85
|
+
return { secretName: m[1], secretKey: m[2] };
|
|
86
|
+
}
|
|
87
|
+
function addEnvToMap(key, raw, envMap) {
|
|
88
|
+
const value = String(raw);
|
|
89
|
+
const secret = parseSecretPlaceholder(value);
|
|
90
|
+
if (secret) {
|
|
91
|
+
envMap.set(key, { name: key, valueFrom: { secretKeyRef: { name: secret.secretName, key: secret.secretKey } } });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
envMap.set(key, { name: key, value });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function addPrometheusEnvVars(cfg, envMap) {
|
|
98
|
+
if (cfg.prometheus?.serverUrl) {
|
|
99
|
+
envMap.set(K6_PROMETHEUS_RW_SERVER_URL, {
|
|
100
|
+
name: K6_PROMETHEUS_RW_SERVER_URL,
|
|
101
|
+
value: cfg.prometheus.serverUrl,
|
|
102
|
+
});
|
|
103
|
+
if (cfg.prometheus.trendStats?.length) {
|
|
104
|
+
envMap.set(K6_PROMETHEUS_RW_TREND_STATS, {
|
|
105
|
+
name: K6_PROMETHEUS_RW_TREND_STATS,
|
|
106
|
+
value: cfg.prometheus.trendStats.join(","),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
89
111
|
//# sourceMappingURL=testRunManifestBuilder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testRunManifestBuilder.js","sourceRoot":"","sources":["../../src/utils/testRunManifestBuilder.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"testRunManifestBuilder.js","sourceRoot":"","sources":["../../src/utils/testRunManifestBuilder.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
import { Argument, Command } from 'commander';
|
|
4
4
|
import { runTest } from './commands/run';
|
|
5
5
|
import { list } from './commands/list';
|
|
6
|
+
import { logs } from './commands/logs';
|
|
7
|
+
import { status } from './commands/status';
|
|
8
|
+
import { deleteLastRun } from './commands/delete';
|
|
6
9
|
import { version } from '../package.json';
|
|
7
10
|
|
|
8
11
|
const program = new Command();
|
|
@@ -13,12 +16,13 @@ program
|
|
|
13
16
|
.version(version);
|
|
14
17
|
|
|
15
18
|
program
|
|
16
|
-
.command('run
|
|
17
|
-
.description('Run a k6 test script')
|
|
18
|
-
.option('-c, --config <path>', 'Path to config file', 'k6ctl.config.
|
|
19
|
+
.command('run [script]')
|
|
20
|
+
.description('Run a k6 test script (omit to select interactively from ${pwd}/dist/tests)')
|
|
21
|
+
.option('-c, --config <path>', 'Path to config file', 'k6ctl.config.json')
|
|
19
22
|
.option('-n, --namespace <namespace>', 'Kubernetes namespace')
|
|
20
23
|
.option('-p, --parallelism <number>', 'Number of parallel test pods')
|
|
21
24
|
.option('-v, --verbose', 'enable debug logging')
|
|
25
|
+
.option('-d, --dir <path>', 'Folder to search for .js test files', 'dist/tests')
|
|
22
26
|
.action(runTest);
|
|
23
27
|
|
|
24
28
|
program
|
|
@@ -28,4 +32,27 @@ program
|
|
|
28
32
|
.option('-n, --namespace <namespace>', 'Kubernetes namespace', 'default')
|
|
29
33
|
.action(list);
|
|
30
34
|
|
|
35
|
+
program
|
|
36
|
+
.command('logs')
|
|
37
|
+
.description('Show logs from the last test run pods')
|
|
38
|
+
.option('-n, --namespace <namespace>', 'Kubernetes namespace (overrides saved value)')
|
|
39
|
+
.option('-c, --container <name>', 'Container name to fetch logs from')
|
|
40
|
+
.action(logs);
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command('status')
|
|
44
|
+
.description('Show status of the last test run')
|
|
45
|
+
.option('-n, --namespace <namespace>', 'Kubernetes namespace (overrides saved value)')
|
|
46
|
+
.action(status);
|
|
47
|
+
|
|
48
|
+
program
|
|
49
|
+
.command('delete')
|
|
50
|
+
.description('Delete the last test run (TestRun + ConfigMap)')
|
|
51
|
+
.option('-n, --namespace <namespace>', 'Kubernetes namespace (overrides saved value)')
|
|
52
|
+
.option('--keep-configmap', 'Skip deletion of the associated ConfigMap')
|
|
53
|
+
.option('-p, --pod <name>', 'Delete a specific pod instead of those associated with the last TestRun')
|
|
54
|
+
.option('-t, --testrun <name>', 'Delete a specific TestRun by name instead of the last one')
|
|
55
|
+
.option('-c, --configmap <name>', 'Delete a specific ConfigMap by name instead of the one associated with the last TestRun')
|
|
56
|
+
.action(deleteLastRun);
|
|
57
|
+
|
|
31
58
|
program.parse();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createDefaultKubernetesService } from '../services/kubernetes.service';
|
|
2
|
+
import { loadLastRun, clearLastRun } from '../utils/lastRunStore';
|
|
3
|
+
import logger from '../utils/logger';
|
|
4
|
+
|
|
5
|
+
interface DeleteOptions {
|
|
6
|
+
namespace?: string;
|
|
7
|
+
keepConfigmap?: boolean;
|
|
8
|
+
pod?: string;
|
|
9
|
+
testrun?: string;
|
|
10
|
+
configmap?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function deleteLastRun(options: DeleteOptions) {
|
|
14
|
+
const kubernetesService = createDefaultKubernetesService();
|
|
15
|
+
|
|
16
|
+
const lastRun = !options.namespace ? await loadLastRun() : null;
|
|
17
|
+
const namespace = options.namespace ?? lastRun?.namespace ?? 'default';
|
|
18
|
+
|
|
19
|
+
if (options.pod) {
|
|
20
|
+
logger.info(`Deleting Pod: ${options.pod} (namespace: ${namespace})`);
|
|
21
|
+
await kubernetesService.deletePodByName(options.pod, namespace);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (options.testrun) {
|
|
26
|
+
logger.info(`Deleting TestRun: ${options.testrun} (namespace: ${namespace})`);
|
|
27
|
+
await kubernetesService.deleteTestRunByName(options.testrun, namespace);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (options.configmap) {
|
|
32
|
+
logger.info(`Deleting ConfigMap: ${options.configmap} (namespace: ${namespace})`);
|
|
33
|
+
await kubernetesService.deleteConfigMap(options.configmap, namespace);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!lastRun) {
|
|
38
|
+
logger.error('No last run found. Run a test first with: k6ctl run <script>');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
logger.info(`Deleting TestRun: ${lastRun.testRunName} (namespace: ${namespace})`);
|
|
43
|
+
await kubernetesService.deleteTestRunByName(lastRun.testRunName, namespace);
|
|
44
|
+
|
|
45
|
+
if (!options.keepConfigmap) {
|
|
46
|
+
logger.info(`Deleting ConfigMap: ${lastRun.configMapName} (namespace: ${namespace})`);
|
|
47
|
+
await kubernetesService.deleteConfigMap(lastRun.configMapName, namespace);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await clearLastRun();
|
|
51
|
+
logger.info('Last run state cleared.');
|
|
52
|
+
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import logger, { setLogLevel } from '../utils/logger';
|
|
2
|
-
import { createDefaultKubernetesService } from '../services/kubernetes.service';
|
|
2
|
+
import { createDefaultKubernetesService, printPodsTable, printTestRunsTable, printConfigMapsTable } from '../services/kubernetes.service';
|
|
3
3
|
|
|
4
4
|
export async function list(type: string, options: { namespace?: string }) {
|
|
5
5
|
const kubernetesService = createDefaultKubernetesService();
|
|
6
6
|
switch (type) {
|
|
7
7
|
case 'all':
|
|
8
8
|
logger.debug(`Listing all resources in namespace: ${options.namespace}`);
|
|
9
|
-
await kubernetesService.listTestRuns(options.namespace);
|
|
10
|
-
await kubernetesService.listPods(options.namespace);
|
|
11
|
-
await kubernetesService.listConfigMaps(options.namespace);
|
|
9
|
+
printTestRunsTable(await kubernetesService.listTestRuns(options.namespace));
|
|
10
|
+
printPodsTable(await kubernetesService.listPods(options.namespace));
|
|
11
|
+
printConfigMapsTable(await kubernetesService.listConfigMaps(options.namespace));
|
|
12
12
|
break;
|
|
13
13
|
case 'pods':
|
|
14
14
|
logger.debug(`Listing Pods in namespace: ${options.namespace}`);
|
|
15
|
-
await kubernetesService.listPods(options.namespace);
|
|
15
|
+
printPodsTable(await kubernetesService.listPods(options.namespace));
|
|
16
16
|
break;
|
|
17
17
|
case 'testruns':
|
|
18
18
|
logger.debug(`Listing TestRuns in namespace: ${options.namespace}`);
|
|
19
|
-
await kubernetesService.listTestRuns(options.namespace);
|
|
19
|
+
printTestRunsTable(await kubernetesService.listTestRuns(options.namespace));
|
|
20
20
|
break;
|
|
21
21
|
case 'configmaps':
|
|
22
22
|
logger.debug(`Listing ConfigMaps in namespace: ${options.namespace}`);
|
|
23
|
-
await kubernetesService.listConfigMaps(options.namespace);
|
|
23
|
+
printConfigMapsTable(await kubernetesService.listConfigMaps(options.namespace));
|
|
24
24
|
break;
|
|
25
25
|
default:
|
|
26
26
|
logger.debug(`Listing ${type} in namespace: ${options.namespace}`);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createDefaultKubernetesService } from '../services/kubernetes.service';
|
|
2
|
+
import { loadLastRun } from '../utils/lastRunStore';
|
|
3
|
+
import logger from '../utils/logger';
|
|
4
|
+
|
|
5
|
+
interface LogsOptions {
|
|
6
|
+
namespace?: string;
|
|
7
|
+
container?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function logs(options: LogsOptions) {
|
|
11
|
+
const lastRun = await loadLastRun();
|
|
12
|
+
if (!lastRun) {
|
|
13
|
+
logger.error('No last run found. Run a test first with: k6ctl run <script>');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const namespace = options.namespace ?? lastRun.namespace;
|
|
18
|
+
logger.info(`Fetching logs for TestRun: ${lastRun.testRunName} (namespace: ${namespace})`);
|
|
19
|
+
|
|
20
|
+
const kubernetesService = createDefaultKubernetesService();
|
|
21
|
+
const podList = await kubernetesService.getPodsForTestRun(lastRun.testRunName, namespace);
|
|
22
|
+
const pods = podList.items ?? [];
|
|
23
|
+
|
|
24
|
+
if (pods.length === 0) {
|
|
25
|
+
logger.warn(`No pods found for TestRun ${lastRun.testRunName}. The run may have already cleaned up.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const pod of pods) {
|
|
30
|
+
const podName = pod.metadata?.name ?? 'unknown';
|
|
31
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
32
|
+
console.log(`Pod: ${podName}`);
|
|
33
|
+
console.log('─'.repeat(60));
|
|
34
|
+
try {
|
|
35
|
+
const podLogs = await kubernetesService.getPodLogs(podName, namespace, options.container);
|
|
36
|
+
console.log(podLogs);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.error(`Failed to get logs for pod ${podName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/commands/run.ts
CHANGED
|
@@ -1,19 +1,78 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as readline from 'readline';
|
|
1
4
|
import { createDefaultKubernetesService } from '../services/kubernetes.service';
|
|
2
5
|
import { ScriptService } from '../services/script.service';
|
|
3
6
|
import { loadK6Config } from '../utils/configLoader';
|
|
4
7
|
import { loadAndValidateEnv } from '../utils/env';
|
|
5
8
|
import logger, { setLogLevel } from '../utils/logger';
|
|
6
9
|
import { buildTestRunManifest } from '../utils/testRunManifestBuilder';
|
|
10
|
+
import { saveLastRun } from '../utils/lastRunStore';
|
|
11
|
+
|
|
12
|
+
function listTestFiles(dir: string): string[] {
|
|
13
|
+
if (!fs.existsSync(dir)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const results: string[] = [];
|
|
17
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
18
|
+
const fullPath = path.join(dir, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
results.push(...listTestFiles(fullPath));
|
|
21
|
+
} else if (entry.isFile() && entry.name.endsWith('.js')) {
|
|
22
|
+
results.push(fullPath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function promptSelection(files: string[], dir: string): Promise<string> {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
console.log('\nAvailable test scripts:');
|
|
31
|
+
files.forEach((file, i) => {
|
|
32
|
+
const relative = path.relative(dir, file);
|
|
33
|
+
console.log(` [${String(i + 1).padStart(2)}] ${relative}`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
39
|
+
rl.question(`Select a test (1-${files.length}): `, answer => {
|
|
40
|
+
rl.close();
|
|
41
|
+
const index = parseInt(answer.trim(), 10) - 1;
|
|
42
|
+
if (isNaN(index) || index < 0 || index >= files.length) {
|
|
43
|
+
reject(new Error(`Invalid selection: "${answer}"`));
|
|
44
|
+
} else {
|
|
45
|
+
resolve(files[index]);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
7
50
|
|
|
8
51
|
interface RunOptions {
|
|
9
52
|
config: string;
|
|
10
53
|
namespace?: string;
|
|
11
54
|
parallelism?: number;
|
|
12
55
|
verbose?: boolean;
|
|
56
|
+
dir: string;
|
|
13
57
|
}
|
|
14
58
|
|
|
15
59
|
export async function runTest(scriptPath: string, options: RunOptions) {
|
|
16
60
|
if (options.verbose) setLogLevel('debug');
|
|
61
|
+
|
|
62
|
+
if (!scriptPath) {
|
|
63
|
+
const files = listTestFiles(options.dir);
|
|
64
|
+
if (files.length === 0) {
|
|
65
|
+
logger.error(`No .js test files found in ${options.dir}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
scriptPath = await promptSelection(files, options.dir);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
17
76
|
logger.debug(`Running k6 test: ${scriptPath}`);
|
|
18
77
|
logger.debug(`Using config: ${JSON.stringify(options.config, null, 2)}`);
|
|
19
78
|
|
|
@@ -46,6 +105,16 @@ export async function runTest(scriptPath: string, options: RunOptions) {
|
|
|
46
105
|
// Create testrun resource
|
|
47
106
|
const testRunResult = await kubernetesService.createTestRun(testRunManifest);
|
|
48
107
|
|
|
108
|
+
// Persist last run state for use by logs/status/delete commands
|
|
109
|
+
await saveLastRun({
|
|
110
|
+
testRunName: testRunManifest.metadata.name,
|
|
111
|
+
namespace: testRunManifest.metadata.namespace,
|
|
112
|
+
configMapName: configMap.configMapName,
|
|
113
|
+
scriptPath,
|
|
114
|
+
createdAt: new Date().toISOString(),
|
|
115
|
+
});
|
|
116
|
+
logger.info(`Last run saved: ${testRunManifest.metadata.name} (namespace: ${testRunManifest.metadata.namespace})`);
|
|
117
|
+
|
|
49
118
|
} catch (error) {
|
|
50
119
|
logger.error(`Error running test: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
120
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createDefaultKubernetesService } from '../services/kubernetes.service';
|
|
2
|
+
import { printPodsTable, printTestRunsTable } from '../services/kubernetes.service';
|
|
3
|
+
import { loadLastRun } from '../utils/lastRunStore';
|
|
4
|
+
import logger from '../utils/logger';
|
|
5
|
+
|
|
6
|
+
interface StatusOptions {
|
|
7
|
+
namespace?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function status(options: StatusOptions) {
|
|
11
|
+
const lastRun = await loadLastRun();
|
|
12
|
+
if (!lastRun) {
|
|
13
|
+
logger.error('No last run found. Run a test first with: k6ctl run <script>');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const namespace = options.namespace ?? lastRun.namespace;
|
|
18
|
+
logger.info(`Status for TestRun: ${lastRun.testRunName} (namespace: ${namespace})`);
|
|
19
|
+
logger.info(`Script: ${lastRun.scriptPath} | Started: ${lastRun.createdAt}`);
|
|
20
|
+
|
|
21
|
+
const kubernetesService = createDefaultKubernetesService();
|
|
22
|
+
|
|
23
|
+
const testRun = await kubernetesService.getTestRun(lastRun.testRunName, namespace);
|
|
24
|
+
printTestRunsTable([testRun]);
|
|
25
|
+
|
|
26
|
+
const podList = await kubernetesService.getPodsForTestRun(lastRun.testRunName, namespace);
|
|
27
|
+
printPodsTable(podList);
|
|
28
|
+
}
|