diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml index 4e7d6b95..d2912ab4 100644 --- a/.github/workflows/common.yml +++ b/.github/workflows/common.yml @@ -67,7 +67,6 @@ jobs: - name: Test the container project run: | - launchctl setenv HTTP_PROXY $HTTP_PROXY APP_ROOT=$(mktemp -d -p "${RUNNER_TEMP}") trap 'rm -rf "${APP_ROOT}"; echo Removing data directory ${APP_ROOT}' EXIT echo "Created data directory ${APP_ROOT}" diff --git a/Package.resolved b/Package.resolved index 6738b8e3..2ae54a2b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e8a26d708e3d286b0e29d74d94b6ff1539848a7a9fd209a230faedae5e285003", + "originHash" : "ee0892935fd158d90844c467fb84568e8fd24f3fe7b04d49504fdf99e6322df6", "pins" : [ { "identity" : "async-http-client", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/containerization.git", "state" : { - "revision" : "992ed9f5aa3b0e875ef8ac6b605a4c352218463b", - "version" : "0.9.1" + "revision" : "d0383eb5bf97f27bceb51538597194b020bc8945", + "version" : "0.10.0" } }, { diff --git a/Package.swift b/Package.swift index 274c3452..679c12f0 100644 --- a/Package.swift +++ b/Package.swift @@ -23,7 +23,7 @@ import PackageDescription let releaseVersion = ProcessInfo.processInfo.environment["RELEASE_VERSION"] ?? "0.0.0" let gitCommit = ProcessInfo.processInfo.environment["GIT_COMMIT"] ?? "unspecified" let builderShimVersion = "0.6.1" -let scVersion = "0.9.1" +let scVersion = "0.10.0" let package = Package( name: "container", diff --git a/Sources/ContainerClient/FileDownloader.swift b/Sources/ContainerClient/FileDownloader.swift index 2de069e4..7ebd7ffa 100644 --- a/Sources/ContainerClient/FileDownloader.swift +++ b/Sources/ContainerClient/FileDownloader.swift @@ -49,24 +49,20 @@ public struct FileDownloader { } }) - let client = FileDownloader.createClient() + let client = FileDownloader.createClient(url: url) _ = try await client.execute(request: request, delegate: delegate).get() try await client.shutdown() } - private static func createClient() -> HTTPClient { + private static func createClient(url: URL) -> HTTPClient { var httpConfiguration = HTTPClient.Configuration() - let proxyConfig: HTTPClient.Configuration.Proxy? = { - let proxyEnv = ProcessInfo.processInfo.environment["HTTP_PROXY"] - guard let proxyEnv else { - return nil + if let host = url.host { + let proxyURL = ProxyUtils.proxyFromEnvironment(scheme: url.scheme, host: host) + if let proxyURL, let proxyHost = proxyURL.host { + httpConfiguration.proxy = HTTPClient.Configuration.Proxy.server(host: proxyHost, port: proxyURL.port ?? 8080) } - guard let url = URL(string: proxyEnv), let host = url.host(), let port = url.port else { - return nil - } - return .server(host: host, port: port) - }() - httpConfiguration.proxy = proxyConfig + } + return HTTPClient(eventLoopGroupProvider: .singleton, configuration: httpConfiguration) } } diff --git a/Sources/ContainerCommands/System/SystemStart.swift b/Sources/ContainerCommands/System/SystemStart.swift index 4634a8aa..57debedb 100644 --- a/Sources/ContainerCommands/System/SystemStart.swift +++ b/Sources/ContainerCommands/System/SystemStart.swift @@ -69,9 +69,8 @@ extension Application { args.append("start") let apiServerDataUrl = appRoot.appending(path: "apiserver") try! FileManager.default.createDirectory(at: apiServerDataUrl, withIntermediateDirectories: true) - var env = ProcessInfo.processInfo.environment.filter { key, _ in - key.hasPrefix("CONTAINER_") - } + + var env = PluginLoader.filterEnvironment() env[ApplicationRoot.environmentName] = appRoot.path(percentEncoded: false) env[InstallRoot.environmentName] = installRoot.path(percentEncoded: false) diff --git a/Sources/ContainerPlugin/PluginLoader.swift b/Sources/ContainerPlugin/PluginLoader.swift index 5d352010..5dd7d8b6 100644 --- a/Sources/ContainerPlugin/PluginLoader.swift +++ b/Sources/ContainerPlugin/PluginLoader.swift @@ -196,6 +196,12 @@ extension PluginLoader { } extension PluginLoader { + public static let proxyKeys = Set([ + "http_proxy", "HTTP_PROXY", + "https_proxy", "HTTPS_PROXY", + "no_proxy", "NO_PROXY", + ]) + public func registerWithLaunchd( plugin: Plugin, pluginStateRoot: URL? = nil, @@ -212,9 +218,8 @@ extension PluginLoader { log?.info("Registering plugin", metadata: ["id": "\(id)"]) let rootURL = pluginStateRoot ?? self.pluginResourceRoot.appending(path: plugin.name) try FileManager.default.createDirectory(at: rootURL, withIntermediateDirectories: true) - var env = ProcessInfo.processInfo.environment.filter { key, _ in - key.hasPrefix("CONTAINER_") - } + + var env = Self.filterEnvironment() env[ApplicationRoot.environmentName] = appRoot.path(percentEncoded: false) env[InstallRoot.environmentName] = installRoot.path(percentEncoded: false) @@ -244,4 +249,13 @@ extension PluginLoader { log?.info("Deregistering plugin", metadata: ["id": "\(plugin.getLaunchdLabel())"]) try ServiceManager.deregister(fullServiceLabel: label) } + + public static func filterEnvironment( + env: [String: String] = ProcessInfo.processInfo.environment, + additionalAllowKeys: Set = Self.proxyKeys + ) -> [String: String] { + env.filter { key, _ in + key.hasPrefix("CONTAINER_") || additionalAllowKeys.contains(key) + } + } } diff --git a/Tests/ContainerPluginTests/PluginLoaderTest.swift b/Tests/ContainerPluginTests/PluginLoaderTest.swift index 8033bdaf..b429f6fa 100644 --- a/Tests/ContainerPluginTests/PluginLoaderTest.swift +++ b/Tests/ContainerPluginTests/PluginLoaderTest.swift @@ -85,6 +85,94 @@ struct PluginLoaderTest { #expect(loader.findPlugin(name: "throw") == nil) } + @Test + func testFilterEnvironmentWithContainerPrefix() async throws { + let env = [ + "CONTAINER_FOO": "bar", + "CONTAINER_BAZ": "qux", + "OTHER_VAR": "value", + ] + let filtered = PluginLoader.filterEnvironment(env: env, additionalAllowKeys: []) + + #expect(filtered == ["CONTAINER_FOO": "bar", "CONTAINER_BAZ": "qux"]) + } + + @Test + func testFilterEnvironmentWithProxyKeys() async throws { + let env = [ + "http_proxy": "http://proxy:8080", + "HTTP_PROXY": "http://proxy:8080", + "https_proxy": "https://proxy:8443", + "HTTPS_PROXY": "https://proxy:8443", + "no_proxy": "localhost,127.0.0.1", + "NO_PROXY": "localhost,127.0.0.1", + "OTHER_VAR": "value", + ] + let filtered = PluginLoader.filterEnvironment(env: env) + + #expect( + filtered == [ + "http_proxy": "http://proxy:8080", + "HTTP_PROXY": "http://proxy:8080", + "https_proxy": "https://proxy:8443", + "HTTPS_PROXY": "https://proxy:8443", + "no_proxy": "localhost,127.0.0.1", + "NO_PROXY": "localhost,127.0.0.1", + ]) + } + + @Test + func testFilterEnvironmentWithBothContainerAndProxy() async throws { + let env = [ + "CONTAINER_FOO": "bar", + "http_proxy": "http://proxy:8080", + "OTHER_VAR": "value", + "ANOTHER_VAR": "value2", + ] + let filtered = PluginLoader.filterEnvironment(env: env) + + #expect( + filtered == [ + "CONTAINER_FOO": "bar", + "http_proxy": "http://proxy:8080", + ]) + } + + @Test + func testFilterEnvironmentWithCustomAllowKeys() async throws { + let env = [ + "CONTAINER_FOO": "bar", + "CUSTOM_KEY": "custom_value", + "OTHER_VAR": "value", + ] + let filtered = PluginLoader.filterEnvironment(env: env, additionalAllowKeys: ["CUSTOM_KEY"]) + + #expect( + filtered == [ + "CONTAINER_FOO": "bar", + "CUSTOM_KEY": "custom_value", + ]) + } + + @Test + func testFilterEnvironmentEmpty() async throws { + let filtered = PluginLoader.filterEnvironment(env: [:]) + + #expect(filtered.isEmpty) + } + + @Test + func testFilterEnvironmentNoMatches() async throws { + let env = [ + "PATH": "/usr/bin", + "HOME": "/Users/test", + "USER": "testuser", + ] + let filtered = PluginLoader.filterEnvironment(env: env, additionalAllowKeys: []) + + #expect(filtered.isEmpty) + } + private func setupMock(tempURL: URL) throws -> MockPluginFactory { let cliConfig = PluginConfig(abstract: "cli", author: "CLI", servicesConfig: nil) let cliPlugin: Plugin = Plugin(binaryURL: URL(filePath: "/bin/cli"), config: cliConfig)