|
| 1 | +#!/usr/bin/env node |
| 2 | +'use strict'; |
| 3 | +const YAML = require('yaml') |
| 4 | +const path = require('path') |
| 5 | +const fs = require('fs') |
| 6 | +process.env.DISABLE_BOX_BANNER = true |
| 7 | +const simplify = require('simplify-sdk') |
| 8 | +const { options } = require('yargs'); |
| 9 | +const readlineSync = require('readline-sync'); |
| 10 | +const { OPT_COMMANDS } = require('./const') |
| 11 | +const yargs = require('yargs'); |
| 12 | +const opName = `executePipeline` |
| 13 | +const CERROR = '\x1b[31m' |
| 14 | +const CGREEN = '\x1b[32m' |
| 15 | +const CPROMPT = '\x1b[33m' |
| 16 | +const CNOTIF = '\x1b[33m' |
| 17 | +const CRESET = '\x1b[0m' |
| 18 | +const CDONE = '\x1b[37m' |
| 19 | +const CBRIGHT = '\x1b[37m' |
| 20 | +const CUNDERLINE = '\x1b[4m' |
| 21 | +const COLORS = function (name) { |
| 22 | + const colorCodes = ["\x1b[31m", "\x1b[32m", "\x1b[33m", "\x1b[34m", "\x1b[35m", "\x1b[36m", "\x1b[31m", "\x1b[32m", "\x1b[33m", "\x1b[34m", "\x1b[35m", "\x1b[36m", "\x1b[31m", "\x1b[32m", "\x1b[33m", "\x1b[34m", "\x1b[35m", "\x1b[36m", "\x1b[31m", "\x1b[32m", "\x1b[33m", "\x1b[34m", "\x1b[35m", "\x1b[36m", "\x1b[31m", "\x1b[32m", "\x1b[33m", "\x1b[34m", "\x1b[35m", "\x1b[36m"] |
| 23 | + return colorCodes[(name.toUpperCase().charCodeAt(0) - 65) % colorCodes.length] |
| 24 | +} |
| 25 | +const envFilePath = path.resolve('.env') |
| 26 | +if (fs.existsSync(envFilePath)) { |
| 27 | + require('dotenv').config({ path: envFilePath }) |
| 28 | +} |
| 29 | + |
| 30 | +const showBoxBanner = function () { |
| 31 | + console.log("╓───────────────────────────────────────────────────────────────╖") |
| 32 | + console.log(`║ Simplify Pipeline - Version ${require('./package.json').version} ║`) |
| 33 | + console.log("╙───────────────────────────────────────────────────────────────╜") |
| 34 | +} |
| 35 | + |
| 36 | +const getErrorMessage = function (error) { |
| 37 | + return error.message ? error.message : JSON.stringify(error) |
| 38 | +} |
| 39 | + |
| 40 | +const getOptionDesc = function (cmdOpt, optName) { |
| 41 | + const options = (OPT_COMMANDS.find(cmd => cmd.name == cmdOpt) || { options: [] }).options |
| 42 | + return (options.find(opt => opt.name == optName) || { desc: '' }).desc |
| 43 | +} |
| 44 | + |
| 45 | +var argv = yargs.usage('simplify-pipeline create|list [stage] [options]') |
| 46 | + .string('help').describe('help', 'display help for a specific command') |
| 47 | + .string('project').describe('project', getOptionDesc('create', 'project')) |
| 48 | + .demandCommand(1).argv; |
| 49 | + |
| 50 | +showBoxBanner() |
| 51 | + |
| 52 | +var cmdOPS = (argv._[0] || 'create').toUpperCase() |
| 53 | +var optCMD = (argv._.length > 1 ? argv._[1] : undefined) |
| 54 | +var index = -1 |
| 55 | +const filename = '.gitlab-ci' |
| 56 | +const projectName = argv['project'] || '.simplify-pipeline' |
| 57 | +const file = fs.readFileSync(path.resolve(__dirname, `${filename}.yml`), 'utf8') |
| 58 | +const yamlObject = YAML.parse(file) |
| 59 | +if (cmdOPS == 'CREATE') { |
| 60 | + if (!optCMD) { |
| 61 | + index = readlineSync.keyInSelect(yamlObject.stages, `Select a stage to execute ?`, { |
| 62 | + cancel: `${CBRIGHT}None${CRESET} - (Escape)` |
| 63 | + }) |
| 64 | + } else { |
| 65 | + index = yamlObject.stages.indexOf(optCMD) |
| 66 | + } |
| 67 | + let dockerfileContent = ['FROM scratch'] |
| 68 | + if (index >= 0) { |
| 69 | + const executedStage = yamlObject.stages[index] |
| 70 | + let dockerComposeContent = { version: '3.8', services: {}, volumes: {} } |
| 71 | + dockerComposeContent.volumes[`${projectName}`] = { |
| 72 | + "driver": "local", |
| 73 | + "driver_opts": { |
| 74 | + "type": "none", |
| 75 | + "device": "$PWD", |
| 76 | + "o": "bind" |
| 77 | + } |
| 78 | + } |
| 79 | + let stageExecutionChains = [] |
| 80 | + let dockerCacheVolumes = [] |
| 81 | + let dockerBaseContent = [`FROM ${yamlObject.image}`, 'WORKDIR /source', 'VOLUME /source', 'COPY . /source'] |
| 82 | + let dockerBasePath = `${projectName}/Dockerfile` |
| 83 | + const baseImage = `base-${projectName.replace(/\./g, '')}` |
| 84 | + const baseDirName = path.dirname(path.resolve(dockerBasePath)) |
| 85 | + if (!fs.existsSync(baseDirName)) { |
| 86 | + fs.mkdirSync(baseDirName, { recursive: true }) |
| 87 | + } |
| 88 | + if (yamlObject.cache && yamlObject.cache.paths && yamlObject.cache.paths.length) { |
| 89 | + dockerCacheVolumes.push() |
| 90 | + } |
| 91 | + fs.writeFileSync(dockerBasePath, dockerBaseContent.join('\n')) |
| 92 | + dockerComposeContent.services[baseImage] = { build: { context: `../`, dockerfile: `${projectName}/Dockerfile`, args: {} } } |
| 93 | + |
| 94 | + const variables = simplify.getContentArgs(yamlObject.variables, { ...process.env }) |
| 95 | + Object.keys(yamlObject).map((key, idx) => { |
| 96 | + if (yamlObject[key].stage === executedStage) { |
| 97 | + const stageName = `${idx}-${executedStage}.${key}` |
| 98 | + dockerComposeContent.services[key] = { build: { context: `../`, dockerfile: `${projectName}/${stageName}.Dockerfile`, args: {} }, volumes: [`${projectName}:/source`] } |
| 99 | + stageExecutionChains.push(`${stageName}`) |
| 100 | + dockerfileContent = [`FROM ${projectName.replace(/\./g, '')}_${baseImage}`, 'WORKDIR /source'] |
| 101 | + let dockerCommands = [] |
| 102 | + simplify.getContentArgs(yamlObject[key].script, { ...process.env }, { ...variables }).map(script => { |
| 103 | + let scriptContent = script |
| 104 | + if (script.startsWith('export ')) { |
| 105 | + let dockerOpts = 'ENV' |
| 106 | + scriptContent = script.replace('export ', '') |
| 107 | + const argKeyValue = scriptContent.split('=') |
| 108 | + dockerComposeContent.services[key].build.args[`${argKeyValue[0].trim()}`] = `${argKeyValue[1].trim()}` |
| 109 | + scriptContent = `${argKeyValue[0].trim()}="${argKeyValue[1].trim()}"` |
| 110 | + dockerfileContent.push(`${dockerOpts} ${scriptContent}`) |
| 111 | + } else { |
| 112 | + dockerCommands.push(`RUN ${scriptContent}`) |
| 113 | + } |
| 114 | + }) |
| 115 | + dockerfileContent.push(`${dockerCommands.join('\n')}`) |
| 116 | + let dockerfilePath = `${projectName}/${stageName}.Dockerfile` |
| 117 | + const pathDirName = path.dirname(path.resolve(dockerfilePath)) |
| 118 | + if (!fs.existsSync(pathDirName)) { |
| 119 | + fs.mkdirSync(pathDirName, { recursive: true }) |
| 120 | + } |
| 121 | + fs.writeFileSync(dockerfilePath, dockerfileContent.join('\n')) |
| 122 | + } |
| 123 | + }) |
| 124 | + let dockerComposePath = `${projectName}/docker-compose.${executedStage}.yml` |
| 125 | + fs.writeFileSync(dockerComposePath, YAML.stringify(dockerComposeContent)) |
| 126 | + console.log(`Created ${projectName} docker-compose for stage '${optCMD}' cached to '${projectName}' volume`) |
| 127 | + fs.writeFileSync(`pipeline.bash`, [ |
| 128 | + '#!/bin/bash', |
| 129 | + `cd ${projectName}`, |
| 130 | + `docker-compose -f docker-compose.$1.yml up --force-recreate` |
| 131 | + ].join('\n')) |
| 132 | + } |
| 133 | +} else if (cmdOPS == 'LIST') { |
| 134 | + yamlObject.stages.map((cmd, idx) => { |
| 135 | + console.log(`\t- ${CPROMPT}${cmd.toLowerCase()}${CRESET}`) |
| 136 | + }) |
| 137 | +} else { |
| 138 | + yargs.showHelp() |
| 139 | + console.log(`\n`, ` * ${CBRIGHT}Supported command list${CRESET}:`, '\n') |
| 140 | + OPT_COMMANDS.map((cmd, idx) => { |
| 141 | + console.log(`\t- ${CPROMPT}${cmd.name.toLowerCase()}${CRESET} : ${cmd.desc}`) |
| 142 | + }) |
| 143 | + console.log(`\n`) |
| 144 | + process.exit(0) |
| 145 | +} |
0 commit comments