Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: opt-in gh app integration #1217

Open
wants to merge 64 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
e939def
feat: gh-app poc
plyr4 Sep 25, 2024
fda1f08
chore: merge with main
plyr4 Sep 30, 2024
e25871d
Merge branch 'main' of github.com:go-vela/server into feat/gh-app
plyr4 Oct 3, 2024
e032e0b
chore: merge with main
plyr4 Oct 4, 2024
545451b
Merge branch 'main' of github.com:go-vela/server into feat/gh-app
plyr4 Oct 4, 2024
cacaaf9
wip: debug files, todos, random code
plyr4 Oct 4, 2024
7e8a676
refactor: required changes from api build types migration
plyr4 Oct 4, 2024
4834c0d
Merge branch 'refactor/api/build' of github.com:go-vela/server into f…
plyr4 Oct 4, 2024
37b30a1
feat: cli auth repo install flow
plyr4 Oct 8, 2024
e84299d
feat: clone token
plyr4 Oct 12, 2024
aa918f0
feat: installation tokens as netrc
plyr4 Oct 12, 2024
b2cb87a
feat: yaml driven git token access
plyr4 Oct 12, 2024
38e8db6
chore: move all web flow code into single file
plyr4 Oct 14, 2024
cbe94e6
chore: moving more code to isolated file for future reference
plyr4 Oct 14, 2024
8b022a3
chore: removing github app web flow install code
plyr4 Oct 14, 2024
7d5d474
chore: wip
plyr4 Oct 15, 2024
c1197eb
feat: webhook handler for repo installation events
plyr4 Oct 22, 2024
f9e7d77
fix: remove install_id when app is deleted
plyr4 Oct 22, 2024
69e89a1
fix: use deleted action to specify added/removed repos
plyr4 Oct 22, 2024
9aca5ff
feat: webhook handlers, debug code cleanup, installation redirect
plyr4 Oct 23, 2024
4238190
chore: remove launch.json
plyr4 Oct 23, 2024
a08d7e5
chore: revert newline
plyr4 Oct 23, 2024
c8affa7
chore: code cleanup, revisions, yaml finalization
plyr4 Oct 23, 2024
8587d2c
chore: more cleanup, wip customizable token permission set
plyr4 Oct 23, 2024
cc81d1b
chore: gut required code from ghinstallation
plyr4 Oct 24, 2024
bd9cabe
chore: go mod tidy
plyr4 Oct 24, 2024
43f4ae8
chore: revert local debug
plyr4 Oct 24, 2024
35954df
chore: merge with main, broken
plyr4 Oct 24, 2024
96f06a3
chore: merge with main, fixed
plyr4 Oct 24, 2024
a218157
fix: imports
plyr4 Oct 24, 2024
949b385
chore: merge with main
plyr4 Oct 24, 2024
6efbf90
chore: more cleanup
plyr4 Oct 24, 2024
9df0c04
enhance: code cleanup, moved netrc defaults
plyr4 Oct 24, 2024
9654dbc
enhance: cleanup and organization
plyr4 Oct 25, 2024
b60da20
enhance: added repo sync to create and repair
plyr4 Oct 25, 2024
c014424
fix: remove checks
plyr4 Oct 29, 2024
9f0faeb
chore: merge with main
plyr4 Oct 29, 2024
771d1a6
chore: cleanup and linting
plyr4 Oct 29, 2024
51960e2
chore: cleanup and linting
plyr4 Oct 29, 2024
6ec7bbc
chore: cleanup and linting
plyr4 Oct 29, 2024
a3d0821
chore: more linting
plyr4 Oct 29, 2024
47e29b4
chore: more linting
plyr4 Oct 29, 2024
03c4232
chore: more linting
plyr4 Oct 29, 2024
836d5cf
chore: constants, cleanup, permissions helper
plyr4 Oct 29, 2024
858da17
enhance: constants, helper funcs, reorganize client code, apply traci…
plyr4 Oct 29, 2024
249099d
fix: unexport func
plyr4 Oct 29, 2024
9410191
fix: repo.install_id test updates
plyr4 Oct 29, 2024
e239480
chore: cleanup
plyr4 Oct 29, 2024
b19e50e
fix: tests
plyr4 Oct 29, 2024
9d62db7
fix: tests
plyr4 Oct 29, 2024
618152d
fix: move netrc defaults into github implementation
plyr4 Oct 29, 2024
744cb00
enhance: new webhook tests
plyr4 Oct 29, 2024
9e0c98f
enhance: new scm tests
plyr4 Oct 29, 2024
ebf1786
enhance: helper for installationCanReadRepo
plyr4 Oct 30, 2024
6be732d
chore: more tests (installationCanReadRepo)
plyr4 Oct 30, 2024
ffae869
chore: more tests (app transport)
plyr4 Oct 30, 2024
8668615
fix: gci
plyr4 Oct 30, 2024
2246b3d
fix: set db in the compiler to support repo sync
plyr4 Oct 30, 2024
ac86e63
fix: only update when necessary, fix test
plyr4 Oct 30, 2024
3916cbe
fix: return repo
plyr4 Oct 31, 2024
198e06e
Merge branch 'main' into feat/gh-app
plyr4 Nov 5, 2024
f7d4723
chore: merge with main
plyr4 Nov 5, 2024
fae9c1f
chore: merge with main
plyr4 Nov 5, 2024
8e0fe7f
chore: lint
plyr4 Nov 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions api/auth/get_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package auth
import (
"fmt"
"net/http"
"strconv"

"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -36,6 +37,10 @@ import (
// name: redirect_uri
// description: The URL where the user will be sent after authorization
// type: string
// - in: query
// name: setup_action
// description: The specific setup action callback identifier
// type: string
// responses:
// '200':
// description: Successfully authenticated
Expand All @@ -46,6 +51,10 @@ import (
// "$ref": "#/definitions/Token"
// '307':
// description: Redirected for authentication
// '400':
// description: Bad Request
// schema:
// "$ref": "#/definitions/Error"
// '401':
// description: Unauthorized
// schema:
Expand All @@ -69,6 +78,32 @@ func GetAuthToken(c *gin.Context) {
// capture the OAuth state if present
oAuthState := c.Request.FormValue("state")

// handle scm setup events
// setup_action==install represents the GitHub App installation callback redirect
if c.Request.FormValue("setup_action") == "install" {
installID, err := strconv.ParseInt(c.Request.FormValue("installation_id"), 10, 0)
if err != nil {
retErr := fmt.Errorf("unable to parse installation_id: %w", err)

util.HandleError(c, http.StatusBadRequest, retErr)

return
}

r, err := scm.FromContext(c).FinishInstallation(ctx, c.Request, installID)
if err != nil {
retErr := fmt.Errorf("unable to finish installation: %w", err)

util.HandleError(c, http.StatusInternalServerError, retErr)

return
}

c.Redirect(http.StatusTemporaryRedirect, r)

return
}

// capture the OAuth code if present
code := c.Request.FormValue("code")
if len(code) == 0 {
Expand Down
1 change: 1 addition & 0 deletions api/build/compile_publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ func CompileAndPublish(
WithRepo(repo).
WithUser(u).
WithLabels(cfg.Labels).
WithSCM(scm).
Compile(ctx, pipelineFile)
if err != nil {
// format the error message with extra information
Expand Down
2 changes: 1 addition & 1 deletion api/build/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func PlanBuild(ctx context.Context, database database.Interface, scm scm.Service
}

// plan all steps for the build
steps, err := step.PlanSteps(ctx, database, scm, p, b)
steps, err := step.PlanSteps(ctx, database, scm, p, b, r)
if err != nil {
// clean up the objects from the pipeline in the database
CleanBuild(ctx, database, b, services, steps, err)
Expand Down
14 changes: 14 additions & 0 deletions api/repo/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,18 @@ func CreateRepo(c *gin.Context) {
}
}

// map this repo to an installation if possible
if r.GetInstallID() == 0 {
r, err = scm.FromContext(c).SyncRepoWithInstallation(ctx, r)
if err != nil {
retErr := fmt.Errorf("unable to sync repo %s with installation: %w", r.GetFullName(), err)

util.HandleError(c, http.StatusInternalServerError, retErr)

return
}
}

// if the repo exists but is inactive
if len(dbRepo.GetOrg()) > 0 && !dbRepo.GetActive() {
// update the repo owner
Expand All @@ -281,6 +293,8 @@ func CreateRepo(c *gin.Context) {
dbRepo.SetBranch(r.GetBranch())
// activate the repo
dbRepo.SetActive(true)
// update the install_id
dbRepo.SetInstallID(r.GetInstallID())

// send API call to update the repo
// NOTE: not logging modification out separately
Expand Down
33 changes: 30 additions & 3 deletions api/repo/repair.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

// RepairRepo represents the API handler to remove
// and then create a webhook for a repo.
func RepairRepo(c *gin.Context) {

Check failure on line 65 in api/repo/repair.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/repo/repair.go#L65

Function 'RepairRepo' has too many statements (75 > 70) (funlen)
Raw output
api/repo/repair.go:65: Function 'RepairRepo' has too many statements (75 > 70) (funlen)
func RepairRepo(c *gin.Context) {
// capture middleware values
m := c.MustGet("metadata").(*internal.Metadata)
l := c.MustGet("logger").(*logrus.Entry)
Expand Down Expand Up @@ -163,21 +163,48 @@
}
}

dirty := false

// if the repo was previously inactive, mark it as active
if !r.GetActive() {
r.SetActive(true)

// send API call to update the repo
dirty = true

l.Tracef("repo %s repaired - set to active", r.GetFullName())
}

// map this repo to an installation, if possible
if r.GetInstallID() == 0 {
r, err = scm.FromContext(c).SyncRepoWithInstallation(ctx, r)
if err != nil {
retErr := fmt.Errorf("unable to sync repo %s with installation: %w", r.GetFullName(), err)

util.HandleError(c, http.StatusInternalServerError, retErr)

return
}

// install_id was synced
if r.GetInstallID() != 0 {
dirty = true

l.Tracef("repo %s repaired - set install_id to %d", r.GetFullName(), r.GetInstallID())
}
}

// update the repo in the database, if necessary
if dirty {
_, err := database.FromContext(c).UpdateRepo(ctx, r)
if err != nil {
retErr := fmt.Errorf("unable to set repo %s to active: %w", r.GetFullName(), err)
retErr := fmt.Errorf("unable to update repo %s during repair: %w", r.GetFullName(), err)

util.HandleError(c, http.StatusInternalServerError, retErr)

return
}

l.Infof("repo %s updated - set to active", r.GetFullName())
l.Infof("repo %s repaired - database updated", r.GetFullName())
}

c.JSON(http.StatusOK, fmt.Sprintf("repo %s repaired", r.GetFullName()))
Expand Down
2 changes: 1 addition & 1 deletion api/step/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
// PlanSteps is a helper function to plan all steps
// in the build for execution. This creates the steps
// for the build.
func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service, p *pipeline.Build, b *types.Build) ([]*types.Step, error) {
func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service, p *pipeline.Build, b *types.Build, r *types.Repo) ([]*types.Step, error) {

Check failure on line 22 in api/step/plan.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/step/plan.go#L22

unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
Raw output
api/step/plan.go:22:118: unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service, p *pipeline.Build, b *types.Build, r *types.Repo) ([]*types.Step, error) {
                                                                                                                     ^
// variable to store planned steps
steps := []*types.Step{}

Expand Down
29 changes: 29 additions & 0 deletions api/types/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Repo struct {
PipelineType *string `json:"pipeline_type,omitempty"`
PreviousName *string `json:"previous_name,omitempty"`
ApproveBuild *string `json:"approve_build,omitempty"`
InstallID *int64 `json:"install_id,omitempty"`
}

// Environment returns a list of environment variables
Expand Down Expand Up @@ -345,6 +346,19 @@ func (r *Repo) GetApproveBuild() string {
return *r.ApproveBuild
}

// GetInstallID returns the InstallID field.
//
// When the provided Repo type is nil, or the field within
// the type is nil, it returns the zero value for the field.
func (r *Repo) GetInstallID() int64 {
// return zero value if Repo type or InstallID field is nil
if r == nil || r.InstallID == nil {
return 0
}

return *r.InstallID
}

// SetID sets the ID field.
//
// When the provided Repo type is nil, it
Expand Down Expand Up @@ -618,6 +632,19 @@ func (r *Repo) SetApproveBuild(v string) {
r.ApproveBuild = &v
}

// SetInstallID sets the InstallID field.
//
// When the provided Repo type is nil, it
// will set nothing and immediately return.
func (r *Repo) SetInstallID(v int64) {
// return if Repo type is nil
if r == nil {
return
}

r.InstallID = &v
}

// String implements the Stringer interface for the Repo type.
func (r *Repo) String() string {
return fmt.Sprintf(`{
Expand All @@ -630,6 +657,7 @@ func (r *Repo) String() string {
Counter: %d,
FullName: %s,
ID: %d,
InstallID: %d,
Link: %s,
Name: %s,
Org: %s,
Expand All @@ -651,6 +679,7 @@ func (r *Repo) String() string {
r.GetCounter(),
r.GetFullName(),
r.GetID(),
r.GetInstallID(),
r.GetLink(),
r.GetName(),
r.GetOrg(),
Expand Down
15 changes: 15 additions & 0 deletions api/webhook/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
// capture middleware values
m := c.MustGet("metadata").(*internal.Metadata)
l := c.MustGet("logger").(*logrus.Entry)
db := database.FromContext(c)
ctx := c.Request.Context()

l.Debug("webhook received")
Expand Down Expand Up @@ -133,6 +134,20 @@
return
}

if webhook.Installation != nil {
err = scm.FromContext(c).ProcessInstallation(ctx, c.Request, webhook, db)
if err != nil {
retErr := fmt.Errorf("unable to process installation: %w", err)
util.HandleError(c, http.StatusBadRequest, retErr)

return
}

c.JSON(http.StatusOK, "installation processed successfully")

return
}

// check if the hook should be skipped
if skip, skipReason := webhook.ShouldSkip(); skip {
c.JSON(http.StatusOK, fmt.Sprintf("skipping build: %s", skipReason))
Expand Down Expand Up @@ -189,7 +204,7 @@

defer func() {
// send API call to update the webhook
_, err = database.FromContext(c).UpdateHook(ctx, h)

Check failure on line 207 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L207

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:207:32: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		_, err = database.FromContext(c).UpdateHook(ctx, h)
		                             ^
if err != nil {
l.Errorf("unable to update webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err)
}
Expand Down Expand Up @@ -658,7 +673,7 @@
case "archived", "unarchived", constants.ActionEdited:
l.Debugf("repository action %s for %s", h.GetEventAction(), r.GetFullName())
// send call to get repository from database
dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())

Check failure on line 676 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L676

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:676:38: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())
		                                   ^
if err != nil {
retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err)

Expand All @@ -669,7 +684,7 @@
}

// send API call to capture the last hook for the repo
lastHook, err := database.FromContext(c).LastHookForRepo(ctx, dbRepo)

Check failure on line 687 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L687

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:687:40: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		lastHook, err := database.FromContext(c).LastHookForRepo(ctx, dbRepo)
		                                     ^
if err != nil {
retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err)

Expand Down
4 changes: 3 additions & 1 deletion cmd/vela-server/scm.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ func setupSCM(c *cli.Context, tc *tracing.Client) (scm.Service, error) {
Address: c.String("scm.addr"),
ClientID: c.String("scm.client"),
ClientSecret: c.String("scm.secret"),
AppID: c.Int64("scm.app.id"),
AppPrivateKey: c.String("scm.app.private_key"),
ServerAddress: c.String("server-addr"),
ServerWebhookAddress: c.String("scm.webhook.addr"),
StatusContext: c.String("scm.context"),
Expand All @@ -31,5 +33,5 @@ func setupSCM(c *cli.Context, tc *tracing.Client) (scm.Service, error) {
// setup the scm
//
// https://pkg.go.dev/github.com/go-vela/server/scm?tab=doc#New
return scm.New(_setup)
return scm.New(c.Context, _setup)
}
4 changes: 4 additions & 0 deletions compiler/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/go-vela/server/compiler/types/raw"
"github.com/go-vela/server/compiler/types/yaml"
"github.com/go-vela/server/internal"
"github.com/go-vela/server/scm"
)

// Engine represents an interface for converting a yaml
Expand Down Expand Up @@ -146,6 +147,9 @@ type Engine interface {
// WithLabel defines a function that sets
// the label(s) in the Engine.
WithLabels([]string) Engine
// WithSCM defines a function that sets
// the scm in the Engine.
WithSCM(scm.Service) Engine
// WithPrivateGitHub defines a function that sets
// the private github client in the Engine.
WithPrivateGitHub(context.Context, string, string) Engine
Expand Down
25 changes: 25 additions & 0 deletions compiler/native/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,31 @@
return nil, nil, err
}

// create the netrc using the scm
// this has to occur after Parse because the scm configurations might be set in yaml
// netrc can be provided directly using WithNetrc for situations like local exec
if c.netrc == nil && c.scm != nil {
// ensure restrictive defaults for the netrc for scms that support granular permissions
if p.Git.Repositories == nil {
p.Git.Repositories = []string{c.repo.GetName()}
}

if p.Git.Permissions == nil {
p.Git.Permissions = map[string]string{
constants.AppInstallResourceContents: constants.AppInstallPermissionRead,
constants.AppInstallResourceChecks: constants.AppInstallPermissionWrite,
}
}

// get the netrc password from the scm
netrc, err := c.scm.GetNetrcPassword(context.Background(), c.repo, c.user, p.Git.Repositories, p.Git.Permissions)
if err != nil {
return nil, nil, err
}

c.WithNetrc(netrc)
}

// create the API pipeline object from the yaml configuration
_pipeline := p.ToPipelineAPI()
_pipeline.SetData(data)
Expand Down Expand Up @@ -330,7 +355,7 @@

if c.ModificationService.Endpoint != "" {
// send config to external endpoint for modification
p, err = c.modifyConfig(p, c.build, c.repo)

Check failure on line 358 in compiler/native/compile.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] compiler/native/compile.go#L358

Function `modifyConfig` should pass the context parameter (contextcheck)
Raw output
compiler/native/compile.go:358:26: Function `modifyConfig` should pass the context parameter (contextcheck)
		p, err = c.modifyConfig(p, c.build, c.repo)
		                       ^
if err != nil {
return nil, _pipeline, err
}
Expand Down Expand Up @@ -425,7 +450,7 @@

if c.ModificationService.Endpoint != "" {
// send config to external endpoint for modification
p, err = c.modifyConfig(p, c.build, c.repo)

Check failure on line 453 in compiler/native/compile.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] compiler/native/compile.go#L453

Function `modifyConfig` should pass the context parameter (contextcheck)
Raw output
compiler/native/compile.go:453:26: Function `modifyConfig` should pass the context parameter (contextcheck)
		p, err = c.modifyConfig(p, c.build, c.repo)
		                       ^
if err != nil {
return nil, _pipeline, err
}
Expand Down
Loading
Loading