utils.go
1 // Copyright 2025 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package utils 16 17 import ( 18 "bufio" 19 "bytes" 20 "fmt" 21 "os" 22 "os/exec" 23 "strings" 24 25 . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck 26 ) 27 28 const ( 29 prometheusOperatorVersion = "v0.77.1" 30 prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + 31 "releases/download/%s/bundle.yaml" 32 33 certmanagerVersion = "v1.16.3" 34 certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml" 35 ) 36 37 func warnError(err error) { 38 _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) 39 } 40 41 // Run executes the provided command within this context 42 func Run(cmd *exec.Cmd) (string, error) { 43 dir, _ := GetProjectDir() 44 cmd.Dir = dir 45 46 if err := os.Chdir(cmd.Dir); err != nil { 47 _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %q\n", err) 48 } 49 50 cmd.Env = append(os.Environ(), "GO111MODULE=on") 51 command := strings.Join(cmd.Args, " ") 52 _, _ = fmt.Fprintf(GinkgoWriter, "running: %q\n", command) 53 output, err := cmd.CombinedOutput() 54 if err != nil { 55 return string(output), fmt.Errorf("%q failed with error %q: %w", command, string(output), err) 56 } 57 58 return string(output), nil 59 } 60 61 // InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics. 62 func InstallPrometheusOperator() error { 63 url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) 64 cmd := exec.Command("kubectl", "create", "-f", url) 65 _, err := Run(cmd) 66 return err 67 } 68 69 // UninstallPrometheusOperator uninstalls the prometheus 70 func UninstallPrometheusOperator() { 71 url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) 72 cmd := exec.Command("kubectl", "delete", "-f", url) 73 if _, err := Run(cmd); err != nil { 74 warnError(err) 75 } 76 } 77 78 // IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed 79 // by verifying the existence of key CRDs related to Prometheus. 80 func IsPrometheusCRDsInstalled() bool { 81 // List of common Prometheus CRDs 82 prometheusCRDs := []string{ 83 "prometheuses.monitoring.coreos.com", 84 "prometheusrules.monitoring.coreos.com", 85 "prometheusagents.monitoring.coreos.com", 86 } 87 88 cmd := exec.Command("kubectl", "get", "crds", "-o", "custom-columns=NAME:.metadata.name") 89 output, err := Run(cmd) 90 if err != nil { 91 return false 92 } 93 crdList := GetNonEmptyLines(output) 94 for _, crd := range prometheusCRDs { 95 for _, line := range crdList { 96 if strings.Contains(line, crd) { 97 return true 98 } 99 } 100 } 101 102 return false 103 } 104 105 // UninstallCertManager uninstalls the cert manager 106 func UninstallCertManager() { 107 url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) 108 cmd := exec.Command("kubectl", "delete", "-f", url) 109 if _, err := Run(cmd); err != nil { 110 warnError(err) 111 } 112 } 113 114 // InstallCertManager installs the cert manager bundle. 115 func InstallCertManager() error { 116 url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) 117 cmd := exec.Command("kubectl", "apply", "-f", url) 118 if _, err := Run(cmd); err != nil { 119 return err 120 } 121 // Wait for cert-manager-webhook to be ready, which can take time if cert-manager 122 // was re-installed after uninstalling on a cluster. 123 cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook", 124 "--for", "condition=Available", 125 "--namespace", "cert-manager", 126 "--timeout", "5m", 127 ) 128 129 _, err := Run(cmd) 130 return err 131 } 132 133 // IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed 134 // by verifying the existence of key CRDs related to Cert Manager. 135 func IsCertManagerCRDsInstalled() bool { 136 // List of common Cert Manager CRDs 137 certManagerCRDs := []string{ 138 "certificates.cert-manager.io", 139 "issuers.cert-manager.io", 140 "clusterissuers.cert-manager.io", 141 "certificaterequests.cert-manager.io", 142 "orders.acme.cert-manager.io", 143 "challenges.acme.cert-manager.io", 144 } 145 146 // Execute the kubectl command to get all CRDs 147 cmd := exec.Command("kubectl", "get", "crds") 148 output, err := Run(cmd) 149 if err != nil { 150 return false 151 } 152 153 // Check if any of the Cert Manager CRDs are present 154 crdList := GetNonEmptyLines(output) 155 for _, crd := range certManagerCRDs { 156 for _, line := range crdList { 157 if strings.Contains(line, crd) { 158 return true 159 } 160 } 161 } 162 163 return false 164 } 165 166 // LoadImageToKindClusterWithName loads a local docker image to the kind cluster 167 func LoadImageToKindClusterWithName(name string) error { 168 cluster := "kind" 169 if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { 170 cluster = v 171 } 172 kindOptions := []string{"load", "docker-image", name, "--name", cluster} 173 cmd := exec.Command("kind", kindOptions...) 174 _, err := Run(cmd) 175 return err 176 } 177 178 // GetNonEmptyLines converts given command output string into individual objects 179 // according to line breakers, and ignores the empty elements in it. 180 func GetNonEmptyLines(output string) []string { 181 var res []string 182 elements := strings.Split(output, "\n") 183 for _, element := range elements { 184 if element != "" { 185 res = append(res, element) 186 } 187 } 188 189 return res 190 } 191 192 // GetProjectDir will return the directory where the project is 193 func GetProjectDir() (string, error) { 194 wd, err := os.Getwd() 195 if err != nil { 196 return wd, fmt.Errorf("failed to get current working directory: %w", err) 197 } 198 wd = strings.ReplaceAll(wd, "/test/e2e", "") 199 return wd, nil 200 } 201 202 // UncommentCode searches for target in the file and remove the comment prefix 203 // of the target content. The target content may span multiple lines. 204 func UncommentCode(filename, target, prefix string) error { 205 // false positive 206 // nolint:gosec 207 content, err := os.ReadFile(filename) 208 if err != nil { 209 return fmt.Errorf("failed to read file %q: %w", filename, err) 210 } 211 strContent := string(content) 212 213 idx := strings.Index(strContent, target) 214 if idx < 0 { 215 return fmt.Errorf("unable to find the code %q to be uncomment", target) 216 } 217 218 out := new(bytes.Buffer) 219 _, err = out.Write(content[:idx]) 220 if err != nil { 221 return fmt.Errorf("failed to write to output: %w", err) 222 } 223 224 scanner := bufio.NewScanner(bytes.NewBufferString(target)) 225 if !scanner.Scan() { 226 return nil 227 } 228 for { 229 if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil { 230 return fmt.Errorf("failed to write to output: %w", err) 231 } 232 // Avoid writing a newline in case the previous line was the last in target. 233 if !scanner.Scan() { 234 break 235 } 236 if _, err = out.WriteString("\n"); err != nil { 237 return fmt.Errorf("failed to write to output: %w", err) 238 } 239 } 240 241 if _, err = out.Write(content[idx+len(target):]); err != nil { 242 return fmt.Errorf("failed to write to output: %w", err) 243 } 244 245 // false positive 246 // nolint:gosec 247 if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil { 248 return fmt.Errorf("failed to write file %q: %w", filename, err) 249 } 250 251 return nil 252 }