Skip to content
5 changes: 5 additions & 0 deletions backend/controllers/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
// do not report if no projects are impacted to minimise noise in the PR thread
// TODO use status checks instead: https://github.com/diggerhq/digger/issues/1135
log.Printf("No projects impacted; not starting any jobs")
// This one is for aggregate reporting
err = utils.SetPRStatusForJobs(ghService, prNumber, jobsForImpactedProjects)
return nil
}

Expand Down Expand Up @@ -618,6 +620,9 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
}

if len(jobs) == 0 {
log.Printf("no projects impacated, succeeding")
// This one is for aggregate reporting
err = utils.SetPRStatusForJobs(ghService, issueNumber, jobs)
return nil
}

Expand Down
26 changes: 26 additions & 0 deletions backend/utils/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,32 @@ func SetPRStatusForJobs(prService *github2.GithubService, prNumber int, jobs []o
}
}
}
// Report aggregate status for digger/plan or digger/apply
if len(jobs) > 0 {
var err error
if orchestrator.IsPlanJobs(jobs) {
err = prService.SetStatus(prNumber, "pending", "digger/plan")
} else {
err = prService.SetStatus(prNumber, "pending", "digger/apply")
}
if err != nil {
log.Printf("error setting status: %v", err)
return fmt.Errorf("error setting pr status: %v", err)
}

} else {
err := prService.SetStatus(prNumber, "success", "digger/plan")
if err != nil {
log.Printf("error setting status: %v", err)
return fmt.Errorf("error setting pr status: %v", err)
}
err = prService.SetStatus(prNumber, "success", "digger/apply")
if err != nil {
log.Printf("error setting status: %v", err)
return fmt.Errorf("error setting pr status: %v", err)
}
}

return nil
}

Expand Down
20 changes: 20 additions & 0 deletions cli/cmd/digger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ func gitHubCI(lock core_locking.Lock, policyChecker core_policy.Checker, backend
}

digger.UpdateStatusComment(serializedBatch.Jobs, serializedBatch.PrNumber, &githubPrService, commentId64)
digger.UpdateAggregateStatus(serializedBatch, &githubPrService)

planStorage := newPlanStorage(ghToken, repoOwner, repositoryName, githubActor, job.PullRequestNumber)

Expand All @@ -150,6 +151,7 @@ func gitHubCI(lock core_locking.Lock, policyChecker core_policy.Checker, backend
reportErrorAndExit(githubActor, fmt.Sprintf("Failed run commands. %s", err), 5)
}
digger.UpdateStatusComment(serializedBatch.Jobs, serializedBatch.PrNumber, &githubPrService, commentId64)
digger.UpdateAggregateStatus(serializedBatch, &githubPrService)

reportErrorAndExit(githubActor, fmt.Sprintf("Failed to run commands. %s", err), 5)
}
Expand All @@ -163,6 +165,7 @@ func gitHubCI(lock core_locking.Lock, policyChecker core_policy.Checker, backend
reportErrorAndExit(githubActor, fmt.Sprintf("Failed run commands. %s", err), 5)
}
digger.UpdateStatusComment(serializedBatch.Jobs, serializedBatch.PrNumber, &githubPrService, commentId64)
digger.UpdateAggregateStatus(serializedBatch, &githubPrService)
reportErrorAndExit(githubActor, fmt.Sprintf("Failed to run commands. %s", err), 5)
}
reportErrorAndExit(githubActor, "Digger finished successfully", 0)
Expand Down Expand Up @@ -327,13 +330,30 @@ func gitHubCI(lock core_locking.Lock, policyChecker core_policy.Checker, backend
allAppliesSuccessful, atLeastOneApply, err := digger.RunJobs(jobs, &githubPrService, &githubPrService, lock, reporter, planStorage, policyChecker, backendApi, "", false, 0, currentDir)
if err != nil {
reportErrorAndExit(githubActor, fmt.Sprintf("Failed to run commands. %s", err), 8)
// aggregate status checks: failure
if allAppliesSuccessful {
if atLeastOneApply {
githubPrService.SetStatus(prNumber, "failure", "digger/apply")
} else {
githubPrService.SetStatus(prNumber, "failure", "digger/plan")
}
}
}

if diggerConfig.AutoMerge && allAppliesSuccessful && atLeastOneApply && coversAllImpactedProjects {
digger.MergePullRequest(&githubPrService, prNumber)
log.Println("PR merged successfully")
}

if allAppliesSuccessful {
// aggreate status checks: success
if atLeastOneApply {
githubPrService.SetStatus(prNumber, "success", "digger/apply")
} else {
githubPrService.SetStatus(prNumber, "success", "digger/plan")
}
}

log.Println("Commands executed successfully")
}

