// ABOUTME: Resource management for EdgeConnect apply command with deployment execution and rollback // ABOUTME: Handles actual deployment operations, manifest processing, and error recovery with parallel execution package v1 import ( "context" "fmt" "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" ) // ResourceManagerInterface defines the interface for resource management type ResourceManagerInterface interface { // ApplyDeployment executes a deployment plan ApplyDeployment(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, manifestContent string) (*ExecutionResult, error) // RollbackDeployment attempts to rollback a failed deployment RollbackDeployment(ctx context.Context, result *ExecutionResult) error // ValidatePrerequisites checks if deployment prerequisites are met ValidatePrerequisites(ctx context.Context, plan *DeploymentPlan) error } // EdgeConnectResourceManager implements resource management for EdgeConnect type EdgeConnectResourceManager struct { client EdgeConnectClientInterface parallelLimit int rollbackOnFail bool logger Logger strategyConfig StrategyConfig } // Logger interface for deployment logging type Logger interface { Printf(format string, v ...interface{}) } // ResourceManagerOptions configures the resource manager behavior type ResourceManagerOptions struct { // ParallelLimit controls how many operations run concurrently ParallelLimit int // RollbackOnFail automatically rolls back on deployment failure RollbackOnFail bool // Logger for deployment operations Logger Logger // Timeout for individual operations OperationTimeout time.Duration // StrategyConfig for deployment strategies StrategyConfig StrategyConfig } // DefaultResourceManagerOptions returns sensible defaults func DefaultResourceManagerOptions() ResourceManagerOptions { return ResourceManagerOptions{ ParallelLimit: 5, // Conservative parallel limit RollbackOnFail: true, OperationTimeout: 2 * time.Minute, StrategyConfig: DefaultStrategyConfig(), } } // NewResourceManager creates a new EdgeConnect resource manager func NewResourceManager(client EdgeConnectClientInterface, opts ...func(*ResourceManagerOptions)) ResourceManagerInterface { options := DefaultResourceManagerOptions() for _, opt := range opts { opt(&options) } return &EdgeConnectResourceManager{ client: client, parallelLimit: options.ParallelLimit, rollbackOnFail: options.RollbackOnFail, logger: options.Logger, strategyConfig: options.StrategyConfig, } } // WithParallelLimit sets the parallel execution limit func WithParallelLimit(limit int) func(*ResourceManagerOptions) { return func(opts *ResourceManagerOptions) { opts.ParallelLimit = limit } } // WithRollbackOnFail enables/disables automatic rollback func WithRollbackOnFail(rollback bool) func(*ResourceManagerOptions) { return func(opts *ResourceManagerOptions) { opts.RollbackOnFail = rollback } } // WithLogger sets a logger for deployment operations func WithLogger(logger Logger) func(*ResourceManagerOptions) { return func(opts *ResourceManagerOptions) { opts.Logger = logger } } // WithStrategyConfig sets the strategy configuration func WithStrategyConfig(config StrategyConfig) func(*ResourceManagerOptions) { return func(opts *ResourceManagerOptions) { opts.StrategyConfig = config } } // ApplyDeployment executes a deployment plan using deployment strategies func (rm *EdgeConnectResourceManager) ApplyDeployment(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, manifestContent string) (*ExecutionResult, error) { rm.logf("Starting deployment: %s", plan.ConfigName) // Step 1: Validate prerequisites if err := rm.ValidatePrerequisites(ctx, plan); err != nil { result := &ExecutionResult{ Plan: plan, CompletedActions: []ActionResult{}, FailedActions: []ActionResult{}, Error: fmt.Errorf("prerequisites validation failed: %w", err), Duration: 0, } return result, err } // Step 2: Determine deployment strategy strategyName := DeploymentStrategy(config.Spec.GetDeploymentStrategy()) rm.logf("Using deployment strategy: %s", strategyName) // Step 3: Create strategy executor strategyConfig := rm.strategyConfig strategyConfig.ParallelOperations = rm.parallelLimit > 1 factory := NewStrategyFactory(rm.client, strategyConfig, rm.logger) strategy, err := factory.CreateStrategy(strategyName) if err != nil { result := &ExecutionResult{ Plan: plan, CompletedActions: []ActionResult{}, FailedActions: []ActionResult{}, Error: fmt.Errorf("failed to create deployment strategy: %w", err), Duration: 0, } return result, err } // Step 4: Validate strategy can handle this deployment if err := strategy.Validate(plan); err != nil { result := &ExecutionResult{ Plan: plan, CompletedActions: []ActionResult{}, FailedActions: []ActionResult{}, Error: fmt.Errorf("strategy validation failed: %w", err), Duration: 0, } return result, err } // Step 5: Execute the deployment strategy rm.logf("Estimated deployment duration: %v", strategy.EstimateDuration(plan)) result, err := strategy.Execute(ctx, plan, config, manifestContent) // Step 6: Handle rollback if needed if err != nil && rm.rollbackOnFail && result != nil { rm.logf("Deployment failed, attempting rollback...") if rollbackErr := rm.RollbackDeployment(ctx, result); rollbackErr != nil { rm.logf("Rollback failed: %v", rollbackErr) } else { result.RollbackPerformed = true result.RollbackSuccess = true } } if result != nil && result.Success { rm.logf("Deployment completed successfully in %v", result.Duration) } return result, err } // ValidatePrerequisites checks if deployment prerequisites are met func (rm *EdgeConnectResourceManager) ValidatePrerequisites(ctx context.Context, plan *DeploymentPlan) error { rm.logf("Validating deployment prerequisites for: %s", plan.ConfigName) // Check if we have any actions to perform if plan.IsEmpty() { return fmt.Errorf("deployment plan is empty - no actions to perform") } // Validate that we have required client capabilities if rm.client == nil { return fmt.Errorf("EdgeConnect client is not configured") } rm.logf("Prerequisites validation passed") return nil } // RollbackDeployment attempts to rollback a failed deployment func (rm *EdgeConnectResourceManager) RollbackDeployment(ctx context.Context, result *ExecutionResult) error { rm.logf("Starting rollback for deployment: %s", result.Plan.ConfigName) rollbackErrors := []error{} // Rollback completed instances (in reverse order) for i := len(result.CompletedActions) - 1; i >= 0; i-- { action := result.CompletedActions[i] switch action.Type { case ActionCreate: if err := rm.rollbackCreateAction(ctx, action, result.Plan); err != nil { rollbackErrors = append(rollbackErrors, fmt.Errorf("failed to rollback %s: %w", action.Target, err)) } else { rm.logf("Successfully rolled back: %s", action.Target) } } } if len(rollbackErrors) > 0 { return fmt.Errorf("rollback encountered %d errors: %v", len(rollbackErrors), rollbackErrors) } rm.logf("Rollback completed successfully") return nil } // rollbackCreateAction rolls back a CREATE action by deleting the resource func (rm *EdgeConnectResourceManager) rollbackCreateAction(ctx context.Context, action ActionResult, plan *DeploymentPlan) error { if action.Type != ActionCreate { return nil } // Determine if this is an app or instance rollback based on the target name isInstance := false for _, instanceAction := range plan.InstanceActions { if instanceAction.InstanceName == action.Target { isInstance = true break } } if isInstance { return rm.rollbackInstance(ctx, action, plan) } else { return rm.rollbackApp(ctx, action, plan) } } // rollbackApp deletes an application that was created func (rm *EdgeConnectResourceManager) rollbackApp(ctx context.Context, action ActionResult, plan *DeploymentPlan) error { appKey := edgeconnect.AppKey{ Organization: plan.AppAction.Desired.Organization, Name: plan.AppAction.Desired.Name, Version: plan.AppAction.Desired.Version, } return rm.client.DeleteApp(ctx, appKey, plan.AppAction.Desired.Region) } // rollbackInstance deletes an instance that was created func (rm *EdgeConnectResourceManager) rollbackInstance(ctx context.Context, action ActionResult, plan *DeploymentPlan) error { // Find the instance action to get the details for _, instanceAction := range plan.InstanceActions { if instanceAction.InstanceName == action.Target { instanceKey := edgeconnect.AppInstanceKey{ Organization: plan.AppAction.Desired.Organization, Name: instanceAction.InstanceName, CloudletKey: edgeconnect.CloudletKey{ Organization: instanceAction.Target.CloudletOrg, Name: instanceAction.Target.CloudletName, }, } return rm.client.DeleteAppInstance(ctx, instanceKey, instanceAction.Target.Region) } } return fmt.Errorf("instance action not found for rollback: %s", action.Target) } // logf logs a message if a logger is configured func (rm *EdgeConnectResourceManager) logf(format string, v ...interface{}) { if rm.logger != nil { rm.logger.Printf("[ResourceManager] "+format, v...) } }