Files
container/Sources/ContainerizationOCI/Bundle.swift
T
Danny Canter f49f668936 Updates to license headers (#17)
- Capitalize containerization.
- Wrap "All rights reserved" to a new line.

Signed-off-by: Danny Canter <danny_canter@apple.com>
2025-06-05 16:16:22 -07:00

109 lines
3.4 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the Containerization 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 ContainerizationError
import Foundation
#if canImport(Musl)
import Musl
private let _mount = Musl.mount
private let _umount = Musl.umount2
#elseif canImport(Glibc)
import Glibc
private let _mount = Glibc.mount
private let _umount = Glibc.umount2
#endif
/// `Bundle` represents an OCI runtime spec bundle for running
/// a container.
public struct Bundle: Sendable {
public let path: URL
public var configPath: URL {
self.path.appending(path: "config.json")
}
public var rootfsPath: URL {
self.path.appending(path: "rootfs")
}
public static func create(path: URL, spec: Data) throws -> Bundle {
try self.init(path: path, spec: spec)
}
public static func create(path: URL, spec: ContainerizationOCI.Spec) throws -> Bundle {
try self.init(path: path, spec: spec)
}
public static func load(path: URL) throws -> Bundle {
try self.init(path: path)
}
private init(path: URL) throws {
let fm = FileManager.default
if !fm.fileExists(atPath: path.path) {
throw ContainerizationError(.invalidArgument, message: "no bundle at \(path.path)")
}
self.path = path
}
// This constructor does not do any validation that data is actually a
// valid OCI spec.
private init(path: URL, spec: Data) throws {
self.path = path
let fm = FileManager.default
try fm.createDirectory(
atPath: self.path.appending(component: "rootfs").path,
withIntermediateDirectories: true
)
try spec.write(to: self.configPath)
}
private init(path: URL, spec: ContainerizationOCI.Spec) throws {
self.path = path
let fm = FileManager.default
try fm.createDirectory(
atPath: self.path.appending(component: "rootfs").path,
withIntermediateDirectories: true
)
let specData = try JSONEncoder().encode(spec)
try specData.write(to: self.configPath)
}
public func delete() throws {
// Unmount, and then blow away the dir.
#if os(Linux)
let rootfs = self.rootfsPath.path
guard _umount(rootfs, 0) == 0 else {
throw POSIXError.fromErrno()
}
#endif
// removeItem is recursive so should blow away the rootfs dir inside as well.
let fm = FileManager.default
try fm.removeItem(at: self.path)
}
public func loadConfig() throws -> ContainerizationOCI.Spec {
let data = try Data(contentsOf: self.configPath)
return try JSONDecoder().decode(ContainerizationOCI.Spec.self, from: data)
}
}