Fix image push failing with tag:undefined under Bare
Release rolling / release (push) Successful in 9m42s
Release rolling / release (push) Successful in 9m42s
bare-querystring stringifies JS undefined as the literal "undefined", so dockerode push options with tag: undefined made Docker look up repo:undefined. Only pass defined push options, and fail the job when Docker streams an error in the progress body.
This commit is contained in:
@@ -20,6 +20,25 @@ function dockerAuthOpts(authconfig) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build dockerode image.push() options.
|
||||
*
|
||||
* Never includes keys with undefined values. Under Bare, bare-querystring
|
||||
* stringifies undefined as the literal "undefined", which Docker treats as a
|
||||
* real tag (e.g. repo:undefined → "tag does not exist").
|
||||
*
|
||||
* @param {{ tag?: string|null, authconfig?: object|null }} opts
|
||||
* @returns {Record<string, unknown>}
|
||||
*/
|
||||
export function buildImagePushOpts(opts = {}) {
|
||||
const out = {}
|
||||
const tag = opts.tag != null ? String(opts.tag).trim() : ''
|
||||
if (tag) out.tag = tag
|
||||
const auth = dockerAuthOpts(opts.authconfig || null)
|
||||
if (auth) out.authconfig = auth
|
||||
return out
|
||||
}
|
||||
|
||||
export function registerImageHandlers(session) {
|
||||
session.respond('listImages', async (args = {}) => {
|
||||
const listOpts = { all: args.all !== false }
|
||||
@@ -248,31 +267,46 @@ export function registerImageHandlers(session) {
|
||||
if (!imageName) throw new Error('image name/id required')
|
||||
// Optional: retag before push (repo:tag destination)
|
||||
let pushRef = imageName
|
||||
/** Explicit tag query only when set — never pass undefined (see below). */
|
||||
let pushTag = null
|
||||
if (args.repo) {
|
||||
const repo = validation.sanitizeString(args.repo, 255)
|
||||
const tag = validation.sanitizeString(args.tag || 'latest', 128)
|
||||
if (!repo) throw new Error('repo required when tagging for push')
|
||||
if (!tag) throw new Error('tag required when tagging for push')
|
||||
await docker.getImage(args.id || imageName).tag({ repo, tag })
|
||||
pushRef = `${repo}:${tag}`
|
||||
// Name already includes the tag; omit tag query param.
|
||||
pushTag = null
|
||||
} else if (args.tag) {
|
||||
pushTag = validation.sanitizeString(args.tag, 128) || null
|
||||
}
|
||||
|
||||
const authconfig = resolveRegistryAuth(session, {
|
||||
credentialId: args.credentialId,
|
||||
auth: args.auth,
|
||||
autoVault: args.autoVault,
|
||||
image: pushRef,
|
||||
})
|
||||
|
||||
const pushOpts = buildImagePushOpts({ tag: pushTag, authconfig })
|
||||
const image = docker.getImage(pushRef)
|
||||
const stream = await image.push({
|
||||
tag: args.repo ? undefined : args.tag || undefined,
|
||||
authconfig: dockerAuthOpts(authconfig),
|
||||
})
|
||||
const stream = await image.push(pushOpts)
|
||||
|
||||
// Docker may return HTTP 200 and stream { error } in the body — treat as failure.
|
||||
let streamError = null
|
||||
await new Promise((resolve, reject) => {
|
||||
docker.modem.followProgress(
|
||||
stream,
|
||||
(err) => (err ? reject(err) : resolve()),
|
||||
(err) => {
|
||||
if (err) reject(err)
|
||||
else if (streamError) reject(new Error(streamError))
|
||||
else resolve()
|
||||
},
|
||||
(event) => {
|
||||
try {
|
||||
if (event?.error) {
|
||||
streamError = String(event.error)
|
||||
logger.debug('push layer error event', { error: event.error, image: pushRef })
|
||||
}
|
||||
session.push(Pushes.pushProgress, {
|
||||
|
||||
Reference in New Issue
Block a user