Skip to content

Commit 1d3bc41

Browse files
committed
Merge branch 'develop' into feat/support-single-comment-summary
# Conflicts: # backend/controllers/github.go # backend/migrations/atlas.sum # backend/models/scheduler.go # backend/models/storage.go # backend/services/spec.go # backend/utils/graphs.go # ee/backend/controllers/gitlab.go # ee/backend/hooks/github.go
2 parents 1a48f98 + d19d04d commit 1d3bc41

31 files changed

+937
-799
lines changed

action.yml

+3-5
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,9 @@ runs:
428428
name: cache-save
429429
if: ${{ always() && inputs.cache-dependencies == 'true' && steps.restore_cache.outputs.cache-hit != 'true' }}
430430
with:
431-
path: |
432-
${{ steps.golang-env.outputs.build-cache-path }}
433-
${{ steps.golang-env.outputs.module-cache-path }}
434-
key: digger-cli-cache-${{ hashFiles('.digger.go.sum') }}
435-
431+
path: ${{ github.workspace }}/cache
432+
key: digger-cache-${{ hashFiles('**/cache') }}
433+
436434
branding:
437435
icon: globe
438436
color: purple

backend/controllers/github.go

+222-14
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,34 @@ import (
1010
"github.com/diggerhq/digger/backend/ci_backends"
1111
config2 "github.com/diggerhq/digger/backend/config"
1212
"github.com/diggerhq/digger/backend/locking"
13+
"github.com/diggerhq/digger/backend/middleware"
14+
"github.com/diggerhq/digger/backend/models"
1315
"github.com/diggerhq/digger/backend/segment"
1416
"github.com/diggerhq/digger/backend/services"
17+
"github.com/diggerhq/digger/backend/utils"
1518
"github.com/diggerhq/digger/libs/ci"
1619
"github.com/diggerhq/digger/libs/ci/generic"
20+
dg_github "github.com/diggerhq/digger/libs/ci/github"
1721
comment_updater "github.com/diggerhq/digger/libs/comment_utils/reporting"
22+
dg_configuration "github.com/diggerhq/digger/libs/digger_config"
1823
dg_locking "github.com/diggerhq/digger/libs/locking"
1924
orchestrator_scheduler "github.com/diggerhq/digger/libs/scheduler"
25+
"github.com/dominikbraun/graph"
26+
"github.com/gin-gonic/gin"
27+
"github.com/google/go-github/v61/github"
2028
"github.com/google/uuid"
29+
"github.com/samber/lo"
30+
"golang.org/x/oauth2"
2131
"gorm.io/gorm"
2232
"log"
2333
"math/rand"
2434
"net/http"
2535
"net/url"
2636
"os"
27-
"path"
2837
"reflect"
2938
"slices"
3039
"strconv"
3140
"strings"
32-
33-
"github.com/diggerhq/digger/backend/middleware"
34-
"github.com/diggerhq/digger/backend/models"
35-
"github.com/diggerhq/digger/backend/utils"
36-
dg_github "github.com/diggerhq/digger/libs/ci/github"
37-
dg_configuration "github.com/diggerhq/digger/libs/digger_config"
38-
"github.com/dominikbraun/graph"
39-
"github.com/gin-gonic/gin"
40-
"github.com/google/go-github/v61/github"
41-
"github.com/samber/lo"
42-
"golang.org/x/oauth2"
4341
)
4442

4543
type IssueCommentHook func(gh utils.GithubClientProvider, payload *github.IssueCommentEvent, ciBackendProvider ci_backends.CiBackendProvider) error
@@ -309,6 +307,16 @@ func handleInstallationDeletedEvent(installation *github.InstallationEvent, appI
309307
}
310308

