k6ctl 1.2.0 → 1.3.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.
Files changed (46) hide show
  1. package/README.md +168 -4
  2. package/dist/cli.js +3 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/delete.d.ts +1 -1
  5. package/dist/commands/delete.d.ts.map +1 -1
  6. package/dist/commands/delete.js +10 -4
  7. package/dist/commands/delete.js.map +1 -1
  8. package/dist/commands/run.d.ts +1 -0
  9. package/dist/commands/run.d.ts.map +1 -1
  10. package/dist/commands/run.js +137 -1
  11. package/dist/commands/run.js.map +1 -1
  12. package/dist/commands/status.d.ts.map +1 -1
  13. package/dist/commands/status.js +4 -0
  14. package/dist/commands/status.js.map +1 -1
  15. package/dist/services/kubernetes.service.d.ts +9 -3
  16. package/dist/services/kubernetes.service.d.ts.map +1 -1
  17. package/dist/services/kubernetes.service.js +121 -2
  18. package/dist/services/kubernetes.service.js.map +1 -1
  19. package/dist/services/script.service.d.ts +3 -1
  20. package/dist/services/script.service.d.ts.map +1 -1
  21. package/dist/services/script.service.js +115 -1
  22. package/dist/services/script.service.js.map +1 -1
  23. package/dist/types/kubernetes.types.d.ts +5 -0
  24. package/dist/types/kubernetes.types.d.ts.map +1 -1
  25. package/dist/types/lastRun.types.d.ts +2 -1
  26. package/dist/types/lastRun.types.d.ts.map +1 -1
  27. package/dist/types/script.types.d.ts +39 -0
  28. package/dist/types/script.types.d.ts.map +1 -1
  29. package/dist/utils/testRunManifestBuilder.d.ts +2 -1
  30. package/dist/utils/testRunManifestBuilder.d.ts.map +1 -1
  31. package/dist/utils/testRunManifestBuilder.js +34 -0
  32. package/dist/utils/testRunManifestBuilder.js.map +1 -1
  33. package/package.json +2 -1
  34. package/src/cli.ts +3 -2
  35. package/src/commands/delete.ts +11 -5
  36. package/src/commands/run.ts +158 -2
  37. package/src/commands/status.ts +4 -0
  38. package/src/services/kubernetes.service.ts +141 -5
  39. package/src/services/script.service.ts +102 -4
  40. package/src/types/kubernetes.types.ts +6 -0
  41. package/src/types/lastRun.types.ts +2 -1
  42. package/src/types/script.types.ts +40 -0
  43. package/src/utils/testRunManifestBuilder.ts +39 -1
  44. package/test/integration/kubernetes.service.test.ts +61 -5
  45. package/test/unit/kubernetes.service.test.ts +13 -8
  46. package/test/unit/script.service.test.ts +11 -4
@@ -1,5 +1,5 @@
1
1
  import logger from '../utils/logger';
2
- import type { ArchivedFile, ConfigMapResult } from '../types/kubernetes.types';
2
+ import type { ArchivedFile, ConfigMapResult, VolumeClaimResult } from '../types/kubernetes.types';
3
3
  import { K6Config } from '../types/config.types';
4
4
  import { TestRunManifest } from '../types/testRunManifest.types';
5
5
 
@@ -124,3 +124,41 @@ function addPrometheusEnvVars(cfg: K6Config, envMap: Map<string, RunnerEnvVar>)
124
124
  }
125
125
  }
126
126
  }
127
+
128
+ export function buildTestRunManifestWithVolumeClaim(
129
+ volumeClaimResult: VolumeClaimResult,
130
+ cfg: K6Config,
131
+ envFromLoader?: Record<string, string>
132
+ ): TestRunManifest {
133
+ const testName = volumeClaimResult.volumeClaimName.replace('archive-', 'test-');
134
+ const argumentsString = buildArgumentsString(cfg.arguments, cfg, testName);
135
+ logger.debug('Constructed arguments string for TestRun manifest (volume):', argumentsString);
136
+ const testRun: TestRunManifest = {
137
+ apiVersion: `${K6_GROUP}/${K6_VERSION}`,
138
+ kind: K6_KIND,
139
+ metadata: {
140
+ name: testName,
141
+ namespace: volumeClaimResult.namespace,
142
+ },
143
+ spec: {
144
+ parallelism: cfg.parallelism,
145
+ arguments: argumentsString,
146
+ quiet: String(cfg.quiet),
147
+ ...(cfg.cleanup === true ? { cleanup: K6_CLEANUP_LABEL } : {}),
148
+ separate: cfg.separate,
149
+ runner: {
150
+ image: cfg.runner?.image,
151
+ env: buildRunnerEnv(cfg, envFromLoader),
152
+ ...(cfg.runner?.resources ? { resources: cfg.runner.resources } : {}),
153
+ },
154
+ script: {
155
+ volumeClaim: {
156
+ name: volumeClaimResult.volumeClaimName,
157
+ file: volumeClaimResult.archiveFilename,
158
+ },
159
+ },
160
+ },
161
+ };
162
+ logger.debug('Constructed TestRun manifest (volume):', JSON.stringify(testRun, null, 2));
163
+ return testRun;
164
+ }
@@ -3,19 +3,18 @@ import { join, resolve } from 'node:path';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { createDefaultKubernetesService } from '../../src/services/kubernetes.service';
5
5
  import { ScriptService } from '../../src/services/script.service';
