Skip to content

Commit de1a02d

Browse files
authored
Merge pull request #54 from constructive-io/devin/1767656849-kubernetes-test
feat: add kubernetes-test package with wait-for-pods functionality
2 parents 362a5e9 + aa15c7a commit de1a02d

10 files changed

Lines changed: 3190 additions & 5221 deletions

File tree

packages/kubernetes-test/README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# kubernetes-test
2+
3+
Kubernetes testing utilities with wait-for-pods and other helpers for integration tests.
4+
5+
## Installation
6+
7+
```bash
8+
npm install kubernetes-test
9+
```
10+
11+
## Usage
12+
13+
### Wait for Pods
14+
15+
The `waitForPods` function allows you to wait for pods to become ready in a Kubernetes cluster.
16+
17+
```typescript
18+
import { KubernetesClient } from 'kubernetesjs';
19+
import { waitForPods, waitForPodByName, waitForPodsWithLabel } from 'kubernetes-test';
20+
21+
// Create a Kubernetes client
22+
const client = new KubernetesClient({
23+
restEndpoint: 'http://localhost:8001', // kubectl proxy endpoint
24+
});
25+
26+
// Wait for all pods with a specific label to be ready
27+
const result = await waitForPods(client, {
28+
namespace: 'default',
29+
labelSelector: 'app=my-app',
30+
timeoutMs: 60000, // 1 minute timeout
31+
pollIntervalMs: 2000, // Check every 2 seconds
32+
onProgress: (progress) => {
33+
console.log(`Ready: ${progress.ready}/${progress.total}`);
34+
},
35+
});
36+
37+
console.log(result.message); // "3/3 pods are ready"
38+
39+
// Wait for a specific pod by name
40+
const podResult = await waitForPodByName(client, 'my-pod-name', {
41+
namespace: 'default',
42+
timeoutMs: 30000,
43+
});
44+
45+
// Wait for pods with a specific label
46+
const labelResult = await waitForPodsWithLabel(client, 'app', 'nginx', {
47+
namespace: 'production',
48+
minReady: 2, // Wait for at least 2 pods to be ready
49+
});
50+
```
51+
52+
### Options
53+
54+
#### `WaitForPodsOptions`
55+
56+
| Option | Type | Default | Description |
57+
|--------|------|---------|-------------|
58+
| `namespace` | `string` | `'default'` | Kubernetes namespace to query |
59+
| `labelSelector` | `string` | - | Label selector to filter pods (e.g., `'app=nginx'`) |
60+
| `fieldSelector` | `string` | - | Field selector to filter pods |
61+
| `timeoutMs` | `number` | `300000` | Maximum time to wait (5 minutes) |
62+
| `pollIntervalMs` | `number` | `2000` | Interval between status checks |
63+
| `minReady` | `number` | - | Minimum number of ready pods required |
64+
| `allReady` | `boolean` | `true` | Wait for all pods to be ready |
65+
| `onProgress` | `function` | - | Callback for progress updates |
66+
67+
### Error Handling
68+
69+
The library provides specific error types for different failure scenarios:
70+
71+
```typescript
72+
import {
73+
waitForPods,
74+
WaitForPodsTimeoutError,
75+
WaitForPodsError
76+
} from 'kubernetes-test';
77+
78+
try {
79+
await waitForPods(client, { labelSelector: 'app=my-app' });
80+
} catch (error) {
81+
if (error instanceof WaitForPodsTimeoutError) {
82+
console.log('Timeout! Progress:', error.progress);
83+
} else if (error instanceof WaitForPodsError) {
84+
console.log('Error:', error.message);
85+
}
86+
}
87+
```
88+
89+
## License
90+
91+
MIT
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import {
2+
WaitForPodsTimeoutError,
3+
WaitForPodsError,
4+
WaitForPodsOptions,
5+
WaitForPodsProgress,
6+
PodStatusInfo,
7+
} from '../src/types';
8+
9+
describe('kubernetes-test types', () => {
10+
describe('WaitForPodsTimeoutError', () => {
11+
it('should create a timeout error with progress', () => {
12+
const progress: WaitForPodsProgress = {
13+
total: 3,
14+
ready: 1,
15+
pending: 2,
16+
failed: 0,
17+
pods: [],
18+
elapsedMs: 5000,
19+
};
20+
21+
const error = new WaitForPodsTimeoutError('Timeout waiting for pods', progress);
22+
23+
expect(error.name).toBe('WaitForPodsTimeoutError');
24+
expect(error.message).toBe('Timeout waiting for pods');
25+
expect(error.progress).toBe(progress);
26+
expect(error.progress.ready).toBe(1);
27+
expect(error.progress.total).toBe(3);
28+
});
29+
});
30+
31+
describe('WaitForPodsError', () => {
32+
it('should create an error without progress', () => {
33+
const error = new WaitForPodsError('Failed to list pods');
34+
35+
expect(error.name).toBe('WaitForPodsError');
36+
expect(error.message).toBe('Failed to list pods');
37+
expect(error.progress).toBeUndefined();
38+
});
39+
40+
it('should create an error with progress', () => {
41+
const progress: WaitForPodsProgress = {
42+
total: 2,
43+
ready: 0,
44+
pending: 1,
45+
failed: 1,
46+
pods: [],
47+
elapsedMs: 3000,
48+
};
49+
50+
const error = new WaitForPodsError('Pods failed', progress);
51+
52+
expect(error.name).toBe('WaitForPodsError');
53+
expect(error.message).toBe('Pods failed');
54+
expect(error.progress).toBe(progress);
55+
});
56+
});
57+
58+
describe('Type definitions', () => {
59+
it('should allow creating WaitForPodsOptions', () => {
60+
const options: WaitForPodsOptions = {
61+
namespace: 'test-namespace',
62+
labelSelector: 'app=test',
63+
timeoutMs: 60000,
64+
pollIntervalMs: 1000,
65+
minReady: 2,
66+
allReady: false,
67+
onProgress: (progress) => {
68+
expect(progress.total).toBeGreaterThanOrEqual(0);
69+
},
70+
};
71+
72+
expect(options.namespace).toBe('test-namespace');
73+
expect(options.labelSelector).toBe('app=test');
74+
expect(options.timeoutMs).toBe(60000);
75+
});
76+
77+
it('should allow creating PodStatusInfo', () => {
78+
const podStatus: PodStatusInfo = {
79+
name: 'test-pod',
80+
namespace: 'default',
81+
phase: 'Running',
82+
ready: true,
83+
conditions: [
84+
{
85+
type: 'Ready',
86+
status: 'True',
87+
reason: 'PodReady',
88+
message: 'Pod is ready',
89+
},
90+
],
91+
containerStatuses: [
92+
{
93+
name: 'main',
94+
ready: true,
95+
restartCount: 0,
96+
state: 'running',
97+
},
98+
],
99+
};
100+
101+
expect(podStatus.name).toBe('test-pod');
102+
expect(podStatus.ready).toBe(true);
103+
expect(podStatus.conditions).toHaveLength(1);
104+
expect(podStatus.containerStatuses).toHaveLength(1);
105+
});
106+
});
107+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: 'ts-jest',
4+
testEnvironment: 'node',
5+
transform: {
6+
'^.+\\.tsx?$': [
7+
'ts-jest',
8+
{
9+
babelConfig: false,
10+
tsconfig: 'tsconfig.json',
11+
},
12+
],
13+
},
14+
transformIgnorePatterns: [`/node_modules/*`],
15+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
16+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
17+
modulePathIgnorePatterns: ['dist/*'],
18+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "kubernetes-test",
3+
"version": "0.1.0",
4+
"author": "Constructive <developers@constructive.io>",
5+
"description": "Kubernetes testing utilities with wait-for-pods and other helpers for integration tests",
6+
"main": "index.js",
7+
"module": "esm/index.js",
8+
"types": "index.d.ts",
9+
"homepage": "https://github.com/constructive-io/dev-utils",
10+
"license": "MIT",
11+
"publishConfig": {
12+
"access": "public",
13+
"directory": "dist"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "https://github.com/constructive-io/dev-utils"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/constructive-io/dev-utils/issues"
21+
},
22+
"scripts": {
23+
"copy": "makage assets",
24+
"clean": "makage clean",
25+
"prepublishOnly": "npm run build",
26+
"build": "makage build",
27+
"lint": "eslint . --fix",
28+
"test": "jest",
29+
"test:watch": "jest --watch"
30+
},
31+
"keywords": [
32+
"kubernetes",
33+
"k8s",
34+
"testing",
35+
"integration-tests",
36+
"pods",
37+
"wait-for-pods",
38+
"kubernetesjs",
39+
"container",
40+
"orchestration",
41+
"devops",
42+
"cloud-native"
43+
],
44+
"dependencies": {
45+
"kubernetesjs": "^0.7.6"
46+
},
47+
"devDependencies": {
48+
"makage": "0.1.8"
49+
}
50+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './types';
2+
export * from './wait-for-pods';
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { Pod, PodList, PodCondition } from 'kubernetesjs';
2+
3+
export type { Pod, PodList, PodCondition };
4+
5+
export interface WaitForPodsOptions {
6+
namespace?: string;
7+
labelSelector?: string;
8+
fieldSelector?: string;
9+
timeoutMs?: number;
10+
pollIntervalMs?: number;
11+
minReady?: number;
12+
allReady?: boolean;
13+
onProgress?: (status: WaitForPodsProgress) => void;
14+
}
15+
16+
export interface WaitForPodsProgress {
17+
total: number;
18+
ready: number;
19+
pending: number;
20+
failed: number;
21+
pods: PodStatusInfo[];
22+
elapsedMs: number;
23+
}
24+
25+
export interface PodStatusInfo {
26+
name: string;
27+
namespace: string;
28+
phase: string;
29+
ready: boolean;
30+
conditions: PodConditionInfo[];
31+
containerStatuses: ContainerStatusInfo[];
32+
}
33+
34+
export interface PodConditionInfo {
35+
type: string;
36+
status: string;
37+
reason?: string;
38+
message?: string;
39+
}
40+
41+
export interface ContainerStatusInfo {
42+
name: string;
43+
ready: boolean;
44+
restartCount: number;
45+
state: 'running' | 'waiting' | 'terminated' | 'unknown';
46+
stateReason?: string;
47+
}
48+
49+
export interface WaitForPodsResult {
50+
success: boolean;
51+
pods: PodStatusInfo[];
52+
message: string;
53+
elapsedMs: number;
54+
}
55+
56+
export interface KubernetesTestClientOptions {
57+
restEndpoint?: string;
58+
namespace?: string;
59+
}
60+
61+
export class WaitForPodsTimeoutError extends Error {
62+
constructor(
63+
message: string,
64+
public readonly progress: WaitForPodsProgress
65+
) {
66+
super(message);
67+
this.name = 'WaitForPodsTimeoutError';
68+
}
69+
}
70+
71+
export class WaitForPodsError extends Error {
72+
constructor(
73+
message: string,
74+
public readonly progress?: WaitForPodsProgress
75+
) {
76+
super(message);
77+
this.name = 'WaitForPodsError';
78+
}
79+
}

0 commit comments

Comments
 (0)