| 
									
										
										
										
											2019-08-29 20:26:23 +02:00
										 |  |  | import * as plugins from './mod.plugins'; | 
					
						
							|  |  |  | import * as paths from '../npmci.paths'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { logger } from '../npmci.logging'; | 
					
						
							|  |  |  | import { bash } from '../npmci.bash'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { DockerRegistry } from './mod.classes.dockerregistry'; | 
					
						
							|  |  |  | import * as helpers from './mod.helpers'; | 
					
						
							|  |  |  | import { NpmciDockerManager } from '.'; | 
					
						
							|  |  |  | import { Npmci } from '../npmci.classes.npmci'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * class Dockerfile represents a Dockerfile on disk in npmci | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class Dockerfile { | 
					
						
							|  |  |  |   // STATIC
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * creates instance of class Dockerfile for all Dockerfiles in cwd | 
					
						
							|  |  |  |    * @returns Promise<Dockerfile[]> | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static async readDockerfiles( | 
					
						
							|  |  |  |     npmciDockerManagerRefArg: NpmciDockerManager | 
					
						
							|  |  |  |   ): Promise<Dockerfile[]> { | 
					
						
							|  |  |  |     const fileTree = await plugins.smartfile.fs.listFileTree(paths.cwd, 'Dockerfile*'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // create the Dockerfile array
 | 
					
						
							|  |  |  |     const readDockerfilesArray: Dockerfile[] = []; | 
					
						
							|  |  |  |     logger.log('info', `found ${fileTree.length} Dockerfiles:`); | 
					
						
							|  |  |  |     console.log(fileTree); | 
					
						
							|  |  |  |     for (const dockerfilePath of fileTree) { | 
					
						
							|  |  |  |       const myDockerfile = new Dockerfile(npmciDockerManagerRefArg, { | 
					
						
							|  |  |  |         filePath: dockerfilePath, | 
					
						
							|  |  |  |         read: true | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       readDockerfilesArray.push(myDockerfile); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return readDockerfilesArray; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * sorts Dockerfiles into a dependency chain | 
					
						
							|  |  |  |    * @param sortableArrayArg an array of instances of class Dockerfile | 
					
						
							|  |  |  |    * @returns Promise<Dockerfile[]> | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static async sortDockerfiles(sortableArrayArg: Dockerfile[]): Promise<Dockerfile[]> { | 
					
						
							|  |  |  |     const done = plugins.smartpromise.defer<Dockerfile[]>(); | 
					
						
							|  |  |  |     logger.log('info', 'sorting Dockerfiles:'); | 
					
						
							|  |  |  |     const sortedArray: Dockerfile[] = []; | 
					
						
							|  |  |  |     const cleanTagsOriginal = Dockerfile.cleanTagsArrayFunction(sortableArrayArg, sortedArray); | 
					
						
							|  |  |  |     let sorterFunctionCounter: number = 0; | 
					
						
							|  |  |  |     const sorterFunction = () => { | 
					
						
							|  |  |  |       sortableArrayArg.forEach(dockerfileArg => { | 
					
						
							|  |  |  |         const cleanTags = Dockerfile.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) { | 
					
						
							|  |  |  |         let counter = 1; | 
					
						
							|  |  |  |         for (const dockerfile of sortedArray) { | 
					
						
							|  |  |  |           logger.log('info', `tag ${counter}: -> ${dockerfile.cleanTag}`); | 
					
						
							|  |  |  |           counter++; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         done.resolve(sortedArray); | 
					
						
							|  |  |  |       } else if (sorterFunctionCounter < 10) { | 
					
						
							|  |  |  |         sorterFunctionCounter++; | 
					
						
							|  |  |  |         sorterFunction(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     sorterFunction(); | 
					
						
							|  |  |  |     return done.promise; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * maps local Dockerfiles dependencies to the correspoding Dockerfile class instances | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static async mapDockerfiles(sortedDockerfileArray: Dockerfile[]): Promise<Dockerfile[]> { | 
					
						
							|  |  |  |     sortedDockerfileArray.forEach(dockerfileArg => { | 
					
						
							|  |  |  |       if (dockerfileArg.localBaseImageDependent) { | 
					
						
							|  |  |  |         sortedDockerfileArray.forEach((dockfile2: Dockerfile) => { | 
					
						
							|  |  |  |           if (dockfile2.cleanTag === dockerfileArg.baseImage) { | 
					
						
							|  |  |  |             dockerfileArg.localBaseDockerfile = dockfile2; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return sortedDockerfileArray; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * builds the correspoding real docker image for each Dockerfile class instance | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static async buildDockerfiles(sortedArrayArg: Dockerfile[]) { | 
					
						
							|  |  |  |     for (const dockerfileArg of sortedArrayArg) { | 
					
						
							|  |  |  |       await dockerfileArg.build(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return sortedArrayArg; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * tests all Dockerfiles in by calling class Dockerfile.test(); | 
					
						
							|  |  |  |    * @param sortedArrayArg Dockerfile[] that contains all Dockerfiles in cwd | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static async testDockerfiles(sortedArrayArg: Dockerfile[]) { | 
					
						
							|  |  |  |     for (const dockerfileArg of sortedArrayArg) { | 
					
						
							|  |  |  |       await dockerfileArg.test(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return sortedArrayArg; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * returns a version for a docker file | 
					
						
							|  |  |  |    * @execution SYNC | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static dockerFileVersion(dockerfileNameArg: string): string { | 
					
						
							|  |  |  |     let versionString: string; | 
					
						
							|  |  |  |     const versionRegex = /Dockerfile_([a-zA-Z0-9\.]*)$/; | 
					
						
							|  |  |  |     const regexResultArray = versionRegex.exec(dockerfileNameArg); | 
					
						
							|  |  |  |     if (regexResultArray && regexResultArray.length === 2) { | 
					
						
							|  |  |  |       versionString = regexResultArray[1]; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       versionString = 'latest'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return versionString; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * returns the docker base image for a Dockerfile | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static dockerBaseImage(dockerfileContentArg: string): string { | 
					
						
							|  |  |  |     const baseImageRegex = /FROM\s([a-zA-z0-9\/\-\:]*)\n?/; | 
					
						
							|  |  |  |     const regexResultArray = baseImageRegex.exec(dockerfileContentArg); | 
					
						
							|  |  |  |     return regexResultArray[1]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * returns the docker tag | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static getDockerTagString( | 
					
						
							|  |  |  |     npmciDockerManagerRef: NpmciDockerManager, | 
					
						
							|  |  |  |     registryArg: string, | 
					
						
							|  |  |  |     repoArg: string, | 
					
						
							|  |  |  |     versionArg: string, | 
					
						
							|  |  |  |     suffixArg?: string | 
					
						
							|  |  |  |   ): string { | 
					
						
							|  |  |  |     // determine wether the repo should be mapped accordingly to the registry
 | 
					
						
							|  |  |  |     const mappedRepo = npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().dockerRegistryRepoMap[ | 
					
						
							|  |  |  |       registryArg | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |     const repo = (() => { | 
					
						
							|  |  |  |       if (mappedRepo) { | 
					
						
							|  |  |  |         return mappedRepo; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         return repoArg; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     })(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // determine wether the version contais a suffix
 | 
					
						
							|  |  |  |     let version = versionArg; | 
					
						
							|  |  |  |     if (suffixArg) { | 
					
						
							|  |  |  |       version = versionArg + '_' + suffixArg; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const tagString = `${registryArg}/${repo}:${version}`; | 
					
						
							|  |  |  |     return tagString; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static async getDockerBuildArgs( | 
					
						
							|  |  |  |     npmciDockerManagerRef: NpmciDockerManager | 
					
						
							|  |  |  |   ): Promise<string> { | 
					
						
							|  |  |  |     logger.log('info', 'checking for env vars to be supplied to the docker build'); | 
					
						
							|  |  |  |     let buildArgsString: string = ''; | 
					
						
							|  |  |  |     for (const key of Object.keys( | 
					
						
							|  |  |  |       npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().dockerBuildargEnvMap | 
					
						
							|  |  |  |     )) { | 
					
						
							|  |  |  |       const targetValue = | 
					
						
							|  |  |  |         process.env[ | 
					
						
							|  |  |  |           npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().dockerBuildargEnvMap[key] | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  |       buildArgsString = `${buildArgsString} --build-arg ${key}="${targetValue}"`; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return buildArgsString; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public static cleanTagsArrayFunction( | 
					
						
							|  |  |  |     dockerfileArrayArg: Dockerfile[], | 
					
						
							|  |  |  |     trackingArrayArg: Dockerfile[] | 
					
						
							|  |  |  |   ): string[] { | 
					
						
							|  |  |  |     const cleanTagsArray: string[] = []; | 
					
						
							|  |  |  |     dockerfileArrayArg.forEach(dockerfileArg => { | 
					
						
							|  |  |  |       if (trackingArrayArg.indexOf(dockerfileArg) === -1) { | 
					
						
							|  |  |  |         cleanTagsArray.push(dockerfileArg.cleanTag); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return cleanTagsArray; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // INSTANCE
 | 
					
						
							|  |  |  |   public npmciDockerManagerRef: NpmciDockerManager; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public filePath: string; | 
					
						
							|  |  |  |   public repo: string; | 
					
						
							|  |  |  |   public version: string; | 
					
						
							|  |  |  |   public cleanTag: string; | 
					
						
							|  |  |  |   public buildTag: string; | 
					
						
							| 
									
										
										
										
											2019-08-30 10:38:47 +02:00
										 |  |  |   public pushTag: string; | 
					
						
							| 
									
										
										
										
											2019-08-29 20:26:23 +02:00
										 |  |  |   public containerName: string; | 
					
						
							|  |  |  |   public content: string; | 
					
						
							|  |  |  |   public baseImage: string; | 
					
						
							|  |  |  |   public localBaseImageDependent: boolean; | 
					
						
							|  |  |  |   public localBaseDockerfile: Dockerfile; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor( | 
					
						
							|  |  |  |     dockerManagerRefArg: NpmciDockerManager, | 
					
						
							|  |  |  |     options: { filePath?: string; fileContents?: string | Buffer; read?: boolean } | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     this.npmciDockerManagerRef = dockerManagerRefArg; | 
					
						
							|  |  |  |     this.filePath = options.filePath; | 
					
						
							|  |  |  |     this.repo = | 
					
						
							|  |  |  |       this.npmciDockerManagerRef.npmciRef.npmciEnv.repo.user + | 
					
						
							|  |  |  |       '/' + | 
					
						
							|  |  |  |       this.npmciDockerManagerRef.npmciRef.npmciEnv.repo.repo; | 
					
						
							|  |  |  |     this.version = Dockerfile.dockerFileVersion(plugins.path.parse(options.filePath).base); | 
					
						
							|  |  |  |     this.cleanTag = this.repo + ':' + this.version; | 
					
						
							|  |  |  |     this.buildTag = this.cleanTag; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.containerName = 'dockerfile-' + this.version; | 
					
						
							|  |  |  |     if (options.filePath && options.read) { | 
					
						
							|  |  |  |       this.content = plugins.smartfile.fs.toStringSync(plugins.path.resolve(options.filePath)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.baseImage = Dockerfile.dockerBaseImage(this.content); | 
					
						
							|  |  |  |     this.localBaseImageDependent = false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * builds the Dockerfile | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async build() { | 
					
						
							|  |  |  |     logger.log('info', 'now building Dockerfile for ' + this.cleanTag); | 
					
						
							|  |  |  |     const buildArgsString = await Dockerfile.getDockerBuildArgs(this.npmciDockerManagerRef); | 
					
						
							| 
									
										
										
										
											2019-08-30 10:38:47 +02:00
										 |  |  |     const buildCommand = `docker build --label="version=${ | 
					
						
							|  |  |  |       this.npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().projectInfo.npm.version | 
					
						
							|  |  |  |     }" -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
 | 
					
						
							| 
									
										
										
										
											2019-08-29 20:26:23 +02:00
										 |  |  |     await bash(buildCommand); | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * pushes the Dockerfile to a registry | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async push(dockerRegistryArg: DockerRegistry, versionSuffix: string = null) { | 
					
						
							| 
									
										
										
										
											2019-08-30 10:38:47 +02:00
										 |  |  |     this.pushTag = Dockerfile.getDockerTagString( | 
					
						
							| 
									
										
										
										
											2019-08-29 20:26:23 +02:00
										 |  |  |       this.npmciDockerManagerRef, | 
					
						
							|  |  |  |       dockerRegistryArg.registryUrl, | 
					
						
							|  |  |  |       this.repo, | 
					
						
							|  |  |  |       this.version, | 
					
						
							|  |  |  |       versionSuffix | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2019-08-30 10:38:47 +02:00
										 |  |  |     await bash(`docker tag ${this.buildTag} ${this.pushTag}`); | 
					
						
							|  |  |  |     await bash(`docker push ${this.pushTag}`); | 
					
						
							| 
									
										
										
										
											2019-08-30 18:39:59 +02:00
										 |  |  |     const imageDigest = (await bash( | 
					
						
							| 
									
										
										
										
											2019-08-30 14:51:03 +02:00
										 |  |  |       `docker inspect --format="{{index .RepoDigests 0}}" ${this.pushTag}` | 
					
						
							| 
									
										
										
										
											2019-08-30 18:39:59 +02:00
										 |  |  |     )).split('@')[1]; | 
					
						
							|  |  |  |     console.log(`The image ${this.pushTag} has digest ${imageDigest}`); | 
					
						
							| 
									
										
										
										
											2019-08-29 20:56:02 +02:00
										 |  |  |     await this.npmciDockerManagerRef.npmciRef.cloudlyConnector.announceDockerContainer({ | 
					
						
							| 
									
										
										
										
											2019-08-30 10:38:47 +02:00
										 |  |  |       dockerImageUrl: this.pushTag, | 
					
						
							|  |  |  |       dockerImageVersion: this.npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().projectInfo | 
					
						
							|  |  |  |         .npm.version | 
					
						
							| 
									
										
										
										
											2019-08-29 20:56:02 +02:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-08-29 20:26:23 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * pulls the Dockerfile from a registry | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async pull(registryArg: DockerRegistry, versionSuffixArg: string = null) { | 
					
						
							|  |  |  |     const pullTag = Dockerfile.getDockerTagString( | 
					
						
							|  |  |  |       this.npmciDockerManagerRef, | 
					
						
							|  |  |  |       registryArg.registryUrl, | 
					
						
							|  |  |  |       this.repo, | 
					
						
							|  |  |  |       this.version, | 
					
						
							|  |  |  |       versionSuffixArg | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     await bash(`docker pull ${pullTag}`); | 
					
						
							|  |  |  |     await bash(`docker tag ${pullTag} ${this.buildTag}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * tests the Dockerfile; | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async test() { | 
					
						
							|  |  |  |     const testFile: string = plugins.path.join(paths.NpmciTestDir, 'test_' + this.version + '.sh'); | 
					
						
							|  |  |  |     const testFileExists: boolean = plugins.smartfile.fs.fileExistsSync(testFile); | 
					
						
							|  |  |  |     if (testFileExists) { | 
					
						
							|  |  |  |       // run tests
 | 
					
						
							|  |  |  |       await bash( | 
					
						
							|  |  |  |         `docker run --name npmci_test_container --entrypoint="bash" ${this.buildTag} -c "mkdir /npmci_test"` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       await bash(`docker cp ${testFile} npmci_test_container:/npmci_test/test.sh`); | 
					
						
							|  |  |  |       await bash(`docker commit npmci_test_container npmci_test_image`); | 
					
						
							|  |  |  |       await bash(`docker run --entrypoint="bash" npmci_test_image -x /npmci_test/test.sh`); | 
					
						
							|  |  |  |       await bash(`docker rm npmci_test_container`); | 
					
						
							|  |  |  |       await bash(`docker rmi --force npmci_test_image`); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       logger.log('warn', 'skipping tests for ' + this.cleanTag + ' because no testfile was found!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * gets the id of a Dockerfile | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async getId() { | 
					
						
							|  |  |  |     const containerId = await bash( | 
					
						
							|  |  |  |       'docker inspect --type=image --format="{{.Id}}" ' + this.buildTag | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return containerId; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |