sigs.k8s.io/kubebuilder/v3@v3.14.0/test/e2e/utils/test_context.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package utils 18 19 import ( 20 "fmt" 21 "io" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "strings" 26 27 log "github.com/sirupsen/logrus" 28 "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" 29 30 . "github.com/onsi/ginkgo/v2" //nolint:golint,revive 31 ) 32 33 const ( 34 certmanagerVersion = "v1.5.3" 35 certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml" 36 prometheusOperatorVersion = "0.51" 37 prometheusOperatorURL = "https://raw.githubusercontent.com/prometheus-operator/" + 38 "prometheus-operator/release-%s/bundle.yaml" 39 ) 40 41 // TestContext specified to run e2e tests 42 type TestContext struct { 43 *CmdContext 44 TestSuffix string 45 Domain string 46 Group string 47 Version string 48 Kind string 49 Resources string 50 ImageName string 51 BinaryName string 52 Kubectl *Kubectl 53 K8sVersion *KubernetesVersion 54 IsRestricted bool 55 } 56 57 // NewTestContext init with a random suffix for test TestContext stuff, 58 // to avoid conflict when running tests synchronously. 59 func NewTestContext(binaryName string, env ...string) (*TestContext, error) { 60 testSuffix, err := util.RandomSuffix() 61 if err != nil { 62 return nil, err 63 } 64 65 cc := &CmdContext{ 66 Env: env, 67 } 68 69 // Use kubectl to get Kubernetes client and cluster version. 70 kubectl := &Kubectl{ 71 Namespace: fmt.Sprintf("e2e-%s-system", testSuffix), 72 ServiceAccount: fmt.Sprintf("e2e-%s-controller-manager", testSuffix), 73 CmdContext: cc, 74 } 75 k8sVersion, err := kubectl.Version() 76 if err != nil { 77 return nil, err 78 } 79 80 // Set CmdContext.Dir after running Kubectl.Version() because dir does not exist yet. 81 if cc.Dir, err = filepath.Abs("e2e-" + testSuffix); err != nil { 82 return nil, err 83 } 84 85 return &TestContext{ 86 TestSuffix: testSuffix, 87 Domain: "example.com" + testSuffix, 88 Group: "bar" + testSuffix, 89 Version: "v1alpha1", 90 Kind: "Foo" + testSuffix, 91 Resources: "foo" + testSuffix + "s", 92 ImageName: "e2e-test/controller-manager:" + testSuffix, 93 CmdContext: cc, 94 Kubectl: kubectl, 95 K8sVersion: &k8sVersion, 96 BinaryName: binaryName, 97 }, nil 98 } 99 100 func warnError(err error) { 101 fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) 102 } 103 104 // Prepare prepares the test environment. 105 func (t *TestContext) Prepare() error { 106 // Remove tools used by projects in the environment so the correct version is downloaded for each test. 107 fmt.Fprintln(GinkgoWriter, "cleaning up tools") 108 for _, toolName := range []string{"controller-gen", "kustomize"} { 109 if toolPath, err := exec.LookPath(toolName); err == nil { 110 if err := os.RemoveAll(toolPath); err != nil { 111 return err 112 } 113 } 114 } 115 116 fmt.Fprintf(GinkgoWriter, "preparing testing directory: %s\n", t.Dir) 117 return os.MkdirAll(t.Dir, 0o755) 118 } 119 120 // makeCertManagerURL returns a kubectl-able URL for the cert-manager bundle. 121 func (t *TestContext) makeCertManagerURL() string { 122 return fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) 123 } 124 125 func (t *TestContext) makePrometheusOperatorURL() string { 126 return fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) 127 } 128 129 // InstallCertManager installs the cert manager bundle. If hasv1beta1CRs is true, 130 // the legacy version (which uses v1alpha2 CRs) is installed. 131 func (t *TestContext) InstallCertManager() error { 132 url := t.makeCertManagerURL() 133 if _, err := t.Kubectl.Apply(false, "-f", url, "--validate=false"); err != nil { 134 return err 135 } 136 // Wait for cert-manager-webhook to be ready, which can take time if cert-manager 137 // was re-installed after uninstalling on a cluster. 138 _, err := t.Kubectl.Wait(false, "deployment.apps/cert-manager-webhook", 139 "--for", "condition=Available", 140 "--namespace", "cert-manager", 141 "--timeout", "5m", 142 ) 143 return err 144 } 145 146 // UninstallCertManager uninstalls the cert manager bundle. 147 func (t *TestContext) UninstallCertManager() { 148 url := t.makeCertManagerURL() 149 if _, err := t.Kubectl.Delete(false, "-f", url); err != nil { 150 warnError(err) 151 } 152 } 153 154 // InstallPrometheusOperManager installs the prometheus manager bundle. 155 func (t *TestContext) InstallPrometheusOperManager() error { 156 url := t.makePrometheusOperatorURL() 157 _, err := t.Kubectl.Apply(false, "-f", url) 158 return err 159 } 160 161 // UninstallPrometheusOperManager uninstalls the prometheus manager bundle. 162 func (t *TestContext) UninstallPrometheusOperManager() { 163 url := t.makePrometheusOperatorURL() 164 if _, err := t.Kubectl.Delete(false, "-f", url); err != nil { 165 warnError(err) 166 } 167 } 168 169 // CleanupManifests is a helper func to run kustomize build and pipe the output to kubectl delete -f - 170 func (t *TestContext) CleanupManifests(dir string) { 171 kustomizePath := filepath.Join(t.Dir, "bin", "kustomize") 172 if _, err := os.Stat(kustomizePath); err != nil { 173 // Just fail below with an error about kustomize not being installed globally. 174 kustomizePath = "kustomize" 175 } 176 cmd := exec.Command(kustomizePath, "build", dir) 177 output, err := t.Run(cmd) 178 if err != nil { 179 warnError(err) 180 } 181 if _, err := t.Kubectl.WithInput(string(output)).Command("delete", "-f", "-"); err != nil { 182 warnError(err) 183 } 184 } 185 186 // Init is for running `kubebuilder init` 187 func (t *TestContext) Init(initOptions ...string) error { 188 initOptions = append([]string{"init"}, initOptions...) 189 //nolint:gosec 190 cmd := exec.Command(t.BinaryName, initOptions...) 191 _, err := t.Run(cmd) 192 return err 193 } 194 195 // Edit is for running `kubebuilder edit` 196 func (t *TestContext) Edit(editOptions ...string) error { 197 editOptions = append([]string{"edit"}, editOptions...) 198 //nolint:gosec 199 cmd := exec.Command(t.BinaryName, editOptions...) 200 _, err := t.Run(cmd) 201 return err 202 } 203 204 // CreateAPI is for running `kubebuilder create api` 205 func (t *TestContext) CreateAPI(resourceOptions ...string) error { 206 resourceOptions = append([]string{"create", "api"}, resourceOptions...) 207 //nolint:gosec 208 cmd := exec.Command(t.BinaryName, resourceOptions...) 209 _, err := t.Run(cmd) 210 return err 211 } 212 213 // CreateWebhook is for running `kubebuilder create webhook` 214 func (t *TestContext) CreateWebhook(resourceOptions ...string) error { 215 resourceOptions = append([]string{"create", "webhook"}, resourceOptions...) 216 //nolint:gosec 217 cmd := exec.Command(t.BinaryName, resourceOptions...) 218 _, err := t.Run(cmd) 219 return err 220 } 221 222 // Regenerate is for running `kubebuilder alpha generate` 223 func (t *TestContext) Regenerate(resourceOptions ...string) error { 224 resourceOptions = append([]string{"alpha", "generate"}, resourceOptions...) 225 //nolint:gosec 226 cmd := exec.Command(t.BinaryName, resourceOptions...) 227 _, err := t.Run(cmd) 228 return err 229 } 230 231 // Make is for running `make` with various targets 232 func (t *TestContext) Make(makeOptions ...string) error { 233 cmd := exec.Command("make", makeOptions...) 234 _, err := t.Run(cmd) 235 return err 236 } 237 238 // Tidy runs `go mod tidy` so that go 1.16 build doesn't fail. 239 // See https://blog.golang.org/go116-module-changes#TOC_3. 240 func (t *TestContext) Tidy() error { 241 cmd := exec.Command("go", "mod", "tidy") 242 _, err := t.Run(cmd) 243 return err 244 } 245 246 // Destroy is for cleaning up the docker images for testing 247 func (t *TestContext) Destroy() { 248 //nolint:gosec 249 // if image name is not present or not provided skip execution of docker command 250 if t.ImageName != "" { 251 // Check white space from image name 252 if len(strings.TrimSpace(t.ImageName)) == 0 { 253 log.Println("Image not set, skip cleaning up of docker image") 254 } else { 255 cmd := exec.Command("docker", "rmi", "-f", t.ImageName) 256 if _, err := t.Run(cmd); err != nil { 257 warnError(err) 258 } 259 } 260 261 } 262 if err := os.RemoveAll(t.Dir); err != nil { 263 warnError(err) 264 } 265 } 266 267 // CreateManagerNamespace will create the namespace where the manager is deployed 268 func (t *TestContext) CreateManagerNamespace() error { 269 _, err := t.Kubectl.Command("create", "ns", t.Kubectl.Namespace) 270 return err 271 } 272 273 // LabelAllNamespacesToWarnAboutRestricted will label all namespaces so that we can verify 274 // if a warning with `Warning: would violate PodSecurity` will be raised when the manifests are applied 275 func (t *TestContext) LabelAllNamespacesToWarnAboutRestricted() error { 276 _, err := t.Kubectl.Command("label", "--overwrite", "ns", "--all", 277 "pod-security.kubernetes.io/audit=restricted", 278 "pod-security.kubernetes.io/enforce-version=v1.24", 279 "pod-security.kubernetes.io/warn=restricted") 280 return err 281 } 282 283 // LoadImageToKindCluster loads a local docker image to the kind cluster 284 func (t *TestContext) LoadImageToKindCluster() error { 285 cluster := "kind" 286 if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { 287 cluster = v 288 } 289 kindOptions := []string{"load", "docker-image", t.ImageName, "--name", cluster} 290 cmd := exec.Command("kind", kindOptions...) 291 _, err := t.Run(cmd) 292 return err 293 } 294 295 // LoadImageToKindClusterWithName loads a local docker image with the name informed to the kind cluster 296 func (t TestContext) LoadImageToKindClusterWithName(image string) error { 297 cluster := "kind" 298 if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { 299 cluster = v 300 } 301 kindOptions := []string{"load", "docker-image", "--name", cluster, image} 302 cmd := exec.Command("kind", kindOptions...) 303 _, err := t.Run(cmd) 304 return err 305 } 306 307 // CmdContext provides context for command execution 308 type CmdContext struct { 309 // environment variables in k=v format. 310 Env []string 311 Dir string 312 Stdin io.Reader 313 } 314 315 // Run executes the provided command within this context 316 func (cc *CmdContext) Run(cmd *exec.Cmd) ([]byte, error) { 317 cmd.Dir = cc.Dir 318 cmd.Env = append(os.Environ(), cc.Env...) 319 cmd.Stdin = cc.Stdin 320 command := strings.Join(cmd.Args, " ") 321 fmt.Fprintf(GinkgoWriter, "running: %s\n", command) 322 output, err := cmd.CombinedOutput() 323 if err != nil { 324 return output, fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) 325 } 326 327 return output, nil 328 } 329 330 // AllowProjectBeMultiGroup will update the PROJECT file with the information to allow we scaffold 331 // apis with different groups. be available. 332 func (t *TestContext) AllowProjectBeMultiGroup() error { 333 const multiGroup = `multigroup: true 334 ` 335 projectBytes, err := os.ReadFile(filepath.Join(t.Dir, "PROJECT")) 336 if err != nil { 337 return err 338 } 339 340 projectBytes = append([]byte(multiGroup), projectBytes...) 341 err = os.WriteFile(filepath.Join(t.Dir, "PROJECT"), projectBytes, 0o644) 342 if err != nil { 343 return err 344 } 345 return nil 346 }