@@ -10,37 +10,37 @@ 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
37
"path"
38
+ "path/filepath"
28
39
"reflect"
29
40
"runtime/debug"
30
41
"slices"
31
42
"strconv"
32
43
"strings"
33
-
34
- "github.com/diggerhq/digger/backend/middleware"
35
- "github.com/diggerhq/digger/backend/models"
36
- "github.com/diggerhq/digger/backend/utils"
37
- dg_github "github.com/diggerhq/digger/libs/ci/github"
38
- dg_configuration "github.com/diggerhq/digger/libs/digger_config"
39
- "github.com/dominikbraun/graph"
40
- "github.com/gin-gonic/gin"
41
- "github.com/google/go-github/v61/github"
42
- "github.com/samber/lo"
43
- "golang.org/x/oauth2"
44
44
)
45
45
46
46
type IssueCommentHook func (gh utils.GithubClientProvider , payload * github.IssueCommentEvent , ciBackendProvider ci_backends.CiBackendProvider ) error
@@ -765,6 +765,158 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
765
765
return fmt .Errorf ("error getting digger config" )
766
766
}
767
767
768
+ // terraform code generator
769
+ if os .Getenv ("DIGGER_GENERATION_ENABLED" ) == "1" {
770
+ if strings .HasPrefix (* payload .Comment .Body , "digger generate" ) {
771
+ projectName := ci .ParseProjectName (* payload .Comment .Body )
772
+ if projectName == "" {
773
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: generate requires argument -p <project_name>: %v" , err ))
774
+ log .Printf ("missing project in command: %v" , * payload .Comment .Body )
775
+ return fmt .Errorf ("generate requires argument -p <project_name>: %v" , err )
776
+ }
777
+
778
+ project := config .GetProject (projectName )
779
+ if project == nil {
780
+ commentReporterManager .UpdateComment (fmt .Sprintf ("could not find project %v in digger.yml" , projectName ))
781
+ log .Printf ("could not find project %v in digger.yml" , projectName )
782
+ return fmt .Errorf ("could not find project %v in digger.yml" , projectName )
783
+ }
784
+
785
+ commentReporterManager .UpdateComment (fmt .Sprintf (":white_check_mark: Successfully loaded project" ))
786
+
787
+ generationEndpoint := os .Getenv ("DIGGER_GENERATION_ENDPOINT" )
788
+ if generationEndpoint == "" {
789
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: server does not have generation endpoint configured, please verify" ))
790
+ log .Printf ("server does not have generation endpoint configured, please verify" )
791
+ return fmt .Errorf ("server does not have generation endpoint configured, please verify" )
792
+ }
793
+ webhookSecret := os .Getenv ("DIGGER_GENERATION_WEBHOOK_SECRET" )
794
+
795
+ // Get all code content from the repository at a specific commit
796
+ getCodeFromCommit := func (ghService * dg_github.GithubService , repoOwner , repoName string , commitSha * string , projectDir string ) (string , error ) {
797
+ const MaxPatchSize = 1024 * 1024 // 1MB limit
798
+
799
+ // Get the commit's changes compared to default branch
800
+ comparison , _ , err := ghService .Client .Repositories .CompareCommits (
801
+ context .Background (),
802
+ repoOwner ,
803
+ repoName ,
804
+ defaultBranch ,
805
+ * commitSha ,
806
+ nil ,
807
+ )
808
+ if err != nil {
809
+ return "" , fmt .Errorf ("error comparing commits: %v" , err )
810
+ }
811
+
812
+ var appCode strings.Builder
813
+ for _ , file := range comparison .Files {
814
+ if file .Patch == nil {
815
+ continue // Skip files without patches
816
+ }
817
+ log .Printf ("Processing patch for file: %s" , * file .Filename )
818
+ if * file .Additions > 0 {
819
+ lines := strings .Split (* file .Patch , "\n " )
820
+ for _ , line := range lines {
821
+ if strings .HasPrefix (line , "+" ) && ! strings .HasPrefix (line , "+++" ) {
822
+ appCode .WriteString (strings .TrimPrefix (line , "+" ))
823
+ appCode .WriteString ("\n " )
824
+ }
825
+ }
826
+ }
827
+ appCode .WriteString ("\n " )
828
+ }
829
+
830
+ if appCode .Len () == 0 {
831
+ return "" , fmt .Errorf ("no code changes found in commit %s. Please ensure the PR contains added or modified code" , * commitSha )
832
+ }
833
+
834
+ return appCode .String (), nil
835
+ }
836
+
837
+ appCode , err := getCodeFromCommit (ghService , repoOwner , repoName , commitSha , project .Dir )
838
+ if err != nil {
839
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to get code content: %v" , err ))
840
+ log .Printf ("Error getting code content: %v" , err )
841
+ return fmt .Errorf ("error getting code content: %v" , err )
842
+ }
843
+
844
+ commentReporterManager .UpdateComment (fmt .Sprintf (":white_check_mark: Successfully loaded code from commit" ))
845
+
846
+ log .Printf ("the app code is: %v" , appCode )
847
+
848
+ commentReporterManager .UpdateComment (fmt .Sprintf ("Generating terraform..." ))
849
+ terraformCode , err := utils .GenerateTerraformCode (appCode , generationEndpoint , webhookSecret )
850
+ if err != nil {
851
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: could not generate terraform code: %v" , err ))
852
+ log .Printf ("could not generate terraform code: %v" , err )
853
+ return fmt .Errorf ("could not generate terraform code: %v" , err )
854
+ }
855
+
856
+ commentReporterManager .UpdateComment (fmt .Sprintf (":white_check_mark: Generated terraform" ))
857
+
858
+ // comment terraform code to project dir
859
+ //project.Dir
860
+ log .Printf ("terraform code is %v" , terraformCode )
861
+
862
+ baseTree , _ , err := ghService .Client .Git .GetTree (context .Background (), repoOwner , repoName , * commitSha , false )
863
+ if err != nil {
864
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to get base tree: %v" , err ))
865
+ log .Printf ("Error getting base tree: %v" , err )
866
+ return fmt .Errorf ("error getting base tree: %v" , err )
867
+ }
868
+
869
+ // Create a new tree with the new file
870
+ treeEntries := []* github.TreeEntry {
871
+ {
872
+ Path : github .String (filepath .Join (project .Dir , fmt .Sprintf ("generated_%v.tf" , issueNumber ))),
873
+ Mode : github .String ("100644" ),
874
+ Type : github .String ("blob" ),
875
+ Content : github .String (terraformCode ),
876
+ },
877
+ }
878
+
879
+ newTree , _ , err := ghService .Client .Git .CreateTree (context .Background (), repoOwner , repoName , * baseTree .SHA , treeEntries )
880
+ if err != nil {
881
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to create new tree: %v" , err ))
882
+ log .Printf ("Error creating new tree: %v" , err )
883
+ return fmt .Errorf ("error creating new tree: %v" , err )
884
+ }
885
+
886
+ // Create the commit
887
+ commitMsg := fmt .Sprintf ("Add generated Terraform code for %v" , projectName )
888
+ commit := & github.Commit {
889
+ Message : & commitMsg ,
890
+ Tree : newTree ,
891
+ Parents : []* github.Commit {{SHA : commitSha }},
892
+ }
893
+
894
+ newCommit , _ , err := ghService .Client .Git .CreateCommit (context .Background (), repoOwner , repoName , commit , nil )
895
+ if err != nil {
896
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to commit Terraform file: %v" , err ))
897
+ log .Printf ("Error committing Terraform file: %v" , err )
898
+ return fmt .Errorf ("error committing Terraform file: %v" , err )
899
+ }
900
+
901
+ // Update the reference to point to the new commit
902
+ ref := & github.Reference {
903
+ Ref : github .String (fmt .Sprintf ("refs/heads/%s" , * branch )),
904
+ Object : & github.GitObject {
905
+ SHA : newCommit .SHA ,
906
+ },
907
+ }
908
+ _ , _ , err = ghService .Client .Git .UpdateRef (context .Background (), repoOwner , repoName , ref , false )
909
+ if err != nil {
910
+ commentReporterManager .UpdateComment (fmt .Sprintf (":x: Failed to update branch reference: %v" , err ))
911
+ log .Printf ("Error updating branch reference: %v" , err )
912
+ return fmt .Errorf ("error updating branch reference: %v" , err )
913
+ }
914
+
915
+ commentReporterManager .UpdateComment (":white_check_mark: Successfully generated and committed Terraform code" )
916
+ return nil
917
+ }
918
+ }
919
+
768
920
commentIdStr := strconv .FormatInt (userCommentId , 10 )
769
921
err = ghService .CreateCommentReaction (commentIdStr , string (dg_github .GithubCommentEyesReaction ))
770
922
if err != nil {
0 commit comments