From 80b3666eb751b7fc7ebb58d419f71ff384911c2b Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Tue, 7 Mar 2017 11:25:18 -0800 Subject: [PATCH] refactoring DockerfileService --- package.json | 2 +- static/js/quay.module.ts | 2 + static/js/services/dockerfile-service.js | 2 +- .../dockerfile.service.impl.spec.ts | 91 ++++++ .../dockerfile/dockerfile.service.impl.ts | 147 +++++++++ .../services/dockerfile/dockerfile.service.ts | 43 +++ test/data/test.db | Bin 1323008 -> 1323008 bytes yarn.lock | 302 +++--------------- 8 files changed, 326 insertions(+), 263 deletions(-) create mode 100644 static/js/services/dockerfile/dockerfile.service.impl.spec.ts create mode 100644 static/js/services/dockerfile/dockerfile.service.impl.ts create mode 100644 static/js/services/dockerfile/dockerfile.service.ts diff --git a/package.json b/package.json index a0da58391..f509e7e51 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS", "test:node": "JASMINE_CONFIG_PATH=static/test/jasmine.json ./node_modules/.bin/jasmine-ts './static/js/**/*.spec.ts'", - "build": "./node_modules/.bin/webpack --progress -p -v", + "build": "./node_modules/.bin/webpack --progress", "watch": "./node_modules/.bin/webpack --watch" }, "repository": { diff --git a/static/js/quay.module.ts b/static/js/quay.module.ts index a21188a25..a8b946fd1 100644 --- a/static/js/quay.module.ts +++ b/static/js/quay.module.ts @@ -14,6 +14,7 @@ import { QuayConfig } from './quay-config.module'; import { QuayRun } from './quay-run.module'; import { BuildServiceImpl } from './services/build/build.service.impl'; import { AvatarServiceImpl } from './services/avatar/avatar.service.impl'; +import { DockerfileServiceImpl } from './services/dockerfile/dockerfile.service.impl'; /** @@ -37,6 +38,7 @@ import { AvatarServiceImpl } from './services/avatar/avatar.service.impl'; ViewArrayImpl, BuildServiceImpl, AvatarServiceImpl, + DockerfileServiceImpl, ], }) export class quay { diff --git a/static/js/services/dockerfile-service.js b/static/js/services/dockerfile-service.js index 8763b8a13..4495d6917 100644 --- a/static/js/services/dockerfile-service.js +++ b/static/js/services/dockerfile-service.js @@ -2,7 +2,7 @@ * Service which provides helper methods for extracting information out from a Dockerfile * or an archive containing a Dockerfile. */ -angular.module('quay').factory('DockerfileService', ['DataFileService', 'Config', function(DataFileService, Config) { +angular.module('quay').factory('DockerfileServiceOld', ['DataFileService', 'Config', function(DataFileService, Config) { var dockerfileService = {}; function DockerfileInfo(contents) { diff --git a/static/js/services/dockerfile/dockerfile.service.impl.spec.ts b/static/js/services/dockerfile/dockerfile.service.impl.spec.ts new file mode 100644 index 000000000..041f31e64 --- /dev/null +++ b/static/js/services/dockerfile/dockerfile.service.impl.spec.ts @@ -0,0 +1,91 @@ +import { DockerfileServiceImpl, DockerfileInfoImpl } from './dockerfile.service.impl'; +import Spy = jasmine.Spy; + + +describe("DockerfileInfoImpl", () => { + var dockerfileInfoImpl: DockerfileInfoImpl; + var contents: string; + var configMock: any; + + beforeEach(() => { + contents = ""; + configMock = jasmine.createSpyObj('configMock', ['getDomain']); + dockerfileInfoImpl = new DockerfileInfoImpl(contents, configMock); + }); + + describe("forData", () => { + + it("returns null if given contents do not contain a 'FROM' command", () => { + expect(DockerfileInfoImpl.forData(contents, configMock)).toBe(null); + }); + + it("returns a new DockerfileInfoImpl instance if given contents are valid", () => { + contents = "FROM quay.io/coreos/nginx"; + + expect(DockerfileInfoImpl.forData(contents, configMock) instanceof DockerfileInfoImpl).toBe(true); + }); + }); + + describe("getRegistryBaseImage", () => { + + + }); + + describe("getBaseImage", () => { + var baseImageAndTag + + it("returns null if instance's contents do not contain a 'FROM' command", () => { + var getBaseImageAndTagSpy: Spy = spyOn(dockerfileInfoImpl, "getBaseImageAndTag").and.returnValue() + }); + + it("returns") + }); + + describe("getBaseImageAndTag", () => { + + it("returns null if instance's contents do not contain a 'FROM' command", () => { + expect(dockerfileInfoImpl.getBaseImageAndTag()).toBe(null); + }); + + it("returns a string containing the base image and tag from the instance's contents", () => { + contents = "FROM quay.io/coreos/nginx"; + dockerfileInfoImpl = new DockerfileInfoImpl(contents, configMock); + var baseImageAndTag: string = dockerfileInfoImpl.getBaseImageAndTag(); + + expect(baseImageAndTag).toEqual(contents.substring('FROM '.length, contents.length).trim()); + }); + + it("handles the presence of newlines", () => { + contents = "FROM quay.io/coreos/nginx\nRUN echo $0"; + dockerfileInfoImpl = new DockerfileInfoImpl(contents, configMock); + var baseImageAndTag: string = dockerfileInfoImpl.getBaseImageAndTag(); + + expect(baseImageAndTag).toEqual(contents.substring('FROM '.length, contents.indexOf('\n')).trim()); + }); + }); +}); + + +describe("DockerfileServiceImpl", () => { + var dockerfileServiceImpl: DockerfileServiceImpl; + var dataFileServiceMock: any; + var configMock: any; + + beforeEach(() => { + dataFileServiceMock = jasmine.createSpyObj('dataFileServiceMock', [ + 'readDataArrayAsPossibleArchive', + 'arrayToString', + 'blobToString', + ]); + configMock = jasmine.createSpyObj('configMock', ['getDomain']); + dockerfileServiceImpl = new DockerfileServiceImpl(dataFileServiceMock, configMock); + }); + + describe("getDockerfile", () => { + + }); + + describe("extractDockerfile", () => { + + }); +}); \ No newline at end of file diff --git a/static/js/services/dockerfile/dockerfile.service.impl.ts b/static/js/services/dockerfile/dockerfile.service.impl.ts new file mode 100644 index 000000000..fbeb02c71 --- /dev/null +++ b/static/js/services/dockerfile/dockerfile.service.impl.ts @@ -0,0 +1,147 @@ +import { DockerfileService, DockerfileInfo } from './dockerfile.service'; +import { Injectable } from 'angular-ts-decorators'; + + +@Injectable(DockerfileService.name) +export class DockerfileServiceImpl implements DockerfileService { + + constructor(private DataFileService: any, private Config: any) { + console.log(`=================== DockerfileServiceImpl ==========================`); + } + + public getDockerfile(file: any, + success: (dockerfile: DockerfileInfoImpl) => void, + failure: (error: Event | string) => void): void { + var reader: FileReader = new FileReader(); + reader.onload = (event: Event) => { + var dataArray: any = reader.result; + this.DataFileService.readDataArrayAsPossibleArchive(dataArray, (files) => { + this.processFiles(files, dataArray, success, failure); + }, () => { + // Not an archive. Read directly as a single file. + this.processFiles([], dataArray, success, failure); + }); + }; + + reader.onerror = failure; + reader.readAsArrayBuffer(file); + } + + public extractDockerfile(file: any): Promise { + return new Promise((resolve, reject) => { + // TODO: Replace callbacks with promise + }); + } + + private processFiles(files: any, + dataArray: any[], + success: (dockerfile: DockerfileInfoImpl) => void, + failure: (error: ErrorEvent | string) => void): void { + // The files array will be empty if the submitted file was not an archive. We therefore + // treat it as a single Dockerfile. + if (files.length == 0) { + this.DataFileService.arrayToString(dataArray, (contents: string) => { + var result = DockerfileInfoImpl.forData(contents, Object.assign({}, this.Config)); + if (!result) { + failure('File chosen is not a valid Dockerfile'); + return; + } + + success(result); + }); + return; + } + + var found: boolean = false; + files.forEach((file) => { + if (file['name'] == 'Dockerfile') { + this.DataFileService.blobToString(file.toBlob(), (contents: string) => { + var result: DockerfileInfoImpl | null = DockerfileInfoImpl.forData(contents, Object.assign({}, this.Config)); + if (result != null) { + failure('Dockerfile inside archive is not a valid Dockerfile'); + return; + } + + success(result); + }); + found = true; + } + }); + + if (!found) { + failure('No Dockerfile found in root of archive'); + } + } +} + + +export class DockerfileInfoImpl implements DockerfileInfo { + + constructor(private contents: string, private config: any) { + + } + + public static forData(contents: string, config: any): DockerfileInfoImpl | null { + var dockerfileInfo: DockerfileInfoImpl = null; + if (contents.indexOf('FROM ') != -1) { + dockerfileInfo = new DockerfileInfoImpl(contents, config); + } + + return dockerfileInfo; + } + + public getRegistryBaseImage(): string | null { + var baseImage = this.getBaseImage(); + if (!baseImage) { + return null; + } + + if (baseImage.indexOf(this.config.getDomain() + '/') != 0) { + return null; + } + + return baseImage.substring(this.config.getDomain().length + 1); + } + + public getBaseImage(): string | null { + var imageAndTag = this.getBaseImageAndTag(); + if (!imageAndTag) { + return null; + } + + // Note, we have to handle a few different cases here: + // 1) someimage + // 2) someimage:tag + // 3) host:port/someimage + // 4) host:port/someimage:tag + var lastIndex = imageAndTag.lastIndexOf(':'); + if (lastIndex < 0) { + return imageAndTag; + } + + // Otherwise, check if there is a / in the portion after the split point. If so, + // then the latter is part of the path (and not a tag). + var afterColon = imageAndTag.substring(lastIndex + 1); + if (afterColon.indexOf('/') >= 0) { + return imageAndTag; + } + + return imageAndTag.substring(0, lastIndex); + } + + public getBaseImageAndTag(): string | null { + var baseImageAndTag: string = null; + + const fromIndex: number = this.contents.indexOf('FROM '); + if (fromIndex != -1) { + var newlineIndex: number = this.contents.indexOf('\n', fromIndex); + if (newlineIndex == -1) { + newlineIndex = this.contents.length; + } + + baseImageAndTag = this.contents.substring(fromIndex + 'FROM '.length, newlineIndex).trim(); + } + + return baseImageAndTag; + } +} \ No newline at end of file diff --git a/static/js/services/dockerfile/dockerfile.service.ts b/static/js/services/dockerfile/dockerfile.service.ts new file mode 100644 index 000000000..db62913c2 --- /dev/null +++ b/static/js/services/dockerfile/dockerfile.service.ts @@ -0,0 +1,43 @@ +/** + * Service which provides helper methods for extracting information out from a Dockerfile + * or an archive containing a Dockerfile. + */ +export abstract class DockerfileService { + + /** + * Retrieve Dockerfile from given archive file. + * @param file File containing Dockerfile. + * @param success Success callback with retrieved Dockerfile as parameter. + * @param failure Failure callback with failure message as parameter. + */ + public abstract getDockerfile(file: any, + success: (dockerfile: DockerfileInfo) => void, + failure: (error: ErrorEvent | string) => void): void; + + public abstract extractDockerfile(file: any): Promise; +} + + +/** + * Model representing information about a specific Dockerfile. + */ +export abstract class DockerfileInfo { + + /** + * Extract the registry base image from the Dockerfile contents. + * @return registryBaseImage The registry base image. + */ + public abstract getRegistryBaseImage(): string | null; + + /** + * Extract the base image from the Dockerfile contents. + * @return baseImage The base image. + */ + public abstract getBaseImage(): string | null; + + /** + * Extract the base image and tag from the Dockerfile contents. + * @return baseImageAndTag The base image and tag. + */ + public abstract getBaseImageAndTag(): string | null; +} \ No newline at end of file diff --git a/test/data/test.db b/test/data/test.db index dc4f5960a4ca8e6d01970518b5266faa231d986f..c583ad3a65f0c25774707134c40a5fe5705d0dfb 100644 GIT binary patch delta 1324 zcmbu9-D@0G7{<@X?8ofcop-k`77T56R~FLBBs05bc6TD^Y$MiEBnzb|QE7KRw$!$E zB^8CbcK4#hiv}XOIKhGxMEnC9okHr3Exis%=}jROyoyLd(W?aYjN1lajI6u8s}-t3LkDCpUnWz!Hfp8KW^K z1+h6nm1MaTqZpTqqB%3BjU3^GaIh7+M<5co$tEPAh!IcYS7gx1myyB7-n5Js=84t7{;VwPgd>mdc9#A)za>=InHZU zUT+i+Qt{V0av(hVF8P5<``aRMnjBKtLv}kfPLt1>*gi*?wX5~0qV5*G;~mP@q#{l4 zibR*#3j2lK4V^CO{~yz6JwJx`J|(Y2%Uu?UK6y!D_t|n->66dc8wCY7G1+57o>j23 z>|uex=gCO8{~Gx%%IS*4LX@+}x?yF3OtLpiF`2VhP`UKXlBZGS;**jn?1i}p*NKEm z6iG}Hmn0-fi7H8j53Vzvs~DCLENNeA;fLK^27g^}G(1?$IX$to#?GfOtRPr{37C*q zIa#lPXryTy9sbw%f1*ZdC)n+K_7C1w05W+j9MmXSE^=N>m*+o-i|zwKY{ ziTTm|?Dtt3&n7nz{2}*BZpRJ!^YAfZ7XCh+-dFf0p2^O|`)Nr=qDep!&E+Kf!g&9= zKdI)}cYVltNfladUKVw7kcK6|2d-BV3X%AMOpZ z#hs?<-I|`>tf*-$<$W}J>EZ?b%-KugYAj#Jz3?&4Yg$^KSoGoOZ#X>6u&_xV!eYVE F{|0hdM!o<5 delta 4466 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 nThAx6jbdUty_(6yCORA>(E=2.1.4, typescript@^2.0.0, typescript@^2.0.3: +typescript@>=2.1.4, typescript@^2.0.0, typescript@^2.0.3, typescript@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.1.tgz#4862b662b988a4c8ff691cc7969622d24db76ae9" @@ -4538,44 +4356,6 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" -yarn@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/yarn/-/yarn-0.21.3.tgz#8dda3a63c798b383cfa577452c2b3cb3e4aa87e0" - dependencies: - babel-runtime "^6.0.0" - bytes "^2.4.0" - camelcase "^3.0.0" - chalk "^1.1.1" - cmd-shim "^2.0.1" - commander "^2.9.0" - death "^1.0.0" - debug "^2.2.0" - defaults "^1.0.3" - detect-indent "^5.0.0" - ini "^1.3.4" - inquirer "^3.0.1" - invariant "^2.2.0" - is-builtin-module "^1.0.0" - is-ci "^1.0.10" - leven "^2.0.0" - loud-rejection "^1.2.0" - minimatch "^3.0.3" - mkdirp "^0.5.1" - node-emoji "^1.0.4" - node-gyp "^3.2.1" - object-path "^0.11.2" - proper-lockfile "^2.0.0" - read "^1.0.7" - request "^2.75.0" - request-capture-har "^1.1.4" - rimraf "^2.5.0" - roadrunner "^1.1.0" - semver "^5.1.0" - strip-bom "^3.0.0" - tar "^2.2.1" - tar-stream "^1.5.2" - validate-npm-package-license "^3.0.1" - yauzl@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"