package app import ( "bufio" "fmt" "os" "path/filepath" "strings" "github.com/spf13/cobra" "github.com/wild-cloud/wild-cli/internal/environment" "github.com/wild-cloud/wild-cli/internal/external" "github.com/wild-cloud/wild-cli/internal/output" ) var ( deleteForce bool deleteDryRun bool ) func newDeleteCommand() *cobra.Command { cmd := &cobra.Command{ Use: "delete ", Short: "Delete an application from the cluster", Long: `Delete a Wild Cloud app and all its resources. This will delete: - App deployment, services, and other Kubernetes resources - App secrets from the app's namespace - App namespace (if empty after resource deletion) - Local app configuration files from apps/ Examples: wild app delete nextcloud wild app delete nextcloud --force wild app delete nextcloud --dry-run`, Args: cobra.ExactArgs(1), RunE: runAppDelete, } cmd.Flags().BoolVar(&deleteForce, "force", false, "skip confirmation prompts") cmd.Flags().BoolVar(&deleteDryRun, "dry-run", false, "show what would be deleted without actually deleting") return cmd } func runAppDelete(cmd *cobra.Command, args []string) error { appName := args[0] // Initialize environment env := environment.New() if err := env.RequiresProject(); err != nil { return err } // Check if app exists appDir := filepath.Join(env.AppsDir(), appName) if _, err := os.Stat(appDir); os.IsNotExist(err) { return fmt.Errorf("app directory 'apps/%s' not found", appName) } // Initialize external tools toolManager := external.NewManager() if err := toolManager.CheckTools(cmd.Context(), []string{"kubectl"}); err != nil { return fmt.Errorf("required tools not available: %w", err) } kubectl := toolManager.Kubectl() // Confirmation prompt (unless --force or --dry-run) if !deleteForce && !deleteDryRun { output.Warning(fmt.Sprintf("This will delete all resources for app '%s'", appName)) output.Info("This includes:") output.Info(" - Kubernetes deployments, services, secrets, and other resources") output.Info(" - App namespace (if empty after deletion)") output.Info(" - Local configuration files in apps/" + appName + "/") output.Info("") reader := bufio.NewReader(os.Stdin) output.Printf("Are you sure you want to delete app '%s'? (y/N): ", appName) response, err := reader.ReadString('\n') if err != nil { return fmt.Errorf("failed to read confirmation: %w", err) } response = strings.TrimSpace(strings.ToLower(response)) if response != "y" && response != "yes" { output.Info("Deletion cancelled") return nil } } if deleteDryRun { output.Header("DRY RUN: Deleting app '" + appName + "'") } else { output.Header("Deleting app '" + appName + "'") } // Step 1: Delete namespace (this will delete ALL resources) output.Info("Deleting namespace and all remaining resources...") var kubectlArgs []string if deleteDryRun { kubectlArgs = []string{"delete", "namespace", appName, "--dry-run=client", "--ignore-not-found=true"} } else { kubectlArgs = []string{"delete", "namespace", appName, "--ignore-not-found=true"} } if _, err := kubectl.Execute(cmd.Context(), kubectlArgs...); err != nil { return fmt.Errorf("failed to delete namespace: %w", err) } // Wait for namespace deletion to complete (only if not dry-run) if !deleteDryRun { output.Info("Waiting for namespace deletion to complete...") waitArgs := []string{"wait", "--for=delete", "namespace", appName, "--timeout=60s"} // Ignore error as namespace might already be deleted _, _ = kubectl.Execute(cmd.Context(), waitArgs...) } // Step 2: Delete local app configuration files output.Info("Deleting local app configuration...") if deleteDryRun { output.Info(fmt.Sprintf("DRY RUN: Would delete directory 'apps/%s/'", appName)) } else { if err := os.RemoveAll(appDir); err != nil { return fmt.Errorf("failed to delete local configuration directory: %w", err) } output.Info(fmt.Sprintf("Deleted local configuration directory: apps/%s/", appName)) } output.Success(fmt.Sprintf("App '%s' deletion complete!", appName)) output.Info("") output.Info("Note: Dependency apps (if any) were not deleted.") output.Info("If you want to delete dependencies, run wild app delete for each dependency separately.") return nil }