Skip to content

Commit

Permalink
Merge pull request #1723 from diggerhq/feat-drift-app
Browse files Browse the repository at this point in the history
feat drift app
  • Loading branch information
ZIJ authored Sep 25, 2024
2 parents 0720b5b + 2465514 commit 616a3cd
Show file tree
Hide file tree
Showing 35 changed files with 5,485 additions and 1 deletion.
17 changes: 17 additions & 0 deletions .github/workflows/drift_deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: driftapp Deploy
on:
push:
branches:
- develop # change to main if needed
- feat-drift-app
jobs:
deploy:
name: Deploy app
runs-on: ubuntu-latest
concurrency: deploy-group # optional: ensure only one action runs at a time
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only --config fly-drift.toml
env:
FLY_API_TOKEN: ${{ secrets.FLYIO_DRIFT_TOKEN }}
49 changes: 49 additions & 0 deletions Dockerfile_drift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
FROM golang:1.22 as builder
ARG COMMIT_SHA
RUN echo "commit sha: ${COMMIT_SHA}"

# Set the working directory
WORKDIR $GOPATH/src/github.com/diggerhq/digger

# Copy all required source, blacklist files that are not required through `.dockerignore`
COPY . .

# Get the vendor library
RUN go version

# RUN vgo install

# https://github.com/ethereum/go-ethereum/issues/2738
# Build static binary "-getmode=vendor" does not work with go-ethereum

RUN go build -ldflags="-X 'main.Version=${COMMIT_SHA}'" -o drift_exe ./ee/drift/

# Multi-stage build will just copy the binary to an alpine image.
FROM ubuntu:24.04 as runner
ENV ATLAS_VERSION v0.16.0
ARG COMMIT_SHA
WORKDIR /app

RUN apt-get update && apt-get install -y ca-certificates curl && apt-get install -y git && apt-get clean all
RUN update-ca-certificates

RUN echo "commit sha: ${COMMIT_SHA}"

# install atlas
RUN curl -sSf https://atlasgo.sh | sh



# Set gin to production
#ENV GIN_MODE=release

# Expose the running port
EXPOSE 3000

# Copy the binary to the corresponding folder
COPY --from=builder /go/src/github.com/diggerhq/digger/drift_exe /app/driftapp
COPY --from=builder /go/src/github.com/diggerhq/digger/ee/drift/scripts/entrypoint.sh /app/entrypoint.sh
ADD ee/backend/templates ./templates

# Run the binary
ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"]
3 changes: 3 additions & 0 deletions ee/drift/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# flyctl launch added from .gitignore
**/.env
fly.toml
3 changes: 3 additions & 0 deletions ee/drift/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
main
drift
44 changes: 44 additions & 0 deletions ee/drift/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
FROM golang:1.22 as builder
ARG COMMIT_SHA
RUN echo "commit sha: ${COMMIT_SHA}"

# Set the working directory
WORKDIR $GOPATH/src/github.com/diggerhq/drift

# Copy all required source, blacklist files that are not required through `.dockerignore`
COPY . .

# Get the vendor library
RUN go version

# RUN vgo install

# https://github.com/ethereum/go-ethereum/issues/2738
# Build static binary "-getmode=vendor" does not work with go-ethereum

RUN go build -ldflags="-X 'main.Version=${COMMIT_SHA}'" -o driftapp_exe ./

# Multi-stage build will just copy the binary to an alpine image.
FROM ubuntu:24.04 as runner
ENV ATLAS_VERSION v0.16.0
ARG COMMIT_SHA
WORKDIR /app

RUN apt-get update && apt-get install -y ca-certificates curl && apt-get install -y git && apt-get clean all
RUN update-ca-certificates

RUN echo "commit sha: ${COMMIT_SHA}"


# Set gin to production
#ENV GIN_MODE=release

# Expose the running port
EXPOSE 3000

# Copy the binary to the corresponding folder
COPY --from=builder /go/src/github.com/diggerhq/drift/driftapp_exe /app/driftapp
COPY --from=builder /go/src/github.com/diggerhq/drift/scripts/entrypoint.sh /app/entrypoint.sh

