singularci
Version:
SingularCI is a DSL transpiler used to generate CI/CD configuration files for existing CI platforms
295 lines • 13.1 kB
JavaScript
"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