Skip to content

Commit e8204ec

Browse files
committed
Unify process.BinPathFinder with envtest's logic
Previously, envtest had separate logic that overrode testing/process.BinPathFinder, mainly because the latter used to be in a separate module. Since they're now in the same module and BinPathFinder is internal, we can just unify the logic and remove some of our hacks.
1 parent 848bc88 commit e8204ec

File tree

6 files changed

+96
-113
lines changed

6 files changed

+96
-113
lines changed

pkg/envtest/server.go

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package envtest
1919
import (
2020
"fmt"
2121
"os"
22-
"path/filepath"
2322
"strings"
2423
"time"
2524

@@ -29,6 +28,7 @@ import (
2928
"sigs.k8s.io/controller-runtime/pkg/client"
3029
"sigs.k8s.io/controller-runtime/pkg/client/config"
3130
"sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane"
31+
"sigs.k8s.io/controller-runtime/pkg/internal/testing/process"
3232

3333
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
3434
)
@@ -48,40 +48,17 @@ It's possible to override some defaults, by setting the following environment va
4848
4949
*/
5050
const (
51-
envUseExistingCluster = "USE_EXISTING_CLUSTER"
52-
envKubeAPIServerBin = "TEST_ASSET_KUBE_APISERVER"
53-
envEtcdBin = "TEST_ASSET_ETCD"
54-
envKubectlBin = "TEST_ASSET_KUBECTL"
55-
envKubebuilderPath = "KUBEBUILDER_ASSETS"
56-
envStartTimeout = "KUBEBUILDER_CONTROLPLANE_START_TIMEOUT"
57-
envStopTimeout = "KUBEBUILDER_CONTROLPLANE_STOP_TIMEOUT"
58-
envAttachOutput = "KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT"
59-
defaultKubebuilderPath = "/usr/local/kubebuilder/bin"
60-
StartTimeout = 60
61-
StopTimeout = 60
51+
envUseExistingCluster = "USE_EXISTING_CLUSTER"
52+
envStartTimeout = "KUBEBUILDER_CONTROLPLANE_START_TIMEOUT"
53+
envStopTimeout = "KUBEBUILDER_CONTROLPLANE_STOP_TIMEOUT"
54+
envAttachOutput = "KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT"
55+
StartTimeout = 60
56+
StopTimeout = 60
6257

6358
defaultKubebuilderControlPlaneStartTimeout = 20 * time.Second
6459
defaultKubebuilderControlPlaneStopTimeout = 20 * time.Second
6560
)
6661

67-
// getBinAssetPath returns a path for binary from the following list of locations,
68-
// ordered by precedence:
69-
// 0. KUBEBUILDER_ASSETS
70-
// 1. Environment.BinaryAssetsDirectory
71-
// 2. The default path, "/usr/local/kubebuilder/bin"
72-
func (te *Environment) getBinAssetPath(binary string) string {
73-
valueFromEnvVar := os.Getenv(envKubebuilderPath)
74-
if valueFromEnvVar != "" {
75-
return filepath.Join(valueFromEnvVar, binary)
76-
}
77-
78-
if te.BinaryAssetsDirectory != "" {
79-
return filepath.Join(te.BinaryAssetsDirectory, binary)
80-
}
81-
82-
return filepath.Join(defaultKubebuilderPath, binary)
83-
}
84-
8562
// ControlPlane is the re-exported ControlPlane type from the internal testing package
8663
type ControlPlane = controlplane.ControlPlane
8764

@@ -237,19 +214,9 @@ func (te *Environment) Start() (*rest.Config, error) {
237214
te.ControlPlane.Etcd.Err = os.Stderr
238215
}
239216

240-
if os.Getenv(envKubeAPIServerBin) == "" {
241-
apiServer.Path = te.getBinAssetPath("kube-apiserver")
242-
}
243-
if os.Getenv(envEtcdBin) == "" {
244-
te.ControlPlane.Etcd.Path = te.getBinAssetPath("etcd")
245-
}
246-
if os.Getenv(envKubectlBin) == "" {
247-
// we can't just set the path manually (it's behind a function), so set the environment variable instead
248-
// TODO(directxman12): re-evaluate this post pkg/internal/testing refactor
249-
if err := os.Setenv(envKubectlBin, te.getBinAssetPath("kubectl")); err != nil {
250-
return nil, fmt.Errorf("unable to override kubectl environment path: %w", err)
251-
}
252-
}
217+
apiServer.Path = process.BinPathFinder("kube-apiserver", te.BinaryAssetsDirectory)
218+
te.ControlPlane.Etcd.Path = process.BinPathFinder("etcd", te.BinaryAssetsDirectory)
219+
te.ControlPlane.KubectlPath = process.BinPathFinder("kubectl", te.BinaryAssetsDirectory)
253220

254221
if err := te.defaultTimeouts(); err != nil {
255222
return nil, fmt.Errorf("failed to default controlplane timeouts: %w", err)

pkg/internal/testing/controlplane/kubectl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ type KubeCtl struct {
8282
// stderr.
8383
func (k *KubeCtl) Run(args ...string) (stdout, stderr io.Reader, err error) {
8484
if k.Path == "" {
85-
k.Path = process.BinPathFinder("kubectl")
85+
k.Path = process.BinPathFinder("kubectl", "")
8686
}
8787

8888
stdoutBuffer := &bytes.Buffer{}

pkg/internal/testing/controlplane/plane.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ type ControlPlane struct {
2323
APIServer *APIServer
2424
Etcd *Etcd
2525

26+
// Kubectl will override the default asset search path for kubectl
27+
KubectlPath string
28+
2629
// for the deprecated methods (Kubectl, etc)
2730
defaultUserCfg *rest.Config
2831
defaultUserKubectl *KubeCtl
@@ -121,7 +124,7 @@ type AuthenticatedUser struct {
121124

122125
// apiServer is a handle to the APIServer that's used when finalizing cfg
123126
// and producing the kubectl instance.
124-
apiServer *APIServer
127+
plane *ControlPlane
125128

126129
// kubectl is our existing, provisioned kubectl. We don't provision one
127130
// till someone actually asks for it.
@@ -139,12 +142,12 @@ func (u *AuthenticatedUser) Config() *rest.Config {
139142
if u.cfgIsComplete {
140143
return u.cfg
141144
}
142-
if len(u.apiServer.SecureServing.CA) == 0 {
145+
if len(u.plane.APIServer.SecureServing.CA) == 0 {
143146
panic("the API server has not yet been started, please do that before accessing connection details")
144147
}
145148

146-
u.cfg.CAData = u.apiServer.SecureServing.CA
147-
u.cfg.Host = u.apiServer.SecureServing.URL("https", "/").String()
149+
u.cfg.CAData = u.plane.APIServer.SecureServing.CA
150+
u.cfg.Host = u.plane.APIServer.SecureServing.URL("https", "/").String()
148151
u.cfgIsComplete = true
149152
return u.cfg
150153
}
@@ -168,12 +171,12 @@ func (u *AuthenticatedUser) Kubectl() (*KubeCtl, error) {
168171
if u.kubectl != nil {
169172
return u.kubectl, nil
170173
}
171-
if len(u.apiServer.CertDir) == 0 {
174+
if len(u.plane.APIServer.CertDir) == 0 {
172175
panic("the API server has not yet been started, please do that before accessing connection details")
173176
}
174177

175178
// cleaning this up is handled when our tmpDir is deleted
176-
out, err := os.CreateTemp(u.apiServer.CertDir, "*.kubecfg")
179+
out, err := os.CreateTemp(u.plane.APIServer.CertDir, "*.kubecfg")
177180
if err != nil {
178181
return nil, fmt.Errorf("unable to create file for kubeconfig: %w", err)
179182
}
@@ -185,7 +188,9 @@ func (u *AuthenticatedUser) Kubectl() (*KubeCtl, error) {
185188
if _, err := out.Write(contents); err != nil {
186189
return nil, fmt.Errorf("unable to write kubeconfig to disk at %s: %w", out.Name(), err)
187190
}
188-
k := &KubeCtl{}
191+
k := &KubeCtl{
192+
Path: u.plane.KubectlPath,
193+
}
189194
k.Opts = append(k.Opts, fmt.Sprintf("--kubeconfig=%s", out.Name()))
190195
u.kubectl = k
191196
return k, nil
@@ -214,8 +219,8 @@ func (f *ControlPlane) AddUser(user User, baseConfig *rest.Config) (*Authenticat
214219
}
215220

216221
return &AuthenticatedUser{
217-
cfg: cfg,
218-
apiServer: f.APIServer,
222+
cfg: cfg,
223+
plane: f,
219224
}, nil
220225
}
221226

pkg/internal/testing/process/bin_path_finder.go

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,51 @@ import (
44
"os"
55
"path/filepath"
66
"regexp"
7-
"runtime"
87
"strings"
98
)
109

11-
var assetsPath string
12-
13-
func init() {
14-
_, thisFile, _, ok := runtime.Caller(0)
15-
if !ok {
16-
panic("Could not determine the path of the BinPathFinder")
17-
}
18-
assetsPath = filepath.Join(filepath.Dir(thisFile), "..", "assets", "bin")
19-
}
20-
21-
// TODO(directxman12): unify this with the logic from envtest
10+
const (
11+
// EnvAssetsPath is the environment variable that stores the global test
12+
// binary location override.
13+
EnvAssetsPath = "KUBEBUILDER_ASSETS"
14+
// EnvAssetOverridePrefix is the environment variable prefix for per-binary
15+
// location overrides.
16+
EnvAssetOverridePrefix = "TEST_ASSET_"
17+
// AssetsDefaultPath is the default location to look for test binaries in,
18+
// if no override was provided.
19+
AssetsDefaultPath = "/usr/local/kubebuilder/bin"
20+
)
2221

23-
// BinPathFinder checks the an environment variable, derived from the symbolic name,
24-
// and falls back to a default assets location when this variable is not set
25-
func BinPathFinder(symbolicName string) (binPath string) {
22+
// BinPathFinder finds the path to the given named binary, using the following locations
23+
// in order of precedence (highest first). Notice that the various env vars only need
24+
// to be set -- the asset is not checked for existence on the filesystem.
25+
//
26+
// 1. TEST_ASSET_{tr/a-z-/A-Z_/} (if set; asset overrides -- EnvAssetOverridePrefix)
27+
// 1. KUBEBUILDER_ASSETS (if set; global asset path -- EnvAssetsPath)
28+
// 3. assetDirectory (if set; per-config asset directory)
29+
// 4. /usr/local/kubebuilder/bin (AssetsDefaultPath)
30+
func BinPathFinder(symbolicName, assetDirectory string) (binPath string) {
2631
punctuationPattern := regexp.MustCompile("[^A-Z0-9]+")
2732
sanitizedName := punctuationPattern.ReplaceAllString(strings.ToUpper(symbolicName), "_")
2833
leadingNumberPattern := regexp.MustCompile("^[0-9]+")
2934
sanitizedName = leadingNumberPattern.ReplaceAllString(sanitizedName, "")
30-
envVar := "TEST_ASSET_" + sanitizedName
35+
envVar := EnvAssetOverridePrefix + sanitizedName
3136

37+
// TEST_ASSET_XYZ
3238
if val, ok := os.LookupEnv(envVar); ok {
3339
return val
3440
}
3541

36-
return filepath.Join(assetsPath, symbolicName)
42+
// KUBEBUILDER_ASSETS
43+
if val, ok := os.LookupEnv(EnvAssetsPath); ok {
44+
return filepath.Join(val, symbolicName)
45+
}
46+
47+
// assetDirectory
48+
if assetDirectory != "" {
49+
return filepath.Join(assetDirectory, symbolicName)
50+
}
51+
52+
// default path
53+
return filepath.Join(AssetsDefaultPath, symbolicName)
3754
}

pkg/internal/testing/process/bin_path_finder_test.go

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,59 +8,53 @@ import (
88
)
99

1010
var _ = Describe("BinPathFinder", func() {
11-
Context("when relying on the default assets path", func() {
12-
var (
13-
previousAssetsPath string
14-
)
11+
var prevAssetPath string
12+
BeforeEach(func() {
13+
prevAssetPath = os.Getenv(EnvAssetsPath)
14+
Expect(os.Unsetenv(EnvAssetsPath)).To(Succeed())
15+
Expect(os.Unsetenv(EnvAssetOverridePrefix + "_SOME_FAKE"))
16+
Expect(os.Unsetenv(EnvAssetOverridePrefix + "OTHERFAKE"))
17+
})
18+
AfterEach(func() {
19+
if prevAssetPath != "" {
20+
Expect(os.Setenv(EnvAssetsPath, prevAssetPath))
21+
}
22+
})
23+
Context("when individual overrides are present", func() {
1524
BeforeEach(func() {
16-
previousAssetsPath = assetsPath
17-
assetsPath = "/some/path/assets/bin"
25+
Expect(os.Setenv(EnvAssetOverridePrefix+"OTHERFAKE", "/other/path")).To(Succeed())
26+
Expect(os.Setenv(EnvAssetOverridePrefix+"_SOME_FAKE", "/some/path")).To(Succeed())
27+
// set the global path to make sure we don't prefer it
28+
Expect(os.Setenv(EnvAssetsPath, "/global/path")).To(Succeed())
1829
})
19-
AfterEach(func() {
20-
assetsPath = previousAssetsPath
30+
31+
It("should prefer individual overrides, using them unmodified", func() {
32+
Expect(BinPathFinder("otherfake", "/hardcoded/path")).To(Equal("/other/path"))
2133
})
22-
It("returns the default path when no env var is configured", func() {
23-
binPath := BinPathFinder("some_bin")
24-
Expect(binPath).To(Equal("/some/path/assets/bin/some_bin"))
34+
35+
It("should convert lowercase to uppercase, remove leading numbers, and replace punctuation with underscores when resolving the env var name", func() {
36+
Expect(BinPathFinder("123.some-fake", "/hardcoded/path")).To(Equal("/some/path"))
2537
})
2638
})
2739

28-
Context("when environment is configured", func() {
29-
var (
30-
previousValue string
31-
wasSet bool
32-
)
40+
Context("when individual overrides are missing but the global override is present", func() {
3341
BeforeEach(func() {
34-
envVarName := "TEST_ASSET_ANOTHER_SYMBOLIC_NAME"
35-
if val, ok := os.LookupEnv(envVarName); ok {
36-
previousValue = val
37-
wasSet = true
38-
}
39-
os.Setenv(envVarName, "/path/to/some_bin.exe")
42+
Expect(os.Setenv(EnvAssetsPath, "/global/path")).To(Succeed())
4043
})
41-
AfterEach(func() {
42-
if wasSet {
43-
os.Setenv("TEST_ASSET_ANOTHER_SYMBOLIC_NAME", previousValue)
44-
} else {
45-
os.Unsetenv("TEST_ASSET_ANOTHER_SYMBOLIC_NAME")
46-
}
44+
It("should prefer the global override, appending the name to that path", func() {
45+
Expect(BinPathFinder("some-fake", "/hardcoded/path")).To(Equal("/global/path/some-fake"))
4746
})
48-
It("returns the path from the env", func() {
49-
binPath := BinPathFinder("another_symbolic_name")
50-
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
47+
})
48+
49+
Context("when an asset directory is given and no overrides are present", func() {
50+
It("should use the asset directory, appending the name to that path", func() {
51+
Expect(BinPathFinder("some-fake", "/hardcoded/path")).To(Equal("/hardcoded/path/some-fake"))
5152
})
53+
})
5254

53-
It("sanitizes the environment variable name", func() {
54-
By("cleaning all non-underscore punctuation")
55-
binPath := BinPathFinder("another-symbolic name")
56-
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
57-
binPath = BinPathFinder("another+symbolic\\name")
58-
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
59-
binPath = BinPathFinder("another=symbolic.name")
60-
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
61-
By("removing numbers from the beginning of the name")
62-
binPath = BinPathFinder("12another_symbolic_name")
63-
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
55+
Context("when no path configuration is given", func() {
56+
It("should just use the default path", func() {
57+
Expect(BinPathFinder("some-fake", "")).To(Equal("/usr/local/kubebuilder/bin/some-fake"))
6458
})
6559
})
6660
})

pkg/internal/testing/process/process.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func (ps *State) Init(name string) error {
8989
if name == "" {
9090
return fmt.Errorf("must have at least one of name or path")
9191
}
92-
ps.Path = BinPathFinder(name)
92+
ps.Path = BinPathFinder(name, "")
9393
}
9494

9595
if ps.Dir == "" {

0 commit comments

Comments
 (0)