mirror of
https://github.com/apple/container.git
synced 2026-07-15 05:57:01 +00:00
d2f48982c1
This PR defines the snapshotter protocol
```swift
///Mount a snapshot and all its previous layers
func prepare(_ snapshot: Snapshot) async throws -> Snapshot
/// Commit a snapshot, making it permanent.
func commit(_ snapshot: Snapshot) async throws -> Snapshot
/// Remove a snapshot from snapshot store
func remove(_ snapshot: Snapshot) async throws
```
It updates executors to work with this new protocol
205 lines
8.0 KiB
Swift
205 lines
8.0 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
|
//
|
|
// 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 ContainerBuildIR
|
|
import ContainerBuildSnapshotter
|
|
import Foundation
|
|
|
|
/// Executes MetadataOperation (ENV, LABEL, USER, etc.).
|
|
public struct MetadataOperationExecutor: OperationExecutor {
|
|
public let capabilities: ExecutorCapabilities
|
|
|
|
public init() {
|
|
self.capabilities = ExecutorCapabilities(
|
|
supportedOperations: [.metadata],
|
|
maxConcurrency: 20 // Metadata ops are lightweight
|
|
)
|
|
}
|
|
|
|
public func execute(_ operation: ContainerBuildIR.Operation, context: ExecutionContext) async throws -> ExecutionResult {
|
|
guard let metadataOp = operation as? MetadataOperation else {
|
|
throw ExecutorError(
|
|
type: .invalidConfiguration,
|
|
context: ExecutorError.ErrorContext(
|
|
operation: operation, underlyingError: NSError(domain: "Executor", code: 1),
|
|
diagnostics: ExecutorError.Diagnostics(environment: [:], workingDirectory: "", recentLogs: [])))
|
|
}
|
|
|
|
do {
|
|
// Stub implementation
|
|
// In a real implementation, this would update the image configuration
|
|
|
|
let startTime = Date()
|
|
var environmentChanges: [String: EnvironmentValue] = [:]
|
|
var metadataChanges: [String: String] = [:]
|
|
|
|
// Apply metadata action
|
|
switch metadataOp.action {
|
|
case .setEnv(let key, let value):
|
|
environmentChanges[key] = value
|
|
context.updateEnvironment([key: value])
|
|
context.updateImageConfig { $0.env.append("\(key)=\(value)") }
|
|
|
|
// Note: There's no unsetEnv in MetadataAction
|
|
// This would need to be handled differently in a real implementation
|
|
|
|
case .setLabel(let key, let value):
|
|
metadataChanges[key] = value
|
|
context.updateImageConfig { $0.labels[key] = value }
|
|
|
|
case .setUser(let user):
|
|
context.setUser(user)
|
|
let userString: String
|
|
switch user {
|
|
case .named(let name):
|
|
userString = name
|
|
case .uid(let uid):
|
|
userString = String(uid)
|
|
case .userGroup(let user, let group):
|
|
userString = "\(user):\(group)"
|
|
case .uidGid(let uid, let gid):
|
|
userString = "\(uid):\(gid)"
|
|
}
|
|
context.updateImageConfig { $0.user = userString }
|
|
|
|
case .setWorkdir(let path):
|
|
context.setWorkingDirectory(path)
|
|
context.updateImageConfig { $0.workingDir = path }
|
|
|
|
case .setEntrypoint(let command):
|
|
context.updateImageConfig { config in
|
|
switch command {
|
|
case .exec(let args):
|
|
config.entrypoint = args
|
|
case .shell(let cmd):
|
|
config.entrypoint = ["/bin/sh", "-c", cmd]
|
|
}
|
|
}
|
|
|
|
case .setCmd(let command):
|
|
context.updateImageConfig { config in
|
|
switch command {
|
|
case .exec(let args):
|
|
config.cmd = args
|
|
case .shell(let cmd):
|
|
config.cmd = ["/bin/sh", "-c", cmd]
|
|
}
|
|
}
|
|
|
|
case .expose(let ports):
|
|
for p in ports {
|
|
context.updateImageConfig { config in
|
|
config.exposedPorts.insert(p.stringValue)
|
|
}
|
|
}
|
|
|
|
case .setHealthcheck(let healthcheck):
|
|
context.updateImageConfig { $0.healthcheck = healthcheck }
|
|
|
|
case .setStopSignal(let signal):
|
|
context.updateImageConfig { $0.stopSignal = signal }
|
|
|
|
case .setShell(let shell):
|
|
// Shell affects how commands are executed
|
|
metadataChanges["shell"] = shell.joined(separator: " ")
|
|
|
|
case .addVolume(let path):
|
|
context.updateImageConfig { $0.volumes.insert(path) }
|
|
|
|
case .setEnvBatch(let vars):
|
|
for (key, value) in vars {
|
|
environmentChanges[key] = value
|
|
context.updateEnvironment([key: value])
|
|
}
|
|
context.updateImageConfig { config in
|
|
for (key, value) in vars {
|
|
config.env.append("\(key)=\(value)")
|
|
}
|
|
}
|
|
|
|
case .setLabelBatch(let labels):
|
|
for (key, value) in labels {
|
|
metadataChanges[key] = value
|
|
}
|
|
context.updateImageConfig { config in
|
|
for (key, value) in labels {
|
|
config.labels[key] = value
|
|
}
|
|
}
|
|
|
|
case .declareArg(let name, let defaultValue):
|
|
// ARG declarations are build-time only
|
|
metadataChanges["arg:\(name)"] = defaultValue ?? ""
|
|
|
|
case .addOnBuild(let instruction):
|
|
// ONBUILD is stored as metadata
|
|
metadataChanges["onbuild:\(UUID().uuidString)"] = instruction
|
|
}
|
|
|
|
// Metadata operations don't change the filesystem, so we reuse the parent snapshot
|
|
// without going through the prepare/commit cycle
|
|
let snapshot = try getOrCreateBaseSnapshot(context: context)
|
|
|
|
let duration = Date().timeIntervalSince(startTime)
|
|
|
|
return ExecutionResult(
|
|
environmentChanges: environmentChanges,
|
|
metadataChanges: metadataChanges,
|
|
snapshot: snapshot,
|
|
duration: duration
|
|
)
|
|
|
|
} catch {
|
|
throw ExecutorError(
|
|
type: .executionFailed,
|
|
context: ExecutorError.ErrorContext(
|
|
operation: operation,
|
|
underlyingError: error,
|
|
diagnostics: ExecutorError.Diagnostics(
|
|
environment: context.environment.effectiveEnvironment,
|
|
workingDirectory: context.workingDirectory,
|
|
recentLogs: ["Failed to execute metadata operation", "Error: \(error.localizedDescription)"]
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Get the latest snapshot or create a base snapshot if none exists.
|
|
///
|
|
/// Metadata operations don't modify the filesystem, so they can reuse
|
|
/// the parent snapshot directly without snapshotter prepare/commit.
|
|
///
|
|
/// - Parameter context: The execution context
|
|
/// - Returns: The snapshot to use for this metadata operation
|
|
private func getOrCreateBaseSnapshot(context: ExecutionContext) throws -> Snapshot {
|
|
guard let latest = context.headSnapshot else {
|
|
// Create a minimal base snapshot if no parent exists
|
|
return Snapshot(
|
|
digest: try Digest(algorithm: .sha256, bytes: Data(count: 32)),
|
|
size: 0,
|
|
parent: nil,
|
|
state: .committed()
|
|
)
|
|
}
|
|
return latest
|
|
}
|
|
|
|
public func canExecute(_ operation: ContainerBuildIR.Operation) -> Bool {
|
|
operation is MetadataOperation
|
|
}
|
|
}
|