@@ -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
4543type 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
311309func 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
694719func 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+
9651173func 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 {
0 commit comments