Skip to content

Commit 2ed65b9

Browse files
authored
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
1 parent 49079da commit 2ed65b9

4 files changed

Lines changed: 64 additions & 29 deletions

File tree

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import PackageDescription
2222

2323
let releaseVersion = ProcessInfo.processInfo.environment["RELEASE_VERSION"] ?? "0.0.0"
2424
let gitCommit = ProcessInfo.processInfo.environment["GIT_COMMIT"] ?? "unspecified"
25-
let builderShimVersion = "0.11.0"
25+
let builderShimVersion = "0.12.0"
2626
let scVersion = "0.30.1"
2727

2828
let package = Package(

Sources/ContainerBuild/Builder.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ public struct Builder: Sendable {
267267
public let secrets: [String: Data]
268268
public let contextDir: String
269269
public let dockerfile: Data
270-
public let hiddenDockerDir: String?
270+
public let dockerignore: Data?
271271
public let labels: [String]
272272
public let noCache: Bool
273273
public let platforms: [Platform]
@@ -287,7 +287,7 @@ public struct Builder: Sendable {
287287
secrets: [String: Data],
288288
contextDir: String,
289289
dockerfile: Data,
290-
hiddenDockerDir: String?,
290+
dockerignore: Data?,
291291
labels: [String],
292292
noCache: Bool,
293293
platforms: [Platform],
@@ -306,7 +306,7 @@ public struct Builder: Sendable {
306306
self.secrets = secrets
307307
self.contextDir = contextDir
308308
self.dockerfile = dockerfile
309-
self.hiddenDockerDir = hiddenDockerDir
309+
self.dockerignore = dockerignore
310310
self.labels = labels
311311
self.noCache = noCache
312312
self.platforms = platforms
@@ -329,8 +329,8 @@ public struct Builder: Sendable {
329329
metadata.addString(config.terminal != nil ? "tty" : "plain", forKey: "progress")
330330
metadata.addString(config.target, forKey: "target")
331331

332-
if let hiddenDockerDir = config.hiddenDockerDir {
333-
metadata.addString(hiddenDockerDir, forKey: "hidden-docker-dir")
332+
if let dockerignore = config.dockerignore {
333+
metadata.addString(dockerignore.base64EncodedString(), forKey: "dockerignore")
334334
}
335335
for tag in config.tags {
336336
metadata.addString(tag, forKey: "tag")

Sources/ContainerCommands/BuildCommand.swift

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import TerminalProgress
2828

2929
extension Application {
3030
public struct BuildCommand: AsyncLoggableCommand {
31-
private static let hiddenDockerDir = ".com.apple.container.dockerfiles"
32-
3331
public init() {}
3432
public static var configuration: CommandConfiguration {
3533
var config = CommandConfiguration()
@@ -220,7 +218,6 @@ extension Application {
220218

221219
let buildFileData: Data
222220
var ignoreFileData: Data? = nil
223-
var hiddenDockerDir: String? = nil
224221
// Dockerfile should be read from stdin
225222
if dockerfile == "-" {
226223
let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent("Dockerfile-\(UUID().uuidString)")
@@ -248,24 +245,6 @@ extension Application {
248245
let ignoreFileURL = URL(filePath: dockerfile + ".dockerignore")
249246
buildFileData = try Data(contentsOf: URL(filePath: dockerfile))
250247
ignoreFileData = try? Data(contentsOf: ignoreFileURL)
251-
252-
if var ignoreFileData {
253-
hiddenDockerDir = Self.hiddenDockerDir
254-
let hiddenDirInContext = URL(fileURLWithPath: contextDir).appendingPathComponent(Self.hiddenDockerDir)
255-
256-
try FileManager.default.createDirectory(at: hiddenDirInContext, withIntermediateDirectories: true)
257-
try buildFileData.write(to: hiddenDirInContext.appendingPathComponent("Dockerfile"))
258-
259-
ignoreFileData.append("\n\(Self.hiddenDockerDir)".data(using: .utf8) ?? Data())
260-
try ignoreFileData.write(to: hiddenDirInContext.appendingPathComponent("Dockerfile.dockerignore"))
261-
}
262-
}
263-
264-
defer {
265-
if let hiddenDockerDir {
266-
let hiddenDirInContext = URL(fileURLWithPath: contextDir).appendingPathComponent(hiddenDockerDir)
267-
try? FileManager.default.removeItem(at: hiddenDirInContext)
268-
}
269248
}
270249

271250
let secretsData: [String: Data] = try self.secrets.mapValues { secret in
@@ -351,15 +330,15 @@ extension Application {
351330
return results
352331
}()
353332
group.addTask {
354-
[terminal, buildArg, secretsData, contextDir, hiddenDockerDir, label, noCache, target, quiet, cacheIn, cacheOut, pull, exports, imageNames, tempURL, log] in
333+
[terminal, buildArg, secretsData, contextDir, ignoreFileData, label, noCache, target, quiet, cacheIn, cacheOut, pull, exports, imageNames, tempURL, log] in
355334
let config = Builder.BuildConfig(
356335
buildID: buildID,
357336
contentStore: RemoteContentStoreClient(),
358337
buildArgs: buildArg,
359338
secrets: secretsData,
360339
contextDir: contextDir,
361340
dockerfile: buildFileData,
362-
hiddenDockerDir: hiddenDockerDir,
341+
dockerignore: ignoreFileData,
363342
labels: label,
364343
noCache: noCache,
365344
platforms: [Platform](platforms),

Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,62 @@ extension TestCLIBuildBase {
11731173
#expect(includedResult.status == 0, "included.txt should be present")
11741174
}
11751175

1176+
// Test: Build context is read-only; Dockerfile and Dockerfile.dockerignore live outside the context
1177+
@Test func testDockerIgnoreReadonlyContext() throws {
1178+
let tempDir: URL = try createTempDir()
1179+
let contextDir = tempDir.appendingPathComponent("context")
1180+
defer {
1181+
// Restore write permission so the directory can be removed
1182+
try? FileManager.default.setAttributes(
1183+
[.posixPermissions: 0o755],
1184+
ofItemAtPath: contextDir.path
1185+
)
1186+
try! FileManager.default.removeItem(at: tempDir)
1187+
}
1188+
1189+
let dockerfile =
1190+
"""
1191+
FROM ghcr.io/linuxcontainers/alpine:3.20
1192+
WORKDIR /app
1193+
COPY . .
1194+
"""
1195+
// Context contains two files; Dockerfile and Dockerfile.dockerignore are placed outside
1196+
// the context directory (co-located in tempDir).
1197+
let context: [FileSystemEntry] = [
1198+
.file("included.txt", content: .data("This file should be included.\n".data(using: .utf8)!)),
1199+
.file("secret.txt", content: .data("This file should be excluded by Dockerfile.dockerignore.\n".data(using: .utf8)!)),
1200+
]
1201+
try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)
1202+
1203+
// Write Dockerfile.dockerignore next to Dockerfile, both outside the context directory
1204+
let dockerignoreData = "secret.txt\n".data(using: .utf8)!
1205+
try dockerignoreData.write(to: tempDir.appendingPathComponent("Dockerfile.dockerignore"), options: .atomic)
1206+
1207+
// Make the context directory read-only before building
1208+
try FileManager.default.setAttributes(
1209+
[.posixPermissions: 0o555],
1210+
ofItemAtPath: contextDir.path
1211+
)
1212+
1213+
let dockerfilePath = tempDir.appendingPathComponent("Dockerfile")
1214+
let imageName = "registry.local/dockerignore-readonly-context:\(UUID().uuidString.prefix(6))"
1215+
let args = ["build", "-f", dockerfilePath.path, "-t", imageName, contextDir.path]
1216+
let response = try run(arguments: args)
1217+
if response.status != 0 {
1218+
throw CLIError.executionFailed("build failed: stdout=\(response.output) stderr=\(response.error)")
1219+
}
1220+
1221+
let containerName = "dockerignore-readonly-context-\(UUID().uuidString.prefix(6))"
1222+
try self.doLongRun(name: containerName, image: imageName)
1223+
defer { try? self.doStop(name: containerName) }
1224+
1225+
let includedResult = try run(arguments: ["exec", containerName, "test", "-f", "/app/included.txt"])
1226+
#expect(includedResult.status == 0, "included.txt should be present")
1227+
1228+
let secretResult = try run(arguments: ["exec", containerName, "test", "-f", "/app/secret.txt"])
1229+
#expect(secretResult.status != 0, "secret.txt should NOT be present (excluded by Dockerfile.dockerignore)")
1230+
}
1231+
11761232
@Test func testNonExistingDockerfile() throws {
11771233
let tempDir: URL = try createTempDir()
11781234
defer {

0 commit comments

Comments
 (0)