Skip to content
6 changes: 6 additions & 0 deletions Sources/ContainerBuild/Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ public struct Builder: Sendable {
public let buildArgs: [String]
public let contextDir: String
public let dockerfile: Data
public let hiddenDockerDir: String?
public let labels: [String]
public let noCache: Bool
public let platforms: [Platform]
Expand All @@ -259,6 +260,7 @@ public struct Builder: Sendable {
buildArgs: [String],
contextDir: String,
dockerfile: Data,
hiddenDockerDir: String?,
labels: [String],
noCache: Bool,
platforms: [Platform],
Expand All @@ -276,6 +278,7 @@ public struct Builder: Sendable {
self.buildArgs = buildArgs
self.contextDir = contextDir
self.dockerfile = dockerfile
self.hiddenDockerDir = hiddenDockerDir
self.labels = labels
self.noCache = noCache
self.platforms = platforms
Expand Down Expand Up @@ -319,6 +322,9 @@ extension CallOptions {
("progress", config.terminal != nil ? "tty" : "plain"),
("target", config.target),
]
if let hiddenDockerDir = config.hiddenDockerDir {
headers.append(("hidden-docker-dir", hiddenDockerDir))
}
for tag in config.tags {
headers.append(("tag", tag))
}
Expand Down
77 changes: 57 additions & 20 deletions Sources/ContainerCommands/BuildCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import TerminalProgress

extension Application {
public struct BuildCommand: AsyncLoggableCommand {
private static let hiddenDockerDir = ".com.apple.container.dockerfiles"

public init() {}
public static var configuration: CommandConfiguration {
var config = CommandConfiguration()
Expand Down Expand Up @@ -72,6 +74,8 @@ extension Application {
@Option(name: .shortAndLong, help: ArgumentHelp("Path to Dockerfile", valueName: "path"))
var file: String?

var dockerfile: String = "-"

@Option(name: .shortAndLong, help: ArgumentHelp("Set a label", valueName: "key=val"))
var label: [String] = []

Expand Down Expand Up @@ -204,24 +208,11 @@ extension Application {
throw ValidationError("builder is not running")
}

let buildFilePath: String
if let file = self.file {
buildFilePath = file
} else {
guard
let resolvedPath = try BuildFile.resolvePath(
contextDir: self.contextDir,
log: log
)
else {
throw ValidationError("failed to find Dockerfile or Containerfile in the context directory \(self.contextDir)")
}
buildFilePath = resolvedPath
}

let buildFileData: Data
var ignoreFileData: Data? = nil
var hiddenDockerDir: String? = nil
// Dockerfile should be read from stdin
if file == "-" {
if dockerfile == "-" {
let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent("Dockerfile-\(UUID().uuidString)")
defer {
try? FileManager.default.removeItem(at: tempFile)
Expand All @@ -244,7 +235,27 @@ extension Application {
try fileHandle.close()
buildFileData = try Data(contentsOf: URL(filePath: tempFile.path()))
} else {
buildFileData = try Data(contentsOf: URL(filePath: buildFilePath))
let ignoreFileURL = URL(filePath: dockerfile + ".dockerignore")
buildFileData = try Data(contentsOf: URL(filePath: dockerfile))
ignoreFileData = try? Data(contentsOf: ignoreFileURL)

if var ignoreFileData {
hiddenDockerDir = Self.hiddenDockerDir
let hiddenDirInContext = URL(fileURLWithPath: contextDir).appendingPathComponent(Self.hiddenDockerDir)

try FileManager.default.createDirectory(at: hiddenDirInContext, withIntermediateDirectories: true)
try buildFileData.write(to: hiddenDirInContext.appendingPathComponent("Dockerfile"))

ignoreFileData.append("\n\(Self.hiddenDockerDir)".data(using: .utf8) ?? Data())
try ignoreFileData.write(to: hiddenDirInContext.appendingPathComponent("Dockerfile.dockerignore"))
}
}

defer {
if let hiddenDockerDir {
let hiddenDirInContext = URL(fileURLWithPath: contextDir).appendingPathComponent(hiddenDockerDir)
try? FileManager.default.removeItem(at: hiddenDirInContext)
}
}

let systemHealth = try await ClientHealthCheck.ping(timeout: .seconds(10))
Expand Down Expand Up @@ -316,13 +327,14 @@ extension Application {
}
return results
}()
group.addTask { [terminal, buildArg, contextDir, label, noCache, target, quiet, cacheIn, cacheOut, pull] in
group.addTask { [terminal, buildArg, contextDir, hiddenDockerDir, label, noCache, target, quiet, cacheIn, cacheOut, pull] in
let config = Builder.BuildConfig(
buildID: buildID,
contentStore: RemoteContentStoreClient(),
buildArgs: buildArg,
contextDir: contextDir,
dockerfile: buildFileData,
hiddenDockerDir: hiddenDockerDir,
labels: label,
noCache: noCache,
platforms: [Platform](platforms),
Expand Down Expand Up @@ -412,8 +424,8 @@ extension Application {
}
}

public func validate() throws {
// NOTE: We'll "validate" the Dockerfile later.
public mutating func validate() throws {
// NOTE: Here we check the Dockerfile exists, and set `dockerfile` to point the valid Dockerfile path or stdin
guard FileManager.default.fileExists(atPath: contextDir) else {
throw ValidationError("context dir does not exist \(contextDir)")
}
Expand All @@ -422,6 +434,31 @@ extension Application {
throw ValidationError("invalid reference \(name)")
}
}

switch file {
case "-":
dockerfile = "-"
break
case .some(let filepath):
let fileURL = URL(fileURLWithPath: filepath, relativeTo: .currentDirectory())
guard FileManager.default.fileExists(atPath: fileURL.path) else {
throw ValidationError("dockerfile does not exist \(filepath)")
}

dockerfile = fileURL.path
break
case .none:
guard let defaultDockerfile = try BuildFile.resolvePath(contextDir: contextDir) else {
throw ValidationError("dockerfile not found in context dir")
}

guard FileManager.default.fileExists(atPath: defaultDockerfile) else {
throw ValidationError("dockerfile does not exist \(defaultDockerfile)")
}

dockerfile = defaultDockerfile
break
}
}
}
}
Loading
Loading