From b85f27964fc99b012e2d938a58ef4095b47b8029 Mon Sep 17 00:00:00 2001 From: Benedikt Iltisberger Date: Mon, 16 Oct 2023 11:58:00 +0200 Subject: [PATCH] fix: elsif typo fixed. preselection of best shell added. multiple minor fixes. --- .github/workflows/develop.yml | 4 +-- .github/workflows/main.yml | 2 -- Makefile | 6 ++++ cmd/context.go | 18 ++++++++++ cmd/system.go | 23 +++++-------- dtos/kubernetes-provider.go | 1 + dtos/punq-context.go | 5 +-- kubernetes/addResources.go | 63 +++++------------------------------ kubernetes/context.go | 33 ++++++++++++++++++ kubernetes/utils.go | 23 +++++++++---- operator/routes-websocket.go | 29 ++++++++++++---- release-dev.sh | 2 +- release.sh | 2 +- utils/format.go | 7 ++++ 14 files changed, 128 insertions(+), 90 deletions(-) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index b3c8e7d..f579358 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -4,8 +4,6 @@ on: push: branches: - develop - # repository_dispatch: - # types: [build-from-punq-frontend-dev] env: IMAGE_NAME: ghcr.io/mogenius/punq-dev @@ -43,7 +41,7 @@ jobs: - name: download and inject punq-frontend run: | - curl https://github.com/mogenius/punq-frontend/releases/download/latest/latest.tar.gz -L -o ui.tar.gz + curl https://github.com/mogenius/punq-frontend/releases/download/latest/latest-dev.tar.gz -L -o ui.tar.gz mkdir -p ui/dist tar -xzf ui.tar.gz -C ui/dist diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b5bc9d..279853f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,8 +4,6 @@ on: push: branches: - main - # repository_dispatch: - # types: [build-from-punq-frontend] env: IMAGE_NAME: ghcr.io/mogenius/punq diff --git a/Makefile b/Makefile index 843d6a8..ec59a61 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,12 @@ tarballs: all tar -czvf builds/`basename "$$file"`.tar.gz -C builds `basename "$$file"`; \ done +update-frontend: + @curl https://github.com/mogenius/punq-frontend/releases/download/latest/latest.tar.gz -L -o ui.tar.gz + @mkdir -p ui/dist + @tar -xzf ui.tar.gz -C ui/dist + @rm ui.tar.gz + clean: $(GOCLEAN) rm -f $(BINARY_NAME)-$(VERSION)-darwin-amd64 diff --git a/cmd/context.go b/cmd/context.go index 7a8445e..00ffeda 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -3,8 +3,10 @@ package cmd import ( "fmt" "os" + "strings" "github.com/mogenius/punq/dtos" + "github.com/mogenius/punq/kubernetes" "github.com/mogenius/punq/services" "github.com/mogenius/punq/structs" "github.com/mogenius/punq/utils" @@ -43,6 +45,22 @@ var addContextCmd = &cobra.Command{ if err != nil { utils.FatalError(err.Error()) } + + // Test Clusters for Reachability + for i := 0; i < len(contexts); i++ { + fmt.Printf("[%02d/%d] Testing context '%s'...", i+1, len(contexts), contexts[i].Name) + testResult, provider, err := kubernetes.CheckContext(contexts[i]) + contexts[i].Reachable = testResult + contexts[i].Provider = string(provider) + if err != nil { + elements := strings.Split(err.Error(), ":") + if len(elements) > 0 { + fmt.Printf(" (%s) ", elements[len(elements)-1]) + } + } + fmt.Printf(" %s\n", utils.StatusEmoji(testResult)) + } + dtos.ListContextsToTerminal(contexts) index := utils.SelectIndexInteractive("Select context to add", len(contexts)) diff --git a/cmd/system.go b/cmd/system.go index c5a6115..831092b 100644 --- a/cmd/system.go +++ b/cmd/system.go @@ -55,14 +55,14 @@ var checkCmd = &cobra.Command{ // check internet access inetResult, inetErr := utils.CheckInternetAccess() t.AppendRow( - table.Row{"Internet Access", StatusEmoji(inetResult), StatusMessage(inetErr, "Check your internet connection.", "Internet access works.")}, + table.Row{"Internet Access", utils.StatusEmoji(inetResult), StatusMessage(inetErr, "Check your internet connection.", "Internet access works.")}, ) t.AppendSeparator() // check for kubectl kubectlResult, kubectlOutput, kubectlErr := utils.IsKubectlInstalled() t.AppendRow( - table.Row{"kubectl", StatusEmoji(kubectlResult), StatusMessage(kubectlErr, "Plase install kubectl (https://kubernetes.io/docs/tasks/tools/) on your system to proceed.", kubectlOutput)}, + table.Row{"kubectl", utils.StatusEmoji(kubectlResult), StatusMessage(kubectlErr, "Plase install kubectl (https://kubernetes.io/docs/tasks/tools/) on your system to proceed.", kubectlOutput)}, ) t.AppendSeparator() @@ -70,35 +70,35 @@ var checkCmd = &cobra.Command{ kubernetesVersion := kubernetes.KubernetesVersion(nil) kubernetesVersionResult := kubernetesVersion != nil t.AppendRow( - table.Row{"Kubernetes Version", StatusEmoji(kubernetesVersionResult), StatusMessage(kubectlErr, "Cannot determin version of kubernetes.", fmt.Sprintf("Version: %s\nPlatform: %s", kubernetesVersion.String(), kubernetesVersion.Platform))}, + table.Row{"Kubernetes Version", utils.StatusEmoji(kubernetesVersionResult), StatusMessage(kubectlErr, "Cannot determin version of kubernetes.", fmt.Sprintf("Version: %s\nPlatform: %s", kubernetesVersion.String(), kubernetesVersion.Platform))}, ) t.AppendSeparator() // check for ingresscontroller ingressType, ingressTypeErr := kubernetes.DetermineIngressControllerType(nil) t.AppendRow( - table.Row{"Ingress Controller", StatusEmoji(ingressTypeErr == nil), StatusMessage(ingressTypeErr, "Cannot determin ingress controller type.", ingressType.String())}, + table.Row{"Ingress Controller", utils.StatusEmoji(ingressTypeErr == nil), StatusMessage(ingressTypeErr, "Cannot determin ingress controller type.", ingressType.String())}, ) t.AppendSeparator() // check for metrics server metricsResult, metricsVersion, metricsErr := kubernetes.IsMetricsServerAvailable(nil) t.AppendRow( - table.Row{"Metrics Server", StatusEmoji(metricsResult), StatusMessage(metricsErr, "kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml\nNote: Running docker-desktop? Please add '- --kubelet-insecure-tls' to the args sction in the deployment of metrics-server.", metricsVersion)}, + table.Row{"Metrics Server", utils.StatusEmoji(metricsResult), StatusMessage(metricsErr, "kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml\nNote: Running docker-desktop? Please add '- --kubelet-insecure-tls' to the args sction in the deployment of metrics-server.", metricsVersion)}, ) t.AppendSeparator() // check for helm helmResult, helmOutput, helmErr := utils.IsHelmInstalled() t.AppendRow( - table.Row{"Helm", StatusEmoji(helmResult), StatusMessage(helmErr, "Plase install helm (https://helm.sh/docs/intro/install/) on your system to proceed.", helmOutput)}, + table.Row{"Helm", utils.StatusEmoji(helmResult), StatusMessage(helmErr, "Plase install helm (https://helm.sh/docs/intro/install/) on your system to proceed.", helmOutput)}, ) t.AppendSeparator() // check cluster provider clusterProvOutput, clusterProvErr := kubernetes.GuessClusterProvider(nil) t.AppendRow( - table.Row{"Cluster Provider", StatusEmoji(clusterProvErr == nil), StatusMessage(clusterProvErr, "We could not determine the provider of this cluster.", string(clusterProvOutput))}, + table.Row{"Cluster Provider", utils.StatusEmoji(clusterProvErr == nil), StatusMessage(clusterProvErr, "We could not determine the provider of this cluster.", string(clusterProvOutput))}, ) t.AppendSeparator() @@ -110,7 +110,7 @@ var checkCmd = &cobra.Command{ } apiVersStr = strings.TrimRight(apiVersStr, "\n\r") t.AppendRow( - table.Row{"API Versions", StatusEmoji(len(apiVerResult) > 0), StatusMessage(apiVerErr, "Cannot determin API versions.", apiVersStr)}, + table.Row{"API Versions", utils.StatusEmoji(len(apiVerResult) > 0), StatusMessage(apiVerErr, "Cannot determin API versions.", apiVersStr)}, ) t.Render() }, @@ -148,13 +148,6 @@ func init() { // UTILS -func StatusEmoji(works bool) string { - if works { - return "✅" - } - return "❌" -} - func StatusMessage(err error, solution string, successMsg string) string { if err != nil { return fmt.Sprintf("Error: %s\nSolution: %s", err.Error(), solution) diff --git a/dtos/kubernetes-provider.go b/dtos/kubernetes-provider.go index 2d32842..da7c4ea 100644 --- a/dtos/kubernetes-provider.go +++ b/dtos/kubernetes-provider.go @@ -3,6 +3,7 @@ package dtos type KubernetesProvider string const ( + UNKNOWN KubernetesProvider = "UNKNOWN" BRING_YOUR_OWN KubernetesProvider = "BRING_YOUR_OWN" DOCKER_ENTERPRISE KubernetesProvider = "DOCKER_ENTERPRISE" // Docker DOCKER_DESKTOP KubernetesProvider = "DOCKER_DESKTOP" // Docker diff --git a/dtos/punq-context.go b/dtos/punq-context.go index 97af262..aa3a935 100644 --- a/dtos/punq-context.go +++ b/dtos/punq-context.go @@ -18,6 +18,7 @@ type PunqContext struct { ContextHash string `json:"contextHash" validate:"required"` Context string `json:"context" validate:"required"` Provider string `json:"provider" validate:"required"` + Reachable bool `json:"reachable" validate:"required"` Access []PunqAccess `json:"access" validate:"required"` } @@ -96,7 +97,7 @@ func (c *PunqContext) PrintToTerminal() { func ListContextsToTerminal(contexts []PunqContext) { t := table.NewWriter() t.SetOutputMirror(os.Stdout) - t.AppendHeader(table.Row{"#", "ID", "Name", "Access", "Hash"}) + t.AppendHeader(table.Row{"#", "ID", "Name", "Reachable", "Provider", "Access"}) for index, context := range contexts { accessStr := "*" accessEntries := []string{} @@ -107,7 +108,7 @@ func ListContextsToTerminal(contexts []PunqContext) { accessStr = strings.Join(accessEntries, ", ") } t.AppendRow( - table.Row{index + 1, context.Id, context.Name, accessStr, context.ContextHash}, + table.Row{index + 1, context.Id, context.Name, utils.StatusEmoji(context.Reachable), context.Provider, accessStr}, ) } t.Render() diff --git a/kubernetes/addResources.go b/kubernetes/addResources.go index 4286903..caa4682 100644 --- a/kubernetes/addResources.go +++ b/kubernetes/addResources.go @@ -225,60 +225,6 @@ func applyNamespace(provider *KubeProvider) { fmt.Println("Created punq namespace. ✅") } -// func CreateClusterSecretIfNotExist(provider *KubeProvider) (utils.ClusterSecret, error) { -// secretClient := provider.ClientSet.CoreV1().Secrets(utils.CONFIG.Kubernetes.OwnNamespace) - -// existingSecret, getErr := secretClient.Get(context.TODO(), utils.CONFIG.Kubernetes.OwnNamespace, metav1.GetOptions{}) -// return writePunqSecret(secretClient, existingSecret, getErr) -// } - -// func writePunqSecret(secretClient v1.SecretInterface, existingSecret *core.Secret, getErr error) (utils.ClusterSecret, error) { -// clusterSecret := utils.ClusterSecret{ -// ApiKey: utils.NanoIdExtraLong(), -// ClusterMfaId: utils.NanoIdExtraLong(), -// ClusterName: utils.CONFIG.Kubernetes.ClusterName, -// } - -// secret := utils.InitSecret() -// secret.ObjectMeta.Name = utils.CONFIG.Kubernetes.OwnNamespace -// secret.ObjectMeta.Namespace = utils.CONFIG.Kubernetes.OwnNamespace -// delete(secret.StringData, "exampleData") // delete example data -// secret.StringData["cluster-mfa-id"] = clusterSecret.ClusterMfaId -// secret.StringData["api-key"] = clusterSecret.ApiKey -// secret.StringData["cluster-name"] = clusterSecret.ClusterName - -// if existingSecret == nil || getErr != nil { -// logger.Log.Info("Creating new punq secret ...") -// _, err := secretClient.Create(context.TODO(), &secret, MoCreateOptions()) -// if err != nil { -// logger.Log.Error(err) -// return clusterSecret, err -// } -// fmt.Println("Created new punq secret. ✅") -// } else { -// if string(existingSecret.Data["api-key"]) != clusterSecret.ApiKey || -// string(existingSecret.Data["cluster-name"]) != clusterSecret.ClusterName { -// fmt.Println("Updating existing punq secret ...") -// // keep existing mfa-id if possible -// if string(existingSecret.Data["cluster-mfa-id"]) != "" { -// clusterSecret.ClusterMfaId = string(existingSecret.Data["cluster-mfa-id"]) -// secret.StringData["cluster-mfa-id"] = clusterSecret.ClusterMfaId -// } -// _, err := secretClient.Update(context.TODO(), &secret, MoUpdateOptions()) -// if err != nil { -// logger.Log.Error(err) -// return clusterSecret, err -// } -// fmt.Println("Updated punq secret. ✅") -// } else { -// clusterSecret.ClusterMfaId = string(existingSecret.Data["cluster-mfa-id"]) -// fmt.Println("Using existing punq secret. ✅") -// } -// } - -// return clusterSecret, nil -// } - func CreateContextSecretIfNotExist(provider *KubeProvider) (*dtos.PunqContext, error) { secretClient := provider.ClientSet.CoreV1().Secrets(utils.CONFIG.Kubernetes.OwnNamespace) @@ -299,6 +245,15 @@ func writeContextSecret(secretClient v1.SecretInterface, existingSecret *core.Se utils.FatalError(err.Error()) } + fmt.Println("Determining cluster provider ...") + ownProvider, err := GuessClusterProvider(nil) + if err == nil { + ownContext.Provider = string(ownProvider) + fmt.Printf("Determined cluster provider: '%s'. ✅\n", ownProvider) + } else { + fmt.Println("Determining cluster provider failed.") + } + ownContext.Id = utils.CONTEXTOWN ownContext.Name = utils.CONTEXTOWN diff --git a/kubernetes/context.go b/kubernetes/context.go index bd19a2b..c035ed8 100644 --- a/kubernetes/context.go +++ b/kubernetes/context.go @@ -1,12 +1,16 @@ package kubernetes import ( + "context" "errors" "fmt" "os" "github.com/mogenius/punq/dtos" "github.com/mogenius/punq/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" ) var allContexts []dtos.PunqContext = []dtos.PunqContext{} @@ -63,3 +67,32 @@ func contextWrite(id string) error { } return nil } + +func CheckContext(ctx dtos.PunqContext) (bool, dtos.KubernetesProvider, error) { + configFromString, err := clientcmd.NewClientConfigFromBytes([]byte(ctx.Context)) + if err != nil { + return false, dtos.UNKNOWN, err + } + + config, err := configFromString.ClientConfig() + if err != nil { + return false, dtos.UNKNOWN, err + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return false, dtos.UNKNOWN, err + } + + nodeList, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return false, dtos.UNKNOWN, err + } + + provider, err := GuessCluserProviderFromNodeList(nodeList) + if err != nil { + return false, dtos.UNKNOWN, err + } else { + return true, provider, nil + } +} diff --git a/kubernetes/utils.go b/kubernetes/utils.go index 5081d6a..712c28c 100644 --- a/kubernetes/utils.go +++ b/kubernetes/utils.go @@ -611,10 +611,15 @@ func GuessClusterProvider(contextId *string) (dtos.KubernetesProvider, error) { return dtos.SELF_HOSTED, err } + return GuessCluserProviderFromNodeList(nodes) +} + +func GuessCluserProviderFromNodeList(nodes *v1.NodeList) (dtos.KubernetesProvider, error) { + for _, node := range nodes.Items { labels := node.GetLabels() - if LabelsContain(labels, "eks.amazonaws.com/capacity") { + if LabelsContain(labels, "eks.amazonaws.com/") { return dtos.EKS, nil } else if LabelsContain(labels, "docker-desktop") { return dtos.DOCKER_DESKTOP, nil @@ -678,23 +683,29 @@ func GuessClusterProvider(contextId *string) (dtos.KubernetesProvider, error) { return dtos.GKE_ON_PREM, nil } else if LabelsContain(labels, "rke.cattle.io") { return dtos.RKE, nil + } else { + fmt.Println("This cluster's provider is unknown or it might be self-managed.") + return dtos.UNKNOWN, nil } } - - fmt.Println("This cluster's provider is unknown or it might be self-managed.") - return dtos.SELF_HOSTED, nil + return dtos.UNKNOWN, nil } func LabelsContain(labels map[string]string, str string) bool { - // Keys + // Keys EQUAL if _, ok := labels[strings.ToLower(str)]; ok { return true } + // Values - for _, label := range labels { + for key, label := range labels { if strings.EqualFold(label, str) { return true } + // KEY CONTAINS + if strings.Contains(key, str) { + return true + } } return false } diff --git a/operator/routes-websocket.go b/operator/routes-websocket.go index 6a6edcb..d1c5daf 100644 --- a/operator/routes-websocket.go +++ b/operator/routes-websocket.go @@ -3,16 +3,17 @@ package operator import ( "encoding/json" "fmt" - "github.com/creack/pty" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" - "github.com/mogenius/punq/dtos" - "github.com/mogenius/punq/utils" "log" "net/http" "os" "os/exec" "strings" + + "github.com/creack/pty" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/mogenius/punq/dtos" + "github.com/mogenius/punq/utils" ) type windowSize struct { @@ -66,7 +67,11 @@ func connectWs(c *gin.Context) { return } - cmd := exec.Command("sh", "-c", fmt.Sprintf("kubectl exec -it -c %s -n %s %s -- sh -c \"clear; (bash || ash || sh || ksh || csh || zsh )\"", container, namespace, podName)) + selectedShell := FindValidShell(container, namespace, podName) + fmt.Println("Selected shell: " + selectedShell) + + cmd := exec.Command("sh", "-c", fmt.Sprintf("kubectl exec -it -c %s -n %s %s -- %s -c 'echo -e \"\033[1;34mConnected to %s/%s/%s using \"$(echo $0)\". Happy hacking!\033[0m 🚀 🚀 🚀\"; %s'", container, namespace, podName, selectedShell, namespace, podName, container, selectedShell)) + fmt.Println(cmd.String()) cmd.Env = append(os.Environ(), "TERM=xterm-color") tty, err := pty.Start(cmd) @@ -123,3 +128,15 @@ func connectWs(c *gin.Context) { tty.Write(reader) } } + +func FindValidShell(container string, namespace string, podName string) string { + availableShells := []string{"bash", "ash", "zsh", "sh", "ksh", "csh"} + for _, shell := range availableShells { + cmd := exec.Command("sh", "-c", fmt.Sprintf("kubectl exec -it -c %s -n %s %s -- sh -c '%s'", container, namespace, podName, shell)) + err := cmd.Run() + if err == nil { + return shell + } + } + return "sh" +} diff --git a/release-dev.sh b/release-dev.sh index 13dfba0..51fd43f 100755 --- a/release-dev.sh +++ b/release-dev.sh @@ -37,7 +37,7 @@ class PunqDev < Formula url "https://github.com/mogenius/homebrew-punq-dev/releases/download/${VERSION}/punq-dev-${VERSION}-linux-386.tar.gz" sha256 "$SHA256_LINUX_386" end - elif Hardware::CPU.arm? + elsif Hardware::CPU.arm? if Hardware::CPU.is_64_bit? url "https://github.com/mogenius/homebrew-punq-dev/releases/download/${VERSION}/punq-dev-${VERSION}-linux-arm64.tar.gz" sha256 "$SHA256_LINUX_ARM64" diff --git a/release.sh b/release.sh index 274d59e..f8f10cf 100755 --- a/release.sh +++ b/release.sh @@ -37,7 +37,7 @@ class Punq < Formula url "https://github.com/mogenius/homebrew-punq/releases/download/${VERSION}/punq-${VERSION}-linux-386.tar.gz" sha256 "$SHA256_LINUX_386" end - elif Hardware::CPU.arm? + elsif Hardware::CPU.arm? if Hardware::CPU.is_64_bit? url "https://github.com/mogenius/homebrew-punq/releases/download/${VERSION}/punq-${VERSION}-linux-arm64.tar.gz" sha256 "$SHA256_LINUX_ARM64" diff --git a/utils/format.go b/utils/format.go index 8414d17..30c8e91 100644 --- a/utils/format.go +++ b/utils/format.go @@ -108,6 +108,13 @@ func PrintInfo(message string) { fmt.Println(yellow(message)) } +func StatusEmoji(works bool) string { + if works { + return "✅" + } + return "❌" +} + func NanoId() string { id, err := nanoid.Custom("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", 21) if err != nil {