Skip to content

Commit 02ceef5

Browse files
authored
feat drift app (#1724)
* modify drift app
1 parent 616a3cd commit 02ceef5

18 files changed

+817
-21
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.idea/
22
**/.env
3+
**/.env*
34
.DS_Store
45
venv/
56
**/__pycache__/

ee/drift/controllers/github.go

+64-3
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,77 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"github.com/diggerhq/digger/backend/utils"
78
"github.com/diggerhq/digger/ee/drift/dbmodels"
89
"github.com/diggerhq/digger/ee/drift/middleware"
910
"github.com/diggerhq/digger/ee/drift/model"
11+
"github.com/diggerhq/digger/ee/drift/tasks"
1012
next_utils "github.com/diggerhq/digger/next/utils"
1113
"github.com/gin-gonic/gin"
1214
"github.com/google/go-github/v61/github"
1315
"golang.org/x/oauth2"
1416
"log"
1517
"net/http"
1618
"os"
19+
"reflect"
1720
"strconv"
1821
"strings"
1922
)
2023

24+
func (mc MainController) GithubAppWebHook(c *gin.Context) {
25+
c.Header("Content-Type", "application/json")
26+
gh := mc.GithubClientProvider
27+
log.Printf("GithubAppWebHook")
28+
29+
payload, err := github.ValidatePayload(c.Request, []byte(os.Getenv("GITHUB_WEBHOOK_SECRET")))
30+
if err != nil {
31+
log.Printf("Error validating github app webhook's payload: %v", err)
32+
c.String(http.StatusBadRequest, "Error validating github app webhook's payload")
33+
return
34+
}
35+
36+
webhookType := github.WebHookType(c.Request)
37+
event, err := github.ParseWebHook(webhookType, payload)
38+
if err != nil {
39+
log.Printf("Failed to parse Github Event. :%v\n", err)
40+
c.String(http.StatusInternalServerError, "Failed to parse Github Event")
41+
return
42+
}
43+
44+
log.Printf("github event type: %v\n", reflect.TypeOf(event))
45+
46+
switch event := event.(type) {
47+
case *github.PushEvent:
48+
log.Printf("Got push event for %d", event.Repo.URL)
49+
err := handlePushEvent(gh, event)
50+
if err != nil {
51+
log.Printf("handlePushEvent error: %v", err)
52+
c.String(http.StatusInternalServerError, err.Error())
53+
return
54+
}
55+
default:
56+
log.Printf("Unhandled event, event type %v", reflect.TypeOf(event))
57+
}
58+
59+
c.JSON(200, "ok")
60+
}
61+
62+
func handlePushEvent(gh utils.GithubClientProvider, payload *github.PushEvent) error {
63+
installationId := *payload.Installation.ID
64+
repoName := *payload.Repo.Name
65+
repoFullName := *payload.Repo.FullName
66+
repoOwner := *payload.Repo.Owner.Login
67+
cloneURL := *payload.Repo.CloneURL
68+
ref := *payload.Ref
69+
defaultBranch := *payload.Repo.DefaultBranch
70+
71+
if strings.HasSuffix(ref, defaultBranch) {
72+
go tasks.LoadProjectsFromGithubRepo(gh, strconv.FormatInt(installationId, 10), repoFullName, repoOwner, repoName, cloneURL, defaultBranch)
73+
}
74+
75+
return nil
76+
}
77+
2178
func (mc MainController) GithubAppCallbackPage(c *gin.Context) {
2279
installationId := c.Request.URL.Query()["installation_id"][0]
2380
//setupAction := c.Request.URL.Query()["setup_action"][0]
@@ -80,7 +137,7 @@ func (mc MainController) GithubAppCallbackPage(c *gin.Context) {
80137

81138
// reset all existing repos (soft delete)
82139
var ExistingRepos []model.Repo
83-
err = dbmodels.DB.GormDB.Delete(ExistingRepos, "organization_id=?", orgId).Error
140+
err = dbmodels.DB.GormDB.Delete(ExistingRepos, "organisation_id=?", orgId).Error
84141
if err != nil {
85142
log.Printf("could not delete repos: %v", err)
86143
c.String(http.StatusInternalServerError, "could not delete repos: %v", err)
@@ -89,20 +146,24 @@ func (mc MainController) GithubAppCallbackPage(c *gin.Context) {
89146

90147
// here we mark repos that are available one by one
91148
for _, repo := range repos {
149+
cloneUrl := *repo.CloneURL
150+
defaultBranch := *repo.DefaultBranch
92151
repoFullName := *repo.FullName
93152
repoOwner := strings.Split(*repo.FullName, "/")[0]
94153
repoName := *repo.Name
95154
repoUrl := fmt.Sprintf("https://github.com/%v", repoFullName)
96155

97-
_, _, err = dbmodels.CreateOrGetDiggerRepoForGithubRepo(repoFullName, repoOwner, repoName, repoUrl, installationId64, *installation.AppID, *installation.Account.ID, *installation.Account.Login)
156+
_, _, err = dbmodels.CreateOrGetDiggerRepoForGithubRepo(repoFullName, repoOwner, repoName, repoUrl, installationId, *installation.AppID, *installation.Account.ID, *installation.Account.Login)
98157
if err != nil {
99158
log.Printf("createOrGetDiggerRepoForGithubRepo error: %v", err)
100159
c.String(http.StatusInternalServerError, "createOrGetDiggerRepoForGithubRepo error: %v", err)
101160
return
102161
}
162+
163+
go tasks.LoadProjectsFromGithubRepo(mc.GithubClientProvider, installationId, repoFullName, repoOwner, repoName, cloneUrl, defaultBranch)
103164
}
104165

105-
c.HTML(http.StatusOK, "github_success.tmpl", gin.H{})
166+
c.String(http.StatusOK, "success", gin.H{})
106167
}
107168

108169
// why this validation is needed: https://roadie.io/blog/avoid-leaking-github-org-data/

ee/drift/dbmodels/github.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const (
1616
GithubAppInstallationLinkInactive GithubAppInstallationLinkStatus = "inactive"
1717
)
1818

19-
func (db *Database) GetGithubInstallationLinkForInstallationId(installationId int64) (*model.GithubAppInstallationLink, error) {
19+
func (db *Database) GetGithubInstallationLinkForInstallationId(installationId string) (*model.GithubAppInstallationLink, error) {
2020
l := model.GithubAppInstallationLink{}
2121
err := db.GormDB.Where("github_installation_id = ? AND status=?", installationId, GithubAppInstallationLinkActive).Find(&l).Error
2222
if err != nil {
@@ -28,7 +28,7 @@ func (db *Database) GetGithubInstallationLinkForInstallationId(installationId in
2828
return &l, nil
2929
}
3030

31-
func CreateOrGetDiggerRepoForGithubRepo(ghRepoFullName string, ghRepoOrganisation string, ghRepoName string, ghRepoUrl string, installationId int64, githubAppId int64, accountId int64, login string) (*model.Repo, *model.Organisation, error) {
31+
func CreateOrGetDiggerRepoForGithubRepo(ghRepoFullName string, ghRepoOrganisation string, ghRepoName string, ghRepoUrl string, installationId string, githubAppId int64, accountId int64, login string) (*model.Repo, *model.Organisation, error) {
3232
link, err := DB.GetGithubInstallationLinkForInstallationId(installationId)
3333
if err != nil {
3434
log.Printf("Error fetching installation link: %v", err)
@@ -45,7 +45,7 @@ func CreateOrGetDiggerRepoForGithubRepo(ghRepoFullName string, ghRepoOrganisatio
4545

4646
// using Unscoped because we also need to include deleted repos (and undelete them if they exist)
4747
var existingRepo model.Repo
48-
r := DB.GormDB.Unscoped().Where("organization_id=? AND repos.name=?", orgId, diggerRepoName).Find(&existingRepo)
48+
r := DB.GormDB.Unscoped().Where("organisation_id=? AND repos.name=?", orgId, diggerRepoName).Find(&existingRepo)
4949

5050
if r.Error != nil {
5151
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -71,3 +71,20 @@ func CreateOrGetDiggerRepoForGithubRepo(ghRepoFullName string, ghRepoOrganisatio
7171
log.Printf("Created digger repo: %v", repo)
7272
return repo, org, nil
7373
}
74+
75+
// GetGithubAppInstallationLink repoFullName should be in the following format: org/repo_name, for example "diggerhq/github-job-scheduler"
76+
func (db *Database) GetGithubAppInstallationLink(installationId string) (*model.GithubAppInstallationLink, error) {
77+
var link model.GithubAppInstallationLink
78+
result := db.GormDB.Where("github_installation_id = ? AND status=?", installationId, GithubAppInstallationLinkActive).Find(&link)
79+
if result.Error != nil {
80+
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
81+
return nil, result.Error
82+
}
83+
}
84+
85+
// If not found, the values will be default values, which means ID will be 0
86+
if link.ID == "" {
87+
return nil, nil
88+
}
89+
return &link, nil
90+
}

ee/drift/dbmodels/projects.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package dbmodels
2+
3+
import (
4+
"errors"
5+
"github.com/diggerhq/digger/ee/drift/model"
6+
"gorm.io/gorm"
7+
"log"
8+
)
9+
10+
type DriftStatus string
11+
12+
var DriftStatusNewDrift = "new drift"
13+
var DriftStatusNoDrift = "no drift"
14+
var DriftStatusAcknowledgeDrift = "acknowledged drift"
15+
16+
// GetProjectByName return project for specified org and repo
17+
// if record doesn't exist return nil
18+
func (db *Database) GetProjectByName(orgId any, repo *model.Repo, name string) (*model.Project, error) {
19+
log.Printf("GetProjectByName, org id: %v, project name: %v\n", orgId, name)
20+
var project model.Project
21+
22+
err := db.GormDB.
23+
Joins("INNER JOIN repos ON projects.repo_id = repos.id").
24+
Where("repos.id = ?", repo.ID).
25+
Where("projects.name = ?", name).First(&project).Error
26+
27+
if err != nil {
28+
if errors.Is(err, gorm.ErrRecordNotFound) {
29+
return nil, nil
30+
}
31+
log.Printf("Unknown error occurred while fetching database, %v\n", err)
32+
return nil, err
33+
}
34+
35+
return &project, nil
36+
}
37+
38+
func (db *Database) CreateProject(name string, repo *model.Repo) (*model.Project, error) {
39+
project := &model.Project{
40+
Name: name,
41+
RepoID: repo.ID,
42+
DriftStatus: DriftStatusNewDrift,
43+
}
44+
result := db.GormDB.Save(project)
45+
if result.Error != nil {
46+
log.Printf("Failed to create project: %v, error: %v\n", name, result.Error)
47+
return nil, result.Error
48+
}
49+
log.Printf("Project %s, (id: %v) has been created successfully\n", name, project.ID)
50+
return project, nil
51+
}

ee/drift/dbmodels/repos.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package dbmodels
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"github.com/diggerhq/digger/ee/drift/model"
7+
configuration "github.com/diggerhq/digger/libs/digger_config"
8+
"gorm.io/gorm"
9+
"log"
10+
)
11+
12+
// GetRepo returns digger repo by organisationId and repo name (diggerhq-digger)
13+
// it will return an empty object if record doesn't exist in database
14+
func (db *Database) GetRepo(orgIdKey any, repoName string) (*model.Repo, error) {
15+
var repo model.Repo
16+
17+
err := db.GormDB.Where("organisation_id = ? AND repos.name=?", orgIdKey, repoName).First(&repo).Error
18+
19+
if err != nil {
20+
if errors.Is(err, gorm.ErrRecordNotFound) {
21+
return nil, nil
22+
}
23+
log.Printf("Failed to find digger repo for orgId: %v, and repoName: %v, error: %v\n", orgIdKey, repoName, err)
24+
return nil, err
25+
}
26+
return &repo, nil
27+
}
28+
29+
func (db *Database) RefreshProjectsFromRepo(orgId string, config configuration.DiggerConfigYaml, repo *model.Repo) error {
30+
log.Printf("UpdateRepoDiggerConfig, repo: %v\n", repo)
31+
32+
err := db.GormDB.Transaction(func(tx *gorm.DB) error {
33+
for _, dc := range config.Projects {
34+
projectName := dc.Name
35+
p, err := db.GetProjectByName(orgId, repo, projectName)
36+
if err != nil {
37+
return fmt.Errorf("error retriving project by name: %v", err)
38+
}
39+
if p == nil {
40+
_, err := db.CreateProject(projectName, repo)
41+
if err != nil {
42+
return fmt.Errorf("could not create project: %v", err)
43+
}
44+
}
45+
}
46+
return nil
47+
})
48+
49+
if err != nil {
50+
return fmt.Errorf("error while updating projects from config: %v", err)
51+
}
52+
return nil
53+
}

ee/drift/dbmodels/storage.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func (db *Database) GetOrganisationById(orgId any) (*model.Organisation, error)
2121
func (db *Database) CreateGithubInstallationLink(orgId string, installationId string) (*model.GithubAppInstallationLink, error) {
2222
l := model.GithubAppInstallationLink{}
2323
// check if there is already a link to another org, and throw an error in this case
24-
result := db.GormDB.Preload("Organisation").Where("github_installation_id = ? AND status=?", installationId, GithubAppInstallationLinkActive).Find(&l)
24+
result := db.GormDB.Where("github_installation_id = ? AND status=?", installationId, GithubAppInstallationLinkActive).Find(&l)
2525
if result.Error != nil {
2626
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
2727
return nil, result.Error
@@ -38,7 +38,7 @@ func (db *Database) CreateGithubInstallationLink(orgId string, installationId st
3838

3939
var list []model.GithubAppInstallationLink
4040
// if there are other installation for this org, we need to make them inactive
41-
result = db.GormDB.Preload("Organisation").Where("github_installation_id <> ? AND organisation_id = ? AND status=?", installationId, orgId, GithubAppInstallationLinkActive).Find(&list)
41+
result = db.GormDB.Where("github_installation_id <> ? AND organisation_id = ? AND status=?", installationId, orgId, GithubAppInstallationLinkActive).Find(&list)
4242
if result.Error != nil {
4343
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
4444
return nil, result.Error
@@ -58,10 +58,10 @@ func (db *Database) CreateGithubInstallationLink(orgId string, installationId st
5858
return &link, nil
5959
}
6060

61-
func (db *Database) CreateRepo(name string, repoFullName string, repoOrganisation string, repoName string, repoUrl string, org *model.Organisation, diggerConfig string, githubInstallationId int64, githubAppId int64, accountId int64, login string) (*model.Repo, error) {
61+
func (db *Database) CreateRepo(name string, repoFullName string, repoOrganisation string, repoName string, repoUrl string, org *model.Organisation, diggerConfig string, githubInstallationId string, githubAppId int64, accountId int64, login string) (*model.Repo, error) {
6262
var repo model.Repo
6363
// check if repo exist already, do nothing in this case
64-
result := db.GormDB.Where("name = ? AND organization_id=?", name, org.ID).Find(&repo)
64+
result := db.GormDB.Where("name = ? AND organisation_id=?", name, org.ID).Find(&repo)
6565
if result.Error != nil {
6666
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
6767
return nil, result.Error
@@ -92,3 +92,20 @@ func (db *Database) CreateRepo(name string, repoFullName string, repoOrganisatio
9292
log.Printf("Repo %s, (id: %v) has been created successfully\n", name, repo.ID)
9393
return &repo, nil
9494
}
95+
96+
// GetGithubAppInstallationByIdAndRepo repoFullName should be in the following format: org/repo_name, for example "diggerhq/github-job-scheduler"
97+
func (db *Database) GetRepoByInstllationIdAndRepoFullName(installationId string, repoFullName string) (*model.Repo, error) {
98+
repo := model.Repo{}
99+
result := db.GormDB.Where("github_installation_id = ? AND repo_full_name=?", installationId, repoFullName).Find(&repo)
100+
if result.Error != nil {
101+
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
102+
return nil, result.Error
103+
}
104+
}
105+
106+
// If not found, the values will be default values, which means ID will be 0
107+
if repo.ID == "" {
108+
return nil, fmt.Errorf("GithubAppInstallation with id=%v doesn't exist", installationId)
109+
}
110+
return &repo, nil
111+
}

ee/drift/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func main() {
6969
//authorized := r.Group("/")
7070
//authorized.Use(middleware.GetApiMiddleware(), middleware.AccessLevel(dbmodels.CliJobAccessType, dbmodels.AccessPolicyType, models.AdminPolicyType))
7171

72+
r.POST("github-app-webhook", controller.GithubAppWebHook)
7273
r.GET("/github/callback_fe", middleware.WebhookAuth(), controller.GithubAppCallbackPage)
7374

7475
port := os.Getenv("DIGGER_PORT")

ee/drift/model/projects.gen.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ee/drift/model/repos.gen.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ee/drift/model/user_settings.gen.go

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ee/drift/model/users.gen.go

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)