From 8f37d459993169fb65aa4af46d60bdbe033bf53b Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Sat, 3 Feb 2024 20:40:05 -0600 Subject: [PATCH] fix(image): account for stderr when looking for an image sha in the case the progress output is set to plain, the build output is written to stderr rather than stdout. This updates the logic to check each line with a regex starting from the end of the output Fixes: #46 --- lib/docker/image.js | 18 +++++-- test/fixture/docker/Dockerfile.publish | 2 +- test/integration/prepare.js | 68 ++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/lib/docker/image.js b/lib/docker/image.js index dd80ea5..0e35298 100644 --- a/lib/docker/image.js +++ b/lib/docker/image.js @@ -1,9 +1,11 @@ 'use strict' +const os = require('os') const path = require('path') const crypto = require('crypto') const execa = require('execa') const array = require('../lang/array/index.js') +const SHA_REGEX = /(?:writing image\s)?[^@](?:sha\d{3}):(?\w+)/i class Image { constructor(opts) { @@ -160,10 +162,18 @@ class Image { const stream = execa('docker', this.build_cmd) stream.stdout.pipe(process.stdout) stream.stderr.pipe(process.stderr) - const {stdout} = await stream - const [_, sha] = stdout.split(':') - this.sha = sha.substring(0, 12) - return this.sha + const {stdout, stderr} = await stream + const lines = (stdout || stderr).split(os.EOL) + const len = lines.length - 1 + + for (let x = len; x >= 0; x--) { + const line = lines[x] + const match = SHA_REGEX.exec(line) + if (match) { + this.sha = match.groups.sha.substring(0, 12) + return this.sha + } + } } async tag(tag, push = true) { diff --git a/test/fixture/docker/Dockerfile.publish b/test/fixture/docker/Dockerfile.publish index 380c614..2d0b7f8 100644 --- a/test/fixture/docker/Dockerfile.publish +++ b/test/fixture/docker/Dockerfile.publish @@ -1,4 +1,4 @@ -FROM debian:buster-slim +FROM debian:bullseye-slim COPY . /opt/whizbang WORKDIR /opt/whizbang CMD ["echo", "$PWD"] diff --git a/test/integration/prepare.js b/test/integration/prepare.js index 8e3ad19..69fda2f 100644 --- a/test/integration/prepare.js +++ b/test/integration/prepare.js @@ -72,11 +72,6 @@ test('steps::prepare', async (t) => { tt.equal(image.opts.args.get('MAJOR_TEMPLATE'), '2', 'MAJOR_TEMPLATE value') tt.equal(image.opts.args.get('GIT_REF'), 'abacadaba', 'GIT_REF value') tt.match(image.opts.args.get('BUILD_DATE'), DATE_REGEX, 'BUILD_DATE value') - tt.match( - image.opts.flags.get('TAG_TEMPLATE') - , ['v2.1.2'] - , 'TAG_TEMPLATE stored as a flag' - ) tt.equal(image.context, path.join(context.cwd, config.context), 'docker context path') const {stdout} = await execa('docker', [ @@ -85,4 +80,67 @@ test('steps::prepare', async (t) => { ]) tt.equal(stdout, build_id, 'build image fully built') }) + + t.test('build image created - progress plain', async (tt) => { + const build_id = crypto.randomBytes(5).toString('hex') + const context = { + env: { + ...process.env + , DOCKER_REGISTRY_USER: 'iamweasel' + , DOCKER_REGISTRY_PASSWORD: 'secretsquirrel' + } + , cwd: fixturedir + , nextRelease: { + version: '2.1.2' + , gitTag: 'v2.1.2' + , gitHead: 'abacadaba' + } + , logger: { + success: sinon.stub() + , info: sinon.stub() + , debug: sinon.stub() + , fatal: sinon.stub() + } + } + + const config = await buildConfig(build_id, { + dockerRegistry: DOCKER_REGISTRY_HOST + , dockerProject: 'docker-prepare' + , dockerImage: 'alternate' + , dockerBuildQuiet: false + , dockerBuildFlags: { + progress: 'plain' + , 'no-cache': null + } + , dockerArgs: { + MY_VARIABLE: '1' + , TAG_TEMPLATE: '{{git_tag}}' + , MAJOR_TEMPLATE: '{{major}}' + , GIT_REF: '{{git_sha}}' + , BUILD_DATE: '{{now}}' + } + , dockerFile: 'docker/Dockerfile.prepare' + , dockerContext: 'docker' + }, context) + + const image = await prepare(config, context) + + tt.on('end', () => { + image.clean() + }) + + tt.equal(image.opts.args.get('TAG_TEMPLATE'), 'v2.1.2', 'TAG_TEMPLATE value') + tt.equal(image.opts.args.get('MAJOR_TEMPLATE'), '2', 'MAJOR_TEMPLATE value') + tt.equal(image.opts.args.get('GIT_REF'), 'abacadaba', 'GIT_REF value') + tt.match(image.opts.args.get('BUILD_DATE'), DATE_REGEX, 'BUILD_DATE value') + tt.equal(image.context, path.join(context.cwd, config.context), 'docker context path') + + const {stdout} = await execa('docker', [ + 'images', image.name + , '-q', '--format={{ .Tag }}:{{ .ID}}' + ]) + const [tag, id] = stdout.split(':') + tt.equal(image.id, id, 'captured id matches docker image id') + tt.equal(tag, build_id, 'build image fully built') + }) }).catch(threw)