Expand Down
30 changes: 7 additions & 23 deletions cli/pkg/digger/digger.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import (
"strings"
"time"

"github.com/diggerhq/digger/libs/orchestrator/scheduler"
"github.com/goccy/go-json"

"github.com/diggerhq/digger/cli/pkg/core/backend"
"github.com/diggerhq/digger/cli/pkg/core/execution"
core_locking "github.com/diggerhq/digger/cli/pkg/core/locking"
Expand Down Expand Up @@ -150,33 +147,20 @@ func RunJobs(

err = UpdateStatusComment(batchResult.Jobs, prNumber, prService, prCommentId)
if err != nil {
log.Printf("error Updating status comment: %v.\n", err)
return false, false, err
}
}

atLeastOneApply := len(appliesPerProject) > 0

return allAppliesSuccess, atLeastOneApply, nil
}

func UpdateStatusComment(jobs []scheduler.SerializedJob, prNumber int, prService orchestrator.PullRequestService, prCommentId int64) error {

message := ":construction_worker: Jobs status:\n\n"
for _, job := range jobs {
var jobSpec orchestrator.JobJson
err := json.Unmarshal(job.JobString, &jobSpec)
err = UpdateAggregateStatus(batchResult, prService)
if err != nil {
log.Printf("Failed to convert unmarshall Serialized job")
log.Printf("error udpating aggregate status check: %v.\n", err)
return false, false, err
}
isPlan := jobSpec.IsPlan()

message = message + fmt.Sprintf("<!-- PROJECTHOLDER %v -->\n", job.ProjectName)
message = message + fmt.Sprintf("%v **%v** <a href='%v'>%v</a>%v\n", job.Status.ToEmoji(), jobSpec.ProjectName, *job.WorkflowRunUrl, job.Status.ToString(), job.ResourcesSummaryString(isPlan))
message = message + fmt.Sprintf("<!-- PROJECTHOLDEREND %v -->\n", job.ProjectName)
}

prService.EditComment(prNumber, prCommentId, message)
return nil
atLeastOneApply := len(appliesPerProject) > 0

return allAppliesSuccess, atLeastOneApply, nil
}

func reportPolicyError(projectName string, command string, requestedBy string, reporter core_reporting.Reporter) string {
Expand Down
48 changes: 48 additions & 0 deletions cli/pkg/digger/io.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package digger

import (
"fmt"
"github.com/diggerhq/digger/libs/orchestrator"
"github.com/diggerhq/digger/libs/orchestrator/scheduler"
"github.com/goccy/go-json"
"log"
)

func UpdateStatusComment(jobs []scheduler.SerializedJob, prNumber int, prService orchestrator.PullRequestService, prCommentId int64) error {

message := ":construction_worker: Jobs status:\n\n"
for _, job := range jobs {
var jobSpec orchestrator.JobJson
err := json.Unmarshal(job.JobString, &jobSpec)
if err != nil {
log.Printf("Failed to convert unmarshall Serialized job, %v", err)
return fmt.Errorf("Failed to unmarshall serialized job: %v", err)
}
isPlan := jobSpec.IsPlan()

message = message + fmt.Sprintf("<!-- PROJECTHOLDER %v -->\n", job.ProjectName)
message = message + fmt.Sprintf("%v **%v** <a href='%v'>%v</a>%v\n", job.Status.ToEmoji(), jobSpec.ProjectName, *job.WorkflowRunUrl, job.Status.ToString(), job.ResourcesSummaryString(isPlan))
message = message + fmt.Sprintf("<!-- PROJECTHOLDEREND %v -->\n", job.ProjectName)
}

prService.EditComment(prNumber, prCommentId, message)
return nil
}

func UpdateAggregateStatus(batch *scheduler.SerializedBatch, prService orchestrator.PullRequestService) error {
// TODO: Introduce batch-level
isPlan, err := batch.IsPlan()
if err != nil {
log.Printf("failed to get batch job plan/apply status: %v", err)
return fmt.Errorf("failed to get batch job plan/apply status: %v", err)
}

if isPlan {
prService.SetStatus(batch.PrNumber, batch.ToStatusCheck(), "digger/plan")
prService.SetStatus(batch.PrNumber, "pending", "digger/apply")
} else {
prService.SetStatus(batch.PrNumber, "success", "digger/plan")
prService.SetStatus(batch.PrNumber, batch.ToStatusCheck(), "digger/apply")
}
return nil
}
16 changes: 16 additions & 0 deletions libs/orchestrator/json_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,19 @@ func stageToJson(stage *Stage) StageJson {
Steps: steps,
}
}

