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 <yashpal2104@gmail.com>
This commit is contained in:
parent
95f18da965
commit
6b345ffe48
|
|
@ -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")
|
||||
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue