UNPKG

@deploystack/docker-to-iac

Version:

Translate docker run and docker compose file to Infrastructure as Code

385 lines (384 loc) 19.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const run_1 = require("../../../../src/sources/run"); const base_1 = require("../../../../src/sources/base"); const base_parser_1 = require("../../../../src/parsers/base-parser"); const normalizePortModule = __importStar(require("../../../../src/utils/normalizePort")); const normalizeVolumeModule = __importStar(require("../../../../src/utils/normalizeVolume")); const normalizeEnvironmentModule = __importStar(require("../../../../src/utils/normalizeEnvironment")); const processEnvironmentVariablesGenerationModule = __importStar(require("../../../../src/utils/processEnvironmentVariablesGeneration")); const parseDockerImageModule = __importStar(require("../../../../src/utils/parseDockerImage")); (0, vitest_1.describe)('RunCommandParser', () => { let parser; (0, vitest_1.beforeEach)(() => { parser = new run_1.RunCommandParser(); // Reset all mocks vitest_1.vi.restoreAllMocks(); }); (0, vitest_1.describe)('validate', () => { (0, vitest_1.test)('should validate valid docker run command', () => { (0, vitest_1.expect)(parser.validate('docker run nginx')).toBe(true); (0, vitest_1.expect)(parser.validate('docker run -p 80:80 nginx')).toBe(true); (0, vitest_1.expect)(parser.validate('docker run --name webapp nginx')).toBe(true); }); (0, vitest_1.test)('should throw error for invalid command', () => { (0, vitest_1.expect)(() => parser.validate('invalid command')) .toThrow(base_1.SourceValidationError); (0, vitest_1.expect)(() => parser.validate('invalid command')) .toThrow('Command must start with "docker run"'); }); (0, vitest_1.test)('should throw error for malformed docker command', () => { (0, vitest_1.expect)(() => parser.validate('dockerrun')) .toThrow(base_1.SourceValidationError); }); }); (0, vitest_1.describe)('parse', () => { (0, vitest_1.test)('should parse simple docker run command', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); const result = parser.parse('docker run nginx'); (0, vitest_1.expect)(result.services).toHaveProperty('default'); (0, vitest_1.expect)(result.services.default.image).toEqual({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); (0, vitest_1.expect)(result.services.default.ports).toEqual([]); (0, vitest_1.expect)(result.services.default.volumes).toEqual([]); (0, vitest_1.expect)(result.services.default.environment).toEqual({}); (0, vitest_1.expect)(result.services.default.command).toBe(''); }); (0, vitest_1.test)('should parse docker run command with port mappings', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); // Mock normalizePort vitest_1.vi.spyOn(normalizePortModule, 'normalizePort').mockReturnValue({ host: 80, container: 80 }); const result = parser.parse('docker run -p 80:80 nginx'); (0, vitest_1.expect)(result.services.default.ports).toEqual([{ host: 80, container: 80 }]); (0, vitest_1.expect)(normalizePortModule.normalizePort).toHaveBeenCalledWith('80:80'); }); (0, vitest_1.test)('should parse docker run command with environment variables', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); // Mock normalizeEnvironment vitest_1.vi.spyOn(normalizeEnvironmentModule, 'normalizeEnvironment').mockReturnValue({ NODE_ENV: 'production' }); const result = parser.parse('docker run -e NODE_ENV=production nginx'); (0, vitest_1.expect)(result.services.default.environment).toEqual({ NODE_ENV: 'production' }); (0, vitest_1.expect)(normalizeEnvironmentModule.normalizeEnvironment).toHaveBeenCalledWith('NODE_ENV=production'); }); (0, vitest_1.test)('should parse docker run command with volumes', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); // Mock normalizeVolume vitest_1.vi.spyOn(normalizeVolumeModule, 'normalizeVolume').mockReturnValue({ host: './html', container: '/usr/share/nginx/html', mode: 'rw' }); const result = parser.parse('docker run -v ./html:/usr/share/nginx/html nginx'); (0, vitest_1.expect)(result.services.default.volumes).toEqual([{ host: './html', container: '/usr/share/nginx/html', mode: 'rw' }]); (0, vitest_1.expect)(normalizeVolumeModule.normalizeVolume).toHaveBeenCalledWith('./html:/usr/share/nginx/html'); }); (0, vitest_1.test)('should parse docker run command with command', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); const result = parser.parse('docker run nginx bash echo hello'); (0, vitest_1.expect)(result.services.default.command).toBe('bash echo hello'); }); (0, vitest_1.test)('should handle quoted arguments correctly', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); // Mock normalizeEnvironment vitest_1.vi.spyOn(normalizeEnvironmentModule, 'normalizeEnvironment').mockReturnValue({ GREETING: 'Hello World' }); const result = parser.parse('docker run -e "GREETING=Hello World" nginx'); (0, vitest_1.expect)(normalizeEnvironmentModule.normalizeEnvironment).toHaveBeenCalledWith('GREETING=Hello World'); (0, vitest_1.expect)(result.services.default.environment).toEqual({ GREETING: 'Hello World' }); }); (0, vitest_1.test)('should process environment variables with environmentOptions', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'mysql', tag: 'latest' }); // Mock normalizeEnvironment vitest_1.vi.spyOn(normalizeEnvironmentModule, 'normalizeEnvironment').mockReturnValue({ MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' }); // Mock processEnvironmentVariablesGeneration vitest_1.vi.spyOn(processEnvironmentVariablesGenerationModule, 'processEnvironmentVariablesGeneration') .mockReturnValue({ MYSQL_ROOT_PASSWORD: 'secure_password', MYSQL_DATABASE: 'app_db' }); // Create properly typed environment options const envOptions = { environmentVariables: { DB_PASSWORD: 'secure_password' }, environmentGeneration: { mysql: { versions: { latest: { environment: { MYSQL_DATABASE: { type: 'string' // Use const assertion to ensure exact string literal type } } } } } }, getPersistedEnvVars: vitest_1.vi.fn().mockReturnValue({}), setPersistedEnvVars: vitest_1.vi.fn() }; const result = parser.parse('docker run -e MYSQL_ROOT_PASSWORD=${DB_PASSWORD} mysql', envOptions); (0, vitest_1.expect)(result.services.default.environment).toEqual({ MYSQL_ROOT_PASSWORD: 'secure_password', MYSQL_DATABASE: 'app_db' }); (0, vitest_1.expect)(envOptions.setPersistedEnvVars).toHaveBeenCalledWith('default', { MYSQL_ROOT_PASSWORD: 'secure_password', MYSQL_DATABASE: 'app_db' }); }); (0, vitest_1.test)('should combine command line and persisted environment variables', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'postgres', tag: 'latest' }); // Mock normalizeEnvironment vitest_1.vi.spyOn(normalizeEnvironmentModule, 'normalizeEnvironment').mockReturnValue({ POSTGRES_PASSWORD: 'example' }); const envOptions = { getPersistedEnvVars: vitest_1.vi.fn().mockReturnValue({ POSTGRES_USER: 'admin', POSTGRES_DB: 'app_db' }), setPersistedEnvVars: vitest_1.vi.fn() }; const result = parser.parse('docker run -e POSTGRES_PASSWORD=example postgres', envOptions); // Check that both command line and persisted vars were combined (0, vitest_1.expect)(result.services.default.environment).toEqual({ POSTGRES_PASSWORD: 'example', POSTGRES_USER: 'admin', POSTGRES_DB: 'app_db' }); }); (0, vitest_1.test)('should handle multiple options of the same type', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); // Mock normalizePort vitest_1.vi.spyOn(normalizePortModule, 'normalizePort') .mockReturnValueOnce({ host: 80, container: 80 }) .mockReturnValueOnce({ host: 443, container: 443 }); // Mock normalizeEnvironment vitest_1.vi.spyOn(normalizeEnvironmentModule, 'normalizeEnvironment') .mockReturnValueOnce({ VAR1: 'value1' }) .mockReturnValueOnce({ VAR2: 'value2' }); const result = parser.parse('docker run -p 80:80 -p 443:443 -e VAR1=value1 -e VAR2=value2 nginx'); (0, vitest_1.expect)(result.services.default.ports).toEqual([ { host: 80, container: 80 }, { host: 443, container: 443 } ]); (0, vitest_1.expect)(result.services.default.environment).toEqual({ VAR1: 'value1', VAR2: 'value2' }); }); (0, vitest_1.test)('should ignore unsupported options', () => { // Mock parseDockerImage vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage').mockReturnValue({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); const result = parser.parse('docker run --name my-nginx --restart always --network my-net nginx'); // These options are ignored but should not cause errors (0, vitest_1.expect)(result.services.default.image).toEqual({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); }); (0, vitest_1.test)('should handle standalone flags like --rm correctly', () => { // We need to use mockImplementation instead of mockReturnValue // to handle different calls to parseDockerImage with different arguments const parseDockerImageMock = vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage'); // Configure the mock to return the expected value when called with 'nginx' parseDockerImageMock.mockImplementation((imageString) => { if (imageString === 'nginx') { return { registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }; } // Default fallback return { registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: imageString, }; }); const result = parser.parse('docker run --rm nginx'); // Verify that parseDockerImage was called with 'nginx' (0, vitest_1.expect)(parseDockerImageMock).toHaveBeenCalledWith('nginx'); // The --rm flag should be ignored but not affect image parsing (0, vitest_1.expect)(result.services.default.image).toEqual({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'nginx', tag: 'latest' }); }); (0, vitest_1.test)('should correctly parse the portkeyai/gateway example', () => { // Use mockImplementation for the specific case that was failing const parseDockerImageMock = vitest_1.vi.spyOn(parseDockerImageModule, 'parseDockerImage'); parseDockerImageMock.mockImplementation((imageString) => { if (imageString === 'portkeyai/gateway:latest') { return { registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'portkeyai/gateway', tag: 'latest' }; } // Default fallback return { registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: imageString, }; }); // Mock normalizePort vitest_1.vi.spyOn(normalizePortModule, 'normalizePort').mockReturnValue({ host: 8787, container: 8787 }); const result = parser.parse('docker run --rm -p 8787:8787 portkeyai/gateway:latest'); // Verify parseDockerImage was called with the correct image string (0, vitest_1.expect)(parseDockerImageMock).toHaveBeenCalledWith('portkeyai/gateway:latest'); // Check that the image and ports are correctly parsed (0, vitest_1.expect)(result.services.default.image).toEqual({ registry_type: base_parser_1.RegistryType.DOCKER_HUB, repository: 'portkeyai/gateway', tag: 'latest' }); (0, vitest_1.expect)(result.services.default.ports).toEqual([ { host: 8787, container: 8787 } ]); // Command should be empty as there are no arguments after the image (0, vitest_1.expect)(result.services.default.command).toBe(''); }); }); (0, vitest_1.describe)('utility functions', () => { (0, vitest_1.test)('splitCommand handles quoted arguments', () => { // Access private method via type casting const splitCommand = parser.splitCommand.bind(parser); const result = splitCommand('docker run -e "FOO=BAR BAZ" image'); (0, vitest_1.expect)(result).toEqual(['docker', 'run', '-e', 'FOO=BAR BAZ', 'image']); }); (0, vitest_1.test)('splitCommand handles single quotes', () => { // Access private method via type casting const splitCommand = parser.splitCommand.bind(parser); const result = splitCommand("docker run -e 'FOO=BAR BAZ' image"); (0, vitest_1.expect)(result).toEqual(['docker', 'run', '-e', 'FOO=BAR BAZ', 'image']); }); (0, vitest_1.test)('splitCommand handles mixed quotes', () => { // Access private method via type casting const splitCommand = parser.splitCommand.bind(parser); const result = splitCommand('docker run -e "FOO=\'BAR\'" -e \'BAZ="QUX"\' image'); (0, vitest_1.expect)(result).toEqual(['docker', 'run', '-e', 'FOO=\'BAR\'', '-e', 'BAZ="QUX"', 'image']); }); }); });