mirror of
https://github.com/apple/container.git
synced 2026-07-12 20:47:04 +00:00
9bd3c47345
- Part of #1833.
176 lines
6.2 KiB
Swift
176 lines
6.2 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// 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 ContainerResource
|
|
import Foundation
|
|
import SystemPackage
|
|
|
|
// MARK: - Inspect types
|
|
|
|
extension ContainerFixture {
|
|
/// Decoded output of `container inspect <name>`.
|
|
struct InspectOutput: Codable {
|
|
struct Status: Codable {
|
|
let state: String
|
|
let networks: [ContainerResource.Attachment]
|
|
}
|
|
let configuration: ContainerConfiguration
|
|
let status: Status
|
|
var networks: [ContainerResource.Attachment] { status.networks }
|
|
}
|
|
}
|
|
|
|
// MARK: - Container lifecycle helpers
|
|
|
|
extension ContainerFixture {
|
|
|
|
/// `-e` flags forwarding proxy env vars into container commands.
|
|
var proxyEnvironmentArgs: [String] {
|
|
let vars = Set(["HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "NO_PROXY", "no_proxy"])
|
|
return ProcessInfo.processInfo.environment
|
|
.filter { vars.contains($0.key) }
|
|
.flatMap { ["-e", "\($0.key)=\($0.value)"] }
|
|
}
|
|
|
|
/// Starts a detached container. Uses the first warmup image when `image` is nil.
|
|
///
|
|
/// `containerEnv` injects environment variables into the container via `-e` flags.
|
|
/// To set the CLI subprocess environment (e.g. for `--ssh`), use ``run(_:env:)`` directly.
|
|
func doLongRun(
|
|
name: String,
|
|
image: String? = nil,
|
|
args: [String] = [],
|
|
containerArgs: [String] = ["sleep", "infinity"],
|
|
autoRemove: Bool = true,
|
|
containerEnv: [String: String] = [:]
|
|
) throws {
|
|
let imageRef = image ?? ContainerFixture.warmupImages[0]
|
|
var runArgs = ["run"]
|
|
if autoRemove { runArgs.append("--rm") }
|
|
runArgs += ["--name", name, "-d"]
|
|
runArgs += proxyEnvironmentArgs
|
|
runArgs += args
|
|
for (k, v) in containerEnv { runArgs += ["-e", "\(k)=\(v)"] }
|
|
runArgs.append(imageRef)
|
|
runArgs += containerArgs
|
|
try run(runArgs).check()
|
|
}
|
|
|
|
/// Creates a stopped container (`container create`).
|
|
func doCreate(
|
|
name: String,
|
|
image: String? = nil,
|
|
args: [String] = ["sleep", "infinity"],
|
|
volumes: [String] = [],
|
|
networks: [String] = [],
|
|
ports: [String] = []
|
|
) throws {
|
|
let imageRef = image ?? ContainerFixture.warmupImages[0]
|
|
var createArgs = ["create", "--rm", "--name", name]
|
|
createArgs += proxyEnvironmentArgs
|
|
for v in volumes { createArgs += ["-v", v] }
|
|
for n in networks { createArgs += ["--network", n] }
|
|
for p in ports { createArgs += ["--publish", "\(p):\(p)"] }
|
|
createArgs.append(imageRef)
|
|
createArgs += args
|
|
try run(createArgs).check()
|
|
}
|
|
|
|
/// Starts a stopped container.
|
|
func doStart(_ name: String) throws {
|
|
try run(["start", name]).check()
|
|
}
|
|
|
|
/// Stops a container. Pass `signal: nil` to use the server's default.
|
|
func doStop(_ name: String, signal: String? = "SIGKILL") throws {
|
|
var args = ["stop"]
|
|
if let signal { args += ["-s", signal] }
|
|
args.append(name)
|
|
try run(args).check()
|
|
}
|
|
|
|
/// Deletes a container.
|
|
func doRemove(_ name: String, force: Bool = false) throws {
|
|
var args = ["delete"]
|
|
if force { args.append("--force") }
|
|
args.append(name)
|
|
try run(args).check()
|
|
}
|
|
|
|
/// Deletes a container.
|
|
///
|
|
/// When `ignoreFailure` is `false` (default) any error is rethrown — use
|
|
/// this when the container is expected to exist and removal must succeed.
|
|
/// Set `ignoreFailure: true` in cleanup contexts where best-effort removal
|
|
/// is acceptable (e.g. the container may have already been removed).
|
|
func doRemoveIfExists(_ name: String, force: Bool = false, ignoreFailure: Bool = false) throws {
|
|
do {
|
|
try doRemove(name, force: force)
|
|
} catch {
|
|
if !ignoreFailure { throw error }
|
|
}
|
|
}
|
|
|
|
/// Runs a command inside a container, returns stdout. Throws on non-zero exit.
|
|
@discardableResult
|
|
func doExec(
|
|
_ name: String,
|
|
cmd: [String],
|
|
detach: Bool = false,
|
|
user: String? = nil
|
|
) throws -> String {
|
|
var args = ["exec"]
|
|
args += proxyEnvironmentArgs
|
|
if detach { args.append("-d") }
|
|
if let user { args += ["-u", user] }
|
|
args.append(name)
|
|
args += cmd
|
|
return try run(args).check().output
|
|
}
|
|
|
|
/// Exports a container filesystem to a tar archive at `path`.
|
|
func doExport(_ name: String, to path: FilePath) throws {
|
|
try run(["export", name, "-o", path.string]).check()
|
|
}
|
|
}
|
|
|
|
// MARK: - Inspect helpers
|
|
|
|
extension ContainerFixture {
|
|
|
|
/// Returns the parsed inspect output for a container.
|
|
func inspectContainer(_ name: String) throws -> InspectOutput {
|
|
let result = try run(["inspect", name]).check()
|
|
let decoder = JSONDecoder()
|
|
decoder.dateDecodingStrategy = .iso8601
|
|
let outputs = try decoder.decode([InspectOutput].self, from: result.outputData)
|
|
guard let first = outputs.first else {
|
|
throw CommandError.executionFailed("container '\(name)' not found in inspect output")
|
|
}
|
|
return first
|
|
}
|
|
|
|
/// Returns the `status.state` string for a container (e.g. `"running"`, `"stopped"`).
|
|
func getContainerStatus(_ name: String) throws -> String {
|
|
try inspectContainer(name).status.state
|
|
}
|
|
|
|
/// Returns the `configuration.id` for a container.
|
|
func getContainerId(_ name: String) throws -> String {
|
|
try inspectContainer(name).configuration.id
|
|
}
|
|
}
|