From 9c5c9c3515207c907406afb62518536d726fb807 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Tue, 29 Sep 2026 14:20:26 +0000 Subject: [PATCH] mkctr: return an error instead of panicking on a missing --files source The filepath.WalkDir callback in layerFromFiles ignored its err argument. When a source path passed to --files did not exist, WalkDir invoked the callback with a nil DirEntry and the callback dereferenced it, crashing with a nil pointer panic. Return the walk error, and also stop ignoring the error from writing directory headers. Fixes #34 Signed-off-by: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> --- mkctr.go | 7 +++++- mkctr_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 mkctr_test.go diff --git a/mkctr.go b/mkctr.go index 86a3882..0322ed7 100644 --- a/mkctr.go +++ b/mkctr.go @@ -570,9 +570,14 @@ func layerFromFiles(logf logf, files map[string]string, layerMediaType types.Med } for src, dst := range files { err := filepath.WalkDir(src, func(srcWalk string, d fs.DirEntry, err error) error { + if err != nil { + return err + } path := strings.TrimPrefix(srcWalk, src) dstWalk := filepath.Join(dst, path) - writeDir(filepath.Dir(dstWalk)) + if err := writeDir(filepath.Dir(dstWalk)); err != nil { + return err + } if d.IsDir() { return writeDir(dstWalk) } diff --git a/mkctr_test.go b/mkctr_test.go new file mode 100644 index 0000000..62cbd8e --- /dev/null +++ b/mkctr_test.go @@ -0,0 +1,64 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "archive/tar" + "io" + "os" + "path/filepath" + "slices" + "strings" + "testing" + + "github.com/google/go-containerregistry/pkg/v1/types" +) + +func TestLayerFromFilesMissingSource(t *testing.T) { + missing := filepath.Join(t.TempDir(), "does-not-exist") + files := map[string]string{missing: "/app/file"} + logf := func(string, ...any) {} + + _, err := layerFromFiles(logf, files, types.OCILayer) + if err == nil { + t.Fatal("layerFromFiles succeeded for a source path that does not exist") + } + if !strings.Contains(err.Error(), "does-not-exist") { + t.Errorf("error = %q; want it to name the missing path", err) + } +} + +func TestLayerFromFilesCopiesFiles(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hello"), 0o644); err != nil { + t.Fatal(err) + } + logf := func(string, ...any) {} + + layer, err := layerFromFiles(logf, map[string]string{dir: "/app"}, types.OCILayer) + if err != nil { + t.Fatal(err) + } + rc, err := layer.Uncompressed() + if err != nil { + t.Fatal(err) + } + defer rc.Close() + var got []string + tr := tar.NewReader(rc) + for { + h, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + got = append(got, h.Name) + } + if !slices.Contains(got, "/app/a.txt") { + t.Errorf("layer entries = %q; want /app/a.txt", got) + } +}