chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
package workflows
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/dataservices/source"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/portainer/portainer/api/set"
|
||||
)
|
||||
|
||||
// FetchWorkflows returns all GitOps workflows visible to the given user.
|
||||
func FetchWorkflows(
|
||||
tx dataservices.DataStoreTx,
|
||||
k8sFactory *cli.ClientFactory,
|
||||
sc *security.RestrictedRequestContext,
|
||||
endpointIDSet set.Set[portainer.EndpointID],
|
||||
) ([]Workflow, error) {
|
||||
gitConfigs := map[portainer.StackID]*gittypes.RepoConfig{}
|
||||
sourceIDs := map[portainer.StackID]portainer.SourceID{}
|
||||
sourcePhases := map[portainer.StackID]WorkflowPhaseStatus{}
|
||||
artifactPhases := map[portainer.StackID]WorkflowPhaseStatus{}
|
||||
|
||||
userContext := source.NewUserContext(sc.User, sc.UserMemberships)
|
||||
|
||||
stacks, err := tx.Stack().ReadAll(func(s portainer.Stack) bool {
|
||||
return s.WorkflowID != 0 && (len(endpointIDSet) == 0 || endpointIDSet.Contains(s.EndpointID))
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpointMap, err := BuildEndpointMap(tx, stacks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stacks, err = FilterDockerStacksByAccess(tx, stacks, sc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First pass: filter by endpoint/stack-type match and collect workflow IDs.
|
||||
preFiltered := make([]portainer.Stack, 0, len(stacks))
|
||||
workflowIDSet := make(set.Set[portainer.WorkflowID], len(stacks))
|
||||
for _, stack := range stacks {
|
||||
if ep, ok := endpointMap[stack.EndpointID]; ok && !EndpointMatchesStackType(ep, stack.Type) {
|
||||
continue
|
||||
}
|
||||
preFiltered = append(preFiltered, stack)
|
||||
workflowIDSet.Add(stack.WorkflowID)
|
||||
}
|
||||
|
||||
workflowMap, sourceMap, err := LoadWorkflowAndSourceMaps(tx, userContext, workflowIDSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Second pass: build filtered list using in-memory lookups.
|
||||
var filtered []portainer.Stack
|
||||
for _, stack := range preFiltered {
|
||||
wf := workflowMap[stack.WorkflowID]
|
||||
|
||||
hasAccessibleSource := false
|
||||
outer:
|
||||
for _, as := range wf.Artifacts {
|
||||
if as.StackID != stack.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range as.Files {
|
||||
src, ok := sourceMap[f.SourceID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
hasAccessibleSource = true
|
||||
|
||||
if src.Type == portainer.SourceTypeGit {
|
||||
gitConfigs[stack.ID] = MergeSourceAndFile(&src, &f)
|
||||
sourceIDs[stack.ID] = src.ID
|
||||
sourcePhases[stack.ID] = SourceStatusToPhase(f.RefStatus, f.RefError)
|
||||
artifactPhases[stack.ID] = SourceStatusToPhase(f.PathStatus, f.PathError)
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if stack.Type == portainer.KubernetesStack && !hasAccessibleSource {
|
||||
continue
|
||||
}
|
||||
|
||||
filtered = append(filtered, stack)
|
||||
}
|
||||
stacks = filtered
|
||||
|
||||
accessMap, err := buildEndpointAccessMap(k8sFactory, sc, endpointMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stacks, err = filterK8SStacks(stacks, endpointMap, k8sFactory, accessMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]Workflow, 0, len(stacks))
|
||||
for _, stack := range stacks {
|
||||
gitConfig := gitConfigs[stack.ID]
|
||||
items = append(items, MapStackToWorkflow(stack, sourceIDs[stack.ID], gitConfig, sourcePhases[stack.ID], artifactPhases[stack.ID]))
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// SourceStats holds aggregated statistics for a GitOps source.
|
||||
type SourceStats struct {
|
||||
WorkflowCount int
|
||||
EndpointIDs set.Set[portainer.EndpointID]
|
||||
}
|
||||
|
||||
// FetchSourceStats returns all sources and per-source stats for sources accessible to the given user.
|
||||
// It applies the same access control as FetchWorkflows but skips git phase checks.
|
||||
func FetchSourceStats(
|
||||
tx dataservices.DataStoreTx,
|
||||
k8sFactory *cli.ClientFactory,
|
||||
sc *security.RestrictedRequestContext,
|
||||
) ([]portainer.Source, map[portainer.SourceID]SourceStats, error) {
|
||||
userContext := source.NewUserContext(sc.User, sc.UserMemberships)
|
||||
|
||||
sources, err := tx.Source().ReadAll(userContext)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
allStacks, err := tx.Stack().ReadAll(func(s portainer.Stack) bool { return s.WorkflowID != 0 })
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
endpointMap, err := BuildEndpointMap(tx, allStacks)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
allStacks, err = FilterDockerStacksByAccess(tx, allStacks, sc)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
workflowIDSet := make(set.Set[portainer.WorkflowID], len(allStacks))
|
||||
preFiltered := make([]portainer.Stack, 0, len(allStacks))
|
||||
for _, stack := range allStacks {
|
||||
if ep, ok := endpointMap[stack.EndpointID]; ok && !EndpointMatchesStackType(ep, stack.Type) {
|
||||
continue
|
||||
}
|
||||
preFiltered = append(preFiltered, stack)
|
||||
workflowIDSet.Add(stack.WorkflowID)
|
||||
}
|
||||
|
||||
wfMap, err := LoadWorkflowMap(tx, workflowIDSet)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
wfSources := make(map[portainer.WorkflowID][]portainer.SourceID, len(wfMap))
|
||||
for id, wf := range wfMap {
|
||||
for _, as := range wf.Artifacts {
|
||||
for _, f := range as.Files {
|
||||
wfSources[id] = append(wfSources[id], f.SourceID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stackSourceIDs := make(map[portainer.StackID][]portainer.SourceID)
|
||||
for _, stack := range preFiltered {
|
||||
if srcIDs := wfSources[stack.WorkflowID]; len(srcIDs) > 0 {
|
||||
stackSourceIDs[stack.ID] = srcIDs
|
||||
}
|
||||
}
|
||||
|
||||
accessMap, err := buildEndpointAccessMap(k8sFactory, sc, endpointMap)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
stacks, err := filterK8SStacks(preFiltered, endpointMap, k8sFactory, accessMap)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
stats := make(map[portainer.SourceID]SourceStats)
|
||||
|
||||
for _, stack := range stacks {
|
||||
var epIDs []portainer.EndpointID
|
||||
if stack.EndpointID != 0 {
|
||||
epIDs = []portainer.EndpointID{stack.EndpointID}
|
||||
}
|
||||
addSourceStats(stats, stackSourceIDs[stack.ID], epIDs)
|
||||
}
|
||||
|
||||
return sources, stats, nil
|
||||
}
|
||||
|
||||
func addSourceStats(result map[portainer.SourceID]SourceStats, srcIDs []portainer.SourceID, epIDs []portainer.EndpointID) {
|
||||
for _, srcID := range srcIDs {
|
||||
st := result[srcID]
|
||||
if st.EndpointIDs == nil {
|
||||
st.EndpointIDs = make(set.Set[portainer.EndpointID])
|
||||
}
|
||||
st.WorkflowCount++
|
||||
for _, epID := range epIDs {
|
||||
st.EndpointIDs.Add(epID)
|
||||
}
|
||||
result[srcID] = st
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user