-
-
Notifications
You must be signed in to change notification settings - Fork 9
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package installer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package installer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.") | ||
} | ||
|
||
// 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 | ||
} |
There was a problem hiding this comment.
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