Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: uninstaller for Parseable installer #75

Merged
merged 3 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the license header


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

"github.com/spf13/cobra"
)

var UnInstallOssCmd = &cobra.Command{
Use: "oss",
Short: "Uninstall Parseable OSS",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a name of each installation so it is possible to install multiple instances and then delete the one you want to delete.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Banner


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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Banner


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.")
}

Check failure on line 45 in pkg/installer/uninstaller.go

View workflow job for this annotation

GitHub Actions / Build and Test the Go code

`cancelled` is a misspelling of `canceled` (misspell)

// 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
}
Loading