6
- import { ConfigMapResult } from '../../src/types/kubernetes.types';
6
+ import { ConfigMapResult, VolumeClaimResult } from '../../src/types/kubernetes.types';
7
7
  import { ArchiveResult } from '../../src/types/script.types';
8
8
  import logger from '../../src/utils/logger';
9
9
  import { TestRunManifest } from '../../src/types/testRunManifest.types';
10
10
  import { K6Config } from '../../src/types/config.types';
11
11
  import { loadK6Config } from '../../src/utils/configLoader';
12
- import { buildTestRunManifest } from '../../src/utils/testRunManifestBuilder';
12
+ import { buildTestRunManifest, buildTestRunManifestWithVolumeClaim } from '../../src/utils/testRunManifestBuilder';
13
13
  import { saveLastRun } from '../../src/utils/lastRunStore';
14
14
  import { status } from '../../src/commands/status';
15
15
  import { logs } from '../../src/commands/logs';
16
16
  import { deleteLastRun } from '../../src/commands/delete';
17
17
 
18
-
19
18
  const samplesPath = resolve(__dirname, '..', 'samples');
20
19
  const scriptSample1 = join(samplesPath, 'k6_script_sample_1.js');
21
20
  const scriptSample2 = join(samplesPath, 'k6_script_sample_2.js');
@@ -27,8 +26,11 @@ const kubernetesService = createDefaultKubernetesService();
27
26
  let archiveOutput: ArchiveResult;
28
27
  let configMapResult: ConfigMapResult;
29
28
 
