Skip to content

Commit 76142d6

Browse files
authored
feat/support layers (#2061)
* support layers in digger.yml
1 parent 216a8b2 commit 76142d6

File tree

26 files changed

+370
-110
lines changed

26 files changed

+370
-110
lines changed

backend/controllers/github.go

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -720,17 +720,6 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
720720
return fmt.Errorf("error initializing comment reporter")
721721
}
722722

723-
err = utils.ReportInitialJobsStatus(commentReporter, jobsForImpactedProjects)
724-
if err != nil {
725-
slog.Error("Failed to comment initial status for jobs",
726-
"prNumber", prNumber,
727-
"jobCount", len(jobsForImpactedProjects),
728-
"error", err,
729-
)
730-
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to comment initial status for jobs: %v", err))
731-
return fmt.Errorf("failed to comment initial status for jobs")
732-
}
733-
734723
err = utils.SetPRStatusForJobs(ghService, prNumber, jobsForImpactedProjects)
735724
if err != nil {
736725
slog.Error("Error setting status for PR",
@@ -741,6 +730,40 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
741730
return fmt.Errorf("error setting status for PR: %v", err)
742731
}
743732

733+
nLayers, _ := orchestrator_scheduler.CountUniqueLayers(jobsForImpactedProjects)
734+
slog.Debug("Number of layers",
735+
"prNumber", prNumber,
736+
"nLayers", nLayers,
737+
"respectLayers", config.RespectLayers,
738+
)
739+
if config.RespectLayers && nLayers > 1 {
740+
slog.Debug("Respecting layers",
741+
"prNumber", prNumber)
742+
err = utils.ReportLayersTableForJobs(commentReporter, jobsForImpactedProjects)
743+
if err != nil {
744+
slog.Error("Failed to comment initial status for jobs",
745+
"prNumber", prNumber,
746+
"jobCount", len(jobsForImpactedProjects),
747+
"error", err,
748+
)
749+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to comment initial status for jobs: %v", err))
750+
return fmt.Errorf("failed to comment initial status for jobs")
751+
}
752+
slog.Debug("not performing plan since there are multiple layers and respect_layers is enabled")
753+
return nil
754+
} else {
755+
err = utils.ReportInitialJobsStatus(commentReporter, jobsForImpactedProjects)
756+
if err != nil {
757+
slog.Error("Failed to comment initial status for jobs",
758+
"prNumber", prNumber,
759+
"jobCount", len(jobsForImpactedProjects),
760+
"error", err,
761+
)
762+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to comment initial status for jobs: %v", err))
763+
return fmt.Errorf("failed to comment initial status for jobs")
764+
}
765+
}
766+
744767
slog.Debug("Preparing job and project maps",
745768
"prNumber", prNumber,
746769
"projectCount", len(impactedProjects),
@@ -788,6 +811,9 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
788811
"jobCount", len(impactedJobsMap),
789812
)
790813

814+
if config.RespectLayers {
815+
816+
}
791817
batchId, _, err := utils.ConvertJobsToDiggerJobs(
792818
*diggerCommand,
793819
models.DiggerVCSGithub,
@@ -1427,7 +1453,7 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
14271453
"branchName", prBranchName,
14281454
)
14291455

1430-
impactedProjects, impactedProjectsSourceMapping, requestedProject, _, err := generic.ProcessIssueCommentEvent(issueNumber, commentBody, config, projectsGraph, ghService)
1456+
processEventResult, err := generic.ProcessIssueCommentEvent(issueNumber, commentBody, config, projectsGraph, ghService)
14311457
if err != nil {
14321458
slog.Error("Error processing issue comment event",
14331459
"issueNumber", issueNumber,
@@ -1436,45 +1462,49 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
14361462
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Error processing event: %v", err))
14371463
return fmt.Errorf("error processing event")
14381464
}
1465+
impactedProjectsForComment := processEventResult.ImpactedProjectsForComment
1466+
impactedProjectsSourceMapping := processEventResult.ImpactedProjectsSourceMapping
1467+
allImpactedProjects := processEventResult.AllImpactedProjects
14391468

14401469
slog.Info("Issue comment event processed successfully",
14411470
"issueNumber", issueNumber,
1442-
"impactedProjectCount", len(impactedProjects),
1443-
"requestedProject", requestedProject,
1471+
"impactedProjectCount", len(impactedProjectsForComment),
1472+
"allImpactedProjectsCount", len(allImpactedProjects),
1473+
14441474
)
14451475

1446-
jobs, coverAllImpactedProjects, err := generic.ConvertIssueCommentEventToJobs(repoFullName, actor, issueNumber, commentBody, impactedProjects, requestedProject, config.Workflows, prBranchName, defaultBranch)
1476+
jobs, coverAllImpactedProjects, err := generic.ConvertIssueCommentEventToJobs(repoFullName, actor, issueNumber, commentBody, impactedProjectsForComment, allImpactedProjects, config.Workflows, prBranchName, defaultBranch)
14471477
if err != nil {
14481478
slog.Error("Error converting event to jobs",
14491479
"issueNumber", issueNumber,
1450-
"impactedProjectCount", len(impactedProjects),
1480+
"impactedProjectCount", len(impactedProjectsForComment),
14511481
"error", err,
14521482
)
14531483
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Error converting event to jobs: %v", err))
14541484
return fmt.Errorf("error converting event to jobs")
14551485
}
1456-
1486+
14571487
slog.Info("Issue comment event converted to jobs successfully",
14581488
"issueNumber", issueNumber,
14591489
"jobCount", len(jobs),
14601490
)
14611491

14621492
// if flag set we dont allow more projects impacted than the number of changed files in PR (safety check)
14631493
if config2.LimitByNumOfFilesChanged() {
1464-
if len(impactedProjects) > len(changedFiles) {
1494+
if len(impactedProjectsForComment) > len(changedFiles) {
14651495
slog.Error("Number of impacted projects exceeds number of changed files",
14661496
"issueNumber", issueNumber,
1467-
"impactedProjectCount", len(impactedProjects),
1497+
"impactedProjectCount", len(impactedProjectsForComment),
14681498
"changedFileCount", len(changedFiles),
14691499
)
14701500

1471-
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Error the number impacted projects %v exceeds number of changed files: %v", len(impactedProjects), len(changedFiles)))
1501+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Error the number impacted projects %v exceeds number of changed files: %v", len(impactedProjectsForComment), len(changedFiles)))
14721502

14731503
slog.Debug("Detailed event information",
14741504
slog.Group("details",
14751505
slog.Any("changedFiles", changedFiles),
14761506
slog.Int("configLength", len(diggerYmlStr)),
1477-
slog.Int("impactedProjectCount", len(impactedProjects)),
1507+
slog.Int("impactedProjectCount", len(impactedProjectsForComment)),
14781508
),
14791509
)
14801510

@@ -1483,20 +1513,20 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
14831513
}
14841514

14851515
maxImpactedProjectsPerChange := config2.MaxImpactedProjectsPerChange()
1486-
if len(impactedProjects) > maxImpactedProjectsPerChange {
1516+
if len(impactedProjectsForComment) > maxImpactedProjectsPerChange {
14871517
slog.Error("Number of impacted projects exceeds number of changed files",
14881518
"prNumber", issueNumber,
1489-
"impactedProjectCount", len(impactedProjects),
1519+
"impactedProjectCount", len(impactedProjectsForComment),
14901520
"changedFileCount", len(changedFiles),
14911521
)
14921522

1493-
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Error the number impacted projects %v exceeds Max allowed ImpactedProjectsPerChange: %v, we set this limit to protect against hitting github API limits", len(impactedProjects), maxImpactedProjectsPerChange))
1523+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Error the number impacted projects %v exceeds Max allowed ImpactedProjectsPerChange: %v, we set this limit to protect against hitting github API limits", len(impactedProjectsForComment), maxImpactedProjectsPerChange))
14941524

14951525
slog.Debug("Detailed event information",
14961526
slog.Group("details",
14971527
slog.Any("changedFiles", changedFiles),
14981528
slog.Int("configLength", len(diggerYmlStr)),
1499-
slog.Int("impactedProjectCount", len(impactedProjects)),
1529+
slog.Int("impactedProjectCount", len(impactedProjectsForComment)),
15001530
),
15011531
)
15021532
return fmt.Errorf("error processing event")
@@ -1506,11 +1536,11 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
15061536
if config.PrLocks {
15071537
slog.Info("Processing PR locks for impacted projects",
15081538
"issueNumber", issueNumber,
1509-
"projectCount", len(impactedProjects),
1539+
"projectCount", len(impactedProjectsForComment),
15101540
"command", *diggerCommand,
15111541
)
15121542

1513-
for _, project := range impactedProjects {
1543+
for _, project := range impactedProjectsForComment {
15141544
prLock := dg_locking.PullRequestLock{
15151545
InternalLock: locking.BackendDBLock{
15161546
OrgId: orgId,
@@ -1580,12 +1610,12 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
15801610

15811611
slog.Debug("Preparing job and project maps",
15821612
"issueNumber", issueNumber,
1583-
"projectCount", len(impactedProjects),
1613+
"projectCount", len(impactedProjectsForComment),
15841614
"jobCount", len(jobs),
15851615
)
15861616

15871617
impactedProjectsMap := make(map[string]dg_configuration.Project)
1588-
for _, p := range impactedProjects {
1618+
for _, p := range impactedProjectsForComment {
15891619
impactedProjectsMap[p.Name] = p
15901620
}
15911621

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Modify "digger_batches" table
2+
ALTER TABLE "public"."digger_batches" ADD COLUMN "layer" bigint NULL;

backend/migrations/atlas.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
h1:DO/UG0tYCnZA69yRLMd2ZBKUORlJtZJLkVCiCogKp+k=
1+
h1:g7vBQEDCfExWRVQBcLZ9Jnxb1y/szNvlaeZjw1ZfRSY=
22
20231227132525.sql h1:43xn7XC0GoJsCnXIMczGXWis9d504FAWi4F1gViTIcw=
33
20240115170600.sql h1:IW8fF/8vc40+eWqP/xDK+R4K9jHJ9QBSGO6rN9LtfSA=
44
20240116123649.sql h1:R1JlUIgxxF6Cyob9HdtMqiKmx/BfnsctTl5rvOqssQw=
@@ -61,3 +61,4 @@ h1:DO/UG0tYCnZA69yRLMd2ZBKUORlJtZJLkVCiCogKp+k=
6161
20250716222603.sql h1:a66teR/6BQwNwhJ/zgNA/NjUVTz5DcoQl7cDZvzVS8c=
6262
20250717032021.sql h1:HaIhNsz3C+c87CDmFjgFlc9zGqoE5BU4m0dofDpeDYk=
6363
20250725041417.sql h1:Dds6fqS415FD1jlsoVEahcEEm1px3EHV5435moL+Vp8=
64+
20250730002811.sql h1:VqNDMnhVinkBXlDJN4BCq5JzZr4NHVu6zuHEGP9dMvk=

backend/models/scheduler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const DiggerVCSBitbucket DiggerVCSType = "bitbucket"
2626
type DiggerBatch struct {
2727
gorm.Model
2828
ID uuid.UUID `gorm:"primary_key"`
29+
Layer uint
2930
VCS DiggerVCSType
3031
PrNumber int
3132
CommentId *int64

backend/utils/pr_comment.go

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package utils
33
import (
44
"fmt"
55
"log/slog"
6+
"sort"
67

78
"github.com/diggerhq/digger/libs/ci"
89
"github.com/diggerhq/digger/libs/scheduler"
@@ -135,6 +136,28 @@ func UpdateCRComment(cr *CommentReporter, comment string) error {
135136
return nil
136137
}
137138

139+
func trimMessageIfExceedsMaxLength(message string) string {
140+
141+
const GithubCommentMaxLength = 65536
142+
143+
if len(message) > GithubCommentMaxLength {
144+
slog.Warn("Comment message is too long, trimming",
145+
"originalLength", len(message),
146+
"maxLength", GithubCommentMaxLength,
147+
)
148+
149+
const footer = "[trimmed]"
150+
trimLength := len(message) - GithubCommentMaxLength + len(footer)
151+
message = message[:len(message)-trimLength] + footer
152+
153+
slog.Debug("Trimmed comment message",
154+
"newLength", len(message),
155+
"trimmedBytes", trimLength,
156+
)
157+
}
158+
return message
159+
}
160+
138161
func ReportInitialJobsStatus(cr *CommentReporter, jobs []scheduler.Job) error {
139162
prNumber := cr.PrNumber
140163
prService := cr.PrService
@@ -158,24 +181,7 @@ func ReportInitialJobsStatus(cr *CommentReporter, jobs []scheduler.Job) error {
158181
}
159182
}
160183

161-
const GithubCommentMaxLength = 65536
162-
163-
if len(message) > GithubCommentMaxLength {
164-
slog.Warn("Comment message is too long, trimming",
165-
"originalLength", len(message),
166-
"maxLength", GithubCommentMaxLength,
167-
)
168-
169-
const footer = "[trimmed]"
170-
trimLength := len(message) - GithubCommentMaxLength + len(footer)
171-
message = message[:len(message)-trimLength] + footer
172-
173-
slog.Debug("Trimmed comment message",
174-
"newLength", len(message),
175-
"trimmedBytes", trimLength,
176-
)
177-
}
178-
184+
message = trimMessageIfExceedsMaxLength(message)
179185
err := prService.EditComment(prNumber, commentId, message)
180186
if err != nil {
181187
slog.Error("Failed to update comment with initial jobs status",
@@ -190,39 +196,61 @@ func ReportInitialJobsStatus(cr *CommentReporter, jobs []scheduler.Job) error {
190196
return nil
191197
}
192198

193-
func ReportNoProjectsImpacted(cr *CommentReporter, jobs []scheduler.Job) error {
199+
func ReportLayersTableForJobs(cr *CommentReporter, jobs []scheduler.Job) error {
194200
prNumber := cr.PrNumber
195201
prService := cr.PrService
196202
commentId := cr.CommentId
197203

198-
slog.Info("Reporting no projects impacted",
204+
// sort jobs by layer for better display (sort by name too)
205+
sort.Slice(jobs, func(i, j int) bool {
206+
if jobs[i].Layer == jobs[j].Layer {
207+
return jobs[i].ProjectName < jobs[j].ProjectName
208+
}
209+
return jobs[i].Layer < jobs[j].Layer
210+
})
211+
212+
slog.Info("Reporting initial jobs status",
199213
"prNumber", prNumber,
200214
"commentId", commentId,
201215
"jobCount", len(jobs),
202216
)
203217

204-
message := "" +
205-
":construction_worker: The following projects are impacted\n\n"
206-
for _, job := range jobs {
207-
message = message + fmt.Sprintf(""+
208-
"<!-- PROJECTHOLDER %v -->\n"+
209-
":clock11: **%v**: pending...\n"+
210-
"<!-- PROJECTHOLDEREND %v -->\n"+
211-
"", job.ProjectName, job.ProjectName, job.ProjectName)
212-
213-
slog.Debug("Added project placeholder to message", "projectName", job.ProjectName)
218+
message := ""
219+
if len(jobs) == 0 {
220+
message = message + ":construction_worker: No projects impacted"
221+
} else {
222+
message = message + fmt.Sprintf("| Project | Layer |\n")
223+
message = message + fmt.Sprintf("|---------|--------|\n")
224+
for _, job := range jobs {
225+
message = message + fmt.Sprintf(""+
226+
"|:clock11: **%v**|%v|\n", job.ProjectName, job.Layer)
227+
}
214228
}
215229

230+
message += "----------------\n\n"
231+
message += `
232+
<details>
233+
<summary>Instructions</summary>
234+
235+
Since you enabled layers in your configuration, you can proceed to perform a layer-by-layer deployment.
236+
To start planning the first layer you can run "digger plan --layer 0". To apply the first layer, run "digger apply --layer 0".
237+
238+
To deploy the next layer, run "digger plan --layer 1". To apply the next layer, run "digger apply --layer 1".
239+
240+
And so on. A new commit on the branch will restart this deployment process.
241+
</details>
242+
`
243+
message = trimMessageIfExceedsMaxLength(message)
216244
err := prService.EditComment(prNumber, commentId, message)
217245
if err != nil {
218-
slog.Error("Failed to update comment with no projects impacted message",
246+
slog.Error("Failed to update comment with initial jobs status",
219247
"prNumber", prNumber,
220248
"commentId", commentId,
221249
"error", err,
222250
)
223251
return err
224252
}
225253

226-
slog.Debug("Successfully reported no projects impacted", "prNumber", prNumber, "commentId", commentId)
254+
slog.Debug("Successfully reported initial jobs status", "prNumber", prNumber, "commentId", commentId)
227255
return nil
228256
}

cli/cmd/digger/main_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ func TestGitHubNewCommentContext(t *testing.T) {
921921
lock := &locking.MockLock{}
922922
prManager := ci.MockPullRequestManager{ChangedFiles: []string{"dev/test.tf"}}
923923
planStorage := storage.MockPlanStorage{}
924-
impactedProjects, requestedProject, prNumber, err := dggithub.ProcessGitHubEvent(ghEvent, &diggerConfig, &prManager)
924+
impactedProjects, _, prNumber, err := dggithub.ProcessGitHubEvent(ghEvent, &diggerConfig, &prManager)
925925
reporter := &reporting.CiReporter{
926926
CiService: &prManager,
927927
PrNumber: prNumber,
@@ -930,7 +930,7 @@ func TestGitHubNewCommentContext(t *testing.T) {
930930
policyChecker := policy.MockPolicyChecker{}
931931
backendApi := backendapi.MockBackendApi{}
932932

933-
jobs, _, err := generic.ConvertIssueCommentEventToJobs("", "", 0, "", impactedProjects, requestedProject, map[string]configuration.Workflow{}, "prbranch", "")
933+
jobs, _, err := generic.ConvertIssueCommentEventToJobs("", "", 0, "", impactedProjects, impactedProjects, map[string]configuration.Workflow{}, "prbranch", "")
934934
_, _, err = digger.RunJobs(jobs, &prManager, prManager, lock, reporter, &planStorage, policyChecker, comment_updater.NoopCommentUpdater{}, backendApi, "123", false, false, "1", "")
935935
assert.NoError(t, err)
936936
if err != nil {
@@ -1020,10 +1020,9 @@ func TestGitHubTestPRCommandCaseInsensitivity(t *testing.T) {
10201020
var impactedProjects []configuration.Project
10211021
impactedProjects = make([]configuration.Project, 1)
10221022
impactedProjects[0] = project
1023-
var requestedProject = project
10241023
workflows := make(map[string]configuration.Workflow, 1)
10251024
workflows["default"] = configuration.Workflow{}
1026-
jobs, _, err := generic.ConvertIssueCommentEventToJobs("", "", 0, "digger plan", impactedProjects, &requestedProject, workflows, "prbranch", "main")
1025+
jobs, _, err := generic.ConvertIssueCommentEventToJobs("", "", 0, "digger plan", impactedProjects, impactedProjects, workflows, "prbranch", "main")
10271026

10281027
assert.Equal(t, 1, len(jobs))
10291028
assert.Equal(t, "digger plan", jobs[0].Commands[0])

cli/pkg/github/github.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,14 @@ func GitHubCI(lock core_locking.Lock, policyCheckerProvider core_policy.PolicyCh
302302
repoFullName := *commentEvent.Repo.FullName
303303
requestedBy := *commentEvent.Sender.Login
304304
commentBody := *commentEvent.Comment.Body
305-
jobs, coversAllImpactedProjects, err = generic.ConvertIssueCommentEventToJobs(repoFullName, requestedBy, prNumber, commentBody, impactedProjects, requestedProject, diggerConfig.Workflows, prBranchName, defaultBranch)
305+
306+
var impactedProjectsForEvent []digger_config.Project
307+
if requestedProject != nil {
308+
impactedProjectsForEvent = []digger_config.Project{*requestedProject}
309+
} else {
310+
impactedProjectsForEvent = impactedProjects
311+
}
312+
jobs, coversAllImpactedProjects, err = generic.ConvertIssueCommentEventToJobs(repoFullName, requestedBy, prNumber, commentBody, impactedProjectsForEvent, impactedProjects, diggerConfig.Workflows, prBranchName, defaultBranch)
306313
} else {
307314
usage.ReportErrorAndExit(githubActor, fmt.Sprintf("Unsupported GitHub event type. %s", err), 6)
308315
}

0 commit comments

Comments
 (0)