chore: import upstream snapshot with attribution
container project - merge build / Invoke build (push) Successful in 1s
container project - merge build / Invoke build (push) Successful in 1s
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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
|
||||
|
||||
/// A named or anonymous volume that can be mounted in containers.
|
||||
public struct VolumeConfiguration: Sendable, Equatable, Identifiable {
|
||||
// id of the volume.
|
||||
public var id: String { name }
|
||||
// Name of the volume.
|
||||
public var name: String
|
||||
// Driver used to create the volume.
|
||||
public var driver: String
|
||||
// Filesystem format of the volume.
|
||||
public var format: String
|
||||
// The mount point of the volume on the host.
|
||||
public var source: String
|
||||
// Timestamp when the volume was created.
|
||||
public var creationDate: Date
|
||||
// User-defined key/value metadata.
|
||||
public var labels: [String: String]
|
||||
// Driver-specific options.
|
||||
public var options: [String: String]
|
||||
// Size of the volume in bytes (optional).
|
||||
public var sizeInBytes: UInt64?
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
driver: String = "local",
|
||||
format: String = "ext4",
|
||||
source: String,
|
||||
creationDate: Date = Date(),
|
||||
labels: [String: String] = [:],
|
||||
options: [String: String] = [:],
|
||||
sizeInBytes: UInt64? = nil
|
||||
) {
|
||||
self.name = name
|
||||
self.driver = driver
|
||||
self.format = format
|
||||
self.source = source
|
||||
self.creationDate = creationDate
|
||||
self.labels = labels
|
||||
self.options = options
|
||||
self.sizeInBytes = sizeInBytes
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name, driver, format, source, labels, options, sizeInBytes
|
||||
case creationDate
|
||||
// TODO: retain for deserialization compatibility, remove in next major version
|
||||
case createdAt
|
||||
}
|
||||
}
|
||||
|
||||
extension VolumeConfiguration: Codable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
name = try container.decode(String.self, forKey: .name)
|
||||
driver = try container.decode(String.self, forKey: .driver)
|
||||
format = try container.decode(String.self, forKey: .format)
|
||||
source = try container.decode(String.self, forKey: .source)
|
||||
// Deprecated: As of 1.0.0. Use ``creationDate`` instead of ``createdAt``.
|
||||
// Note: Will be removed in a later release.
|
||||
creationDate =
|
||||
try container.decodeIfPresent(Date.self, forKey: .creationDate)
|
||||
?? container.decodeIfPresent(Date.self, forKey: .createdAt)
|
||||
?? Date(timeIntervalSince1970: 0)
|
||||
labels = try container.decodeIfPresent([String: String].self, forKey: .labels) ?? [:]
|
||||
options = try container.decodeIfPresent([String: String].self, forKey: .options) ?? [:]
|
||||
sizeInBytes = try container.decodeIfPresent(UInt64.self, forKey: .sizeInBytes)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(driver, forKey: .driver)
|
||||
try container.encode(format, forKey: .format)
|
||||
try container.encode(source, forKey: .source)
|
||||
try container.encode(creationDate, forKey: .creationDate)
|
||||
try container.encode(labels, forKey: .labels)
|
||||
try container.encode(options, forKey: .options)
|
||||
try container.encodeIfPresent(sizeInBytes, forKey: .sizeInBytes)
|
||||
}
|
||||
}
|
||||
|
||||
extension VolumeConfiguration {
|
||||
/// Reserved label key for marking anonymous volumes
|
||||
public static let anonymousLabel = "com.apple.container.resource.anonymous"
|
||||
|
||||
/// Whether this is an anonymous volume (detected via label)
|
||||
public var isAnonymous: Bool {
|
||||
labels[Self.anonymousLabel] != nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Error types for volume operations.
|
||||
public enum VolumeError: Error, LocalizedError {
|
||||
case volumeNotFound(String)
|
||||
case volumeAlreadyExists(String)
|
||||
case volumeInUse(String)
|
||||
case invalidVolumeName(String)
|
||||
case driverNotSupported(String)
|
||||
case storageError(String)
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .volumeNotFound(let name):
|
||||
return "volume '\(name)' not found"
|
||||
case .volumeAlreadyExists(let name):
|
||||
return "volume '\(name)' already exists"
|
||||
case .volumeInUse(let name):
|
||||
return "volume '\(name)' is currently in use and cannot be accessed by another container, or deleted"
|
||||
case .invalidVolumeName(let name):
|
||||
return "invalid volume name '\(name)'"
|
||||
case .driverNotSupported(let driver):
|
||||
return "volume driver '\(driver)' is not supported"
|
||||
case .storageError(let message):
|
||||
return "storage error: \(message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Volume storage management utilities.
|
||||
public struct VolumeStorage {
|
||||
public static let volumeNamePattern = "^[A-Za-z0-9][A-Za-z0-9_.-]*$"
|
||||
public static let defaultVolumeSizeBytes: UInt64 = 512 * 1024 * 1024 * 1024 // 512GB
|
||||
|
||||
public static func isValidVolumeName(_ name: String) -> Bool {
|
||||
guard name.count <= 255 else { return false }
|
||||
|
||||
do {
|
||||
let regex = try Regex(volumeNamePattern)
|
||||
return (try? regex.wholeMatch(in: name)) != nil
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates an anonymous volume name with UUID format
|
||||
public static func generateAnonymousVolumeName() -> String {
|
||||
UUID().uuidString.lowercased()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user