// ABOUTME: CLI command for declarative deployment of EdgeConnect applications from YAML configuration // ABOUTME: Integrates config parser, deployment planner, and resource manager for complete deployment workflow package cmd import ( "context" "fmt" "log" "os" "path/filepath" "strings" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/apply" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" "github.com/spf13/cobra" ) var ( configFile string dryRun bool ) var applyCmd = &cobra.Command{ Use: "apply", Short: "Deploy EdgeConnect applications from configuration files", Long: `Deploy EdgeConnect applications and their instances from YAML configuration files. This command reads a configuration file, analyzes the current state, and applies the necessary changes to deploy your applications across multiple cloudlets.`, Run: func(cmd *cobra.Command, args []string) { if configFile == "" { fmt.Fprintf(os.Stderr, "Error: configuration file is required\n") cmd.Usage() os.Exit(1) } if err := runApply(configFile, dryRun); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } }, } func runApply(configPath string, isDryRun 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, manifestContent, 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: Create EdgeConnect client client := newSDKClient() // Step 4: Create deployment planner planner := apply.NewPlanner(client) // Step 5: Generate deployment plan fmt.Println("šŸ” Analyzing current state and generating deployment plan...") planOptions := apply.DefaultPlanOptions() planOptions.DryRun = isDryRun result, err := planner.PlanWithOptions(context.Background(), cfg, planOptions) if err != nil { return fmt.Errorf("failed to generate deployment plan: %w", err) } // Step 6: Display plan summary fmt.Println("\nšŸ“‹ Deployment 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) } } // Step 7: If dry-run, stop here if isDryRun { fmt.Println("\nšŸ” Dry-run complete. No changes were made.") return nil } // Step 8: Confirm deployment (in non-dry-run mode) if result.Plan.TotalActions == 0 { fmt.Println("\nāœ… No changes needed. Resources are already in desired state.") return nil } fmt.Printf("\nThis will perform %d actions. Estimated time: %v\n", result.Plan.TotalActions, result.Plan.EstimatedDuration) if !confirmDeployment() { fmt.Println("Deployment cancelled.") return nil } // Step 9: Execute deployment fmt.Println("\nšŸš€ Starting deployment...") manager := apply.NewResourceManager(client, apply.WithLogger(log.Default())) deployResult, err := manager.ApplyDeployment(context.Background(), result.Plan, cfg, manifestContent) if err != nil { return fmt.Errorf("deployment failed: %w", err) } // Step 10: Display results if deployResult.Success { fmt.Printf("\nāœ… Deployment completed successfully in %v\n", deployResult.Duration) if len(deployResult.CompletedActions) > 0 { fmt.Println("\nCompleted actions:") for _, action := range deployResult.CompletedActions { fmt.Printf(" āœ… %s %s\n", action.Type, action.Target) } } } else { fmt.Printf("\nāŒ Deployment failed after %v\n", deployResult.Duration) if deployResult.Error != nil { fmt.Printf("Error: %v\n", deployResult.Error) } if len(deployResult.FailedActions) > 0 { fmt.Println("\nFailed actions:") for _, action := range deployResult.FailedActions { fmt.Printf(" āŒ %s %s: %v\n", action.Type, action.Target, action.Error) } } return fmt.Errorf("deployment failed with %d failed actions", len(deployResult.FailedActions)) } return nil } func confirmDeployment() bool { fmt.Print("Do you want to proceed? (yes/no): ") var response string fmt.Scanln(&response) switch response { case "yes", "y", "YES", "Y": return true default: return false } } func init() { rootCmd.AddCommand(applyCmd) applyCmd.Flags().StringVarP(&configFile, "file", "f", "", "configuration file path (required)") applyCmd.Flags().BoolVar(&dryRun, "dry-run", false, "preview changes without applying them") applyCmd.MarkFlagRequired("file") }