Skip to content

Commit 197fb15

Browse files
Fix --tag parameter to properly push tags and update latest (#168)
Fix --tag parameter to properly push tags and update latest Motivation ---------- The --tag parameter was accepted by containertool but had no effect on the actual tags pushed to the registry. When running: swift package build-container-image --repository ghcr.io/org/image --tag v1.0.0 The image was only pushed to `ghcr.io/org/image:latest` (the default reference from --repository), while the output misleadingly reported `ghcr.io/org/image:v1.0.0`. This made it impossible to publish versioned releases using the --tag flag. Modifications ------------- - Collect all tags to publish: the --tag value (if provided) plus the default reference from --repository (typically 'latest') - Push the manifest and index to each tag in the collection - Skip duplicate pushes when --tag matches the repository reference - Return the --tag reference (if provided) as the primary result Result ------ When --tag is provided, the image is pushed to both the specified tag AND the default reference: --tag v1.0.0 → pushes to v1.0.0 and latest --tag latest → pushes to latest (once, no duplicate) (no --tag) → pushes to latest (existing behavior) Test Plan --------- 1. Build with --tag flag: `swift package build-container-image --repository ghcr.io/org/image --tag v1.0.0` 2. Verify both tags exist: `docker pull ghcr.io/org/image:v1.0.0` and `docker pull ghcr.io/org/image:latest` 3. Build with --tag latest: verify no errors and single push 4. Build without --tag: verify existing behavior unchanged
1 parent e10069f commit 197fb15

File tree

1 file changed

+43
-41
lines changed

1 file changed

+43
-41
lines changed

Sources/containertool/Extensions/RegistryClient+publish.swift

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -179,56 +179,58 @@ func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(
179179
)
180180
}
181181

182-
// MARK: Upload application manifest
183-
184-
let manifestDescriptor = try await destination.putManifest(
185-
repository: destinationImage.repository,
186-
reference: destinationImage.reference,
187-
manifest: manifest
188-
)
189-
190-
if verbose {
191-
log("manifest: \(manifestDescriptor.digest) (\(manifestDescriptor.size) bytes)")
182+
// Determine the tags to push. Always include the reference from --repository (defaults to 'latest').
183+
// If --tag is provided and differs from the repository reference, push to both.
184+
var tagsToPublish: [any ImageReference.Reference] = [destinationImage.reference]
185+
if let tag {
186+
let tagReference = try ImageReference.Tag(tag)
187+
// Avoid duplicates if --tag matches the reference already in --repository
188+
if "\(tagReference)" != "\(destinationImage.reference)" {
189+
tagsToPublish.insert(tagReference, at: 0)
190+
}
192191
}
193192

194-
// MARK: Create application index
193+
// MARK: Upload application manifest and index for each tag
195194

196-
let index = ImageIndex(
197-
schemaVersion: 2,
198-
mediaType: "application/vnd.oci.image.index.v1+json",
199-
manifests: [
200-
ContentDescriptor(
201-
mediaType: manifestDescriptor.mediaType,
202-
digest: manifestDescriptor.digest,
203-
size: Int64(manifestDescriptor.size),
204-
platform: .init(architecture: architecture, os: os)
205-
)
206-
]
207-
)
195+
for tagReference in tagsToPublish {
196+
let manifestDescriptor = try await destination.putManifest(
197+
repository: destinationImage.repository,
198+
reference: tagReference,
199+
manifest: manifest
200+
)
208201

209-
// MARK: Upload application manifest
202+
if verbose {
203+
log("manifest (\(tagReference)): \(manifestDescriptor.digest) (\(manifestDescriptor.size) bytes)")
204+
}
210205

211-
let indexDescriptor = try await destination.putIndex(
212-
repository: destinationImage.repository,
213-
reference: destinationImage.reference,
214-
index: index
215-
)
206+
let index = ImageIndex(
207+
schemaVersion: 2,
208+
mediaType: "application/vnd.oci.image.index.v1+json",
209+
manifests: [
210+
ContentDescriptor(
211+
mediaType: manifestDescriptor.mediaType,
212+
digest: manifestDescriptor.digest,
213+
size: Int64(manifestDescriptor.size),
214+
platform: .init(architecture: architecture, os: os)
215+
)
216+
]
217+
)
216218

217-
if verbose {
218-
log("index: \(indexDescriptor.digest) (\(indexDescriptor.size) bytes)")
219-
}
219+
let indexDescriptor = try await destination.putIndex(
220+
repository: destinationImage.repository,
221+
reference: tagReference,
222+
index: index
223+
)
220224

221-
// Use the index digest if the user did not provide a human-readable tag
222-
// To support multiarch images, we should also create an an index pointing to
223-
// this manifest.
224-
let reference: ImageReference.Reference
225-
if let tag {
226-
reference = try ImageReference.Tag(tag)
227-
} else {
228-
reference = try ImageReference.Digest(indexDescriptor.digest)
225+
if verbose {
226+
log("index (\(tagReference)): \(indexDescriptor.digest) (\(indexDescriptor.size) bytes)")
227+
}
229228
}
230229

230+
// Return the primary tag (--tag if provided, otherwise the repository reference)
231231
var result = destinationImage
232-
result.reference = reference
232+
if let tag {
233+
result.reference = try ImageReference.Tag(tag)
234+
}
233235
return result
234236
}

0 commit comments

Comments
 (0)