Skip to content

Commit

Permalink
feat: uninstaller for Parseable installer (#75)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Nitish Tiwari <[email protected]>
  • Loading branch information
AdheipSingh and nitisht authored Dec 17, 2024
1 parent f86d7ef commit ab642cb
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 0 deletions.
28 changes: 28 additions & 0 deletions cmd/uninstaller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"fmt"
"pb/pkg/common"
"pb/pkg/installer"

"github.com/spf13/cobra"
)

var UnInstallOssCmd = &cobra.Command{
Use: "oss",
Short: "Uninstall Parseable OSS",
Example: "pb uninstall oss",
RunE: func(cmd *cobra.Command, _ []string) error {
// Add verbose flag
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")

// Print the banner
printBanner()

if err := installer.Uninstaller(verbose); err != nil {
fmt.Println(common.Red + err.Error())
}

return nil
},
}
20 changes: 20 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,23 @@ var install = &cobra.Command{
},
}

var uninstall = &cobra.Command{
Use: "uninstall",
Short: "Uninstall parseable on kubernetes cluster",
Long: "\nuninstall command is used to uninstall parseable oss/enterprise on k8s cluster..",
PersistentPreRunE: combinedPreRun,
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if os.Getenv("PB_ANALYTICS") == "disable" {
return
}
wg.Add(1)
go func() {
defer wg.Done()
analytics.PostRunAnalytics(cmd, "uninstall", args)
}()
},
}

func main() {
profile.AddCommand(pb.AddProfileCmd)
profile.AddCommand(pb.RemoveProfileCmd)
Expand Down Expand Up @@ -231,6 +248,8 @@ func main() {

install.AddCommand(pb.InstallOssCmd)

uninstall.AddCommand(pb.UnInstallOssCmd)

cli.AddCommand(profile)
cli.AddCommand(query)
cli.AddCommand(stream)
Expand All @@ -240,6 +259,7 @@ func main() {

cli.AddCommand(pb.AutocompleteCmd)
cli.AddCommand(install)
cli.AddCommand(uninstall)
cli.AddCommand(schema)

// Set as command
Expand Down
45 changes: 45 additions & 0 deletions pkg/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,48 @@ func Upgrade(h Helm) error {
}
return nil
}

func Uninstall(h Helm, verbose bool) (*release.UninstallReleaseResponse, error) {

// Create a logger that does nothing by default
silentLogger := func(_ string, _ ...interface{}) {}

// Create settings
settings := cli.New()

// Create action configuration
actionConfig := new(action.Configuration)

// Choose logging method based on verbose flag
logMethod := silentLogger
if verbose {
logMethod = log.Printf
}

// Initialize action configuration with chosen logger
if err := actionConfig.Init(
settings.RESTClientGetter(),
h.Namespace,
os.Getenv("HELM_DRIVER"),
logMethod,
); err != nil {
return &release.UninstallReleaseResponse{}, fmt.Errorf("failed to initialize Helm configuration: %w", err)
}

client := action.NewUninstall(actionConfig)
// Setting Namespace
settings.SetNamespace(h.Namespace)
settings.EnvVars()

settings.EnvVars()

client.Wait = true
client.Timeout = 5 * time.Minute

resp, err := client.Run(h.ReleaseName)
if err != nil {
return &release.UninstallReleaseResponse{}, err
}

return resp, nil
}
27 changes: 27 additions & 0 deletions pkg/installer/spinner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package installer

import (
"fmt"
"pb/pkg/common"
"time"

"github.com/briandowns/spinner"
)

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(common.Yellow),
spinner.WithSuffix(" ..."),
)

s.Prefix = fmt.Sprintf(common.Yellow+infoMsg+" %s ", namespace)

return s
}
158 changes: 158 additions & 0 deletions pkg/installer/uninstaller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package installer

