From 421ef3a52fd470b9f9bfebeb8d7f551f74dd0509 Mon Sep 17 00:00:00 2001 From: Chris George Date: Sat, 2 May 2026 11:57:50 -0700 Subject: [PATCH] feat(api): reserve HealthStatus enum + health field on ContainerSnapshot Adds a new public enum HealthStatus { none, starting, healthy, unhealthy } and a new optional 'health: HealthStatus?' field on ContainerSnapshot, defaulting to nil at all construction sites. Motivation ---------- External orchestrators that drive the API server (the canonical use case is the compose-spec depends_on: condition: service_healthy gate) need to know whether a container is up AND healthy, not just up. Today ContainerSnapshot exposes .running for any started container, so consumers have to fall back to .running and treat liveness == health. Real workloads (databases that take seconds to accept connections, queue brokers that warm up an in-memory state) hit this regularly and end up either waiting too long or proceeding too early. Scope of this PR (deliberately minimal) --------------------------------------- This PR is data-shape only. It adds the enum and the field to the SDK. It does NOT wire a healthcheck observer into the daemon: at runtime the field is always nil, so the on-the-wire behavior is unchanged modulo one new Codable key on ContainerSnapshot. Why ship a nil-only field? ~~~~~~~~~~~~~~~~~~~~~~~~~~ A container-level healthcheck observer is a non-trivial design discussion (where does the spec live? does the API server exec into the container, or does the runtime drive it? does it leak into the sandbox boundary?) and we'd rather have that discussion separately, referencing a concrete companion issue. Reserving the SDK shape now lets downstream tools start coding against the field with the 'always nil today' guarantee documented inline; flipping the implementation on later does not require another SDK-shape PR. Wire compatibility ------------------ ContainerSnapshot is marshaled as Codable JSON over XPC. Adding an optional field is forward-compatible: - Older clients reading from a newer server: ignore the new key. - Newer clients reading from an older server: decode health as nil. Files ----- - Sources/ContainerResource/Container/HealthStatus.swift (new): the enum, with cases documented and a note on the daemon-side observer caveat. - Sources/ContainerResource/Container/ContainerSnapshot.swift: new optional field + init parameter (default nil). Companion issue --------------- Filed at apple/container with the design proposal for the eventual healthcheck observer; this PR is deliberately the smaller surface so the data shape can land independently of that discussion. --- .../Container/ContainerSnapshot.swift | 11 +++++- .../Container/HealthStatus.swift | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 Sources/ContainerResource/Container/HealthStatus.swift diff --git a/Sources/ContainerResource/Container/ContainerSnapshot.swift b/Sources/ContainerResource/Container/ContainerSnapshot.swift index bae992423..0168b7a2e 100644 --- a/Sources/ContainerResource/Container/ContainerSnapshot.swift +++ b/Sources/ContainerResource/Container/ContainerSnapshot.swift @@ -39,16 +39,25 @@ public struct ContainerSnapshot: Codable, Sendable { public var networks: [Attachment] /// When the container was started. public var startedDate: Date? + /// The most recently observed health of the container. + /// + /// At present the daemon does not run a container-level healthcheck + /// observer, so this field is always `nil`. The shape is reserved so that + /// downstream tools (e.g. `compose`) have a stable type to read from once + /// a healthcheck observer is wired into the API server. + public var health: HealthStatus? public init( configuration: ContainerConfiguration, status: RuntimeStatus, networks: [Attachment], - startedDate: Date? = nil + startedDate: Date? = nil, + health: HealthStatus? = nil ) { self.configuration = configuration self.status = status self.networks = networks self.startedDate = startedDate + self.health = health } } diff --git a/Sources/ContainerResource/Container/HealthStatus.swift b/Sources/ContainerResource/Container/HealthStatus.swift new file mode 100644 index 000000000..3f13a4ffc --- /dev/null +++ b/Sources/ContainerResource/Container/HealthStatus.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// 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 Foundation + +/// The observed health status of a container, as derived from a periodic +/// healthcheck probe. +/// +/// At present the daemon does not run a container-level healthcheck observer, +/// so ``ContainerSnapshot/health`` is always `nil`. This type is reserved for +/// downstream tools (e.g. `compose`) that want a stable shape to read from +/// once a healthcheck observer is wired into the API server. +public enum HealthStatus: String, CaseIterable, Sendable, Codable { + /// No healthcheck has been configured or no result is yet available. + case none + /// The healthcheck is running but has not yet produced a successful probe. + case starting + /// The most recent probe(s) reported the container as healthy. + case healthy + /// The most recent probe(s) reported the container as unhealthy. + case unhealthy +}