Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions Plugins/ContainerImageBuilder/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ extension PluginError: CustomStringConvertible {

OPTIONS
--product Product to include in the image
--resources Directory of resources to include in the image

Other arguments are passed to the containertool helper.
"""
Expand Down Expand Up @@ -89,16 +90,29 @@ extension PluginError: CustomStringConvertible {

for built in builtExecutables { Diagnostics.remark("Built product: \(built.url.path)") }

let resources = builtExecutables[0].url
let resourcesBundle = builtExecutables[0].url
.deletingLastPathComponent()
.appendingPathComponent(
"\(context.package.displayName)_\(productName).resources"
)

var resources: [String] = []
if FileManager.default.fileExists(atPath: resourcesBundle.path) {
resources.append(resourcesBundle.path)
}

for resource in extractor.extractOption(named: "resources") {
let paths = resource.split(separator: ":", maxSplits: 1)
if paths.count >= 1 && !FileManager.default.fileExists(atPath: String(paths[0])) {
throw PluginError.argumentError("Resource directory \(resource) does not exist")
}
resources.append(resource)
}

// Run a command line helper to upload the image
let helperURL = try context.tool(named: "containertool").url
let helperArgs =
(FileManager.default.fileExists(atPath: resources.path) ? ["--resources", resources.path] : [])
resources.map { "--resources=\($0)" }
+ builtExecutables.map { $0.url.path }
+ extractor.remainingArguments
let helperEnv = ProcessInfo.processInfo.environment.filter { $0.key.starts(with: "CONTAINERTOOL_") }
Expand Down
17 changes: 16 additions & 1 deletion Sources/containertool/Extensions/Archive+appending.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,25 @@ extension Archive {
try self.appendingFile(name: path.lastPathComponent, data: try [UInt8](Data(contentsOf: path)))
}

func appendingFile(at path: URL, to destinationPath: URL) throws -> Self {
var ret = self
let data = try [UInt8](Data(contentsOf: path))
let components = destinationPath.pathComponents
precondition(!components.isEmpty, "Destination path is empty")
for i in 1..<components.count - 1 {
let directoryPath = components[..<i].joined(separator: "/")
try ret.appendDirectory(name: directoryPath)
}

try ret.appendFile(name: destinationPath.path(), data: data)
return ret
}

/// Recursively append a single directory tree to the archive.
/// Parameters:
/// - root: The path to the directory to add.
/// Returns: A new archive made by appending `root` to the receiver.
func appendingDirectoryTree(at root: URL) throws -> Self {
func appendingDirectoryTree(at root: URL, to destinationPath: URL = URL(filePath: "/")) throws -> Self {
var ret = self

guard let enumerator = FileManager.default.enumerator(atPath: root.path) else {
Expand All @@ -66,6 +80,7 @@ extension Archive {
throw ("Unable to get file type for \(subpath)")
}

let subpath = destinationPath.appending(path: subpath).path()
switch filetype {
case .typeRegular:
let resource = try [UInt8](Data(contentsOf: root.appending(path: subpath)))
Expand Down
64 changes: 54 additions & 10 deletions Sources/containertool/Extensions/RegistryClient+publish.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//

import class Foundation.FileManager
import struct Foundation.ObjCBool
import struct Foundation.Date
import struct Foundation.URL

Expand All @@ -25,6 +27,7 @@ func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(
destination: Destination,
architecture: String,
os: String,
entrypoint: String?,
cmd: [String],
resources: [String],
tag: String?,
Expand All @@ -50,17 +53,54 @@ func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(

var resourceLayers: [(descriptor: ContentDescriptor, diffID: ImageReference.Digest)] = []
for resourceDir in resources {
let resourceTardiff = try Archive().appendingRecursively(atPath: resourceDir).bytes
let resourceLayer = try await destination.uploadLayer(
repository: destinationImage.repository,
contents: resourceTardiff
)
let paths = resourceDir.split(separator: ":", maxSplits: 1)
switch paths.count {
case 1:
let resourceTardiff = try Archive().appendingRecursively(atPath: resourceDir).bytes
let resourceLayer = try await destination.uploadLayer(
repository: destinationImage.repository,
contents: resourceTardiff
)

if verbose {
log("resource layer: \(resourceLayer.descriptor.digest) (\(resourceLayer.descriptor.size) bytes)")
}
if verbose {
log("resource layer: \(resourceLayer.descriptor.digest) (\(resourceLayer.descriptor.size) bytes)")
}

resourceLayers.append(resourceLayer)
case 2:
let sourcePath = paths[0]
let destinationPath = paths[1]

var isDirectory: ObjCBool = false
guard FileManager.default.fileExists(atPath: String(sourcePath), isDirectory: &isDirectory) else {
preconditionFailure("Source does not exist: \(source)")
}

let archive: Archive
if isDirectory.boolValue {
// archive = try Archive().appendingDirectoryTree(at: URL(fileURLWithPath: String(sourcePath)))
preconditionFailure("Directory trees are not supported yet")
} else {
archive = try Archive()
.appendingFile(
at: URL(fileURLWithPath: String(sourcePath)),
to: URL(fileURLWithPath: String(destinationPath))
)
}

let resourceLayer = try await destination.uploadLayer(
repository: destinationImage.repository,
contents: archive.bytes
)

resourceLayers.append(resourceLayer)
if verbose {
log("resource layer: \(resourceLayer.descriptor.digest) (\(resourceLayer.descriptor.size) bytes)")
}

resourceLayers.append(resourceLayer)
default:
preconditionFailure("Invalid resource directory: \(resourceDir)")
}
}

// MARK: Upload the application layer
Expand All @@ -80,7 +120,11 @@ func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(
// Inherit the configuration of the base image - UID, GID, environment etc -
// and override the entrypoint.
var inheritedConfiguration = baseImageConfiguration.config ?? .init()
inheritedConfiguration.Entrypoint = ["/\(executableURL.lastPathComponent)"]
if let entrypoint {
inheritedConfiguration.Entrypoint = [entrypoint]
} else {
inheritedConfiguration.Entrypoint = ["/\(executableURL.lastPathComponent)"]
}
inheritedConfiguration.Cmd = cmd
inheritedConfiguration.WorkingDir = "/"

Expand Down
4 changes: 4 additions & 0 deletions Sources/containertool/containertool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
@Option(help: "Operating system")
var os: String?

@Option(help: "Entrypoint process")
var entrypoint: String?

@Option(parsing: .remaining, help: "Default arguments to pass to the entrypoint process")
var cmd: [String] = []
}
Expand Down Expand Up @@ -225,6 +228,7 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
destination: destination,
architecture: architecture,
os: os,
entrypoint: imageConfigurationOptions.entrypoint,
cmd: imageConfigurationOptions.cmd,
resources: imageBuildOptions.resources,
tag: repositoryOptions.tag,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import struct Foundation.Data
import struct Foundation.Date
import struct Foundation.URL
#if canImport(Security)
import Security
import Security
#endif

public protocol AuthorizationProvider: Sendable {
Expand Down Expand Up @@ -56,14 +56,14 @@ public final class NetrcAuthorizationProvider: AuthorizationProvider {
}

public func authentication(for url: URL) -> (user: String, password: String)? {
return self.machine(for: url).map { (user: $0.login, password: $0.password) }
self.machine(for: url).map { (user: $0.login, password: $0.password) }
}

private func machine(for url: URL) -> Basics.Netrc.Machine? {
// Since updates are appended to the end of the file, we
// take the _last_ match to use the most recent entry.
if let machine = NetrcAuthorizationProvider.machine(for: url),
let existing = self.netrc?.machines.last(where: { $0.name.lowercased() == machine })
let existing = self.netrc?.machines.last(where: { $0.name.lowercased() == machine })
{
return existing
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ public struct Netrc: Sendable {
/// - Parameters:
/// - url: The url to retrieve authorization information for.
public func authorization(for url: URL) -> Authorization? {
guard let index = machines.firstIndex(where: { $0.name == url.host }) ?? machines
.firstIndex(where: { $0.isDefault })
guard
let index = machines.firstIndex(where: { $0.name == url.host })
?? machines
.firstIndex(where: { $0.isDefault })
else {
return .none
}
Expand All @@ -53,8 +55,10 @@ public struct Netrc: Sendable {
}

init?(for match: NSTextCheckingResult, string: String, variant: String = "") {
guard let name = RegexUtil.Token.machine.capture(in: match, string: string) ?? RegexUtil.Token.default
.capture(in: match, string: string),
guard
let name = RegexUtil.Token.machine.capture(in: match, string: string)
?? RegexUtil.Token.default
.capture(in: match, string: string),
let login = RegexUtil.Token.login.capture(prefix: variant, in: match, string: string),
let password = RegexUtil.Token.password.capture(prefix: variant, in: match, string: string)
else {
Expand Down Expand Up @@ -87,11 +91,12 @@ public struct NetrcParser {
let matches = regex.matches(
in: content,
options: [],
range: NSRange(content.startIndex ..< content.endIndex, in: content)
range: NSRange(content.startIndex..<content.endIndex, in: content)
)

let machines: [Netrc.Machine] = matches.compactMap {
Netrc.Machine(for: $0, string: content, variant: "lp") ?? Netrc
Netrc.Machine(for: $0, string: content, variant: "lp")
?? Netrc
.Machine(for: $0, string: content, variant: "pl")
}

Expand All @@ -116,7 +121,8 @@ public struct NetrcParser {
let matches = regex.matches(in: text, range: range)
var trimmedCommentsText = text
matches.forEach {
trimmedCommentsText = trimmedCommentsText
trimmedCommentsText =
trimmedCommentsText
.replacingOccurrences(of: nsString.substring(with: $0.range), with: "")
}
return trimmedCommentsText
Expand Down