Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions packages/kubernetes-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# kubernetes-test

Kubernetes testing utilities with wait-for-pods and other helpers for integration tests.

## Installation

```bash
npm install kubernetes-test
```

## Usage

### Wait for Pods

The `waitForPods` function allows you to wait for pods to become ready in a Kubernetes cluster.

```typescript
import { KubernetesClient } from 'kubernetesjs';
import { waitForPods, waitForPodByName, waitForPodsWithLabel } from 'kubernetes-test';

// Create a Kubernetes client
const client = new KubernetesClient({
restEndpoint: 'http://localhost:8001', // kubectl proxy endpoint
});

// Wait for all pods with a specific label to be ready
const result = await waitForPods(client, {
namespace: 'default',
labelSelector: 'app=my-app',
timeoutMs: 60000, // 1 minute timeout
pollIntervalMs: 2000, // Check every 2 seconds
onProgress: (progress) => {
console.log(`Ready: ${progress.ready}/${progress.total}`);
},
});

console.log(result.message); // "3/3 pods are ready"

// Wait for a specific pod by name
const podResult = await waitForPodByName(client, 'my-pod-name', {
namespace: 'default',
timeoutMs: 30000,
});

// Wait for pods with a specific label
const labelResult = await waitForPodsWithLabel(client, 'app', 'nginx', {
namespace: 'production',
minReady: 2, // Wait for at least 2 pods to be ready
});
```

### Options

#### `WaitForPodsOptions`

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `namespace` | `string` | `'default'` | Kubernetes namespace to query |
| `labelSelector` | `string` | - | Label selector to filter pods (e.g., `'app=nginx'`) |
| `fieldSelector` | `string` | - | Field selector to filter pods |
| `timeoutMs` | `number` | `300000` | Maximum time to wait (5 minutes) |
| `pollIntervalMs` | `number` | `2000` | Interval between status checks |
| `minReady` | `number` | - | Minimum number of ready pods required |
| `allReady` | `boolean` | `true` | Wait for all pods to be ready |
| `onProgress` | `function` | - | Callback for progress updates |

### Error Handling

The library provides specific error types for different failure scenarios:

```typescript
import {
waitForPods,
WaitForPodsTimeoutError,
WaitForPodsError
} from 'kubernetes-test';

try {
await waitForPods(client, { labelSelector: 'app=my-app' });
} catch (error) {
if (error instanceof WaitForPodsTimeoutError) {
console.log('Timeout! Progress:', error.progress);
} else if (error instanceof WaitForPodsError) {
console.log('Error:', error.message);
}
}
```

## License

MIT
107 changes: 107 additions & 0 deletions packages/kubernetes-test/__tests__/wait-for-pods.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
WaitForPodsTimeoutError,
WaitForPodsError,
WaitForPodsOptions,
WaitForPodsProgress,
PodStatusInfo,
} from '../src/types';

