diff --git a/Package.resolved b/Package.resolved index 3b29d5751..0eed16103 100644 --- a/Package.resolved +++ b/Package.resolved @@ -226,6 +226,15 @@ "version" : "1.6.2" } }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "deaf82e867fa2cbd3cd865978b079bfcf384ac28", + "version" : "6.2.1" + } + }, { "identity" : "zstd", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 1bbb3c309..52c8d5ace 100644 --- a/Package.swift +++ b/Package.swift @@ -57,6 +57,7 @@ let package = Package( .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.26.0"), .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/jpsim/Yams.git", from: "6.2.1"), ], targets: [ .executableTarget( @@ -79,6 +80,7 @@ let package = Package( "ContainerBuild", "ContainerLog", "ContainerResource", + "Yams", ], path: "Tests/CLITests" ), @@ -101,6 +103,7 @@ let package = Package( "ContainerVersion", "ContainerXPC", "TerminalProgress", + "Yams", ], path: "Sources/ContainerCommands" ), diff --git a/Sources/ContainerCommands/Application.swift b/Sources/ContainerCommands/Application.swift index c0733a0be..15da8499c 100644 --- a/Sources/ContainerCommands/Application.swift +++ b/Sources/ContainerCommands/Application.swift @@ -248,6 +248,7 @@ extension Application { public enum ListFormat: String, CaseIterable, ExpressibleByArgument { case json case table + case yaml } func isTranslated() throws -> Bool { diff --git a/Sources/ContainerCommands/System/SystemVersion.swift b/Sources/ContainerCommands/System/SystemVersion.swift index 4e4e5d68f..14039a4b2 100644 --- a/Sources/ContainerCommands/System/SystemVersion.swift +++ b/Sources/ContainerCommands/System/SystemVersion.swift @@ -18,6 +18,7 @@ import ArgumentParser import ContainerAPIClient import ContainerVersion import Foundation +import Yams extension Application { public struct SystemVersion: AsyncLoggableCommand { @@ -63,6 +64,8 @@ extension Application { printVersionTable(versions: versions) case .json: try printVersionJSON(versions: versions) + case .yaml: + try printVersionYAML(versions: versions) } } @@ -78,6 +81,11 @@ extension Application { let data = try JSONEncoder().encode(versions) print(String(data: data, encoding: .utf8) ?? "[]") } + + private func printVersionYAML(versions: [VersionInfo]) throws { + let data = try YAMLEncoder().encode(versions) + print(data) + } } public struct VersionInfo: Codable { diff --git a/Tests/CLITests/Subcommands/System/TestCLIVersion.swift b/Tests/CLITests/Subcommands/System/TestCLIVersion.swift index f3985f0e6..271612477 100644 --- a/Tests/CLITests/Subcommands/System/TestCLIVersion.swift +++ b/Tests/CLITests/Subcommands/System/TestCLIVersion.swift @@ -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)") diff --git a/docs/command-reference.md b/docs/command-reference.md index 7aea2a329..2a151d4a6 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -1033,7 +1033,7 @@ container system version [--format ] **Options** -* `--format `: Output format (values: json, table; default: table) +* `--format `: Output format (values: json, table, yaml; default: table) **Table Output** @@ -1070,6 +1070,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.