import (
"bufio"
"context"
"fmt"
"os"
"path/filepath"
"pb/pkg/common"
"pb/pkg/helm"
"strings"
"time"

"gopkg.in/yaml.v2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

func Uninstaller(verbose bool) error {
// Load configuration from the parseable.yaml file
configPath := filepath.Join(os.Getenv("HOME"), ".parseable", "parseable.yaml")
config, err := loadParseableConfig(configPath)
if err != nil {
return fmt.Errorf("failed to load configuration: %v", err)
}

if config == (&ValuesHolder{}) {
return fmt.Errorf("no existing configuration found in ~/.parseable/parseable.yaml")
}

// Prompt for Kubernetes context
_, err = promptK8sContext()
if err != nil {
return fmt.Errorf("failed to prompt for Kubernetes context: %v", err)
}

// Prompt user to confirm namespace
namespace := config.ParseableSecret.Namespace
confirm, err := promptUserConfirmation(fmt.Sprintf(common.Yellow+"Do you wish to uninstall Parseable from namespace '%s'?", namespace))
if err != nil {
return fmt.Errorf("failed to get user confirmation: %v", err)
}
if !confirm {
return fmt.Errorf("Uninstall cancelled.")
}

// Helm application configuration
helmApp := helm.Helm{
ReleaseName: "parseable",
Namespace: namespace,
RepoName: "parseable",
RepoURL: "https://charts.parseable.com",
ChartName: "parseable",
Version: "1.6.5",
}

// Create a spinner
spinner := createDeploymentSpinner(namespace, "Uninstalling parseable in ")

// Redirect standard output if not in verbose mode
var oldStdout *os.File
if !verbose {
oldStdout = os.Stdout
_, w, _ := os.Pipe()
os.Stdout = w
}

spinner.Start()

// Run Helm uninstall
_, err = helm.Uninstall(helmApp, verbose)
spinner.Stop()

// Restore stdout
if !verbose {
os.Stdout = oldStdout
}

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

// Namespace cleanup using Kubernetes client
fmt.Printf(common.Yellow+"Cleaning up namespace '%s'...\n"+common.Reset, namespace)
cleanupErr := cleanupNamespaceWithClient(namespace)
if cleanupErr != nil {
return fmt.Errorf("failed to clean up namespace '%s': %v", namespace, cleanupErr)
}

// Print success banner
fmt.Printf(common.Green+"Successfully uninstalled Parseable from namespace '%s'.\n"+common.Reset, namespace)

return nil

}

// promptUserConfirmation prompts the user for a yes/no confirmation
func promptUserConfirmation(message string) (bool, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("%s [y/N]: ", message)
response, err := reader.ReadString('\n')
if err != nil {
return false, err
}
response = strings.TrimSpace(strings.ToLower(response))
return response == "y" || response == "yes", nil
}

// loadParseableConfig loads the configuration from the specified file
func loadParseableConfig(path string) (*ValuesHolder, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config ValuesHolder
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}

// cleanupNamespaceWithClient deletes the specified namespace using Kubernetes client-go
func cleanupNamespaceWithClient(namespace string) error {
// Load the kubeconfig
config, err := loadKubeConfig()
if err != nil {
return fmt.Errorf("failed to load kubeconfig: %w", err)
}

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

// Create a context with a timeout for namespace deletion
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()

// Delete the namespace
err = clientset.CoreV1().Namespaces().Delete(ctx, namespace, v1.DeleteOptions{})
if err != nil {
return fmt.Errorf("error deleting namespace: %v", err)
}

// Wait for the namespace to be fully removed
fmt.Printf("Waiting for namespace '%s' to be deleted...\n", namespace)
for {
_, err := clientset.CoreV1().Namespaces().Get(ctx, namespace, v1.GetOptions{})
if err != nil {
fmt.Printf("Namespace '%s' successfully deleted.\n", namespace)
break
}
time.Sleep(2 * time.Second)
}

return nil
}

0 comments on commit ab642cb

Please sign in to comment.