describe('kubernetes-test types', () => {
describe('WaitForPodsTimeoutError', () => {
it('should create a timeout error with progress', () => {
const progress: WaitForPodsProgress = {
total: 3,
ready: 1,
pending: 2,
failed: 0,
pods: [],
elapsedMs: 5000,
};

const error = new WaitForPodsTimeoutError('Timeout waiting for pods', progress);

expect(error.name).toBe('WaitForPodsTimeoutError');
expect(error.message).toBe('Timeout waiting for pods');
expect(error.progress).toBe(progress);
expect(error.progress.ready).toBe(1);
expect(error.progress.total).toBe(3);
});
});

describe('WaitForPodsError', () => {
it('should create an error without progress', () => {
const error = new WaitForPodsError('Failed to list pods');

expect(error.name).toBe('WaitForPodsError');
expect(error.message).toBe('Failed to list pods');
expect(error.progress).toBeUndefined();
});

it('should create an error with progress', () => {
const progress: WaitForPodsProgress = {
total: 2,
ready: 0,
pending: 1,
failed: 1,
pods: [],
elapsedMs: 3000,
};

const error = new WaitForPodsError('Pods failed', progress);

expect(error.name).toBe('WaitForPodsError');
expect(error.message).toBe('Pods failed');
expect(error.progress).toBe(progress);
});
});

describe('Type definitions', () => {
it('should allow creating WaitForPodsOptions', () => {
const options: WaitForPodsOptions = {
namespace: 'test-namespace',
labelSelector: 'app=test',
timeoutMs: 60000,
pollIntervalMs: 1000,
minReady: 2,
allReady: false,
onProgress: (progress) => {
expect(progress.total).toBeGreaterThanOrEqual(0);
},
};

expect(options.namespace).toBe('test-namespace');
expect(options.labelSelector).toBe('app=test');
expect(options.timeoutMs).toBe(60000);
});

it('should allow creating PodStatusInfo', () => {
const podStatus: PodStatusInfo = {
name: 'test-pod',
namespace: 'default',
phase: 'Running',
ready: true,
conditions: [
{
type: 'Ready',
status: 'True',
reason: 'PodReady',
message: 'Pod is ready',
},
],
containerStatuses: [
{
name: 'main',
ready: true,
restartCount: 0,
state: 'running',
},
],
};

expect(podStatus.name).toBe('test-pod');
expect(podStatus.ready).toBe(true);
expect(podStatus.conditions).toHaveLength(1);
expect(podStatus.containerStatuses).toHaveLength(1);
});
});
});
18 changes: 18 additions & 0 deletions packages/kubernetes-test/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
babelConfig: false,
tsconfig: 'tsconfig.json',
},
],
},
transformIgnorePatterns: [`/node_modules/*`],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
modulePathIgnorePatterns: ['dist/*'],
};
50 changes: 50 additions & 0 deletions packages/kubernetes-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "kubernetes-test",
"version": "0.1.0",
"author": "Constructive <developers@constructive.io>",
"description": "Kubernetes testing utilities with wait-for-pods and other helpers for integration tests",
"main": "index.js",
"module": "esm/index.js",
"types": "index.d.ts",
"homepage": "https://github.com/constructive-io/dev-utils",
"license": "MIT",
"publishConfig": {
"access": "public",
"directory": "dist"
},
"repository": {
"type": "git",
"url": "https://github.com/constructive-io/dev-utils"
},
"bugs": {
"url": "https://github.com/constructive-io/dev-utils/issues"
},
"scripts": {
"copy": "makage assets",
"clean": "makage clean",
"prepublishOnly": "npm run build",
"build": "makage build",
"lint": "eslint . --fix",
"test": "jest",
"test:watch": "jest --watch"
},
"keywords": [
"kubernetes",
"k8s",
"testing",
"integration-tests",
"pods",
"wait-for-pods",
"kubernetesjs",
"container",
"orchestration",
"devops",
"cloud-native"
],
"dependencies": {
"kubernetesjs": "^0.7.6"
},
"devDependencies": {
"makage": "0.1.8"
}
}
2 changes: 2 additions & 0 deletions packages/kubernetes-test/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './types';
export * from './wait-for-pods';
79 changes: 79 additions & 0 deletions packages/kubernetes-test/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { Pod, PodList, PodCondition } from 'kubernetesjs';

export type { Pod, PodList, PodCondition };

export interface WaitForPodsOptions {
namespace?: string;
labelSelector?: string;
fieldSelector?: string;
timeoutMs?: number;
pollIntervalMs?: number;
minReady?: number;
allReady?: boolean;
onProgress?: (status: WaitForPodsProgress) => void;
}

export interface WaitForPodsProgress {
total: number;
ready: number;
pending: number;
failed: number;
pods: PodStatusInfo[];
elapsedMs: number;
}

export interface PodStatusInfo {
name: string;
namespace: string;
phase: string;
ready: boolean;
conditions: PodConditionInfo[];
containerStatuses: ContainerStatusInfo[];
}

export interface PodConditionInfo {
type: string;
status: string;
reason?: string;
message?: string;
}

export interface ContainerStatusInfo {
name: string;
ready: boolean;
restartCount: number;
state: 'running' | 'waiting' | 'terminated' | 'unknown';
stateReason?: string;
}

export interface WaitForPodsResult {
success: boolean;
pods: PodStatusInfo[];
message: string;
elapsedMs: number;
}

export interface KubernetesTestClientOptions {
restEndpoint?: string;
namespace?: string;
}

export class WaitForPodsTimeoutError extends Error {
constructor(
message: string,
public readonly progress: WaitForPodsProgress
) {
super(message);
this.name = 'WaitForPodsTimeoutError';
}
}

export class WaitForPodsError extends Error {
constructor(
message: string,
public readonly progress?: WaitForPodsProgress
) {
super(message);
this.name = 'WaitForPodsError';
}
}
Loading