mirror of
https://github.com/apple/container.git
synced 2026-09-23 16:15:37 +00:00
Fix using docker specific ignore with read-only build context (#1349)
This PR resolves #1343. This PR depends on apple/container-builder-shim#72. Do not create staging directory under build context, but pass dockerignore file bytes to the container-builder-shim. ## Type of Change - [x] Bug fix - [ ] New feature - [ ] Breaking change - [ ] Documentation update ## Motivation and Context [Why is this change needed?] ## Testing - [x] Tested locally - [ ] Added/updated tests - [ ] Added/updated docs
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ import PackageDescription
|
||||
|
||||
let releaseVersion = ProcessInfo.processInfo.environment["RELEASE_VERSION"] ?? "0.0.0"
|
||||
let gitCommit = ProcessInfo.processInfo.environment["GIT_COMMIT"] ?? "unspecified"
|
||||
let builderShimVersion = "0.11.0"
|
||||
let builderShimVersion = "0.12.0"
|
||||
let scVersion = "0.30.1"
|
||||
|
||||
let package = Package(
|
||||
|
||||
@@ -267,7 +267,7 @@ public struct Builder: Sendable {
|
||||
public let secrets: [String: Data]
|
||||
public let contextDir: String
|
||||
public let dockerfile: Data
|
||||
public let hiddenDockerDir: String?
|
||||
public let dockerignore: Data?
|
||||
public let labels: [String]
|
||||
public let noCache: Bool
|
||||
public let platforms: [Platform]
|
||||
@@ -287,7 +287,7 @@ public struct Builder: Sendable {
|
||||
secrets: [String: Data],
|
||||
contextDir: String,
|
||||
dockerfile: Data,
|
||||
hiddenDockerDir: String?,
|
||||
dockerignore: Data?,
|
||||
labels: [String],
|
||||
noCache: Bool,
|
||||
platforms: [Platform],
|
||||
@@ -306,7 +306,7 @@ public struct Builder: Sendable {
|
||||
self.secrets = secrets
|
||||
self.contextDir = contextDir
|
||||
self.dockerfile = dockerfile
|
||||
self.hiddenDockerDir = hiddenDockerDir
|
||||
self.dockerignore = dockerignore
|
||||
self.labels = labels
|
||||
self.noCache = noCache
|
||||
self.platforms = platforms
|
||||
@@ -329,8 +329,8 @@ public struct Builder: Sendable {
|
||||
metadata.addString(config.terminal != nil ? "tty" : "plain", forKey: "progress")
|
||||
metadata.addString(config.target, forKey: "target")
|
||||
|
||||
if let hiddenDockerDir = config.hiddenDockerDir {
|
||||
metadata.addString(hiddenDockerDir, forKey: "hidden-docker-dir")
|
||||
if let dockerignore = config.dockerignore {
|
||||
metadata.addString(dockerignore.base64EncodedString(), forKey: "dockerignore")
|
||||
}
|
||||
for tag in config.tags {
|
||||
metadata.addString(tag, forKey: "tag")
|
||||
|
||||
@@ -28,8 +28,6 @@ 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()
|
||||
@@ -220,7 +218,6 @@ extension Application {
|
||||
|
||||
let buildFileData: Data
|
||||
var ignoreFileData: Data? = nil
|
||||
var hiddenDockerDir: String? = nil
|
||||
// Dockerfile should be read from stdin
|
||||
if dockerfile == "-" {
|
||||
let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent("Dockerfile-\(UUID().uuidString)")
|
||||
@@ -248,24 +245,6 @@ extension Application {
|
||||
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 secretsData: [String: Data] = try self.secrets.mapValues { secret in
|
||||
@@ -351,7 +330,7 @@ extension Application {
|
||||
return results
|
||||
}()
|
||||
group.addTask {
|
||||
[terminal, buildArg, secretsData, contextDir, hiddenDockerDir, label, noCache, target, quiet, cacheIn, cacheOut, pull, exports, imageNames, tempURL, log] in
|
||||
[terminal, buildArg, secretsData, contextDir, ignoreFileData, label, noCache, target, quiet, cacheIn, cacheOut, pull, exports, imageNames, tempURL, log] in
|
||||
let config = Builder.BuildConfig(
|
||||
buildID: buildID,
|
||||
contentStore: RemoteContentStoreClient(),
|
||||
@@ -359,7 +338,7 @@ extension Application {
|
||||
secrets: secretsData,
|
||||
contextDir: contextDir,
|
||||
dockerfile: buildFileData,
|
||||
hiddenDockerDir: hiddenDockerDir,
|
||||
dockerignore: ignoreFileData,
|
||||
labels: label,
|
||||
noCache: noCache,
|
||||
platforms: [Platform](platforms),
|
||||
|
||||
@@ -1173,6 +1173,62 @@ extension TestCLIBuildBase {
|
||||
#expect(includedResult.status == 0, "included.txt should be present")
|
||||
}
|
||||
|
||||
// Test: Build context is read-only; Dockerfile and Dockerfile.dockerignore live outside the context
|
||||
@Test func testDockerIgnoreReadonlyContext() throws {
|
||||
let tempDir: URL = try createTempDir()
|
||||
let contextDir = tempDir.appendingPathComponent("context")
|
||||
defer {
|
||||
// Restore write permission so the directory can be removed
|
||||
try? FileManager.default.setAttributes(
|
||||
[.posixPermissions: 0o755],
|
||||
ofItemAtPath: contextDir.path
|
||||
)
|
||||
try! FileManager.default.removeItem(at: tempDir)
|
||||
}
|
||||
|
||||
let dockerfile =
|
||||
"""
|
||||
FROM ghcr.io/linuxcontainers/alpine:3.20
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
"""
|
||||
// Context contains two files; Dockerfile and Dockerfile.dockerignore are placed outside
|
||||
// the context directory (co-located in tempDir).
|
||||
let context: [FileSystemEntry] = [
|
||||
.file("included.txt", content: .data("This file should be included.\n".data(using: .utf8)!)),
|
||||
.file("secret.txt", content: .data("This file should be excluded by Dockerfile.dockerignore.\n".data(using: .utf8)!)),
|
||||
]
|
||||
try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)
|
||||
|
||||
// Write Dockerfile.dockerignore next to Dockerfile, both outside the context directory
|
||||
let dockerignoreData = "secret.txt\n".data(using: .utf8)!
|
||||
try dockerignoreData.write(to: tempDir.appendingPathComponent("Dockerfile.dockerignore"), options: .atomic)
|
||||
|
||||
// Make the context directory read-only before building
|
||||
try FileManager.default.setAttributes(
|
||||
[.posixPermissions: 0o555],
|
||||
ofItemAtPath: contextDir.path
|
||||
)
|
||||
|
||||
let dockerfilePath = tempDir.appendingPathComponent("Dockerfile")
|
||||
let imageName = "registry.local/dockerignore-readonly-context:\(UUID().uuidString.prefix(6))"
|
||||
let args = ["build", "-f", dockerfilePath.path, "-t", imageName, contextDir.path]
|
||||
let response = try run(arguments: args)
|
||||
if response.status != 0 {
|
||||
throw CLIError.executionFailed("build failed: stdout=\(response.output) stderr=\(response.error)")
|
||||
}
|
||||
|
||||
let containerName = "dockerignore-readonly-context-\(UUID().uuidString.prefix(6))"
|
||||
try self.doLongRun(name: containerName, image: imageName)
|
||||
defer { try? self.doStop(name: containerName) }
|
||||
|
||||
let includedResult = try run(arguments: ["exec", containerName, "test", "-f", "/app/included.txt"])
|
||||
#expect(includedResult.status == 0, "included.txt should be present")
|
||||
|
||||
let secretResult = try run(arguments: ["exec", containerName, "test", "-f", "/app/secret.txt"])
|
||||
#expect(secretResult.status != 0, "secret.txt should NOT be present (excluded by Dockerfile.dockerignore)")
|
||||
}
|
||||
|
||||
@Test func testNonExistingDockerfile() throws {
|
||||
let tempDir: URL = try createTempDir()
|
||||
defer {
|
||||
|
||||
Reference in New Issue
Block a user