/**
* @fileoverview Container management utilities for VyOS MCP testing
*
* This module provides utilities for managing VyOS and MCP server containers
* during integration testing, using testcontainers for container lifecycle
* management.
*
* @author VyOS MCP Server Tests
* @version 1.0.0
* @since 2025-07-12
*/
import { GenericContainer, StartedTestContainer, Wait } from 'testcontainers';
import { VyOSClient } from '../../src/vyos-client';
export interface ContainerTestConfig {
vyosApiKey: string;
networkName: string;
vyosImage: string;
mcpImage?: string;
timeout: number;
}
export interface StartedContainers {
vyosContainer: StartedTestContainer;
mcpContainer?: StartedTestContainer;
networkName: string;
vyosClient: VyOSClient;
cleanup: () => Promise<void>;
}
export class VyOSContainerManager {
private config: ContainerTestConfig;
private containers: StartedTestContainer[] = [];
constructor(config: Partial<ContainerTestConfig> = {}) {
this.config = {
vyosApiKey: 'vyos-mcp-test-key',
networkName: 'vyos-test-network',
// Note: vyos/vyos-build:current is a build environment, not runtime VyOS
// For proper testing, build a runtime image with scripts/build-vyos-image.sh
vyosImage: 'docker.io/vyos/vyos-build:current',
timeout: 120000, // 2 minutes
...config,
};
}
/**
* Start VyOS container with API configuration
*/
async startVyOSContainer(): Promise<StartedTestContainer> {
console.log('Starting VyOS container...');
// Warn if using build image
if (this.config.vyosImage.includes('vyos-build')) {
console.warn('⚠️ WARNING: Using vyos/vyos-build:current (build environment)');
console.warn(' For proper VyOS API testing, build a runtime image:');
console.warn(' sudo ./scripts/build-vyos-image.sh');
console.warn(' Then update tests to use: vyos-runtime:1.4-rolling-*');
}
const container = await new GenericContainer(this.config.vyosImage)
.withPrivilegedMode()
.withExposedPorts(22, 443)
.withEnvironment({
VYOS_API_KEY: this.config.vyosApiKey,
})
.withWaitStrategy(
Wait.forAll([
Wait.forListeningPorts(),
// For build image, just wait for container to be running
Wait.forLogMessage(/.*/, 1, 30000),
])
)
.withStartupTimeout(this.config.timeout)
.withEntrypoint([
'sh', '-c',
`if [ -x /sbin/init ]; then /sbin/init & else sleep infinity & fi; wait`
])
.start();
this.containers.push(container);
// Wait a bit more for VyOS to fully initialize
await this.sleep(10000);
// Configure VyOS via CLI commands
await this.configureVyOS(container);
return container;
}
/**
* Configure VyOS instance with API access
*/
private async configureVyOS(container: StartedTestContainer): Promise<void> {
console.log('Configuring VyOS API...');
const commands = [
// Enter configuration mode
'/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper begin',
// Configure system
"/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper set system host-name 'vyos-test'",
// Configure API
`/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper set service https api keys id vyos-mcp key '${this.config.vyosApiKey}'`,
'/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper set service https port 443',
"/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper set service https api cors allow-origin '*'",
// Commit and save
'/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper commit',
'/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper save',
'/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper end',
];
for (const command of commands) {
try {
const result = await container.exec(command.split(' '));
if (result.exitCode !== 0) {
console.warn(`Command failed: ${command}, exit code: ${result.exitCode}`);
}
} catch (error) {
console.warn(`Error executing command: ${command}`, error);
}
}
// Wait for API to be ready
await this.sleep(5000);
}
/**
* Create VyOS client for the container
*/
createVyOSClient(container: StartedTestContainer): VyOSClient {
const host = container.getHost();
const port = container.getMappedPort(443);
return new VyOSClient({
host: `https://${host}:${port}`,
apiKey: this.config.vyosApiKey,
timeout: 30000,
verifySSL: false,
});
}
/**
* Start all containers and return managed setup
*/
async startContainers(): Promise<StartedContainers> {
const vyosContainer = await this.startVyOSContainer();
const vyosClient = this.createVyOSClient(vyosContainer);
return {
vyosContainer,
networkName: this.config.networkName,
vyosClient,
cleanup: async () => {
await this.cleanup();
},
};
}
/**
* Wait for VyOS API to be ready
*/
async waitForVyOSAPI(client: VyOSClient, maxRetries = 30): Promise<boolean> {
console.log('Waiting for VyOS API to be ready...');
for (let i = 0; i < maxRetries; i++) {
try {
await client.getSystemInfo();
console.log('VyOS API is ready!');
return true;
} catch (error) {
console.log(`Waiting for API... attempt ${i + 1}/${maxRetries}`);
await this.sleep(2000);
}
}
console.error('VyOS API failed to become ready');
return false;
}
/**
* Test VyOS API connectivity
*/
async testConnectivity(client: VyOSClient): Promise<boolean> {
try {
const info = await client.getSystemInfo();
console.log('VyOS connectivity test passed:', info);
return true;
} catch (error) {
console.error('VyOS connectivity test failed:', error);
return false;
}
}
/**
* Clean up all containers
*/
async cleanup(): Promise<void> {
console.log('Cleaning up containers...');
for (const container of this.containers) {
try {
await container.stop();
} catch (error) {
console.warn('Error stopping container:', error);
}
}
this.containers = [];
}
/**
* Utility function to sleep
*/
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
/**
* Create a managed VyOS test environment
*/
export async function createVyOSTestEnvironment(config?: Partial<ContainerTestConfig>): Promise<StartedContainers> {
const manager = new VyOSContainerManager(config);
const containers = await manager.startContainers();
// Wait for API to be ready
const apiReady = await manager.waitForVyOSAPI(containers.vyosClient);
if (!apiReady) {
await containers.cleanup();
throw new Error('VyOS API failed to become ready');
}
return containers;
}