311309
func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullRequestEvent, ciBackendProvider ci_backends.CiBackendProvider, appId int64) error {
310+
defer func() {
311+
if r := recover(); r != nil {
312+
log.Printf("Recovered from panic in handlePullRequestEvent handler: %v", r)
313+
log.Printf("\n=== PANIC RECOVERED ===\n")
314+
log.Printf("Error: %v\n", r)
315+
log.Printf("Stack Trace:\n%s", string(debug.Stack()))
316+
log.Printf("=== END PANIC ===\n")
317+
}
318+
}()
319+
312320
installationId := *payload.Installation.ID
313321
repoName := *payload.Repo.Name
314322
repoOwner := *payload.Repo.Owner.Login
@@ -376,6 +384,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
376384
diggerYmlStr, ghService, config, projectsGraph, _, _, changedFiles, err := getDiggerConfigForPR(gh, organisationId, prLabelsStr, installationId, repoFullName, repoOwner, repoName, cloneURL, prNumber)
377385
if err != nil {
378386
log.Printf("getDiggerConfigForPR error: %v", err)
387+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Error loading digger config: %v", err))
379388
return fmt.Errorf("error getting digger config")
380389
}
381390

@@ -501,6 +510,19 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
501510
commentReporterManager.UpdateComment(fmt.Sprintf(":x: could not handle commentId: %v", err))
502511
}
503512

