import * as plugins from "./npmci.plugins"; import * as paths from "./npmci.paths"; import * as NpmciEnv from "./npmci.env"; import {bashBare} from "./npmci.bash"; /** * builds a cwd of Dockerfiles by triggering a promisechain */ export let build = function(){ let done = plugins.q.defer(); readDockerfiles() .then(sortDockerfiles) .then(mapDockerfiles) .then(buildDockerfiles) .then(pushDockerfiles) .then(() => { done.resolve(); }); return done.promise; } /** * creates instance of class Dockerfile for all Dockerfiles in cwd * @returns Promise */ export let readDockerfiles = function():plugins.q.Promise{ let done = plugins.q.defer(); let readDockerfilesArray:Dockerfile[] = [] plugins.gulp.src("./Dockerfile*") .pipe(plugins.through2.obj(function(file,enc,cb){ let myDockerfile = new Dockerfile({ filePath:file.path, read:true }); readDockerfilesArray.push(myDockerfile); cb(null,file); },function(){ done.resolve(readDockerfilesArray); })); return done.promise; } /** * sorts Dockerfiles into a dependency chain * @param sortableArrayArg an array of instances of class Dockerfile * @returns Promise */ export let sortDockerfiles = function(sortableArrayArg:Dockerfile[]):plugins.q.Promise{ let done = plugins.q.defer(); let sortedArray:Dockerfile[] = []; let cleanTagsOriginal = cleanTagsArrayFunction(sortableArrayArg,sortedArray); let sorterFunctionCounter:number = 0; let sorterFunction = function(){ sortableArrayArg.forEach((dockerfileArg)=>{ let cleanTags = cleanTagsArrayFunction(sortableArrayArg,sortedArray); if(cleanTags.indexOf(dockerfileArg.baseImage) == -1 && sortedArray.indexOf(dockerfileArg) == -1){ sortedArray.push(dockerfileArg); }; if(cleanTagsOriginal.indexOf(dockerfileArg.baseImage) != -1){ dockerfileArg.localBaseImageDependent = true; }; }); if(sortableArrayArg.length == sortedArray.length){ done.resolve(sortedArray); } else if (sorterFunctionCounter < 10) { sorterFunctionCounter++; sorterFunction(); }; } sorterFunction(); return done.promise; }; /** * maps local Dockerfiles dependencies to the correspoding Dockerfile class instances */ export let mapDockerfiles = function(sortedArray:Dockerfile[]):plugins.q.Promise{ let done = plugins.q.defer(); sortedArray.forEach((dockerfileArg) => { if(dockerfileArg.localBaseImageDependent){ sortedArray.forEach((dockfile2:Dockerfile) => { if(dockfile2.cleanTag == dockerfileArg.baseImage){ dockerfileArg.localBaseDockerfile = dockfile2; } }) }; }); done.resolve(sortedArray); return done.promise; } /** * builds the correspoding real docker image for each Dockerfile class instance */ export let buildDockerfiles = (sortedArrayArg:Dockerfile[]) => { let done = plugins.q.defer(); sortedArrayArg.forEach(function(dockerfileArg){ dockerfileArg.build(); }) done.resolve(sortedArrayArg); return done.promise; } /** * pushes the real Dockerfile images to a Docker registry */ export let pushDockerfiles = function(sortedArrayArg:Dockerfile[]){ let done = plugins.q.defer(); sortedArrayArg.forEach(function(dockerfileArg){ dockerfileArg.push(NpmciEnv.buildStage); }); done.resolve(sortedArrayArg); return done.promise; } /** * pulls corresponding real Docker images for instances of Dockerfile from a registry. * This is needed if building, testing, and publishing of Docker images is carried out in seperate CI stages. */ export let pullDockerfileImages = (sortableArrayArg:Dockerfile[],registryArg = "registry.gitlab.com") => { let done = plugins.q.defer(); sortableArrayArg.forEach((dockerfileArg) => { dockerfileArg.pull(registryArg); }); done.resolve(sortableArrayArg); return done.promise; } /** * tests all Dockerfiles in by calling class Dockerfile.test(); * @param sortedArrayArg Dockerfile[] that contains all Dockerfiles in cwd */ export let testDockerfiles = (sortedArrayArg:Dockerfile[]) => { let done = plugins.q.defer(); sortedArrayArg.forEach(function(dockerfileArg){ dockerfileArg.test(); }); done.resolve(sortedArrayArg); return done.promise; }; /** * class Dockerfile represents a Dockerfile on disk in npmci */ export class Dockerfile { filePath:string; repo:string; version:string; cleanTag:string; buildTag:string; testTag:string; releaseTag:string; containerName:string content:string; baseImage:string; localBaseImageDependent:boolean; localBaseDockerfile:Dockerfile; constructor(options:{filePath?:string,fileContents?:string|Buffer,read?:boolean}){ this.filePath = options.filePath; this.repo = NpmciEnv.repo.user + "/" + NpmciEnv.repo.repo; this.version = dockerFileVersion(plugins.path.parse(options.filePath).base); this.cleanTag = this.repo + ":" + this.version; this.buildTag = this.cleanTag; this.testTag = dockerTag("registry.gitlab.com",this.repo,this.version,"test"); this.releaseTag = dockerTag(NpmciEnv.dockerRegistry,this.repo,this.version); this.containerName = "dockerfile-" + this.version; if(options.filePath && options.read){ this.content = plugins.smartfile.fs.toStringSync(plugins.path.resolve(options.filePath)); }; this.baseImage = dockerBaseImage(this.content); this.localBaseImageDependent = false; }; /** * builds the Dockerfile */ build(){ let done = plugins.q.defer(); plugins.beautylog.info("now building Dockerfile for " + this.cleanTag); bashBare("docker build -t " + this.buildTag + " -f " + this.filePath + " ."); NpmciEnv.dockerFilesBuilt.push(this); done.resolve(); return done.promise; }; /** * pushes the Dockerfile to a registry */ push(stageArg){ let done = plugins.q.defer(); let pushTag; switch (stageArg){ case "release": pushTag = this.releaseTag; break; case "test": default: pushTag = this.testTag; break; } bashBare("docker tag " + this.buildTag + " " + pushTag); bashBare("docker push " + pushTag); done.resolve(); return done.promise; }; /** * pulls the Dockerfile from a registry */ pull(registryArg:string){ let pullTag = this.testTag; bashBare("docker pull " + pullTag); bashBare("docker tag " + pullTag + " " + this.buildTag); }; /** * tests the Dockerfile; */ test(){ let testFile:string = plugins.path.join(paths.NpmciTestDir,"test_" + this.version + ".sh"); let testFileExists:boolean = plugins.smartfile.fs.fileExistsSync(testFile); if(testFileExists){ bashBare("docker run --name npmci_test_container " + this.buildTag + " mkdir /npmci_test"); bashBare("docker cp " + testFile + " npmci_test_container:/npmci_test/test.sh"); bashBare("docker commit npmci_test_container npmci_test_image"); bashBare("docker run npmci_test_image sh /npmci_test/test.sh"); bashBare("docker rm npmci_test_container"); bashBare("docker rmi --force npmci_test_image"); } else { plugins.beautylog.warn("skipping tests for " + this.cleanTag + " because no testfile was found!"); } }; /** * gets the id of a Dockerfile */ getId(){ let containerId = bashBare("docker inspect --type=image --format=\"{{.Id}}\" " + this.buildTag); return containerId; }; } /** * */ export let dockerFileVersion = function(dockerfileNameArg:string):string{ let versionString:string; let versionRegex = /Dockerfile_([a-zA-Z0-9\.]*)$/; let regexResultArray = versionRegex.exec(dockerfileNameArg); if(regexResultArray && regexResultArray.length == 2){ versionString = regexResultArray[1]; } else { versionString = "latest"; } return versionString; } /** * */ export let dockerBaseImage = function(dockerfileContentArg:string){ let baseImageRegex = /FROM\s([a-zA-z0-9\/\-\:]*)\n?/ let regexResultArray = baseImageRegex.exec(dockerfileContentArg) return regexResultArray[1]; } /** * */ export let dockerTag = function(registryArg:string,repoArg:string,versionArg:string,suffixArg?:string):string{ let tagString:string; let registry = registryArg; let repo = repoArg; let version = versionArg; if(suffixArg){ version = versionArg + "_" + suffixArg; }; tagString = registry + "/" + repo + ":" + version; return tagString; }; /** * */ export let cleanTagsArrayFunction = function(dockerfileArrayArg:Dockerfile[],trackingArrayArg:Dockerfile[]):string[]{ let cleanTagsArray:string[] = []; dockerfileArrayArg.forEach(function(dockerfileArg){ if(trackingArrayArg.indexOf(dockerfileArg) == -1){ cleanTagsArray.push(dockerfileArg.cleanTag); } }); return cleanTagsArray; }