diff --git a/Sources/ContainerClient/Flags.swift b/Sources/ContainerClient/Flags.swift index 4a5cfd27..28e210fa 100644 --- a/Sources/ContainerClient/Flags.swift +++ b/Sources/ContainerClient/Flags.swift @@ -1,5 +1,5 @@ //===----------------------------------------------------------------------===// -// Copyright © 2025 Apple Inc. and the container project authors. +// Copyright © 2025-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. @@ -29,7 +29,7 @@ public struct Flags { public struct Process: ParsableArguments { public init() {} - @Option(name: .shortAndLong, help: "Set environment variables (format: key=value)") + @Option(name: .shortAndLong, help: "Set environment variables (key=value, or just key to inherit from host)") public var env: [String] = [] @Option( diff --git a/Sources/ContainerClient/Parser.swift b/Sources/ContainerClient/Parser.swift index 5397c06a..3613b60a 100644 --- a/Sources/ContainerClient/Parser.swift +++ b/Sources/ContainerClient/Parser.swift @@ -1,5 +1,5 @@ //===----------------------------------------------------------------------===// -// Copyright © 2025 Apple Inc. and the container project authors. +// Copyright © 2025-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. @@ -192,8 +192,9 @@ public struct Parser { var envVar: [String] = [] for env in envList { var env = env - let parts = env.split(separator: "=", maxSplits: 2) - if parts.count == 1 { + // Only inherit from host if no "=" is present (e.g., "--env VAR") + // "VAR=" should set an explicit empty value, not inherit. + if !env.contains("=") { guard let val = ProcessInfo.processInfo.environment[env] else { continue } diff --git a/Tests/ContainerClientTests/ParserTest.swift b/Tests/ContainerClientTests/ParserTest.swift index 961ae6fb..334a2f64 100644 --- a/Tests/ContainerClientTests/ParserTest.swift +++ b/Tests/ContainerClientTests/ParserTest.swift @@ -1,5 +1,5 @@ //===----------------------------------------------------------------------===// -// Copyright © 2025 Apple Inc. and the container project authors. +// Copyright © 2025-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. @@ -423,6 +423,50 @@ struct ParserTest { } } + // MARK: - Environment Variable Tests + + @Test + func testEnvExplicitValue() throws { + let result = Parser.env(envList: ["FOO=bar", "BAZ=qux"]) + #expect(result == ["FOO=bar", "BAZ=qux"]) + } + + @Test + func testEnvImplicitInheritance() throws { + guard let homeValue = ProcessInfo.processInfo.environment["PATH"] else { + Issue.record("PATH environment variable not set") + return + } + + let result = Parser.env(envList: ["PATH"]) + #expect(result == ["PATH=\(homeValue)"]) + } + + @Test + func testEnvImplicitUndefinedVariable() throws { + // A variable that doesn't exist should be silently skipped + let result = Parser.env(envList: ["THIS_VAR_DEFINITELY_DOES_NOT_EXIST_12345"]) + #expect(result.isEmpty) + } + + @Test + func testEnvMixedExplicitAndImplicit() throws { + guard let homeValue = ProcessInfo.processInfo.environment["HOME"] else { + Issue.record("HOME environment variable not set") + return + } + + let result = Parser.env(envList: ["FOO=bar", "HOME", "BAZ=qux"]) + #expect(result == ["FOO=bar", "HOME=\(homeValue)", "BAZ=qux"]) + } + + @Test + func testEnvEmptyValue() throws { + // Explicit empty value should be preserved + let result = Parser.env(envList: ["EMPTY="]) + #expect(result == ["EMPTY="]) + } + private func tmpFileWithContent(_ content: String) throws -> URL { let tempDir = FileManager.default.temporaryDirectory let tempFile = tempDir.appendingPathComponent("envfile-test-\(UUID().uuidString)")