Skip to content

Commit 7837c35

Browse files
committed
test: implement Istio installation utilities for e2e testing
- Add InstallIstioctl() function to download and install istioctl binary - Add InstallIstioMinimalWithIngress() to set up Istio with minimal profile - Add IsIstioInstalled() and WaitIstioctlAvailable() helper functions - Use positional formatting in URL template for istioctl downloads - Support configurable Istio namespace for installation - Add error handling and proper command output redirection This enables e2e tests to automatically set up Istio service mesh components required for workspace HTTP proxy functionality.
1 parent 616d1a8 commit 7837c35

File tree

1 file changed

+330
-0
lines changed
  • workspaces/controller/test/utils

1 file changed

+330
-0
lines changed
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
/*
2+
Copyright 2024.
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+
"errors"
21+
"fmt"
22+
"os"
23+
"os/exec"
24+
"strings"
25+
26+
. "github.com/onsi/ginkgo/v2" //nolint:golint
27+
)
28+
29+
const (
30+
31+
// use LTS version of istioctl
32+
istioctlVersion = "1.27.0"
33+
istioctlURL = ""
34+
35+
// use LTS version of prometheus-operator
36+
prometheusOperatorVersion = "v0.72.0"
37+
prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" +
38+
"releases/download/%s/bundle.yaml"
39+
40+
// use LTS version of cert-manager
41+
certManagerVersion = "v1.12.13"
42+
certManagerURLTmpl = "https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml"
43+
)
44+
45+
func warnError(err error) {
46+
_, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err)
47+
}
48+
49+
// Run executes the provided command within this context
50+
func Run(cmd *exec.Cmd) (string, error) {
51+
dir, _ := GetProjectDir()
52+
cmd.Dir = dir
53+
54+
if err := os.Chdir(cmd.Dir); err != nil {
55+
_, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err)
56+
}
57+
58+
cmd.Env = append(os.Environ(), "GO111MODULE=on")
59+
command := strings.Join(cmd.Args, " ")
60+
_, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command)
61+
output, err := cmd.CombinedOutput()
62+
if err != nil {
63+
return string(output), fmt.Errorf("%s failed with error: (%w) %s", command, err, string(output))
64+
}
65+
66+
return string(output), nil
67+
}
68+
69+
// UninstallPrometheusOperator uninstalls the prometheus
70+
func UninstallIstioctl() {
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+
// InstallIstioctl installs the istioctl to be used to manage istio resources.
79+
func InstallIstioctl() error{
80+
url := fmt.Sprintf("https://github.com/istio/istio/releases/download/%[1]s/istioctl-%[1]s-linux-amd64.tar.gz", istioctlVersion)
81+
downloadCmd := exec.Command("curl", "-L", url, "-o", "istioctl.tar.gz")
82+
extractCmd := exec.Command("tar", "-xzf", "istioctl.tar.gz")
83+
chmodCmd := exec.Command("chmod", "+x", "istioctl")
84+
moveCmd := exec.Command("sudo", "mv", "istioctl", "~/usr/local/bin/istioctl")
85+
86+
downloadCmd.Stdout, downloadCmd.Stderr = os.Stdout, os.Stderr
87+
extractCmd.Stdout, extractCmd.Stderr = os.Stdout, os.Stderr
88+
chmodCmd.Stdout, chmodCmd.Stderr = os.Stdout, os.Stderr
89+
moveCmd.Stdout, moveCmd.Stderr = os.Stdout, os.Stderr
90+
91+
if err := downloadCmd.Run(); err != nil {
92+
return fmt.Errorf("failed to download istioctl: %w", err)
93+
}
94+
if err := extractCmd.Run(); err != nil {
95+
return fmt.Errorf("failed to extract istioctl: %w", err)
96+
}
97+
if err := chmodCmd.Run(); err != nil {
98+
return fmt.Errorf("failed to make istioctl executable: %w", err)
99+
}
100+
if err := moveCmd.Run(); err != nil {
101+
return fmt.Errorf("failed to move istioctl to /usr/local/bin: %w", err)
102+
}
103+
return nil
104+
}
105+
106+
// InstallIstioMinimalWithIngress installs Istio with minimal profile and ingressgateway enabled.
107+
func InstallIstioMinimalWithIngress(namespace string) error {
108+
cmd := exec.Command("istioctl",
109+
"install",
110+
"--set", "profile=minimal",
111+
"--set", "values.gateways.istio-ingressgateway.enabled=true",
112+
"--set", fmt.Sprintf("values.global.istioNamespace=%s", namespace),
113+
"-y",
114+
)
115+
cmd.Stdout = os.Stdout
116+
cmd.Stderr = os.Stderr
117+
return cmd.Run()
118+
}
119+
120+
// TODO:
121+
func IsIstioInstalled() bool {
122+
cmd := exec.Command("istioctl", "version")
123+
_, err := Run(cmd)
124+
return err == nil
125+
}
126+
127+
// WaitIstioctlAvailable checks if 'istioctl' is available in PATH.
128+
// Returns nil if found, or an error if not found.
129+
func WaitIstioctlAvailable() error {
130+
if _, err := exec.LookPath("istioctl"); err != nil {
131+
return errors.New("istioctl binary not found in PATH")
132+
}
133+
return nil
134+
}
135+
136+
// UninstallPrometheusOperator uninstalls the prometheus
137+
func UninstallPrometheusOperator() {
138+
url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
139+
cmd := exec.Command("kubectl", "delete", "-f", url)
140+
if _, err := Run(cmd); err != nil {
141+
warnError(err)
142+
}
143+
}
144+
145+
// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics.
146+
func InstallPrometheusOperator() error {
147+
url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
148+
cmd := exec.Command("kubectl", "apply", "-f", url)
149+
_, err := Run(cmd)
150+
return err
151+
}
152+
153+
// WaitPrometheusOperatorRunning waits for prometheus operator to be running, and returns an error if not.
154+
func WaitPrometheusOperatorRunning() error {
155+
cmd := exec.Command("kubectl", "wait",
156+
"deployment.apps",
157+
"--for", "condition=Available",
158+
"--selector", "app.kubernetes.io/name=prometheus-operator",
159+
"--all-namespaces",
160+
"--timeout", "5m",
161+
)
162+
_, err := Run(cmd)
163+
return err
164+
}
165+
166+
// IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed
167+
// by verifying the existence of key CRDs related to Prometheus.
168+
func IsPrometheusCRDsInstalled() bool {
169+
// List of common Prometheus CRDs
170+
prometheusCRDs := []string{
171+
"prometheuses.monitoring.coreos.com",
172+
"prometheusrules.monitoring.coreos.com",
173+
"prometheusagents.monitoring.coreos.com",
174+
}
175+
176+
cmd := exec.Command("kubectl", "get", "crds", "-o", "name")
177+
output, err := Run(cmd)
178+
if err != nil {
179+
return false
180+
}
181+
crdList := GetNonEmptyLines(output)
182+
for _, crd := range prometheusCRDs {
183+
for _, line := range crdList {
184+
if strings.Contains(line, crd) {
185+
return true
186+
}
187+
}
188+
}
189+
190+
return false
191+
}
192+
193+
// UninstallCertManager uninstalls the cert manager
194+
func UninstallCertManager() {
195+
url := fmt.Sprintf(certManagerURLTmpl, certManagerVersion)
196+
cmd := exec.Command("kubectl", "delete", "-f", url)
197+
if _, err := Run(cmd); err != nil {
198+
warnError(err)
199+
}
200+
}
201+
202+
// InstallCertManager installs the cert manager bundle.
203+
func InstallCertManager() error {
204+
// remove any existing cert-manager leases
205+
// NOTE: this is required to avoid issues where cert-manager is reinstalled quickly due to rerunning tests
206+
cmd := exec.Command("kubectl", "delete",
207+
"leases",
208+
"--ignore-not-found",
209+
"--namespace", "kube-system",
210+
"cert-manager-controller",
211+
"cert-manager-cainjector-leader-election",
212+
)
213+
_, err := Run(cmd)
214+
if err != nil {
215+
return err
216+
}
217+
218+
// install cert-manager
219+
url := fmt.Sprintf(certManagerURLTmpl, certManagerVersion)
220+
cmd = exec.Command("kubectl", "apply", "-f", url)
221+
_, err = Run(cmd)
222+
return err
223+
}
224+
225+
// WaitCertManagerRunning waits for cert manager to be running, and returns an error if not.
226+
func WaitCertManagerRunning() error {
227+
228+
// Wait for the cert-manager Deployments to be Available
229+
cmd := exec.Command("kubectl", "wait",
230+
"deployment.apps",
231+
"--for", "condition=Available",
232+
"--selector", "app.kubernetes.io/instance=cert-manager",
233+
"--all-namespaces",
234+
"--timeout", "5m",
235+
)
236+
_, err := Run(cmd)
237+
if err != nil {
238+
return err
239+
}
240+
241+
// Wait for the cert-manager Endpoints to be ready
242+
// NOTE: the webhooks will not function correctly until this is ready
243+
cmd = exec.Command("kubectl", "wait",
244+
"endpoints",
245+
"--for", "jsonpath=subsets[0].addresses[0].targetRef.kind=Pod",
246+
"--selector", "app.kubernetes.io/instance=cert-manager",
247+
"--all-namespaces",
248+
"--timeout", "2m",
249+
)
250+
_, err = Run(cmd)
251+
return err
252+
}
253+
254+
// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed
255+
// by verifying the existence of key CRDs related to Cert Manager.
256+
func IsCertManagerCRDsInstalled() bool {
257+
// List of common Cert Manager CRDs
258+
certManagerCRDs := []string{
259+
"certificates.cert-manager.io",
260+
"issuers.cert-manager.io",
261+
"clusterissuers.cert-manager.io",
262+
"certificaterequests.cert-manager.io",
263+
"orders.acme.cert-manager.io",
264+
"challenges.acme.cert-manager.io",
265+
}
266+
267+
// Execute the kubectl command to get all CRDs
268+
cmd := exec.Command("kubectl", "get", "crds", "-o", "name")
269+
output, err := Run(cmd)
270+
if err != nil {
271+
return false
272+
}
273+
274+
// Check if any of the Cert Manager CRDs are present
275+
crdList := GetNonEmptyLines(output)
276+
for _, crd := range certManagerCRDs {
277+
for _, line := range crdList {
278+
if strings.Contains(line, crd) {
279+
return true
280+
}
281+
}
282+
}
283+
284+
return false
285+
}
286+
287+
// LoadImageToKindClusterWithName loads a local docker image to the kind cluster
288+
func LoadImageToKindClusterWithName(name string) error {
289+
var cluster string
290+
if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {
291+
cluster = v
292+
} else {
293+
// if `KIND_CLUSTER` is not set, get the cluster name from the kubeconfig
294+
cmd := exec.Command("kubectl", "config", "current-context")
295+
output, err := Run(cmd)
296+
if err != nil {
297+
return err
298+
}
299+
cluster = strings.TrimSpace(output)
300+
cluster = strings.Replace(cluster, "kind-", "", 1)
301+
}
302+
kindOptions := []string{"load", "docker-image", name, "--name", cluster}
303+
cmd := exec.Command("kind", kindOptions...)
304+
_, err := Run(cmd)
305+
return err
306+
}
307+
308+
// GetNonEmptyLines converts given command output string into individual objects
309+
// according to line breakers, and ignores the empty elements in it.
310+
func GetNonEmptyLines(output string) []string {
311+
var res []string
312+
elements := strings.Split(output, "\n")
313+
for _, element := range elements {
314+
if element != "" {
315+
res = append(res, element)
316+
}
317+
}
318+
319+
return res
320+
}
321+
322+
// GetProjectDir will return the directory where the project is
323+
func GetProjectDir() (string, error) {
324+
wd, err := os.Getwd()
325+
if err != nil {
326+
return wd, err
327+
}
328+
wd = strings.ReplaceAll(wd, "/test/e2e", "")
329+
return wd, nil
330+
}

0 commit comments

Comments
 (0)