From a18abb2c1b4d55645938319054b547037471677f Mon Sep 17 00:00:00 2001 From: Anas Khan <83116240+anxkhn@users.noreply.github.com> Date: Fri, 3 Jul 2026 08:03:06 +0530 Subject: [PATCH] Fix container machine setup script being dropped from artifact. When fetching container machine resources from an OCI artifact, the setup script layer was decoded separately and then merged into the config with `if var resources, let setupScript`. Because MachineResources is a value type, that binding mutated a throwaway copy, so the fetched setup script was silently discarded and the returned resources kept a nil setupScript. As a result MachineBundle fell back to the default create-user.sh instead of the per-image script. Merge through the optional so the script survives on the returned value, and add unit tests for the merge behavior. --- Package.swift | 6 +++ .../Client/MachineClient.swift | 14 +++++-- .../MachineClientMergeTests.swift | 39 +++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 Tests/MachineAPIClientTests/MachineClientMergeTests.swift diff --git a/Package.swift b/Package.swift index bd46c8510..210d34b57 100644 --- a/Package.swift +++ b/Package.swift @@ -583,6 +583,12 @@ let package = Package( ], path: "Sources/Services/MachineAPIService/Client" ), + .testTarget( + name: "MachineAPIClientTests", + dependencies: [ + "MachineAPIClient" + ] + ), .target( name: "MachineAPIService", dependencies: [ diff --git a/Sources/Services/MachineAPIService/Client/MachineClient.swift b/Sources/Services/MachineAPIService/Client/MachineClient.swift index 7f3167ff4..e8d7da5c0 100644 --- a/Sources/Services/MachineAPIService/Client/MachineClient.swift +++ b/Sources/Services/MachineAPIService/Client/MachineClient.swift @@ -390,10 +390,18 @@ extension MachineClient { } } - if var resources, let setupScript { - resources.setupScript = setupScript - } + return merge(resources: resources, setupScript: setupScript) + } + /// Merge a separately-fetched setup script into the decoded machine resources. + /// + /// `MachineResources` is a value type, so mutating a copy bound via `if var` + /// would not affect the returned value. Assign through the optional instead. + static func merge(resources: MachineResources?, setupScript: String?) -> MachineResources? { + var resources = resources + if let setupScript { + resources?.setupScript = setupScript + } return resources } } diff --git a/Tests/MachineAPIClientTests/MachineClientMergeTests.swift b/Tests/MachineAPIClientTests/MachineClientMergeTests.swift new file mode 100644 index 000000000..24f80acd3 --- /dev/null +++ b/Tests/MachineAPIClientTests/MachineClientMergeTests.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// Copyright © 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 Testing + +@testable import MachineAPIClient + +struct MachineClientMergeTests { + @Test func mergeAppliesSetupScriptToResources() throws { + let resources = MachineResources(schemaVersion: 1, shell: "/bin/sh") + let merged = MachineClient.merge(resources: resources, setupScript: "echo hello") + #expect(merged?.setupScript == "echo hello") + #expect(merged?.shell == "/bin/sh") + } + + @Test func mergeLeavesResourcesUnchangedWhenNoSetupScript() throws { + let resources = MachineResources(schemaVersion: 1, setupScript: "existing") + let merged = MachineClient.merge(resources: resources, setupScript: nil) + #expect(merged?.setupScript == "existing") + } + + @Test func mergeReturnsNilWhenNoResources() throws { + let merged = MachineClient.merge(resources: nil, setupScript: "echo hello") + #expect(merged == nil) + } +}