@deploystack/docker-to-iac
Version:
Translate docker run and docker compose file to Infrastructure as Code
385 lines (384 loc) • 19.4 kB
JavaScript
"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']);
});
});
});