Skip to content

Commit 84ee9cc

Browse files
authored
Merge branch 'main' into dependabot/github_actions/github-actions-e6ee9d7de3
2 parents 2d5225a + d872270 commit 84ee9cc

6 files changed

Lines changed: 79 additions & 6 deletions

File tree

Sources/ContainerCommands/Image/ImageList.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ extension Application {
4242
}
4343

4444
static func createVerboseHeader() -> [[String]] {
45-
[["NAME", "TAG", "INDEX DIGEST", "OS", "ARCH", "VARIANT", "SIZE", "CREATED", "MANIFEST DIGEST"]]
45+
[["NAME", "TAG", "INDEX DIGEST", "OS", "ARCH", "VARIANT", "FULL SIZE", "CREATED", "MANIFEST DIGEST"]]
4646
}
4747

4848
static func printImagesVerbose(images: [ClientImage]) async throws {
@@ -108,7 +108,17 @@ extension Application {
108108
}
109109

110110
if format == .json {
111-
let data = try JSONEncoder().encode(images.map { $0.description })
111+
var printableImages: [PrintableImage] = []
112+
for image in images {
113+
let formatter = ByteCountFormatter()
114+
let size = try await ClientImage.getFullImageSize(image: image)
115+
let formattedSize = formatter.string(fromByteCount: size)
116+
117+
printableImages.append(
118+
PrintableImage(reference: image.reference, fullSize: formattedSize, descriptor: image.descriptor)
119+
)
120+
}
121+
let data = try JSONEncoder().encode(printableImages)
112122
print(String(data: data, encoding: .utf8)!)
113123
return
114124
}
@@ -157,6 +167,18 @@ extension Application {
157167
}
158168
try await printImages(images: images, format: options.format, options: options)
159169
}
170+
171+
struct PrintableImage: Codable {
172+
let reference: String
173+
let fullSize: String
174+
let descriptor: Descriptor
175+
176+
init(reference: String, fullSize: String, descriptor: Descriptor) {
177+
self.reference = reference
178+
self.fullSize = fullSize
179+
self.descriptor = descriptor
180+
}
181+
}
160182
}
161183

162184
public struct ImageList: AsyncLoggableCommand {

Sources/ContainerResource/Network/NetworkMode.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@
1414
// limitations under the License.
1515
//===----------------------------------------------------------------------===//
1616

17-
import ArgumentParser
18-
1917
/// Networking mode that applies to client containers.
20-
public enum NetworkMode: String, Codable, Sendable, ExpressibleByArgument {
18+
public enum NetworkMode: String, Codable, Sendable {
2119
/// NAT networking mode.
2220
/// Containers do not have routable IPs, and the host performs network
2321
/// address translation to allow containers to reach external services.

Sources/Helpers/NetworkVmnet/NetworkVmnetHelper+Start.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ enum Variant: String, ExpressibleByArgument {
2929
case allocationOnly
3030
}
3131

32+
extension NetworkMode: ExpressibleByArgument {}
33+
3234
extension NetworkVmnetHelper {
3335
struct Start: AsyncParsableCommand {
3436
static let configuration = CommandConfiguration(

Sources/Services/ContainerAPIService/Client/ClientImage.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,33 @@ extension ClientImage {
194194
return found
195195
}
196196

197+
/// Returns the total size of an image in bytes.
198+
/// - Parameter image: The image to get the size for.
199+
/// - Returns: The full image size in bytes.
200+
/// - Throws: An error if the image cannot be retrieved.
201+
public static func getFullImageSize(image: ClientImage) async throws -> Int64 {
202+
for descriptor in try await image.index().manifests {
203+
if let referenceType = descriptor.annotations?["vnd.docker.reference.type"],
204+
referenceType == "attestation-manifest"
205+
{
206+
continue
207+
}
208+
209+
guard let platform = descriptor.platform else {
210+
continue
211+
}
212+
213+
do {
214+
let manifest = try await image.manifest(for: platform)
215+
return
216+
descriptor.size + manifest.config.size + manifest.layers.reduce(0) { $0 + $1.size }
217+
} catch {
218+
continue
219+
}
220+
}
221+
return 0
222+
}
223+
197224
private static func _search(reference: String, in all: [ClientImage]) throws -> ClientImage? {
198225
let locallyBuiltImage = try {
199226
// Check if we have an image whose index descriptor contains the image name

Tests/CLITests/Subcommands/Images/TestCLIImagesCommand.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,30 @@ class TestCLIImagesCommand: CLITest {
505505
}
506506
}
507507

508+
@Test func testImageFullSizeFieldExists() throws {
509+
// 1. pull image
510+
try doPull(imageName: alpine)
511+
512+
// 2. run the image ls command
513+
let (_, output, error, status) = try run(arguments: ["image", "ls", "--format", "json"])
514+
if status != 0 {
515+
throw CLIError.executionFailed("failed to list images: \(error)")
516+
}
517+
518+
// 3. parse the json output
519+
guard let data = output.data(using: .utf8),
520+
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]],
521+
let image = json.first
522+
else {
523+
Issue.record("failed to parse JSON output or no images found: \(output)")
524+
return
525+
}
526+
527+
// 4. check that the output has a non-empty 'fullSize' field
528+
let size = image["fullSize"] as? String ?? ""
529+
#expect(!size.isEmpty, "expected image to have non-empty 'fullSize' field: \(image)")
530+
}
531+
508532
private func addInvalidMemberToTar(tarPath: String, maliciousFilename: String) throws {
509533
// Create a malicious entry with path traversal
510534
let evilEntryName = "../../../../../../../../../../../tmp/\(maliciousFilename)"

docs/command-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ No options.
432432

433433
### `container image list (ls)`
434434

435-
Lists local images. Verbose output provides additional details such as image ID, creation time and size; JSON output provides the same data in machine-readable form.
435+
Lists local images. Verbose output provides additional details such as image ID, creation time and full size; JSON output provides the same data in machine-readable form.
436436

437437
**Usage**
438438

0 commit comments

Comments
 (0)