# Run the binary
ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"]
7 changes: 7 additions & 0 deletions ee/drift/controllers/controllers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package controllers

import "github.com/diggerhq/digger/next/utils"

type MainController struct {
GithubClientProvider utils.GithubClientProvider
}
177 changes: 177 additions & 0 deletions ee/drift/controllers/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package controllers

import (
"context"
"encoding/json"
"fmt"
"github.com/diggerhq/digger/ee/drift/dbmodels"
"github.com/diggerhq/digger/ee/drift/middleware"
"github.com/diggerhq/digger/ee/drift/model"
next_utils "github.com/diggerhq/digger/next/utils"
"github.com/gin-gonic/gin"
"github.com/google/go-github/v61/github"
"golang.org/x/oauth2"
"log"
"net/http"
"os"
"strconv"
"strings"
)

func (mc MainController) GithubAppCallbackPage(c *gin.Context) {
installationId := c.Request.URL.Query()["installation_id"][0]
//setupAction := c.Request.URL.Query()["setup_action"][0]
code := c.Request.URL.Query()["code"][0]
clientId := os.Getenv("GITHUB_APP_CLIENT_ID")
clientSecret := os.Getenv("GITHUB_APP_CLIENT_SECRET")

installationId64, err := strconv.ParseInt(installationId, 10, 64)
if err != nil {
log.Printf("err: %v", err)
c.String(http.StatusInternalServerError, "Failed to parse installation_id.")
return
}

result, installation, err := validateGithubCallback(mc.GithubClientProvider, clientId, clientSecret, code, installationId64)
if !result {
log.Printf("Failed to validated installation id, %v\n", err)
c.String(http.StatusInternalServerError, "Failed to validate installation_id.")
return
}

// retrive org for current orgID
orgId, exists := c.Get(middleware.ORGANISATION_ID_KEY)
if !exists {
log.Printf("missing argument orgId in github callback")
c.String(http.StatusBadRequest, "missing orgID in request")
return
}
org, err := dbmodels.DB.GetOrganisationById(orgId)
if err != nil {
log.Printf("Error fetching organisation: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error fetching organisation"})
return
}

// create a github installation link (org ID matched to installation ID)
_, err = dbmodels.DB.CreateGithubInstallationLink(org.ID, installationId)
if err != nil {
log.Printf("Error saving GithubInstallationLink to database: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error updating GitHub installation"})
return
}

client, _, err := mc.GithubClientProvider.Get(*installation.AppID, installationId64)
if err != nil {
log.Printf("Error retriving github client: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error fetching organisation"})
return

}

// we get repos accessible to this installation
listRepos, _, err := client.Apps.ListRepos(context.Background(), nil)
if err != nil {
log.Printf("Failed to validated list existing repos, %v\n", err)
c.String(http.StatusInternalServerError, "Failed to list existing repos: %v", err)
return
}
repos := listRepos.Repositories

// reset all existing repos (soft delete)
var ExistingRepos []model.Repo
err = dbmodels.DB.GormDB.Delete(ExistingRepos, "organization_id=?", orgId).Error
if err != nil {
log.Printf("could not delete repos: %v", err)
c.String(http.StatusInternalServerError, "could not delete repos: %v", err)
return
}

// here we mark repos that are available one by one
for _, repo := range repos {
repoFullName := *repo.FullName
repoOwner := strings.Split(*repo.FullName, "/")[0]
repoName := *repo.Name
repoUrl := fmt.Sprintf("https://github.com/%v", repoFullName)

_, _, err = dbmodels.CreateOrGetDiggerRepoForGithubRepo(repoFullName, repoOwner, repoName, repoUrl, installationId64, *installation.AppID, *installation.Account.ID, *installation.Account.Login)
if err != nil {
log.Printf("createOrGetDiggerRepoForGithubRepo error: %v", err)
c.String(http.StatusInternalServerError, "createOrGetDiggerRepoForGithubRepo error: %v", err)
return
}
}

