Files
container/Tests/CLITests/Subcommands/Images/TestCLIImages.swift
J Logan 58e0ddd948 Revert inadvertent pull platform filtering from #545. (#593)
- Reverts an inadvertent "fix" from #545.
- Closes #592.

## Type of Change
- [x] Bug fix
- [ ] New feature  
- [ ] Breaking change
- [ ] Documentation update

## Motivation and Context
See #592 

## Testing
- [x] Tested locally
- [ ] Added/updated tests
- [ ] Added/updated docs
2025-09-10 10:22:55 -07:00

363 lines
13 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerClient
import ContainerizationOCI
import Foundation
import Testing
class TestCLIImagesCommand: CLITest {
func doRemoveImages(images: [String]? = nil) throws {
var args = [
"image",
"rm",
]
if let images {
args.append(contentsOf: images)
} else {
args.append("--all")
}
let (_, error, status) = try run(arguments: args)
if status != 0 {
throw CLIError.executionFailed("command failed: \(error)")
}
}
func isImagePresent(targetImage: String) throws -> Bool {
let images = try doListImages()
return images.contains(where: { image in
if image.reference == targetImage {
return true
}
return false
})
}
func doListImages() throws -> [Image] {
let (output, error, status) = try run(arguments: [
"image",
"list",
"--format",
"json",
])
if status != 0 {
throw CLIError.executionFailed("command failed: \(error)")
}
guard let jsonData = output.data(using: .utf8) else {
throw CLIError.invalidOutput("image list output invalid \(output)")
}
let decoder = JSONDecoder()
return try decoder.decode([Image].self, from: jsonData)
}
func doImageTag(image: String, newName: String) throws {
let tagArgs = [
"image",
"tag",
image,
newName,
]
let (_, error, status) = try run(arguments: tagArgs)
if status != 0 {
throw CLIError.executionFailed("command failed: \(error)")
}
}
}
extension TestCLIImagesCommand {
@Test func testPull() throws {
do {
try doPull(imageName: alpine)
let imagePresent = try isImagePresent(targetImage: alpine)
#expect(imagePresent, "expected to see \(alpine) pulled")
} catch {
Issue.record("failed to pull alpine image \(error)")
return
}
}
@Test func testPullMulti() throws {
do {
try doPull(imageName: alpine)
try doPull(imageName: busybox)
let alpinePresent = try isImagePresent(targetImage: alpine)
#expect(alpinePresent, "expected to see \(alpine) pulled")
let busyPresent = try isImagePresent(targetImage: busybox)
#expect(busyPresent, "expected to see \(busybox) pulled")
} catch {
Issue.record("failed to pull images \(error)")
return
}
}
@Test func testPullPlatform() throws {
do {
let os = "linux"
let arch = "amd64"
let pullArgs = [
"--platform",
"\(os)/\(arch)",
]
try doPull(imageName: alpine, args: pullArgs)
let output = try doInspectImages(image: alpine)
#expect(output.count == 1, "expected a single image inspect output, got \(output)")
var found = false
for v in output[0].variants {
if v.platform.os == os && v.platform.architecture == arch {
found = true
}
}
#expect(found, "expected to find image with os \(os) and architecture \(arch), instead got \(output[0])")
} catch {
Issue.record("failed to pull and inspect image \(error)")
return
}
}
@Test func testPullOsArch() throws {
do {
let os = "linux"
let arch = "amd64"
let pullArgs = [
"--os",
os,
"--arch",
arch,
]
try doPull(imageName: alpine318, args: pullArgs)
let output = try doInspectImages(image: alpine318)
#expect(output.count == 1, "expected a single image inspect output, got \(output)")
var found = false
for v in output[0].variants {
if v.platform.os == os && v.platform.architecture == arch {
found = true
}
}
#expect(found, "expected to find image with os \(os) and architecture \(arch), instead got \(output[0])")
} catch {
Issue.record("failed to pull and inspect image \(error)")
return
}
}
@Test func testPullOs() throws {
do {
let os = "linux"
let arch = Arch.hostArchitecture().rawValue
let pullArgs = [
"--os",
os,
]
try doPull(imageName: alpine318, args: pullArgs)
let output = try doInspectImages(image: alpine318)
#expect(output.count == 1, "expected a single image inspect output, got \(output)")
var found = false
for v in output[0].variants {
if v.platform.os == os && v.platform.architecture == arch {
found = true
}
}
#expect(found, "expected to find image with os \(os) and architecture \(arch), instead got \(output[0])")
} catch {
Issue.record("failed to pull and inspect image \(error)")
return
}
}
@Test func testPullArch() throws {
do {
let os = "linux"
let arch = "amd64"
let pullArgs = [
"--arch",
arch,
]
try doPull(imageName: alpine318, args: pullArgs)
let output = try doInspectImages(image: alpine318)
#expect(output.count == 1, "expected a single image inspect output, got \(output)")
var found = false
for v in output[0].variants {
if v.platform.os == os && v.platform.architecture == arch {
found = true
}
}
#expect(found, "expected to find image with os \(os) and architecture \(arch), instead got \(output[0])")
} catch {
Issue.record("failed to pull and inspect image \(error)")
return
}
}
@Test func testPullRemoveSingle() throws {
do {
try doPull(imageName: alpine)
let imagePulled = try isImagePresent(targetImage: alpine)
#expect(imagePulled, "expected to see image \(alpine) pulled")
// tag image so we can safely remove later
let alpineRef: Reference = try Reference.parse(alpine)
let alpineTagged = "\(alpineRef.name):testPullRemoveSingle"
try doImageTag(image: alpine, newName: alpineTagged)
let taggedImagePresent = try isImagePresent(targetImage: alpineTagged)
#expect(taggedImagePresent, "expected to see image \(alpineTagged) tagged")
try doRemoveImages(images: [alpineTagged])
let imageRemoved = try !isImagePresent(targetImage: alpineTagged)
#expect(imageRemoved, "expected not to see image \(alpineTagged)")
} catch {
Issue.record("failed to pull and remove image \(error)")
return
}
}
@Test func testImageTag() throws {
do {
try doPull(imageName: alpine)
let alpineRef: Reference = try Reference.parse(alpine)
let alpineTagged = "\(alpineRef.name):testImageTag"
try doImageTag(image: alpine, newName: alpineTagged)
let imagePresent = try isImagePresent(targetImage: alpineTagged)
#expect(imagePresent, "expected to see image \(alpineTagged) tagged")
} catch {
Issue.record("failed to pull and tag image \(error)")
return
}
}
@Test func testImageDefaultRegistry() throws {
do {
let defaultDomain = "ghcr.io"
let imageName = "linuxcontainers/alpine:3.20"
defer {
try? doDefaultRegistrySet(domain: "docker.io")
}
try doDefaultRegistrySet(domain: defaultDomain)
try doPull(imageName: imageName, args: ["--platform", "linux/arm64"])
guard let alpineImageDetails = try doInspectImages(image: imageName).first else {
Issue.record("alpine image not found")
return
}
#expect(alpineImageDetails.name == "\(defaultDomain)/\(imageName)")
try doImageTag(image: imageName, newName: "username/image-name:mytag")
guard let taggedImage = try doInspectImages(image: "username/image-name:mytag").first else {
Issue.record("Tagged image not found")
return
}
#expect(taggedImage.name == "\(defaultDomain)/username/image-name:mytag")
let listOutput = try doImageListQuite()
#expect(listOutput.contains("username/image-name:mytag"))
#expect(listOutput.contains(imageName))
} catch {
Issue.record("failed default registry test")
return
}
}
@Test func testImageSaveAndLoad() throws {
do {
// 1. pull image
try doPull(imageName: alpine)
try doPull(imageName: busybox)
// 2. Tag image so we can safely remove later
let alpineRef: Reference = try Reference.parse(alpine)
let alpineTagged = "\(alpineRef.name):testImageSaveAndLoad"
try doImageTag(image: alpine, newName: alpineTagged)
let alpineTaggedImagePresent = try isImagePresent(targetImage: alpineTagged)
#expect(alpineTaggedImagePresent, "expected to see image \(alpineTagged) tagged")
let busyboxRef: Reference = try Reference.parse(busybox)
let busyboxTagged = "\(busyboxRef.name):testImageSaveAndLoad"
try doImageTag(image: busybox, newName: busyboxTagged)
let busyboxTaggedImagePresent = try isImagePresent(targetImage: busyboxTagged)
#expect(busyboxTaggedImagePresent, "expected to see image \(busyboxTagged) tagged")
// 3. save the image as a tarball
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
defer {
try? FileManager.default.removeItem(at: tempDir)
}
let tempFile = tempDir.appendingPathComponent(UUID().uuidString)
let saveArgs = [
"image",
"save",
alpineTagged,
busyboxTagged,
"--output",
tempFile.path(),
]
let (_, error, status) = try run(arguments: saveArgs)
if status != 0 {
throw CLIError.executionFailed("command failed: \(error)")
}
// 4. remove the image through container
try doRemoveImages(images: [alpineTagged, busyboxTagged])
// 5. verify image is no longer present
let alpineImageRemoved = try !isImagePresent(targetImage: alpineTagged)
#expect(alpineImageRemoved, "expected image \(alpineTagged) to be removed")
let busyboxImageRemoved = try !isImagePresent(targetImage: busyboxTagged)
#expect(busyboxImageRemoved, "expected image \(busyboxTagged) to be removed")
// 6. load the tarball
let loadArgs = [
"image",
"load",
"-i",
tempFile.path(),
]
let (_, loadErr, loadStatus) = try run(arguments: loadArgs)
if loadStatus != 0 {
throw CLIError.executionFailed("command failed: \(loadErr)")
}
// 7. verify image is in the list again
let alpineImagePresent = try isImagePresent(targetImage: alpineTagged)
#expect(alpineImagePresent, "expected \(alpineTagged) to be present")
let busyboxImagePresent = try isImagePresent(targetImage: busyboxTagged)
#expect(busyboxImagePresent, "expected \(busyboxTagged) to be present")
} catch {
Issue.record("failed to save and load image \(error)")
return
}
}
}