diff --git a/Sources/ContainerBuild/Builder.swift b/Sources/ContainerBuild/Builder.swift index 796ba6b3..506f0473 100644 --- a/Sources/ContainerBuild/Builder.swift +++ b/Sources/ContainerBuild/Builder.swift @@ -29,6 +29,22 @@ import NIOPosix public struct Builder: Sendable { public static let builderContainerId = "buildkit" + /// The `:authority` sent with every request to the builder shim. + /// + /// The shim connection is an already-connected socket, so grpc-swift has no target + /// address to derive an authority from and would send none. gRPC-Go v1.82.2, v1.83.2 + /// and v1.84.0 or later reject requests without an `:authority` (grpc-go #9365), which + /// leaves the builder unable to answer even the `Info` probe. The shim does not route + /// on the authority, so any valid host name works. + static let grpcAuthority = Builder.builderContainerId + + /// The transport configuration for the connection to the builder shim. + static func transportConfig() -> HTTP2ClientTransport.WrappedChannel.Config { + var config = HTTP2ClientTransport.WrappedChannel.Config.defaults + config.http2.authority = grpcAuthority + return config + } + let client: Com_Apple_Container_Build_V1_Builder.Client let grpcClient: GRPCClient let group: EventLoopGroup @@ -41,7 +57,7 @@ public struct Builder: Sendable { try socket.setRecvBufSize(2 << 20) let transport = try await HTTP2ClientTransport.WrappedChannel.wrapping( - config: .defaults, + config: Builder.transportConfig(), serviceConfig: .init() ) { configure in try await withCheckedThrowingContinuation { continuation in diff --git a/Tests/ContainerBuildTests/BuilderTransportConfigTests.swift b/Tests/ContainerBuildTests/BuilderTransportConfigTests.swift new file mode 100644 index 00000000..437057b0 --- /dev/null +++ b/Tests/ContainerBuildTests/BuilderTransportConfigTests.swift @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// 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 ContainerBuild + +@Suite struct BuilderTransportConfigTests { + /// The builder connection wraps an already-connected socket, so grpc-swift derives no + /// `:authority` for it. gRPC-Go v1.84.0 and later reject requests without one, so the + /// configuration must set it explicitly. + @Test func transportConfigSetsAuthority() { + let authority = Builder.transportConfig().http2.authority + #expect(authority == Builder.grpcAuthority) + #expect(authority?.isEmpty == false) + } +}