Skip to content

Commit e213714

Browse files
authored
Merge pull request #1705 from diggerhq/feat/ee-gh-drift-handle-reconciliation
Feat/ee gh drift handle reconciliation
2 parents 8c13c7c + 2027648 commit e213714

File tree

6 files changed

+294
-15
lines changed

6 files changed

+294
-15
lines changed

backend/controllers/github.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ import (
3939
"golang.org/x/oauth2"
4040
)
4141

42+
type IssueCommentHook func(gh utils.GithubClientProvider, payload *github.IssueCommentEvent, ciBackendProvider ci_backends.CiBackendProvider) error
43+
4244
type DiggerController struct {
43-
CiBackendProvider ci_backends.CiBackendProvider
44-
GithubClientProvider utils.GithubClientProvider
45+
CiBackendProvider ci_backends.CiBackendProvider
46+
GithubClientProvider utils.GithubClientProvider
47+
GithubWebhookPostIssueCommentHooks []IssueCommentHook
4548
}
4649

4750
func (d DiggerController) GithubAppWebHook(c *gin.Context) {
@@ -89,6 +92,16 @@ func (d DiggerController) GithubAppWebHook(c *gin.Context) {
8992
c.String(http.StatusInternalServerError, err.Error())
9093
return
9194
}
95+
96+
log.Printf("executing issue comment event post hooks:")
97+
for _, hook := range d.GithubWebhookPostIssueCommentHooks {
98+
err := hook(gh, event, d.CiBackendProvider)
99+
if err != nil {
100+
log.Printf("handleIssueCommentEvent post hook error: %v", err)
101+
c.String(http.StatusInternalServerError, err.Error())
102+
return
103+
}
104+
}
92105
case *github.PullRequestEvent:
93106
log.Printf("Got pull request event for %d", *event.PullRequest.ID)
94107
err := handlePullRequestEvent(gh, event, d.CiBackendProvider)
@@ -599,7 +612,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
599612
return nil
600613
}
601614

