UNPKG

singularci

Version:

SingularCI is a DSL transpiler used to generate CI/CD configuration files for existing CI platforms

295 lines 13.1 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const yaml_1 = __importDefault(require("yaml")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const StageSymbolTable_1 = __importDefault(require("./StageSymbolTable")); const Job_1 = __importDefault(require("../SemanticModel/Job")); const Stage_1 = __importDefault(require("../SemanticModel/Stage")); const Targets_1 = require("./../SemanticModel/Targets"); const typedi_1 = require("typedi"); const JobBuilderFactory_1 = require("./JobBuilderFactory"); const Run_1 = __importDefault(require("../SemanticModel/Tasks/Run")); const Checkout_1 = __importDefault(require("../SemanticModel/Tasks/Checkout")); const Variables_1 = __importDefault(require("./../SemanticModel/Variables")); let DSLParser = class DSLParser { constructor(inputFileName) { this.inputFileClone = ""; this.createTempFile = () => { if (!fs_1.default.existsSync(this.inputFilePath)) throw new Error(`Error: File was not found in the root of the project: ${this.inputFilePath}`); if (fs_1.default.existsSync(this.fileClonePath)) { fs_1.default.rmSync(this.fileClonePath); } fs_1.default.copyFileSync(this.inputFilePath, this.fileClonePath); this.inputFileClone = fs_1.default.readFileSync(this.fileClonePath, 'utf8'); }; this.inputFilePath = path_1.default.join(process.cwd(), inputFileName); const fileCloneName = '.singularci-copy.yml'; this.fileClonePath = path_1.default.join(process.cwd(), fileCloneName); } parse() { this.createTempFile(); this.validateYAMLStructure(); const variables = this.buildVariables(); this.resolveVariables(variables); const targets = this.buildTargets(); const triggers = this.buildTriggers(); const stages = this.buildStages(); this.buildSymbolTable(stages); return this.buildPipeline(targets, variables, triggers); } validateYAMLStructure() { if (yaml_1.default.parse(this.inputFileClone)['pipeline'] == null) throw new Error('The keyword "pipeline" is missing'); if (yaml_1.default.parse(this.inputFileClone)['pipeline']['targets'] == null) throw new Error('No targets defined'); if (yaml_1.default.parse(this.inputFileClone)['pipeline']['triggers'] == null) throw new Error('No triggers defined'); if (yaml_1.default.parse(this.inputFileClone)['pipeline']['stages'] == null) throw new Error('The keyword "stages" is missing'); } resolveVariables(variables) { for (const variable in variables.getVariables()) { this.inputFileClone = this.inputFileClone.replaceAll("${" + variable + "}", variables.getVariable(variable)); } // The regex tests if there are any undeclared variables in the file, which are not platform specific secrets const undefinedVariables = this.inputFileClone.match(/\$\{(?!secrets\.)[a-zA-Z][^{}]+\}/gm); if (undefinedVariables != null) { throw new Error(`Error: The following variable(s) are used, but not declared: ${undefinedVariables}`); } } buildTargets() { try { const targetsArray = yaml_1.default.parse(this.inputFileClone)['pipeline']['targets']; const targets = this.targetsFactory.createTargets(); for (const target of targetsArray) { targets.addTarget(target); } return targets; } catch (error) { throw new Error(error.message); } } buildTriggers() { try { const triggersArray = yaml_1.default.parse(this.inputFileClone)['pipeline']['triggers']; const triggers = this.triggerFactory.createTrigger(); if (!triggersArray.trigger_types) throw new Error('No trigger types defined'); if (!triggersArray.branches) throw new Error('No trigger branches defined'); for (const triggerTypes of triggersArray.trigger_types) { triggers.addType(triggerTypes); } for (const triggerBranch of triggersArray.branches) { triggers.addBranch(triggerBranch); } return triggers; } catch (error) { throw new Error(error.message); } } buildVariables() { try { const variables = this.variablesFactory.createVariables(); if (yaml_1.default.parse(this.inputFileClone)['pipeline']['variables'] != null) { const variablesArray = yaml_1.default.parse(this.inputFileClone)['pipeline']['variables']; if (variablesArray) { for (const variable of variablesArray) { variables.addVariable(variable.key, variable.value); } } } return variables; } catch (error) { throw new Error(error.message); } } buildStages() { try { const stages = yaml_1.default.parse(this.inputFileClone)['pipeline']['stages']; const stageList = []; for (const stageObject of stages) { stageList.push(this.buildStage(stageObject.stage)); } return stageList; } catch (error) { throw new Error(error.message); } } buildSymbolTable(stages) { const stageSymbolTable = StageSymbolTable_1.default.getInstance(); stageSymbolTable.reset(); for (const stage of stages) { stageSymbolTable.addStage(stage); } } buildStage(stage) { try { if (!stage.name) throw new Error(`A stage is missing a name`); if (!stage.runs_on) throw new Error(`The stage ${stage.name} is missing a runs_on property`); if (!stage.jobs) throw new Error(`The stage ${stage.name} does not contain any jobs`); const needs = this.getNeedsFromStage(stage); const jobs = this.buildJobs(stage); return this.stageFactory.createStage(stage.name, jobs, needs, stage.runs_on); } catch (error) { throw new Error(error.message); } } getNeedsFromStage(stage) { const needs = []; if (stage.needs) { for (const need of stage.needs) { needs.push(need); } } return needs; } buildJobs(stage) { const jobs = []; for (const job of stage.jobs) { const jobBuilder = this.jobBuilderFactory.createJobBuilder(); if (!job.name) throw new Error(`A job is missing a name`); jobBuilder.setName(job.name); this.addTasksToJob(job, jobBuilder); jobs.push(new Job_1.default(jobBuilder.getName(), jobBuilder.getTasks())); } return jobs; } addTasksToJob(job, jobBuilder) { try { if (job.run) { if (!Array.isArray(job.run)) throw new Error(`The run property of job ${job.name} is not an array`); jobBuilder.addTask(this.generateRunTask(job.run)); } if (job.docker_build) { jobBuilder.addTask(this.generateDockerBuildTask(job.docker_build)); } if (job.checkout) { jobBuilder.addTask(this.generateCheckoutTask(job.checkout)); } } catch (error) { throw new Error(error.message); } } generateRunTask(commands) { if (commands.length === 0) throw new Error(`A run task does not contain any commands`); if (!commands) throw new Error(`A run task is not valid`); const run = this.runFactory.createRunTask(commands); return run; } generateDockerBuildTask(job) { if (!job.image_name) throw new Error(`A docker build task is missing the property image_name`); if (!job.docker_file_path) throw new Error(`A docker build task is missing the property docker_file_path`); if (!job.user_name) throw new Error(`A docker build task is missing the property user_name`); if (!job.password) throw new Error(`A docker build task is missing the property password`); const docker_build = this.buildDockerImageFactory.createBuildDockerImageTask(job.image_name, job.docker_file_path, job.user_name, job.password); return docker_build; } generateCheckoutTask(job) { if (!job.repo_url) throw new Error(`A checkout task is missing the property repo_url`); if (!job.repo_name) throw new Error(`A checkout task is missing the property repo_name`); const checkout = this.checkoutFactory.createCheckoutTask(job.repo_url, job.repo_name); return checkout; } buildPipeline(targets, variables, trigger) { const stageSymbolTable = StageSymbolTable_1.default.getInstance(); this.pipeline.reset(); for (const stage in stageSymbolTable.getStages()) { const stageBuilder = stageSymbolTable.getStage(stage); const finalStage = this.stageFactory.createStage(stageBuilder.getName(), stageBuilder.getJobs(), stageBuilder.getNeeds(), stageBuilder.getRunsOn()); this.pipeline.addStage(finalStage); } this.pipeline.setPlatformTargets(targets); this.pipeline.setVariables(variables); this.pipeline.setTrigger(trigger); return this.pipeline; } }; __decorate([ (0, typedi_1.Inject)('Pipeline') // @ts-ignore , __metadata("design:type", Object) ], DSLParser.prototype, "pipeline", void 0); __decorate([ (0, typedi_1.Inject)('JobBuilderFactory') // @ts-ignore , __metadata("design:type", JobBuilderFactory_1.JobBuilderFactory) ], DSLParser.prototype, "jobBuilderFactory", void 0); __decorate([ (0, typedi_1.Inject)('TargetsFactory') // @ts-ignore , __metadata("design:type", Targets_1.TargetsFactory) ], DSLParser.prototype, "targetsFactory", void 0); __decorate([ (0, typedi_1.Inject)('TriggerFactory') // @ts-ignore , __metadata("design:type", Object) ], DSLParser.prototype, "triggerFactory", void 0); __decorate([ (0, typedi_1.Inject)('VariablesFactory') // @ts-ignore , __metadata("design:type", Variables_1.default) ], DSLParser.prototype, "variablesFactory", void 0); __decorate([ (0, typedi_1.Inject)('StageFactory') // @ts-ignore , __metadata("design:type", Stage_1.default) ], DSLParser.prototype, "stageFactory", void 0); __decorate([ (0, typedi_1.Inject)('BuildDockerImageFactory') // @ts-ignore , __metadata("design:type", Object) ], DSLParser.prototype, "buildDockerImageFactory", void 0); __decorate([ (0, typedi_1.Inject)('RunFactory') // @ts-ignore , __metadata("design:type", Run_1.default) ], DSLParser.prototype, "runFactory", void 0); __decorate([ (0, typedi_1.Inject)('CheckoutFactory') //@ts-ignore , __metadata("design:type", Checkout_1.default) ], DSLParser.prototype, "checkoutFactory", void 0); DSLParser = __decorate([ (0, typedi_1.Service)({ id: 'dslparser' }), __param(0, (0, typedi_1.Inject)('dslparser.inputFileName')), __metadata("design:paramtypes", [String]) ], DSLParser); exports.default = DSLParser; //# sourceMappingURL=DSLParser.js.map