From dee731e22b58e8720aab721e140daefe86a9bd68 Mon Sep 17 00:00:00 2001 From: Matej Vasek Date: Thu, 13 Jul 2023 21:03:41 +0200 Subject: [PATCH] fix: OCI content creation by "host" builder on Win Signed-off-by: Matej Vasek --- pkg/oci/builder_test.go | 95 ++++++++++++++++++++++++++++++++++++-- pkg/oci/containerize.go | 3 +- pkg/oci/containerize_go.go | 5 +- 3 files changed, 97 insertions(+), 6 deletions(-) diff --git a/pkg/oci/builder_test.go b/pkg/oci/builder_test.go index 0def0164a1..d5563af6df 100644 --- a/pkg/oci/builder_test.go +++ b/pkg/oci/builder_test.go @@ -1,15 +1,22 @@ package oci import ( + "archive/tar" + "compress/gzip" "context" "encoding/json" "errors" "fmt" + "io" + "io/fs" "os" "path/filepath" "runtime" + "sort" + "strings" "testing" + "github.com/google/go-cmp/cmp" v1 "github.com/google/go-containerregistry/pkg/v1" fn "knative.dev/func/pkg/functions" . "knative.dev/func/pkg/testing" @@ -172,8 +179,88 @@ func validateOCI(path string, t *testing.T) { t.Fatalf("invalid schema version, expected 2, got %d", imageIndex.SchemaVersion) } - // Additional validation of the Image Index structure can be added here - // extract. for example checking that the path includes the README.md - // and one of the binaries in the exact location expected (the data layer - // blob and exec layer blob, respectively) + if len(imageIndex.Manifests) < 1 { + t.Fatal("fewer manifests") + } + + digestParts := strings.SplitN(imageIndex.Manifests[0].Digest, ":", 2) + if len(digestParts) < 2 { + t.Fatal("invalid digest format") + } + manifestFile := filepath.Join(path, "blobs", "sha256", digestParts[1]) + manifestFileData, err := os.ReadFile(manifestFile) + if err != nil { + t.Fatal(err) + } + mf := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{} + err = json.Unmarshal(manifestFileData, &mf) + if err != nil { + t.Fatal(err) + } + + type fileInfo struct { + Path string + Type fs.FileMode + Executable bool + } + var files []fileInfo + + for _, layer := range mf.Layers { + func() { + digestParts = strings.SplitN(layer.Digest, ":", 2) + if len(digestParts) < 2 { + t.Fatal("invalid digest format") + } + f, err := os.Open(filepath.Join(path, "blobs", "sha256", digestParts[1])) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + gr, err := gzip.NewReader(f) + if err != nil { + t.Fatal(err) + } + defer gr.Close() + + tr := tar.NewReader(gr) + for { + hdr, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + t.Fatal(err) + } + files = append(files, fileInfo{ + Path: hdr.Name, + Type: hdr.FileInfo().Mode() & fs.ModeType, + Executable: (hdr.FileInfo().Mode()&0111 == 0111) && !hdr.FileInfo().IsDir(), + }) + } + }() + } + sort.Slice(files, func(i, j int) bool { + return files[i].Path < files[j].Path + }) + + expectedFiles := []fileInfo{ + {Path: "/etc/pki/tls/certs/ca-certificates.crt"}, + {Path: "/etc/ssl/certs/ca-certificates.crt"}, + {Path: "/func", Type: fs.ModeDir}, + {Path: "/func/README.md"}, + {Path: "/func/f", Executable: true}, + {Path: "/func/func.yaml"}, + {Path: "/func/go.mod"}, + {Path: "/func/handle.go"}, + {Path: "/func/handle_test.go"}, + } + + if diff := cmp.Diff(expectedFiles, files); diff != "" { + t.Error("files in oci differ from expectation (-want, +got):", diff) + } } diff --git a/pkg/oci/containerize.go b/pkg/oci/containerize.go index e69bd7bfd7..768be07144 100644 --- a/pkg/oci/containerize.go +++ b/pkg/oci/containerize.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + slashpath "path" "path/filepath" "strings" @@ -159,7 +160,7 @@ func newDataTarball(source, target string, ignored []string, verbose bool) error return err } - header.Name = filepath.Join("/func", relPath) + header.Name = slashpath.Join("/func", filepath.ToSlash(relPath)) // TODO: should we set file timestamps to the build start time of cfg.t? // header.ModTime = timestampArgument diff --git a/pkg/oci/containerize_go.go b/pkg/oci/containerize_go.go index f6fd6fd0aa..8dce2ac884 100644 --- a/pkg/oci/containerize_go.go +++ b/pkg/oci/containerize_go.go @@ -5,8 +5,10 @@ import ( "compress/gzip" "fmt" "io" + "io/fs" "os" "os/exec" + slashpath "path" "path/filepath" "strings" @@ -169,8 +171,9 @@ func newExecTarball(source, target string, verbose bool) error { if err != nil { return err } + header.Mode = (header.Mode & ^int64(fs.ModePerm)) | 0755 - header.Name = path("/func", "f") + header.Name = slashpath.Join("/func", "f") // TODO: should we set file timestamps to the build start time of cfg.t? // header.ModTime = timestampArgument