From 2717599f9395c3660036fe2aa52adf27a2c87a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Rod=C3=A1k?= Date: Mon, 19 May 2025 14:25:01 +0200 Subject: [PATCH] Ensure extendedGlob returns paths in lexical order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `filepath.Glob` function does not provide deterministic output. In order to achieve a reproducible build, files must be copied in a deterministic manner, and `filepath.Glob` did not guarantee this. Other functions such as `filepath.Walk` and `os.ReadDir` return deterministic output. So copying files to the image is done in the same order each time. Fixes: https://issues.redhat.com/browse/RUN-2661 Signed-off-by: Jan Rodák --- copier/copier.go | 3 +++ copier/copier_test.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/copier/copier.go b/copier/copier.go index d989c23e199..38468e2ae39 100644 --- a/copier/copier.go +++ b/copier/copier.go @@ -14,6 +14,7 @@ import ( "path" "path/filepath" "slices" + "sort" "strconv" "strings" "sync" @@ -48,6 +49,7 @@ func init() { // "**" component in the pattern, filepath.Glob() will be called with the "**" // replaced with all of the subdirectories under that point, and the results // will be concatenated. +// The matched paths are returned in lexical order, which makes the output deterministic. func extendedGlob(pattern string) (matches []string, err error) { subdirs := func(dir string) []string { var subdirectories []string @@ -113,6 +115,7 @@ func extendedGlob(pattern string) (matches []string, err error) { } matches = append(matches, theseMatches...) } + sort.Strings(matches) return matches, nil } diff --git a/copier/copier_test.go b/copier/copier_test.go index 0b4dc87c634..14469ad944e 100644 --- a/copier/copier_test.go +++ b/copier/copier_test.go @@ -2341,3 +2341,18 @@ func TestConditionalRemoveNoChroot(t *testing.T) { testConditionalRemove(t) canChroot = couldChroot } + +func TestSortedExtendedGlob(t *testing.T) { + tmpdir := t.TempDir() + buf := []byte("buffer") + expect := []string{} + for _, name := range []string{"z", "y", "x", "a", "b", "c", "d", "e", "f"} { + require.NoError(t, os.WriteFile(filepath.Join(tmpdir, name), buf, 0o600)) + expect = append(expect, filepath.Join(tmpdir, name)) + } + sort.Strings(expect) + + matched, err := extendedGlob(filepath.Join(tmpdir, "*")) + require.NoError(t, err, "globbing") + require.ElementsMatch(t, expect, matched, "sorted globbing") +}