// ABOUTME: Deployment planning types for EdgeConnect apply command with state management // ABOUTME: Defines structures for deployment plans, actions, and state comparison results package apply import ( "fmt" "strings" "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" ) // SecurityRule defines network access rules (alias to SDK type for consistency) type SecurityRule = edgeconnect.SecurityRule // ActionType represents the type of action to be performed type ActionType string const ( // ActionCreate indicates a resource needs to be created ActionCreate ActionType = "CREATE" // ActionUpdate indicates a resource needs to be updated ActionUpdate ActionType = "UPDATE" // ActionNone indicates no action is needed ActionNone ActionType = "NONE" // ActionDelete indicates a resource needs to be deleted (for rollback scenarios) ActionDelete ActionType = "DELETE" ) // String returns the string representation of ActionType func (a ActionType) String() string { return string(a) } // DeploymentPlan represents the complete deployment plan for a configuration type DeploymentPlan struct { // ConfigName is the name from metadata ConfigName string // AppAction defines what needs to be done with the application AppAction AppAction // InstanceActions defines what needs to be done with each instance InstanceActions []InstanceAction // Summary provides a human-readable summary of the plan Summary string // TotalActions is the count of all actions that will be performed TotalActions int // EstimatedDuration is the estimated time to complete the deployment EstimatedDuration time.Duration // CreatedAt timestamp when the plan was created CreatedAt time.Time // DryRun indicates if this is a dry-run plan DryRun bool } // AppAction represents an action to be performed on an application type AppAction struct { // Type of action to perform Type ActionType // Current state of the app (nil if doesn't exist) Current *AppState // Desired state of the app Desired *AppState // Changes describes what will change Changes []string // Reason explains why this action is needed Reason string // ManifestHash is the hash of the current manifest file ManifestHash string // ManifestChanged indicates if the manifest content has changed ManifestChanged bool } // InstanceAction represents an action to be performed on an application instance type InstanceAction struct { // Type of action to perform Type ActionType // Target infrastructure where the instance will be deployed Target config.InfraTemplate // Current state of the instance (nil if doesn't exist) Current *InstanceState // Desired state of the instance Desired *InstanceState // Changes describes what will change Changes []string // Reason explains why this action is needed Reason string // InstanceName is the generated name for this instance InstanceName string // Dependencies lists other instances this depends on Dependencies []string } // AppState represents the current state of an application type AppState struct { // Name of the application Name string // Version of the application Version string // Organization that owns the app Organization string // Region where the app is deployed Region string // ManifestHash is the stored hash of the manifest file ManifestHash string // LastUpdated timestamp when the app was last modified LastUpdated time.Time // Exists indicates if the app currently exists Exists bool // AppType indicates whether this is a k8s or docker app AppType AppType // OutboundConnections contains the required outbound network connections OutboundConnections []SecurityRule } // InstanceState represents the current state of an application instance type InstanceState struct { // Name of the instance Name string // AppName that this instance belongs to AppName string // AppVersion of the associated app AppVersion string // Organization that owns the instance Organization string // Region where the instance is deployed Region string // CloudletOrg that hosts the cloudlet CloudletOrg string // CloudletName where the instance is running CloudletName string // FlavorName used for the instance FlavorName string // State of the instance (e.g., "Ready", "Pending", "Error") State string // PowerState of the instance PowerState string // LastUpdated timestamp when the instance was last modified LastUpdated time.Time // Exists indicates if the instance currently exists Exists bool } // AppType represents the type of application type AppType string const ( // AppTypeK8s represents a Kubernetes application AppTypeK8s AppType = "k8s" // AppTypeDocker represents a Docker application AppTypeDocker AppType = "docker" ) // String returns the string representation of AppType func (a AppType) String() string { return string(a) } // DeploymentSummary provides a high-level overview of the deployment plan type DeploymentSummary struct { // TotalActions is the total number of actions to be performed TotalActions int // ActionCounts breaks down actions by type ActionCounts map[ActionType]int // EstimatedDuration for the entire deployment EstimatedDuration time.Duration // ResourceSummary describes the resources involved ResourceSummary ResourceSummary // Warnings about potential issues Warnings []string } // ResourceSummary provides details about resources in the deployment type ResourceSummary struct { // AppsToCreate number of apps that will be created AppsToCreate int // AppsToUpdate number of apps that will be updated AppsToUpdate int // InstancesToCreate number of instances that will be created InstancesToCreate int // InstancesToUpdate number of instances that will be updated InstancesToUpdate int // CloudletsAffected number of unique cloudlets involved CloudletsAffected int // RegionsAffected number of unique regions involved RegionsAffected int } // PlanResult represents the result of a deployment planning operation type PlanResult struct { // Plan is the generated deployment plan Plan *DeploymentPlan // Error if planning failed Error error // Warnings encountered during planning Warnings []string } // ExecutionResult represents the result of executing a deployment plan type ExecutionResult struct { // Plan that was executed Plan *DeploymentPlan // Success indicates if the deployment was successful Success bool // CompletedActions lists actions that were successfully completed CompletedActions []ActionResult // FailedActions lists actions that failed FailedActions []ActionResult // Error that caused the deployment to fail (if any) Error error // Duration taken to execute the plan Duration time.Duration // RollbackPerformed indicates if rollback was executed RollbackPerformed bool // RollbackSuccess indicates if rollback was successful RollbackSuccess bool } // ActionResult represents the result of executing a single action type ActionResult struct { // Type of action that was attempted Type ActionType // Target describes what was being acted upon Target string // Success indicates if the action succeeded Success bool // Error if the action failed Error error // Duration taken to complete the action Duration time.Duration // Details provides additional information about the action Details string } // IsEmpty returns true if the deployment plan has no actions to perform func (dp *DeploymentPlan) IsEmpty() bool { if dp.AppAction.Type != ActionNone { return false } for _, action := range dp.InstanceActions { if action.Type != ActionNone { return false } } return true } // HasErrors returns true if the plan contains any error conditions func (dp *DeploymentPlan) HasErrors() bool { // Check for conflicting actions or invalid states return false // Implementation would check for various error conditions } // GetTargetCloudlets returns a list of unique cloudlets that will be affected func (dp *DeploymentPlan) GetTargetCloudlets() []string { cloudletSet := make(map[string]bool) var cloudlets []string for _, action := range dp.InstanceActions { if action.Type != ActionNone { key := fmt.Sprintf("%s:%s", action.Target.CloudletOrg, action.Target.CloudletName) if !cloudletSet[key] { cloudletSet[key] = true cloudlets = append(cloudlets, key) } } } return cloudlets } // GetTargetRegions returns a list of unique regions that will be affected func (dp *DeploymentPlan) GetTargetRegions() []string { regionSet := make(map[string]bool) var regions []string for _, action := range dp.InstanceActions { if action.Type != ActionNone && !regionSet[action.Target.Region] { regionSet[action.Target.Region] = true regions = append(regions, action.Target.Region) } } return regions } // GenerateSummary creates a human-readable summary of the deployment plan func (dp *DeploymentPlan) GenerateSummary() string { if dp.IsEmpty() { return "No changes required - configuration matches current state" } var sb strings.Builder sb.WriteString(fmt.Sprintf("Deployment plan for '%s':\n", dp.ConfigName)) // App actions if dp.AppAction.Type != ActionNone { sb.WriteString(fmt.Sprintf("- %s application '%s'\n", dp.AppAction.Type, dp.AppAction.Desired.Name)) if len(dp.AppAction.Changes) > 0 { for _, change := range dp.AppAction.Changes { sb.WriteString(fmt.Sprintf(" - %s\n", change)) } } } // Instance actions createCount := 0 updateActions := []InstanceAction{} for _, action := range dp.InstanceActions { switch action.Type { case ActionCreate: createCount++ case ActionUpdate: updateActions = append(updateActions, action) } } if createCount > 0 { sb.WriteString(fmt.Sprintf("- CREATE %d instance(s) across %d cloudlet(s)\n", createCount, len(dp.GetTargetCloudlets()))) } if len(updateActions) > 0 { sb.WriteString(fmt.Sprintf("- UPDATE %d instance(s)\n", len(updateActions))) for _, action := range updateActions { if len(action.Changes) > 0 { sb.WriteString(fmt.Sprintf(" - Instance '%s':\n", action.InstanceName)) for _, change := range action.Changes { sb.WriteString(fmt.Sprintf(" - %s\n", change)) } } } } sb.WriteString(fmt.Sprintf("Estimated duration: %s", dp.EstimatedDuration.String())) return sb.String() } // Validate checks if the deployment plan is valid and safe to execute func (dp *DeploymentPlan) Validate() error { if dp.ConfigName == "" { return fmt.Errorf("deployment plan must have a config name") } // Validate app action if dp.AppAction.Type != ActionNone && dp.AppAction.Desired == nil { return fmt.Errorf("app action of type %s must have desired state", dp.AppAction.Type) } // Validate instance actions for i, action := range dp.InstanceActions { if action.Type != ActionNone { if action.Desired == nil { return fmt.Errorf("instance action %d of type %s must have desired state", i, action.Type) } if action.InstanceName == "" { return fmt.Errorf("instance action %d must have an instance name", i) } } } return nil } // Clone creates a deep copy of the deployment plan func (dp *DeploymentPlan) Clone() *DeploymentPlan { clone := &DeploymentPlan{ ConfigName: dp.ConfigName, Summary: dp.Summary, TotalActions: dp.TotalActions, EstimatedDuration: dp.EstimatedDuration, CreatedAt: dp.CreatedAt, DryRun: dp.DryRun, AppAction: dp.AppAction, // Struct copy is sufficient for this use case } // Deep copy instance actions clone.InstanceActions = make([]InstanceAction, len(dp.InstanceActions)) copy(clone.InstanceActions, dp.InstanceActions) return clone } // convertNetworkRules converts config network rules to EdgeConnect SecurityRules func convertNetworkRules(network *config.NetworkConfig) []edgeconnect.SecurityRule { rules := make([]edgeconnect.SecurityRule, len(network.OutboundConnections)) for i, conn := range network.OutboundConnections { rules[i] = edgeconnect.SecurityRule{ Protocol: conn.Protocol, PortRangeMin: conn.PortRangeMin, PortRangeMax: conn.PortRangeMax, RemoteCIDR: conn.RemoteCIDR, } } return rules }