UNPKG

dryrun-ci

Version:

DryRun CI - Local GitLab CI/CD pipeline testing tool with Docker execution, performance monitoring, and security sandboxing

182 lines (181 loc) โ€ข 8.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DockerExecutor = void 0; const child_process_1 = require("child_process"); class DockerExecutor { async createContainer(job) { const image = job.image || 'node:18'; await this.validateAndPullImage(image, job); const envVars = this.prepareEnvironmentVariables(job); const createArgs = [ 'create', '-it', '--entrypoint', 'sh', ...envVars, '-w', '/workspace', '-v', `${process.cwd()}:/workspace`, image, '-c', 'sleep infinity' ]; const create = (0, child_process_1.spawnSync)('docker', createArgs, { encoding: 'utf-8' }); if (create.status !== 0) { throw new Error(`Failed to create Docker container: ${create.stderr || create.stdout}`); } const containerId = create.stdout.trim(); const start = (0, child_process_1.spawnSync)('docker', ['start', containerId], { encoding: 'utf-8' }); if (start.status !== 0) { throw new Error(`Failed to start Docker container: ${start.stderr || start.stdout}`); } return containerId; } async validateAndPullImage(image, job) { console.log(`๐Ÿ” Validating image: ${image}`); const inspect = (0, child_process_1.spawnSync)('docker', ['inspect', image], { encoding: 'utf-8' }); if (inspect.status !== 0) { console.log(`๐Ÿ“ฅ Image not found locally, pulling: ${image}`); const pull = (0, child_process_1.spawnSync)('docker', ['pull', image], { encoding: 'utf-8' }); if (pull.status !== 0) { const suggestions = this.getImageSuggestions(image, job); throw new Error(`Failed to pull Docker image '${image}':\n${pull.stderr || pull.stdout}\n\n๐Ÿ’ก Suggestions:\n${suggestions}`); } } else { console.log(`โœ… Image found locally: ${image}`); } } getImageSuggestions(image, job) { const suggestions = []; if (image.includes('latest')) { suggestions.push('- Consider using a specific version tag for reproducible builds'); } if (image === 'docker:latest' && job.script?.some(cmd => cmd.includes('pip install'))) { suggestions.push('- For Python/pip operations, consider using python:3.11-slim instead of docker:latest'); suggestions.push('- Or use alpine:latest with apk package manager'); } if (image.includes('node') && job.script?.some(cmd => cmd.includes('docker build'))) { suggestions.push('- For Docker operations, consider using docker:latest or docker:dind'); } suggestions.push('- Check your internet connection'); suggestions.push('- Verify Docker daemon is running: docker info'); return suggestions.join('\n'); } async executeJob(containerId, job) { let output = ''; let exitCode = 0; try { if (job.before_script && job.before_script.length > 0) { console.log('๐Ÿ”ง Running before_script...'); const beforeResult = await this.executeCommands(containerId, job.before_script); output += beforeResult.output; if (beforeResult.exitCode !== 0) { output += this.getBeforeScriptDebugInfo(job); return { exitCode: beforeResult.exitCode, output }; } } if (job.script && job.script.length > 0) { console.log('๐Ÿš€ Running script...'); const scriptResult = await this.executeCommands(containerId, job.script); output += scriptResult.output; exitCode = scriptResult.exitCode; } if (job.after_script && job.after_script.length > 0) { console.log('๐Ÿงน Running after_script...'); const afterResult = await this.executeCommands(containerId, job.after_script); output += afterResult.output; } } catch (error) { output += `\nError executing job: ${error}\n`; exitCode = 1; } return { exitCode, output }; } getBeforeScriptDebugInfo(job) { const debugInfo = ['\n๐Ÿ” Debug Information:']; if (job.image?.includes('alpine') && job.before_script?.some(cmd => cmd.includes('pip install'))) { debugInfo.push('- Alpine Linux detected with pip install commands'); debugInfo.push('- Consider using apk package manager instead: apk add py3-awscli'); debugInfo.push('- Or use python:3.11-slim base image'); } if (job.image?.includes('docker:latest') && job.before_script?.some(cmd => cmd.includes('python'))) { debugInfo.push('- Docker image with Python operations detected'); debugInfo.push('- Consider using python:3.11-slim for Python-heavy jobs'); } debugInfo.push('- Check the command output above for specific error details'); debugInfo.push('- Verify all required tools are available in the base image'); return debugInfo.join('\n'); } async executeCommands(containerId, commands) { let output = ''; let exitCode = 0; for (const command of commands) { console.log(` โ†’ ${command}`); let exec = (0, child_process_1.spawnSync)('docker', [ 'exec', '-i', containerId, 'sh', '-c', command ], { encoding: 'utf-8', cwd: process.cwd() }); if (exec.stdout) output += exec.stdout; if (exec.stderr) output += exec.stderr; if (exec.status !== 0) { console.log(` โŒ Command failed: ${command}`); console.log(` Exit code: ${exec.status || 1}`); if (exec.stderr) { console.log(` Error: ${exec.stderr}`); } output += `\nโŒ Command failed: ${command}\n`; output += `Exit code: ${exec.status || 1}\n`; if (exec.stderr) { output += `Error: ${exec.stderr}\n`; } if (exec.stderr && exec.stderr.includes('externally-managed-environment')) { output += `\n๐Ÿ’ก This is a real Python environment issue (PEP 668).\n`; output += ` Your pipeline needs to be fixed to handle this properly.\n`; output += ` Consider using: apk add aws-cli (instead of pip install awscli)\n`; } exitCode = exec.status || 1; break; } } return { exitCode, output }; } prepareEnvironmentVariables(job) { const envVars = []; const commonVars = { 'CI': 'true', 'CI_COMMIT_SHA': 'test-commit-sha', 'CI_PIPELINE_ID': '12345', 'CI_PROJECT_DIR': '/workspace', 'CI_JOB_NAME': job.name || 'unknown', 'CI_JOB_STAGE': job.stage || 'unknown' }; Object.entries(commonVars).forEach(([key, value]) => { envVars.push('-e', `${key}=${value}`); }); if (job.variables) { Object.entries(job.variables).forEach(([key, value]) => { envVars.push('-e', `${key}=${value}`); }); } return envVars; } async stopContainer(containerId) { const stop = (0, child_process_1.spawnSync)('docker', ['stop', containerId], { encoding: 'utf-8' }); if (stop.status !== 0) { throw new Error(`Failed to stop Docker container: ${stop.stderr || stop.stdout}`); } } async removeContainer(containerId) { const rm = (0, child_process_1.spawnSync)('docker', ['rm', '-f', containerId], { encoding: 'utf-8' }); if (rm.status !== 0) { throw new Error(`Failed to remove Docker container: ${rm.stderr || rm.stdout}`); } } } exports.DockerExecutor = DockerExecutor;