From a633299c97f0a3bd8c3b10571c368b048e5041df Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 10 Apr 2019 01:17:48 +0200 Subject: [PATCH 01/22] chore(wip): upload multiple docker images --- .dockerignore | 1 + .vscode/launch.json | 27 +++++---- Dockerfile | 2 + develop.ts | 48 ++++++++++++++++ src/prepare/index.spec.ts | 9 +-- src/prepare/index.ts | 70 ++++++++++++++--------- src/publish/index.ts | 92 ++++++++++++++++++------------ src/verifyConditions/index.spec.ts | 24 ++++---- src/verifyConditions/index.ts | 21 ++++--- 9 files changed, 194 insertions(+), 100 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 develop.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1d085ca --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +** diff --git a/.vscode/launch.json b/.vscode/launch.json index 5d06db0..8ef6a27 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,22 +12,27 @@ "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", - "args": [ - "src/**/*.spec.ts", - "-r", "chai", - "-r", "chai-as-promised", - "-r", "ts-node/register" - ], + "args": ["src/**/*.spec.ts", "-r", "chai", "-r", "chai-as-promised", "-r", "ts-node/register"], "sourceMaps": true }, { + "name": "Current TS File", "type": "node", "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}\\index.js", - "outFiles": [ - "${workspaceFolder}/**/*.js" - ] + "args": ["${workspaceRoot}/develop.ts"], + "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + "protocol": "inspector" + }, + { + "name": "Current TS Tests File", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + "args": ["-r", "ts-node/register", "${relativeFile}"], + "cwd": "${workspaceRoot}", + "protocol": "inspector" } ] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2a08a89 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +ARG TEST diff --git a/develop.ts b/develop.ts new file mode 100644 index 0000000..500e50c --- /dev/null +++ b/develop.ts @@ -0,0 +1,48 @@ +import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; +import { prepare, publish, verifyConditions } from './src'; +import { DockerPluginConfig } from './src/dockerPluginConfig'; + +const config: SemanticReleaseConfig = { + branch: '', + noCi: true, + repositoryUrl: '', + tagFormat: '', +}; +const context: SemanticReleaseContext = { + logger: { + // tslint:disable-next-line:no-empty + log: (message: string) => {}, + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + additionalTags: ['latest'], + imageName: 'phonebook', + repositoryName: 'danielhabenicht', + path: '@iteratec/semantic-release-docker', + } as DockerPluginConfig, + { + additionalTags: ['latest'], + imageName: 'otherimage', + repositoryName: 'danielhabenicht', + path: '@iteratec/semantic-release-docker', + } as DockerPluginConfig, + ], + repositoryUrl: '', + tagFormat: '', + }, + nextRelease: { + version: '1.0.3', + gitHead: 'sdfsdfsdfsdf', + gitTag: 'v.1.0.3', + notes: 'Nothin special', + }, +}; +context.logger.log = (string: string) => { + console.log(string); +}; +verifyConditions(config, context); +prepare(config, context); +publish(config, context); diff --git a/src/prepare/index.spec.ts b/src/prepare/index.spec.ts index 618d596..a2697c7 100644 --- a/src/prepare/index.spec.ts +++ b/src/prepare/index.spec.ts @@ -7,7 +7,6 @@ import { DockerPluginConfig } from '../dockerPluginConfig'; import { prepare } from './index'; describe('@iteratec/semantic-release-docker', function() { - describe('prepare', function() { const config: SemanticReleaseConfig = { branch: '', @@ -16,9 +15,8 @@ describe('@iteratec/semantic-release-docker', function() { tagFormat: '', }; const context: SemanticReleaseContext = { - // tslint:disable-next-line:no-empty - logger: { log: (message: string) => {}}, + logger: { log: (message: string) => {} }, nextRelease: { gitTag: '', notes: '', @@ -54,14 +52,13 @@ describe('@iteratec/semantic-release-docker', function() { it('should tag an image', function() { (context.options.prepare![0] as DockerPluginConfig).imageName = 'hello-world'; - return expect(prepare(config, context)).to.eventually.deep.equal(['hello-world']); + return expect(prepare(config, context)).to.eventually.deep.equal([['hello-world']]); }); it('should add multiple tags to an image', function() { (context.options.prepare![0] as DockerPluginConfig).imageName = 'hello-world'; (context.options.prepare![0] as DockerPluginConfig).additionalTags = ['tag1', 'tag2']; - return expect(prepare(config, context)).to.eventually.have.length(3); + return expect(prepare(config, context).then((data) => data[0])).to.eventually.have.length(3); }); - }); }); diff --git a/src/prepare/index.ts b/src/prepare/index.ts index b60f6e6..44011aa 100644 --- a/src/prepare/index.ts +++ b/src/prepare/index.ts @@ -1,37 +1,55 @@ -import Dockerode from 'dockerode'; +import Dockerode from "dockerode"; -import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; -import { DockerPluginConfig } from '../dockerPluginConfig'; +import { SemanticReleaseConfig, SemanticReleaseContext } from "semantic-release"; +import { DockerPluginConfig } from "../dockerPluginConfig"; export var prepared = false; -export async function prepare(pluginConfig: SemanticReleaseConfig, context: SemanticReleaseContext): Promise { - const preparePlugin = context.options.prepare! - .find((p) => p.path === '@iteratec/semantic-release-docker') as DockerPluginConfig; - if (!preparePlugin.imageName) { - throw new Error('\'imageName\' is not set in plugin configuration'); - } - const docker = new Dockerode(); - const image = docker.getImage(preparePlugin.imageName); - let tags = [context.nextRelease!.version!]; - if (preparePlugin.additionalTags && preparePlugin.additionalTags.length > 0) { - tags = tags.concat(preparePlugin.additionalTags); - } - return Promise.all(tags.map((imagetag) => { - return image.tag({ - repo: `${preparePlugin.registryUrl ? `${preparePlugin.registryUrl}/` : ''}` + - `${preparePlugin.repositoryName ? `${preparePlugin.repositoryName}/` : ''}` + - `${preparePlugin.imageName}`, - tag: imagetag, - }); - })) - .then((data) => { +export async function prepare(pluginConfig: SemanticReleaseConfig, context: SemanticReleaseContext): Promise { + const preparePlugins = context.options.prepare!.filter( + p => p.path === "@iteratec/semantic-release-docker" + ) as DockerPluginConfig[]; + + return Promise.all( + preparePlugins.map(preparePlugin => { + if (!preparePlugin.imageName) { + throw new Error("'imageName' is not set in plugin configuration"); + } + const docker = new Dockerode(); + const image = docker.getImage(preparePlugin.imageName); + let tags = [context.nextRelease!.version!]; + if (preparePlugin.additionalTags && preparePlugin.additionalTags.length > 0) { + tags = tags.concat(preparePlugin.additionalTags); + } + return Promise.all( + tags.map(imagetag => { + return image.tag({ + repo: + `${preparePlugin.registryUrl ? `${preparePlugin.registryUrl}/` : ""}` + + `${preparePlugin.repositoryName ? `${preparePlugin.repositoryName}/` : ""}` + + `${preparePlugin.imageName}`, + tag: imagetag + }); + }) + ) + .then(data => { + if (!prepared) { + prepared = true; + } + return data.map(result => result.name); + }) + .catch(error => { + throw new Error(error); + }); + }) + ) + .then(data => { if (!prepared) { prepared = true; } - return data.map((result) => result.name); + return data.map(result => result); }) - .catch((error) => { + .catch(error => { throw new Error(error); }); } diff --git a/src/publish/index.ts b/src/publish/index.ts index 40c5384..5e04970 100644 --- a/src/publish/index.ts +++ b/src/publish/index.ts @@ -18,39 +18,61 @@ export async function publish(pluginConfig: SemanticReleaseConfig, context: Sema prepare(pluginConfig, context); } const docker = new Dockerode(); - let tags = [context.nextRelease!.version!]; - const preparePlugin = context.options.prepare! - .find((p) => p.path === '@iteratec/semantic-release-docker')! as DockerPluginConfig; - if (preparePlugin.additionalTags && preparePlugin.additionalTags.length > 0) { - tags = tags.concat(preparePlugin.additionalTags); - } - const imageName = `${preparePlugin.registryUrl ? `${preparePlugin.registryUrl}/` : ''}` + - `${preparePlugin.repositoryName ? `${preparePlugin.repositoryName}/` : ''}` + - `${preparePlugin.imageName}`; - const image = docker.getImage(imageName); - const options: PushOptions = { - password: process.env.DOCKER_REGISTRY_PASSWORD!, - serveraddress: process.env.DOCKER_REGISTRY_URL ? - process.env.DOCKER_REGISTRY_URL : preparePlugin.registryUrl ? preparePlugin.registryUrl : '', - tag: '', - username: process.env.DOCKER_REGISTRY_USER!, - }; - return Promise.all(tags.map((imageTag: string) => { - options.tag = imageTag; - context.logger.log(`pushing image ${imageName}:${imageTag}`); - return image.push(options); - })) - .then((streams) => Promise.all(streams.map((stream) => new Promise((resolve, reject) => { - stream.on('data', (chunk) => context.logger.log(chunk.toString())); - stream.on('end', () => resolve()); - stream.on('error', (error) => reject(error)); - })))) - .then(() => { - return { - completeImageName: tags.map((tag: string) => `${imageName}:${tag}`), - } as PublishedRelease; - }) - .catch((error) => { - throw new Error(error); - }); + + const preparePlugins = context.options.prepare!.filter( + (p) => p.path === '@iteratec/semantic-release-docker', + ) as DockerPluginConfig[]; + + return Promise.all( + preparePlugins.map((preparePlugin) => { + let tags = [context.nextRelease!.version!]; + if (preparePlugin.additionalTags && preparePlugin.additionalTags.length > 0) { + tags = tags.concat(preparePlugin.additionalTags); + } + const imageName = + `${preparePlugin.registryUrl ? `${preparePlugin.registryUrl}/` : ''}` + + `${preparePlugin.repositoryName ? `${preparePlugin.repositoryName}/` : ''}` + + `${preparePlugin.imageName}`; + const image = docker.getImage(imageName); + const options: PushOptions = { + password: process.env.DOCKER_REGISTRY_PASSWORD!, + serveraddress: process.env.DOCKER_REGISTRY_URL + ? process.env.DOCKER_REGISTRY_URL + : preparePlugin.registryUrl + ? preparePlugin.registryUrl + : '', + tag: '', + username: process.env.DOCKER_REGISTRY_USER!, + }; + return Promise.all( + tags.map((imageTag: string) => { + options.tag = imageTag; + context.logger.log(`pushing image ${imageName}:${imageTag}`); + return image.push(options); + }), + ) + .then((streams) => + Promise.all( + streams.map( + (stream) => + new Promise((resolve, reject) => { + stream.on('data', (chunk) => context.logger.log(chunk.toString())); + stream.on('end', () => resolve()); + stream.on('error', (error) => { + reject(error); + }); + }), + ), + ), + ) + .then(() => { + return { + completeImageName: tags.map((tag: string) => `${imageName}:${tag}`), + } as PublishedRelease; + }) + .catch((error) => { + throw new Error(error); + }); + }), + ); } diff --git a/src/verifyConditions/index.spec.ts b/src/verifyConditions/index.spec.ts index dd98959..24aa652 100644 --- a/src/verifyConditions/index.spec.ts +++ b/src/verifyConditions/index.spec.ts @@ -6,7 +6,6 @@ import { DockerPluginConfig } from '../dockerPluginConfig'; import { verifyConditions } from './index'; describe('@iteratec/semantic-release-docker', function() { - describe('verifyConditions', function() { const config: SemanticReleaseConfig = { branch: '', @@ -44,14 +43,16 @@ describe('@iteratec/semantic-release-docker', function() { }); it('should throw when the username is not set', function() { - return expect(verifyConditions(config, context)).to.eventually.be - .rejectedWith('Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.'); + return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( + 'Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.', + ); }); it('should throw when the password is not set', function() { process.env.DOCKER_REGISTRY_USER = 'username'; - return expect(verifyConditions(config, context)).to.eventually.be - .rejectedWith('Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.'); + return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( + 'Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.', + ); }); it('should use the registry from the config', function() { @@ -59,8 +60,7 @@ describe('@iteratec/semantic-release-docker', function() { process.env.DOCKER_REGISTRY_USER = 'username'; process.env.DOCKER_REGISTRY_PASSWORD = 'password'; (context.options.prepare![0] as DockerPluginConfig).registryUrl = 'my_private_registry'; - return expect(verifyConditions(config, context)) - .to.eventually.be.rejectedWith(/(?:my_private_registry)/); + return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith(/(?:my_private_registry)/); }); it('should prefer the registry from the environment variable over the one from the config', function() { @@ -69,8 +69,7 @@ describe('@iteratec/semantic-release-docker', function() { process.env.DOCKER_REGISTRY_PASSWORD = 'password'; process.env.DOCKER_REGISTRY_URL = 'my_other_private_registry'; (context.options.prepare![0] as DockerPluginConfig).registryUrl = 'my_private_registry'; - return expect(verifyConditions(config, context)) - .to.eventually.be.rejectedWith(/(?:my_other_private_registry)/); + return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith(/(?:my_other_private_registry)/); }); it('should default to docker hub if no registry is specified', function() { @@ -79,10 +78,9 @@ describe('@iteratec/semantic-release-docker', function() { (context.options.prepare![0] as DockerPluginConfig).imageName = ''; process.env.DOCKER_REGISTRY_USER = 'badusername'; process.env.DOCKER_REGISTRY_PASSWORD = 'pass@w0rd'; - return expect(verifyConditions(config, context)).to.eventually.be - .rejectedWith(/(?:index.docker.com|registry-1.docker.io)/); + return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( + /(?:index.docker.com|registry-1.docker.io)/, + ); }); - }); - }); diff --git a/src/verifyConditions/index.ts b/src/verifyConditions/index.ts index 8ec40bf..68fcc3f 100644 --- a/src/verifyConditions/index.ts +++ b/src/verifyConditions/index.ts @@ -2,6 +2,10 @@ import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release' import { DockerPluginConfig } from '../dockerPluginConfig'; import { Registry } from '../model/registry'; +// Just for Development +// process.env.DOCKER_REGISTRY_USER = "username"; +// process.env.DOCKER_REGISTRY_PASSWORD = "password"; + export var verified = false; export async function verifyConditions(pluginConfig: SemanticReleaseConfig, context: SemanticReleaseContext) { @@ -12,12 +16,12 @@ export async function verifyConditions(pluginConfig: SemanticReleaseConfig, cont throw new Error('Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.'); } let preparePlugin: DockerPluginConfig; - if (!context.options.prepare || - !context.options.prepare!.find((p) => p.path === '@iteratec/semantic-release-docker')) { + if (!context.options.prepare || !context.options.prepare!.find((p) => p.path === '@iteratec/semantic-release-docker')) { throw new Error('\'prepare\' is not configured'); } - preparePlugin = context.options.prepare - .find((p) => p.path === '@iteratec/semantic-release-docker') as DockerPluginConfig; + preparePlugin = context.options.prepare.find( + (p) => p.path === '@iteratec/semantic-release-docker', + ) as DockerPluginConfig; let registryUrl: string; if (process.env.DOCKER_REGISTRY_URL || preparePlugin.registryUrl) { registryUrl = process.env.DOCKER_REGISTRY_URL ? process.env.DOCKER_REGISTRY_URL : preparePlugin.registryUrl!; @@ -25,10 +29,9 @@ export async function verifyConditions(pluginConfig: SemanticReleaseConfig, cont registryUrl = ''; } const registry = new Registry(registryUrl); - return registry.login(process.env.DOCKER_REGISTRY_USER, process.env.DOCKER_REGISTRY_PASSWORD) - .then((result) => { - if (!verified) { - verified = true; - } + return registry.login(process.env.DOCKER_REGISTRY_USER, process.env.DOCKER_REGISTRY_PASSWORD).then((result) => { + if (!verified) { + verified = true; + } }); } From b081ec8f5e2c667265d04cde74258beaecc090ce Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Thu, 11 Apr 2019 01:53:22 +0200 Subject: [PATCH 02/22] chore(wip): work in progress towards new features #5, #4 --- develop.ts | 2 +- package-lock.json | 27 ++--- package.json | 2 +- src/dockerPluginConfig.ts | 7 -- src/model/auth.ts | 5 - src/model/registry.ts | 25 ----- src/models/PluginSettings.ts | 6 ++ src/models/authentication.ts | 9 ++ src/models/credentials.ts | 4 + src/models/dockerPluginConfig.ts | 8 ++ src/models/index.ts | 3 + src/plugin-settings.ts | 13 +++ src/prepare/index.spec.ts | 161 +++++++++++++++++++++++------ src/prepare/index.ts | 52 +++++----- src/publish/index.ts | 25 ++--- src/shared-logic.spec.ts | 73 +++++++++++++ src/shared-logic.ts | 38 +++++++ src/verifyConditions/index.spec.ts | 38 +++---- src/verifyConditions/index.ts | 79 +++++++++----- 19 files changed, 399 insertions(+), 178 deletions(-) delete mode 100644 src/dockerPluginConfig.ts delete mode 100644 src/model/auth.ts delete mode 100644 src/model/registry.ts create mode 100644 src/models/PluginSettings.ts create mode 100644 src/models/authentication.ts create mode 100644 src/models/credentials.ts create mode 100644 src/models/dockerPluginConfig.ts create mode 100644 src/models/index.ts create mode 100644 src/plugin-settings.ts create mode 100644 src/shared-logic.spec.ts create mode 100644 src/shared-logic.ts diff --git a/develop.ts b/develop.ts index 500e50c..46f5055 100644 --- a/develop.ts +++ b/develop.ts @@ -1,6 +1,6 @@ import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; import { prepare, publish, verifyConditions } from './src'; -import { DockerPluginConfig } from './src/dockerPluginConfig'; +import { DockerPluginConfig } from './src/models'; const config: SemanticReleaseConfig = { branch: '', diff --git a/package-lock.json b/package-lock.json index 173b4da..d984ad0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -610,21 +610,14 @@ } }, "@types/dockerode": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.4.tgz", - "integrity": "sha512-Mvdxibijt8nYY8lLiQ9IAWRUiQ2LUin8IatLGz1ZlVxzpfFzKks15Kpz8COXDSrR64xjECUs9hzYsJ/Tk6jzVg==", + "version": "2.5.13", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.13.tgz", + "integrity": "sha512-TgSP2nhCZgKOYcuMyuUs1SvLWZCd20z6SczPadLL11iCEEMDiblE23cwIyc1BR7FPpntwT9Z+IcdFNAXUAKmKQ==", "dev": true, "requires": { - "@types/events": "*", "@types/node": "*" } }, - "@types/events": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", - "dev": true - }, "@types/mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.0.tgz", @@ -632,9 +625,9 @@ "dev": true }, "@types/node": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.1.0.tgz", - "integrity": "sha512-sELcX/cJHwRp8kn4hYSvBxKGJ+ubl3MvS8VJQe5gz/sp7CifYxsiCxIJ35wMIYyGVMgfO2AzRa8UcVReAcJRlw==", + "version": "11.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz", + "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==", "dev": true }, "JSONStream": { @@ -960,7 +953,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { "readable-stream": "^2.3.5", @@ -1423,7 +1416,7 @@ }, "concat-stream": { "version": "1.6.2", - "resolved": "http://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { "buffer-from": "^1.0.0", @@ -1770,7 +1763,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "requires": { "core-util-is": "~1.0.0", @@ -1781,7 +1774,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } diff --git a/package.json b/package.json index 5dc660a..75880c2 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@commitlint/config-conventional": "^7.1.2", "@types/chai": "^4.1.3", "@types/chai-as-promised": "^7.1.0", - "@types/dockerode": "^2.5.4", + "@types/dockerode": "^2.5.13", "@types/mocha": "^5.2.0", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", diff --git a/src/dockerPluginConfig.ts b/src/dockerPluginConfig.ts deleted file mode 100644 index 9ac3c61..0000000 --- a/src/dockerPluginConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SemanticReleasePlugin } from 'semantic-release'; -export interface DockerPluginConfig extends SemanticReleasePlugin { - additionalTags?: string[]; - imageName: string; - registryUrl?: string; - repositoryName?: string; -} diff --git a/src/model/auth.ts b/src/model/auth.ts deleted file mode 100644 index 3207409..0000000 --- a/src/model/auth.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface Auth { - username: string; - password: string; - serveraddress?: string; -} diff --git a/src/model/registry.ts b/src/model/registry.ts deleted file mode 100644 index 65b0039..0000000 --- a/src/model/registry.ts +++ /dev/null @@ -1,25 +0,0 @@ -import Docker from 'dockerode'; - -import { Auth } from './auth'; - -export class Registry { - private docker = new Docker(); - - constructor(readonly url?: string) {} - - public login(username: string, password: string) { - const auth: Auth = { - password: `${password}`, - serveraddress: `${this.url ? `${this.url}` : ''}`, - username: `${username}`, - }; - - return this.docker.checkAuth(auth) - .then((data) => { - return true; - }) - .catch((error) => { - throw new Error(error); - }); - } -} diff --git a/src/models/PluginSettings.ts b/src/models/PluginSettings.ts new file mode 100644 index 0000000..fb34060 --- /dev/null +++ b/src/models/PluginSettings.ts @@ -0,0 +1,6 @@ +import { DockerPluginConfig } from './dockerPluginConfig'; + +export interface PluginSettings { + path: '@iteratec/semantic-release-docker'; + defaultValues: DockerPluginConfig; +} diff --git a/src/models/authentication.ts b/src/models/authentication.ts new file mode 100644 index 0000000..f724072 --- /dev/null +++ b/src/models/authentication.ts @@ -0,0 +1,9 @@ +import { Credentials } from './credentials'; + +/** + * Authentication + * From: https://docs.docker.com/engine/api/v1.37/#section/Authentication + */ +export interface Authentication extends Credentials { + serveraddress: string; +} diff --git a/src/models/credentials.ts b/src/models/credentials.ts new file mode 100644 index 0000000..279aabe --- /dev/null +++ b/src/models/credentials.ts @@ -0,0 +1,4 @@ +export interface Credentials { + username: string; + password: string; +} diff --git a/src/models/dockerPluginConfig.ts b/src/models/dockerPluginConfig.ts new file mode 100644 index 0000000..a590ec6 --- /dev/null +++ b/src/models/dockerPluginConfig.ts @@ -0,0 +1,8 @@ +import { SemanticReleasePlugin } from "semantic-release"; +export interface DockerPluginConfig extends SemanticReleasePlugin { + additionalTags?: string[]; + imageName: string; + registryUrl?: string; + repositoryName?: string; + pushVersionTag?: boolean; +} diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 0000000..2b9fb18 --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,3 @@ +export { Authentication } from './authentication'; +export { DockerPluginConfig } from './dockerPluginConfig'; +export { Credentials } from './credentials'; diff --git a/src/plugin-settings.ts b/src/plugin-settings.ts new file mode 100644 index 0000000..8a1080f --- /dev/null +++ b/src/plugin-settings.ts @@ -0,0 +1,13 @@ +import { PluginSettings } from "./models/PluginSettings"; + +export const pluginSettings: PluginSettings = { + path: "@iteratec/semantic-release-docker", + defaultValues: { + additionalTags: [], + imageName: "", + path: "@iteratec/semantic-release-docker", + pushVersionTag: true, + registryUrl: "", + repositoryName: "" + } +}; diff --git a/src/prepare/index.spec.ts b/src/prepare/index.spec.ts index a2697c7..18017f0 100644 --- a/src/prepare/index.spec.ts +++ b/src/prepare/index.spec.ts @@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised'; import Dockerode from 'dockerode'; import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; -import { DockerPluginConfig } from '../dockerPluginConfig'; +import { DockerPluginConfig } from '../models'; import { prepare } from './index'; describe('@iteratec/semantic-release-docker', function() { @@ -14,27 +14,6 @@ describe('@iteratec/semantic-release-docker', function() { repositoryUrl: '', tagFormat: '', }; - const context: SemanticReleaseContext = { - // tslint:disable-next-line:no-empty - logger: { log: (message: string) => {} }, - nextRelease: { - gitTag: '', - notes: '', - version: 'next', - }, - options: { - branch: '', - noCi: true, - prepare: [ - { - imageName: '', - path: '@iteratec/semantic-release-docker', - } as DockerPluginConfig, - ], - repositoryUrl: '', - tagFormat: '', - }, - }; before(function() { use(chaiAsPromised); @@ -43,22 +22,140 @@ describe('@iteratec/semantic-release-docker', function() { before(async function() { this.timeout(10000); const docker = new Dockerode(); - return await docker.pull('hello-world', {}); + docker.buildImage( + { + context: './', + src: ['Dockerfile'], + }, + { + t: 'test1:latest', + }, + function(error, output) { + if (error) { + return console.error(error); + } + }, + ); + docker.buildImage( + { + context: './', + src: ['Dockerfile'], + }, + { + t: 'test2:latest', + }, + function(error, output) { + if (error) { + return console.error(error); + } + }, + ); + process.env.DOCKER_REGISTRY_USER = 'username'; + process.env.DOCKER_REGISTRY_PASSWORD = 'password'; }); it('should throw if no imagename is provided', function() { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next', + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + path: '@iteratec/semantic-release-docker', + } as DockerPluginConfig, + ], + repositoryUrl: '', + tagFormat: '', + }, + } as SemanticReleaseContext; return expect(prepare(config, context)).to.be.rejectedWith('\'imageName\' is not set in plugin configuration'); }); - it('should tag an image', function() { - (context.options.prepare![0] as DockerPluginConfig).imageName = 'hello-world'; - return expect(prepare(config, context)).to.eventually.deep.equal([['hello-world']]); - }); + // it('should tag an image', function() { + // const context = { + // // tslint:disable-next-line:no-empty + // logger: { log: (message: string) => {} }, + // nextRelease: { + // gitTag: '', + // notes: '', + // version: 'next', + // }, + // options: { + // branch: '', + // noCi: true, + // prepare: [ + // { + // imageName: 'test1', + // path: '@iteratec/semantic-release-docker', + // } as DockerPluginConfig, + // ], + // repositoryUrl: '', + // tagFormat: '', + // }, + // } as SemanticReleaseContext; + // return expect(prepare(config, context)).to.eventually.deep.equal([['test1']]); + // }); - it('should add multiple tags to an image', function() { - (context.options.prepare![0] as DockerPluginConfig).imageName = 'hello-world'; - (context.options.prepare![0] as DockerPluginConfig).additionalTags = ['tag1', 'tag2']; - return expect(prepare(config, context).then((data) => data[0])).to.eventually.have.length(3); - }); + // it('should add multiple tags to an image', function() { + // const context = { + // // tslint:disable-next-line:no-empty + // logger: { log: (message: string) => {} }, + // nextRelease: { + // gitTag: '', + // notes: '', + // version: 'next', + // }, + // options: { + // branch: '', + // noCi: true, + // prepare: [ + // { + // imageName: 'test1', + // path: '@iteratec/semantic-release-docker', + // additionalTags: ['tag1', 'tag2'], + // } as DockerPluginConfig, + // ], + // repositoryUrl: '', + // tagFormat: '', + // }, + // } as SemanticReleaseContext; + // return expect(prepare(config, context).then((data) => data[0])).to.eventually.have.length(3); + // }); + + // it('should add multiple images', function() { + // const context = { + // // tslint:disable-next-line:no-empty + // logger: { log: (message: string) => {} }, + // nextRelease: { + // gitTag: '', + // notes: '', + // version: 'next', + // }, + // options: { + // branch: '', + // noCi: true, + // prepare: [ + // { + // imageName: 'test1', + // path: '@iteratec/semantic-release-docker', + // } as DockerPluginConfig, + // { + // imageName: 'test2', + // path: '@iteratec/semantic-release-docker', + // } as DockerPluginConfig, + // ], + // repositoryUrl: '', + // tagFormat: '', + // }, + // } as SemanticReleaseContext; + // return expect(prepare(config, context)).to.eventually.have.length(2); + // }); }); }); diff --git a/src/prepare/index.ts b/src/prepare/index.ts index 44011aa..72e12ec 100644 --- a/src/prepare/index.ts +++ b/src/prepare/index.ts @@ -1,20 +1,27 @@ -import Dockerode from "dockerode"; +import Dockerode from 'dockerode'; -import { SemanticReleaseConfig, SemanticReleaseContext } from "semantic-release"; -import { DockerPluginConfig } from "../dockerPluginConfig"; +import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; +import { DockerPluginConfig } from '../models'; +import { pluginSettings } from '../plugin-settings'; +import { constructImageName } from '../shared-logic'; +import { verified, verifyConditions } from '../verifyConditions'; export var prepared = false; export async function prepare(pluginConfig: SemanticReleaseConfig, context: SemanticReleaseContext): Promise { - const preparePlugins = context.options.prepare!.filter( - p => p.path === "@iteratec/semantic-release-docker" - ) as DockerPluginConfig[]; + if (!verified) { + await verifyConditions(pluginConfig, context).then( + () => {}, + (reject) => { + return Promise.reject(reject); + }, + ); + } + + const preparePlugins = context.options.prepare!.filter((p) => p.path === pluginSettings.path) as DockerPluginConfig[]; return Promise.all( - preparePlugins.map(preparePlugin => { - if (!preparePlugin.imageName) { - throw new Error("'imageName' is not set in plugin configuration"); - } + preparePlugins.map((preparePlugin) => { const docker = new Dockerode(); const image = docker.getImage(preparePlugin.imageName); let tags = [context.nextRelease!.version!]; @@ -22,34 +29,31 @@ export async function prepare(pluginConfig: SemanticReleaseConfig, context: Sema tags = tags.concat(preparePlugin.additionalTags); } return Promise.all( - tags.map(imagetag => { + tags.map((imagetag) => { return image.tag({ - repo: - `${preparePlugin.registryUrl ? `${preparePlugin.registryUrl}/` : ""}` + - `${preparePlugin.repositoryName ? `${preparePlugin.repositoryName}/` : ""}` + - `${preparePlugin.imageName}`, - tag: imagetag + repo: constructImageName(preparePlugin), + tag: imagetag, }); - }) + }), ) - .then(data => { + .then((data) => { if (!prepared) { prepared = true; } - return data.map(result => result.name); + return data.map((result) => result.name); }) - .catch(error => { + .catch((error) => { throw new Error(error); }); - }) + }), ) - .then(data => { + .then((data) => { if (!prepared) { prepared = true; } - return data.map(result => result); + return data.map((result) => result); }) - .catch(error => { + .catch((error) => { throw new Error(error); }); } diff --git a/src/publish/index.ts b/src/publish/index.ts index 5e04970..7a1d74c 100644 --- a/src/publish/index.ts +++ b/src/publish/index.ts @@ -1,11 +1,12 @@ import Dockerode from 'dockerode'; import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; -import { DockerPluginConfig } from '../dockerPluginConfig'; -import { Auth } from '../model/auth'; +import { Authentication, DockerPluginConfig } from '../models'; +import { pluginSettings } from '../plugin-settings'; import { prepare, prepared } from '../prepare'; +import { constructImageName, getRegistryUrlFromConfig } from '../shared-logic'; -interface PushOptions extends Auth { +interface PushOptions extends Authentication { tag: string; } @@ -19,28 +20,22 @@ export async function publish(pluginConfig: SemanticReleaseConfig, context: Sema } const docker = new Dockerode(); - const preparePlugins = context.options.prepare!.filter( - (p) => p.path === '@iteratec/semantic-release-docker', - ) as DockerPluginConfig[]; + const preparePlugins = context.options.prepare!.filter((p) => p.path === pluginSettings.path) as DockerPluginConfig[]; return Promise.all( preparePlugins.map((preparePlugin) => { let tags = [context.nextRelease!.version!]; + if (preparePlugin.additionalTags && preparePlugin.additionalTags.length > 0) { tags = tags.concat(preparePlugin.additionalTags); } - const imageName = - `${preparePlugin.registryUrl ? `${preparePlugin.registryUrl}/` : ''}` + - `${preparePlugin.repositoryName ? `${preparePlugin.repositoryName}/` : ''}` + - `${preparePlugin.imageName}`; + + const imageName = constructImageName(preparePlugin); + const image = docker.getImage(imageName); const options: PushOptions = { password: process.env.DOCKER_REGISTRY_PASSWORD!, - serveraddress: process.env.DOCKER_REGISTRY_URL - ? process.env.DOCKER_REGISTRY_URL - : preparePlugin.registryUrl - ? preparePlugin.registryUrl - : '', + serveraddress: getRegistryUrlFromConfig(preparePlugin), tag: '', username: process.env.DOCKER_REGISTRY_USER!, }; diff --git a/src/shared-logic.spec.ts b/src/shared-logic.spec.ts new file mode 100644 index 0000000..a116904 --- /dev/null +++ b/src/shared-logic.spec.ts @@ -0,0 +1,73 @@ +import { expect } from 'chai'; +import { Credentials, DockerPluginConfig } from './models'; +import { constructImageName, getCredentials, getRegistryUrlFromConfig } from './shared-logic'; + +describe('@iteratec/semantic-release-docker', function() { + describe('shared-logic', function() { + afterEach(function() { + process.env.DOCKER_REGISTRY_USER = ''; + process.env.DOCKER_REGISTRY_PASSWORD = ''; + process.env.DOCKER_REGISTRY_URL = ''; + }); + + it('should use only image name', function() { + const config: DockerPluginConfig = { + path: '@iteratec/semantic-release-docker', + imageName: 'test', + }; + expect(constructImageName(config)).to.be.equal('test'); + }); + + it('should use image name and repository', function() { + const config: DockerPluginConfig = { + path: '@iteratec/semantic-release-docker', + imageName: 'test', + repositoryName: 'repo', + }; + expect(constructImageName(config)).to.be.equal('repo/test'); + }); + + it('should use image name, repository and registry', function() { + const config: DockerPluginConfig = { + path: '@iteratec/semantic-release-docker', + imageName: 'test', + repositoryName: 'repo', + registryUrl: 'registry', + }; + expect(constructImageName(config)).to.be.equal('registry/repo/test'); + }); + + it('should use the registry from the config', function() { + const config: DockerPluginConfig = { + path: '@iteratec/semantic-release-docker', + imageName: 'test', + registryUrl: 'registry', + }; + expect(getRegistryUrlFromConfig(config)).to.be.equal('registry'); + }); + + it('should prefer the registry from the environment variable over the one from the config', function() { + process.env.DOCKER_REGISTRY_URL = 'my_other_private_registry'; + const config: DockerPluginConfig = { + path: '@iteratec/semantic-release-docker', + imageName: 'test', + registryUrl: 'registry', + }; + expect(getRegistryUrlFromConfig(config)).to.be.equal('my_other_private_registry'); + }); + + it('should default to empty string if no registry is specified', function() { + const config: DockerPluginConfig = { + path: '@iteratec/semantic-release-docker', + imageName: 'test', + }; + expect(getRegistryUrlFromConfig(config)).to.be.equal(''); + }); + + it('should get Credentials', function() { + process.env.DOCKER_REGISTRY_USER = 'username'; + process.env.DOCKER_REGISTRY_PASSWORD = 'password'; + expect(getCredentials()).to.eql({ password: 'password', username: 'username' } as Credentials); + }); + }); +}); diff --git a/src/shared-logic.ts b/src/shared-logic.ts new file mode 100644 index 0000000..c6d5b2a --- /dev/null +++ b/src/shared-logic.ts @@ -0,0 +1,38 @@ +import { Credentials, DockerPluginConfig } from "./models"; + +export function constructImageName(config: DockerPluginConfig): string { + return ( + `${config.registryUrl ? `${config.registryUrl}/` : ""}` + + `${config.repositoryName ? `${config.repositoryName}/` : ""}` + + `${config.imageName}` + ); +} + +export function getRegistryUrlFromConfig(config: DockerPluginConfig): string { + return process.env.DOCKER_REGISTRY_URL + ? process.env.DOCKER_REGISTRY_URL + : config.registryUrl + ? config.registryUrl + : ""; +} + +/** + * Get Authentication object from Environment Variables + * Throws Error if Variables are not set. + */ +export function getCredentials(): Credentials { + // Check DOCKER_REGISTRY_USER Environment Variable + if (!process.env.DOCKER_REGISTRY_USER) { + throw new Error("Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry."); + } + + // Check DOCKER_REGISTRY_PASSWORD Environment Variable + if (!process.env.DOCKER_REGISTRY_PASSWORD) { + throw new Error("Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry."); + } + + return { + username: process.env.DOCKER_REGISTRY_USER, + password: process.env.DOCKER_REGISTRY_PASSWORD + }; +} diff --git a/src/verifyConditions/index.spec.ts b/src/verifyConditions/index.spec.ts index 24aa652..29ae0ce 100644 --- a/src/verifyConditions/index.spec.ts +++ b/src/verifyConditions/index.spec.ts @@ -2,7 +2,7 @@ import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; -import { DockerPluginConfig } from '../dockerPluginConfig'; +import { DockerPluginConfig } from '../models'; import { verifyConditions } from './index'; describe('@iteratec/semantic-release-docker', function() { @@ -23,7 +23,7 @@ describe('@iteratec/semantic-release-docker', function() { noCi: true, prepare: [ { - imageName: '', + imageName: 'test', path: '@iteratec/semantic-release-docker', } as DockerPluginConfig, ], @@ -36,46 +36,38 @@ describe('@iteratec/semantic-release-docker', function() { use(chaiAsPromised); }); - afterEach(function() { - process.env.DOCKER_REGISTRY_USER = ''; - process.env.DOCKER_REGISTRY_PASSWORD = ''; - process.env.DOCKER_REGISTRY_URL = ''; - }); - it('should throw when the username is not set', function() { + delete process.env.DOCKER_REGISTRY_USER; return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( 'Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.', ); }); - it('should throw when the password is not set', function() { + it('should NOT throw when the username is set', function() { process.env.DOCKER_REGISTRY_USER = 'username'; - return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( - 'Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.', + return expect(verifyConditions(config, context)).to.not.eventually.be.rejectedWith( + 'Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.', ); }); - it('should use the registry from the config', function() { - this.timeout(5000); + it('should throw when the password is not set', function() { process.env.DOCKER_REGISTRY_USER = 'username'; - process.env.DOCKER_REGISTRY_PASSWORD = 'password'; - (context.options.prepare![0] as DockerPluginConfig).registryUrl = 'my_private_registry'; - return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith(/(?:my_private_registry)/); + delete process.env.DOCKER_REGISTRY_PASSWORD; + return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( + 'Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.', + ); }); - it('should prefer the registry from the environment variable over the one from the config', function() { - this.timeout(5000); - process.env.DOCKER_REGISTRY_USER = 'username'; + it('should NOT throw when the password is set', function() { process.env.DOCKER_REGISTRY_PASSWORD = 'password'; - process.env.DOCKER_REGISTRY_URL = 'my_other_private_registry'; - (context.options.prepare![0] as DockerPluginConfig).registryUrl = 'my_private_registry'; - return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith(/(?:my_other_private_registry)/); + return expect(verifyConditions(config, context)).to.not.eventually.be.rejectedWith( + 'Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.', + ); }); it('should default to docker hub if no registry is specified', function() { this.timeout(10000); (context.options.prepare![0] as DockerPluginConfig).registryUrl = ''; - (context.options.prepare![0] as DockerPluginConfig).imageName = ''; process.env.DOCKER_REGISTRY_USER = 'badusername'; process.env.DOCKER_REGISTRY_PASSWORD = 'pass@w0rd'; return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( diff --git a/src/verifyConditions/index.ts b/src/verifyConditions/index.ts index 68fcc3f..54f024e 100644 --- a/src/verifyConditions/index.ts +++ b/src/verifyConditions/index.ts @@ -1,37 +1,60 @@ +import Docker from 'dockerode'; import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; -import { DockerPluginConfig } from '../dockerPluginConfig'; -import { Registry } from '../model/registry'; - -// Just for Development -// process.env.DOCKER_REGISTRY_USER = "username"; -// process.env.DOCKER_REGISTRY_PASSWORD = "password"; +import { Credentials, DockerPluginConfig } from '../models'; +import { Authentication } from '../models'; +import { pluginSettings } from '../plugin-settings'; +import { getCredentials, getRegistryUrlFromConfig } from '../shared-logic'; export var verified = false; -export async function verifyConditions(pluginConfig: SemanticReleaseConfig, context: SemanticReleaseContext) { - if (!process.env.DOCKER_REGISTRY_USER) { - throw new Error('Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.'); - } - if (!process.env.DOCKER_REGISTRY_PASSWORD) { - throw new Error('Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.'); +/** + * First Step + * Verify all conditions in order to proceed with the release + */ +export async function verifyConditions( + pluginConfig: SemanticReleaseConfig, + context: SemanticReleaseContext, +): Promise { + let cred: Credentials; + + // Check if Username and Password are set if not reject Promise with Error Message + try { + cred = getCredentials(); + } catch (err) { + return Promise.reject(err.message); } - let preparePlugin: DockerPluginConfig; - if (!context.options.prepare || !context.options.prepare!.find((p) => p.path === '@iteratec/semantic-release-docker')) { + + // Check if plugin is configured in prepare step + if (!context.options.prepare || !context.options.prepare!.find((p) => p.path === pluginSettings.path)) { throw new Error('\'prepare\' is not configured'); } - preparePlugin = context.options.prepare.find( - (p) => p.path === '@iteratec/semantic-release-docker', - ) as DockerPluginConfig; - let registryUrl: string; - if (process.env.DOCKER_REGISTRY_URL || preparePlugin.registryUrl) { - registryUrl = process.env.DOCKER_REGISTRY_URL ? process.env.DOCKER_REGISTRY_URL : preparePlugin.registryUrl!; - } else { - registryUrl = ''; - } - const registry = new Registry(registryUrl); - return registry.login(process.env.DOCKER_REGISTRY_USER, process.env.DOCKER_REGISTRY_PASSWORD).then((result) => { - if (!verified) { - verified = true; + + const preparePlugins = context.options.prepare!.filter((p) => p.path === pluginSettings.path) as DockerPluginConfig[]; + + for (let i = 0; i < preparePlugins.length; i++) { + const preparePlugin = preparePlugins[i]; + + // Check if imagename is set + if (preparePlugin.imageName == null || preparePlugin.imageName.length === 0) { + throw new Error('\'imageName\' is not set in plugin configuration'); } - }); + + // Check Authentication + const docker = new Docker(); + const auth: Authentication = { + ...cred, + serveraddress: getRegistryUrlFromConfig(preparePlugin), + }; + + return docker + .checkAuth(auth) + .then((data) => { + if (!verified) { + verified = true; + } + }) + .catch((error) => { + throw new Error(error); + }); + } } From b6ab9af2cb374ca4a50c4903cf4e2079ee58d6da Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Sun, 14 Apr 2019 17:33:11 +0200 Subject: [PATCH 03/22] Remoev Develop.ts --- develop.ts | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 develop.ts diff --git a/develop.ts b/develop.ts deleted file mode 100644 index 46f5055..0000000 --- a/develop.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; -import { prepare, publish, verifyConditions } from './src'; -import { DockerPluginConfig } from './src/models'; - -const config: SemanticReleaseConfig = { - branch: '', - noCi: true, - repositoryUrl: '', - tagFormat: '', -}; -const context: SemanticReleaseContext = { - logger: { - // tslint:disable-next-line:no-empty - log: (message: string) => {}, - }, - options: { - branch: '', - noCi: true, - prepare: [ - { - additionalTags: ['latest'], - imageName: 'phonebook', - repositoryName: 'danielhabenicht', - path: '@iteratec/semantic-release-docker', - } as DockerPluginConfig, - { - additionalTags: ['latest'], - imageName: 'otherimage', - repositoryName: 'danielhabenicht', - path: '@iteratec/semantic-release-docker', - } as DockerPluginConfig, - ], - repositoryUrl: '', - tagFormat: '', - }, - nextRelease: { - version: '1.0.3', - gitHead: 'sdfsdfsdfsdf', - gitTag: 'v.1.0.3', - notes: 'Nothin special', - }, -}; -context.logger.log = (string: string) => { - console.log(string); -}; -verifyConditions(config, context); -prepare(config, context); -publish(config, context); From 36e1293136326911f8648248320a3d2d083eb9c1 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Tue, 16 Apr 2019 23:38:35 +0200 Subject: [PATCH 04/22] Add some docs --- .gitignore | 2 ++ .vscode/launch.json | 2 +- README.md | 84 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 5d8d9ed..07cf5bb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ node_modules dist *.log + +develop.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 8ef6a27..be65aef 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,7 @@ "sourceMaps": true }, { - "name": "Current TS File", + "name": "Debug", "type": "node", "request": "launch", "args": ["${workspaceRoot}/develop.ts"], diff --git a/README.md b/README.md index d244767..facae85 100644 --- a/README.md +++ b/README.md @@ -32,24 +32,25 @@ The `docker registry` authentication is **required** and can be set via environm ### Environment variables | Variable | Description | -|--------------------------|-------------------------------------------------------------------------------------------| +| ------------------------ | ----------------------------------------------------------------------------------------- | | DOCKER_REGISTRY_URL | The hostname and port used by the desired docker registry. Leave blank to use docker hub. | | DOCKER_REGISTRY_USER | The user name to authenticate with at the registry. | | DOCKER_REGISTRY_PASSWORD | The password used for authentication at the registry. | ### Options -| Option | Description | -|----------------|--------------------------------------------------------------------------------------------| -| additionalTags | _Optional_. An array of strings allowing to specify additional tags to apply to the image. | -| imageName | **_Required_** The name of the image to release. | +| Option | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| additionalTags | _Optional_. An array of strings allowing to specify additional tags to apply to the image. | +| imageName | **_Required_** The name of the image to release. | | registryUrl | _Optional_. The hostname and port used by the the registry in format `hostname[:port]`. Omit the port if the registry uses the default port | -| repositoryName | _Optional_. The name of the repository in the registry, e.g. username on docker hub | +| repositoryName | _Optional_. The name of the repository in the registry, e.g. username on docker hub | ### Usage full configuration: -``` json + +```json { "verifyConfig": ["@iteratec/semantic-release-docker"], "prepare": { @@ -64,10 +65,12 @@ full configuration: } } ``` + results in `my-private-registry:5678/my-repository/my-image` with tags `test`, `demo` and the `` determined by `semantic-release`. minimum configuration: -``` json + +```json { "verifyConfig": ["@iteratec/semantic-release-docker"], "prepare": { @@ -79,4 +82,67 @@ minimum configuration: } } ``` -results in `my-image:` \ No newline at end of file + +results in `my-image:` + +## Contribute + +### Develop + +1. Create a develop.ts file in the root of this Git-Repository and copy this: + +```typescript +import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; +import { prepare, publish, verifyConditions } from './src'; +import { DockerPluginConfig } from './src/models'; + +process.env.DOCKER_REGISTRY_USER = ''; +process.env.DOCKER_REGISTRY_PASSWORD = ''; + +const config: SemanticReleaseConfig = { + branch: '', + noCi: true, + repositoryUrl: '', + tagFormat: '' +}; +const context: SemanticReleaseContext = { + logger: { + // tslint:disable-next-line:no-empty + log: (message: string) => {} + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + additionalTags: ['latest'], + imageName: 'testimage', + repositoryName: '', + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig, + { + additionalTags: ['latest'], + imageName: 'testimage1', + repositoryName: '', + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + }, + nextRelease: { + version: '1.0.3', + gitHead: '45jh345g', + gitTag: 'v1.0.3', + notes: 'Nothing special' + } +}; +context.logger.log = (string: string) => { + console.log(string); +}; +verifyConditions(config, context); +prepare(config, context); +publish(config, context); +``` + +2. Simply run the "Debug" VS Code Task From c453fbf455b4e14010c7c6fbf09bc93caea9af40 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Tue, 16 Apr 2019 23:39:23 +0200 Subject: [PATCH 05/22] update format --- src/shared-logic.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/shared-logic.spec.ts b/src/shared-logic.spec.ts index a116904..e1bbd32 100644 --- a/src/shared-logic.spec.ts +++ b/src/shared-logic.spec.ts @@ -13,7 +13,7 @@ describe('@iteratec/semantic-release-docker', function() { it('should use only image name', function() { const config: DockerPluginConfig = { path: '@iteratec/semantic-release-docker', - imageName: 'test', + imageName: 'test' }; expect(constructImageName(config)).to.be.equal('test'); }); @@ -22,7 +22,7 @@ describe('@iteratec/semantic-release-docker', function() { const config: DockerPluginConfig = { path: '@iteratec/semantic-release-docker', imageName: 'test', - repositoryName: 'repo', + repositoryName: 'repo' }; expect(constructImageName(config)).to.be.equal('repo/test'); }); @@ -32,7 +32,7 @@ describe('@iteratec/semantic-release-docker', function() { path: '@iteratec/semantic-release-docker', imageName: 'test', repositoryName: 'repo', - registryUrl: 'registry', + registryUrl: 'registry' }; expect(constructImageName(config)).to.be.equal('registry/repo/test'); }); @@ -41,7 +41,7 @@ describe('@iteratec/semantic-release-docker', function() { const config: DockerPluginConfig = { path: '@iteratec/semantic-release-docker', imageName: 'test', - registryUrl: 'registry', + registryUrl: 'registry' }; expect(getRegistryUrlFromConfig(config)).to.be.equal('registry'); }); @@ -51,7 +51,7 @@ describe('@iteratec/semantic-release-docker', function() { const config: DockerPluginConfig = { path: '@iteratec/semantic-release-docker', imageName: 'test', - registryUrl: 'registry', + registryUrl: 'registry' }; expect(getRegistryUrlFromConfig(config)).to.be.equal('my_other_private_registry'); }); @@ -59,7 +59,7 @@ describe('@iteratec/semantic-release-docker', function() { it('should default to empty string if no registry is specified', function() { const config: DockerPluginConfig = { path: '@iteratec/semantic-release-docker', - imageName: 'test', + imageName: 'test' }; expect(getRegistryUrlFromConfig(config)).to.be.equal(''); }); From acb2e3ad8da62309cdb29cb8786bbc51c1be0481 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Tue, 16 Apr 2019 23:43:10 +0200 Subject: [PATCH 06/22] Add Check: If Image exists on machine --- src/verifyConditions/index.spec.ts | 138 ++++++++++++++++++++++++++--- src/verifyConditions/index.ts | 32 +++++-- 2 files changed, 148 insertions(+), 22 deletions(-) diff --git a/src/verifyConditions/index.spec.ts b/src/verifyConditions/index.spec.ts index 29ae0ce..7f8ff23 100644 --- a/src/verifyConditions/index.spec.ts +++ b/src/verifyConditions/index.spec.ts @@ -1,5 +1,6 @@ import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import Docker from 'dockerode'; import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; import { DockerPluginConfig } from '../models'; @@ -7,29 +8,30 @@ import { verifyConditions } from './index'; describe('@iteratec/semantic-release-docker', function() { describe('verifyConditions', function() { + const imageName = 'abcdefghijklmnopqrstuvwxyz'; const config: SemanticReleaseConfig = { branch: '', noCi: true, repositoryUrl: '', - tagFormat: '', + tagFormat: '' }; const context: SemanticReleaseContext = { logger: { // tslint:disable-next-line:no-empty - log: (message: string) => {}, + log: (message: string) => {} }, options: { branch: '', noCi: true, prepare: [ { - imageName: 'test', - path: '@iteratec/semantic-release-docker', - } as DockerPluginConfig, + imageName, + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig ], repositoryUrl: '', - tagFormat: '', - }, + tagFormat: '' + } }; before(function() { @@ -39,14 +41,14 @@ describe('@iteratec/semantic-release-docker', function() { it('should throw when the username is not set', function() { delete process.env.DOCKER_REGISTRY_USER; return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( - 'Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.', + 'Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.' ); }); it('should NOT throw when the username is set', function() { process.env.DOCKER_REGISTRY_USER = 'username'; return expect(verifyConditions(config, context)).to.not.eventually.be.rejectedWith( - 'Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.', + 'Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.' ); }); @@ -54,25 +56,135 @@ describe('@iteratec/semantic-release-docker', function() { process.env.DOCKER_REGISTRY_USER = 'username'; delete process.env.DOCKER_REGISTRY_PASSWORD; return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( - 'Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.', + 'Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.' ); }); it('should NOT throw when the password is set', function() { process.env.DOCKER_REGISTRY_PASSWORD = 'password'; return expect(verifyConditions(config, context)).to.not.eventually.be.rejectedWith( - 'Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.', + 'Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.' ); }); - it('should default to docker hub if no registry is specified', function() { + it('should default to docker hub if no registry is specified', async function() { this.timeout(10000); + const docker = new Docker(); + await docker.buildImage( + { + context: './', + src: ['Dockerfile'] + }, + { + t: imageName + } + ); (context.options.prepare![0] as DockerPluginConfig).registryUrl = ''; process.env.DOCKER_REGISTRY_USER = 'badusername'; process.env.DOCKER_REGISTRY_PASSWORD = 'pass@w0rd'; return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( - /(?:index.docker.com|registry-1.docker.io)/, + /(?:index.docker.com|registry-1.docker.io)/ + ); + }); + + it('should throw if no imagename is provided', function() { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( + "'imageName' is not set in plugin configuration" + ); + }); + + it('should throw if image with imagename does not exist', function() { + const docker = new Docker(); + try { + docker.getImage(imageName).remove(); + } catch (err) {} + + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + imageName, + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + return expect(verifyConditions(config, context)).to.be.rejectedWith( + `Image with name '${imageName}' does not exist on this machine.` ); }); + + it('should NOT throw if image with imagename does exist', function() { + const docker = new Docker(); + + return docker + .buildImage( + { + context: './', + src: ['Dockerfile'] + }, + { + t: imageName + } + ) + .then(success => { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + imageName, + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + + return expect(verifyConditions(config, context)).to.not.be.rejectedWith( + `Image with name '${imageName}' does not exist on this machine.` + ); + }); + }); }); }); diff --git a/src/verifyConditions/index.ts b/src/verifyConditions/index.ts index 54f024e..2c30fb2 100644 --- a/src/verifyConditions/index.ts +++ b/src/verifyConditions/index.ts @@ -6,6 +6,13 @@ import { pluginSettings } from '../plugin-settings'; import { getCredentials, getRegistryUrlFromConfig } from '../shared-logic'; export var verified = false; +/** + * Just for test purposes. + * @param val + */ +export function setVerified() { + verified = true; +} /** * First Step @@ -13,7 +20,7 @@ export var verified = false; */ export async function verifyConditions( pluginConfig: SemanticReleaseConfig, - context: SemanticReleaseContext, + context: SemanticReleaseContext ): Promise { let cred: Credentials; @@ -25,35 +32,42 @@ export async function verifyConditions( } // Check if plugin is configured in prepare step - if (!context.options.prepare || !context.options.prepare!.find((p) => p.path === pluginSettings.path)) { - throw new Error('\'prepare\' is not configured'); + if (!context.options.prepare || !context.options.prepare!.find(p => p.path === pluginSettings.path)) { + throw new Error("'prepare' is not configured"); } - const preparePlugins = context.options.prepare!.filter((p) => p.path === pluginSettings.path) as DockerPluginConfig[]; + const preparePlugins = context.options.prepare!.filter(p => p.path === pluginSettings.path) as DockerPluginConfig[]; for (let i = 0; i < preparePlugins.length; i++) { const preparePlugin = preparePlugins[i]; // Check if imagename is set if (preparePlugin.imageName == null || preparePlugin.imageName.length === 0) { - throw new Error('\'imageName\' is not set in plugin configuration'); + throw new Error("'imageName' is not set in plugin configuration"); } - // Check Authentication const docker = new Docker(); + + // Check if image exists on machine + const imagelist = await docker.listImages({ filters: { reference: [preparePlugin.imageName] } }); + if (imagelist.length === 0) { + throw new Error(`Image with name '${preparePlugin.imageName}' does not exist on this machine.`); + } + + // Check Authentication const auth: Authentication = { ...cred, - serveraddress: getRegistryUrlFromConfig(preparePlugin), + serveraddress: getRegistryUrlFromConfig(preparePlugin) }; return docker .checkAuth(auth) - .then((data) => { + .then(data => { if (!verified) { verified = true; } }) - .catch((error) => { + .catch(error => { throw new Error(error); }); } From 50e77818f8fbd237788274b371b73f8ef0b8cb03 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Tue, 16 Apr 2019 23:45:13 +0200 Subject: [PATCH 07/22] Update Prepare Tests --- src/prepare/index.spec.ts | 173 ++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 93 deletions(-) diff --git a/src/prepare/index.spec.ts b/src/prepare/index.spec.ts index 18017f0..095baca 100644 --- a/src/prepare/index.spec.ts +++ b/src/prepare/index.spec.ts @@ -5,6 +5,7 @@ import Dockerode from 'dockerode'; import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; import { DockerPluginConfig } from '../models'; import { prepare } from './index'; +import { setVerified } from '../verifyConditions'; describe('@iteratec/semantic-release-docker', function() { describe('prepare', function() { @@ -12,150 +13,136 @@ describe('@iteratec/semantic-release-docker', function() { branch: '', noCi: true, repositoryUrl: '', - tagFormat: '', + tagFormat: '' }; + const testImage1 = 'test1:latest'; + const testImage2 = 'test2:latest'; + before(function() { use(chaiAsPromised); + setVerified(); }); before(async function() { this.timeout(10000); const docker = new Dockerode(); - docker.buildImage( + await docker.buildImage( { context: './', - src: ['Dockerfile'], + src: ['Dockerfile'] }, { - t: 'test1:latest', + t: testImage1 }, function(error, output) { if (error) { return console.error(error); } - }, + } ); - docker.buildImage( + await docker.buildImage( { context: './', - src: ['Dockerfile'], + src: ['Dockerfile'] }, { - t: 'test2:latest', + t: testImage2 }, function(error, output) { if (error) { return console.error(error); } - }, + } ); process.env.DOCKER_REGISTRY_USER = 'username'; process.env.DOCKER_REGISTRY_PASSWORD = 'password'; }); - it('should throw if no imagename is provided', function() { + it('should tag an image', function() { const context = { // tslint:disable-next-line:no-empty logger: { log: (message: string) => {} }, nextRelease: { gitTag: '', notes: '', - version: 'next', + version: 'next' }, options: { branch: '', noCi: true, prepare: [ { - path: '@iteratec/semantic-release-docker', - } as DockerPluginConfig, + imageName: testImage1, + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig ], repositoryUrl: '', - tagFormat: '', - }, + tagFormat: '' + } } as SemanticReleaseContext; - return expect(prepare(config, context)).to.be.rejectedWith('\'imageName\' is not set in plugin configuration'); + return expect(prepare(config, context)).to.eventually.deep.equal([[testImage1]]); }); - // it('should tag an image', function() { - // const context = { - // // tslint:disable-next-line:no-empty - // logger: { log: (message: string) => {} }, - // nextRelease: { - // gitTag: '', - // notes: '', - // version: 'next', - // }, - // options: { - // branch: '', - // noCi: true, - // prepare: [ - // { - // imageName: 'test1', - // path: '@iteratec/semantic-release-docker', - // } as DockerPluginConfig, - // ], - // repositoryUrl: '', - // tagFormat: '', - // }, - // } as SemanticReleaseContext; - // return expect(prepare(config, context)).to.eventually.deep.equal([['test1']]); - // }); + it('should add multiple tags to an image', function() { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + imageName: testImage1, + path: '@iteratec/semantic-release-docker', + additionalTags: ['tag1', 'tag2'] + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + return expect(prepare(config, context).then(data => data[0])).to.eventually.have.length(3); + }); - // it('should add multiple tags to an image', function() { - // const context = { - // // tslint:disable-next-line:no-empty - // logger: { log: (message: string) => {} }, - // nextRelease: { - // gitTag: '', - // notes: '', - // version: 'next', - // }, - // options: { - // branch: '', - // noCi: true, - // prepare: [ - // { - // imageName: 'test1', - // path: '@iteratec/semantic-release-docker', - // additionalTags: ['tag1', 'tag2'], - // } as DockerPluginConfig, - // ], - // repositoryUrl: '', - // tagFormat: '', - // }, - // } as SemanticReleaseContext; - // return expect(prepare(config, context).then((data) => data[0])).to.eventually.have.length(3); - // }); + it('should add multiple images', function() { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + imageName: testImage1, + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig, + { + imageName: 'test2', + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + return expect(prepare(config, context)).to.eventually.have.length(2); + }); - // it('should add multiple images', function() { - // const context = { - // // tslint:disable-next-line:no-empty - // logger: { log: (message: string) => {} }, - // nextRelease: { - // gitTag: '', - // notes: '', - // version: 'next', - // }, - // options: { - // branch: '', - // noCi: true, - // prepare: [ - // { - // imageName: 'test1', - // path: '@iteratec/semantic-release-docker', - // } as DockerPluginConfig, - // { - // imageName: 'test2', - // path: '@iteratec/semantic-release-docker', - // } as DockerPluginConfig, - // ], - // repositoryUrl: '', - // tagFormat: '', - // }, - // } as SemanticReleaseContext; - // return expect(prepare(config, context)).to.eventually.have.length(2); - // }); + after(async function() { + const docker = new Dockerode(); + docker.getImage(testImage1).remove(); + docker.getImage(testImage2).remove(); + }); }); }); From 44b144dce55c0b74a61422a1bcde6892bf07e58e Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 00:06:33 +0200 Subject: [PATCH 08/22] eliminate competing tests --- src/verifyConditions/index.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/verifyConditions/index.spec.ts b/src/verifyConditions/index.spec.ts index 7f8ff23..2f13229 100644 --- a/src/verifyConditions/index.spec.ts +++ b/src/verifyConditions/index.spec.ts @@ -113,10 +113,10 @@ describe('@iteratec/semantic-release-docker', function() { ); }); - it('should throw if image with imagename does not exist', function() { + it('should throw if image with imagename does not exist', async function() { const docker = new Docker(); try { - docker.getImage(imageName).remove(); + await docker.getImage(imageName).remove(); } catch (err) {} const context = { From b7b0797ac7d9dcb3561b571a6ef99c7ab764cdd0 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 01:07:55 +0200 Subject: [PATCH 09/22] fix format --- src/shared-logic.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/shared-logic.ts b/src/shared-logic.ts index c6d5b2a..2734b85 100644 --- a/src/shared-logic.ts +++ b/src/shared-logic.ts @@ -1,9 +1,9 @@ -import { Credentials, DockerPluginConfig } from "./models"; +import { Credentials, DockerPluginConfig } from './models'; export function constructImageName(config: DockerPluginConfig): string { return ( - `${config.registryUrl ? `${config.registryUrl}/` : ""}` + - `${config.repositoryName ? `${config.repositoryName}/` : ""}` + + `${config.registryUrl ? `${config.registryUrl}/` : ''}` + + `${config.repositoryName ? `${config.repositoryName}/` : ''}` + `${config.imageName}` ); } @@ -13,7 +13,7 @@ export function getRegistryUrlFromConfig(config: DockerPluginConfig): string { ? process.env.DOCKER_REGISTRY_URL : config.registryUrl ? config.registryUrl - : ""; + : ''; } /** @@ -23,12 +23,12 @@ export function getRegistryUrlFromConfig(config: DockerPluginConfig): string { export function getCredentials(): Credentials { // Check DOCKER_REGISTRY_USER Environment Variable if (!process.env.DOCKER_REGISTRY_USER) { - throw new Error("Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry."); + throw new Error('Environment variable DOCKER_REGISTRY_USER must be set in order to login to the registry.'); } // Check DOCKER_REGISTRY_PASSWORD Environment Variable if (!process.env.DOCKER_REGISTRY_PASSWORD) { - throw new Error("Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry."); + throw new Error('Environment variable DOCKER_REGISTRY_PASSWORD must be set in order to login to the registry.'); } return { From cfe071714b00994791f6ecafa95116fb50e74004 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 01:08:47 +0200 Subject: [PATCH 10/22] Fix tests by using dockerode.build the right way --- src/prepare/index.spec.ts | 81 ++++++++++++++-------------- src/test/test-helpers.ts | 27 ++++++++++ src/verifyConditions/index.spec.ts | 86 ++++++++++++------------------ 3 files changed, 103 insertions(+), 91 deletions(-) create mode 100644 src/test/test-helpers.ts diff --git a/src/prepare/index.spec.ts b/src/prepare/index.spec.ts index 095baca..44fec39 100644 --- a/src/prepare/index.spec.ts +++ b/src/prepare/index.spec.ts @@ -6,6 +6,7 @@ import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release' import { DockerPluginConfig } from '../models'; import { prepare } from './index'; import { setVerified } from '../verifyConditions'; +import { buildImage } from '../test/test-helpers'; describe('@iteratec/semantic-release-docker', function() { describe('prepare', function() { @@ -19,47 +20,18 @@ describe('@iteratec/semantic-release-docker', function() { const testImage1 = 'test1:latest'; const testImage2 = 'test2:latest'; - before(function() { + before(async function() { use(chaiAsPromised); setVerified(); - }); - before(async function() { - this.timeout(10000); - const docker = new Dockerode(); - await docker.buildImage( - { - context: './', - src: ['Dockerfile'] - }, - { - t: testImage1 - }, - function(error, output) { - if (error) { - return console.error(error); - } - } - ); - await docker.buildImage( - { - context: './', - src: ['Dockerfile'] - }, - { - t: testImage2 - }, - function(error, output) { - if (error) { - return console.error(error); - } - } - ); + await buildImage(testImage1); + await buildImage(testImage2); + process.env.DOCKER_REGISTRY_USER = 'username'; process.env.DOCKER_REGISTRY_PASSWORD = 'password'; }); - it('should tag an image', function() { + it('should tag an image', async function() { const context = { // tslint:disable-next-line:no-empty logger: { log: (message: string) => {} }, @@ -81,10 +53,16 @@ describe('@iteratec/semantic-release-docker', function() { tagFormat: '' } } as SemanticReleaseContext; - return expect(prepare(config, context)).to.eventually.deep.equal([[testImage1]]); + const docker = new Dockerode(); + let prepareResult = await prepare(config, context); + + expect(prepareResult).to.deep.equal([[testImage1]]); + + let imagelist = await docker.listImages({ filters: { reference: [testImage1] } }); + expect(imagelist.length).to.equal(1); }); - it('should add multiple tags to an image', function() { + it('should add multiple tags to an image', async function() { const context = { // tslint:disable-next-line:no-empty logger: { log: (message: string) => {} }, @@ -110,7 +88,7 @@ describe('@iteratec/semantic-release-docker', function() { return expect(prepare(config, context).then(data => data[0])).to.eventually.have.length(3); }); - it('should add multiple images', function() { + it('should add multiple images', async function() { const context = { // tslint:disable-next-line:no-empty logger: { log: (message: string) => {} }, @@ -141,8 +119,33 @@ describe('@iteratec/semantic-release-docker', function() { after(async function() { const docker = new Dockerode(); - docker.getImage(testImage1).remove(); - docker.getImage(testImage2).remove(); + await docker.getImage(testImage1).remove(); + await docker.getImage(testImage2).remove(); }); }); }); + +// describe('test', function() { +// before(async function() { +// console.log('before'); +// }); + +// it('should be true', async function() { +// await expect(delayed(100)).to.eventually.be.equal(1); + +// return expect(delayed(100)).to.eventually.be.equal(1); +// }); + +// after(async function() { +// console.log('after'); +// }); +// }); + +// function delayed(delay: number) { +// return new Promise(resolve => { +// setTimeout(() => { +// resolve(1); +// console.log('delayed'); +// }, delay); +// }); +// } diff --git a/src/test/test-helpers.ts b/src/test/test-helpers.ts new file mode 100644 index 0000000..bef986b --- /dev/null +++ b/src/test/test-helpers.ts @@ -0,0 +1,27 @@ +import Dockerode from 'dockerode'; + +export function buildImage(imageName: string): Promise { + const docker = new Dockerode(); + return new Promise((resolve, reject) => { + docker.buildImage( + { + context: './', + src: ['Dockerfile'] + }, + { + t: imageName + }, + function(error, stream) { + if (error) { + reject(error); + } + if (stream) { + stream.resume(); + stream.on('end', function() { + resolve(); + }); + } + } + ); + }); +} diff --git a/src/verifyConditions/index.spec.ts b/src/verifyConditions/index.spec.ts index 2f13229..5c4f3e3 100644 --- a/src/verifyConditions/index.spec.ts +++ b/src/verifyConditions/index.spec.ts @@ -5,6 +5,7 @@ import Docker from 'dockerode'; import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; import { DockerPluginConfig } from '../models'; import { verifyConditions } from './index'; +import { buildImage } from '../test/test-helpers'; describe('@iteratec/semantic-release-docker', function() { describe('verifyConditions', function() { @@ -69,16 +70,7 @@ describe('@iteratec/semantic-release-docker', function() { it('should default to docker hub if no registry is specified', async function() { this.timeout(10000); - const docker = new Docker(); - await docker.buildImage( - { - context: './', - src: ['Dockerfile'] - }, - { - t: imageName - } - ); + await buildImage(imageName); (context.options.prepare![0] as DockerPluginConfig).registryUrl = ''; process.env.DOCKER_REGISTRY_USER = 'badusername'; process.env.DOCKER_REGISTRY_PASSWORD = 'pass@w0rd'; @@ -115,9 +107,7 @@ describe('@iteratec/semantic-release-docker', function() { it('should throw if image with imagename does not exist', async function() { const docker = new Docker(); - try { - await docker.getImage(imageName).remove(); - } catch (err) {} + await docker.getImage(imageName).remove(); const context = { // tslint:disable-next-line:no-empty @@ -140,51 +130,43 @@ describe('@iteratec/semantic-release-docker', function() { tagFormat: '' } } as SemanticReleaseContext; - return expect(verifyConditions(config, context)).to.be.rejectedWith( + return expect(verifyConditions(config, context)).to.eventually.be.rejectedWith( `Image with name '${imageName}' does not exist on this machine.` ); }); - it('should NOT throw if image with imagename does exist', function() { - const docker = new Docker(); + it('should NOT throw if image with imagename does exist', async function() { + await buildImage(imageName); + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + imageName, + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; - return docker - .buildImage( - { - context: './', - src: ['Dockerfile'] - }, - { - t: imageName - } - ) - .then(success => { - const context = { - // tslint:disable-next-line:no-empty - logger: { log: (message: string) => {} }, - nextRelease: { - gitTag: '', - notes: '', - version: 'next' - }, - options: { - branch: '', - noCi: true, - prepare: [ - { - imageName, - path: '@iteratec/semantic-release-docker' - } as DockerPluginConfig - ], - repositoryUrl: '', - tagFormat: '' - } - } as SemanticReleaseContext; + return expect(verifyConditions(config, context)).to.eventually.not.be.rejectedWith( + `Image with name '${imageName}' does not exist on this machine.` + ); + }); - return expect(verifyConditions(config, context)).to.not.be.rejectedWith( - `Image with name '${imageName}' does not exist on this machine.` - ); - }); + after(async function() { + const docker = new Docker(); + await docker.getImage(imageName).remove(); }); }); }); From 3965455f515b9002b8c99a991dab54d104a67567 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 01:11:29 +0200 Subject: [PATCH 11/22] fix test --- src/verifyConditions/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/verifyConditions/index.spec.ts b/src/verifyConditions/index.spec.ts index 5c4f3e3..8fb17a1 100644 --- a/src/verifyConditions/index.spec.ts +++ b/src/verifyConditions/index.spec.ts @@ -159,7 +159,7 @@ describe('@iteratec/semantic-release-docker', function() { } } as SemanticReleaseContext; - return expect(verifyConditions(config, context)).to.eventually.not.be.rejectedWith( + return expect(verifyConditions(config, context)).to.not.eventually.be.rejectedWith( `Image with name '${imageName}' does not exist on this machine.` ); }); From dd6f25904eaa384badf2b61d77fb985e235ba7b0 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 02:03:48 +0200 Subject: [PATCH 12/22] Add more test - refactoring --- src/prepare/index.spec.ts | 161 ++++++++++++++++++++++++++++---------- src/prepare/index.ts | 35 ++++----- src/publish/index.ts | 36 ++++----- src/shared-logic.ts | 12 +++ 4 files changed, 164 insertions(+), 80 deletions(-) diff --git a/src/prepare/index.spec.ts b/src/prepare/index.spec.ts index 44fec39..a8aeff0 100644 --- a/src/prepare/index.spec.ts +++ b/src/prepare/index.spec.ts @@ -7,6 +7,7 @@ import { DockerPluginConfig } from '../models'; import { prepare } from './index'; import { setVerified } from '../verifyConditions'; import { buildImage } from '../test/test-helpers'; +import { afterEach } from 'mocha'; describe('@iteratec/semantic-release-docker', function() { describe('prepare', function() { @@ -17,21 +18,25 @@ describe('@iteratec/semantic-release-docker', function() { tagFormat: '' }; - const testImage1 = 'test1:latest'; - const testImage2 = 'test2:latest'; + const testImage1 = 'test1'; + const testImage2 = 'test2'; + + const docker = new Dockerode(); before(async function() { use(chaiAsPromised); setVerified(); - await buildImage(testImage1); - await buildImage(testImage2); - process.env.DOCKER_REGISTRY_USER = 'username'; process.env.DOCKER_REGISTRY_PASSWORD = 'password'; }); - it('should tag an image', async function() { + beforeEach(async function() { + await buildImage(testImage1); + await buildImage(testImage2); + }); + + it('should tag image with next version', async function() { const context = { // tslint:disable-next-line:no-empty logger: { log: (message: string) => {} }, @@ -53,16 +58,84 @@ describe('@iteratec/semantic-release-docker', function() { tagFormat: '' } } as SemanticReleaseContext; - const docker = new Dockerode(); let prepareResult = await prepare(config, context); expect(prepareResult).to.deep.equal([[testImage1]]); - let imagelist = await docker.listImages({ filters: { reference: [testImage1] } }); + let imagelist2 = await docker.listImages({ filters: { reference: [`${testImage1}:next`] } }); + expect(imagelist2.length).to.equal(1); + }); + + it('should tag image without next tag', async function() { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + pushVersionTag: false, + imageName: testImage1, + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + let prepareResult = await prepare(config, context); + + expect(prepareResult).to.deep.equal([[]]); + + let imagelist2 = await docker.listImages({ filters: { reference: [`${testImage1}:next`] } }); + expect(imagelist2.length).to.equal(0); + }); + + it('should add multiple tags to an image (with next version)', async function() { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + imageName: testImage1, + path: '@iteratec/semantic-release-docker', + additionalTags: ['tag1', 'tag2'] + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + + let prepareResult = await prepare(config, context).then(data => data[0]); + + expect(prepareResult).to.have.length(3); + + let imagelist = await docker.listImages({ filters: { reference: [`${testImage1}:next`] } }); expect(imagelist.length).to.equal(1); + + let imagelist1 = await docker.listImages({ filters: { reference: [`${testImage1}:tag1`] } }); + expect(imagelist1.length).to.equal(1); + + let imagelist2 = await docker.listImages({ filters: { reference: [`${testImage1}:tag2`] } }); + expect(imagelist2.length).to.equal(1); }); - it('should add multiple tags to an image', async function() { + it('should add multiple tags to an image (without next version)', async function() { const context = { // tslint:disable-next-line:no-empty logger: { log: (message: string) => {} }, @@ -76,6 +149,7 @@ describe('@iteratec/semantic-release-docker', function() { noCi: true, prepare: [ { + pushVersionTag: false, imageName: testImage1, path: '@iteratec/semantic-release-docker', additionalTags: ['tag1', 'tag2'] @@ -85,7 +159,16 @@ describe('@iteratec/semantic-release-docker', function() { tagFormat: '' } } as SemanticReleaseContext; - return expect(prepare(config, context).then(data => data[0])).to.eventually.have.length(3); + + let prepareResult = await prepare(config, context).then(data => data[0]); + + expect(prepareResult).to.have.length(2); + + let imagelist1 = await docker.listImages({ filters: { reference: [`${testImage1}:tag1`] } }); + expect(imagelist1.length).to.equal(1); + + let imagelist2 = await docker.listImages({ filters: { reference: [`${testImage1}:tag2`] } }); + expect(imagelist2.length).to.equal(1); }); it('should add multiple images', async function() { @@ -106,7 +189,7 @@ describe('@iteratec/semantic-release-docker', function() { path: '@iteratec/semantic-release-docker' } as DockerPluginConfig, { - imageName: 'test2', + imageName: testImage2, path: '@iteratec/semantic-release-docker' } as DockerPluginConfig ], @@ -114,38 +197,34 @@ describe('@iteratec/semantic-release-docker', function() { tagFormat: '' } } as SemanticReleaseContext; - return expect(prepare(config, context)).to.eventually.have.length(2); + let prepareResult = await prepare(config, context); + + expect(prepareResult).to.have.length(2); + + let imagelist1 = await docker.listImages({ filters: { reference: [`${testImage1}:next`] } }); + expect(imagelist1.length).to.equal(1); + + let imagelist2 = await docker.listImages({ filters: { reference: [`${testImage2}:next`] } }); + expect(imagelist2.length).to.equal(1); }); - after(async function() { - const docker = new Dockerode(); - await docker.getImage(testImage1).remove(); - await docker.getImage(testImage2).remove(); + afterEach(async function() { + const imagelist1 = await docker.listImages({ filters: { reference: [testImage1] } }); + await Promise.all( + imagelist1.map(image => { + return docker.getImage(image.Id).remove({ + force: true + }); + }) + ); + const imagelist2 = await docker.listImages({ filters: { reference: [testImage2] } }); + await Promise.all( + imagelist2.map(image => { + return docker.getImage(image.Id).remove({ + force: true + }); + }) + ); }); }); }); - -// describe('test', function() { -// before(async function() { -// console.log('before'); -// }); - -// it('should be true', async function() { -// await expect(delayed(100)).to.eventually.be.equal(1); - -// return expect(delayed(100)).to.eventually.be.equal(1); -// }); - -// after(async function() { -// console.log('after'); -// }); -// }); - -// function delayed(delay: number) { -// return new Promise(resolve => { -// setTimeout(() => { -// resolve(1); -// console.log('delayed'); -// }, delay); -// }); -// } diff --git a/src/prepare/index.ts b/src/prepare/index.ts index 72e12ec..09747a2 100644 --- a/src/prepare/index.ts +++ b/src/prepare/index.ts @@ -3,7 +3,7 @@ import Dockerode from 'dockerode'; import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release'; import { DockerPluginConfig } from '../models'; import { pluginSettings } from '../plugin-settings'; -import { constructImageName } from '../shared-logic'; +import { constructImageName, getImageTagsFromConfig } from '../shared-logic'; import { verified, verifyConditions } from '../verifyConditions'; export var prepared = false; @@ -12,48 +12,45 @@ export async function prepare(pluginConfig: SemanticReleaseConfig, context: Sema if (!verified) { await verifyConditions(pluginConfig, context).then( () => {}, - (reject) => { + reject => { return Promise.reject(reject); - }, + } ); } - const preparePlugins = context.options.prepare!.filter((p) => p.path === pluginSettings.path) as DockerPluginConfig[]; + const preparePlugins = context.options.prepare!.filter(p => p.path === pluginSettings.path) as DockerPluginConfig[]; return Promise.all( - preparePlugins.map((preparePlugin) => { + preparePlugins.map(preparePlugin => { const docker = new Dockerode(); const image = docker.getImage(preparePlugin.imageName); - let tags = [context.nextRelease!.version!]; - if (preparePlugin.additionalTags && preparePlugin.additionalTags.length > 0) { - tags = tags.concat(preparePlugin.additionalTags); - } + const tags = getImageTagsFromConfig(preparePlugin, context); return Promise.all( - tags.map((imagetag) => { + tags.map(imagetag => { return image.tag({ repo: constructImageName(preparePlugin), - tag: imagetag, + tag: imagetag }); - }), + }) ) - .then((data) => { + .then(data => { if (!prepared) { prepared = true; } - return data.map((result) => result.name); + return data.map(result => result.name); }) - .catch((error) => { + .catch(error => { throw new Error(error); }); - }), + }) ) - .then((data) => { + .then(data => { if (!prepared) { prepared = true; } - return data.map((result) => result); + return data.map(result => result); }) - .catch((error) => { + .catch(error => { throw new Error(error); }); } diff --git a/src/publish/index.ts b/src/publish/index.ts index 7a1d74c..48721da 100644 --- a/src/publish/index.ts +++ b/src/publish/index.ts @@ -4,7 +4,7 @@ import { SemanticReleaseConfig, SemanticReleaseContext } from 'semantic-release' import { Authentication, DockerPluginConfig } from '../models'; import { pluginSettings } from '../plugin-settings'; import { prepare, prepared } from '../prepare'; -import { constructImageName, getRegistryUrlFromConfig } from '../shared-logic'; +import { constructImageName, getRegistryUrlFromConfig, getImageTagsFromConfig } from '../shared-logic'; interface PushOptions extends Authentication { tag: string; @@ -20,15 +20,11 @@ export async function publish(pluginConfig: SemanticReleaseConfig, context: Sema } const docker = new Dockerode(); - const preparePlugins = context.options.prepare!.filter((p) => p.path === pluginSettings.path) as DockerPluginConfig[]; + const preparePlugins = context.options.prepare!.filter(p => p.path === pluginSettings.path) as DockerPluginConfig[]; return Promise.all( - preparePlugins.map((preparePlugin) => { - let tags = [context.nextRelease!.version!]; - - if (preparePlugin.additionalTags && preparePlugin.additionalTags.length > 0) { - tags = tags.concat(preparePlugin.additionalTags); - } + preparePlugins.map(preparePlugin => { + const tags = getImageTagsFromConfig(preparePlugin, context); const imageName = constructImageName(preparePlugin); @@ -37,37 +33,37 @@ export async function publish(pluginConfig: SemanticReleaseConfig, context: Sema password: process.env.DOCKER_REGISTRY_PASSWORD!, serveraddress: getRegistryUrlFromConfig(preparePlugin), tag: '', - username: process.env.DOCKER_REGISTRY_USER!, + username: process.env.DOCKER_REGISTRY_USER! }; return Promise.all( tags.map((imageTag: string) => { options.tag = imageTag; context.logger.log(`pushing image ${imageName}:${imageTag}`); return image.push(options); - }), + }) ) - .then((streams) => + .then(streams => Promise.all( streams.map( - (stream) => + stream => new Promise((resolve, reject) => { - stream.on('data', (chunk) => context.logger.log(chunk.toString())); + stream.on('data', chunk => context.logger.log(chunk.toString())); stream.on('end', () => resolve()); - stream.on('error', (error) => { + stream.on('error', error => { reject(error); }); - }), - ), - ), + }) + ) + ) ) .then(() => { return { - completeImageName: tags.map((tag: string) => `${imageName}:${tag}`), + completeImageName: tags.map((tag: string) => `${imageName}:${tag}`) } as PublishedRelease; }) - .catch((error) => { + .catch(error => { throw new Error(error); }); - }), + }) ); } diff --git a/src/shared-logic.ts b/src/shared-logic.ts index 2734b85..617ac8e 100644 --- a/src/shared-logic.ts +++ b/src/shared-logic.ts @@ -1,4 +1,5 @@ import { Credentials, DockerPluginConfig } from './models'; +import { SemanticReleaseContext } from 'semantic-release'; export function constructImageName(config: DockerPluginConfig): string { return ( @@ -16,6 +17,17 @@ export function getRegistryUrlFromConfig(config: DockerPluginConfig): string { : ''; } +export function getImageTagsFromConfig(config: DockerPluginConfig, context: SemanticReleaseContext): string[] { + let tags = []; + if (config.pushVersionTag == null || config.pushVersionTag === true) { + tags.push(context.nextRelease!.version!); + } + if (config.additionalTags && config.additionalTags.length > 0) { + tags = tags.concat(config.additionalTags); + } + return tags; +} + /** * Get Authentication object from Environment Variables * Throws Error if Variables are not set. From fd0b5e55835a37889dba1a7f8e21e25f538686c6 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 02:06:14 +0200 Subject: [PATCH 13/22] greater timeouts --- src/prepare/index.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/prepare/index.spec.ts b/src/prepare/index.spec.ts index a8aeff0..59aa49d 100644 --- a/src/prepare/index.spec.ts +++ b/src/prepare/index.spec.ts @@ -32,6 +32,7 @@ describe('@iteratec/semantic-release-docker', function() { }); beforeEach(async function() { + this.timeout(10000); await buildImage(testImage1); await buildImage(testImage2); }); @@ -209,6 +210,7 @@ describe('@iteratec/semantic-release-docker', function() { }); afterEach(async function() { + this.timeout(5000); const imagelist1 = await docker.listImages({ filters: { reference: [testImage1] } }); await Promise.all( imagelist1.map(image => { From f086a189b00b688c94cc19e9835b3221ac0a200f Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 02:31:07 +0200 Subject: [PATCH 14/22] Add registry and registryUrl tests --- src/prepare/index.spec.ts | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/prepare/index.spec.ts b/src/prepare/index.spec.ts index 59aa49d..03724f0 100644 --- a/src/prepare/index.spec.ts +++ b/src/prepare/index.spec.ts @@ -67,6 +67,71 @@ describe('@iteratec/semantic-release-docker', function() { expect(imagelist2.length).to.equal(1); }); + it('should tag image with next version and repositoryName', async function() { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + imageName: testImage1, + repositoryName: 'repository', + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + let prepareResult = await prepare(config, context); + + expect(prepareResult).to.deep.equal([[testImage1]]); + + let imagelist2 = await docker.listImages({ filters: { reference: [`repository/${testImage1}:next`] } }); + expect(imagelist2.length).to.equal(1); + }); + + it('should tag image with next version and repositoryName and url', async function() { + const context = { + // tslint:disable-next-line:no-empty + logger: { log: (message: string) => {} }, + nextRelease: { + gitTag: '', + notes: '', + version: 'next' + }, + options: { + branch: '', + noCi: true, + prepare: [ + { + imageName: testImage1, + repositoryName: 'repository', + registryUrl: 'repositoryurl', + path: '@iteratec/semantic-release-docker' + } as DockerPluginConfig + ], + repositoryUrl: '', + tagFormat: '' + } + } as SemanticReleaseContext; + let prepareResult = await prepare(config, context); + + expect(prepareResult).to.deep.equal([[testImage1]]); + + let imagelist2 = await docker.listImages({ + filters: { reference: [`repositoryurl/repository/${testImage1}:next`] } + }); + expect(imagelist2.length).to.equal(1); + }); + it('should tag image without next tag', async function() { const context = { // tslint:disable-next-line:no-empty From 4383fb37624173e8f6be5e54c568c1c9d7a2eed1 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 02:31:17 +0200 Subject: [PATCH 15/22] Update Readme --- README.md | 85 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index facae85..36fbbc0 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,13 @@ A [semantic-release](https://github.com/semantic-release/semantic-release) plugin to use semantic versioning for docker images. -## verifyConditions - -verifies that environment variables for authentication via username and password are set. -It uses a registry server provided via config or environment variable (preferred) or defaults to docker hub if none is given. -It also verifies that the credentials are correct by logging in to the given registry. - -## prepare - -tags the specified image with the version number determined by semantic-release and additional tags provided in the configuration. -In addition it supports specifying a complete image name (CIN) via configuration settings according to the canonical format specified by docker: - -`[registryhostname[:port]/][username/]imagename[:tag]` +## Configuration -## publish +### Installation -pushes the tagged images to the registry. +`npm i --save @iteratec/semantic-release-docker` -## Configuration - -### docker registry authentication +### Docker registry authentication The `docker registry` authentication is **required** and can be set via environment variables. @@ -37,38 +24,32 @@ The `docker registry` authentication is **required** and can be set via environm | DOCKER_REGISTRY_USER | The user name to authenticate with at the registry. | | DOCKER_REGISTRY_PASSWORD | The password used for authentication at the registry. | -### Options - -| Option | Description | -| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| additionalTags | _Optional_. An array of strings allowing to specify additional tags to apply to the image. | -| imageName | **_Required_** The name of the image to release. | -| registryUrl | _Optional_. The hostname and port used by the the registry in format `hostname[:port]`. Omit the port if the registry uses the default port | -| repositoryName | _Optional_. The name of the repository in the registry, e.g. username on docker hub | - ### Usage -full configuration: +#### Full configuration ```json { "verifyConfig": ["@iteratec/semantic-release-docker"], - "prepare": { - "path": "@iteratec/semantic-release-docker", - "additionalTags": ["test", "demo"], - "imageName": "my-image", - "registryUrl": "my-private-registry:5678", - "respositoryName": "my-repository" - }, + "prepare": [ + { + "path": "@iteratec/semantic-release-docker", + "additionalTags": ["test", "demo"], + "imageName": "my-image", + "registryUrl": "my-private-registry:5678", + "respositoryName": "my-repository", + "pushVersionTag": true + } + ], "publish": { "path": "@iteratec/semantic-release-docker" } } ``` -results in `my-private-registry:5678/my-repository/my-image` with tags `test`, `demo` and the `` determined by `semantic-release`. +Results in `my-private-registry:5678/my-repository/my-image` with tags `test`, `demo` and the `` determined by `semantic-release`. -minimum configuration: +#### Minimum configuration ```json { @@ -83,7 +64,37 @@ minimum configuration: } ``` -results in `my-image:` +Results in `my-image:`. + +### Options + +| Option | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| additionalTags | _Optional_. An array of strings allowing to specify additional tags to apply to the image. | +| imageName | **_Required_** The name of the image to release. | +| registryUrl | _Optional_. The hostname and port used by the the registry in format `hostname[:port]`. Omit the port if the registry uses the default port | +| repositoryName | _Optional_. The name of the repository in the registry, e.g. username on docker hub | +| pushVersionTag | _Optional_. Whether the semantic release tag, determined by the version, should be pushed. Default is `true`. | + +## Steps + +### verifyConditions + +It uses a registry server provided via config or environment variable (preferred) or defaults to docker hub if none is given. + +1. Verifies that environment variables for authentication via username and password are set. +2. It also verifies that the credentials are correct by logging in to the given registry. + +### prepare + +Tags the specified image with the version number determined by semantic-release and additional tags provided in the configuration. +In addition it supports specifying a complete image name (CIN) via configuration settings according to the canonical format specified by docker: + +`[registryhostname[:port]/][username/]imagename[:tag]` + +### publish + +Pushes the tagged images to the registry. ## Contribute From 2f526641d488109702a8f4e0b4e880dab201ccb6 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 02:34:53 +0200 Subject: [PATCH 16/22] Add Readme to package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 75880c2..e7b394e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "build": "rimraf dist && tsc", - "postbuild": "cpx package.json dist/ && cpx package-lock.json dist/", + "postbuild": "cpx package.json dist/ && cpx package-lock.json dist/ && cpx README.md dist/", "commit": "git-cz", "test": "mocha -r chai -r chai-as-promised -r ts-node/register src/**/*.spec.ts" }, From 94a4b4a34932c5d9433fc1485d231df8d5cdb409 Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 02:42:26 +0200 Subject: [PATCH 17/22] Add prepare Step before publish --- src/publish/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/publish/index.ts b/src/publish/index.ts index 48721da..5168ac6 100644 --- a/src/publish/index.ts +++ b/src/publish/index.ts @@ -16,8 +16,14 @@ export interface PublishedRelease { export async function publish(pluginConfig: SemanticReleaseConfig, context: SemanticReleaseContext) { if (!prepared) { - prepare(pluginConfig, context); + await prepare(pluginConfig, context).then( + () => {}, + reject => { + return Promise.reject(reject); + } + ); } + const docker = new Dockerode(); const preparePlugins = context.options.prepare!.filter(p => p.path === pluginSettings.path) as DockerPluginConfig[]; From ada661ac755cf9435ec0b96449aa84432d7a4b2b Mon Sep 17 00:00:00 2001 From: Daniel Habenicht Date: Wed, 17 Apr 2019 13:47:51 +0200 Subject: [PATCH 18/22] Return object instead of array --- src/publish/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/publish/index.ts b/src/publish/index.ts index 5168ac6..103fd69 100644 --- a/src/publish/index.ts +++ b/src/publish/index.ts @@ -71,5 +71,9 @@ export async function publish(pluginConfig: SemanticReleaseConfig, context: Sema throw new Error(error); }); }) - ); + ).then(publishedImages => { + return { + publishedImages: publishedImages + }; + }); } From 7f103e630ff4e74b2c7f87f85092c61d5bce4439 Mon Sep 17 00:00:00 2001 From: DanielHabenicht Date: Fri, 26 Apr 2019 23:54:03 +0200 Subject: [PATCH 19/22] Create azure-pipelines.yml --- azure-pipelines.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..53124fe --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,30 @@ +jobs: + - job: Build + pool: + name: Hosted Ubuntu 1604 + demands: npm + steps: + - task: NodeTool@0 + displayName: "Use Node 10" + inputs: + versionSpec: 10.x + - task: Npm@1 + displayName: "Install dependencies" + inputs: + verbose: false + - task: Npm@1 + displayName: Build + inputs: + command: custom + customCommand: run build + - task: Npm@1 + displayName: Test + inputs: + command: custom + customCommand: run test + - script: | + npm run release + displayName: Publish + env: + NPM_TOKEN: $(NPMTOKEN) + GITHUB_TOKEN: $(GITHUBTOKEN) From c0e726f5f82db9784388c6540dcf8452e5fc6621 Mon Sep 17 00:00:00 2001 From: DanielHabenicht Date: Fri, 26 Apr 2019 23:55:22 +0200 Subject: [PATCH 20/22] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 53124fe..72a1c73 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,9 +22,3 @@ jobs: inputs: command: custom customCommand: run test - - script: | - npm run release - displayName: Publish - env: - NPM_TOKEN: $(NPMTOKEN) - GITHUB_TOKEN: $(GITHUBTOKEN) From e3b26709ca45036e1dad6c840a1bef600fe20aa0 Mon Sep 17 00:00:00 2001 From: DanielHabenicht Date: Fri, 26 Apr 2019 23:58:40 +0200 Subject: [PATCH 21/22] Update index.spec.ts --- src/prepare/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prepare/index.spec.ts b/src/prepare/index.spec.ts index 03724f0..e353c93 100644 --- a/src/prepare/index.spec.ts +++ b/src/prepare/index.spec.ts @@ -32,7 +32,7 @@ describe('@iteratec/semantic-release-docker', function() { }); beforeEach(async function() { - this.timeout(10000); + this.timeout(20000); await buildImage(testImage1); await buildImage(testImage2); }); From d7e44466b0cb263e2a982eb5343efc61dba79be2 Mon Sep 17 00:00:00 2001 From: DanielHabenicht Date: Sat, 27 Apr 2019 00:01:36 +0200 Subject: [PATCH 22/22] Update azure-pipelines.yml --- azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 72a1c73..53124fe 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,3 +22,9 @@ jobs: inputs: command: custom customCommand: run test + - script: | + npm run release + displayName: Publish + env: + NPM_TOKEN: $(NPMTOKEN) + GITHUB_TOKEN: $(GITHUBTOKEN)