602-
func getDiggerConfigForBranch(gh utils.GithubClientProvider, installationId int64, repoFullName string, repoOwner string, repoName string, cloneUrl string, branch string, prNumber int) (string, *dg_github.GithubService, *dg_configuration.DiggerConfig, graph.Graph[string, dg_configuration.Project], error) {
615+
func GetDiggerConfigForBranch(gh utils.GithubClientProvider, installationId int64, repoFullName string, repoOwner string, repoName string, cloneUrl string, branch string, changedFiles []string) (string, *dg_github.GithubService, *dg_configuration.DiggerConfig, graph.Graph[string, dg_configuration.Project], error) {
603616
ghService, token, err := utils.GetGithubService(gh, installationId, repoFullName, repoOwner, repoName)
604617
if err != nil {
605618
log.Printf("Error getting github service: %v", err)
@@ -610,11 +623,6 @@ func getDiggerConfigForBranch(gh utils.GithubClientProvider, installationId int6
610623
var diggerYmlStr string
611624
var dependencyGraph graph.Graph[string, dg_configuration.Project]
612625

613-
changedFiles, err := ghService.GetChangedFiles(prNumber)
614-
if err != nil {
615-
log.Printf("Error getting changed files: %v", err)
616-
return "", nil, nil, nil, fmt.Errorf("error getting changed files")
617-
}
618626
err = utils.CloneGitRepoAndDoAction(cloneUrl, branch, *token, func(dir string) error {
619627
diggerYmlBytes, err := os.ReadFile(path.Join(dir, "digger.yml"))
620628
diggerYmlStr = string(diggerYmlBytes)
@@ -649,7 +657,13 @@ func getDiggerConfigForPR(gh utils.GithubClientProvider, installationId int64, r
649657
return "", nil, nil, nil, nil, nil, fmt.Errorf("error getting branch name")
650658
}
651659

652-
diggerYmlStr, ghService, config, dependencyGraph, err := getDiggerConfigForBranch(gh, installationId, repoFullName, repoOwner, repoName, cloneUrl, prBranch, prNumber)
660+
changedFiles, err := ghService.GetChangedFiles(prNumber)
661+
if err != nil {
662+
log.Printf("Error getting changed files: %v", err)
663+
return "", nil, nil, nil, nil, nil, fmt.Errorf("error getting changed files")
664+
}
665+
666+
diggerYmlStr, ghService, config, dependencyGraph, err := GetDiggerConfigForBranch(gh, installationId, repoFullName, repoOwner, repoName, cloneUrl, prBranch, changedFiles)
653667
if err != nil {
654668
log.Printf("Error loading digger.yml: %v", err)
655669
return "", nil, nil, nil, nil, nil, fmt.Errorf("error loading digger.yml")

backend/controllers/projects_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ func TestAutomergeWhenBatchIsSuccessfulStatus(t *testing.T) {
1717
defer teardownSuite(t)
1818
isMergeCalled := false
1919
mockedHTTPClient := mock.NewMockedHTTPClient(
20+
mock.WithRequestMatch(
21+
mock.GetReposIssuesByOwnerByRepoByIssueNumber,
22+
github.Issue{
23+
Number: github.Int(2),
24+
PullRequestLinks: &github.PullRequestLinks{},
25+
}),
2026
mock.WithRequestMatch(
2127
mock.GetReposPullsByOwnerByRepoByPullNumber,
2228
github.PullRequest{

backend/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ var templates embed.FS
1515

1616
func main() {
1717
ghController := controllers.DiggerController{
18-
CiBackendProvider: ci_backends.DefaultBackendProvider{},
19-
GithubClientProvider: utils.DiggerGithubRealClientProvider{},
18+
CiBackendProvider: ci_backends.DefaultBackendProvider{},
19+
GithubClientProvider: utils.DiggerGithubRealClientProvider{},
20+
GithubWebhookPostIssueCommentHooks: make([]controllers.IssueCommentHook, 0),
2021
}
2122
r := bootstrap.Bootstrap(templates, ghController)
2223
r.GET("/", controllers.Home)

ee/backend/hooks/github.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package hooks
2+
3+
import (
4+
"fmt"
5+
"github.com/diggerhq/digger/backend/ci_backends"
6+
ce_controllers "github.com/diggerhq/digger/backend/controllers"
7+
"github.com/diggerhq/digger/backend/locking"
8+
"github.com/diggerhq/digger/backend/models"
9+
"github.com/diggerhq/digger/backend/utils"
10+
"github.com/diggerhq/digger/libs/ci/generic"
11+
dg_github "github.com/diggerhq/digger/libs/ci/github"
12+
comment_updater "github.com/diggerhq/digger/libs/comment_utils/reporting"
13+
"github.com/diggerhq/digger/libs/digger_config"
14+
dg_locking "github.com/diggerhq/digger/libs/locking"
15+
"github.com/diggerhq/digger/libs/scheduler"
16+
"github.com/google/go-github/v61/github"
17+
"github.com/samber/lo"
18+
"log"
19+
"regexp"
20+
"strconv"
21+
"strings"
22+
)
23+
24+
var DriftReconcilliationHook ce_controllers.IssueCommentHook = func(gh utils.GithubClientProvider, payload *github.IssueCommentEvent, ciBackendProvider ci_backends.CiBackendProvider) error {
25+
log.Printf("handling the drift reconcilliation hook")
26+
installationId := *payload.Installation.ID
27+
repoName := *payload.Repo.Name
28+
repoOwner := *payload.Repo.Owner.Login
29+
repoFullName := *payload.Repo.FullName
30+
cloneURL := *payload.Repo.CloneURL
31+
issueTitle := *payload.Issue.Title
32+
issueNumber := *payload.Issue.Number
33+
userCommentId := *payload.GetComment().ID
34+
actor := *payload.Sender.Login
35+
commentBody := *payload.Comment.Body
36+
defaultBranch := *payload.Repo.DefaultBranch
37+
isPullRequest := payload.Issue.IsPullRequest()
38+
39+
if isPullRequest {
40+
log.Printf("Comment is not an issue, ignoring")
41+
return nil
42+
}
43+
44+
// checking that the title of the issue matches regex
45+
var projectName string
46+
re := regexp.MustCompile(`^Drift detected in project:\s*(\S+)`)
47+
matches := re.FindStringSubmatch(issueTitle)
48+
if len(matches) > 1 {
49+
projectName = matches[1]
50+
} else {
51+
log.Printf("does not look like a drift issue, ignoring")
52+
}
53+
54+
link, err := models.DB.GetGithubAppInstallationLink(installationId)
55+
if err != nil {
56+
log.Printf("Error getting GetGithubAppInstallationLink: %v", err)
57+
return fmt.Errorf("error getting github app link")
58+
}
59+
orgId := link.OrganisationId
60+
61+
if *payload.Action != "created" {
62+
log.Printf("comment is not of type 'created', ignoring")
63+
return nil
64+
}
65+
66+
allowedCommands := []string{"digger apply", "digger unlock"}
67+
if !lo.Contains(allowedCommands, strings.TrimSpace(*payload.Comment.Body)) {
68+
log.Printf("comment is not in allowed commands, ignoring")
69+
log.Printf("allowed commands: %v", allowedCommands)
70+
return nil
71+
}
72+
73+
diggerYmlStr, ghService, config, projectsGraph, err := ce_controllers.GetDiggerConfigForBranch(gh, installationId, repoFullName, repoOwner, repoName, cloneURL, defaultBranch, nil)
74+
if err != nil {
75+
log.Printf("Error loading digger.yml: %v", err)
76+
return fmt.Errorf("error loading digger.yml")
77+
}
78+
79+
commentIdStr := strconv.FormatInt(userCommentId, 10)
80+
err = ghService.CreateCommentReaction(commentIdStr, string(dg_github.GithubCommentEyesReaction))
81+
if err != nil {
82+
log.Printf("CreateCommentReaction error: %v", err)
83+
}
84+
85+
diggerCommand, err := scheduler.GetCommandFromComment(*payload.Comment.Body)
86+
if err != nil {
87+
log.Printf("unkown digger command in comment: %v", *payload.Comment.Body)
88+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: Could not recognise comment, error: %v", err))
89+
return fmt.Errorf("unkown digger command in comment %v", err)
90+
}
91+
92+
// attempting to lock for performing drift apply command
93+
prLock := dg_locking.PullRequestLock{
94+
InternalLock: locking.BackendDBLock{
95+
OrgId: orgId,
96+
},
97+
CIService: ghService,
98+
Reporter: comment_updater.NoopReporter{},
99+
ProjectName: projectName,
100+
ProjectNamespace: repoFullName,
101+
PrNumber: issueNumber,
102+
}
103+
err = dg_locking.PerformLockingActionFromCommand(prLock, *diggerCommand)
104+
if err != nil {
105+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: Failed perform lock action on project: %v %v", projectName, err))
106+
return fmt.Errorf("failed perform lock action on project: %v %v", projectName, err)
107+
}
108+
109+
if *diggerCommand == scheduler.DiggerCommandUnlock {
110+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":white_check_mark: Command %v completed successfully", *diggerCommand))
111+
return nil
112+
}
113+
114+
// === if we get here its a "digger apply command and we are already locked for this project ====
115+
// perform apply here then unlock the project
116+
commentReporter, err := utils.InitCommentReporter(ghService, issueNumber, ":construction_worker: Digger starting....")
117+
if err != nil {
118+
log.Printf("Error initializing comment reporter: %v", err)
119+
return fmt.Errorf("error initializing comment reporter")
120+
}
121+
122+
impactedProjects := config.GetProjects(projectName)
123+
jobs, _, err := generic.ConvertIssueCommentEventToJobs(repoFullName, actor, issueNumber, commentBody, impactedProjects, nil, config.Workflows, defaultBranch, defaultBranch)
124+
if err != nil {
125+
log.Printf("Error converting event to jobs: %v", err)
126+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: Error converting event to jobs: %v", err))
127+
return fmt.Errorf("error converting event to jobs")
128+
}
129+
log.Printf("GitHub IssueComment event converted to Jobs successfully\n")
130+
131+
err = utils.ReportInitialJobsStatus(commentReporter, jobs)
132+
if err != nil {
133+
log.Printf("Failed to comment initial status for jobs: %v", err)
134+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: Failed to comment initial status for jobs: %v", err))
135+
return fmt.Errorf("failed to comment initial status for jobs")
136+
}
137+
138+
impactedProjectsMap := make(map[string]digger_config.Project)
139+
for _, p := range impactedProjects {
140+
impactedProjectsMap[p.Name] = p
141+
}
142+
143+
impactedProjectsJobMap := make(map[string]scheduler.Job)
144+
for _, j := range jobs {
145+
impactedProjectsJobMap[j.ProjectName] = j
146+
}
147+
148+
reporterCommentId, err := strconv.ParseInt(commentReporter.CommentId, 10, 64)
149+
if err != nil {
150+
log.Printf("strconv.ParseInt error: %v", err)
151+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: could not handle commentId: %v", err))
152+
}
153+
154+
batchId, _, err := utils.ConvertJobsToDiggerJobs(*diggerCommand, "github", orgId, impactedProjectsJobMap, impactedProjectsMap, projectsGraph, installationId, defaultBranch, issueNumber, repoOwner, repoName, repoFullName, "", reporterCommentId, diggerYmlStr, 0)
155+
if err != nil {
156+
log.Printf("ConvertJobsToDiggerJobs error: %v", err)
157+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: ConvertJobsToDiggerJobs error: %v", err))
158+
return fmt.Errorf("error convertingjobs")
159+
}
160+
161+
ciBackend, err := ciBackendProvider.GetCiBackend(
162+
ci_backends.CiBackendOptions{
163+
GithubClientProvider: gh,
164+
GithubInstallationId: installationId,
165+
RepoName: repoName,
166+
RepoOwner: repoOwner,
167+
RepoFullName: repoFullName,
168+
},
169+
)
170+
if err != nil {
171+
log.Printf("GetCiBackend error: %v", err)
172+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: GetCiBackend error: %v", err))
173+
return fmt.Errorf("error fetching ci backed %v", err)
174+
}
175+
176+
err = ce_controllers.TriggerDiggerJobs(ciBackend, repoFullName, repoOwner, repoName, batchId, issueNumber, ghService, gh)
177+
if err != nil {
178+
log.Printf("TriggerDiggerJobs error: %v", err)
179+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: TriggerDiggerJobs error: %v", err))
180+
return fmt.Errorf("error triggerring Digger Jobs")
181+
}
182+
183+
// === now unlocking the project ===
184+
err = dg_locking.PerformLockingActionFromCommand(prLock, scheduler.DiggerCommandUnlock)
185+
if err != nil {
186+
utils.InitCommentReporter(ghService, issueNumber, fmt.Sprintf(":x: Failed perform lock action on project: %v %v", projectName, err))
187+
return fmt.Errorf("failed perform lock action on project: %v %v", projectName, err)
188+
}
189+
190+
return nil
191+
}

ee/backend/main.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/diggerhq/digger/backend/utils"
1111
ci_backends2 "github.com/diggerhq/digger/ee/backend/ci_backends"
1212
"github.com/diggerhq/digger/ee/backend/controllers"
13+
"github.com/diggerhq/digger/ee/backend/hooks"
1314
"github.com/diggerhq/digger/ee/backend/providers/github"
1415
"github.com/diggerhq/digger/libs/license"
1516
"github.com/gin-gonic/gin"
@@ -31,8 +32,9 @@ func main() {
3132
os.Exit(1)
3233
}
3334
diggerController := ce_controllers.DiggerController{
34-
CiBackendProvider: ci_backends2.EEBackendProvider{},
35-
GithubClientProvider: github.DiggerGithubEEClientProvider{},
35+
CiBackendProvider: ci_backends2.EEBackendProvider{},
36+
GithubClientProvider: github.DiggerGithubEEClientProvider{},
37+
GithubWebhookPostIssueCommentHooks: []ce_controllers.IssueCommentHook{hooks.DriftReconcilliationHook},
3638
}
3739

3840
r := bootstrap.Bootstrap(templates, diggerController)
@@ -92,7 +94,7 @@ func main() {
9294
jobArtefactsGroup.Use(middleware.GetApiMiddleware())
9395
jobArtefactsGroup.PUT("/", controllers.SetJobArtefact)
9496
jobArtefactsGroup.GET("/", controllers.DownloadJobArtefact)
95-
97+
9698
port := config.GetPort()
9799
r.Run(fmt.Sprintf(":%d", port))
98100
}

0 commit comments

Comments
 (0)