Skip to content

Commit

Permalink
feat(docker)!: include support for buildx
Browse files Browse the repository at this point in the history
when the dockerPlatform array option is supplied and has a non zero
number of items, buildx will be used to build and push images rather
than the standard docker builder.

BREAKING CHANGE: images build with buildx will not be stored locally

Fixes: #44
Fixes: #33
  • Loading branch information
esatterwhite committed Mar 13, 2024
1 parent 1a86e77 commit 8b6c3b7
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 31 deletions.
2 changes: 2 additions & 0 deletions lib/build-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ async function buildConfig(build_id, config, context) {
, dockerRegistry: registry = null
, dockerLogin: login = true
, dockerImage: image
, dockerPlatform: platform = null
, dockerPublish: publish = true
, dockerContext = '.'
, dockerVerifyCmd: verifycmd = null
Expand Down Expand Up @@ -75,5 +76,6 @@ async function buildConfig(build_id, config, context) {
, network: network
, quiet: typeCast(quiet) === true
, clean: typeCast(clean) === true
, platform: array.toArray(platform)
}
}
98 changes: 79 additions & 19 deletions lib/docker/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class Image {
, context = '.'
, network = 'default'
, quiet = true
, dry_run = false
, tags = []
, platform = []
} = opts || {}

if (!name || typeof name !== 'string') {
Expand All @@ -36,12 +39,20 @@ class Image {
, dockerfile: dockerfile
, flags: new Map()
, name: name
, tags: tags
, network: network
, project: project
, registry: registry
, dry_run: dry_run
, platform: array.toArray(platform)
}

if (quiet) this.flag('quiet', null)


for (const tag of this.tags) {
this.flag('tag', tag)
}
}

get id() {
Expand Down Expand Up @@ -102,6 +113,56 @@ class Image {
return output
}

get tags() {
const output = []
if (this.opts.dry_run) return output
for (const tag of this.opts.tags) {
output.push(
`${this.repo}:${tag}`
)
}

return output
}

get build_cmd() {
return [
'build'
, `--network=${this.network}`
, '--tag'
, this.name
, ...this.flags
, '-f'
, this.dockerfile
, this.context
].filter(Boolean)
}

get buildx_cmd() {
if (!this.opts.platform?.length) return
this.opts.flags.delete('provenance') // incompatible with load/push
this.opts.flags.delete('output') // alias of load/push
this.opts.flags.delete('load')
this.opts.flags.set('platform', [this.opts.platform.join(',')])

this.flag('pull', null)
if (this.opts.dry_run) {
this.opts.flags.delete('push')
} else {
this.flag('push', null)
}

const cmd = this.build_cmd
cmd.unshift('buildx')

this.opts.flags.delete('platform')
this.opts.flags.delete('push')
this.opts.flags.delete('pull')
// remove the build id tag
cmd.splice(cmd.indexOf(this.name) - 1, 2)
return cmd
}

arg(key, val = null) {
if (val === true || val == null) { // eslint-disable-line no-eq-null
this.flag('build-arg', key)
Expand Down Expand Up @@ -130,19 +191,6 @@ class Image {
return this
}

get build_cmd() {
return [
'build'
, `--network=${this.network}`
, '--tag'
, this.name
, ...this.flags
, '-f'
, this.dockerfile
, this.context
].filter(Boolean)
}

async run(cmd) {
const stream = execa('docker', [
'run'
Expand All @@ -159,7 +207,11 @@ class Image {
}

async build() {
const stream = execa('docker', this.build_cmd)
const cmd = this.opts.platform.length
? this.buildx_cmd
: this.build_cmd

const stream = execa('docker', cmd)
stream.stdout.pipe(process.stdout)
stream.stderr.pipe(process.stderr)
const {stdout, stderr} = await stream
Expand All @@ -174,6 +226,8 @@ class Image {
return this.sha
}
}
this.sha = this.build_id
return this.sha
}

async tag(tag, push = true) {
Expand All @@ -187,14 +241,20 @@ class Image {
}

async push() {
await execa('docker', ['push', this.repo])
// push is a part of the buildx build operation
// At this point the tags have already been pushed.
// re-pushing manually is considered destructive
if (this.opts.platform.length) return

for (const tag of this.opts.tags) {
await this.tag(tag)
}
}

async clean() {
const images = execa('docker', ['images', this.repo, '-q'])
const rm = execa('xargs', ['docker', 'rmi', '-f'])
images.stdout.pipe(rm.stdin)
return rm
const {stdout: images} = await execa('docker', ['images', this.repo, '-q'])
if (!images) return
await execa('docker', ['rmi', '-f', ...images.split(os.EOL)])
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ async function dockerPrepare(opts, context) {
, context: opts.context
, network: opts.network
, quiet: opts.quiet
, platform: opts.platform
, dry_run: !!opts.dryRun
})

const vars = buildTemplateVars(opts, context)
Expand Down
16 changes: 8 additions & 8 deletions lib/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@ module.exports = publish

async function publish(opts, context) {
const {cwd, logger} = context
const vars = buildTemplateVars(opts, context)
const tags = opts.tags.map((template) => {
return string.template(template)(vars)
}).filter(Boolean)

const image = new docker.Image({
registry: opts.registry
, project: opts.project
, name: opts.name
, dockerfile: opts.dockerfile
, build_id: opts.build
, cwd: cwd
, tags: tags
, context: opts.context
, quiet: opts.quiet
})

const vars = buildTemplateVars(opts, context)
const tags = opts.tags.map((template) => {
return string.template(template)(vars)
}).filter(Boolean)

logger.info('tagging docker image', image.id)
for (const tag of tags) {
logger.info(`pushing image: ${image.repo} tag: ${tag}`)
await image.tag(tag, opts.publish)
}
if (!opts.publish) return
await image.push()
}
3 changes: 3 additions & 0 deletions lib/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ async function verify(opts, context) {
, cwd: cwd
, context: opts.context
, network: opts.network
, platform: opts.platform
, quiet: opts.quiet
, dry_run: !!opts.dryRun
})

debug('docker options', opts)
Expand Down
8 changes: 4 additions & 4 deletions test/integration/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ test('docker release', async (t) => {
]
}
, devDependencies: {
'semantic-release': '*'
, '@semantic-release/commit-analyzer': '*'
, '@semantic-release/release-notes-generator': '*'
, '@semantic-release/npm': '*'
'semantic-release': '^19.0.0'
, '@semantic-release/commit-analyzer': '^9'
, '@semantic-release/release-notes-generator': '^10'
, '@semantic-release/npm': '^9'
, '@codedependant/semantic-release-docker': 'file:../../../'
}
})
Expand Down

0 comments on commit 8b6c3b7

Please sign in to comment.