30
- describe('KubernetesService integration tests', () => {
29
+ let volumeArchiveOutput: ArchiveResult;
30
+ let volumeClaimResult: VolumeClaimResult;
31
+ let volumeTestRunManifest: TestRunManifest;
31
32
 
33
+ describe('KubernetesService integration tests', () => {
32
34
  test('create config map from archived script', async () => {
33
35
  archiveOutput = await scriptService.archiveTest(scriptSample2);
34
36
  expect(existsSync(archiveOutput.archivePath)).toBe(true);
@@ -70,4 +72,58 @@ describe('KubernetesService integration tests', () => {
70
72
  // Clean up the TestRun after successful creation and execution
71
73
  await deleteLastRun({ namespace: testRunManifest.metadata.namespace });
72
74
  }, 120000);
73
- });
75
+
76
+ test('archives a script and creates PVC', async () => {
77
+ volumeArchiveOutput = await scriptService.archiveTest(scriptSample1);
78
+ expect(existsSync(volumeArchiveOutput.archivePath)).toBe(true);
79
+
80
+ volumeClaimResult = await kubernetesService.createPVCWithArchive(volumeArchiveOutput, 'default');
81
+
82
+ expect(volumeClaimResult).toBeDefined();
83
+ expect(volumeClaimResult.volumeClaimName).toMatch(/^archive-/);
84
+ expect(volumeClaimResult.namespace).toBe('default');
85
+ expect(volumeClaimResult.archiveFilename).toMatch(/\.tar$/);
86
+
87
+ // createPVCWithArchive deletes the local archive file after uploading
88
+ expect(existsSync(volumeArchiveOutput.archivePath)).toBe(false);
89
+
90
+ logger.info(`PVC created: ${JSON.stringify(volumeClaimResult, null, 2)}`);
91
+ }, 120000);
92
+
93
+ test('creates and runs a TestRun from the PVC', async () => {
94
+ const cfg: K6Config = loadK6Config();
95
+ logger.debug('Loaded K6 Config:', JSON.stringify(cfg, null, 2));
96
+
97
+ volumeTestRunManifest = buildTestRunManifestWithVolumeClaim(volumeClaimResult, cfg);
98
+
99
+ expect(volumeTestRunManifest.spec.script.volumeClaim).toBeDefined();
100
+ expect(volumeTestRunManifest.spec.script.configMap).toBeUndefined();
101
+ expect(volumeTestRunManifest.spec.script.volumeClaim?.name).toBe(volumeClaimResult.volumeClaimName);
102
+ expect(volumeTestRunManifest.spec.script.volumeClaim?.file).toBe(volumeClaimResult.archiveFilename);
103
+
104
+ const response = await kubernetesService.createTestRun(volumeTestRunManifest);
105
+ expect(response).toBeDefined();
106
+ logger.info('TestRun result:', JSON.stringify(response));
107
+
108
+ await saveLastRun({
109
+ testRunName: volumeTestRunManifest.metadata.name,
110
+ namespace: volumeTestRunManifest.metadata.namespace,
111
+ volumeClaimName: volumeClaimResult.volumeClaimName,
112
+ scriptPath: volumeArchiveOutput.scriptPath,
113
+ createdAt: new Date().toISOString(),
114
+ });
115
+ logger.info(`Last run saved: ${volumeTestRunManifest.metadata.name} (namespace: ${volumeTestRunManifest.metadata.namespace})`);
116
+
117
+ // Wait for the TestRun to be created and start running
118
+ await new Promise((resolve) => setTimeout(resolve, 60000));
119
+
120
+ // Check status command output
121
+ await status({ namespace: volumeTestRunManifest.metadata.namespace });
122
+
123
+ // Check logs command output
124
+ await logs({ namespace: volumeTestRunManifest.metadata.namespace });
125
+
126
+ // Clean up the TestRun and PVC
127
+ await deleteLastRun({ namespace: volumeTestRunManifest.metadata.namespace });
128
+ }, 120000);
129
+ });
@@ -5,14 +5,19 @@ import logger from '../../src/utils/logger';
5
5
  import { KubernetesService, createDefaultKubernetesService } from '../../src/services/kubernetes.service';
6
6
  import type { ArchivedFile } from '../../src/types/kubernetes.types';
7
7
 
8
- jest.mock('fs', () => ({
9
- existsSync: jest.fn(),
10
- promises: {
11
- stat: jest.fn(),
12
- readFile: jest.fn(),
13
- unlink: jest.fn(),
14
- },
15
- }));
8
+ jest.mock('fs', () => {
9
+ const actualFs = jest.requireActual<typeof import('fs')>('fs');
10
+ return {
11
+ ...actualFs,
12
+ existsSync: jest.fn(),
13
+ promises: {
14
+ ...actualFs.promises,
15
+ stat: jest.fn(),
16
+ readFile: jest.fn(),
17
+ unlink: jest.fn(),
18
+ },
19
+ };
20
+ });
16
21
 
17
22
  jest.mock('@kubernetes/client-node', () => {
18
23
  const mockApiClient = {
@@ -1,11 +1,16 @@
1
1
  import { beforeEach, describe, expect, jest, test } from '@jest/globals';
2
2
  import { ScriptService } from '../../src/services/script.service';
3
- import { existsSync } from 'fs';
3
+ import { existsSync, statSync } from 'fs';
4
4
  import type { ExecFn } from '../../src/types/script.types';
5
5
 
6
- jest.mock('fs', () => ({
7
- existsSync: jest.fn(),
8
- }));
6
+ jest.mock('fs', () => {
7
+ const actualFs = jest.requireActual<typeof import('fs')>('fs');
8
+ return {
9
+ ...actualFs,
10
+ existsSync: jest.fn(),
11
+ statSync: jest.fn(),
12
+ };
13
+ });
9
14
 
10
15
  jest.mock('../../src/utils/logger', () => ({
11
16
  __esModule: true,
@@ -13,6 +18,7 @@ jest.mock('../../src/utils/logger', () => ({
13
18
  }));
14
19
 
15
20
  const mockedExistsSync = existsSync as unknown as jest.Mock;
21
+ const mockedStatSync = statSync as unknown as jest.Mock;
16
22
 
17
23
  describe('ScriptService', () => {
18
24
  let mockExec: jest.MockedFunction<ExecFn>;
@@ -22,6 +28,7 @@ describe('ScriptService', () => {
22
28
  jest.resetAllMocks();
23
29
  mockExec = jest.fn<ExecFn>();
24
30
  service = new ScriptService(mockExec);
31
+ mockedStatSync.mockReturnValue({ size: 1024 });
25
32
  });
26
33
 
27
34
  describe('archiveTest', () => {