513+
var aiSummaryCommentId = ""
514+
if config.Reporting.AiSummary {
515+
aiSummaryComment, err := ghService.PublishComment(prNumber, "AI Summary will be posted here after completion")
516+
if err != nil {
517+
log.Printf("could not post ai summary comment: %v", err)
518+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: could not post ai comment summary comment id: %v", err))
519+
return fmt.Errorf("could not post ai summary comment: %v", err)
520+
}
521+
aiSummaryCommentId = aiSummaryComment.Id
522+
}
523+
524+
batchId, _, err := utils.ConvertJobsToDiggerJobs(*diggerCommand, models.DiggerVCSGithub, organisationId, impactedJobsMap, impactedProjectsMap, projectsGraph, installationId, branch, prNumber, repoOwner, repoName, repoFullName, commitSha, commentId, diggerYmlStr, 0, aiSummaryCommentId, config.ReportTerraformOutputs)
525+
504526
placeholderComment, err := ghService.PublishComment(prNumber, "digger report placehoder")
505527
if err != nil {
506528
log.Printf("strconv.ParseInt error: %v", err)
@@ -582,8 +604,11 @@ func GetDiggerConfigForBranch(gh utils.GithubClientProvider, installationId int6
582604
var dependencyGraph graph.Graph[string, dg_configuration.Project]
583605

584606
err = utils.CloneGitRepoAndDoAction(cloneUrl, branch, "", *token, func(dir string) error {
585-
diggerYmlBytes, err := os.ReadFile(path.Join(dir, "digger.yml"))
586-
diggerYmlStr = string(diggerYmlBytes)
607+
diggerYmlStr, err = dg_configuration.ReadDiggerYmlFileContents(dir)
608+
if err != nil {
609+
log.Printf("could not load digger config: %v", err)
610+
return err
611+
}
587612
config, _, dependencyGraph, err = dg_configuration.LoadDiggerConfig(dir, true, changedFiles)
588613
if err != nil {
589614
log.Printf("Error loading digger config: %v", err)
@@ -692,6 +717,16 @@ func getBatchType(jobs []orchestrator_scheduler.Job) orchestrator_scheduler.Digg
692717
}
693718

694719
func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.IssueCommentEvent, ciBackendProvider ci_backends.CiBackendProvider, appId int64, postCommentHooks []IssueCommentHook) error {
720+
defer func() {
721+
if r := recover(); r != nil {
722+
log.Printf("Recovered from panic in handleIssueCommentEvent handler: %v", r)
723+
log.Printf("\n=== PANIC RECOVERED ===\n")
724+
log.Printf("Error: %v\n", r)
725+
log.Printf("Stack Trace:\n%s", string(debug.Stack()))
726+
log.Printf("=== END PANIC ===\n")
727+
}
728+
}()
729+
695730
installationId := *payload.Installation.ID
696731
repoName := *payload.Repo.Name
697732
repoOwner := *payload.Repo.Owner.Login
@@ -752,6 +787,15 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
752787
return fmt.Errorf("error getting digger config")
753788
}
754789

790+
// terraform code generator
791+
if os.Getenv("DIGGER_GENERATION_ENABLED") == "1" {
792+
err = GenerateTerraformFromCode(payload, commentReporterManager, config, defaultBranch, ghService, repoOwner, repoName, commitSha, issueNumber, branch)
793+
if err != nil {
794+
log.Printf("terraform generation failed: %v", err)
795+
return err
796+
}
797+
}
798+
755799
commentIdStr := strconv.FormatInt(userCommentId, 10)
756800
err = ghService.CreateCommentReaction(commentIdStr, string(dg_github.GithubCommentEyesReaction))
757801
if err != nil {
@@ -883,6 +927,18 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
883927
return fmt.Errorf("comment reporter error: %v", err)
884928
}
885929

930+
var aiSummaryCommentId = ""
931+
if config.Reporting.AiSummary {
932+
aiSummaryComment, err := ghService.PublishComment(issueNumber, "AI Summary will be posted here after completion")
933+
if err != nil {
934+
log.Printf("could not post ai summary comment: %v", err)
935+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: could not post ai comment summary comment id: %v", err))
936+
return fmt.Errorf("could not post ai summary comment: %v", err)
937+
}
938+
aiSummaryCommentId = aiSummaryComment.Id
939+
}
940+
941+
batchId, _, err := utils.ConvertJobsToDiggerJobs(*diggerCommand, "github", orgId, impactedProjectsJobMap, impactedProjectsMap, projectsGraph, installationId, *branch, issueNumber, repoOwner, repoName, repoFullName, *commitSha, reporterCommentId, diggerYmlStr, 0, aiSummaryCommentId, config.ReportTerraformOutputs)
886942
placeholderComment, err := ghService.PublishComment(issueNumber, "digger report placehoder")
887943
if err != nil {
888944
log.Printf("strconv.ParseInt error: %v", err)
@@ -962,6 +1018,158 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
9621018
return nil
9631019
}
9641020

1021+
func GenerateTerraformFromCode(payload *github.IssueCommentEvent, commentReporterManager utils.CommentReporterManager, config *dg_configuration.DiggerConfig, defaultBranch string, ghService *dg_github.GithubService, repoOwner string, repoName string, commitSha *string, issueNumber int, branch *string) error {
1022+
if strings.HasPrefix(*payload.Comment.Body, "digger generate") {
1023+
projectName := ci.ParseProjectName(*payload.Comment.Body)
1024+
if projectName == "" {
1025+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: generate requires argument -p <project_name>"))
1026+
log.Printf("missing project in command: %v", *payload.Comment.Body)
1027+
return fmt.Errorf("generate requires argument -p <project_name>")
1028+
}
1029+
1030+
project := config.GetProject(projectName)
1031+
if project == nil {
1032+
commentReporterManager.UpdateComment(fmt.Sprintf("could not find project %v in digger.yml", projectName))
1033+
log.Printf("could not find project %v in digger.yml", projectName)
1034+
return fmt.Errorf("could not find project %v in digger.yml", projectName)
1035+
}
1036+
1037+
commentReporterManager.UpdateComment(fmt.Sprintf(":white_check_mark: Successfully loaded project"))
1038+
1039+
generationEndpoint := os.Getenv("DIGGER_GENERATION_ENDPOINT")
1040+
if generationEndpoint == "" {
1041+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: server does not have generation endpoint configured, please verify"))
1042+
log.Printf("server does not have generation endpoint configured, please verify")
1043+
return fmt.Errorf("server does not have generation endpoint configured, please verify")
1044+
}
1045+
apiToken := os.Getenv("DIGGER_GENERATION_API_TOKEN")
1046+
1047+
// Get all code content from the repository at a specific commit
1048+
getCodeFromCommit := func(ghService *dg_github.GithubService, repoOwner, repoName string, commitSha *string, projectDir string) (string, error) {
1049+
const MaxPatchSize = 1024 * 1024 // 1MB limit
1050+
1051+
// Get the commit's changes compared to default branch
1052+
comparison, _, err := ghService.Client.Repositories.CompareCommits(
1053+
context.Background(),
1054+
repoOwner,
1055+
repoName,
1056+
defaultBranch,
1057+
*commitSha,
1058+
nil,
1059+
)
1060+
if err != nil {
1061+
return "", fmt.Errorf("error comparing commits: %v", err)
1062+
}
1063+
1064+
var appCode strings.Builder
1065+
for _, file := range comparison.Files {
1066+
if file.Patch == nil {
1067+
continue // Skip files without patches
1068+
}
1069+
log.Printf("Processing patch for file: %s", *file.Filename)
1070+
if *file.Additions > 0 {
1071+
lines := strings.Split(*file.Patch, "\n")
1072+
for _, line := range lines {
1073+
if strings.HasPrefix(line, "+") && !strings.HasPrefix(line, "+++") {
1074+
appCode.WriteString(strings.TrimPrefix(line, "+"))
1075+
appCode.WriteString("\n")
1076+
}
1077+
}
1078+
}
1079+
appCode.WriteString("\n")
1080+
}
1081+
1082+
if appCode.Len() == 0 {
1083+
return "", fmt.Errorf("no code changes found in commit %s. Please ensure the PR contains added or modified code", *commitSha)
1084+
}
1085+
1086+
return appCode.String(), nil
1087+
}
1088+
1089+
appCode, err := getCodeFromCommit(ghService, repoOwner, repoName, commitSha, project.Dir)
1090+
if err != nil {
1091+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to get code content: %v", err))
1092+
log.Printf("Error getting code content: %v", err)
1093+
return fmt.Errorf("error getting code content: %v", err)
1094+
}
1095+
1096+
commentReporterManager.UpdateComment(fmt.Sprintf(":white_check_mark: Successfully loaded code from commit"))
1097+
1098+
log.Printf("the app code is: %v", appCode)
1099+
1100+
commentReporterManager.UpdateComment(fmt.Sprintf("Generating terraform..."))
1101+
terraformCode, err := utils.GenerateTerraformCode(appCode, generationEndpoint, apiToken)
1102+
if err != nil {
1103+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: could not generate terraform code: %v", err))
1104+
log.Printf("could not generate terraform code: %v", err)
1105+
return fmt.Errorf("could not generate terraform code: %v", err)
1106+
}
1107+
1108+
commentReporterManager.UpdateComment(fmt.Sprintf(":white_check_mark: Generated terraform"))
1109+
1110+
// comment terraform code to project dir
1111+
//project.Dir
1112+
log.Printf("terraform code is %v", terraformCode)
1113+
1114+
baseTree, _, err := ghService.Client.Git.GetTree(context.Background(), repoOwner, repoName, *commitSha, false)
1115+
if err != nil {
1116+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to get base tree: %v", err))
1117+
log.Printf("Error getting base tree: %v", err)
1118+
return fmt.Errorf("error getting base tree: %v", err)
1119+
}
1120+
1121+
// Create a new tree with the new file
1122+
treeEntries := []*github.TreeEntry{
1123+
{
1124+
Path: github.String(filepath.Join(project.Dir, fmt.Sprintf("generated_%v.tf", issueNumber))),
1125+
Mode: github.String("100644"),
1126+
Type: github.String("blob"),
1127+
Content: github.String(terraformCode),
1128+
},
1129+
}
1130+
1131+
newTree, _, err := ghService.Client.Git.CreateTree(context.Background(), repoOwner, repoName, *baseTree.SHA, treeEntries)
1132+
if err != nil {
1133+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to create new tree: %v", err))
1134+
log.Printf("Error creating new tree: %v", err)
1135+
return fmt.Errorf("error creating new tree: %v", err)
1136+
}
1137+
1138+
// Create the commit
1139+
commitMsg := fmt.Sprintf("Add generated Terraform code for %v", projectName)
1140+
commit := &github.Commit{
1141+
Message: &commitMsg,
1142+
Tree: newTree,
1143+
Parents: []*github.Commit{{SHA: commitSha}},
1144+
}
1145+
1146+
newCommit, _, err := ghService.Client.Git.CreateCommit(context.Background(), repoOwner, repoName, commit, nil)
1147+
if err != nil {
1148+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to commit Terraform file: %v", err))
1149+
log.Printf("Error committing Terraform file: %v", err)
1150+
return fmt.Errorf("error committing Terraform file: %v", err)
1151+
}
1152+
1153+
// Update the reference to point to the new commit
1154+
ref := &github.Reference{
1155+
Ref: github.String(fmt.Sprintf("refs/heads/%s", *branch)),
1156+
Object: &github.GitObject{
1157+
SHA: newCommit.SHA,
1158+
},
1159+
}
1160+
_, _, err = ghService.Client.Git.UpdateRef(context.Background(), repoOwner, repoName, ref, false)
1161+
if err != nil {
1162+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to update branch reference: %v", err))
1163+
log.Printf("Error updating branch reference: %v", err)
1164+
return fmt.Errorf("error updating branch reference: %v", err)
1165+
}
1166+
1167+
commentReporterManager.UpdateComment(":white_check_mark: Successfully generated and committed Terraform code")
1168+
return nil
1169+
}
1170+
return nil
1171+
}
1172+
9651173
func TriggerDiggerJobs(ciBackend ci_backends.CiBackend, repoFullName string, repoOwner string, repoName string, batchId *uuid.UUID, prNumber int, prService ci.PullRequestService, gh utils.GithubClientProvider) error {
9661174
_, err := models.DB.GetDiggerBatch(batchId)
9671175
if err != nil {

backend/controllers/github_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ func TestJobsTreeWithOneJobsAndTwoProjects(t *testing.T) {
731731
graph, err := configuration.CreateProjectDependencyGraph(projects)
732732
assert.NoError(t, err)
733733

734-
_, result, err := utils.ConvertJobsToDiggerJobs("", "github", 1, jobs, projectMap, graph, 41584295, "", 2, "diggerhq", "parallel_jobs_demo", "diggerhq/parallel_jobs_demo", "", 123, "test", 0)
734+
_, result, err := utils.ConvertJobsToDiggerJobs("", "github", 1, jobs, projectMap, graph, 41584295, "", 2, "diggerhq", "parallel_jobs_demo", "diggerhq/parallel_jobs_demo", "", 123, "test", 0, "", false)
735735
assert.NoError(t, err)
736736
assert.Equal(t, 1, len(result))
737737
parentLinks, err := models.DB.GetDiggerJobParentLinksChildId(&result["dev"].DiggerJobID)
@@ -760,7 +760,7 @@ func TestJobsTreeWithTwoDependantJobs(t *testing.T) {
760760
projectMap["dev"] = project1
761761
projectMap["prod"] = project2
762762

763-
_, result, err := utils.ConvertJobsToDiggerJobs("", "github", 1, jobs, projectMap, graph, 123, "", 2, "", "", "test", "", 123, "test", 0)
763+
_, result, err := utils.ConvertJobsToDiggerJobs("", "github", 1, jobs, projectMap, graph, 123, "", 2, "", "", "test", "", 123, "test", 0, "", false)
764764
assert.NoError(t, err)
765765
assert.Equal(t, 2, len(result))
766766

@@ -793,7 +793,7 @@ func TestJobsTreeWithTwoIndependentJobs(t *testing.T) {
793793
projectMap["dev"] = project1
794794
projectMap["prod"] = project2
795795

796-
_, result, err := utils.ConvertJobsToDiggerJobs("", "github", 1, jobs, projectMap, graph, 123, "", 2, "", "", "test", "", 123, "test", 0)
796+
_, result, err := utils.ConvertJobsToDiggerJobs("", "github", 1, jobs, projectMap, graph, 123, "", 2, "", "", "test", "", 123, "test", 0, "", false)
797797
assert.NoError(t, err)
798798
assert.Equal(t, 2, len(result))
799799
parentLinks, err := models.DB.GetDiggerJobParentLinksChildId(&result["dev"].DiggerJobID)
@@ -838,7 +838,7 @@ func TestJobsTreeWithThreeLevels(t *testing.T) {
838838
projectMap["555"] = project5
839839
projectMap["666"] = project6
840840

841-
_, result, err := utils.ConvertJobsToDiggerJobs("", "github", 1, jobs, projectMap, graph, 123, "", 2, "", "", "test", "", 123, "test", 0)
841+
_, result, err := utils.ConvertJobsToDiggerJobs("", "github", 1, jobs, projectMap, graph, 123, "", 2, "", "", "test", "", 123, "test", 0, "", false)
842842
assert.NoError(t, err)
843843
assert.Equal(t, 6, len(result))
844844
parentLinks, err := models.DB.GetDiggerJobParentLinksChildId(&result["111"].DiggerJobID)

0 commit comments

Comments
 (0)