From 9fd15f01bf0f7caf757b66e17ac3f8e1be432bde Mon Sep 17 00:00:00 2001 From: Saehej Kang <20051028+saehejkang@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:35:30 -0700 Subject: [PATCH] [networks]: add decoder/encoder to Attachment and NetworkStatus (#1216) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Type of Change - [x] Bug fix - [ ] New feature - [x] Breaking change - [ ] Documentation update ## Motivation and Context Adds backward-compatible JSON decoding/encoding fallback for `Attachment` and `NetworkStatus` to prevent `keyNotFound` errors when communicating with older `container-apiserver` versions. Temporary fix but closes #1196 ## Testing Tests ✅ based on comment [here](https://github.com/apple/container/issues/1196#issuecomment-3902754548) --------- Co-authored-by: Ed Saipetch --- .../Network/Attachment.swift | 48 +++++++++++++++++++ .../Network/NetworkConfiguration.swift | 2 +- .../Network/NetworkState.swift | 37 ++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/Sources/ContainerResource/Network/Attachment.swift b/Sources/ContainerResource/Network/Attachment.swift index ada29d9f..bbadad75 100644 --- a/Sources/ContainerResource/Network/Attachment.swift +++ b/Sources/ContainerResource/Network/Attachment.swift @@ -51,4 +51,52 @@ public struct Attachment: Codable, Sendable { self.macAddress = macAddress self.mtu = mtu } + + enum CodingKeys: String, CodingKey { + case network + case hostname + case ipv4Address + case ipv4Gateway + case ipv6Address + case macAddress + case mtu + // TODO: retain for deserialization compatibility for now, remove later + case address + case gateway + } + + /// Create a configuration from the supplied Decoder, initializing missing + /// values where possible to reasonable defaults. + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + network = try container.decode(String.self, forKey: .network) + hostname = try container.decode(String.self, forKey: .hostname) + if let address = try? container.decode(CIDRv4.self, forKey: .ipv4Address) { + ipv4Address = address + } else { + ipv4Address = try container.decode(CIDRv4.self, forKey: .address) + } + if let gateway = try? container.decode(IPv4Address.self, forKey: .ipv4Gateway) { + ipv4Gateway = gateway + } else { + ipv4Gateway = try container.decode(IPv4Address.self, forKey: .gateway) + } + ipv6Address = try container.decodeIfPresent(CIDRv6.self, forKey: .ipv6Address) + macAddress = try container.decodeIfPresent(MACAddress.self, forKey: .macAddress) + mtu = try container.decodeIfPresent(UInt32.self, forKey: .mtu) + } + + /// Encode the configuration to the supplied Encoder. + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(network, forKey: .network) + try container.encode(hostname, forKey: .hostname) + try container.encode(ipv4Address, forKey: .ipv4Address) + try container.encode(ipv4Gateway, forKey: .ipv4Gateway) + try container.encodeIfPresent(ipv6Address, forKey: .ipv6Address) + try container.encodeIfPresent(macAddress, forKey: .macAddress) + try container.encodeIfPresent(mtu, forKey: .mtu) + } } diff --git a/Sources/ContainerResource/Network/NetworkConfiguration.swift b/Sources/ContainerResource/Network/NetworkConfiguration.swift index 2f0e108a..d5cd606e 100644 --- a/Sources/ContainerResource/Network/NetworkConfiguration.swift +++ b/Sources/ContainerResource/Network/NetworkConfiguration.swift @@ -80,7 +80,7 @@ public struct NetworkConfiguration: Codable, Sendable, Identifiable { case ipv6Subnet case labels case pluginInfo - // TODO: retain for deserialization compatability for now, remove later + // TODO: retain for deserialization compatibility for now, remove later case subnet } diff --git a/Sources/ContainerResource/Network/NetworkState.swift b/Sources/ContainerResource/Network/NetworkState.swift index db5ba7d6..2c219ea0 100644 --- a/Sources/ContainerResource/Network/NetworkState.swift +++ b/Sources/ContainerResource/Network/NetworkState.swift @@ -39,6 +39,43 @@ public struct NetworkStatus: Codable, Sendable { self.ipv4Gateway = ipv4Gateway self.ipv6Subnet = ipv6Subnet } + + enum CodingKeys: String, CodingKey { + case ipv4Subnet + case ipv4Gateway + case ipv6Subnet + // TODO: retain for deserialization compatibility for now, remove later + case address + case gateway + } + + /// Create a configuration from the supplied Decoder, initializing missing + /// values where possible to reasonable defaults. + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + if let address = try? container.decode(CIDRv4.self, forKey: .ipv4Subnet) { + ipv4Subnet = address + } else { + ipv4Subnet = try container.decode(CIDRv4.self, forKey: .address) + } + if let gateway = try? container.decode(IPv4Address.self, forKey: .ipv4Gateway) { + ipv4Gateway = gateway + } else { + ipv4Gateway = try container.decode(IPv4Address.self, forKey: .gateway) + } + ipv6Subnet = try container.decodeIfPresent(String.self, forKey: .ipv6Subnet) + .map { try CIDRv6($0) } + } + + /// Encode the configuration to the supplied Encoder. + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(ipv4Subnet, forKey: .ipv4Subnet) + try container.encode(ipv4Gateway, forKey: .ipv4Gateway) + try container.encodeIfPresent(ipv6Subnet, forKey: .ipv6Subnet) + } } /// The configuration and runtime attributes for a network.