edge-connect-client/internal/apply/v2/manager.go
Richard Robert Reitz 732e10fc16
Some checks failed
test / test (push) Failing after 48s
feat(apply): add v1 API support to apply command
Refactor apply command to support both v1 and v2 APIs:
- Split internal/apply into v1 and v2 subdirectories
- v1: Uses sdk/edgeconnect (from revision/v1 branch)
- v2: Uses sdk/edgeconnect/v2
- Update cmd/apply.go to route to appropriate version based on api_version config
- Both versions now fully functional with their respective API endpoints

Changes:
- Created internal/apply/v1/ with v1 SDK implementation
- Created internal/apply/v2/ with v2 SDK implementation
- Updated cmd/apply.go with runApplyV1() and runApplyV2() functions
- Removed validation error that rejected v1
- Apply command now respects --api-version flag and config setting

Testing:
- V1 with edge.platform:  Generates deployment plan correctly
- V2 with orca.platform:  Works as before

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 13:57:57 +02:00

286 lines
9.3 KiB
Go

// 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 v2
import (
"context"
"fmt"
"time"
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config"
v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2"
)
// 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 := v2.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 := v2.AppInstanceKey{
Organization: plan.AppAction.Desired.Organization,
Name: instanceAction.InstanceName,
CloudletKey: v2.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...)
}
}