From 95f18da965c11a4ae53d4037efeec1d82b7636b8 Mon Sep 17 00:00:00 2001 From: Yash Pal Date: Fri, 5 Sep 2025 17:15:19 +0530 Subject: [PATCH 1/6] 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. Signed-off-by: Yash Pal --- .../controller/test/e2e/e2e_suite_test.go | 18 +++++ workspaces/controller/test/utils/utils.go | 72 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/workspaces/controller/test/e2e/e2e_suite_test.go b/workspaces/controller/test/e2e/e2e_suite_test.go index 956d1ff0..c7515dae 100644 --- a/workspaces/controller/test/e2e/e2e_suite_test.go +++ b/workspaces/controller/test/e2e/e2e_suite_test.go @@ -40,6 +40,9 @@ var ( // isPrometheusOperatorAlreadyInstalled will be set true when prometheus CRDs be found on the cluster // isPrometheusOperatorAlreadyInstalled = false + + isIstioctlAlreadyInstalled = false + skipIstioctlInstall = os.Getenv("ISTIO_INSTALL_SKIP") == "true" ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, @@ -88,6 +91,21 @@ var _ = BeforeSuite(func() { } By("checking that cert manager is running") Expect(utils.WaitCertManagerRunning()).To(Succeed(), "CertManager is not running") + + + if !skipIstioctlInstall { + By("checking if istioctl is installed already") + isIstioctlAlreadyInstalled = utils.IsIstioInstalled() + if !isIstioctlAlreadyInstalled { + _, _ = fmt.Fprintf(GinkgoWriter, "Installing istioctl...\n") + Expect(utils.InstallIstioctl()).To(Succeed(), "Failed to install istioctl") + } else { + _, _ = fmt.Fprintf(GinkgoWriter, "WARNING: istioctl is already installed. Skipping installation...\n") + } + } + By("checking that istioctl is available") + Expect(utils.WaitIstioctlAvailable()).To(Succeed(), "istioctl is not available") + }) var _ = AfterSuite(func() { diff --git a/workspaces/controller/test/utils/utils.go b/workspaces/controller/test/utils/utils.go index eba1f02e..91655b14 100644 --- a/workspaces/controller/test/utils/utils.go +++ b/workspaces/controller/test/utils/utils.go @@ -17,6 +17,7 @@ limitations under the License. package utils import ( + "errors" "fmt" "os" "os/exec" @@ -27,6 +28,10 @@ import ( const ( + // use LTS version of istioctl + istioctlVersion = "1.27.0" + istioctlURL = "" + // use LTS version of prometheus-operator prometheusOperatorVersion = "v0.72.0" prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + @@ -61,6 +66,73 @@ func Run(cmd *exec.Cmd) (string, error) { return string(output), nil } +// UninstallPrometheusOperator uninstalls the prometheus +func UninstallIstioctl() { + url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) + cmd := exec.Command("kubectl", "delete", "-f", url) + if _, err := Run(cmd); err != nil { + warnError(err) + } +} + +// InstallIstioctl installs the istioctl to be used to manage istio resources. +func InstallIstioctl() error{ + url := fmt.Sprintf("https://github.com/istio/istio/releases/download/%[1]s/istioctl-%[1]s-linux-amd64.tar.gz", istioctlVersion) + downloadCmd := exec.Command("curl", "-L", url, "-o", "istioctl.tar.gz") + extractCmd := exec.Command("tar", "-xzf", "istioctl.tar.gz") + chmodCmd := exec.Command("chmod", "+x", "istioctl") + moveCmd := exec.Command("sudo", "mv", "istioctl", "~/usr/local/bin/istioctl") + + downloadCmd.Stdout, downloadCmd.Stderr = os.Stdout, os.Stderr + extractCmd.Stdout, extractCmd.Stderr = os.Stdout, os.Stderr + chmodCmd.Stdout, chmodCmd.Stderr = os.Stdout, os.Stderr + moveCmd.Stdout, moveCmd.Stderr = os.Stdout, os.Stderr + + if err := downloadCmd.Run(); err != nil { + return fmt.Errorf("failed to download istioctl: %w", err) + } + if err := extractCmd.Run(); err != nil { + return fmt.Errorf("failed to extract istioctl: %w", err) + } + if err := chmodCmd.Run(); err != nil { + return fmt.Errorf("failed to make istioctl executable: %w", err) + } + if err := moveCmd.Run(); err != nil { + return fmt.Errorf("failed to move istioctl to /usr/local/bin: %w", err) + } + return nil +} + +// InstallIstioMinimalWithIngress installs Istio with minimal profile and ingressgateway enabled. +func InstallIstioMinimalWithIngress(namespace string) error { + cmd := exec.Command("istioctl", + "install", + "--set", "profile=minimal", + "--set", "values.gateways.istio-ingressgateway.enabled=true", + "--set", fmt.Sprintf("values.global.istioNamespace=%s", namespace), + "-y", + ) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// TODO: +func IsIstioInstalled() bool { + cmd := exec.Command("istioctl", "version") + _, err := Run(cmd) + return err == nil +} + +// WaitIstioctlAvailable checks if 'istioctl' is available in PATH. +// Returns nil if found, or an error if not found. +func WaitIstioctlAvailable() error { + if _, err := exec.LookPath("istioctl"); err != nil { + return errors.New("istioctl binary not found in PATH") + } + return nil +} + // UninstallPrometheusOperator uninstalls the prometheus func UninstallPrometheusOperator() { url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) From 6b345ffe48d2c970d45f80db6f02cda35b1d28a6 Mon Sep 17 00:00:00 2001 From: Yash Pal Date: Sat, 6 Sep 2025 16:33:46 +0530 Subject: [PATCH 2/6] feat: add cross-platform istioctl installation and Istio gateway management - Add InstallIstioctl() with OS/arch detection for Linux, macOS, Windows - Add comprehensive Istio gateway functions (install, check, wait) - Enhance WaitIstioAvailable() to verify all Istio components - Add UninstallIstioctl() for complete cleanup - Use platform-specific download methods with fallbacks Signed-off-by: Yash Pal --- .../controller/test/e2e/e2e_suite_test.go | 2 +- workspaces/controller/test/utils/utils.go | 398 ++++++++++++++++-- 2 files changed, 353 insertions(+), 47 deletions(-) diff --git a/workspaces/controller/test/e2e/e2e_suite_test.go b/workspaces/controller/test/e2e/e2e_suite_test.go index c7515dae..4f4975a2 100644 --- a/workspaces/controller/test/e2e/e2e_suite_test.go +++ b/workspaces/controller/test/e2e/e2e_suite_test.go @@ -104,7 +104,7 @@ var _ = BeforeSuite(func() { } } By("checking that istioctl is available") - Expect(utils.WaitIstioctlAvailable()).To(Succeed(), "istioctl is not available") + Expect(utils.WaitIstioAvailable()).To(Succeed(), "istioctl is not available") }) diff --git a/workspaces/controller/test/utils/utils.go b/workspaces/controller/test/utils/utils.go index 91655b14..fc3194c6 100644 --- a/workspaces/controller/test/utils/utils.go +++ b/workspaces/controller/test/utils/utils.go @@ -17,10 +17,10 @@ limitations under the License. package utils import ( - "errors" "fmt" "os" "os/exec" + "runtime" "strings" . "github.com/onsi/ginkgo/v2" //nolint:golint @@ -30,7 +30,7 @@ const ( // use LTS version of istioctl istioctlVersion = "1.27.0" - istioctlURL = "" + istioctlURL = "" // use LTS version of prometheus-operator prometheusOperatorVersion = "v0.72.0" @@ -66,55 +66,215 @@ func Run(cmd *exec.Cmd) (string, error) { return string(output), nil } -// UninstallPrometheusOperator uninstalls the prometheus func UninstallIstioctl() { - url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) - cmd := exec.Command("kubectl", "delete", "-f", url) - if _, err := Run(cmd); err != nil { - warnError(err) - } + // First, uninstall Istio components if they exist + if IsIstioInstalled() { + fmt.Println("Uninstalling Istio components...") + cmd := exec.Command("istioctl", "uninstall", "--purge", "-y") + if _, err := Run(cmd); err != nil { + warnError(fmt.Errorf("failed to uninstall Istio components: %w", err)) + } + + // Delete istio-system namespace + cmd = exec.Command("kubectl", "delete", "namespace", "istio-system", "--ignore-not-found") + if _, err := Run(cmd); err != nil { + warnError(fmt.Errorf("failed to delete istio-system namespace: %w", err)) + } + } + + // Remove istioctl binary from system + osName := runtime.GOOS + var rmCmd *exec.Cmd + + if osName == "windows" { + // Windows: Remove from C:\istioctl + rmCmd = exec.Command("del", "/f", "/q", "C:\\istioctl\\istioctl.exe") + } else { + // Unix-like: Remove from /usr/local/bin + rmCmd = exec.Command("sudo", "rm", "-f", "/usr/local/bin/istioctl") + } + + if _, err := Run(rmCmd); err != nil { + warnError(fmt.Errorf("failed to remove istioctl binary: %w", err)) + } + + fmt.Println("Istioctl uninstalled successfully") } // InstallIstioctl installs the istioctl to be used to manage istio resources. -func InstallIstioctl() error{ - url := fmt.Sprintf("https://github.com/istio/istio/releases/download/%[1]s/istioctl-%[1]s-linux-amd64.tar.gz", istioctlVersion) - downloadCmd := exec.Command("curl", "-L", url, "-o", "istioctl.tar.gz") - extractCmd := exec.Command("tar", "-xzf", "istioctl.tar.gz") - chmodCmd := exec.Command("chmod", "+x", "istioctl") - moveCmd := exec.Command("sudo", "mv", "istioctl", "~/usr/local/bin/istioctl") +func InstallIstioctl() error { + osName := runtime.GOOS + archName := runtime.GOARCH - downloadCmd.Stdout, downloadCmd.Stderr = os.Stdout, os.Stderr - extractCmd.Stdout, extractCmd.Stderr = os.Stdout, os.Stderr - chmodCmd.Stdout, chmodCmd.Stderr = os.Stdout, os.Stderr - moveCmd.Stdout, moveCmd.Stderr = os.Stdout, os.Stderr + // Map Go architecture names to Istio release names + switch archName { + case "amd64": + archName = "amd64" + case "arm64": + archName = "arm64" + case "386": + return fmt.Errorf("32-bit architectures are not supported by Istio") + default: + return fmt.Errorf("unsupported architecture: %s", archName) + } - if err := downloadCmd.Run(); err != nil { - return fmt.Errorf("failed to download istioctl: %w", err) - } - if err := extractCmd.Run(); err != nil { - return fmt.Errorf("failed to extract istioctl: %w", err) - } - if err := chmodCmd.Run(); err != nil { - return fmt.Errorf("failed to make istioctl executable: %w", err) - } - if err := moveCmd.Run(); err != nil { - return fmt.Errorf("failed to move istioctl to /usr/local/bin: %w", err) - } - return nil + // Map Go OS names to Istio release names and determine file extension + var fileExt string + switch osName { + case "linux": + osName = "linux" + fileExt = "tar.gz" + case "darwin": + osName = "osx" + fileExt = "tar.gz" + case "windows": + osName = "win" + fileExt = "zip" + default: + return fmt.Errorf("unsupported operating system: %s", osName) + } + + // Construct the download URL dynamically + fileName := fmt.Sprintf("istioctl-%s-%s-%s.%s", istioctlVersion, osName, archName, fileExt) + url := fmt.Sprintf("https://github.com/istio/istio/releases/download/%s/%s", istioctlVersion, fileName) + + // Set the binary name based on OS + binaryName := "istioctl" + if osName == "win" { + binaryName = "istioctl.exe" + } + + // Download the file using platform-appropriate method with fallbacks + downloadSuccess := false + + // Try primary download method + var primaryCmd *exec.Cmd + var fallbackCmd *exec.Cmd + + if osName == "win" { + // Windows: PowerShell first, curl as fallback + // Use proper PowerShell syntax with double quotes and escape handling + primaryCmd = exec.Command("powershell", "-ExecutionPolicy", "Bypass", "-Command", + fmt.Sprintf(`Invoke-WebRequest -Uri "%s" -OutFile "%s"`, url, fileName)) + fallbackCmd = exec.Command("curl", "-L", url, "-o", fileName) + } else { + // Unix-like: curl first, wget as fallback + primaryCmd = exec.Command("curl", "-L", url, "-o", fileName) + fallbackCmd = exec.Command("wget", "-O", fileName, url) + } + + // Try primary method + primaryCmd.Stdout, primaryCmd.Stderr = os.Stdout, os.Stderr + if err := primaryCmd.Run(); err == nil { + downloadSuccess = true + } else { + // Try fallback method + fallbackCmd.Stdout, fallbackCmd.Stderr = os.Stdout, os.Stderr + if err := fallbackCmd.Run(); err == nil { + downloadSuccess = true + } + } + + if !downloadSuccess { + if osName == "win" { + return fmt.Errorf("failed to download istioctl from %s using both PowerShell and curl", url) + } else { + return fmt.Errorf("failed to download istioctl from %s using both curl and wget", url) + } + } + + // Extract based on file type + var extractCmd *exec.Cmd + switch fileExt { + case "tar.gz": + extractCmd = exec.Command("tar", "-xzf", fileName) + case "zip": + extractCmd = exec.Command("unzip", "-q", fileName) + default: + return fmt.Errorf("unsupported file extension: %s", fileExt) + } + extractCmd.Stdout, extractCmd.Stderr = os.Stdout, os.Stderr + + if err := extractCmd.Run(); err != nil { + return fmt.Errorf("failed to extract %s: %w", fileName, err) + } + + // Find the extracted binary (it could be in various subdirectories) + findCmd := exec.Command("find", ".", "-name", binaryName, "-type", "f") + output, err := findCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to find istioctl binary after extraction: %w", err) + } + + binaryPath := strings.TrimSpace(string(output)) + if binaryPath == "" { + return fmt.Errorf("istioctl binary not found in extracted files") + } + + // Use the first found binary if multiple exist + if strings.Contains(binaryPath, "\n") { + binaryPath = strings.Split(binaryPath, "\n")[0] + } + + // Copy the binary to current directory with standard name + if binaryPath != binaryName { + var cpCmd *exec.Cmd + if osName == "win" { + cpCmd = exec.Command("copy", binaryPath, binaryName) + } else { + cpCmd = exec.Command("cp", binaryPath, binaryName) + } + if err := cpCmd.Run(); err != nil { + return fmt.Errorf("failed to copy istioctl binary: %w", err) + } + } + + // Make executable (not needed on Windows) + if osName != "win" { + chmodCmd := exec.Command("chmod", "+x", binaryName) + if err := chmodCmd.Run(); err != nil { + return fmt.Errorf("failed to make istioctl executable: %w", err) + } + } + + // Move to appropriate bin directory + var moveCmd *exec.Cmd + if osName == "win" { + // Try to create and use a local bin directory on Windows + mkdirCmd := exec.Command("mkdir", "-p", "C:\\istioctl") + mkdirCmd.Run() // Ignore errors if directory exists + moveCmd = exec.Command("move", binaryName, "C:\\istioctl\\istioctl.exe") + } else { + moveCmd = exec.Command("sudo", "mv", binaryName, "/usr/local/bin/istioctl") + } + + moveCmd.Stdout, moveCmd.Stderr = os.Stdout, os.Stderr + if err := moveCmd.Run(); err != nil { + return fmt.Errorf("failed to move istioctl to bin directory: %w", err) + } + + // Clean up downloaded files + cleanupCmd := exec.Command("rm", "-f", fileName) + if osName == "win" { + cleanupCmd = exec.Command("del", "/f", fileName) + } + cleanupCmd.Run() // Ignore cleanup errors + + return nil } // InstallIstioMinimalWithIngress installs Istio with minimal profile and ingressgateway enabled. func InstallIstioMinimalWithIngress(namespace string) error { - cmd := exec.Command("istioctl", - "install", - "--set", "profile=minimal", - "--set", "values.gateways.istio-ingressgateway.enabled=true", - "--set", fmt.Sprintf("values.global.istioNamespace=%s", namespace), - "-y", - ) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + cmd := exec.Command("istioctl", + "install", + "--set", "profile=minimal", + "--set", "values.gateways.istio-ingressgateway.enabled=true", + "--set", fmt.Sprintf("values.global.istioNamespace=%s", namespace), + "-y", + ) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() } // TODO: @@ -124,15 +284,161 @@ func IsIstioInstalled() bool { return err == nil } -// WaitIstioctlAvailable checks if 'istioctl' is available in PATH. -// Returns nil if found, or an error if not found. -func WaitIstioctlAvailable() error { - if _, err := exec.LookPath("istioctl"); err != nil { - return errors.New("istioctl binary not found in PATH") +// WaitIstioAvailable waits for Istio to be available and running. +// Returns nil if Istio is ready, or an error if not ready within timeout. +func WaitIstioAvailable() error { + // Wait for istio-system namespace to exist + cmd := exec.Command("kubectl", "wait", + "--for=condition=Ready", + "namespace/istio-system", + "--timeout=300s") + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istio-system namespace not ready: %w", err) + } + + // Wait for Istio control plane (istiod) pods to be ready + cmd = exec.Command("kubectl", "wait", + "--for=condition=Ready", + "pods", + "-l", "app=istiod", + "-n", "istio-system", + "--timeout=300s") + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istiod pods not ready: %w", err) + } + + // Wait for Istio ingress gateway pods to be ready + cmd = exec.Command("kubectl", "wait", + "--for=condition=Ready", + "pods", + "-l", "app=istio-ingressgateway", + "-n", "istio-system", + "--timeout=300s") + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istio-ingressgateway pods not ready: %w", err) + } + + // Wait for Istio egress gateway pods to be ready (if present) + // Note: egress gateway is optional, so we don't fail if it's not found + cmd = exec.Command("kubectl", "get", + "pods", + "-l", "app=istio-egressgateway", + "-n", "istio-system", + "--no-headers") + output, err := Run(cmd) + if err == nil && len(strings.TrimSpace(output)) > 0 { + // Egress gateway exists, wait for it to be ready + cmd = exec.Command("kubectl", "wait", + "--for=condition=Ready", + "pods", + "-l", "app=istio-egressgateway", + "-n", "istio-system", + "--timeout=300s") + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istio-egressgateway pods not ready: %w", err) + } + } + + // Verify istioctl can analyze (optional validation) + cmd = exec.Command("istioctl", "analyze", "--all-namespaces") + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istioctl analyze failed: %w", err) + } + + return nil +} + +// IsIstioIngressGatewayInstalled checks if istio-ingressgateway is installed +func IsIstioIngressGatewayInstalled() bool { + cmd := exec.Command("kubectl", "get", "deployment", + "-n", "istio-system", + "istio-ingressgateway", + "--ignore-not-found") + output, err := Run(cmd) + if err != nil { + return false } + return len(strings.TrimSpace(output)) > 0 +} + +// InstallIstioIngressGateway installs the istio-ingressgateway +func InstallIstioIngressGateway() error { + // Check if Istio is installed first + if !IsIstioInstalled() { + return fmt.Errorf("istio must be installed before installing ingress gateway") + } + + // Install ingress gateway using istioctl + cmd := exec.Command("istioctl", "install", + "--set", "components.ingressGateways[0].enabled=true", + "--set", "components.ingressGateways[0].name=istio-ingressgateway", + "-y") + + _, err := Run(cmd) + return err +} + +// WaitIstioIngressGatewayReady waits for istio-ingressgateway to be ready +func WaitIstioIngressGatewayReady() error { + // Wait for the deployment to be available + cmd := exec.Command("kubectl", "wait", + "--for=condition=Available", + "deployment/istio-ingressgateway", + "-n", "istio-system", + "--timeout=300s") + + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istio-ingressgateway deployment not available: %w", err) + } + + // Wait for the pods to be ready + cmd = exec.Command("kubectl", "wait", + "--for=condition=Ready", + "pods", + "-l", "app=istio-ingressgateway", + "-n", "istio-system", + "--timeout=300s") + + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istio-ingressgateway pods not ready: %w", err) + } + + // Wait for the service to have external IP (if LoadBalancer type) + cmd = exec.Command("kubectl", "get", "service", + "istio-ingressgateway", + "-n", "istio-system", + "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}") + + // Note: This might not apply for all environments (like kind/minikube) + // so we don't fail if external IP is not assigned + Run(cmd) // Ignore error for external IP check + return nil } +// EnsureIstioIngressGateway checks, installs, and waits for istio-ingressgateway +func EnsureIstioIngressGateway() error { + // Check if already installed + if IsIstioIngressGatewayInstalled() { + fmt.Println("Istio ingress gateway is already installed") + } else { + fmt.Println("Installing Istio ingress gateway...") + if err := InstallIstioIngressGateway(); err != nil { + return fmt.Errorf("failed to install istio-ingressgateway: %w", err) + } + } + + // Wait for it to be ready + fmt.Println("Waiting for Istio ingress gateway to be ready...") + if err := WaitIstioIngressGatewayReady(); err != nil { + return fmt.Errorf("istio-ingressgateway failed to become ready: %w", err) + } + + fmt.Println("Istio ingress gateway is ready!") + return nil +} + + // UninstallPrometheusOperator uninstalls the prometheus func UninstallPrometheusOperator() { url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) From 1caea0fa250aea656f3357ad97b9c110e5f315da Mon Sep 17 00:00:00 2001 From: Yash Pal Date: Tue, 9 Sep 2025 00:51:14 +0530 Subject: [PATCH 3/6] feat: add istioctl installation and istio deployment support for e2e tests - Add istioctl binary download and installation for multiple platforms - Implement Istio minimal profile deployment with ingress gateway - Add comprehensive Istio availability checks and wait functions - Improve error handling and cleanup for test components - Add platform-specific binary management for Windows/Linux/macOS Signed-off-by: Yash Pal --- workspaces/controller/Makefile | 19 +- workspaces/controller/go.mod | 2 +- .../controller/test/e2e/e2e_suite_test.go | 3 +- workspaces/controller/test/utils/utils.go | 312 +++++++++++------- 4 files changed, 211 insertions(+), 125 deletions(-) diff --git a/workspaces/controller/Makefile b/workspaces/controller/Makefile index 6032c8c9..4f0cc1f1 100644 --- a/workspaces/controller/Makefile +++ b/workspaces/controller/Makefile @@ -64,9 +64,10 @@ test: manifests generate fmt vet envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. -# Prometheus and CertManager are installed by default; skip with: +# Prometheus, CertManager, and Istio are installed by default; skip with: # - PROMETHEUS_INSTALL_SKIP=true # - CERT_MANAGER_INSTALL_SKIP=true +# - ISTIO_INSTALL_SKIP=true .PHONY: test-e2e test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. @command -v kind >/dev/null 2>&1 || { \ @@ -77,6 +78,22 @@ test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ exit 1; \ } + @echo "Installing CertManager..." + @if [ "$(CERT_MANAGER_INSTALL_SKIP)" != "true" ]; then \ + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml; \ + echo "Waiting for CertManager deployments to be ready..."; \ + kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager -n cert-manager; \ + kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager-cainjector -n cert-manager; \ + kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager-webhook -n cert-manager; \ + echo "CertManager is ready!"; \ + fi + @echo "Installing Istio..." + @if [ "$(ISTIO_INSTALL_SKIP)" != "true" ]; then \ + echo "Istio installation will be handled by the test suite"; \ + fi + @if [ "$(PROMETHEUS_INSTALL_SKIP)" != "true" ]; then \ + echo "Installing Prometheus (if needed)..."; \ + fi go test ./test/e2e/ -v -ginkgo.v .PHONY: lint diff --git a/workspaces/controller/go.mod b/workspaces/controller/go.mod index 8e1b5b5b..b5c84804 100644 --- a/workspaces/controller/go.mod +++ b/workspaces/controller/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-logr/logr v1.4.2 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 + golang.org/x/time v0.3.0 k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 k8s.io/client-go v0.31.0 @@ -56,7 +57,6 @@ require ( golang.org/x/sys v0.21.0 // indirect golang.org/x/term v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/workspaces/controller/test/e2e/e2e_suite_test.go b/workspaces/controller/test/e2e/e2e_suite_test.go index 4f4975a2..59860cd0 100644 --- a/workspaces/controller/test/e2e/e2e_suite_test.go +++ b/workspaces/controller/test/e2e/e2e_suite_test.go @@ -42,7 +42,7 @@ var ( // isPrometheusOperatorAlreadyInstalled = false isIstioctlAlreadyInstalled = false - skipIstioctlInstall = os.Getenv("ISTIO_INSTALL_SKIP") == "true" + skipIstioctlInstall = os.Getenv("ISTIO_INSTALL_SKIP") == "true" ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, @@ -92,7 +92,6 @@ var _ = BeforeSuite(func() { By("checking that cert manager is running") Expect(utils.WaitCertManagerRunning()).To(Succeed(), "CertManager is not running") - if !skipIstioctlInstall { By("checking if istioctl is installed already") isIstioctlAlreadyInstalled = utils.IsIstioInstalled() diff --git a/workspaces/controller/test/utils/utils.go b/workspaces/controller/test/utils/utils.go index fc3194c6..3b50d3a3 100644 --- a/workspaces/controller/test/utils/utils.go +++ b/workspaces/controller/test/utils/utils.go @@ -67,38 +67,43 @@ func Run(cmd *exec.Cmd) (string, error) { } func UninstallIstioctl() { - // First, uninstall Istio components if they exist - if IsIstioInstalled() { - fmt.Println("Uninstalling Istio components...") - cmd := exec.Command("istioctl", "uninstall", "--purge", "-y") - if _, err := Run(cmd); err != nil { - warnError(fmt.Errorf("failed to uninstall Istio components: %w", err)) - } - - // Delete istio-system namespace - cmd = exec.Command("kubectl", "delete", "namespace", "istio-system", "--ignore-not-found") - if _, err := Run(cmd); err != nil { - warnError(fmt.Errorf("failed to delete istio-system namespace: %w", err)) - } - } + // First, uninstall Istio components if they exist + if IsIstioInstalled() { + fmt.Println("Uninstalling Istio components...") + cmd := exec.Command("istioctl", "uninstall", "--purge", "-y") + if _, err := Run(cmd); err != nil { + warnError(fmt.Errorf("failed to uninstall Istio components: %w", err)) + } - // Remove istioctl binary from system - osName := runtime.GOOS - var rmCmd *exec.Cmd - - if osName == "windows" { - // Windows: Remove from C:\istioctl - rmCmd = exec.Command("del", "/f", "/q", "C:\\istioctl\\istioctl.exe") - } else { - // Unix-like: Remove from /usr/local/bin - rmCmd = exec.Command("sudo", "rm", "-f", "/usr/local/bin/istioctl") - } - - if _, err := Run(rmCmd); err != nil { - warnError(fmt.Errorf("failed to remove istioctl binary: %w", err)) - } - - fmt.Println("Istioctl uninstalled successfully") + // Delete istio-system namespace + cmd = exec.Command("kubectl", "delete", "namespace", "istio-system", "--ignore-not-found") + if _, err := Run(cmd); err != nil { + warnError(fmt.Errorf("failed to delete istio-system namespace: %w", err)) + } + } + + // Remove istioctl binary from system + osName := runtime.GOOS + var rmCmd *exec.Cmd + + if osName == "windows" { + // Windows: Remove from C:\istioctl + rmCmd = exec.Command("del", "/f", "/q", "C:\\istioctl\\istioctl.exe") + } else { + // Unix-like: Remove from local bin directory + homeDir, err := os.UserHomeDir() + if err != nil { + warnError(fmt.Errorf("failed to get user home directory: %w", err)) + return + } + rmCmd = exec.Command("rm", "-f", homeDir+"/.local/bin/istioctl") + } + + if _, err := Run(rmCmd); err != nil { + warnError(fmt.Errorf("failed to remove istioctl binary: %w", err)) + } + + fmt.Println("Istioctl uninstalled successfully") } // InstallIstioctl installs the istioctl to be used to manage istio resources. @@ -186,13 +191,13 @@ func InstallIstioctl() error { // Extract based on file type var extractCmd *exec.Cmd switch fileExt { - case "tar.gz": - extractCmd = exec.Command("tar", "-xzf", fileName) - case "zip": - extractCmd = exec.Command("unzip", "-q", fileName) - default: - return fmt.Errorf("unsupported file extension: %s", fileExt) - } + case "tar.gz": + extractCmd = exec.Command("tar", "-xzf", fileName) + case "zip": + extractCmd = exec.Command("unzip", "-q", fileName) + default: + return fmt.Errorf("unsupported file extension: %s", fileExt) + } extractCmd.Stdout, extractCmd.Stderr = os.Stdout, os.Stderr if err := extractCmd.Run(); err != nil { @@ -216,8 +221,10 @@ func InstallIstioctl() error { binaryPath = strings.Split(binaryPath, "\n")[0] } - // Copy the binary to current directory with standard name - if binaryPath != binaryName { + // Copy the binary to current directory with standard name if needed + // Handle case where binaryPath might be "./istioctl" vs "istioctl" + normalizedPath := strings.TrimPrefix(binaryPath, "./") + if normalizedPath != binaryName { var cpCmd *exec.Cmd if osName == "win" { cpCmd = exec.Command("copy", binaryPath, binaryName) @@ -238,14 +245,31 @@ func InstallIstioctl() error { } // Move to appropriate bin directory + // Use local bin directory to avoid sudo requirements + var binDir string var moveCmd *exec.Cmd + if osName == "win" { - // Try to create and use a local bin directory on Windows - mkdirCmd := exec.Command("mkdir", "-p", "C:\\istioctl") + // Use a local bin directory on Windows + binDir = "C:\\istioctl" + mkdirCmd := exec.Command("mkdir", "-p", binDir) mkdirCmd.Run() // Ignore errors if directory exists - moveCmd = exec.Command("move", binaryName, "C:\\istioctl\\istioctl.exe") + moveCmd = exec.Command("move", binaryName, binDir+"\\istioctl.exe") } else { - moveCmd = exec.Command("sudo", "mv", binaryName, "/usr/local/bin/istioctl") + // Use local bin directory instead of /usr/local/bin to avoid sudo + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get user home directory: %w", err) + } + binDir = homeDir + "/.local/bin" + + // Create the bin directory if it doesn't exist + mkdirCmd := exec.Command("mkdir", "-p", binDir) + if err := mkdirCmd.Run(); err != nil { + return fmt.Errorf("failed to create local bin directory: %w", err) + } + + moveCmd = exec.Command("mv", binaryName, binDir+"/istioctl") } moveCmd.Stdout, moveCmd.Stderr = os.Stdout, os.Stderr @@ -253,6 +277,13 @@ func InstallIstioctl() error { return fmt.Errorf("failed to move istioctl to bin directory: %w", err) } + // Add to PATH notice + if osName != "win" { + fmt.Printf("istioctl installed to %s\n", binDir) + fmt.Printf("Make sure %s is in your PATH by adding this to your shell profile:\n", binDir) + fmt.Printf("export PATH=\"%s:$PATH\"\n", binDir) + } + // Clean up downloaded files cleanupCmd := exec.Command("rm", "-f", fileName) if osName == "win" { @@ -287,13 +318,10 @@ func IsIstioInstalled() bool { // WaitIstioAvailable waits for Istio to be available and running. // Returns nil if Istio is ready, or an error if not ready within timeout. func WaitIstioAvailable() error { - // Wait for istio-system namespace to exist - cmd := exec.Command("kubectl", "wait", - "--for=condition=Ready", - "namespace/istio-system", - "--timeout=300s") + // First check if istio-system namespace exists + cmd := exec.Command("kubectl", "get", "namespace", "istio-system") if _, err := Run(cmd); err != nil { - return fmt.Errorf("istio-system namespace not ready: %w", err) + return fmt.Errorf("istio-system namespace not found: %w", err) } // Wait for Istio control plane (istiod) pods to be ready @@ -326,7 +354,7 @@ func WaitIstioAvailable() error { "-n", "istio-system", "--no-headers") output, err := Run(cmd) - if err == nil && len(strings.TrimSpace(output)) > 0 { + if err == nil && len(strings.TrimSpace(output)) > 0 && !strings.Contains(output, "No resources found") { // Egress gateway exists, wait for it to be ready cmd = exec.Command("kubectl", "wait", "--for=condition=Ready", @@ -350,95 +378,94 @@ func WaitIstioAvailable() error { // IsIstioIngressGatewayInstalled checks if istio-ingressgateway is installed func IsIstioIngressGatewayInstalled() bool { - cmd := exec.Command("kubectl", "get", "deployment", - "-n", "istio-system", - "istio-ingressgateway", - "--ignore-not-found") - output, err := Run(cmd) - if err != nil { - return false - } - return len(strings.TrimSpace(output)) > 0 + cmd := exec.Command("kubectl", "get", "deployment", + "-n", "istio-system", + "istio-ingressgateway", + "--ignore-not-found") + output, err := Run(cmd) + if err != nil { + return false + } + return len(strings.TrimSpace(output)) > 0 } // InstallIstioIngressGateway installs the istio-ingressgateway func InstallIstioIngressGateway() error { - // Check if Istio is installed first - if !IsIstioInstalled() { - return fmt.Errorf("istio must be installed before installing ingress gateway") - } + // Check if Istio is installed first + if !IsIstioInstalled() { + return fmt.Errorf("istio must be installed before installing ingress gateway") + } - // Install ingress gateway using istioctl - cmd := exec.Command("istioctl", "install", - "--set", "components.ingressGateways[0].enabled=true", - "--set", "components.ingressGateways[0].name=istio-ingressgateway", - "-y") - - _, err := Run(cmd) - return err + // Install ingress gateway using istioctl + cmd := exec.Command("istioctl", "install", + "--set", "components.ingressGateways[0].enabled=true", + "--set", "components.ingressGateways[0].name=istio-ingressgateway", + "-y") + + _, err := Run(cmd) + return err } // WaitIstioIngressGatewayReady waits for istio-ingressgateway to be ready func WaitIstioIngressGatewayReady() error { - // Wait for the deployment to be available - cmd := exec.Command("kubectl", "wait", - "--for=condition=Available", - "deployment/istio-ingressgateway", - "-n", "istio-system", - "--timeout=300s") - - if _, err := Run(cmd); err != nil { - return fmt.Errorf("istio-ingressgateway deployment not available: %w", err) - } + // Wait for the deployment to be available + cmd := exec.Command("kubectl", "wait", + "--for=condition=Available", + "deployment/istio-ingressgateway", + "-n", "istio-system", + "--timeout=300s") - // Wait for the pods to be ready - cmd = exec.Command("kubectl", "wait", - "--for=condition=Ready", - "pods", - "-l", "app=istio-ingressgateway", - "-n", "istio-system", - "--timeout=300s") - - if _, err := Run(cmd); err != nil { - return fmt.Errorf("istio-ingressgateway pods not ready: %w", err) - } + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istio-ingressgateway deployment not available: %w", err) + } - // Wait for the service to have external IP (if LoadBalancer type) - cmd = exec.Command("kubectl", "get", "service", - "istio-ingressgateway", - "-n", "istio-system", - "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}") - - // Note: This might not apply for all environments (like kind/minikube) - // so we don't fail if external IP is not assigned - Run(cmd) // Ignore error for external IP check - - return nil + // Wait for the pods to be ready + cmd = exec.Command("kubectl", "wait", + "--for=condition=Ready", + "pods", + "-l", "app=istio-ingressgateway", + "-n", "istio-system", + "--timeout=300s") + + if _, err := Run(cmd); err != nil { + return fmt.Errorf("istio-ingressgateway pods not ready: %w", err) + } + + // Wait for the service to have external IP (if LoadBalancer type) + cmd = exec.Command("kubectl", "get", "service", + "istio-ingressgateway", + "-n", "istio-system", + "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}") + + // Note: This might not apply for all environments (like kind/minikube) + // so we don't fail if external IP is not assigned + Run(cmd) // Ignore error for external IP check + + return nil } // EnsureIstioIngressGateway checks, installs, and waits for istio-ingressgateway func EnsureIstioIngressGateway() error { - // Check if already installed - if IsIstioIngressGatewayInstalled() { - fmt.Println("Istio ingress gateway is already installed") - } else { - fmt.Println("Installing Istio ingress gateway...") - if err := InstallIstioIngressGateway(); err != nil { - return fmt.Errorf("failed to install istio-ingressgateway: %w", err) - } - } + // Check if already installed + if IsIstioIngressGatewayInstalled() { + fmt.Println("Istio ingress gateway is already installed") + } else { + fmt.Println("Installing Istio ingress gateway...") + if err := InstallIstioIngressGateway(); err != nil { + return fmt.Errorf("failed to install istio-ingressgateway: %w", err) + } + } - // Wait for it to be ready - fmt.Println("Waiting for Istio ingress gateway to be ready...") - if err := WaitIstioIngressGatewayReady(); err != nil { - return fmt.Errorf("istio-ingressgateway failed to become ready: %w", err) - } + // Wait for it to be ready + fmt.Println("Waiting for Istio ingress gateway to be ready...") + if err := WaitIstioIngressGatewayReady(); err != nil { + return fmt.Errorf("istio-ingressgateway failed to become ready: %w", err) + } - fmt.Println("Istio ingress gateway is ready!") - return nil + fmt.Println("Istio ingress gateway is ready!") + return nil } - // UninstallPrometheusOperator uninstalls the prometheus func UninstallPrometheusOperator() { url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) @@ -555,6 +582,49 @@ func WaitCertManagerRunning() error { ) _, err = Run(cmd) return err + + // First check if cert-manager namespace exists, if not install cert-manager + // cmd := exec.Command("kubectl", "get", "namespace", "cert-manager") + // if _, err := Run(cmd); err != nil { + // // Namespace doesn't exist, install cert-manager + // fmt.Println("cert-manager namespace not found, installing cert-manager...") + // if err := InstallCertManager(); err != nil { + // return fmt.Errorf("failed to install cert-manager: %w", err) + // } + // } + + // // Wait for the cert-manager namespace to be ready + // cmd = exec.Command("kubectl", "wait", "--for=condition=Ready", "namespace/cert-manager", "--timeout=300s") + // if _, err := Run(cmd); err != nil { + // return fmt.Errorf("cert-manager namespace not ready: %w", err) + // } + + // // Wait for each CertManager deployment individually by name (most reliable) + // deployments := []string{"cert-manager", "cert-manager-cainjector", "cert-manager-webhook"} + + // for _, deployment := range deployments { + // cmd := exec.Command("kubectl", "wait", "deployment", deployment, + // "-n", "cert-manager", + // "--for", "condition=Available", + // "--timeout", "300s") + + // if _, err := Run(cmd); err != nil { + // return fmt.Errorf("deployment %s not ready: %w", deployment, err) + // } + // } + + // // Wait for the cert-manager webhook to be ready (critical for functionality) + // cmd = exec.Command("kubectl", "wait", "pods", + // "-n", "cert-manager", + // "-l", "app=webhook", + // "--for", "condition=Ready", + // "--timeout", "300s") + + // if _, err := Run(cmd); err != nil { + // return fmt.Errorf("cert-manager webhook pods not ready: %w", err) + // } + + // return nil } // IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed From 633c52afd87c6f6ec4069cf3c3c27f9c4110db2f Mon Sep 17 00:00:00 2001 From: Yash Pal Date: Sat, 13 Sep 2025 16:34:59 +0530 Subject: [PATCH 4/6] fix: Auto-install Istio in e2e tests when missing - Add InstallIstio() function with auto-detection - Modify WaitIstioAvailable() to install if istio-system namespace missing - Improve Istio/istioctl installation checks - Fix cert-manager "Stdout already set" error Resolves "istio-system namespace not found" test failures. Signed-off-by: Yash Pal --- workspaces/controller/Makefile | 17 +- .../controller/test/e2e/e2e_suite_test.go | 2 +- workspaces/controller/test/utils/utils.go | 148 +++++++++++++----- 3 files changed, 107 insertions(+), 60 deletions(-) diff --git a/workspaces/controller/Makefile b/workspaces/controller/Makefile index 4f0cc1f1..9fc007fc 100644 --- a/workspaces/controller/Makefile +++ b/workspaces/controller/Makefile @@ -78,22 +78,7 @@ test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ exit 1; \ } - @echo "Installing CertManager..." - @if [ "$(CERT_MANAGER_INSTALL_SKIP)" != "true" ]; then \ - kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml; \ - echo "Waiting for CertManager deployments to be ready..."; \ - kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager -n cert-manager; \ - kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager-cainjector -n cert-manager; \ - kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager-webhook -n cert-manager; \ - echo "CertManager is ready!"; \ - fi - @echo "Installing Istio..." - @if [ "$(ISTIO_INSTALL_SKIP)" != "true" ]; then \ - echo "Istio installation will be handled by the test suite"; \ - fi - @if [ "$(PROMETHEUS_INSTALL_SKIP)" != "true" ]; then \ - echo "Installing Prometheus (if needed)..."; \ - fi + go test ./test/e2e/ -v -ginkgo.v .PHONY: lint diff --git a/workspaces/controller/test/e2e/e2e_suite_test.go b/workspaces/controller/test/e2e/e2e_suite_test.go index 59860cd0..ca8016f9 100644 --- a/workspaces/controller/test/e2e/e2e_suite_test.go +++ b/workspaces/controller/test/e2e/e2e_suite_test.go @@ -94,7 +94,7 @@ var _ = BeforeSuite(func() { if !skipIstioctlInstall { By("checking if istioctl is installed already") - isIstioctlAlreadyInstalled = utils.IsIstioInstalled() + isIstioctlAlreadyInstalled = utils.IsIstioctlInstalled() if !isIstioctlAlreadyInstalled { _, _ = fmt.Fprintf(GinkgoWriter, "Installing istioctl...\n") Expect(utils.InstallIstioctl()).To(Succeed(), "Failed to install istioctl") diff --git a/workspaces/controller/test/utils/utils.go b/workspaces/controller/test/utils/utils.go index 3b50d3a3..d2a72d57 100644 --- a/workspaces/controller/test/utils/utils.go +++ b/workspaces/controller/test/utils/utils.go @@ -308,9 +308,74 @@ func InstallIstioMinimalWithIngress(namespace string) error { return cmd.Run() } -// TODO: +// InstallIstio installs Istio with default configuration. +func InstallIstio() error { + // First ensure istioctl is available + if !IsIstioctlInstalled() { + fmt.Println("istioctl not found, installing istioctl...") + if err := InstallIstioctl(); err != nil { + return fmt.Errorf("failed to install istioctl: %w", err) + } + + // Verify installation + if !IsIstioctlInstalled() { + return fmt.Errorf("istioctl installation failed - binary not available after installation") + } + } + + // Check if Istio is already installed + if IsIstioInstalled() { + fmt.Println("Istio is already installed") + return nil + } + + // Install Istio with default configuration + fmt.Println("Installing Istio...") + cmd := exec.Command("istioctl", "install", + "--set", "values.defaultRevision=default", + "-y") + if _, err := Run(cmd); err != nil { + return fmt.Errorf("failed to install Istio: %w", err) + } + + fmt.Println("Istio installation completed") + return nil +} + +// IsIstioctlInstalled checks if istioctl binary is available and working +func IsIstioctlInstalled() bool { + // Check if istioctl binary exists in PATH + if _, err := exec.LookPath("istioctl"); err != nil { + return false + } + + // Verify istioctl can run and show version + cmd := exec.Command("istioctl", "version", "--short", "--remote=false") + _, err := Run(cmd) + return err == nil +} + +// IsIstioInstalled checks if Istio is installed in the cluster func IsIstioInstalled() bool { - cmd := exec.Command("istioctl", "version") + // Check if istioctl binary is available first + if !IsIstioctlInstalled() { + return false + } + + // Check if istio-system namespace exists + cmd := exec.Command("kubectl", "get", "namespace", "istio-system") + if _, err := Run(cmd); err != nil { + return false + } + + // Check if istiod deployment exists and is available + cmd = exec.Command("kubectl", "get", "deployment", "istiod", "-n", "istio-system") + if _, err := Run(cmd); err != nil { + return false + } + + // Verify istioctl can communicate with the cluster + cmd = exec.Command("istioctl", "version", "--short") _, err := Run(cmd) return err == nil } @@ -318,10 +383,14 @@ func IsIstioInstalled() bool { // WaitIstioAvailable waits for Istio to be available and running. // Returns nil if Istio is ready, or an error if not ready within timeout. func WaitIstioAvailable() error { - // First check if istio-system namespace exists + // First check if istio-system namespace exists, if not install Istio cmd := exec.Command("kubectl", "get", "namespace", "istio-system") if _, err := Run(cmd); err != nil { - return fmt.Errorf("istio-system namespace not found: %w", err) + // Namespace doesn't exist, install Istio + fmt.Println("istio-system namespace not found, installing Istio...") + if err := InstallIstio(); err != nil { + return fmt.Errorf("failed to install Istio: %w", err) + } } // Wait for Istio control plane (istiod) pods to be ready @@ -580,51 +649,44 @@ func WaitCertManagerRunning() error { "--all-namespaces", "--timeout", "2m", ) - _, err = Run(cmd) - return err - + if _, err := Run(cmd); err != nil { + return fmt.Errorf("cert-manager endpoints not ready: %w", err) + } // First check if cert-manager namespace exists, if not install cert-manager - // cmd := exec.Command("kubectl", "get", "namespace", "cert-manager") - // if _, err := Run(cmd); err != nil { - // // Namespace doesn't exist, install cert-manager - // fmt.Println("cert-manager namespace not found, installing cert-manager...") - // if err := InstallCertManager(); err != nil { - // return fmt.Errorf("failed to install cert-manager: %w", err) - // } - // } + cmd = exec.Command("kubectl", "get", "namespace", "cert-manager") + if _, err := Run(cmd); err != nil { + // Namespace doesn't exist, install cert-manager + fmt.Println("cert-manager namespace not found, installing cert-manager...") + if err := InstallCertManager(); err != nil { + return fmt.Errorf("failed to install cert-manager: %w", err) + } + } - // // Wait for the cert-manager namespace to be ready - // cmd = exec.Command("kubectl", "wait", "--for=condition=Ready", "namespace/cert-manager", "--timeout=300s") - // if _, err := Run(cmd); err != nil { - // return fmt.Errorf("cert-manager namespace not ready: %w", err) - // } + // Wait for each CertManager deployment individually by name (most reliable) + deployments := []string{"cert-manager", "cert-manager-cainjector", "cert-manager-webhook"} - // // Wait for each CertManager deployment individually by name (most reliable) - // deployments := []string{"cert-manager", "cert-manager-cainjector", "cert-manager-webhook"} + for _, deployment := range deployments { + cmd := exec.Command("kubectl", "wait", "deployment", deployment, + "-n", "cert-manager", + "--for", "condition=Available", + "--timeout", "300s") - // for _, deployment := range deployments { - // cmd := exec.Command("kubectl", "wait", "deployment", deployment, - // "-n", "cert-manager", - // "--for", "condition=Available", - // "--timeout", "300s") + if _, err := Run(cmd); err != nil { + return fmt.Errorf("deployment %s not ready: %w", deployment, err) + } + } - // if _, err := Run(cmd); err != nil { - // return fmt.Errorf("deployment %s not ready: %w", deployment, err) - // } - // } + // Wait for the cert-manager webhook to be ready (critical for functionality) + cmd = exec.Command("kubectl", "wait", "pods", + "-n", "cert-manager", + "-l", "app=webhook", + "--for", "condition=Ready", + "--timeout", "300s") - // // Wait for the cert-manager webhook to be ready (critical for functionality) - // cmd = exec.Command("kubectl", "wait", "pods", - // "-n", "cert-manager", - // "-l", "app=webhook", - // "--for", "condition=Ready", - // "--timeout", "300s") - - // if _, err := Run(cmd); err != nil { - // return fmt.Errorf("cert-manager webhook pods not ready: %w", err) - // } - - // return nil + if _, err := Run(cmd); err != nil { + return fmt.Errorf("cert-manager webhook pods not ready: %w", err) + } + return err } // IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed From 3fef467db90628e6f748287e1cff3119725aac26 Mon Sep 17 00:00:00 2001 From: Yash Pal Date: Sat, 13 Sep 2025 16:53:43 +0530 Subject: [PATCH 5/6] add: IstioURLTemplate Signed-off-by: Yash Pal --- workspaces/controller/test/utils/utils.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/controller/test/utils/utils.go b/workspaces/controller/test/utils/utils.go index d2a72d57..960918c7 100644 --- a/workspaces/controller/test/utils/utils.go +++ b/workspaces/controller/test/utils/utils.go @@ -29,8 +29,8 @@ import ( const ( // use LTS version of istioctl - istioctlVersion = "1.27.0" - istioctlURL = "" + istioctlVersion = "1.27.0" + istioctlURLTemplate = "https://github.com/istio/istio/releases/download/%s/%s" // use LTS version of prometheus-operator prometheusOperatorVersion = "v0.72.0" @@ -141,7 +141,7 @@ func InstallIstioctl() error { // Construct the download URL dynamically fileName := fmt.Sprintf("istioctl-%s-%s-%s.%s", istioctlVersion, osName, archName, fileExt) - url := fmt.Sprintf("https://github.com/istio/istio/releases/download/%s/%s", istioctlVersion, fileName) + url := fmt.Sprintf(istioctlURLTemplate, istioctlVersion, fileName) // Set the binary name based on OS binaryName := "istioctl" From a7ded8c2d8d0a75bad96ac74fb75ce8d748668c5 Mon Sep 17 00:00:00 2001 From: Yash Pal Date: Tue, 16 Sep 2025 20:39:22 +0530 Subject: [PATCH 6/6] remove windows compatibility Signed-off-by: Yash Pal --- workspaces/controller/test/utils/utils.go | 139 ++++++---------------- 1 file changed, 39 insertions(+), 100 deletions(-) diff --git a/workspaces/controller/test/utils/utils.go b/workspaces/controller/test/utils/utils.go index 960918c7..baaf73a1 100644 --- a/workspaces/controller/test/utils/utils.go +++ b/workspaces/controller/test/utils/utils.go @@ -82,22 +82,13 @@ func UninstallIstioctl() { } } - // Remove istioctl binary from system - osName := runtime.GOOS - var rmCmd *exec.Cmd - - if osName == "windows" { - // Windows: Remove from C:\istioctl - rmCmd = exec.Command("del", "/f", "/q", "C:\\istioctl\\istioctl.exe") - } else { - // Unix-like: Remove from local bin directory - homeDir, err := os.UserHomeDir() - if err != nil { - warnError(fmt.Errorf("failed to get user home directory: %w", err)) - return - } - rmCmd = exec.Command("rm", "-f", homeDir+"/.local/bin/istioctl") + // Remove istioctl binary from local bin directory + homeDir, err := os.UserHomeDir() + if err != nil { + warnError(fmt.Errorf("failed to get user home directory: %w", err)) + return } + rmCmd := exec.Command("rm", "-f", homeDir+"/.local/bin/istioctl") if _, err := Run(rmCmd); err != nil { warnError(fmt.Errorf("failed to remove istioctl binary: %w", err)) @@ -123,21 +114,16 @@ func InstallIstioctl() error { return fmt.Errorf("unsupported architecture: %s", archName) } - // Map Go OS names to Istio release names and determine file extension - var fileExt string + // Map Go OS names to Istio release names switch osName { case "linux": osName = "linux" - fileExt = "tar.gz" case "darwin": osName = "osx" - fileExt = "tar.gz" - case "windows": - osName = "win" - fileExt = "zip" default: - return fmt.Errorf("unsupported operating system: %s", osName) + return fmt.Errorf("only Linux and macOS are supported, got: %s", osName) } + fileExt := "tar.gz" // Construct the download URL dynamically fileName := fmt.Sprintf("istioctl-%s-%s-%s.%s", istioctlVersion, osName, archName, fileExt) @@ -145,47 +131,26 @@ func InstallIstioctl() error { // Set the binary name based on OS binaryName := "istioctl" - if osName == "win" { - binaryName = "istioctl.exe" - } - // Download the file using platform-appropriate method with fallbacks + // Download the file using curl with wget as fallback downloadSuccess := false - // Try primary download method - var primaryCmd *exec.Cmd - var fallbackCmd *exec.Cmd - - if osName == "win" { - // Windows: PowerShell first, curl as fallback - // Use proper PowerShell syntax with double quotes and escape handling - primaryCmd = exec.Command("powershell", "-ExecutionPolicy", "Bypass", "-Command", - fmt.Sprintf(`Invoke-WebRequest -Uri "%s" -OutFile "%s"`, url, fileName)) - fallbackCmd = exec.Command("curl", "-L", url, "-o", fileName) - } else { - // Unix-like: curl first, wget as fallback - primaryCmd = exec.Command("curl", "-L", url, "-o", fileName) - fallbackCmd = exec.Command("wget", "-O", fileName, url) - } - - // Try primary method - primaryCmd.Stdout, primaryCmd.Stderr = os.Stdout, os.Stderr - if err := primaryCmd.Run(); err == nil { + // Try curl first + curlCmd := exec.Command("curl", "-L", url, "-o", fileName) + curlCmd.Stdout, curlCmd.Stderr = os.Stdout, os.Stderr + if err := curlCmd.Run(); err == nil { downloadSuccess = true } else { - // Try fallback method - fallbackCmd.Stdout, fallbackCmd.Stderr = os.Stdout, os.Stderr - if err := fallbackCmd.Run(); err == nil { + // Try wget as fallback + wgetCmd := exec.Command("wget", "-O", fileName, url) + wgetCmd.Stdout, wgetCmd.Stderr = os.Stdout, os.Stderr + if err := wgetCmd.Run(); err == nil { downloadSuccess = true } } if !downloadSuccess { - if osName == "win" { - return fmt.Errorf("failed to download istioctl from %s using both PowerShell and curl", url) - } else { - return fmt.Errorf("failed to download istioctl from %s using both curl and wget", url) - } + return fmt.Errorf("failed to download istioctl from %s using both curl and wget", url) } // Extract based on file type @@ -226,51 +191,30 @@ func InstallIstioctl() error { normalizedPath := strings.TrimPrefix(binaryPath, "./") if normalizedPath != binaryName { var cpCmd *exec.Cmd - if osName == "win" { - cpCmd = exec.Command("copy", binaryPath, binaryName) - } else { - cpCmd = exec.Command("cp", binaryPath, binaryName) - } + cpCmd = exec.Command("cp", binaryPath, binaryName) + if err := cpCmd.Run(); err != nil { return fmt.Errorf("failed to copy istioctl binary: %w", err) } } + chmodCmd := exec.Command("chmod", "+x", binaryName) + if err := chmodCmd.Run(); err != nil { + return fmt.Errorf("failed to make istioctl executable: %w", err) + } + // Move to local bin directory + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get user home directory: %w", err) + } + binDir := homeDir + "/.local/bin" - // Make executable (not needed on Windows) - if osName != "win" { - chmodCmd := exec.Command("chmod", "+x", binaryName) - if err := chmodCmd.Run(); err != nil { - return fmt.Errorf("failed to make istioctl executable: %w", err) - } + // Create the bin directory if it doesn't exist + mkdirCmd := exec.Command("mkdir", "-p", binDir) + if err := mkdirCmd.Run(); err != nil { + return fmt.Errorf("failed to create local bin directory: %w", err) } - // Move to appropriate bin directory - // Use local bin directory to avoid sudo requirements - var binDir string - var moveCmd *exec.Cmd - - if osName == "win" { - // Use a local bin directory on Windows - binDir = "C:\\istioctl" - mkdirCmd := exec.Command("mkdir", "-p", binDir) - mkdirCmd.Run() // Ignore errors if directory exists - moveCmd = exec.Command("move", binaryName, binDir+"\\istioctl.exe") - } else { - // Use local bin directory instead of /usr/local/bin to avoid sudo - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("failed to get user home directory: %w", err) - } - binDir = homeDir + "/.local/bin" - - // Create the bin directory if it doesn't exist - mkdirCmd := exec.Command("mkdir", "-p", binDir) - if err := mkdirCmd.Run(); err != nil { - return fmt.Errorf("failed to create local bin directory: %w", err) - } - - moveCmd = exec.Command("mv", binaryName, binDir+"/istioctl") - } + moveCmd := exec.Command("mv", binaryName, binDir+"/istioctl") moveCmd.Stdout, moveCmd.Stderr = os.Stdout, os.Stderr if err := moveCmd.Run(); err != nil { @@ -278,17 +222,12 @@ func InstallIstioctl() error { } // Add to PATH notice - if osName != "win" { - fmt.Printf("istioctl installed to %s\n", binDir) - fmt.Printf("Make sure %s is in your PATH by adding this to your shell profile:\n", binDir) - fmt.Printf("export PATH=\"%s:$PATH\"\n", binDir) - } + fmt.Printf("istioctl installed to %s\n", binDir) + fmt.Printf("Make sure %s is in your PATH by adding this to your shell profile:\n", binDir) + fmt.Printf("export PATH=\"%s:$PATH\"\n", binDir) // Clean up downloaded files cleanupCmd := exec.Command("rm", "-f", fileName) - if osName == "win" { - cleanupCmd = exec.Command("del", "/f", fileName) - } cleanupCmd.Run() // Ignore cleanup errors return nil