From a99e5c381d730a17d918b77b9c62e0c4890d3e9b Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Mon, 6 May 2024 19:56:05 +0200 Subject: [PATCH] Port Sideload tests; all tests pass --- pkg/envtest/setup/env/env.go | 5 +- pkg/envtest/setup/list/list_test.go | 3 +- pkg/envtest/setup/sideload/sideload_test.go | 76 +++++++++++++++++++++ pkg/envtest/setup/store/store.go | 2 +- pkg/envtest/setup/testhelpers/package.go | 52 ++++++++++++++ pkg/envtest/setup/testhelpers/remote.go | 71 +++++++------------ 6 files changed, 159 insertions(+), 50 deletions(-) create mode 100644 pkg/envtest/setup/sideload/sideload_test.go create mode 100644 pkg/envtest/setup/testhelpers/package.go diff --git a/pkg/envtest/setup/env/env.go b/pkg/envtest/setup/env/env.go index 44cc75b271..147bedee6f 100644 --- a/pkg/envtest/setup/env/env.go +++ b/pkg/envtest/setup/env/env.go @@ -34,8 +34,11 @@ func WithStore(store *store.Store) Option { func WithClient(client *remote.Client) Option { return func(c *Env) { c.Client = client } } // WithFS allows injecting a configured fs.FS, e.g. for mocking. +// TODO: fix this so it's actually used! func WithFS(fs fs.FS) Option { - return func(c *Env) { c.FS = fs } + return func(c *Env) { + c.FS = fs + } } // New returns a new environment, configured with the provided options. diff --git a/pkg/envtest/setup/list/list_test.go b/pkg/envtest/setup/list/list_test.go index 10d2c1d4ba..9c3547e306 100644 --- a/pkg/envtest/setup/list/list_test.go +++ b/pkg/envtest/setup/list/list_test.go @@ -38,7 +38,8 @@ var _ = Describe("List", func() { ) JustBeforeEach(func() { - addr, shutdown := testhelpers.NewServer() + addr, shutdown, err := testhelpers.NewServer() + Expect(err).NotTo(HaveOccurred()) DeferCleanup(shutdown) envOpts = append( diff --git a/pkg/envtest/setup/sideload/sideload_test.go b/pkg/envtest/setup/sideload/sideload_test.go new file mode 100644 index 0000000000..a447a25733 --- /dev/null +++ b/pkg/envtest/setup/sideload/sideload_test.go @@ -0,0 +1,76 @@ +package sideload_test + +import ( + "bytes" + "context" + "io" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/envtest/setup/env" + "sigs.k8s.io/controller-runtime/pkg/envtest/setup/sideload" + "sigs.k8s.io/controller-runtime/pkg/envtest/setup/testhelpers" + "sigs.k8s.io/controller-runtime/pkg/envtest/setup/versions" +) + +var ( + testLog logr.Logger + ctx context.Context +) + +func TestEnv(t *testing.T) { + testLog = testhelpers.GetLogger() + ctx = logr.NewContext(context.Background(), testLog) + + RegisterFailHandler(Fail) + RunSpecs(t, "Sideload Suite") +} + +var _ = Describe("Sideload", func() { + var ( + prefix = "a-test-package" + input io.Reader + ) + BeforeEach(func() { + contents, err := testhelpers.ContentsFor(prefix) + Expect(err).NotTo(HaveOccurred()) + + input = bytes.NewReader(contents) + }) + + It("should fail if a non-concrete version is given", func() { + err := sideload.Sideload(ctx, versions.LatestVersion) + Expect(err).To(HaveOccurred()) + }) + + It("should fail if a non-concrete platform is given", func() { + err := sideload.Sideload(ctx, versions.Spec{Selector: &versions.Concrete{Major: 1, Minor: 2, Patch: 3}}, sideload.WithPlatform("*", "*")) + Expect(err).To(HaveOccurred()) + }) + + It("should load the given tarball into our store as the given version", func() { + v := &versions.Concrete{Major: 1, Minor: 2, Patch: 3} + store := testhelpers.NewMockStore() + Expect(sideload.Sideload( + ctx, + versions.Spec{Selector: v}, + sideload.WithInput(input), + sideload.WithEnvOptions(env.WithStore(store)), + )).To(Succeed()) + + baseName := versions.Platform{OS: runtime.GOOS, Arch: runtime.GOARCH}.BaseName(*v) + expectedPath := filepath.Join("k8s", baseName, prefix) + + outFile, err := store.Root.Open(expectedPath) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(outFile.Close) + contents, err := io.ReadAll(outFile) + Expect(err).NotTo(HaveOccurred()) + Expect(contents).To(HavePrefix(prefix)) + }) +}) diff --git a/pkg/envtest/setup/store/store.go b/pkg/envtest/setup/store/store.go index 1e8173f9b5..88acde4131 100644 --- a/pkg/envtest/setup/store/store.go +++ b/pkg/envtest/setup/store/store.go @@ -182,7 +182,7 @@ func (s *Store) Add(ctx context.Context, item Item, contents io.Reader) (resErr return err } } - if err != nil && !errors.Is(err, io.EOF) { //nolint:govet + if !errors.Is(err, io.EOF) { return fmt.Errorf("unable to finish un-tar-ing the downloaded archive: %w", err) } log.V(1).Info("unpacked archive") diff --git a/pkg/envtest/setup/testhelpers/package.go b/pkg/envtest/setup/testhelpers/package.go new file mode 100644 index 0000000000..28aea358c1 --- /dev/null +++ b/pkg/envtest/setup/testhelpers/package.go @@ -0,0 +1,52 @@ +package testhelpers + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto/md5" //nolint:gosec + "crypto/rand" + "encoding/base64" + "fmt" + "path/filepath" +) + +func ContentsFor(filename string) ([]byte, error) { //nolint:revive + var chunk [1024 * 48]byte // 1.5 times our chunk read size in GetVersion + copy(chunk[:], filename) + if _, err := rand.Read(chunk[len(filename):]); err != nil { + return nil, err + } + + out := new(bytes.Buffer) + gzipWriter := gzip.NewWriter(out) + + tarWriter := tar.NewWriter(gzipWriter) + + if err := tarWriter.WriteHeader(&tar.Header{ + Name: filepath.Join("kubebuilder/bin", filename), + Size: int64(len(chunk)), + Mode: 0777, // so we can check that we fix this later + }); err != nil { + return nil, fmt.Errorf("write tar header: %w", err) + } + if _, err := tarWriter.Write(chunk[:]); err != nil { + return nil, fmt.Errorf("write tar contents: %w", err) + } + + // can't defer these, because they need to happen before out.Bytes() below + tarWriter.Close() + gzipWriter.Close() + + return out.Bytes(), nil +} + +func verWith(name string, contents []byte) item { + res := item{ + meta: bucketObject{Name: name}, + contents: contents, + } + hash := md5.Sum(res.contents) //nolint:gosec + res.meta.Hash = base64.StdEncoding.EncodeToString(hash[:]) + return res +} diff --git a/pkg/envtest/setup/testhelpers/remote.go b/pkg/envtest/setup/testhelpers/remote.go index 50ba238069..1447bce7f0 100644 --- a/pkg/envtest/setup/testhelpers/remote.go +++ b/pkg/envtest/setup/testhelpers/remote.go @@ -1,12 +1,7 @@ package testhelpers import ( - "archive/tar" - "bytes" - "compress/gzip" - "crypto/md5" //nolint:gosec - "crypto/rand" - "encoding/base64" + "errors" "net/http" "github.com/onsi/gomega" @@ -73,57 +68,47 @@ var ( contents map[string]item ) -func makeContents(names []string) []item { +func makeContents(names []string) ([]item, error) { res := make([]item, len(names)) if contents == nil { contents = make(map[string]item, len(RemoteNames)) } + var errs error for i, name := range names { if item, ok := contents[name]; ok { res[i] = item continue } - var chunk [1024 * 48]byte // 1.5 times our chunk read size in GetVersion - copy(chunk[:], name) - if _, err := rand.Read(chunk[len(name):]); err != nil { - panic(err) + chunk, err := ContentsFor(name) + if err != nil { + errs = errors.Join(errs, err) + continue } - item := verWith(name, chunk[:]) + + item := verWith(name, chunk) contents[name] = item res[i] = item } - return res -} -func verWith(name string, contents []byte) item { - out := new(bytes.Buffer) - gzipWriter := gzip.NewWriter(out) - tarWriter := tar.NewWriter(gzipWriter) - err := tarWriter.WriteHeader(&tar.Header{ - Name: "kubebuilder/bin/some-file", - Size: int64(len(contents)), - Mode: 0777, // so we can check that we fix this later - }) - if err != nil { - panic(err) + + if errs != nil { + return nil, errs } - _, err = tarWriter.Write(contents) + + return res, nil +} + +// NewServer spins up a mock server that knows about the provided packages. +// The package names should be a subset of RemoteNames. +// +// The returned shutdown function should be called at the end of the test +func NewServer() (addr string, shutdown func(), err error) { + versions, err := makeContents(RemoteNames) if err != nil { - panic(err) - } - tarWriter.Close() - gzipWriter.Close() - res := item{ - meta: bucketObject{Name: name}, - contents: out.Bytes(), + return } - hash := md5.Sum(res.contents) //nolint:gosec - res.meta.Hash = base64.StdEncoding.EncodeToString(hash[:]) - return res -} -func configure(versions []item) (string, func()) { server := ghttp.NewServer() list := objectList{Items: make([]bucketObject, len(versions))} @@ -147,13 +132,5 @@ func configure(versions []item) (string, func()) { list, )) - return server.Addr(), server.Close -} - -// NewServer spins up a mock server that knows about the provided packages. -// The package names should be a subset of RemoteNames. -// -// The returned shutdown function should be called at the end of the test -func NewServer() (addr string, shutdown func()) { - return configure(makeContents(RemoteNames)) + return server.Addr(), server.Close, nil }