mirror of
https://github.com/apple/container.git
synced 2026-07-13 21:17:05 +00:00
e20aca8203
Normalize JSON output for network and volume resources. (#1624) - Reworks both JSON output presets to use sorted keys, ISO timestamps. `compact` is used for `ls` output, and `pretty` is used for `inspect`. - Extracts non-DRY option configuration into presets. - Closes #1623.
157 lines
6.0 KiB
Swift
157 lines
6.0 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 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()
|
|
}
|
|
}
|