@@ -10,36 +10,36 @@ import (
10
10
"github.com/diggerhq/digger/backend/ci_backends"
11
11
config2 "github.com/diggerhq/digger/backend/config"
12
12
"github.com/diggerhq/digger/backend/locking"
13
+ "github.com/diggerhq/digger/backend/middleware"
14
+ "github.com/diggerhq/digger/backend/models"
13
15
"github.com/diggerhq/digger/backend/segment"
14
16
"github.com/diggerhq/digger/backend/services"
17
+ "github.com/diggerhq/digger/backend/utils"
15
18
"github.com/diggerhq/digger/libs/ci"
16
19
"github.com/diggerhq/digger/libs/ci/generic"
20
+ dg_github "github.com/diggerhq/digger/libs/ci/github"
17
21
comment_updater "github.com/diggerhq/digger/libs/comment_utils/reporting"
22
+ dg_configuration "github.com/diggerhq/digger/libs/digger_config"
18
23
dg_locking "github.com/diggerhq/digger/libs/locking"
19
24
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"
20
28
"github.com/google/uuid"
29
+ "github.com/samber/lo"
30
+ "golang.org/x/oauth2"
21
31
"gorm.io/gorm"
22
32
"log"
23
33
"math/rand"
24
34
"net/http"
25
35
"net/url"
26
36
"os"
27
- "path"
37
+ "path/filepath "
28
38
"reflect"
39
+ "runtime/debug"
29
40
"slices"
30
41
"strconv"
31
42
"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"
43
43
)
44
44
45
45
type IssueCommentHook func (gh utils.GithubClientProvider , payload * github.IssueCommentEvent , ciBackendProvider ci_backends.CiBackendProvider ) error
@@ -309,6 +309,16 @@ func handleInstallationDeletedEvent(installation *github.InstallationEvent, appI
309
309
}
310
310
311
311
func handlePullRequestEvent (gh utils.GithubClientProvider , payload * github.PullRequestEvent , ciBackendProvider ci_backends.CiBackendProvider , appId int64 ) error {
312
+ defer func () {
313
+ if r := recover (); r != nil {
314
+ log .Printf ("Recovered from panic in handlePullRequestEvent handler: %v" , r )
315
+ log .Printf ("\n === PANIC RECOVERED ===\n " )
316
+ log .Printf ("Error: %v\n " , r )
317
+ log .Printf ("Stack Trace:\n %s" , string (debug .Stack ()))
318
+ log .Printf ("=== END PANIC ===\n " )
319
+ }
320
+ }()
321
+
312
322
installationId := * payload .Installation .ID
313
323
repoName := * payload .Repo .Name
314
324
repoOwner := * payload .Repo .Owner .Login
@@ -376,6 +386,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
376
386
diggerYmlStr , ghService , config , projectsGraph , _ , _ , changedFiles , err := getDiggerConfigForPR (gh , organisationId , prLabelsStr , installationId , repoFullName , repoOwner , repoName , cloneURL , prNumber )
377
387
if err != nil {
378
388
log .Printf ("getDiggerConfigForPR error: %v" , err )
389
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Error loading digger config: %v" , err ))
379
390
return fmt .Errorf ("error getting digger config" )
380
391
}
381
392
@@ -501,6 +512,22 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
501
512
commentReporterManager .UpdateComment (fmt .Sprintf (":x: could not handle commentId: %v" , err ))
502
513
}
503
514
515
+
516
+
517
+ var aiSummaryCommentId = ""
518
+ if config .Reporting .AiSummary {
519
+ aiSummaryComment , err := ghService .PublishComment (prNumber , "AI Summary will be posted here after completion" )
520
+ if err != nil {
521
+ log .Printf ("could not post ai summary comment: %v" , err )
522
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: could not post ai comment summary comment id: %v" , err ))
523
+ return fmt .Errorf ("could not post ai summary comment: %v" , err )
524
+ }
525
+ aiSummaryCommentId = aiSummaryComment .Id
526
+ }
527
+
528
+ batchId , _ , err := utils .ConvertJobsToDiggerJobs (* diggerCommand , models .DiggerVCSGithub , organisationId , impactedJobsMap , impactedProjectsMap , projectsGraph , installationId , branch , prNumber , repoOwner , repoName , repoFullName , commitSha , commentId , diggerYmlStr , 0 , aiSummaryCommentId , config .ReportTerraformOutputs )
529
+
530
+
504
531
placeholderComment , err := ghService .PublishComment (prNumber , "digger report placehoder" )
505
532
if err != nil {
506
533
log .Printf ("strconv.ParseInt error: %v" , err )
@@ -509,6 +536,8 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
509
536
}
510
537
511
538
batchId , _ , err := utils .ConvertJobsToDiggerJobs (* diggerCommand , models .DiggerVCSGithub , organisationId , impactedJobsMap , impactedProjectsMap , projectsGraph , installationId , branch , prNumber , repoOwner , repoName , repoFullName , commitSha , commentId , & placeholderComment .Id , diggerYmlStr , 0 )
539
+
540
+
512
541
if err != nil {
513
542
log .Printf ("ConvertJobsToDiggerJobs error: %v" , err )
514
543
commentReporterManager .UpdateComment (fmt .Sprintf (":x: ConvertJobsToDiggerJobs error: %v" , err ))
@@ -582,8 +611,11 @@ func GetDiggerConfigForBranch(gh utils.GithubClientProvider, installationId int6
582
611
var dependencyGraph graph.Graph [string , dg_configuration.Project ]
583
612
584
613
err = utils .CloneGitRepoAndDoAction (cloneUrl , branch , "" , * token , func (dir string ) error {
585
- diggerYmlBytes , err := os .ReadFile (path .Join (dir , "digger.yml" ))
586
- diggerYmlStr = string (diggerYmlBytes )
614
+ diggerYmlStr , err = dg_configuration .ReadDiggerYmlFileContents (dir )
615
+ if err != nil {
616
+ log .Printf ("could not load digger config: %v" , err )
617
+ return err
618
+ }
587
619
config , _ , dependencyGraph , err = dg_configuration .LoadDiggerConfig (dir , true , changedFiles )
588
620
if err != nil {
589
621
log .Printf ("Error loading digger config: %v" , err )
@@ -692,6 +724,16 @@ func getBatchType(jobs []orchestrator_scheduler.Job) orchestrator_scheduler.Digg
692
724
}
693
725
694
726
func handleIssueCommentEvent (gh utils.GithubClientProvider , payload * github.IssueCommentEvent , ciBackendProvider ci_backends.CiBackendProvider , appId int64 , postCommentHooks []IssueCommentHook ) error {
727
+ defer func () {
728
+ if r := recover (); r != nil {
729
+ log .Printf ("Recovered from panic in handleIssueCommentEvent handler: %v" , r )
730
+ log .Printf ("\n === PANIC RECOVERED ===\n " )
731
+ log .Printf ("Error: %v\n " , r )
732
+ log .Printf ("Stack Trace:\n %s" , string (debug .Stack ()))
733
+ log .Printf ("=== END PANIC ===\n " )
734
+ }
735
+ }()
736
+
695
737
installationId := * payload .Installation .ID
696
738
repoName := * payload .Repo .Name
697
739
repoOwner := * payload .Repo .Owner .Login
@@ -752,6 +794,15 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
752
794
return fmt .Errorf ("error getting digger config" )
753
795
}
754
796
797
+ // terraform code generator
798
+ if os .Getenv ("DIGGER_GENERATION_ENABLED" ) == "1" {
799
+ err = GenerateTerraformFromCode (payload , commentReporterManager , config , defaultBranch , ghService , repoOwner , repoName , commitSha , issueNumber , branch )
800
+ if err != nil {
801
+ log .Printf ("terraform generation failed: %v" , err )
802
+ return err
803
+ }
804
+ }
805
+
755
806
commentIdStr := strconv .FormatInt (userCommentId , 10 )
756
807
err = ghService .CreateCommentReaction (commentIdStr , string (dg_github .GithubCommentEyesReaction ))
757
808
if err != nil {
@@ -883,6 +934,20 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
883
934
return fmt .Errorf ("comment reporter error: %v" , err )
884
935
}
885
936
937
+
938
+ var aiSummaryCommentId = ""
939
+ if config .Reporting .AiSummary {
940
+ aiSummaryComment , err := ghService .PublishComment (issueNumber , "AI Summary will be posted here after completion" )
941
+ if err != nil {
942
+ log .Printf ("could not post ai summary comment: %v" , err )
943
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: could not post ai comment summary comment id: %v" , err ))
944
+ return fmt .Errorf ("could not post ai summary comment: %v" , err )
945
+ }
946
+ aiSummaryCommentId = aiSummaryComment .Id
947
+ }
948
+
949
+ batchId , _ , err := utils .ConvertJobsToDiggerJobs (* diggerCommand , "github" , orgId , impactedProjectsJobMap , impactedProjectsMap , projectsGraph , installationId , * branch , issueNumber , repoOwner , repoName , repoFullName , * commitSha , reporterCommentId , diggerYmlStr , 0 , aiSummaryCommentId , config .ReportTerraformOutputs )
950
+
886
951
placeholderComment , err := ghService .PublishComment (issueNumber , "digger report placehoder" )
887
952
if err != nil {
888
953
log .Printf ("strconv.ParseInt error: %v" , err )
@@ -891,6 +956,7 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
891
956
}
892
957
893
958
batchId , _ , err := utils .ConvertJobsToDiggerJobs (* diggerCommand , "github" , orgId , impactedProjectsJobMap , impactedProjectsMap , projectsGraph , installationId , * branch , issueNumber , repoOwner , repoName , repoFullName , * commitSha , reporterCommentId , & placeholderComment .Id , diggerYmlStr , 0 )
959
+
894
960
if err != nil {
895
961
log .Printf ("ConvertJobsToDiggerJobs error: %v" , err )
896
962
commentReporterManager .UpdateComment (fmt .Sprintf (":x: ConvertJobsToDiggerJobs error: %v" , err ))
@@ -962,6 +1028,158 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
962
1028
return nil
963
1029
}
964
1030
1031
+ 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 {
1032
+ if strings .HasPrefix (* payload .Comment .Body , "digger generate" ) {
1033
+ projectName := ci .ParseProjectName (* payload .Comment .Body )
1034
+ if projectName == "" {
1035
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: generate requires argument -p <project_name>" ))
1036
+ log .Printf ("missing project in command: %v" , * payload .Comment .Body )
1037
+ return fmt .Errorf ("generate requires argument -p <project_name>" )
1038
+ }
1039
+
1040
+ project := config .GetProject (projectName )
1041
+ if project == nil {
1042
+ commentReporterManager .UpdateComment (fmt .Sprintf ("could not find project %v in digger.yml" , projectName ))
1043
+ log .Printf ("could not find project %v in digger.yml" , projectName )
1044
+ return fmt .Errorf ("could not find project %v in digger.yml" , projectName )
1045
+ }
1046
+
1047
+ commentReporterManager .UpdateComment (fmt .Sprintf (":white_check_mark: Successfully loaded project" ))
1048
+
1049
+ generationEndpoint := os .Getenv ("DIGGER_GENERATION_ENDPOINT" )
1050
+ if generationEndpoint == "" {
1051
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: server does not have generation endpoint configured, please verify" ))
1052
+ log .Printf ("server does not have generation endpoint configured, please verify" )
1053
+ return fmt .Errorf ("server does not have generation endpoint configured, please verify" )
1054
+ }
1055
+ apiToken := os .Getenv ("DIGGER_GENERATION_API_TOKEN" )
1056
+
1057
+ // Get all code content from the repository at a specific commit
1058
+ getCodeFromCommit := func (ghService * dg_github.GithubService , repoOwner , repoName string , commitSha * string , projectDir string ) (string , error ) {
1059
+ const MaxPatchSize = 1024 * 1024 // 1MB limit
1060
+
1061
+ // Get the commit's changes compared to default branch
1062
+ comparison , _ , err := ghService .Client .Repositories .CompareCommits (
1063
+ context .Background (),
1064
+ repoOwner ,
1065
+ repoName ,
1066
+ defaultBranch ,
1067
+ * commitSha ,
1068
+ nil ,
1069
+ )
1070
+ if err != nil {
1071
+ return "" , fmt .Errorf ("error comparing commits: %v" , err )
1072
+ }
1073
+
1074
+ var appCode strings.Builder
1075
+ for _ , file := range comparison .Files {
1076
+ if file .Patch == nil {
1077
+ continue // Skip files without patches
1078
+ }
1079
+ log .Printf ("Processing patch for file: %s" , * file .Filename )
1080
+ if * file .Additions > 0 {
1081
+ lines := strings .Split (* file .Patch , "\n " )
1082
+ for _ , line := range lines {
1083
+ if strings .HasPrefix (line , "+" ) && ! strings .HasPrefix (line , "+++" ) {
1084
+ appCode .WriteString (strings .TrimPrefix (line , "+" ))
1085
+ appCode .WriteString ("\n " )
1086
+ }
1087
+ }
1088
+ }
1089
+ appCode .WriteString ("\n " )
1090
+ }
1091
+
1092
+ if appCode .Len () == 0 {
1093
+ return "" , fmt .Errorf ("no code changes found in commit %s. Please ensure the PR contains added or modified code" , * commitSha )
1094
+ }
1095
+
1096
+ return appCode .String (), nil
1097
+ }
1098
+
1099
+ appCode , err := getCodeFromCommit (ghService , repoOwner , repoName , commitSha , project .Dir )
1100
+ if err != nil {
1101
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to get code content: %v" , err ))
1102
+ log .Printf ("Error getting code content: %v" , err )
1103
+ return fmt .Errorf ("error getting code content: %v" , err )
1104
+ }
1105
+
1106
+ commentReporterManager .UpdateComment (fmt .Sprintf (":white_check_mark: Successfully loaded code from commit" ))
1107
+
1108
+ log .Printf ("the app code is: %v" , appCode )
1109
+
1110
+ commentReporterManager .UpdateComment (fmt .Sprintf ("Generating terraform..." ))
1111
+ terraformCode , err := utils .GenerateTerraformCode (appCode , generationEndpoint , apiToken )
1112
+ if err != nil {
1113
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: could not generate terraform code: %v" , err ))
1114
+ log .Printf ("could not generate terraform code: %v" , err )
1115
+ return fmt .Errorf ("could not generate terraform code: %v" , err )
1116
+ }
1117
+
1118
+ commentReporterManager .UpdateComment (fmt .Sprintf (":white_check_mark: Generated terraform" ))
1119
+
1120
+ // comment terraform code to project dir
1121
+ //project.Dir
1122
+ log .Printf ("terraform code is %v" , terraformCode )
1123
+
1124
+ baseTree , _ , err := ghService .Client .Git .GetTree (context .Background (), repoOwner , repoName , * commitSha , false )
1125
+ if err != nil {
1126
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to get base tree: %v" , err ))
1127
+ log .Printf ("Error getting base tree: %v" , err )
1128
+ return fmt .Errorf ("error getting base tree: %v" , err )
1129
+ }
1130
+
1131
+ // Create a new tree with the new file
1132
+ treeEntries := []* github.TreeEntry {
1133
+ {
1134
+ Path : github .String (filepath .Join (project .Dir , fmt .Sprintf ("generated_%v.tf" , issueNumber ))),
1135
+ Mode : github .String ("100644" ),
1136
+ Type : github .String ("blob" ),
1137
+ Content : github .String (terraformCode ),
1138
+ },
1139
+ }
1140
+
1141
+ newTree , _ , err := ghService .Client .Git .CreateTree (context .Background (), repoOwner , repoName , * baseTree .SHA , treeEntries )
1142
+ if err != nil {
1143
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to create new tree: %v" , err ))
1144
+ log .Printf ("Error creating new tree: %v" , err )
1145
+ return fmt .Errorf ("error creating new tree: %v" , err )
1146
+ }
1147
+
1148
+ // Create the commit
1149
+ commitMsg := fmt .Sprintf ("Add generated Terraform code for %v" , projectName )
1150
+ commit := & github.Commit {
1151
+ Message : & commitMsg ,
1152
+ Tree : newTree ,
1153
+ Parents : []* github.Commit {{SHA : commitSha }},
1154
+ }
1155
+
1156
+ newCommit , _ , err := ghService .Client .Git .CreateCommit (context .Background (), repoOwner , repoName , commit , nil )
1157
+ if err != nil {
1158
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to commit Terraform file: %v" , err ))
1159
+ log .Printf ("Error committing Terraform file: %v" , err )
1160
+ return fmt .Errorf ("error committing Terraform file: %v" , err )
1161
+ }
1162
+
1163
+ // Update the reference to point to the new commit
1164
+ ref := & github.Reference {
1165
+ Ref : github .String (fmt .Sprintf ("refs/heads/%s" , * branch )),
1166
+ Object : & github.GitObject {
1167
+ SHA : newCommit .SHA ,
1168
+ },
1169
+ }
1170
+ _ , _ , err = ghService .Client .Git .UpdateRef (context .Background (), repoOwner , repoName , ref , false )
1171
+ if err != nil {
1172
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to update branch reference: %v" , err ))
1173
+ log .Printf ("Error updating branch reference: %v" , err )
1174
+ return fmt .Errorf ("error updating branch reference: %v" , err )
1175
+ }
1176
+
1177
+ commentReporterManager .UpdateComment (":white_check_mark: Successfully generated and committed Terraform code" )
1178
+ return nil
1179
+ }
1180
+ return nil
1181
+ }
1182
+
965
1183
func TriggerDiggerJobs (ciBackend ci_backends.CiBackend , repoFullName string , repoOwner string , repoName string , batchId * uuid.UUID , prNumber int , prService ci.PullRequestService , gh utils.GithubClientProvider ) error {
966
1184
_ , err := models .DB .GetDiggerBatch (batchId )
967
1185
if err != nil {
0 commit comments