// ABOUTME: CLI command for deleting EdgeConnect applications from YAML configuration // ABOUTME: Removes applications and their instances based on configuration file specification package cmd import ( "context" "fmt" "log" "os" "path/filepath" "strings" "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" deletev1 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/delete/v1" deletev2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/delete/v2" "github.com/spf13/cobra" ) var ( deleteConfigFile string deleteDryRun bool deleteAutoApprove bool ) var deleteCmd = &cobra.Command{ Use: "delete", Short: "Delete EdgeConnect applications from configuration files", Long: `Delete EdgeConnect applications and their instances based on YAML configuration files. This command reads a configuration file, finds matching resources, and deletes them. Instances are always deleted before the application.`, Run: func(cmd *cobra.Command, args []string) { if deleteConfigFile == "" { fmt.Fprintf(os.Stderr, "Error: configuration file is required\n") _ = cmd.Usage() os.Exit(1) } if err := runDelete(deleteConfigFile, deleteDryRun, deleteAutoApprove); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } }, } func runDelete(configPath string, isDryRun bool, autoApprove bool) error { // Step 1: Validate and resolve config file path absPath, err := filepath.Abs(configPath) if err != nil { return fmt.Errorf("failed to resolve config file path: %w", err) } if _, err := os.Stat(absPath); os.IsNotExist(err) { return fmt.Errorf("configuration file not found: %s", absPath) } fmt.Printf("šŸ“„ Loading configuration from: %s\n", absPath) // Step 2: Parse and validate configuration parser := config.NewParser() cfg, _, err := parser.ParseFile(absPath) if err != nil { return fmt.Errorf("failed to parse configuration: %w", err) } if err := parser.Validate(cfg); err != nil { return fmt.Errorf("configuration validation failed: %w", err) } fmt.Printf("āœ… Configuration loaded successfully: %s\n", cfg.Metadata.Name) // Step 3: Determine API version and create appropriate client apiVersion := getAPIVersion() // Step 4: Execute deletion based on API version if apiVersion == "v1" { return runDeleteV1(cfg, isDryRun, autoApprove) } return runDeleteV2(cfg, isDryRun, autoApprove) } func runDeleteV1(cfg *config.EdgeConnectConfig, isDryRun bool, autoApprove bool) error { // Create v1 client client := newSDKClientV1() // Create deletion planner planner := deletev1.NewPlanner(client) // Generate deletion plan fmt.Println("šŸ” Analyzing current state and generating deletion plan...") planOptions := deletev1.DefaultPlanOptions() planOptions.DryRun = isDryRun result, err := planner.PlanWithOptions(context.Background(), cfg, planOptions) if err != nil { return fmt.Errorf("failed to generate deletion plan: %w", err) } // Display plan summary fmt.Println("\nšŸ“‹ Deletion Plan:") fmt.Println(strings.Repeat("=", 50)) fmt.Println(result.Plan.Summary) fmt.Println(strings.Repeat("=", 50)) // Display warnings if any if len(result.Warnings) > 0 { fmt.Println("\nāš ļø Warnings:") for _, warning := range result.Warnings { fmt.Printf(" • %s\n", warning) } } // If dry-run, stop here if isDryRun { fmt.Println("\nšŸ” Dry-run complete. No changes were made.") return nil } // Check if there's anything to delete if result.Plan.TotalActions == 0 { fmt.Println("\nāœ… No resources found to delete.") return nil } fmt.Printf("\nThis will delete %d resource(s). Estimated time: %v\n", result.Plan.TotalActions, result.Plan.EstimatedDuration) if !autoApprove && !confirmDeletion() { fmt.Println("Deletion cancelled.") return nil } // Execute deletion fmt.Println("\nšŸ—‘ļø Starting deletion...") manager := deletev1.NewResourceManager(client, deletev1.WithLogger(log.Default())) deleteResult, err := manager.ExecuteDeletion(context.Background(), result.Plan) if err != nil { return fmt.Errorf("deletion failed: %w", err) } // Display results return displayDeletionResults(deleteResult) } func runDeleteV2(cfg *config.EdgeConnectConfig, isDryRun bool, autoApprove bool) error { // Create v2 client client := newSDKClientV2() // Create deletion planner planner := deletev2.NewPlanner(client) // Generate deletion plan fmt.Println("šŸ” Analyzing current state and generating deletion plan...") planOptions := deletev2.DefaultPlanOptions() planOptions.DryRun = isDryRun result, err := planner.PlanWithOptions(context.Background(), cfg, planOptions) if err != nil { return fmt.Errorf("failed to generate deletion plan: %w", err) } // Display plan summary fmt.Println("\nšŸ“‹ Deletion Plan:") fmt.Println(strings.Repeat("=", 50)) fmt.Println(result.Plan.Summary) fmt.Println(strings.Repeat("=", 50)) // Display warnings if any if len(result.Warnings) > 0 { fmt.Println("\nāš ļø Warnings:") for _, warning := range result.Warnings { fmt.Printf(" • %s\n", warning) } } // If dry-run, stop here if isDryRun { fmt.Println("\nšŸ” Dry-run complete. No changes were made.") return nil } // Check if there's anything to delete if result.Plan.TotalActions == 0 { fmt.Println("\nāœ… No resources found to delete.") return nil } fmt.Printf("\nThis will delete %d resource(s). Estimated time: %v\n", result.Plan.TotalActions, result.Plan.EstimatedDuration) if !autoApprove && !confirmDeletion() { fmt.Println("Deletion cancelled.") return nil } // Execute deletion fmt.Println("\nšŸ—‘ļø Starting deletion...") manager := deletev2.NewResourceManager(client, deletev2.WithLogger(log.Default())) deleteResult, err := manager.ExecuteDeletion(context.Background(), result.Plan) if err != nil { return fmt.Errorf("deletion failed: %w", err) } // Display results return displayDeletionResults(deleteResult) } func displayDeletionResults(result interface{}) error { // Use type assertion to handle both v1 and v2 result types switch r := result.(type) { case *deletev1.DeletionResult: return displayDeletionResultsV1(r) case *deletev2.DeletionResult: return displayDeletionResultsV2(r) default: return fmt.Errorf("unknown deletion result type") } } func displayDeletionResultsV1(deleteResult *deletev1.DeletionResult) error { if deleteResult.Success { fmt.Printf("\nāœ… Deletion completed successfully in %v\n", deleteResult.Duration) if len(deleteResult.CompletedActions) > 0 { fmt.Println("\nDeleted resources:") for _, action := range deleteResult.CompletedActions { fmt.Printf(" āœ… %s %s\n", action.Type, action.Target) } } } else { fmt.Printf("\nāŒ Deletion failed after %v\n", deleteResult.Duration) if deleteResult.Error != nil { fmt.Printf("Error: %v\n", deleteResult.Error) } if len(deleteResult.FailedActions) > 0 { fmt.Println("\nFailed actions:") for _, action := range deleteResult.FailedActions { fmt.Printf(" āŒ %s %s: %v\n", action.Type, action.Target, action.Error) } } return fmt.Errorf("deletion failed with %d failed actions", len(deleteResult.FailedActions)) } return nil } func displayDeletionResultsV2(deleteResult *deletev2.DeletionResult) error { if deleteResult.Success { fmt.Printf("\nāœ… Deletion completed successfully in %v\n", deleteResult.Duration) if len(deleteResult.CompletedActions) > 0 { fmt.Println("\nDeleted resources:") for _, action := range deleteResult.CompletedActions { fmt.Printf(" āœ… %s %s\n", action.Type, action.Target) } } } else { fmt.Printf("\nāŒ Deletion failed after %v\n", deleteResult.Duration) if deleteResult.Error != nil { fmt.Printf("Error: %v\n", deleteResult.Error) } if len(deleteResult.FailedActions) > 0 { fmt.Println("\nFailed actions:") for _, action := range deleteResult.FailedActions { fmt.Printf(" āŒ %s %s: %v\n", action.Type, action.Target, action.Error) } } return fmt.Errorf("deletion failed with %d failed actions", len(deleteResult.FailedActions)) } return nil } func confirmDeletion() bool { fmt.Print("Do you want to proceed with deletion? (yes/no): ") var response string _, _ = fmt.Scanln(&response) switch response { case "yes", "y", "YES", "Y": return true default: return false } } func init() { rootCmd.AddCommand(deleteCmd) deleteCmd.Flags().StringVarP(&deleteConfigFile, "file", "f", "", "configuration file path (required)") deleteCmd.Flags().BoolVar(&deleteDryRun, "dry-run", false, "preview deletion without actually deleting resources") deleteCmd.Flags().BoolVar(&deleteAutoApprove, "auto-approve", false, "automatically approve the deletion plan") if err := deleteCmd.MarkFlagRequired("file"); err != nil { panic(err) } }