-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathImageList.swift
More file actions
215 lines (186 loc) · 8.31 KB
/
Copy pathImageList.swift
File metadata and controls
215 lines (186 loc) · 8.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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 ArgumentParser
import ContainerAPIClient
import ContainerPersistence
import ContainerPlugin
import Containerization
import ContainerizationError
import ContainerizationOCI
import Foundation
import SwiftProtobuf
extension Application {
public struct ImageList: AsyncLoggableCommand {
public init() {}
public static let configuration = CommandConfiguration(
commandName: "list",
abstract: "List images",
aliases: ["ls"])
@Option(name: .long, help: "Format of the output")
var format: ListFormat = .table
@Flag(name: .shortAndLong, help: "Only output the image name")
var quiet = false
@Flag(name: .shortAndLong, help: "Verbose output")
var verbose = false
@OptionGroup
public var logOptions: Flags.Logging
public mutating func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try ConfigurationLoader.load()
try Self.validate(format: format, quiet: quiet, verbose: verbose)
var images = try await ClientImage.list().filter { img in
!Utility.isInfraImage(name: img.reference, builderImage: containerSystemConfig.build.image, initImage: containerSystemConfig.vminit.image)
}
images.sort { $0.reference < $1.reference }
if format == .json {
try await Self.emitJSON(images: images)
return
}
if quiet {
for image in images {
let processedReferenceString = try ClientImage.denormalizeReference(image.reference, containerSystemConfig: containerSystemConfig)
print(processedReferenceString)
}
return
}
if verbose {
let items = try await Self.buildVerboseItems(images: images, containerSystemConfig: containerSystemConfig)
Output.emit(Output.renderTable(items))
return
}
let items = try await Self.buildTableItems(images: images, containerSystemConfig: containerSystemConfig)
Output.emit(Output.renderTable(items))
}
private static func validate(format: ListFormat, quiet: Bool, verbose: Bool) throws {
if quiet && verbose {
throw ContainerizationError(.invalidArgument, message: "cannot use flag --quiet and --verbose together")
}
let modifier = quiet || verbose
if modifier && format == .json {
throw ContainerizationError(.invalidArgument, message: "cannot use flag --quiet or --verbose along with --format json")
}
}
private static func emitJSON(images: [ClientImage]) async throws {
let formatter = ByteCountFormatter()
var printableImages: [PrintableImage] = []
for image in images {
let size = try await ClientImage.getFullImageSize(image: image)
let formattedSize = formatter.string(fromByteCount: size)
printableImages.append(
PrintableImage(reference: image.reference, fullSize: formattedSize, descriptor: image.descriptor)
)
}
try Output.emit(Output.renderJSON(printableImages))
}
private static func buildTableItems(images: [ClientImage], containerSystemConfig: ContainerSystemConfig) async throws -> [ImageRow] {
var items: [ImageRow] = []
for image in images {
let processedReferenceString = try ClientImage.denormalizeReference(image.reference, containerSystemConfig: containerSystemConfig)
let reference = try ContainerizationOCI.Reference.parse(processedReferenceString)
let digest = try await image.resolved().digest
items.append(
ImageRow(
name: reference.name,
tag: reference.tag ?? "<none>",
trimmedDigest: Utility.trimDigest(digest: digest)
))
}
return items
}
private static func buildVerboseItems(images: [ClientImage], containerSystemConfig: ContainerSystemConfig) async throws -> [VerboseImageRow] {
let formatter = ByteCountFormatter()
var items: [VerboseImageRow] = []
for image in images {
let imageDigest = try await image.resolved().digest
let processedReferenceString = try ClientImage.denormalizeReference(image.reference, containerSystemConfig: containerSystemConfig)
let reference = try ContainerizationOCI.Reference.parse(processedReferenceString)
for descriptor in try await image.index().manifests {
if let referenceType = descriptor.annotations?["vnd.docker.reference.type"],
referenceType == "attestation-manifest"
{
continue
}
guard let platform = descriptor.platform else {
continue
}
var config: ContainerizationOCI.Image
do {
config = try await image.config(for: platform)
} catch {
continue
}
let created = config.created ?? ""
let sizes = try await image.getImageSizes(platform: platform)
let size: Int64 = sizes.snapshotSize > 0 ? Int64(sizes.snapshotSize) : sizes.ociImageSize
let formattedSize = formatter.string(fromByteCount: size)
items.append(
VerboseImageRow(
name: reference.name,
tag: reference.tag ?? "<none>",
indexDigest: Utility.trimDigest(digest: imageDigest),
os: platform.os,
arch: platform.architecture,
variant: platform.variant ?? "",
fullSize: formattedSize,
created: created,
manifestDigest: Utility.trimDigest(digest: descriptor.digest)
))
}
}
return items
}
struct PrintableImage: Codable {
let reference: String
let fullSize: String
let descriptor: Descriptor
}
}
}
private struct ImageRow: ListDisplayable {
let name: String
let tag: String
let trimmedDigest: String
static var tableHeader: [String] {
["NAME", "TAG", "DIGEST"]
}
var tableRow: [String] {
[name, tag, trimmedDigest]
}
// Required by ListDisplayable but unused — ImageList handles quiet mode
// separately to avoid expensive digest resolution.
var quietValue: String {
name
}
}
private struct VerboseImageRow: ListDisplayable {
let name: String
let tag: String
let indexDigest: String
let os: String
let arch: String
let variant: String
let fullSize: String
let created: String
let manifestDigest: String
static var tableHeader: [String] {
["NAME", "TAG", "INDEX DIGEST", "OS", "ARCH", "VARIANT", "FULL SIZE", "CREATED", "MANIFEST DIGEST"]
}
var tableRow: [String] {
[name, tag, indexDigest, os, arch, variant, fullSize, created, manifestDigest]
}
var quietValue: String {
name
}
}