diff --git a/index.js b/index.js index e3844ec..95798e8 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ /* eslint no-underscore-dangle: ["error", { "allowAfterThis": true }] */ const Executor = require('screwdriver-executor-base'); -const hoek = require('hoek'); +const hoek = require('@hapi/hoek'); const imageParser = require('docker-parse-image'); const Fusebox = require('circuit-fuses').breaker; const Docker = require('dockerode'); @@ -40,12 +40,15 @@ class DockerExecutor extends Executor { this.launchVersion = options.launchVersion || 'stable'; this.prefix = options.prefix || ''; - const breakerOptions = hoek.applyToDefaults({ - breaker: { - maxFailures: 10, - timeout: 5 * 60 * 1000 // Default to 5 minute timeout, - } - }, options.fusebox || {}); + const breakerOptions = hoek.applyToDefaults( + { + breaker: { + maxFailures: 10, + timeout: 5 * 60 * 1000 // Default to 5 minute timeout, + } + }, + options.fusebox || {} + ); this.breaker = new Fusebox((obj, cb) => obj.func(cb), breakerOptions); } @@ -107,16 +110,16 @@ class DockerExecutor extends Executor { _findContainers(buildId) { const listArgs = { filters: JSON.stringify({ - label: [ - `sdbuild=${this.prefix}${buildId}` - ] + label: [`sdbuild=${this.prefix}${buildId}`] }), all: true }; - return this.breaker.runCommand({ - func: cb => this.docker.listContainers(listArgs, cb) - }).then(containers => containers.map(container => this.docker.getContainer(container.Id))); + return this.breaker + .runCommand({ + func: cb => this.docker.listContainers(listArgs, cb) + }) + .then(containers => containers.map(container => this.docker.getContainer(container.Id))); } /** @@ -133,8 +136,7 @@ class DockerExecutor extends Executor { const piecesParts = imageParser(config.container); let buildTag = piecesParts.tag; let buildImage = piecesParts.name; - const buildTimeout = hoek.reach(config, 'annotations>screwdriver.cd/timeout', - { separator: '>' }); + const buildTimeout = hoek.reach(config, 'annotations>screwdriver.cd/timeout', { separator: '>' }); const timeout = parseInt(buildTimeout || DEFAULT_BUILD_TIMEOUT, 10); /** @@ -158,54 +160,55 @@ class DockerExecutor extends Executor { buildTag = 'latest'; } - return Promise.all( - [ - this._createImage({ - fromImage: 'screwdrivercd/launcher', - tag: this.launchVersion - }), - this._createImage({ - fromImage: buildImage, - tag: buildTag + return Promise.all([ + this._createImage({ + fromImage: 'screwdrivercd/launcher', + tag: this.launchVersion + }), + this._createImage({ + fromImage: buildImage, + tag: buildTag + }) + ]) + .then(() => + this._createContainer({ + name: `${this.prefix}${config.buildId}-init`, + Image: `screwdrivercd/launcher:${this.launchVersion}`, + Entrypoint: '/bin/true', + Labels: { + sdbuild: `${this.prefix}${config.buildId}` + } }) - ]) - .then(() => this._createContainer({ - name: `${this.prefix}${config.buildId}-init`, - Image: `screwdrivercd/launcher:${this.launchVersion}`, - Entrypoint: '/bin/true', - Labels: { - sdbuild: `${this.prefix}${config.buildId}` - } - })) - .then(launchContainer => this._createContainer({ - name: `${this.prefix}${config.buildId}-build`, - Image: config.container, - Entrypoint: '/opt/sd/launcher_entrypoint.sh', - Labels: { - sdbuild: `${this.prefix}${config.buildId}` - }, - Cmd: [ - [ - // Run the wrapper script - '/opt/sd/run.sh', - `"${config.token}"`, - this.ecosystem.api, - this.ecosystem.store, - timeout, - config.buildId, - this.ecosystem.ui - ].join(' ') - ], - HostConfig: { - // 2 GB of memory - Memory: 2 * 1024 * 1024 * 1024, - // 3 GB of memory + swap (aka, 1 GB of swap) - MemoryLimit: 3 * 1024 * 1024 * 1024, - VolumesFrom: [ - `${launchContainer.id}:rw` - ] - } - })) + ) + .then(launchContainer => + this._createContainer({ + name: `${this.prefix}${config.buildId}-build`, + Image: config.container, + Entrypoint: '/opt/sd/launcher_entrypoint.sh', + Labels: { + sdbuild: `${this.prefix}${config.buildId}` + }, + Cmd: [ + [ + // Run the wrapper script + '/opt/sd/run.sh', + `"${config.token}"`, + this.ecosystem.api, + this.ecosystem.store, + timeout, + config.buildId, + this.ecosystem.ui + ].join(' ') + ], + HostConfig: { + // 2 GB of memory + Memory: 2 * 1024 * 1024 * 1024, + // 3 GB of memory + swap (aka, 1 GB of swap) + MemoryLimit: 3 * 1024 * 1024 * 1024, + VolumesFrom: [`${launchContainer.id}:rw`] + } + }) + ) .then(buildContainer => this._startContainer(buildContainer)); } @@ -217,9 +220,9 @@ class DockerExecutor extends Executor { * @return {Promise} */ _stop(config) { - return this._findContainers(config.buildId) - .then(containers => Promise.all(containers.map(container => - this._removeContainer(container)))); + return this._findContainers(config.buildId).then(containers => + Promise.all(containers.map(container => this._removeContainer(container))) + ); } /** @@ -259,10 +262,10 @@ class DockerExecutor extends Executor { } /** - * Retreive stats for the executor/breaker - * @method stats - * @param {Response} Object Object containing stats for the executor/breaker - */ + * Retreive stats for the executor/breaker + * @method stats + * @param {Response} Object Object containing stats for the executor/breaker + */ stats() { return this.breaker.stats(); } diff --git a/package.json b/package.json index d3f3637..dd8ebae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "screwdriver-executor-docker", - "version": "2.0.0", + "version": "5.0.0", "description": "Docker Swarm Executor for Screwdriver", "main": "index.js", "scripts": { @@ -37,18 +37,18 @@ }, "devDependencies": { "chai": "^4.2.0", - "eslint": "^4.19.1", - "eslint-config-screwdriver": "^3.0.1", - "mocha": "^7.0.1", + "eslint": "^7.7.0", + "eslint-config-screwdriver": "^5.0.4", + "mocha": "^8.1.1", "mockery": "^2.0.0", "nyc": "^15.0.0", - "sinon": "^7.1.0" + "sinon": "^9.0.3" }, "dependencies": { + "@hapi/hoek": "^9.0.4", "circuit-fuses": "^4.0.4", "docker-parse-image": "^3.0.1", "dockerode": "^2.5.8", - "hoek": "^5.0.4", - "screwdriver-executor-base": "^7.0.0" + "screwdriver-executor-base": "^8.0.0" } } diff --git a/test/index.test.js b/test/index.test.js index 4eb3040..2b2a671 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -6,7 +6,7 @@ const mockery = require('mockery'); sinon.assert.expose(assert, { prefix: '' }); -describe('index', function () { +describe('index', function() { // Time not important. Only life important. this.timeout(5000); @@ -140,23 +140,11 @@ describe('index', function () { Labels: { sdbuild: buildId.toString() }, - Cmd: [ - [ - '/opt/sd/run.sh', - `"${token}"`, - 'api', - 'store', - '90', - buildId, - 'ui' - ].join(' ') - ], + Cmd: [['/opt/sd/run.sh', `"${token}"`, 'api', 'store', '90', buildId, 'ui'].join(' ')], HostConfig: { Memory: 2 * 1024 * 1024 * 1024, MemoryLimit: 3 * 1024 * 1024 * 1024, - VolumesFrom: [ - 'launcherID:rw' - ] + VolumesFrom: ['launcherID:rw'] } }; }); @@ -168,22 +156,25 @@ describe('index', function () { }; dockerMock.createContainer.yieldsAsync(new Error('bad container args')); - dockerMock.createContainer.withArgs(launcherArgs) - .yieldsAsync(null, launcherContainer); - dockerMock.createContainer.withArgs(buildArgs) - .yieldsAsync(null, buildContainer); - - return executor.start({ - buildId, container, apiUri, token - }).then(() => { - assert.calledWith(dockerMock.createImage, buildImageArgs); - assert.calledWith(dockerMock.createImage, launcherImageArgs); - assert.callCount(dockerMock.createImage, 2); - assert.calledWith(dockerMock.createContainer, buildArgs); - assert.calledWith(dockerMock.createContainer, launcherArgs); - assert.callCount(dockerMock.createContainer, 2); - assert.callCount(buildContainer.start, 1); - }); + dockerMock.createContainer.withArgs(launcherArgs).yieldsAsync(null, launcherContainer); + dockerMock.createContainer.withArgs(buildArgs).yieldsAsync(null, buildContainer); + + return executor + .start({ + buildId, + container, + apiUri, + token + }) + .then(() => { + assert.calledWith(dockerMock.createImage, buildImageArgs); + assert.calledWith(dockerMock.createImage, launcherImageArgs); + assert.callCount(dockerMock.createImage, 2); + assert.calledWith(dockerMock.createContainer, buildArgs); + assert.calledWith(dockerMock.createContainer, launcherArgs); + assert.callCount(dockerMock.createContainer, 2); + assert.callCount(buildContainer.start, 1); + }); }); it('creates the containers with correct args from build config', () => { @@ -192,41 +183,31 @@ describe('index', function () { tag: '6' }; - buildArgs.Cmd = [ - [ - '/opt/sd/run.sh', - `"${token}"`, - 'api', - 'store', - 5, - buildId, - 'ui' - ].join(' ') - ]; + buildArgs.Cmd = [['/opt/sd/run.sh', `"${token}"`, 'api', 'store', 5, buildId, 'ui'].join(' ')]; dockerMock.createContainer.yieldsAsync(new Error('bad container args')); - dockerMock.createContainer.withArgs(launcherArgs) - .yieldsAsync(null, launcherContainer); - dockerMock.createContainer.withArgs(buildArgs) - .yieldsAsync(null, buildContainer); - - return executor.start({ - buildId, - container, - apiUri, - token, - annotations: { - 'screwdriver.cd/timeout': 5 - } - }).then(() => { - assert.calledWith(dockerMock.createImage, buildImageArgs); - assert.calledWith(dockerMock.createImage, launcherImageArgs); - assert.callCount(dockerMock.createImage, 2); - assert.calledWith(dockerMock.createContainer, buildArgs); - assert.calledWith(dockerMock.createContainer, launcherArgs); - assert.callCount(dockerMock.createContainer, 2); - assert.callCount(buildContainer.start, 1); - }); + dockerMock.createContainer.withArgs(launcherArgs).yieldsAsync(null, launcherContainer); + dockerMock.createContainer.withArgs(buildArgs).yieldsAsync(null, buildContainer); + + return executor + .start({ + buildId, + container, + apiUri, + token, + annotations: { + 'screwdriver.cd/timeout': 5 + } + }) + .then(() => { + assert.calledWith(dockerMock.createImage, buildImageArgs); + assert.calledWith(dockerMock.createImage, launcherImageArgs); + assert.callCount(dockerMock.createImage, 2); + assert.calledWith(dockerMock.createContainer, buildArgs); + assert.calledWith(dockerMock.createContainer, launcherArgs); + assert.callCount(dockerMock.createContainer, 2); + assert.callCount(buildContainer.start, 1); + }); }); it('supports prefixed containers', () => { @@ -251,31 +232,17 @@ describe('index', function () { Labels: { sdbuild: `${prefix}${buildId}` }, - Cmd: [ - [ - '/opt/sd/run.sh', - `"${token}"`, - 'api', - 'store', - '90', - buildId, - 'ui' - ].join(' ') - ], + Cmd: [['/opt/sd/run.sh', `"${token}"`, 'api', 'store', '90', buildId, 'ui'].join(' ')], HostConfig: { Memory: 2 * 1024 * 1024 * 1024, MemoryLimit: 3 * 1024 * 1024 * 1024, - VolumesFrom: [ - 'launcherID:rw' - ] + VolumesFrom: ['launcherID:rw'] } }; dockerMock.createContainer.yieldsAsync(new Error('bad container args')); - dockerMock.createContainer.withArgs(launcherArgs) - .yieldsAsync(null, launcherContainer); - dockerMock.createContainer.withArgs(buildArgs) - .yieldsAsync(null, buildContainer); + dockerMock.createContainer.withArgs(launcherArgs).yieldsAsync(null, launcherContainer); + dockerMock.createContainer.withArgs(buildArgs).yieldsAsync(null, buildContainer); executor = new Executor({ prefix, @@ -286,17 +253,22 @@ describe('index', function () { } }); - return executor.start({ - buildId, container, apiUri, token - }).then(() => { - assert.calledWith(dockerMock.createImage, buildImageArgs); - assert.calledWith(dockerMock.createImage, launcherImageArgs); - assert.callCount(dockerMock.createImage, 2); - assert.calledWith(dockerMock.createContainer, buildArgs); - assert.calledWith(dockerMock.createContainer, launcherArgs); - assert.callCount(dockerMock.createContainer, 2); - assert.callCount(buildContainer.start, 1); - }); + return executor + .start({ + buildId, + container, + apiUri, + token + }) + .then(() => { + assert.calledWith(dockerMock.createImage, buildImageArgs); + assert.calledWith(dockerMock.createImage, launcherImageArgs); + assert.callCount(dockerMock.createImage, 2); + assert.calledWith(dockerMock.createContainer, buildArgs); + assert.calledWith(dockerMock.createContainer, launcherArgs); + assert.callCount(dockerMock.createContainer, 2); + assert.callCount(buildContainer.start, 1); + }); }); it('creates containers without specifying a tag', () => { @@ -309,22 +281,25 @@ describe('index', function () { buildArgs.Image = container; dockerMock.createContainer.yieldsAsync(new Error('bad container args')); - dockerMock.createContainer.withArgs(launcherArgs) - .yieldsAsync(null, launcherContainer); - dockerMock.createContainer.withArgs(buildArgs) - .yieldsAsync(null, buildContainer); - - return executor.start({ - buildId, container, apiUri, token - }).then(() => { - assert.calledWith(dockerMock.createImage, buildImageArgs); - assert.calledWith(dockerMock.createImage, launcherImageArgs); - assert.callCount(dockerMock.createImage, 2); - assert.calledWith(dockerMock.createContainer, buildArgs); - assert.calledWith(dockerMock.createContainer, launcherArgs); - assert.callCount(dockerMock.createContainer, 2); - assert.callCount(buildContainer.start, 1); - }); + dockerMock.createContainer.withArgs(launcherArgs).yieldsAsync(null, launcherContainer); + dockerMock.createContainer.withArgs(buildArgs).yieldsAsync(null, buildContainer); + + return executor + .start({ + buildId, + container, + apiUri, + token + }) + .then(() => { + assert.calledWith(dockerMock.createImage, buildImageArgs); + assert.calledWith(dockerMock.createImage, launcherImageArgs); + assert.callCount(dockerMock.createImage, 2); + assert.calledWith(dockerMock.createContainer, buildArgs); + assert.calledWith(dockerMock.createContainer, launcherArgs); + assert.callCount(dockerMock.createContainer, 2); + assert.callCount(buildContainer.start, 1); + }); }); it('creates containers from a private docker registry and starts them', () => { @@ -337,22 +312,25 @@ describe('index', function () { buildArgs.Image = container; dockerMock.createContainer.yieldsAsync(new Error('bad container args')); - dockerMock.createContainer.withArgs(launcherArgs) - .yieldsAsync(null, launcherContainer); - dockerMock.createContainer.withArgs(buildArgs) - .yieldsAsync(null, buildContainer); - - return executor.start({ - buildId, container, apiUri, token - }).then(() => { - assert.calledWith(dockerMock.createImage, buildImageArgs); - assert.calledWith(dockerMock.createImage, launcherImageArgs); - assert.callCount(dockerMock.createImage, 2); - assert.calledWith(dockerMock.createContainer, buildArgs); - assert.calledWith(dockerMock.createContainer, launcherArgs); - assert.callCount(dockerMock.createContainer, 2); - assert.callCount(buildContainer.start, 1); - }); + dockerMock.createContainer.withArgs(launcherArgs).yieldsAsync(null, launcherContainer); + dockerMock.createContainer.withArgs(buildArgs).yieldsAsync(null, buildContainer); + + return executor + .start({ + buildId, + container, + apiUri, + token + }) + .then(() => { + assert.calledWith(dockerMock.createImage, buildImageArgs); + assert.calledWith(dockerMock.createImage, launcherImageArgs); + assert.callCount(dockerMock.createImage, 2); + assert.calledWith(dockerMock.createContainer, buildArgs); + assert.calledWith(dockerMock.createContainer, launcherArgs); + assert.callCount(dockerMock.createContainer, 2); + assert.callCount(buildContainer.start, 1); + }); }); it('creates containers from a private docker registry without specifying a tag', () => { @@ -365,46 +343,61 @@ describe('index', function () { buildArgs.Image = container; dockerMock.createContainer.yieldsAsync(new Error('bad container args')); - dockerMock.createContainer.withArgs(launcherArgs) - .yieldsAsync(null, launcherContainer); - dockerMock.createContainer.withArgs(buildArgs) - .yieldsAsync(null, buildContainer); - - return executor.start({ - buildId, container, apiUri, token - }).then(() => { - assert.calledWith(dockerMock.createImage, buildImageArgs); - assert.calledWith(dockerMock.createImage, launcherImageArgs); - assert.callCount(dockerMock.createImage, 2); - assert.calledWith(dockerMock.createContainer, buildArgs); - assert.calledWith(dockerMock.createContainer, launcherArgs); - assert.callCount(dockerMock.createContainer, 2); - assert.callCount(buildContainer.start, 1); - }); + dockerMock.createContainer.withArgs(launcherArgs).yieldsAsync(null, launcherContainer); + dockerMock.createContainer.withArgs(buildArgs).yieldsAsync(null, buildContainer); + + return executor + .start({ + buildId, + container, + apiUri, + token + }) + .then(() => { + assert.calledWith(dockerMock.createImage, buildImageArgs); + assert.calledWith(dockerMock.createImage, launcherImageArgs); + assert.callCount(dockerMock.createImage, 2); + assert.calledWith(dockerMock.createContainer, buildArgs); + assert.calledWith(dockerMock.createContainer, launcherArgs); + assert.callCount(dockerMock.createContainer, 2); + assert.callCount(buildContainer.start, 1); + }); }); it('bubbles create problems back', () => { dockerMock.createContainer.yieldsAsync(new Error('Unable to create container')); - return executor.start({ - buildId, container, apiUri, token - }).then(() => { - throw new Error('should not have gotten here'); - }).catch((error) => { - assert.equal(error.message, 'Unable to create container'); - }); + return executor + .start({ + buildId, + container, + apiUri, + token + }) + .then(() => { + throw new Error('should not have gotten here'); + }) + .catch(error => { + assert.equal(error.message, 'Unable to create container'); + }); }); it('bubbles start problems back', () => { containerMock.start.yieldsAsync(new Error('Unable to start container')); - return executor.start({ - buildId, container, apiUri, token - }).then(() => { - throw new Error('should not have gotten here'); - }).catch((error) => { - assert.equal(error.message, 'Unable to start container'); - }); + return executor + .start({ + buildId, + container, + apiUri, + token + }) + .then(() => { + throw new Error('should not have gotten here'); + }) + .catch(error => { + assert.equal(error.message, 'Unable to start container'); + }); }); }); @@ -422,16 +415,17 @@ describe('index', function () { }; dockerMock.listContainers.yieldsAsync(new Error('bad container args')); - dockerMock.listContainers.withArgs(findArgs) - .yieldsAsync(null, [containerShellMock, containerShellMock]); - - return executor.stop({ - buildId - }).then(() => { - assert.calledWith(dockerMock.listContainers, findArgs); - assert.calledWith(containerMock.remove, removeArgs); - assert.callCount(containerMock.remove, 2); - }); + dockerMock.listContainers.withArgs(findArgs).yieldsAsync(null, [containerShellMock, containerShellMock]); + + return executor + .stop({ + buildId + }) + .then(() => { + assert.calledWith(dockerMock.listContainers, findArgs); + assert.calledWith(containerMock.remove, removeArgs); + assert.callCount(containerMock.remove, 2); + }); }); it('supports prefixes', () => { @@ -446,8 +440,7 @@ describe('index', function () { }; dockerMock.listContainers.yieldsAsync(new Error('bad container args')); - dockerMock.listContainers.withArgs(findArgs) - .yieldsAsync(null, [containerShellMock, containerShellMock]); + dockerMock.listContainers.withArgs(findArgs).yieldsAsync(null, [containerShellMock, containerShellMock]); executor = new Executor({ prefix, @@ -458,71 +451,80 @@ describe('index', function () { } }); - return executor.stop({ - buildId - }).then(() => { - assert.calledWith(dockerMock.listContainers, findArgs); - assert.calledWith(containerMock.remove, removeArgs); - assert.callCount(containerMock.remove, 2); - }); + return executor + .stop({ + buildId + }) + .then(() => { + assert.calledWith(dockerMock.listContainers, findArgs); + assert.calledWith(containerMock.remove, removeArgs); + assert.callCount(containerMock.remove, 2); + }); }); it('bubbles list problems back', () => { dockerMock.listContainers.yieldsAsync(new Error('Unable to list containers')); - return executor.stop({ - buildId - }).then(() => { - throw new Error('should not have gotten here'); - }).catch((error) => { - assert.equal(error.message, 'Unable to list containers'); - }); + return executor + .stop({ + buildId + }) + .then(() => { + throw new Error('should not have gotten here'); + }) + .catch(error => { + assert.equal(error.message, 'Unable to list containers'); + }); }); it('bubbles remove problems back', () => { containerMock.remove.yieldsAsync(new Error('Unable to remove container')); - return executor.stop({ - buildId - }).then(() => { - throw new Error('should not have gotten here'); - }).catch((error) => { - assert.equal(error.message, 'Unable to remove container'); - }); + return executor + .stop({ + buildId + }) + .then(() => { + throw new Error('should not have gotten here'); + }) + .catch(error => { + assert.equal(error.message, 'Unable to remove container'); + }); }); }); describe('stats', () => { it('bubbles stats from circuit fuses', () => { - assert.deepEqual({ - requests: { - total: 0, - timeouts: 0, - success: 0, - failure: 0, - concurrent: 0, - averageTime: 0 + assert.deepEqual( + { + requests: { + total: 0, + timeouts: 0, + success: 0, + failure: 0, + concurrent: 0, + averageTime: 0 + }, + breaker: { + isClosed: true + } }, - breaker: { - isClosed: true - } - }, executor.stats()); + executor.stats() + ); }); }); describe('periodic', () => { - it('resolves to null when calling periodic start', - () => executor.startPeriodic().then(res => assert.isNull(res))); + it('resolves to null when calling periodic start', () => + executor.startPeriodic().then(res => assert.isNull(res))); - it('resolves to null when calling periodic stop', - () => executor.stopPeriodic().then(res => assert.isNull(res))); + it('resolves to null when calling periodic stop', () => + executor.stopPeriodic().then(res => assert.isNull(res))); }); describe('frozen', () => { - it('resolves to null when calling frozen start', - () => executor.startFrozen().then(res => assert.isNull(res))); + it('resolves to null when calling frozen start', () => executor.startFrozen().then(res => assert.isNull(res))); - it('resolves to null when calling frozen stop', - () => executor.stopFrozen().then(res => assert.isNull(res))); + it('resolves to null when calling frozen stop', () => executor.stopFrozen().then(res => assert.isNull(res))); }); });