Skip to content

Commit 60dbc32

Browse files
motatoescoderabbitai[bot]aldoborrerohtplbc
authored
merge develop into comment refactors (#1867)
* add flag to ignore all external directories per project (#1851) * add flag to ignore all external directories per project * revert includeparentblocks flag (#1852) * improve efficiency in terragrunt generation (#1854) * improve efficiency in terragrunt generation * Update action.yml (#1856) * handle crashes in goroutine events (#1857) * fix/recover from webhook goroutines (#1858) * handle crashes in goroutine events * include stacktrace in errors * wip generation of terraform code from application code (#1855) * terraform code generation demo --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: incorrect comment in backend/migrations (#1860) * Update setup-opentofu to fix issues with 1.6.x downloads (#1861) * restructure docs to have no columns (#1862) * Create curl_bootstrap.sh * refactor codegen parts (#1863) * refactor codegen parts * publish ai summaries (#1864) * publish ai summaries * add a header for summary comment (#1865) * fix heading (#1866) --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Aldo <82811+aldoborrero@users.noreply.github.com> Co-authored-by: Hugo Samayoa <htplbc@gmail.com>
1 parent 1a48f98 commit 60dbc32

31 files changed

+949
-784
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

+232-14
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,36 @@ 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"
37+
"path/filepath"
2838
"reflect"
39+
"runtime/debug"
2940
"slices"
3041
"strconv"
3142
"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"
4343
)
4444

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

311311
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+
312322
installationId := *payload.Installation.ID
313323
repoName := *payload.Repo.Name
314324
repoOwner := *payload.Repo.Owner.Login
@@ -376,6 +386,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
376386
diggerYmlStr, ghService, config, projectsGraph, _, _, changedFiles, err := getDiggerConfigForPR(gh, organisationId, prLabelsStr, installationId, repoFullName, repoOwner, repoName, cloneURL, prNumber)
377387
if err != nil {
378388
log.Printf("getDiggerConfigForPR error: %v", err)
389+
commentReporterManager.UpdateComment(fmt.Sprintf(":x: Error loading digger config: %v", err))
379390
return fmt.Errorf("error getting digger config")
380391
}
381392

@@ -501,6 +512,22 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
501512
commentReporterManager.UpdateComment(fmt.Sprintf(":x: could not handle commentId: %v", err))
502513
}
503514

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+
504531
placeholderComment, err := ghService.PublishComment(prNumber, "digger report placehoder")
505532
if err != nil {
506533
log.Printf("strconv.ParseInt error: %v", err)
@@ -509,6 +536,8 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR
509536
}
510537

511538
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+
512541
if err != nil {
513542
log.Printf("ConvertJobsToDiggerJobs error: %v", err)
514543
commentReporterManager.UpdateComment(fmt.Sprintf(":x: ConvertJobsToDiggerJobs error: %v", err))
@@ -582,8 +611,11 @@ func GetDiggerConfigForBranch(gh utils.GithubClientProvider, installationId int6
582611
var dependencyGraph graph.Graph[string, dg_configuration.Project]
583612

584613
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+
}
587619
config, _, dependencyGraph, err = dg_configuration.LoadDiggerConfig(dir, true, changedFiles)
588620
if err != nil {
589621
log.Printf("Error loading digger config: %v", err)
@@ -692,6 +724,16 @@ func getBatchType(jobs []orchestrator_scheduler.Job) orchestrator_scheduler.Digg
692724
}
693725

694726
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+
695737
installationId := *payload.Installation.ID
696738
repoName := *payload.Repo.Name
697739
repoOwner := *payload.Repo.Owner.Login
@@ -752,6 +794,15 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
752794
return fmt.Errorf("error getting digger config")
753795
}
754796

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+
755806
commentIdStr := strconv.FormatInt(userCommentId, 10)
756807
err = ghService.CreateCommentReaction(commentIdStr, string(dg_github.GithubCommentEyesReaction))
757808
if err != nil {
@@ -883,6 +934,20 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
883934
return fmt.Errorf("comment reporter error: %v", err)
884935
}
885936

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+
886951
placeholderComment, err := ghService.PublishComment(issueNumber, "digger report placehoder")
887952
if err != nil {
888953
log.Printf("strconv.ParseInt error: %v", err)
@@ -891,6 +956,7 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
891956
}
892957

893958
batchId, _, err := utils.ConvertJobsToDiggerJobs(*diggerCommand, "github", orgId, impactedProjectsJobMap, impactedProjectsMap, projectsGraph, installationId, *branch, issueNumber, repoOwner, repoName, repoFullName, *commitSha, reporterCommentId, &placeholderComment.Id, diggerYmlStr, 0)
959+
894960
if err != nil {
895961
log.Printf("ConvertJobsToDiggerJobs error: %v", err)
896962
commentReporterManager.UpdateComment(fmt.Sprintf(":x: ConvertJobsToDiggerJobs error: %v", err))
@@ -962,6 +1028,158 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu
9621028
return nil
9631029
}
9641030

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+
9651183
func TriggerDiggerJobs(ciBackend ci_backends.CiBackend, repoFullName string, repoOwner string, repoName string, batchId *uuid.UUID, prNumber int, prService ci.PullRequestService, gh utils.GithubClientProvider) error {
9661184
_, err := models.DB.GetDiggerBatch(batchId)
9671185
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)