Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
AdheipSingh committed Dec 22, 2024
1 parent c835c1d commit 1f22051
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 188 deletions.
60 changes: 2 additions & 58 deletions cmd/list.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
package cmd

import (
"context"
"fmt"
"log"
"os"

"pb/pkg/common"
"pb/pkg/installer"

"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

// ListOssCmd lists the Parseable OSS servers
Expand All @@ -24,13 +17,13 @@ var ListOssCmd = &cobra.Command{
Short: "List available Parseable OSS servers",
Example: "pb list oss",
Run: func(cmd *cobra.Command, _ []string) {
_, err := installer.PromptK8sContext()
_, err := common.PromptK8sContext()
if err != nil {
log.Fatalf("Failed to prompt for kubernetes context: %v", err)
}

// Read the installer data from the ConfigMap
entries, err := readInstallerConfigMap()
entries, err := common.ReadInstallerConfigMap()
if err != nil {
log.Fatalf("Failed to list OSS servers: %v", err)
}
Expand All @@ -52,52 +45,3 @@ var ListOssCmd = &cobra.Command{
table.Render()
},
}

// readInstallerConfigMap fetches and parses installer data from a ConfigMap
func readInstallerConfigMap() ([]installer.InstallerEntry, error) {
const (
configMapName = "parseable-installer"
namespace = "pb-system"
dataKey = "installer-data"
)

// Load kubeconfig and create a Kubernetes client
config, err := loadKubeConfig()
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig: %w", err)
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("failed to create Kubernetes client: %w", err)
}

// Get the ConfigMap
cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMapName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to fetch ConfigMap: %w", err)
}

// Retrieve and parse the installer data
rawData, ok := cm.Data[dataKey]
if !ok {
fmt.Println(common.Yellow + "\n────────────────────────────────────────────────────────────────────────────")
fmt.Println(common.Yellow + "⚠️ No Parseable clusters found!")
fmt.Println(common.Yellow + "To get started, run: `pb install oss`")
fmt.Println(common.Yellow + "────────────────────────────────────────────────────────────────────────────\n")
return nil, nil
}

var entries []installer.InstallerEntry
if err := yaml.Unmarshal([]byte(rawData), &entries); err != nil {
return nil, fmt.Errorf("failed to parse ConfigMap data: %w", err)
}

return entries, nil
}

// loadKubeConfig loads the kubeconfig from the default location
func loadKubeConfig() (*rest.Config, error) {
kubeconfig := clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
return clientcmd.BuildConfigFromFlags("", kubeconfig)
}
90 changes: 66 additions & 24 deletions cmd/uninstaller.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,82 @@
// Copyright (c) 2024 Parseable, Inc
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package cmd

import (
"fmt"
"log"

"pb/pkg/common"
"pb/pkg/installer"
"pb/pkg/helm"

"github.com/spf13/cobra"
)

