Skip to content

Commit

Permalink
Implement setup-envtest use in controller-runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasaschan committed Apr 30, 2024
1 parent 11ed5ff commit 2b6dcd6
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 126 deletions.
14 changes: 7 additions & 7 deletions pkg/envtest/setup/env/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,29 @@ var expectedExecutables = []string{
"kubectl",
}

// CanUseAssetsFromPath attempts to use the assets from the provided path if they match the spec.
// TryUseAssetsFromPath attempts to use the assets from the provided path if they match the spec.
// If they do not, or if some executable is missing, it returns an empty string.
func (e *Env) CanUseAssetsFromPath(ctx context.Context, spec versions.Spec, path string) bool {
func (e *Env) TryUseAssetsFromPath(ctx context.Context, spec versions.Spec, path string) (*versions.Concrete, bool) {
v, err := versions.FromPath(path)
if err != nil {
log.FromContext(ctx).Error(err, "Unable to use assets from path, ignoring", "path", path)
return false
return nil, false
}

if !spec.Matches(*v) {
log.FromContext(ctx).Error(nil, "Assets path does not match spec, ignoring", "path", path, "spec", spec)
return false
return nil, false
}

if ok, err := e.hasAllExecutables(path); err != nil {
log.FromContext(ctx).Error(err, "Failed checking if assets path has all binaries, ignoring", "path", path)
return false
return nil, false
} else if !ok {
log.FromContext(ctx).Error(nil, "Assets path is missing some executables, ignoring", "path", path)
return false
return nil, false
}

return true
return v, true
}

func (e *Env) hasAllExecutables(path string) (bool, error) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/envtest/setup/setup-envtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import (
// Use implements the use workflow for selecting and using a version of the environment.
//
// It will download a remote version if required (and options allow), and return the path to the binary asset directory.
func Use(ctx context.Context, version string, options ...use.Option) (string, error) {
func Use(ctx context.Context, version string, options ...use.Option) (use.Result, error) {
spec, err := readSpec(version)
if err != nil {
return "", err
return use.Result{}, err
}

return use.Use(ctx, spec, options...)
Expand Down
16 changes: 11 additions & 5 deletions pkg/envtest/setup/use/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/envtest/setup/versions"
)

const kubebuilderAssets = "KUBEBUILDER_ASSETS"

type config struct {
platform versions.Platform
assetPath string
Expand All @@ -28,15 +30,19 @@ func WithAssetsAt(dir string) Option {
}

// WithAssetsFromEnv sets the path to the assets directory from the environment.
func WithAssetsFromEnv() Option {
return func(c *config) { c.assetPath = cmp.Or(os.Getenv(kubebuilderAssets), c.assetPath) }
func WithAssetsFromEnv(useEnv bool) Option {
return func(c *config) {
if useEnv {
c.assetPath = cmp.Or(os.Getenv(kubebuilderAssets), c.assetPath)
}
}
}

// ForceDownload forces the download of the specified version, even if it's already present.
func ForceDownload() Option { return func(c *config) { c.forceDownload = true } }
func ForceDownload(force bool) Option { return func(c *config) { c.forceDownload = force } }

// NoDownload ensures only local versions are considered
func NoDownload() Option { return func(c *config) { c.noDownload = true } }
func NoDownload(noDownload bool) Option { return func(c *config) { c.noDownload = noDownload } }

// WithPlatform sets the target OS and architecture for the download.
func WithPlatform(os string, arch string) Option {
Expand All @@ -49,7 +55,7 @@ func WithEnvOption(o env.Option) Option {
}

// VerifySum turns on md5 verification of the downloaded package
func VerifySum() Option { return func(c *config) { c.verifySum = true } }
func VerifySum(verify bool) Option { return func(c *config) { c.verifySum = !verify } }

func configure(options ...Option) *config {
cfg := &config{}
Expand Down
44 changes: 33 additions & 11 deletions pkg/envtest/setup/use/use.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,64 @@ import (
"sigs.k8s.io/controller-runtime/pkg/envtest/setup/versions"
)

const kubebuilderAssets = "KUBEBUILDER_ASSETS"
// Result summarizes the output of the Use workflow
type Result struct {
Version versions.Concrete
Platform versions.Platform
MD5 string
Path string
}

// Use selects an appropriate version based on the user's spec, downloads it if needed,
// and returns the path to the binary asset directory.
func Use(ctx context.Context, version versions.Spec, options ...Option) (string, error) {
func Use(ctx context.Context, version versions.Spec, options ...Option) (Result, error) {
cfg := configure(options...)

env, err := env.New(cfg.envOpts...)
if err != nil {
return "", err
return Result{}, err
}

if cfg.assetPath != "" && env.CanUseAssetsFromPath(ctx, version, cfg.assetPath) {
return cfg.assetPath, nil
if cfg.assetPath != "" {
if v, ok := env.TryUseAssetsFromPath(ctx, version, cfg.assetPath); ok {
return Result{
Version: *v,
Platform: cfg.platform,
Path: cfg.assetPath,
}, nil
}
}

if !cfg.forceDownload && !version.CheckLatest {
if selected, err := env.SelectLocalVersion(ctx, version, cfg.platform); err != nil {
return "", err
return Result{}, err
} else if selected != nil {
return env.PathTo(selected, cfg.platform), nil
return Result{
Path: env.PathTo(selected, cfg.platform),
Version: *selected,
Platform: cfg.platform,
}, nil

}
}

if cfg.noDownload {
return "", fmt.Errorf("no local version matching %s found, but you specified NoDownload()", version)
return Result{}, fmt.Errorf("no local version matching %s found, but you specified NoDownload()", version)
}

selectedVersion, selectedPlatform, err := env.SelectRemoteVersion(ctx, version, cfg.platform)
if err != nil {
return "", err
return Result{}, err
}

if err := env.FetchRemoteVersion(ctx, selectedVersion, selectedPlatform, cfg.verifySum); err != nil {
return "", err
return Result{}, err
}

return env.PathTo(selectedVersion, selectedPlatform.Platform), nil
return Result{
Version: *selectedVersion,
Platform: selectedPlatform.Platform,
Path: env.PathTo(selectedVersion, selectedPlatform.Platform),
MD5: selectedPlatform.MD5,
}, nil
}
39 changes: 39 additions & 0 deletions tools/setup-envtest/env/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package env

import (
"fmt"
"io"
"strings"

"sigs.k8s.io/controller-runtime/tools/setup-envtest/versions"
)
Expand Down Expand Up @@ -66,3 +68,40 @@ func (f *PrintFormat) Set(val string) error {
func (PrintFormat) Type() string {
return "{overview|path|env}"
}

// Sprint returns the string to be printed
func (f PrintFormat) Sprintf(out io.Writer, version versions.Concrete, platform versions.Platform, md5 string, path string) (err error) {
switch f {
case PrintOverview:
if _, e := fmt.Fprintf(out, "Version: %s\n", version); e != nil {
return e
}
if _, e := fmt.Fprintf(out, "OS/Arch: %s\n", platform); e != nil {
return e
}
if md5 != "" {
if _, e := fmt.Fprintf(out, "md5: %s\n", md5); e != nil {
return e
}
}
if _, e := fmt.Fprintf(out, "Path: %s\n", path); e != nil {
return e
}
return nil
case PrintPath:
_, e := fmt.Fprint(out, path) // NB(directxman12): no newline -- want the bare path here
return e
case PrintEnv:
// quote in case there are spaces, etc in the path
// the weird string below works like this:
// - you can't escape quotes in shell
// - shell strings that are next to each other are concatenated (so "a""b""c" == "abc")
// - you can intermix quote styles using the above
// - so `'"'"'` --> CLOSE_QUOTE + "'" + OPEN_QUOTE
shellQuoted := strings.ReplaceAll(path, "'", `'"'"'`)
_, e := fmt.Fprintf(out, "export KUBEBUILDER_ASSETS='%s'\n", shellQuoted)
return e
default:
return fmt.Errorf("unexpected print format %v", f)
}
}
32 changes: 18 additions & 14 deletions tools/setup-envtest/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@ module sigs.k8s.io/controller-runtime/tools/setup-envtest

go 1.22.0

replace sigs.k8s.io/controller-runtime => ../../

require (
github.com/go-logr/logr v1.2.4
github.com/go-logr/zapr v1.2.4
github.com/onsi/ginkgo/v2 v2.12.1
github.com/onsi/gomega v1.27.10
github.com/spf13/afero v1.6.0
github.com/go-logr/logr v1.4.1
github.com/go-logr/zapr v1.3.0
github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.32.0
github.com/spf13/afero v1.11.0
github.com/spf13/pflag v1.0.5
go.uber.org/zap v1.26.0
sigs.k8s.io/controller-runtime v0.0.0-00010101000000-000000000000
)

require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.12.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/kr/pretty v0.1.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 2b6dcd6

Please sign in to comment.