From b3a4f857a9e5b9efaba9a01a3b77779640fc4395 Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Fri, 3 May 2024 14:09:43 +0200 Subject: [PATCH] Nuke stuff that has been ported --- tools/setup-envtest/remote/client.go | 219 ------------- tools/setup-envtest/store/helpers.go | 57 ---- tools/setup-envtest/store/store.go | 305 ------------------ tools/setup-envtest/store/store_suite_test.go | 51 --- tools/setup-envtest/store/store_test.go | 250 -------------- tools/setup-envtest/versions/misc_test.go | 143 -------- tools/setup-envtest/versions/parse.go | 122 ------- tools/setup-envtest/versions/parse_test.go | 95 ------ tools/setup-envtest/versions/platform.go | 85 ----- .../setup-envtest/versions/selectors_test.go | 216 ------------- tools/setup-envtest/versions/version.go | 234 -------------- .../versions/versions_suite_test.go | 29 -- 12 files changed, 1806 deletions(-) delete mode 100644 tools/setup-envtest/remote/client.go delete mode 100644 tools/setup-envtest/store/helpers.go delete mode 100644 tools/setup-envtest/store/store.go delete mode 100644 tools/setup-envtest/store/store_suite_test.go delete mode 100644 tools/setup-envtest/store/store_test.go delete mode 100644 tools/setup-envtest/versions/misc_test.go delete mode 100644 tools/setup-envtest/versions/parse.go delete mode 100644 tools/setup-envtest/versions/parse_test.go delete mode 100644 tools/setup-envtest/versions/platform.go delete mode 100644 tools/setup-envtest/versions/selectors_test.go delete mode 100644 tools/setup-envtest/versions/version.go delete mode 100644 tools/setup-envtest/versions/versions_suite_test.go diff --git a/tools/setup-envtest/remote/client.go b/tools/setup-envtest/remote/client.go deleted file mode 100644 index be82532583..0000000000 --- a/tools/setup-envtest/remote/client.go +++ /dev/null @@ -1,219 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2021 The Kubernetes Authors - -package remote - -import ( - "context" - "crypto/md5" //nolint:gosec - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "path" - "sort" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/tools/setup-envtest/versions" -) - -// objectList is the parts we need of the GCS "list-objects-in-bucket" endpoint. -type objectList struct { - Items []bucketObject `json:"items"` - NextPageToken string `json:"nextPageToken"` -} - -// bucketObject is the parts we need of the GCS object metadata. -type bucketObject struct { - Name string `json:"name"` - Hash string `json:"md5Hash"` -} - -// Client is a basic client for fetching versions of the envtest binary archives -// from GCS. -type Client struct { - // Bucket is the bucket to fetch from. - Bucket string - - // Server is the GCS-like storage server - Server string - - // Log allows us to log. - Log logr.Logger - - // Insecure uses http for testing - Insecure bool -} - -func (c *Client) scheme() string { - if c.Insecure { - return "http" - } - return "https" -} - -// ListVersions lists all available tools versions in the given bucket, along -// with supported os/arch combos and the corresponding hash. -// -// The results are sorted with newer versions first. -func (c *Client) ListVersions(ctx context.Context) ([]versions.Set, error) { - loc := &url.URL{ - Scheme: c.scheme(), - Host: c.Server, - Path: path.Join("/storage/v1/b/", c.Bucket, "o"), - } - query := make(url.Values) - - knownVersions := map[versions.Concrete][]versions.PlatformItem{} - for cont := true; cont; { - c.Log.V(1).Info("listing bucket to get versions", "bucket", c.Bucket) - - loc.RawQuery = query.Encode() - req, err := http.NewRequestWithContext(ctx, "GET", loc.String(), nil) - if err != nil { - return nil, fmt.Errorf("unable to construct request to list bucket items: %w", err) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, fmt.Errorf("unable to perform request to list bucket items: %w", err) - } - - err = func() error { - defer resp.Body.Close() - if resp.StatusCode != 200 { - return fmt.Errorf("unable list bucket items -- got status %q from GCS", resp.Status) - } - - var list objectList - if err := json.NewDecoder(resp.Body).Decode(&list); err != nil { - return fmt.Errorf("unable unmarshal bucket items list: %w", err) - } - - // continue listing if needed - cont = list.NextPageToken != "" - query.Set("pageToken", list.NextPageToken) - - for _, item := range list.Items { - ver, details := versions.ExtractWithPlatform(versions.ArchiveRE, item.Name) - if ver == nil { - c.Log.V(1).Info("skipping bucket object -- does not appear to be a versioned tools object", "name", item.Name) - continue - } - c.Log.V(1).Info("found version", "version", ver, "platform", details) - knownVersions[*ver] = append(knownVersions[*ver], versions.PlatformItem{ - Platform: details, - MD5: item.Hash, - }) - } - - return nil - }() - if err != nil { - return nil, err - } - } - - res := make([]versions.Set, 0, len(knownVersions)) - for ver, details := range knownVersions { - res = append(res, versions.Set{Version: ver, Platforms: details}) - } - // sort in inverse order so that the newest one is first - sort.Slice(res, func(i, j int) bool { - first, second := res[i].Version, res[j].Version - return first.NewerThan(second) - }) - - return res, nil -} - -// GetVersion downloads the given concrete version for the given concrete platform, writing it to the out. -func (c *Client) GetVersion(ctx context.Context, version versions.Concrete, platform versions.PlatformItem, out io.Writer) error { - itemName := platform.ArchiveName(version) - loc := &url.URL{ - Scheme: c.scheme(), - Host: c.Server, - Path: path.Join("/storage/v1/b/", c.Bucket, "o", itemName), - RawQuery: "alt=media", - } - - req, err := http.NewRequestWithContext(ctx, "GET", loc.String(), nil) - if err != nil { - return fmt.Errorf("unable to construct request to fetch %s: %w", itemName, err) - } - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("unable to fetch %s (%s): %w", itemName, req.URL, err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return fmt.Errorf("unable fetch %s (%s) -- got status %q from GCS", itemName, req.URL, resp.Status) - } - - if platform.MD5 != "" { - // stream in chunks to do the checksum, don't load the whole thing into - // memory to avoid causing issues with big files. - buf := make([]byte, 32*1024) // 32KiB, same as io.Copy - checksum := md5.New() //nolint:gosec - for cont := true; cont; { - amt, err := resp.Body.Read(buf) - if err != nil && !errors.Is(err, io.EOF) { - return fmt.Errorf("unable read next chunk of %s: %w", itemName, err) - } - if amt > 0 { - // checksum never returns errors according to docs - checksum.Write(buf[:amt]) - if _, err := out.Write(buf[:amt]); err != nil { - return fmt.Errorf("unable write next chunk of %s: %w", itemName, err) - } - } - cont = amt > 0 && !errors.Is(err, io.EOF) - } - - sum := base64.StdEncoding.EncodeToString(checksum.Sum(nil)) - - if sum != platform.MD5 { - return fmt.Errorf("checksum mismatch for %s: %s (computed) != %s (reported from GCS)", itemName, sum, platform.MD5) - } - } else if _, err := io.Copy(out, resp.Body); err != nil { - return fmt.Errorf("unable to download %s: %w", itemName, err) - } - return nil -} - -// FetchSum fetches the checksum for the given concrete version & platform into -// the given platform item. -func (c *Client) FetchSum(ctx context.Context, ver versions.Concrete, pl *versions.PlatformItem) error { - itemName := pl.ArchiveName(ver) - loc := &url.URL{ - Scheme: c.scheme(), - Host: c.Server, - Path: path.Join("/storage/v1/b/", c.Bucket, "o", itemName), - } - - req, err := http.NewRequestWithContext(ctx, "GET", loc.String(), nil) - if err != nil { - return fmt.Errorf("unable to construct request to fetch metadata for %s: %w", itemName, err) - } - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("unable to fetch metadata for %s: %w", itemName, err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return fmt.Errorf("unable fetch metadata for %s -- got status %q from GCS", itemName, resp.Status) - } - - var item bucketObject - if err := json.NewDecoder(resp.Body).Decode(&item); err != nil { - return fmt.Errorf("unable to unmarshal metadata for %s: %w", itemName, err) - } - - pl.MD5 = item.Hash - return nil -} diff --git a/tools/setup-envtest/store/helpers.go b/tools/setup-envtest/store/helpers.go deleted file mode 100644 index 30902187e9..0000000000 --- a/tools/setup-envtest/store/helpers.go +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2021 The Kubernetes Authors - -package store - -import ( - "errors" - "os" - "path/filepath" - "runtime" -) - -// DefaultStoreDir returns the default location for the store. -// It's dependent on operating system: -// -// - Windows: %LocalAppData%\kubebuilder-envtest -// - OSX: ~/Library/Application Support/io.kubebuilder.envtest -// - Others: ${XDG_DATA_HOME:-~/.local/share}/kubebuilder-envtest -// -// Otherwise, it errors out. Note that these paths must not be relied upon -// manually. -func DefaultStoreDir() (string, error) { - var baseDir string - - // find the base data directory - switch runtime.GOOS { - case "windows": - baseDir = os.Getenv("LocalAppData") - if baseDir == "" { - return "", errors.New("%LocalAppData% is not defined") - } - case "darwin", "ios": - homeDir := os.Getenv("HOME") - if homeDir == "" { - return "", errors.New("$HOME is not defined") - } - baseDir = filepath.Join(homeDir, "Library/Application Support") - default: - baseDir = os.Getenv("XDG_DATA_HOME") - if baseDir == "" { - homeDir := os.Getenv("HOME") - if homeDir == "" { - return "", errors.New("neither $XDG_DATA_HOME nor $HOME are defined") - } - baseDir = filepath.Join(homeDir, ".local/share") - } - } - - // append our program-specific dir to it (OSX has a slightly different - // convention so try to follow that). - switch runtime.GOOS { - case "darwin", "ios": - return filepath.Join(baseDir, "io.kubebuilder.envtest"), nil - default: - return filepath.Join(baseDir, "kubebuilder-envtest"), nil - } -} diff --git a/tools/setup-envtest/store/store.go b/tools/setup-envtest/store/store.go deleted file mode 100644 index 6001eb2a4e..0000000000 --- a/tools/setup-envtest/store/store.go +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2021 The Kubernetes Authors - -package store - -import ( - "archive/tar" - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "sort" - - "github.com/go-logr/logr" - "github.com/spf13/afero" - - "sigs.k8s.io/controller-runtime/tools/setup-envtest/versions" -) - -// TODO(directxman12): error messages don't show full path, which is gonna make -// things hard to debug - -// Item is a version-platform pair. -type Item struct { - Version versions.Concrete - Platform versions.Platform -} - -// dirName returns the directory name in the store for this item. -func (i Item) dirName() string { - return i.Platform.BaseName(i.Version) -} -func (i Item) String() string { - return fmt.Sprintf("%s (%s)", i.Version, i.Platform) -} - -// Filter is a version spec & platform selector (i.e. platform -// potentially with wilcards) to filter store items. -type Filter struct { - Version versions.Spec - Platform versions.Platform -} - -// Matches checks if this filter matches the given item. -func (f Filter) Matches(item Item) bool { - return f.Version.Matches(item.Version) && f.Platform.Matches(item.Platform) -} - -// Store knows how to list, load, store, and delete envtest tools. -type Store struct { - // Root is the root FS that the store stores in. You'll probably - // want to use a BasePathFS to scope it down to a particular directory. - // - // Note that if for some reason there are nested BasePathFSes, and they're - // interrupted by a non-BasePathFS, Path won't work properly. - Root afero.Fs -} - -// NewAt creates a new store on disk at the given path. -func NewAt(path string) *Store { - return &Store{ - Root: afero.NewBasePathFs(afero.NewOsFs(), path), - } -} - -// Initialize ensures that the store is all set up on disk, etc. -func (s *Store) Initialize(ctx context.Context) error { - log, err := logr.FromContext(ctx) - if err != nil { - return err - } - - log.V(1).Info("ensuring base binaries dir exists") - if err := s.unpackedBase().MkdirAll("", 0755); err != nil { - return fmt.Errorf("unable to make sure base binaries dir exists: %w", err) - } - return nil -} - -// Has checks if an item exists in the store. -func (s *Store) Has(item Item) (bool, error) { - path := s.unpackedPath(item.dirName()) - _, err := path.Stat("") - if err != nil && !errors.Is(err, afero.ErrFileNotFound) { - return false, fmt.Errorf("unable to check if version-platform dir exists: %w", err) - } - return err == nil, nil -} - -// List lists all items matching the given filter. -// -// Results are stored by version (newest first), and OS/arch (consistently, -// but no guaranteed ordering). -func (s *Store) List(ctx context.Context, matching Filter) ([]Item, error) { - var res []Item - if err := s.eachItem(ctx, matching, func(_ string, item Item) { - res = append(res, item) - }); err != nil { - return nil, fmt.Errorf("unable to list version-platform pairs in store: %w", err) - } - - sort.Slice(res, func(i, j int) bool { - if !res[i].Version.Matches(res[j].Version) { - return res[i].Version.NewerThan(res[j].Version) - } - return orderPlatforms(res[i].Platform, res[j].Platform) - }) - - return res, nil -} - -// Add adds this item to the store, with the given contents (a .tar.gz file). -func (s *Store) Add(ctx context.Context, item Item, contents io.Reader) (resErr error) { - log, err := logr.FromContext(ctx) - if err != nil { - return err - } - - itemName := item.dirName() - log = log.WithValues("version-platform", itemName) - itemPath := s.unpackedPath(itemName) - - // make sure to clean up if we hit an error - defer func() { - if resErr != nil { - // intentially ignore this because we can't really do anything - err := s.removeItem(itemPath) - if err != nil { - log.Error(err, "unable to clean up partially added version-platform pair after error") - } - } - }() - - log.V(1).Info("ensuring version-platform binaries dir exists and is empty & writable") - _, err = itemPath.Stat("") - if err != nil && !errors.Is(err, afero.ErrFileNotFound) { - return fmt.Errorf("unable to ensure version-platform binaries dir %s exists", itemName) - } - if err == nil { // exists - log.V(1).Info("cleaning up old version-platform binaries dir") - if err := s.removeItem(itemPath); err != nil { - return fmt.Errorf("unable to clean up existing version-platform binaries dir %s", itemName) - } - } - if err := itemPath.MkdirAll("", 0755); err != nil { - return fmt.Errorf("unable to make sure entry dir %s exists", itemName) - } - - log.V(1).Info("extracting archive") - gzStream, err := gzip.NewReader(contents) - if err != nil { - return fmt.Errorf("unable to start un-gz-ing entry archive") - } - tarReader := tar.NewReader(gzStream) - - var header *tar.Header - for header, err = tarReader.Next(); err == nil; header, err = tarReader.Next() { - if header.Typeflag != tar.TypeReg { // TODO(directxman12): support symlinks, etc? - log.V(1).Info("skipping non-regular-file entry in archive", "entry", header.Name) - continue - } - // just dump all files to the main path, ignoring the prefixed directory - // paths -- they're redundant. We also ignore bits for the most part (except for X), - // preferfing our own scheme. - targetPath := filepath.Base(header.Name) - log.V(1).Info("writing archive file to disk", "archive file", header.Name, "on-disk file", targetPath) - perms := 0555 & header.Mode // make sure we're at most r+x - binOut, err := itemPath.OpenFile(targetPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(perms)) - if err != nil { - return fmt.Errorf("unable to create file %s from archive to disk for version-platform pair %s", targetPath, itemName) - } - if err := func() error { // IIFE to get the defer properly in a loop - defer binOut.Close() - if _, err := io.Copy(binOut, tarReader); err != nil { //nolint:gosec - return fmt.Errorf("unable to write file %s from archive to disk for version-platform pair %s", targetPath, itemName) - } - return nil - }(); err != nil { - return err - } - } - if err != nil && !errors.Is(err, io.EOF) { //nolint:govet - return fmt.Errorf("unable to finish un-tar-ing the downloaded archive: %w", err) - } - log.V(1).Info("unpacked archive") - - log.V(1).Info("switching version-platform directory to read-only") - if err := itemPath.Chmod("", 0555); err != nil { - // don't bail, this isn't fatal - log.Error(err, "unable to make version-platform directory read-only") - } - return nil -} - -// Remove removes all items matching the given filter. -// -// It returns a list of the successfully removed items (even in the case -// of an error). -func (s *Store) Remove(ctx context.Context, matching Filter) ([]Item, error) { - log, err := logr.FromContext(ctx) - if err != nil { - return nil, err - } - - var removed []Item - var savedErr error - if err := s.eachItem(ctx, matching, func(name string, item Item) { - log.V(1).Info("Removing version-platform pair at path", "version-platform", item, "path", name) - - if err := s.removeItem(s.unpackedPath(name)); err != nil { - log.Error(err, "unable to make existing version-platform dir writable to clean it up", "path", name) - savedErr = fmt.Errorf("unable to remove version-platform pair %s (dir %s): %w", item, name, err) - return // don't mark this as removed in the report - } - removed = append(removed, item) - }); err != nil { - return removed, fmt.Errorf("unable to list version-platform pairs to figure out what to delete: %w", err) - } - if savedErr != nil { - return removed, savedErr - } - return removed, nil -} - -// Path returns an actual path that case be used to access this item. -func (s *Store) Path(item Item) (string, error) { - path := s.unpackedPath(item.dirName()) - // NB(directxman12): we need root's realpath because RealPath only - // looks at its own path, and so thus doesn't prepend the underlying - // root's base path. - // - // Technically, if we're fed something that's double wrapped as root, - // this'll be wrong, but this is basically as much as we can do - return afero.FullBaseFsPath(path.(*afero.BasePathFs), ""), nil -} - -// unpackedBase returns the directory in which item dirs lives. -func (s *Store) unpackedBase() afero.Fs { - return afero.NewBasePathFs(s.Root, "k8s") -} - -// unpackedPath returns the item dir with this name. -func (s *Store) unpackedPath(name string) afero.Fs { - return afero.NewBasePathFs(s.unpackedBase(), name) -} - -// eachItem iterates through the on-disk versions that match our version & platform selector, -// calling the callback for each. -func (s *Store) eachItem(ctx context.Context, filter Filter, cb func(name string, item Item)) error { - log, err := logr.FromContext(ctx) - if err != nil { - return err - } - - entries, err := afero.ReadDir(s.unpackedBase(), "") - if err != nil { - return fmt.Errorf("unable to list folders in store's unpacked directory: %w", err) - } - - for _, entry := range entries { - if !entry.IsDir() { - log.V(1).Info("skipping dir entry, not a folder", "entry", entry.Name()) - continue - } - ver, pl := versions.ExtractWithPlatform(versions.VersionPlatformRE, entry.Name()) - if ver == nil { - log.V(1).Info("skipping dir entry, not a version", "entry", entry.Name()) - continue - } - item := Item{Version: *ver, Platform: pl} - - if !filter.Matches(item) { - log.V(1).Info("skipping on disk version, does not match version and platform selectors", "platform", pl, "version", ver, "entry", entry.Name()) - continue - } - - cb(entry.Name(), item) - } - - return nil -} - -// removeItem removes the given item directory from disk. -func (s *Store) removeItem(itemDir afero.Fs) error { - if err := itemDir.Chmod("", 0755); err != nil { - // no point in trying to remove if we can't fix the permissions, bail here - return fmt.Errorf("unable to make version-platform dir writable: %w", err) - } - if err := itemDir.RemoveAll(""); err != nil && !errors.Is(err, afero.ErrFileNotFound) { - return fmt.Errorf("unable to remove version-platform dir: %w", err) - } - return nil -} - -// orderPlatforms orders platforms by OS then arch. -func orderPlatforms(first, second versions.Platform) bool { - // sort by OS, then arch - if first.OS != second.OS { - return first.OS < second.OS - } - return first.Arch < second.Arch -} diff --git a/tools/setup-envtest/store/store_suite_test.go b/tools/setup-envtest/store/store_suite_test.go deleted file mode 100644 index c2795a3227..0000000000 --- a/tools/setup-envtest/store/store_suite_test.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package store_test - -import ( - "context" - "testing" - - "github.com/go-logr/logr" - "github.com/go-logr/zapr" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var testLog logr.Logger - -func zapLogger() logr.Logger { - testOut := zapcore.AddSync(GinkgoWriter) - enc := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) - // bleh setting up logging to the ginkgo writer is annoying - zapLog := zap.New(zapcore.NewCore(enc, testOut, zap.DebugLevel), - zap.ErrorOutput(testOut), zap.Development(), zap.AddStacktrace(zap.WarnLevel)) - return zapr.NewLogger(zapLog) -} - -func logCtx() context.Context { - return logr.NewContext(context.Background(), testLog) -} - -func TestStore(t *testing.T) { - testLog = zapLogger() - RegisterFailHandler(Fail) - RunSpecs(t, "Store Suite") -} diff --git a/tools/setup-envtest/store/store_test.go b/tools/setup-envtest/store/store_test.go deleted file mode 100644 index d5607aede6..0000000000 --- a/tools/setup-envtest/store/store_test.go +++ /dev/null @@ -1,250 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package store_test - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "crypto/rand" - "io" - "io/fs" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/spf13/afero" - - "sigs.k8s.io/controller-runtime/tools/setup-envtest/store" - "sigs.k8s.io/controller-runtime/tools/setup-envtest/versions" -) - -const ( - fakeStorePath = "/path/to/the/store" -) - -var _ = Describe("Store", func() { - var st *store.Store - BeforeEach(func() { - fs := afero.NewMemMapFs() - fakeStoreFiles(fs, fakeStorePath) - st = &store.Store{ - Root: afero.NewBasePathFs(fs, fakeStorePath), - } - }) - Describe("initialization", func() { - It("should ensure the repo root exists", func() { - // remove the old dir - Expect(st.Root.RemoveAll("")).To(Succeed(), "should be able to remove the store before trying to initialize") - - Expect(st.Initialize(logCtx())).To(Succeed(), "initialization should succeed") - Expect(st.Root.Stat("k8s")).NotTo(BeNil(), "store's binary dir should exist") - }) - - It("should be fine if the repo root already exists", func() { - Expect(st.Initialize(logCtx())).To(Succeed()) - }) - }) - Describe("listing items", func() { - It("should filter results by the given filter, sorted in version order (newest first)", func() { - sel, err := versions.FromExpr("<=1.16") - Expect(err).NotTo(HaveOccurred(), "should be able to construct <=1.16 selector") - Expect(st.List(logCtx(), store.Filter{ - Version: sel, - Platform: versions.Platform{OS: "*", Arch: "amd64"}, - })).To(Equal([]store.Item{ - {Version: ver(1, 16, 2), Platform: versions.Platform{OS: "ifonlysingularitywasstillathing", Arch: "amd64"}}, - {Version: ver(1, 16, 1), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - {Version: ver(1, 16, 0), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - {Version: ver(1, 14, 26), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - })) - }) - It("should skip non-folders in the store", func() { - Expect(afero.WriteFile(st.Root, "k8s/2.3.6-linux-amd128", []byte{0x01}, fs.ModePerm)).To(Succeed(), "should be able to create a non-store file in the store directory") - Expect(st.List(logCtx(), store.Filter{ - Version: versions.AnyVersion, Platform: versions.Platform{OS: "linux", Arch: "amd128"}, - })).To(BeEmpty()) - }) - - It("should skip non-matching names in the store", func() { - Expect(st.Root.Mkdir("k8s/somedir-2.3.6-linux-amd128", fs.ModePerm)).To(Succeed(), "should be able to create a non-store file in the store directory") - Expect(st.List(logCtx(), store.Filter{ - Version: versions.AnyVersion, Platform: versions.Platform{OS: "linux", Arch: "amd128"}, - })).To(BeEmpty()) - }) - }) - - Describe("removing items", func() { - var res []store.Item - BeforeEach(func() { - sel, err := versions.FromExpr("<=1.16") - Expect(err).NotTo(HaveOccurred(), "should be able to construct <=1.16 selector") - res, err = st.Remove(logCtx(), store.Filter{ - Version: sel, - Platform: versions.Platform{OS: "*", Arch: "amd64"}, - }) - Expect(err).NotTo(HaveOccurred(), "should be able to remove <=1.16 & */amd64") - }) - It("should return all items removed", func() { - Expect(res).To(ConsistOf( - store.Item{Version: ver(1, 16, 2), Platform: versions.Platform{OS: "ifonlysingularitywasstillathing", Arch: "amd64"}}, - store.Item{Version: ver(1, 16, 1), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - store.Item{Version: ver(1, 16, 0), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - store.Item{Version: ver(1, 14, 26), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - )) - }) - It("should remove all items matching the given filter from disk", func() { - Expect(afero.ReadDir(st.Root, "k8s")).NotTo(ContainElements( - WithTransform(fs.FileInfo.Name, Equal("1.16.2-ifonlysingularitywasstillathing-amd64")), - WithTransform(fs.FileInfo.Name, Equal("1.16.1-linux-amd64")), - WithTransform(fs.FileInfo.Name, Equal("1.16.0-linux-amd64")), - WithTransform(fs.FileInfo.Name, Equal("1.14.26-linux-amd64")), - )) - }) - - It("should leave items that don't match in place", func() { - Expect(afero.ReadDir(st.Root, "k8s")).To(ContainElements( - WithTransform(fs.FileInfo.Name, Equal("1.17.9-linux-amd64")), - WithTransform(fs.FileInfo.Name, Equal("1.16.2-linux-yourimagination")), - WithTransform(fs.FileInfo.Name, Equal("1.14.26-hyperwarp-pixiedust")), - )) - }) - }) - - Describe("adding items", func() { - It("should support .tar.gz input", func() { - Expect(st.Add(logCtx(), newItem, makeFakeArchive(newName))).To(Succeed()) - Expect(st.Has(newItem)).To(BeTrue(), "should have the item after adding it") - }) - - It("should extract binaries from the given archive to a directly to the item's directory, regardless of path", func() { - Expect(st.Add(logCtx(), newItem, makeFakeArchive(newName))).To(Succeed()) - - dirName := newItem.Platform.BaseName(newItem.Version) - Expect(afero.ReadFile(st.Root, filepath.Join("k8s", dirName, "some-file"))).To(HavePrefix(newName + "some-file")) - Expect(afero.ReadFile(st.Root, filepath.Join("k8s", dirName, "other-file"))).To(HavePrefix(newName + "other-file")) - }) - - It("should clean up any existing item directory before creating the new one", func() { - item := localVersions[0] - Expect(st.Add(logCtx(), item, makeFakeArchive(newName))).To(Succeed()) - Expect(st.Root.Stat(filepath.Join("k8s", item.Platform.BaseName(item.Version)))).NotTo(BeNil(), "new files should exist") - }) - It("should clean up if it errors before finishing", func() { - item := localVersions[0] - Expect(st.Add(logCtx(), item, new(bytes.Buffer))).NotTo(Succeed(), "should fail to extract") - _, err := st.Root.Stat(filepath.Join("k8s", item.Platform.BaseName(item.Version))) - Expect(err).To(HaveOccurred(), "the binaries dir for the item should be gone") - - }) - }) - - Describe("checking if items are present", func() { - It("should report that present directories are present", func() { - Expect(st.Has(localVersions[0])).To(BeTrue()) - }) - - It("should report that absent directories are absent", func() { - Expect(st.Has(newItem)).To(BeFalse()) - }) - }) - - Describe("getting the path", func() { - It("should return the absolute on-disk path of the given item", func() { - item := localVersions[0] - Expect(st.Path(item)).To(Equal(filepath.Join(fakeStorePath, "k8s", item.Platform.BaseName(item.Version)))) - }) - }) -}) - -var ( - // keep this sorted. - localVersions = []store.Item{ - {Version: ver(1, 17, 9), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - {Version: ver(1, 16, 2), Platform: versions.Platform{OS: "linux", Arch: "yourimagination"}}, - {Version: ver(1, 16, 2), Platform: versions.Platform{OS: "ifonlysingularitywasstillathing", Arch: "amd64"}}, - {Version: ver(1, 16, 1), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - {Version: ver(1, 16, 0), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - {Version: ver(1, 14, 26), Platform: versions.Platform{OS: "linux", Arch: "amd64"}}, - {Version: ver(1, 14, 26), Platform: versions.Platform{OS: "hyperwarp", Arch: "pixiedust"}}, - } - - newItem = store.Item{ - Version: ver(1, 16, 3), - Platform: versions.Platform{OS: "linux", Arch: "amd64"}, - } - - newName = "kubebuilder-tools-1.16.3-linux-amd64.tar.gz" -) - -func ver(major, minor, patch int) versions.Concrete { - return versions.Concrete{ - Major: major, - Minor: minor, - Patch: patch, - } -} - -func makeFakeArchive(magic string) io.Reader { - out := new(bytes.Buffer) - gzipWriter := gzip.NewWriter(out) - tarWriter := tar.NewWriter(gzipWriter) - Expect(tarWriter.WriteHeader(&tar.Header{ - Typeflag: tar.TypeDir, - Name: "kubebuilder/bin/", // so we can ensure we skip non-files - Mode: 0777, - })).To(Succeed()) - for _, fileName := range []string{"some-file", "other-file"} { - // create fake file contents: magic+fileName+randomBytes() - var chunk [1024 * 48]byte // 1.5 times our chunk read size in GetVersion - copy(chunk[:], magic) - copy(chunk[len(magic):], fileName) - start := len(magic) + len(fileName) - if _, err := rand.Read(chunk[start:]); err != nil { - panic(err) - } - - // write to kubebuilder/bin/fileName - err := tarWriter.WriteHeader(&tar.Header{ - Name: "kubebuilder/bin/" + fileName, - Size: int64(len(chunk[:])), - Mode: 0777, // so we can check that we fix this later - }) - if err != nil { - panic(err) - } - _, err = tarWriter.Write(chunk[:]) - if err != nil { - panic(err) - } - } - tarWriter.Close() - gzipWriter.Close() - - return out -} - -func fakeStoreFiles(fs afero.Fs, dir string) { - By("making the unpacked directory") - unpackedBase := filepath.Join(dir, "k8s") - Expect(fs.Mkdir(unpackedBase, 0755)).To(Succeed()) - - By("making some fake (empty) versions") - for _, item := range localVersions { - Expect(fs.Mkdir(filepath.Join(unpackedBase, item.Platform.BaseName(item.Version)), 0755)).To(Succeed()) - } -} diff --git a/tools/setup-envtest/versions/misc_test.go b/tools/setup-envtest/versions/misc_test.go deleted file mode 100644 index 8a97de0410..0000000000 --- a/tools/setup-envtest/versions/misc_test.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package versions_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - . "sigs.k8s.io/controller-runtime/tools/setup-envtest/versions" -) - -var _ = Describe("Concrete", func() { - It("should match the only same version", func() { - ver16 := Concrete{Major: 1, Minor: 16} - ver17 := Concrete{Major: 1, Minor: 17} - Expect(ver16.Matches(ver16)).To(BeTrue(), "should match the same version") - Expect(ver16.Matches(ver17)).To(BeFalse(), "should not match a different version") - }) - It("should serialize as X.Y.Z", func() { - Expect(Concrete{Major: 1, Minor: 16, Patch: 3}.String()).To(Equal("1.16.3")) - }) - Describe("when ordering relative to other versions", func() { - ver1163 := Concrete{Major: 1, Minor: 16, Patch: 3} - Specify("newer patch should be newer", func() { - Expect(ver1163.NewerThan(Concrete{Major: 1, Minor: 16})).To(BeTrue()) - }) - Specify("newer minor should be newer", func() { - Expect(ver1163.NewerThan(Concrete{Major: 1, Minor: 15, Patch: 3})).To(BeTrue()) - }) - Specify("newer major should be newer", func() { - Expect(ver1163.NewerThan(Concrete{Major: 0, Minor: 16, Patch: 3})).To(BeTrue()) - }) - }) -}) - -var _ = Describe("Platform", func() { - Specify("a concrete platform should match exactly itself", func() { - plat1 := Platform{OS: "linux", Arch: "amd64"} - plat2 := Platform{OS: "linux", Arch: "s390x"} - plat3 := Platform{OS: "windows", Arch: "amd64"} - Expect(plat1.Matches(plat1)).To(BeTrue(), "should match itself") - Expect(plat1.Matches(plat2)).To(BeFalse(), "should reject a different arch") - Expect(plat1.Matches(plat3)).To(BeFalse(), "should reject a different os") - }) - Specify("a wildcard arch should match any arch", func() { - sel := Platform{OS: "linux", Arch: "*"} - plat1 := Platform{OS: "linux", Arch: "amd64"} - plat2 := Platform{OS: "linux", Arch: "s390x"} - plat3 := Platform{OS: "windows", Arch: "amd64"} - Expect(sel.Matches(sel)).To(BeTrue(), "should match itself") - Expect(sel.Matches(plat1)).To(BeTrue(), "should match some arch with the same OS") - Expect(sel.Matches(plat2)).To(BeTrue(), "should match another arch with the same OS") - Expect(plat1.Matches(plat3)).To(BeFalse(), "should reject a different os") - }) - Specify("a wildcard os should match any os", func() { - sel := Platform{OS: "*", Arch: "amd64"} - plat1 := Platform{OS: "linux", Arch: "amd64"} - plat2 := Platform{OS: "windows", Arch: "amd64"} - plat3 := Platform{OS: "linux", Arch: "s390x"} - Expect(sel.Matches(sel)).To(BeTrue(), "should match itself") - Expect(sel.Matches(plat1)).To(BeTrue(), "should match some os with the same arch") - Expect(sel.Matches(plat2)).To(BeTrue(), "should match another os with the same arch") - Expect(plat1.Matches(plat3)).To(BeFalse(), "should reject a different arch") - }) - It("should report a wildcard OS as a wildcard platform", func() { - Expect(Platform{OS: "*", Arch: "amd64"}.IsWildcard()).To(BeTrue()) - }) - It("should report a wildcard arch as a wildcard platform", func() { - Expect(Platform{OS: "linux", Arch: "*"}.IsWildcard()).To(BeTrue()) - }) - It("should serialize as os/arch", func() { - Expect(Platform{OS: "linux", Arch: "amd64"}.String()).To(Equal("linux/amd64")) - }) - - Specify("knows how to produce a base store name", func() { - plat := Platform{OS: "linux", Arch: "amd64"} - ver := Concrete{Major: 1, Minor: 16, Patch: 3} - Expect(plat.BaseName(ver)).To(Equal("1.16.3-linux-amd64")) - }) - - Specify("knows how to produce an archive name", func() { - plat := Platform{OS: "linux", Arch: "amd64"} - ver := Concrete{Major: 1, Minor: 16, Patch: 3} - Expect(plat.ArchiveName(ver)).To(Equal("kubebuilder-tools-1.16.3-linux-amd64.tar.gz")) - }) - - Describe("parsing", func() { - Context("for version-platform names", func() { - It("should accept strings of the form x.y.z-os-arch", func() { - ver, plat := ExtractWithPlatform(VersionPlatformRE, "1.16.3-linux-amd64") - Expect(ver).To(Equal(&Concrete{Major: 1, Minor: 16, Patch: 3})) - Expect(plat).To(Equal(Platform{OS: "linux", Arch: "amd64"})) - }) - It("should reject nonsense strings", func() { - ver, _ := ExtractWithPlatform(VersionPlatformRE, "1.16-linux-amd64") - Expect(ver).To(BeNil()) - }) - }) - Context("for archive names", func() { - It("should accept strings of the form kubebuilder-tools-x.y.z-os-arch.tar.gz", func() { - ver, plat := ExtractWithPlatform(ArchiveRE, "kubebuilder-tools-1.16.3-linux-amd64.tar.gz") - Expect(ver).To(Equal(&Concrete{Major: 1, Minor: 16, Patch: 3})) - Expect(plat).To(Equal(Platform{OS: "linux", Arch: "amd64"})) - }) - It("should reject nonsense strings", func() { - ver, _ := ExtractWithPlatform(ArchiveRE, "kubebuilder-tools-1.16.3-linux-amd64.tar.sum") - Expect(ver).To(BeNil()) - }) - }) - }) -}) - -var _ = Describe("Spec helpers", func() { - Specify("can fill a spec with a concrete version", func() { - spec := Spec{Selector: AnySelector{}} // don't just use AnyVersion so we don't modify it - spec.MakeConcrete(Concrete{Major: 1, Minor: 16}) - Expect(spec.AsConcrete()).To(Equal(&Concrete{Major: 1, Minor: 16})) - }) - It("should serialize as the underlying selector with ! for check latest", func() { - spec, err := FromExpr("1.16.*!") - Expect(err).NotTo(HaveOccurred()) - Expect(spec.String()).To(Equal("1.16.*!")) - }) - It("should serialize as the underlying selector by itself if not check latest", func() { - spec, err := FromExpr("1.16.*") - Expect(err).NotTo(HaveOccurred()) - Expect(spec.String()).To(Equal("1.16.*")) - }) -}) diff --git a/tools/setup-envtest/versions/parse.go b/tools/setup-envtest/versions/parse.go deleted file mode 100644 index c053bf8757..0000000000 --- a/tools/setup-envtest/versions/parse.go +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2021 The Kubernetes Authors - -package versions - -import ( - "fmt" - "regexp" - "strconv" -) - -var ( - // baseVersionRE is a semver-ish version -- either X.Y.Z, X.Y, or X.Y.{*|x}. - baseVersionRE = `(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:\.(?P0|[1-9]\d*|x|\*))?` - // versionExprRe matches valid version input for FromExpr. - versionExprRE = regexp.MustCompile(`^(?P<|~|<=)?` + baseVersionRE + `(?P!)?$`) - - // ConcreteVersionRE matches a concrete version anywhere in the string. - ConcreteVersionRE = regexp.MustCompile(`(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)`) - // OnlyConcreteVersionRE matches a string that's just a concrete version. - OnlyConcreteVersionRE = regexp.MustCompile(`^` + ConcreteVersionRE.String() + `$`) -) - -// FromExpr extracts a version from a string in the form of a semver version, -// where X, Y, and Z may also be wildcards ('*', 'x'), -// and pre-release names & numbers may also be wildcards. The prerelease section is slightly -// restricted to match what k8s does. -// The whole string is a version selector as follows: -// - X.Y.Z matches version X.Y.Z where x, y, and z are -// are ints >= 0, and Z may be '*' or 'x' -// - X.Y is equivalent to X.Y.* -// - ~X.Y.Z means >= X.Y.Z && < X.Y+1.0 -// - = comparisons, if we use - // wildcards with a selector we can just set them to zero. - if verInfo.Patch == AnyPoint { - verInfo.Patch = PointVersion(0) - } - baseVer := *verInfo.AsConcrete() - spec.Selector = TildeSelector{Concrete: baseVer} - default: - panic("unreachable: mismatch between FromExpr and its RE in selector") - } - - return spec, nil -} - -// PointVersionFromValidString extracts a point version -// from the corresponding string representation, which may -// be a number >= 0, or x|* (AnyPoint). -// -// Anything else will cause a panic (use this on strings -// extracted from regexes). -func PointVersionFromValidString(str string) PointVersion { - switch str { - case "*", "x": - return AnyPoint - default: - ver, err := strconv.Atoi(str) - if err != nil { - panic(err) - } - return PointVersion(ver) - } -} - -// PatchSelectorFromMatch constructs a simple selector according to the -// ParseExpr rules out of pre-validated sections. -// -// re must include name captures for major, minor, patch, prenum, and prelabel -// -// Any bad input may cause a panic. Use with when you got the parts from an RE match. -func PatchSelectorFromMatch(match []string, re *regexp.Regexp) PatchSelector { - // already parsed via RE, should be fine to ignore errors unless it's a - // *huge* number - major, err := strconv.Atoi(match[re.SubexpIndex("major")]) - if err != nil { - panic("invalid input passed as patch selector (invalid state)") - } - minor, err := strconv.Atoi(match[re.SubexpIndex("minor")]) - if err != nil { - panic("invalid input passed as patch selector (invalid state)") - } - - // patch is optional, means wilcard if left off - patch := AnyPoint - if patchRaw := match[re.SubexpIndex("patch")]; patchRaw != "" { - patch = PointVersionFromValidString(patchRaw) - } - return PatchSelector{ - Major: major, - Minor: minor, - Patch: patch, - } -} diff --git a/tools/setup-envtest/versions/parse_test.go b/tools/setup-envtest/versions/parse_test.go deleted file mode 100644 index 062fdcc6c8..0000000000 --- a/tools/setup-envtest/versions/parse_test.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package versions_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - . "sigs.k8s.io/controller-runtime/tools/setup-envtest/versions" -) - -func patchSel(x, y int, z PointVersion) PatchSelector { - return PatchSelector{Major: x, Minor: y, Patch: z} -} - -func patchSpec(x, y int, z PointVersion) Spec { - return Spec{Selector: patchSel(x, y, z)} -} - -func tildeSel(x, y, z int) TildeSelector { - return TildeSelector{ - Concrete: Concrete{ - Major: x, Minor: y, Patch: z, - }, - } -} - -func tildeSpec(x, y, z int) Spec { - return Spec{Selector: tildeSel(x, y, z)} -} -func ltSpec(x, y int, z PointVersion) Spec { - // this just keeps the table a bit shorter - return Spec{Selector: LessThanSelector{ - PatchSelector: patchSel(x, y, z), - }} -} -func lteSpec(x, y int, z PointVersion) Spec { - // this just keeps the table a bit shorter - return Spec{Selector: LessThanSelector{ - PatchSelector: patchSel(x, y, z), - OrEquals: true, - }} -} - -var _ = Describe("Parse", func() { - DescribeTable("it should support", - func(spec string, expected Spec) { - Expect(FromExpr(spec)).To(Equal(expected)) - }, - Entry("X.Y versions", "1.16", patchSpec(1, 16, AnyPoint)), - Entry("X.Y.Z versions", "1.16.3", patchSpec(1, 16, PointVersion(3))), - Entry("X.Y.x wildcard", "1.16.x", patchSpec(1, 16, AnyPoint)), - Entry("X.Y.* wildcard", "1.16.*", patchSpec(1, 16, AnyPoint)), - - Entry("~X.Y selector", "~1.16", tildeSpec(1, 16, 0)), - Entry("~X.Y.Z selector", "~1.16.3", tildeSpec(1, 16, 3)), - Entry("~X.Y.x selector", "~1.16.x", tildeSpec(1, 16, 0)), - Entry("~X.Y.* selector", "~1.16.*", tildeSpec(1, 16, 0)), - - Entry("\w+)-(?P\w+)` - // VersionPlatformRE matches concrete version-platform strings. - VersionPlatformRE = regexp.MustCompile(`^` + versionPlatformREBase + `$`) - // ArchiveRE matches concrete version-platform.tar.gz strings. - ArchiveRE = regexp.MustCompile(`^kubebuilder-tools-` + versionPlatformREBase + `\.tar\.gz$`) -) diff --git a/tools/setup-envtest/versions/selectors_test.go b/tools/setup-envtest/versions/selectors_test.go deleted file mode 100644 index 8357d41c80..0000000000 --- a/tools/setup-envtest/versions/selectors_test.go +++ /dev/null @@ -1,216 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package versions_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - . "sigs.k8s.io/controller-runtime/tools/setup-envtest/versions" -) - -var _ = Describe("Selectors", func() { - Describe("patch", func() { - var sel Selector - Context("with any patch", func() { - BeforeEach(func() { - var err error - sel, err = FromExpr("1.16.*") - Expect(err).NotTo(HaveOccurred()) - }) - - It("should match any patch version with the same major & minor", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 3})).To(BeTrue(), "should match 1.16.3") - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 0})).To(BeTrue(), "should match 1.16.0") - }) - - It("should reject a different major", func() { - Expect(sel.Matches(Concrete{Major: 2, Minor: 16, Patch: 3})).To(BeFalse(), "should reject 2.16.3") - - }) - - It("should reject a different minor", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 17, Patch: 3})).To(BeFalse(), "should reject 1.17.3") - }) - - It("should serialize as X.Y.*", func() { - Expect(sel.String()).To(Equal("1.16.*")) - }) - - It("should not be concrete", func() { - Expect(sel.AsConcrete()).To(BeNil()) - }) - }) - - Context("with a specific patch", func() { - BeforeEach(func() { - var err error - sel, err = FromExpr("1.16.3") - Expect(err).NotTo(HaveOccurred()) - }) - It("should match exactly the major/minor/patch", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 3})).To(BeTrue(), "should match 1.16.3") - }) - - It("should reject a different major", func() { - Expect(sel.Matches(Concrete{Major: 2, Minor: 16, Patch: 3})).To(BeFalse(), "should reject 2.16.3") - - }) - - It("should reject a different minor", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 17, Patch: 3})).To(BeFalse(), "should reject 1.17.3") - - }) - - It("should reject a different patch", func() { - - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 4})).To(BeFalse(), "should reject 1.16.4") - }) - It("should serialize as X.Y.Z", func() { - Expect(sel.String()).To(Equal("1.16.3")) - }) - It("may be concrete", func() { - Expect(sel.AsConcrete()).To(Equal(&Concrete{Major: 1, Minor: 16, Patch: 3})) - }) - }) - - }) - - Describe("tilde", func() { - var sel Selector - BeforeEach(func() { - var err error - sel, err = FromExpr("~1.16.3") - Expect(err).NotTo(HaveOccurred()) - }) - It("should match exactly the major/minor/patch", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 3})).To(BeTrue(), "should match 1.16.3") - }) - - It("should match a patch greater than the given one, with the same major/minor", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 4})).To(BeTrue(), "should match 1.16.4") - }) - - It("should reject a patch less than the given one, with the same major/minor", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 2})).To(BeFalse(), "should reject 1.16.2") - - }) - - It("should reject a different major", func() { - Expect(sel.Matches(Concrete{Major: 2, Minor: 16, Patch: 3})).To(BeFalse(), "should reject 2.16.3") - - }) - - It("should reject a different minor", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 17, Patch: 3})).To(BeFalse(), "should reject 1.17.3") - - }) - - It("should treat ~X.Y.* as ~X.Y.Z", func() { - sel, err := FromExpr("~1.16.*") - Expect(err).NotTo(HaveOccurred()) - - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 0})).To(BeTrue(), "should match 1.16.0") - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 3})).To(BeTrue(), "should match 1.16.3") - Expect(sel.Matches(Concrete{Major: 1, Minor: 17, Patch: 0})).To(BeFalse(), "should reject 1.17.0") - }) - It("should serialize as ~X.Y.Z", func() { - Expect(sel.String()).To(Equal("~1.16.3")) - }) - It("should never be concrete", func() { - Expect(sel.AsConcrete()).To(BeNil()) - }) - }) - - Describe("less-than", func() { - var sel Selector - BeforeEach(func() { - var err error - sel, err = FromExpr("<1.16.3") - Expect(err).NotTo(HaveOccurred()) - }) - It("should reject the exact major/minor/patch", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 3})).To(BeFalse(), "should reject 1.16.3") - - }) - It("should reject greater patches", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 4})).To(BeFalse(), "should reject 1.16.4") - - }) - It("should reject greater majors", func() { - Expect(sel.Matches(Concrete{Major: 2, Minor: 16, Patch: 3})).To(BeFalse(), "should reject 2.16.3") - - }) - It("should reject greater minors", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 17, Patch: 3})).To(BeFalse(), "should reject 1.17.3") - - }) - - It("should accept lesser patches", func() { - - Expect(sel.Matches(Concrete{Major: 1, Minor: 16, Patch: 2})).To(BeTrue(), "should accept 1.16.2") - }) - It("should accept lesser majors", func() { - Expect(sel.Matches(Concrete{Major: 0, Minor: 16, Patch: 3})).To(BeTrue(), "should accept 0.16.3") - - }) - It("should accept lesser minors", func() { - Expect(sel.Matches(Concrete{Major: 1, Minor: 15, Patch: 3})).To(BeTrue(), "should accept 1.15.3") - - }) - It("should serialize as other.Major - } - if c.Minor != other.Minor { - return c.Minor > other.Minor - } - return c.Patch > other.Patch -} - -// Matches checks if this version is equal to the other one. -func (c Concrete) Matches(other Concrete) bool { - return c == other -} - -func (c Concrete) String() string { - return fmt.Sprintf("%d.%d.%d", c.Major, c.Minor, c.Patch) -} - -// PatchSelector selects a set of versions where the patch is a wildcard. -type PatchSelector struct { - Major, Minor int - Patch PointVersion -} - -func (s PatchSelector) String() string { - return fmt.Sprintf("%d.%d.%s", s.Major, s.Minor, s.Patch) -} - -// Matches checks if the given version matches this selector. -func (s PatchSelector) Matches(ver Concrete) bool { - return s.Major == ver.Major && s.Minor == ver.Minor && s.Patch.Matches(ver.Patch) -} - -// AsConcrete returns nil if there are wildcards in this selector, -// and the concrete version that this selects otherwise. -func (s PatchSelector) AsConcrete() *Concrete { - if s.Patch == AnyPoint { - return nil - } - - return &Concrete{ - Major: s.Major, - Minor: s.Minor, - Patch: int(s.Patch), // safe to cast, we've just checked wilcards above - } -} - -// TildeSelector selects [X.Y.Z, X.Y+1.0). -type TildeSelector struct { - Concrete -} - -// Matches checks if the given version matches this selector. -func (s TildeSelector) Matches(ver Concrete) bool { - if s.Concrete.Matches(ver) { - // easy, "exact" match - return true - } - return ver.Major == s.Major && ver.Minor == s.Minor && ver.Patch >= s.Patch -} -func (s TildeSelector) String() string { - return "~" + s.Concrete.String() -} - -// AsConcrete returns nil (this is never a concrete version). -func (s TildeSelector) AsConcrete() *Concrete { - return nil -} - -// LessThanSelector selects versions older than the given one -// (mainly useful for cleaning up). -type LessThanSelector struct { - PatchSelector - OrEquals bool -} - -// Matches checks if the given version matches this selector. -func (s LessThanSelector) Matches(ver Concrete) bool { - if s.Major != ver.Major { - return s.Major > ver.Major - } - if s.Minor != ver.Minor { - return s.Minor > ver.Minor - } - if !s.Patch.Matches(ver.Patch) { - // matches rules out a wildcard, so it's fine to compare as normal numbers - return int(s.Patch) > ver.Patch - } - return s.OrEquals -} -func (s LessThanSelector) String() string { - if s.OrEquals { - return "<=" + s.PatchSelector.String() - } - return "<" + s.PatchSelector.String() -} - -// AsConcrete returns nil (this is never a concrete version). -func (s LessThanSelector) AsConcrete() *Concrete { - return nil -} - -// AnySelector matches any version at all. -type AnySelector struct{} - -// Matches checks if the given version matches this selector. -func (AnySelector) Matches(_ Concrete) bool { return true } - -// AsConcrete returns nil (this is never a concrete version). -func (AnySelector) AsConcrete() *Concrete { return nil } -func (AnySelector) String() string { return "*" } - -// Selector selects some concrete version or range of versions. -type Selector interface { - // AsConcrete tries to return this selector as a concrete version. - // If the selector would only match a single version, it'll return - // that, otherwise it'll return nil. - AsConcrete() *Concrete - // Matches checks if this selector matches the given concrete version. - Matches(ver Concrete) bool - String() string -} - -// Spec matches some version or range of versions, and tells us how to deal with local and -// remote when selecting a version. -type Spec struct { - Selector - - // CheckLatest tells us to check the remote server for the latest - // version that matches our selector, instead of just relying on - // matching local versions. - CheckLatest bool -} - -// MakeConcrete replaces the contents of this spec with one that -// matches the given concrete version (without checking latest -// from the server). -func (s *Spec) MakeConcrete(ver Concrete) { - s.Selector = ver - s.CheckLatest = false -} - -// AsConcrete returns the underlying selector as a concrete version, if -// possible. -func (s Spec) AsConcrete() *Concrete { - return s.Selector.AsConcrete() -} - -// Matches checks if the underlying selector matches the given version. -func (s Spec) Matches(ver Concrete) bool { - return s.Selector.Matches(ver) -} - -func (s Spec) String() string { - res := s.Selector.String() - if s.CheckLatest { - res += "!" - } - return res -} - -// PointVersion represents a wildcard (patch) version -// or concrete number. -type PointVersion int - -const ( - // AnyPoint matches any point version. - AnyPoint PointVersion = -1 -) - -// Matches checks if a point version is compatible -// with a concrete point version. -// Two point versions are compatible if they are -// a) both concrete -// b) one is a wildcard. -func (p PointVersion) Matches(other int) bool { - switch p { - case AnyPoint: - return true - default: - return int(p) == other - } -} -func (p PointVersion) String() string { - switch p { - case AnyPoint: - return "*" - default: - return strconv.Itoa(int(p)) - } -} - -var ( - // LatestVersion matches the most recent version on the remote server. - LatestVersion = Spec{ - Selector: AnySelector{}, - CheckLatest: true, - } - // AnyVersion matches any local or remote version. - AnyVersion = Spec{ - Selector: AnySelector{}, - } -) diff --git a/tools/setup-envtest/versions/versions_suite_test.go b/tools/setup-envtest/versions/versions_suite_test.go deleted file mode 100644 index db1fe76403..0000000000 --- a/tools/setup-envtest/versions/versions_suite_test.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package versions_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestVersions(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Versions Suite") -}