var UnInstallOssCmd = &cobra.Command{
// UninstallOssCmd removes Parseable OSS servers
var UninstallOssCmd = &cobra.Command{
Use: "oss",
Short: "Uninstall Parseable OSS",
Short: "Uninstall Parseable OSS servers",
Example: "pb uninstall oss",
RunE: func(cmd *cobra.Command, _ []string) error {
// Add verbose flag
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")
Run: func(cmd *cobra.Command, _ []string) {
_, err := common.PromptK8sContext()
if err != nil {
log.Fatalf("Failed to prompt for Kubernetes context: %v", err)
}

// Read the installer data from the ConfigMap
entries, err := common.ReadInstallerConfigMap()
if err != nil {
log.Fatalf("Failed to fetch OSS servers: %v", err)
}

// Check if there are no entries
if len(entries) == 0 {
fmt.Println(common.Yellow + "\nNo Parseable OSS servers found to uninstall.\n")

Check failure on line 32 in cmd/uninstaller.go

View workflow job for this annotation

GitHub Actions / Build and Test the Go code

fmt.Println arg list ends with redundant newline
return
}

// Prompt user to select a cluster
selectedCluster, err := common.PromptClusterSelection(entries)
if err != nil {
log.Fatalf("Failed to select a cluster: %v", err)
}

if err := installer.Uninstaller(verbose); err != nil {
fmt.Println(common.Red + err.Error())
// Confirm uninstallation
fmt.Printf("\nYou have selected to uninstall the cluster '%s' in namespace '%s'.\n", selectedCluster.Name, selectedCluster.Namespace)
if !common.PromptConfirmation(fmt.Sprintf("Do you want to proceed with uninstalling '%s'?", selectedCluster.Name)) {
fmt.Println(common.Yellow + "Uninstall operation canceled.")
return
}

return nil
// Perform uninstallation
if err := uninstallCluster(selectedCluster); err != nil {
log.Fatalf("Failed to uninstall cluster: %v", err)
}

fmt.Println(common.Green + "Uninstallation completed successfully." + common.Reset)
},
}

func uninstallCluster(entry common.InstallerEntry) error {
helmApp := helm.Helm{
ReleaseName: entry.Name,
Namespace: entry.Namespace,
RepoName: "parseable",
RepoURL: "https://charts.parseable.com",
ChartName: "parseable",
Version: entry.Version,
}

fmt.Println(common.Yellow + "Starting uninstallation process..." + common.Reset)

spinner := common.CreateDeploymentSpinner(entry.Namespace, fmt.Sprintf("Uninstalling Parseable OSS '%s'...", entry.Name))
spinner.Start()

_, err := helm.Uninstall(helmApp, false)
spinner.Stop()

if err != nil {
return fmt.Errorf("failed to uninstall Parseable OSS: %v", err)
}

fmt.Printf(common.Green+"Successfully uninstalled '%s' from namespace '%s'.\n"+common.Reset, entry.Name, entry.Namespace)
return nil
}
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,10 @@ func main() {
schema.AddCommand(pb.CreateSchemaCmd)

install.AddCommand(pb.InstallOssCmd)

list.AddCommand(pb.ListOssCmd)

uninstall.AddCommand(pb.UnInstallOssCmd)
uninstall.AddCommand(pb.UninstallOssCmd)

cli.AddCommand(profile)
cli.AddCommand(query)
Expand Down
176 changes: 176 additions & 0 deletions pkg/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@

package common

import (
"context"
"fmt"
"os"
"time"

"github.com/briandowns/spinner"
"github.com/manifoldco/promptui"
"gopkg.in/yaml.v2"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

// ANSI escape codes for colors
const (
Yellow = "\033[33m"
Expand All @@ -24,3 +40,163 @@ const (
Blue = "\033[34m"
Cyan = "\033[36m"
)

// InstallerEntry represents an entry in the installer.yaml file
type InstallerEntry struct {
Name string `yaml:"name"`
Namespace string `yaml:"namespace"`
Version string `yaml:"version"`
Status string `yaml:"status"` // todo ideally should be a heartbeat
}

// ReadInstallerConfigMap fetches and parses installer data from a ConfigMap
func ReadInstallerConfigMap() ([]InstallerEntry, error) {
const (
configMapName = "parseable-installer"
namespace = "pb-system"
dataKey = "installer-data"
)

// Load kubeconfig and create a Kubernetes client
config, err := LoadKubeConfig()
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig: %w", err)
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("failed to create Kubernetes client: %w", err)
}

// Get the ConfigMap
cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMapName, metav1.GetOptions{})
if err != nil {
if apiErrors.IsNotFound(err) {
fmt.Println(Yellow + "\nNo existing Parseable OSS clusters found.\n" + Reset)
return nil, nil
}
return nil, fmt.Errorf("failed to fetch ConfigMap: %w", err)
}
// Retrieve and parse the installer data
rawData, ok := cm.Data[dataKey]
if !ok {
fmt.Println(Yellow + "\n────────────────────────────────────────────────────────────────────────────")
fmt.Println(Yellow + "⚠️ No Parseable clusters found!")
fmt.Println(Yellow + "To get started, run: `pb install oss`")
fmt.Println(Yellow + "────────────────────────────────────────────────────────────────────────────\n")

Check failure on line 86 in pkg/common/common.go

View workflow job for this annotation

GitHub Actions / Build and Test the Go code

fmt.Println arg list ends with redundant newline
return nil, nil
}

var entries []InstallerEntry
if err := yaml.Unmarshal([]byte(rawData), &entries); err != nil {
return nil, fmt.Errorf("failed to parse ConfigMap data: %w", err)
}

return entries, nil
}

// LoadKubeConfig loads the kubeconfig from the default location
func LoadKubeConfig() (*rest.Config, error) {
kubeconfig := clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
return clientcmd.BuildConfigFromFlags("", kubeconfig)
}

// PromptK8sContext retrieves Kubernetes contexts from kubeconfig.
func PromptK8sContext() (clusterName string, err error) {
kubeconfigPath := os.Getenv("KUBECONFIG")
if kubeconfigPath == "" {
kubeconfigPath = os.Getenv("HOME") + "/.kube/config"
}

// Load kubeconfig file
config, err := clientcmd.LoadFromFile(kubeconfigPath)
if err != nil {
fmt.Printf("\033[31mError loading kubeconfig: %v\033[0m\n", err)
os.Exit(1)
}

// Get current contexts
currentContext := config.Contexts
var contexts []string
for i := range currentContext {
contexts = append(contexts, i)
}

// Prompt user to select Kubernetes context
promptK8s := promptui.Select{
Items: contexts,
Templates: &promptui.SelectTemplates{
Label: "{{ `Select your Kubernetes context` | yellow }}",
Active: "▸ {{ . | yellow }} ", // Yellow arrow and context name for active selection
Inactive: " {{ . | yellow }}", // Default color for inactive items
Selected: "{{ `Selected Kubernetes context:` | green }} '{{ . | green }}' ✔",
},
}

_, clusterName, err = promptK8s.Run()
if err != nil {
return "", err
}

// Set current context as selected
config.CurrentContext = clusterName
err = clientcmd.WriteToFile(*config, kubeconfigPath)
if err != nil {
return "", err
}

return clusterName, nil
}

func PromptClusterSelection(entries []InstallerEntry) (InstallerEntry, error) {
clusterNames := make([]string, len(entries))
for i, entry := range entries {
clusterNames[i] = fmt.Sprintf("[Name: %s] [Namespace: %s] [Version: %s]", entry.Name, entry.Namespace, entry.Version)
}

prompt := promptui.Select{
Label: "Select a cluster to uninstall",
Items: clusterNames,
Templates: &promptui.SelectTemplates{
Label: "{{ `Select Cluster` | yellow }}",
Active: "▸ {{ . | yellow }}",
Inactive: " {{ . | yellow }}",
Selected: "{{ `Selected:` | green }} {{ . | green }}",
},
}

index, _, err := prompt.Run()
if err != nil {
return InstallerEntry{}, fmt.Errorf("failed to prompt for cluster selection: %v", err)
}

return entries[index], nil
}

func PromptConfirmation(message string) bool {
prompt := promptui.Prompt{
Label: message,
IsConfirm: true,
}

_, err := prompt.Run()
return err == nil
}

func CreateDeploymentSpinner(namespace, infoMsg string) *spinner.Spinner {
// Custom spinner with multiple character sets for dynamic effect
spinnerChars := []string{
"●", "○", "◉", "○", "◉", "○", "◉", "○", "◉",
}

s := spinner.New(
spinnerChars,
120*time.Millisecond,
spinner.WithColor(Yellow),
spinner.WithSuffix(" ..."),
)

s.Prefix = fmt.Sprintf(Yellow + infoMsg)

return s
}
Loading

0 comments on commit 1f22051

Please sign in to comment.