Skip to content

Commit 249040d

Browse files
authored
Ensure parent dirs exist for partial idb (#1776)
Some runtimes will get mad if the image is missing parent directories, this ensures we have them. Signed-off-by: Jon Johnson <[email protected]>
1 parent 045ce8a commit 249040d

File tree

3 files changed

+157
-27
lines changed

3 files changed

+157
-27
lines changed

internal/cli/publish_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ func TestPublishLayering(t *testing.T) {
167167

168168
// This test will fail if we ever make a change in apko that changes the image.
169169
// Sometimes, this is intentional, and we need to change this and bump the version.
170-
want := "sha256:88a8065a9ccb964f27ca842a3e297e152997ddae2997fe68ae7535653c1ebc46"
170+
want := "sha256:66dc21761826860d1152e8a6defa7919ad3f65af5d56fd75164c4c36ba85c648"
171171
require.Equal(t, want, digest.String())
172172

173173
im, err := idx.IndexManifest()

pkg/build/layers.go

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,6 @@ func splitLayers(ctx context.Context, fsys apkfs.FullFS, groups []*group, pkgToD
284284

285285
top := newLayerWriter(f)
286286

287-
// We want to match the file info from the top layer's idb file below,
288-
// so when we run into it, just stash it for later.
289-
var idb tar.Header
290-
291287
// In a tar file, it is customary to include directories before files in those directories.
292288
// In order to know which directories we need to include, we maintain a directory stack for each layer.
293289
// We compare those stacks to the full FS that we're walking whenever we create a new tar entry.
@@ -371,7 +367,6 @@ func splitLayers(ctx context.Context, fsys apkfs.FullFS, groups []*group, pkgToD
371367
if err != nil {
372368
return nil, fmt.Errorf("opening %s: %w", f.path, err)
373369
}
374-
375370
if _, err := io.CopyBuffer(w.w, data, buf); err != nil {
376371
return nil, fmt.Errorf("copying %s: %w", f.path, err)
377372
}
@@ -383,35 +378,46 @@ func splitLayers(ctx context.Context, fsys apkfs.FullFS, groups []*group, pkgToD
383378
}
384379

385380
if f.header.Name == "usr/lib/apk/db/installed" {
386-
idb = *f.header
387-
}
388-
}
381+
// Add a partial installed db to each layer to satisfy scanners.
382+
for _, g := range groups {
383+
w := groupToWriter[g]
389384

390-
// Once we're done walking the FS, we need to finalize each layer...
391-
layers := make([]v1.Layer, 0, len(groups)+1)
392-
for i, g := range groups {
393-
w := groupToWriter[g]
385+
// Make sure we have all parent directories to appease AWS Lambda.
386+
for _, todo := range w.alignStacks(stack) {
387+
todo.header.ModTime = f.header.ModTime
394388

395-
// Add a partial installed db to satisfy scanners.
396-
{
397-
var buf bytes.Buffer
398-
for _, pkg := range g.pkgs {
399-
if _, err := buf.Write(pkgToDiff[pkg]); err != nil {
400-
return nil, err
389+
if err := w.w.WriteHeader(todo.header); err != nil {
390+
return nil, fmt.Errorf("writing header %s: %w", todo.header.Name, err)
391+
}
401392
}
402-
}
403393

404-
// Only the size should be different across layers.
405-
idb.Size = int64(buf.Len())
394+
// Accumulate all the idb entries for this layer.
395+
var buf bytes.Buffer
396+
for _, pkg := range g.pkgs {
397+
if _, err := buf.Write(pkgToDiff[pkg]); err != nil {
398+
return nil, err
399+
}
400+
}
406401

407-
if err := w.w.WriteHeader(&idb); err != nil {
408-
return nil, err
409-
}
402+
// Only the size should be different across layers.
403+
idb := *f.header
404+
idb.Size = int64(buf.Len())
410405

411-
if _, err := io.Copy(w.w, &buf); err != nil {
412-
return nil, err
406+
if err := w.w.WriteHeader(&idb); err != nil {
407+
return nil, err
408+
}
409+
410+
if _, err := io.Copy(w.w, &buf); err != nil {
411+
return nil, err
412+
}
413413
}
414414
}
415+
}
416+
417+
// Once we're done walking the FS, we need to finalize each layer...
418+
layers := make([]v1.Layer, 0, len(groups)+1)
419+
for i, g := range groups {
420+
w := groupToWriter[g]
415421

416422
l, err := w.finalize()
417423
if err != nil {

pkg/build/layers_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@
1515
package build
1616

1717
import (
18+
"archive/tar"
19+
"context"
1820
"fmt"
21+
"io"
22+
"os"
1923
"slices"
2024
"testing"
2125

2226
"chainguard.dev/apko/pkg/apk/apk"
27+
apkfs "chainguard.dev/apko/pkg/apk/fs"
2328
)
2429

2530
func size(pkgs ...*apk.Package) uint64 {
@@ -224,3 +229,122 @@ func compareStacks(a, b []*file) error {
224229

225230
return nil
226231
}
232+
233+
func TestSplitLayersDirectoryCreation(t *testing.T) {
234+
// Create a minimal filesystem with an installed DB file
235+
fsys := apkfs.NewMemFS()
236+
237+
// Create the parent directories first
238+
if err := fsys.MkdirAll("usr/lib/apk/db", 0755); err != nil {
239+
t.Fatalf("failed to create parent directories: %v", err)
240+
}
241+
242+
// Create the installed DB file with some content
243+
idbContent := []byte("test db content")
244+
if err := fsys.WriteFile("usr/lib/apk/db/installed", idbContent, 0644); err != nil {
245+
t.Fatalf("failed to create installed DB file: %v", err)
246+
}
247+
248+
// Create test packages for multiple layers
249+
pkg1 := &apk.Package{
250+
Name: "pkg1",
251+
Origin: "pkg1",
252+
Version: "1.0.0",
253+
InstalledSize: 1000,
254+
}
255+
pkg2 := &apk.Package{
256+
Name: "pkg2",
257+
Origin: "pkg2",
258+
Version: "1.0.0",
259+
InstalledSize: 2000,
260+
}
261+
262+
// Create package groups (this will result in multiple layers)
263+
groups := []*group{
264+
{pkgs: []*apk.Package{pkg1}, size: 1000, tiebreaker: "pkg1"},
265+
{pkgs: []*apk.Package{pkg2}, size: 2000, tiebreaker: "pkg2"},
266+
}
267+
268+
// Create package diffs (minimal content for each package)
269+
pkgToDiff := map[*apk.Package][]byte{
270+
pkg1: []byte("pkg1 info\n"),
271+
pkg2: []byte("pkg2 info\n"),
272+
}
273+
274+
// Create temp directory for layer files
275+
tmpDir, err := os.MkdirTemp("", "layer-test-*")
276+
if err != nil {
277+
t.Fatalf("failed to create temp dir: %v", err)
278+
}
279+
defer os.RemoveAll(tmpDir)
280+
281+
// Call splitLayers to create the layers
282+
ctx := context.Background()
283+
layers, err := splitLayers(ctx, fsys, groups, pkgToDiff, tmpDir)
284+
if err != nil {
285+
t.Fatalf("splitLayers failed: %v", err)
286+
}
287+
288+
// We expect 3 layers: one for each package group + top layer
289+
if len(layers) != 3 {
290+
t.Fatalf("expected 3 layers, got %d", len(layers))
291+
}
292+
293+
wantDirs := map[string]struct{}{
294+
"usr": {},
295+
"usr/lib": {},
296+
"usr/lib/apk": {},
297+
"usr/lib/apk/db": {},
298+
}
299+
300+
foundDirs := map[string]struct{}{}
301+
302+
// Check each of the first 2 layers (package layers) for directory and file
303+
for i := range 2 {
304+
layer := layers[i]
305+
306+
// Get layer content as tar reader
307+
rc, err := layer.Uncompressed()
308+
if err != nil {
309+
t.Fatalf("failed to get layer %d content: %v", i, err)
310+
}
311+
312+
tr := tar.NewReader(rc)
313+
314+
for {
315+
header, err := tr.Next()
316+
if err == io.EOF {
317+
break
318+
}
319+
if err != nil {
320+
t.Fatalf("failed to read tar entry in layer %d: %v", i, err)
321+
}
322+
323+
// Check for the parent directory
324+
if _, ok := wantDirs[header.Name]; ok && header.Typeflag == tar.TypeDir {
325+
foundDirs[header.Name] = struct{}{}
326+
}
327+
328+
// Check for the installed DB file
329+
if header.Name == "usr/lib/apk/db/installed" && header.Typeflag == tar.TypeReg {
330+
// Verify the file has content
331+
content, err := io.ReadAll(tr)
332+
if err != nil {
333+
t.Fatalf("failed to read installed DB content in layer %d: %v", i, err)
334+
}
335+
if len(content) == 0 {
336+
t.Errorf("installed DB file in layer %d is empty", i)
337+
}
338+
}
339+
}
340+
341+
rc.Close()
342+
343+
// Verify both directory and file were found
344+
for dir := range wantDirs {
345+
if _, ok := foundDirs[dir]; !ok {
346+
t.Errorf("layer %d missing parent directory %q - this indicates the directory creation fix is not working", i, dir)
347+
}
348+
}
349+
}
350+
}

0 commit comments

Comments
 (0)