From 3a9aed8669661b104fd29bd8ba9599fcefc0e108 Mon Sep 17 00:00:00 2001 From: Yibo Zhuang Date: Thu, 18 Dec 2025 23:17:50 -0800 Subject: [PATCH] Add custom decoder to Linux struct for optional fields Allows decoding of minimal OCI specs with empty linux objects by providing default values for missing fields. Also added unit test to ensure that empty Linux struct {} works correctly with the fix. --- Sources/ContainerizationOCI/Spec.swift | 53 +++++++++++++++++++ .../OCISpecTests.swift | 22 ++++++++ 2 files changed, 75 insertions(+) diff --git a/Sources/ContainerizationOCI/Spec.swift b/Sources/ContainerizationOCI/Spec.swift index 5171925c..5c89c3ea 100644 --- a/Sources/ContainerizationOCI/Spec.swift +++ b/Sources/ContainerizationOCI/Spec.swift @@ -426,6 +426,22 @@ public struct Linux: Codable, Sendable { public var mountLabel: String public var personality: LinuxPersonality? + public enum CodingKeys: String, CodingKey { + case uidMappings + case gidMappings + case sysctl + case resources + case cgroupsPath + case namespaces + case devices + case seccomp + case rootfsPropagation + case maskedPaths + case readonlyPaths + case mountLabel + case personality + } + public init( uidMappings: [LinuxIDMapping] = [], gidMappings: [LinuxIDMapping] = [], @@ -455,6 +471,43 @@ public struct Linux: Codable, Sendable { self.mountLabel = mountLabel self.personality = personality } + + public init(from decoder: Decoder) throws { + self.init() + + let container = try decoder.container(keyedBy: CodingKeys.self) + if let uidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .uidMappings) { + self.uidMappings = uidMappings + } + if let gidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .gidMappings) { + self.gidMappings = gidMappings + } + self.sysctl = try container.decodeIfPresent([String: String].self, forKey: .sysctl) + self.resources = try container.decodeIfPresent(LinuxResources.self, forKey: .resources) + if let cgroupsPath = try container.decodeIfPresent(String.self, forKey: .cgroupsPath) { + self.cgroupsPath = cgroupsPath + } + if let namespaces = try container.decodeIfPresent([LinuxNamespace].self, forKey: .namespaces) { + self.namespaces = namespaces + } + if let devices = try container.decodeIfPresent([LinuxDevice].self, forKey: .devices) { + self.devices = devices + } + self.seccomp = try container.decodeIfPresent(LinuxSeccomp.self, forKey: .seccomp) + if let rootfsPropagation = try container.decodeIfPresent(String.self, forKey: .rootfsPropagation) { + self.rootfsPropagation = rootfsPropagation + } + if let maskedPaths = try container.decodeIfPresent([String].self, forKey: .maskedPaths) { + self.maskedPaths = maskedPaths + } + if let readonlyPaths = try container.decodeIfPresent([String].self, forKey: .readonlyPaths) { + self.readonlyPaths = readonlyPaths + } + if let mountLabel = try container.decodeIfPresent(String.self, forKey: .mountLabel) { + self.mountLabel = mountLabel + } + self.personality = try container.decodeIfPresent(LinuxPersonality.self, forKey: .personality) + } } public struct LinuxNamespace: Codable, Sendable { diff --git a/Tests/ContainerizationOCITests/OCISpecTests.swift b/Tests/ContainerizationOCITests/OCISpecTests.swift index 3507abff..9218bfdf 100644 --- a/Tests/ContainerizationOCITests/OCISpecTests.swift +++ b/Tests/ContainerizationOCITests/OCISpecTests.swift @@ -143,4 +143,26 @@ struct OCISpecTests { #expect(decodedSpec.uidMappings == nil) #expect(decodedSpec.gidMappings == nil) } + + @Test func minimalCapabilitiesDecode() throws { + let minCapabilitiesSpec = + """ + { + "ociVersion": "1.1.0", + "capabilities": { + "permitted": [ + "CAP_SYS_ADMIN" + ] + }, + "linux": {} + } + """ + + guard let data = minCapabilitiesSpec.data(using: .utf8) else { + Issue.record("test capabilities spec is not valid: \(minCapabilitiesSpec)") + return + } + + let _ = try JSONDecoder().decode(ContainerizationOCI.Spec.self, from: data) + } }