@@ -10,36 +10,34 @@ 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"
28
37
"reflect"
29
38
"slices"
30
39
"strconv"
31
40
"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
41
)
44
42
45
43
type IssueCommentHook func (gh utils.GithubClientProvider , payload * github.IssueCommentEvent , ciBackendProvider ci_backends.CiBackendProvider ) error
@@ -309,6 +307,16 @@ func handleInstallationDeletedEvent(installation *github.InstallationEvent, appI
309
307
}
310
308
311
309
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
+
312
320
installationId := * payload .Installation .ID
313
321
repoName := * payload .Repo .Name
314
322
repoOwner := * payload .Repo .Owner .Login
@@ -376,6 +384,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
376
384
diggerYmlStr , ghService , config , projectsGraph , _ , _ , changedFiles , err := getDiggerConfigForPR (gh , organisationId , prLabelsStr , installationId , repoFullName , repoOwner , repoName , cloneURL , prNumber )
377
385
if err != nil {
378
386
log .Printf ("getDiggerConfigForPR error: %v" , err )
387
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Error loading digger config: %v" , err ))
379
388
return fmt .Errorf ("error getting digger config" )
380
389
}
381
390
@@ -501,6 +510,19 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
501
510
commentReporterManager .UpdateComment (fmt .Sprintf (":x: could not handle commentId: %v" , err ))
502
511
}
503
512
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
+
504
526
placeholderComment , err := ghService .PublishComment (prNumber , "digger report placehoder" )
505
527
if err != nil {
506
528
log .Printf ("strconv.ParseInt error: %v" , err )
@@ -582,8 +604,11 @@ func GetDiggerConfigForBranch(gh utils.GithubClientProvider, installationId int6
582
604
var dependencyGraph graph.Graph [string , dg_configuration.Project ]
583
605
584
606
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
+ }
587
612
config , _ , dependencyGraph , err = dg_configuration .LoadDiggerConfig (dir , true , changedFiles )
588
613
if err != nil {
589
614
log .Printf ("Error loading digger config: %v" , err )
@@ -692,6 +717,16 @@ func getBatchType(jobs []orchestrator_scheduler.Job) orchestrator_scheduler.Digg
692
717
}
693
718
694
719
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
+
695
730
installationId := * payload .Installation .ID
696
731
repoName := * payload .Repo .Name
697
732
repoOwner := * payload .Repo .Owner .Login
@@ -752,6 +787,15 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
752
787
return fmt .Errorf ("error getting digger config" )
753
788
}
754
789
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
+
755
799
commentIdStr := strconv .FormatInt (userCommentId , 10 )
756
800
err = ghService .CreateCommentReaction (commentIdStr , string (dg_github .GithubCommentEyesReaction ))
757
801
if err != nil {
@@ -883,6 +927,18 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
883
927
return fmt .Errorf ("comment reporter error: %v" , err )
884
928
}
885
929
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 )
886
942
placeholderComment , err := ghService .PublishComment (issueNumber , "digger report placehoder" )
887
943
if err != nil {
888
944
log .Printf ("strconv.ParseInt error: %v" , err )
@@ -962,6 +1018,158 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
962
1018
return nil
963
1019
}
964
1020
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
+
965
1173
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
1174
_ , err := models .DB .GetDiggerBatch (batchId )
967
1175
if err != nil {
0 commit comments