diff --git a/api/build/graph.go b/api/build/graph.go index bc1c26319..7f95410ed 100644 --- a/api/build/graph.go +++ b/api/build/graph.go @@ -5,6 +5,7 @@ package build import ( "fmt" "net/http" + "sort" "strings" "github.com/gin-gonic/gin" @@ -229,6 +230,16 @@ func GetBuildGraph(c *gin.Context) { return } + if p == nil { + retErr := fmt.Errorf("unable to compile pipeline configuration for %s: pipeline is nil", r.GetFullName()) + + logger.Error(retErr) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + // skip the build if only the init or clone steps are found skip := SkipEmptyBuild(p) if skip != "" { @@ -340,6 +351,10 @@ func GetBuildGraph(c *gin.Context) { } for _, step := range steps { + if step == nil { + continue + } + name := step.GetStage() if len(name) == 0 { name = step.GetName() @@ -353,7 +368,10 @@ func GetBuildGraph(c *gin.Context) { } // retrieve the stage to update - s := stageMap[name] + s, ok := stageMap[name] + if !ok { + continue + } // count each step status switch step.GetStatus() { @@ -394,6 +412,10 @@ func GetBuildGraph(c *gin.Context) { // construct services nodes separately // services are grouped via cluster and manually-constructed edges for _, service := range services { + if service == nil { + continue + } + // create the node nodeID := len(nodes) node := nodeFromService(nodeID, service) @@ -413,6 +435,17 @@ func GetBuildGraph(c *gin.Context) { // construct pipeline stages nodes when stages exist for _, stage := range p.Stages { + if stage == nil { + continue + } + + // skip steps/stages that were not present in the build + // this fixes the scenario where mutable templates are updated + s, ok := stageMap[stage.Name] + if !ok { + continue + } + // scrub the environment for _, step := range stage.Steps { step.Environment = nil @@ -433,35 +466,46 @@ func GetBuildGraph(c *gin.Context) { cluster = BuiltInCluster } - node := nodeFromStage(nodeID, cluster, stage, stageMap[stage.Name]) + node := nodeFromStage(nodeID, cluster, stage, s) nodes[nodeID] = node } // create single-step stages when no stages exist if len(p.Stages) == 0 { - for _, step := range p.Steps { - // scrub the environment - step.Environment = nil + // sort steps by number + sort.Slice(steps, func(i, j int) bool { + return steps[i].GetNumber() < steps[j].GetNumber() + }) + for _, step := range steps { // mock stage for edge creation stage := &pipeline.Stage{ - Name: step.Name, + Name: step.GetName(), Needs: []string{}, } + s, ok := stageMap[stage.Name] + if !ok { + continue + } + // create the node nodeID := len(nodes) // no built-in step separation for graphs without stages cluster := PipelineCluster - node := nodeFromStage(nodeID, cluster, stage, stageMap[stage.Name]) + node := nodeFromStage(nodeID, cluster, stage, s) nodes[nodeID] = node } } // loop over all nodes and create edges based on 'needs' for _, destinationNode := range nodes { + if destinationNode == nil { + continue + } + // if theres no stage, skip because the edge is already created if destinationNode.Stage == nil { continue @@ -498,6 +542,10 @@ func GetBuildGraph(c *gin.Context) { continue } + if destinationNode.Stage == nil { + continue + } + // use needs to create an edge if len((*destinationNode.Stage).Needs) > 0 { // check destination node needs