c.HTML(http.StatusOK, "github_success.tmpl", gin.H{})
}

// why this validation is needed: https://roadie.io/blog/avoid-leaking-github-org-data/
// validation based on https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app , step 3
func validateGithubCallback(githubClientProvider next_utils.GithubClientProvider, clientId string, clientSecret string, code string, installationId int64) (bool, *github.Installation, error) {
ctx := context.Background()
type OAuthAccessResponse struct {
AccessToken string `json:"access_token"`
}
httpClient := http.Client{}

githubHostname := "github.com"
reqURL := fmt.Sprintf("https://%v/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", githubHostname, clientId, clientSecret, code)
req, err := http.NewRequest(http.MethodPost, reqURL, nil)
if err != nil {
return false, nil, fmt.Errorf("could not create HTTP request: %v\n", err)
}
req.Header.Set("accept", "application/json")

res, err := httpClient.Do(req)
if err != nil {
return false, nil, fmt.Errorf("request to login/oauth/access_token failed: %v\n", err)
}

if err != nil {
return false, nil, fmt.Errorf("Failed to read response's body: %v\n", err)
}

var t OAuthAccessResponse
if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
return false, nil, fmt.Errorf("could not parse JSON response: %v\n", err)
}

ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: t.AccessToken},
)
tc := oauth2.NewClient(ctx, ts)
//tc := &http.Client{
// Transport: &oauth2.Transport{
// Base: httpClient.Transport,
// Source: oauth2.ReuseTokenSource(nil, ts),
// },
//}

client, err := githubClientProvider.NewClient(tc)
if err != nil {
log.Printf("could create github client: %v", err)
return false, nil, fmt.Errorf("could not create github client: %v", err)
}

installationIdMatch := false
// list all installations for the user
var matchedInstallation *github.Installation
installations, _, err := client.Apps.ListUserInstallations(ctx, nil)
if err != nil {
log.Printf("could not retrieve installations: %v", err)
return false, nil, fmt.Errorf("could not retrieve installations: %v", installationId)
}
log.Printf("installations %v", installations)
for _, v := range installations {
log.Printf("installation id: %v\n", *v.ID)
if *v.ID == installationId {
matchedInstallation = v
installationIdMatch = true
}
}
if !installationIdMatch {
return false, nil, fmt.Errorf("InstallationId %v doesn't match any id for specified user\n", installationId)
}

return true, matchedInstallation, nil
}
9 changes: 9 additions & 0 deletions ee/drift/controllers/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package controllers

import (
"github.com/gin-gonic/gin"
)

func (mc MainController) Ping(c *gin.Context) {
c.String(200, "pong")
}
37 changes: 37 additions & 0 deletions ee/drift/dbgen/dbgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"os"

"gorm.io/driver/postgres"
"gorm.io/gen"
"gorm.io/gorm"
)

// Dynamic SQL
type Querier interface {
// SELECT * FROM @@table WHERE name = @name{{if role !=""}} AND role = @role{{end}}
FilterWithNameAndRole(name, role string) ([]gen.T, error)
}

func main() {
g := gen.NewGenerator(gen.Config{
OutPath: "../models_generated",
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode
})

dburl := os.Getenv("DB_URL")
if dburl == "" {
dburl = "postgresql://postgres:[email protected]:54322/postgres"
}
gormdb, _ := gorm.Open(postgres.Open(dburl))
g.UseDB(gormdb) // reuse your gorm db

g.ApplyBasic(
// Generate structs from all tables of current database
g.GenerateAllTable()...,
)

// Generate the code
g.Execute()
}
28 changes: 28 additions & 0 deletions ee/drift/dbgen/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module dbgen

go 1.22.0

require (
gorm.io/driver/postgres v1.5.9
gorm.io/gen v0.3.26
gorm.io/gorm v1.25.12
)

require (
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/hints v1.1.0 // indirect
gorm.io/plugin/dbresolver v1.5.3 // indirect
)
Loading

0 comments on commit 616a3cd

Please sign in to comment.