Add container system version with format yaml (#1353)

Fixed #1156. Introduces yaml formatting.

Signed-off-by: ChengHao Yang <17496418+tico88612@users.noreply.github.com>
This commit is contained in:
ChengHao Yang
2026-04-23 05:26:20 +08:00
committed by GitHub
parent 49e93cb1a9
commit 44754dacaf
8 changed files with 60 additions and 6 deletions
+9
View File
@@ -253,6 +253,15 @@
"version" : "2.0.0"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams.git",
"state" : {
"revision" : "deaf82e867fa2cbd3cd865978b079bfcf384ac28",
"version" : "6.2.1"
}
},
{
"identity" : "zstd",
"kind" : "remoteSourceControl",
+3
View File
@@ -60,6 +60,7 @@ let package = Package(
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.20.1"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin.git", from: "1.1.0"),
.package(url: "https://github.com/mattt/swift-toml.git", from: "2.0.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "6.2.1"),
],
targets: [
.executableTarget(
@@ -82,6 +83,7 @@ let package = Package(
"ContainerBuild",
"ContainerLog",
"ContainerResource",
"Yams",
],
path: "Tests/CLITests"
),
@@ -104,6 +106,7 @@ let package = Package(
"ContainerVersion",
"ContainerXPC",
"TerminalProgress",
"Yams",
],
path: "Sources/ContainerCommands"
),
@@ -19,4 +19,5 @@ import ArgumentParser
public enum ListFormat: String, CaseIterable, ExpressibleByArgument, Sendable {
case json
case table
case yaml
}
@@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//
import Foundation
import Yams
/// Options for JSON rendering, wrapping the knobs on `JSONEncoder`.
public struct JSONOptions: Sendable {
@@ -48,6 +49,12 @@ public enum Output {
return String(decoding: data, as: UTF8.self)
}
public static func renderYAML<T: Encodable>(_ value: T) throws -> String {
let encoder = YAMLEncoder()
let data = try encoder.encode(value)
return data
}
/// Renders a list of displayable items as a table (with header) or quiet-mode identifiers.
public static func renderList<T: ListDisplayable>(_ items: [T], quiet: Bool) -> String {
if quiet {
@@ -74,6 +81,7 @@ public enum Output {
) throws {
switch format {
case .json: try emit(renderJSON(json))
case .yaml: try emit(renderYAML(json))
case .table: emit(renderList(display, quiet: quiet))
}
}
@@ -63,6 +63,8 @@ extension Application {
printVersionTable(versions: versions)
case .json:
try Output.emit(Output.renderJSON(versions))
case .yaml:
try Output.emit(Output.renderYAML(versions))
}
}
@@ -16,6 +16,7 @@
import Foundation
import Testing
import Yams
/// Tests for `container system version` output formats and build type detection.
final class TestCLIVersion: CLITest {
@@ -26,7 +27,7 @@ final class TestCLIVersion: CLITest {
let appName: String
}
struct VersionJSON: Codable {
struct VersionOutput: Codable {
let version: String
let buildType: String
let commit: String
@@ -68,7 +69,21 @@ final class TestCLIVersion: CLITest {
#expect(status == 0, "system version --format json should succeed, stderr: \(err)")
#expect(!out.isEmpty)
let decoded = try JSONDecoder().decode([VersionJSON].self, from: data)
let decoded = try JSONDecoder().decode([VersionOutput].self, from: data)
#expect(decoded[0].appName == "container")
#expect(!decoded[0].version.isEmpty)
#expect(!decoded[0].commit.isEmpty)
let expected = try expectedBuildType()
#expect(decoded[0].buildType == expected)
}
@Test func yamlFormat() throws {
let (data, out, err, status) = try run(arguments: ["system", "version", "--format", "yaml"])
#expect(status == 0, "system version --format yaml should succeed, stderr: \(err)")
#expect(!out.isEmpty)
let decoded = try YAMLDecoder().decode([VersionOutput].self, from: data)
#expect(decoded[0].appName == "container")
#expect(!decoded[0].version.isEmpty)
#expect(!decoded[0].commit.isEmpty)
@@ -92,7 +107,7 @@ final class TestCLIVersion: CLITest {
// Validate build type via JSON to avoid parsing table text loosely
let (data, _, err, status) = try run(arguments: ["system", "version", "--format", "json"])
#expect(status == 0, "version --format json should succeed, stderr: \(err)")
let decoded = try JSONDecoder().decode([VersionJSON].self, from: data)
let decoded = try JSONDecoder().decode([VersionOutput].self, from: data)
let expected = try expectedBuildType()
#expect(decoded[0].buildType == expected, "Expected build type \(expected) but got \(decoded[0].buildType)")
@@ -250,10 +250,11 @@ struct PrintableNetworkDisplayTests {
struct ListFormatTests {
@Test
func hasJsonAndTableCases() {
#expect(ListFormat.allCases.count == 2)
func hasAllOutputFormatCases() {
#expect(ListFormat.allCases.count == 3)
#expect(ListFormat.json.rawValue == "json")
#expect(ListFormat.table.rawValue == "table")
#expect(ListFormat.yaml.rawValue == "yaml")
}
}
+16 -1
View File
@@ -1062,7 +1062,7 @@ container system version [--format <format>]
**Options**
* `--format <format>`: Output format (values: json, table; default: table)
* `--format <format>`: Output format (values: json, table, yaml; default: table)
**Table Output**
@@ -1099,6 +1099,21 @@ Backward-compatible with previous CLI-only output. Top-level fields describe the
}
```
**YAML Output**
Equivalent to the JSON output but in YAML format. Each entry in the array represents a component.
```yaml
- version: 1.2.3
buildType: debug
commit: abcdef1
appName: container
- version: 1.2.3
buildType: release
commit: 1234abc
appName: container-apiserver
```
### `container system logs`
Displays logs from the container services. You can specify a time interval or follow new logs in real time.