func IsPlanJobSpecs(jobs []JobJson) bool {
isPlan := true
for _, job := range jobs {
isPlan = isPlan && job.IsPlan()
}
return isPlan
}

func IsApplyJobSpecs(jobs []JobJson) bool {
isApply := true
for _, job := range jobs {
isApply = isApply && job.IsApply()
}
return isApply
}
25 changes: 25 additions & 0 deletions libs/orchestrator/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/diggerhq/digger/libs/digger_config"
configuration "github.com/diggerhq/digger/libs/digger_config"
"log"
"slices"
)

type Job struct {
Expand Down Expand Up @@ -63,6 +64,30 @@ func ToConfigStage(configStage *configuration.Stage) *Stage {
}
}

func (j *Job) IsPlan() bool {
return slices.Contains(j.Commands, "digger plan")
}

func (j *Job) IsApply() bool {
return slices.Contains(j.Commands, "digger apply")
}

func IsPlanJobs(jobs []Job) bool {
isPlan := true
for _, job := range jobs {
isPlan = isPlan && job.IsPlan()
}
return isPlan
}

func IsApplyJobs(jobs []JobJson) bool {
isApply := true
for _, job := range jobs {
isApply = isApply && job.IsApply()
}
return isApply
}

func ConvertProjectsToJobs(actor string, repoNamespace string, command string, prNumber int, impactedProjects []digger_config.Project, requestedProject *digger_config.Project, workflows map[string]digger_config.Workflow) ([]Job, bool, error) {
jobs := make([]Job, 0)

Expand Down
55 changes: 54 additions & 1 deletion libs/orchestrator/scheduler/models.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package scheduler

import "fmt"
import (
"fmt"
"github.com/diggerhq/digger/libs/orchestrator"
"github.com/goccy/go-json"
"log"
)

type DiggerBatchStatus int8

Expand Down Expand Up @@ -86,6 +91,40 @@ type SerializedBatch struct {
Jobs []SerializedJob `json:"jobs"`
}

func (b *SerializedBatch) IsPlan() (bool, error) {
// TODO: Introduce a batch-level field to check for is plan or apply
jobSpecs, err := GetJobSpecs(b.Jobs)
if err != nil {
log.Printf("error while fetching job specs: %v", err)
return false, fmt.Errorf("error while fetching job specs: %v", err)
}
return orchestrator.IsPlanJobSpecs(jobSpecs), nil
}

func (b *SerializedBatch) IsApply() (bool, error) {
jobSpecs, err := GetJobSpecs(b.Jobs)
if err != nil {
log.Printf("error while fetching job specs: %v", err)
return false, fmt.Errorf("error while fetching job specs: %v", err)
}
return orchestrator.IsPlanJobSpecs(jobSpecs), nil
}

func (b *SerializedBatch) ToStatusCheck() string {
switch b.Status {
case BatchJobCreated:
return "pending"
case BatchJobInvalidated:
return "failure"
case BatchJobFailed:
return "success"
case BatchJobSucceeded:
return "success"
default:
return "pending"
}
}

func (s *SerializedJob) ResourcesSummaryString(isPlan bool) string {
if !isPlan {
return ""
Expand All @@ -97,3 +136,17 @@ func (s *SerializedJob) ResourcesSummaryString(isPlan bool) string {
return "..."
}
}

func GetJobSpecs(jobs []SerializedJob) ([]orchestrator.JobJson, error) {
jobSpecs := make([]orchestrator.JobJson, 0)
for _, job := range jobs {
var jobSpec orchestrator.JobJson
err := json.Unmarshal(job.JobString, &jobSpec)
if err != nil {
log.Printf("Failed to convert unmarshall Serialized job")
return nil, err
}
jobSpecs = append(jobSpecs, jobSpec)
}
return jobSpecs, nil
}