package workflows import portainer "github.com/portainer/portainer/api" func SourceStatusToPhase(s portainer.SourceStatus, errMsg string) WorkflowPhaseStatus { switch s { case portainer.SourceStatusHealthy: return WorkflowPhaseStatus{Status: StatusHealthy} case portainer.SourceStatusError: return WorkflowPhaseStatus{Status: StatusError, Error: errMsg} default: return WorkflowPhaseStatus{Status: StatusUnknown} } } // ArtifactPhases returns the source and artifact-path health phases for an artifact's files, // aggregating the worst-priority status across all of its files. The source phase also folds in // each file's Source connectivity status, since a broken source invalidates ref resolution // regardless of the file's own cached RefStatus. func ArtifactPhases(files []portainer.ArtifactFile, sourceMap map[portainer.SourceID]portainer.Source) (source, artifact WorkflowPhaseStatus) { source = WorkflowPhaseStatus{Status: StatusUnknown} artifact = WorkflowPhaseStatus{Status: StatusUnknown} for _, f := range files { src, ok := sourceMap[f.SourceID] if !ok { continue } if srcPhase := SourceStatusToPhase(src.Status, src.StatusError); statusPriority(srcPhase.Status) > statusPriority(source.Status) { source = srcPhase } if refPhase := SourceStatusToPhase(f.RefStatus, f.RefError); statusPriority(refPhase.Status) > statusPriority(source.Status) { source = refPhase } if artifactPhase := SourceStatusToPhase(f.PathStatus, f.PathError); statusPriority(artifactPhase.Status) > statusPriority(artifact.Status) { artifact = artifactPhase } } return source, artifact } func WorkflowPhaseToStatus(p WorkflowPhaseStatus) (portainer.SourceStatus, string) { switch p.Status { case StatusHealthy: return portainer.SourceStatusHealthy, "" case StatusError: return portainer.SourceStatusError, p.Error default: return portainer.SourceStatusUnknown, "" } } func deriveStackTargetState(s portainer.Stack) WorkflowPhaseStatus { if len(s.DeploymentStatus) == 0 { return WorkflowPhaseStatus{Status: StatusHealthy} } last := s.DeploymentStatus[len(s.DeploymentStatus)-1] switch last.Status { case portainer.StackStatusActive: return WorkflowPhaseStatus{Status: StatusHealthy} case portainer.StackStatusError: return WorkflowPhaseStatus{Status: StatusError, Error: last.Message} case portainer.StackStatusDeploying: return WorkflowPhaseStatus{Status: StatusSyncing} case portainer.StackStatusInactive: return WorkflowPhaseStatus{Status: StatusPaused} default: return WorkflowPhaseStatus{Status: StatusUnknown} } } func deriveEdgeStackTargetState(statuses []portainer.EdgeStackStatusForEnv) WorkflowPhaseStatus { result := StatusUnknown for _, epStatus := range statuses { ws, msg := endpointWorkflowStatus(epStatus) if ws == StatusError { return WorkflowPhaseStatus{Status: ws, Error: msg} } if statusPriority(ws) > statusPriority(result) { result = ws } } return WorkflowPhaseStatus{Status: result} } func endpointWorkflowStatus(epStatus portainer.EdgeStackStatusForEnv) (Status, string) { if len(epStatus.Status) == 0 { return StatusUnknown, "" } last := epStatus.Status[len(epStatus.Status)-1] switch last.Type { case portainer.EdgeStackStatusError: return StatusError, last.Error case portainer.EdgeStackStatusDeploying, portainer.EdgeStackStatusRollingBack, portainer.EdgeStackStatusRemoving, portainer.EdgeStackStatusPending, portainer.EdgeStackStatusDeploymentReceived, portainer.EdgeStackStatusAcknowledged, portainer.EdgeStackStatusImagesPulled: return StatusSyncing, "" case portainer.EdgeStackStatusPausedDeploying: return StatusPaused, "" case portainer.EdgeStackStatusRunning, portainer.EdgeStackStatusRolledBack, portainer.EdgeStackStatusCompleted, portainer.EdgeStackStatusRemoved, portainer.EdgeStackStatusRemoteUpdateSuccess: return StatusHealthy, "" default: return StatusUnknown, "" } } // EffectiveStatus returns the highest-priority status across all three phases of a workflow. func EffectiveStatus(w Workflow) Status { s := w.Status.Target.Status if statusPriority(w.Status.Source.Status) > statusPriority(s) { s = w.Status.Source.Status } if statusPriority(w.Status.Artifact.Status) > statusPriority(s) { s = w.Status.Artifact.Status } return s } // CountByStatus counts workflows per effective status and returns a StatusSummary. func CountByStatus(workflows []Workflow) StatusSummary { var s StatusSummary for _, w := range workflows { switch EffectiveStatus(w) { case StatusHealthy: s.Healthy++ case StatusSyncing: s.Syncing++ case StatusError: s.Error++ case StatusPaused: s.Paused++ default: s.Unknown++ } } return s } func statusPriority(s Status) int { switch s { case StatusError: return 4 case StatusSyncing: return 3 case StatusPaused: return 2 case StatusHealthy: return 1 default: return 0 } }