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: add github app #1070

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
74626c9
Merge branch 'main' of github.com:go-vela/server
jbrockopp Feb 14, 2023
974c8e6
Merge branch 'main' of github.com:go-vela/server
jbrockopp Mar 3, 2023
2f2c425
Merge branch 'main' of github.com:go-vela/server
jbrockopp Mar 20, 2023
528291a
Merge branch 'main' of github.com:go-vela/server
jbrockopp Apr 8, 2023
b53c687
Merge branch 'main' of github.com:go-vela/server
jbrockopp Apr 8, 2023
4734dcb
Merge branch 'main' of github.com:go-vela/server
jbrockopp Apr 16, 2023
1fb52df
Merge branch 'main' of github.com:go-vela/server
jbrockopp Apr 21, 2023
e996aa6
Merge branch 'main' of github.com:go-vela/server
jbrockopp Apr 27, 2023
c299ee4
Merge branch 'main' of github.com:go-vela/server
jbrockopp May 11, 2023
c8da9e3
Merge branch 'main' of github.com:go-vela/server
jbrockopp May 16, 2023
1ee254f
Merge branch 'main' of github.com:go-vela/server
jbrockopp May 22, 2023
8dd6033
Merge branch 'main' of github.com:go-vela/server
jbrockopp May 22, 2023
0eb92b1
Merge branch 'main' of github.com:go-vela/server
jbrockopp May 26, 2023
d5dcb6d
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 1, 2023
be8dd9a
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 5, 2023
73893b5
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 7, 2023
355017b
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 8, 2023
3e17278
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 8, 2023
d3c1e06
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 12, 2023
d63abfe
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 17, 2023
a353552
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 21, 2023
f1a8ab6
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 26, 2023
c23c599
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jun 28, 2023
f0a145d
Merge branch 'main' of github.com:go-vela/server
jbrockopp Jul 12, 2023
00e13f2
Merge branches 'main' and 'main' of github.com:go-vela/server
jbrockopp Jul 26, 2023
ccab2bf
Merge branch 'main' of github.com:go-vela/server
jbrockopp Aug 18, 2023
f4456e5
Merge branch 'main' of github.com:go-vela/server
jbrockopp Oct 15, 2023
8c14e0f
Merge branch 'main' of github.com:go-vela/server
jbrockopp Feb 27, 2024
6de5bcc
feat: initial start of github app
jbrockopp Feb 27, 2024
7b89f5b
feat: create token from github app
jbrockopp Feb 27, 2024
b011b7c
feat(scm): initial checks code
jbrockopp Feb 27, 2024
d4efd6b
chore: push up updates
jbrockopp Feb 27, 2024
8dec758
Merge branch 'main' into hackathon/github_app
ecrupper Jun 21, 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
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
19 changes: 14 additions & 5 deletions api/step/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// 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) ([]*library.Step, error) {
func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service, p *pipeline.Build, b *types.Build, r *types.Repo) ([]*library.Step, error) {
// variable to store planned steps
steps := []*library.Step{}

Expand All @@ -29,7 +29,7 @@ func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service
// iterate through all steps for each pipeline stage
for _, step := range stage.Steps {
// create the step object
s, err := planStep(ctx, database, scm, b, step, stage.Name)
s, err := planStep(ctx, database, scm, b, r, step, stage.Name)
if err != nil {
return steps, err
}
Expand All @@ -40,7 +40,7 @@ func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service

// iterate through all pipeline steps
for _, step := range p.Steps {
s, err := planStep(ctx, database, scm, b, step, "")
s, err := planStep(ctx, database, scm, b, r, step, "")
if err != nil {
return steps, err
}
Expand All @@ -51,7 +51,7 @@ func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service
return steps, nil
}

func planStep(ctx context.Context, database database.Interface, scm scm.Service, b *types.Build, c *pipeline.Container, stage string) (*library.Step, error) {
func planStep(ctx context.Context, database database.Interface, scm scm.Service, b *types.Build, r *types.Repo, c *pipeline.Container, stage string) (*library.Step, error) {
// create the step object
s := new(library.Step)
s.SetBuildID(b.GetID())
Expand All @@ -64,8 +64,17 @@ func planStep(ctx context.Context, database database.Interface, scm scm.Service,
s.SetReportAs(c.ReportAs)
s.SetCreated(time.Now().UTC().Unix())

id, err := scm.CreateChecks(ctx, r, b.GetCommit(), s.GetName())
if err != nil {
// TODO: make this error more meaningful
return nil, err
}

// TODO: have to store the check ID somewhere
s.SetCheckID(id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
s.SetCheckID undefined (type *library.Step has no field or method SetCheckID) (typecheck)


// send API call to create the step
s, err := database.CreateStep(ctx, s)
s, err = database.CreateStep(ctx, s)
if err != nil {
return nil, fmt.Errorf("unable to create step %s: %w", s.GetName(), err)
}
Expand Down
9 changes: 9 additions & 0 deletions api/step/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ func UpdateStep(c *gin.Context) {
return
}

err = scm.FromContext(c).UpdateChecks(ctx, r, s, b.GetCommit())
if err != nil {
retErr := fmt.Errorf("unable to set step check %s: %w", entry, err)

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

return
}

c.JSON(http.StatusOK, s)

// check if the build is in a "final" state
Expand Down
2 changes: 2 additions & 0 deletions cmd/vela-server/scm.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func setupSCM(c *cli.Context) (scm.Service, error) {
StatusContext: c.String("scm.context"),
WebUIAddress: c.String("webui-addr"),
Scopes: c.StringSlice("scm.scopes"),
GithubAppID: c.Int64("scm.app.id"),
GithubAppPrivateKey: c.String("scm.app.private_key"),
}

// setup the scm
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.22.0

toolchain go1.22.4

replace github.com/go-vela/types => ../types

require (
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
github.com/DATA-DOG/go-sqlmock v1.5.2
Expand All @@ -12,6 +14,7 @@ require (
github.com/adhocore/gronx v1.8.1
github.com/alicebob/miniredis/v2 v2.33.0
github.com/aws/aws-sdk-go v1.54.1
github.com/bradleyfalzon/ghinstallation/v2 v2.11.0
github.com/buildkite/yaml v0.0.0-20181016232759-0caa5f0796e3
github.com/distribution/reference v0.6.0
github.com/drone/envsubst v1.0.3
Expand Down Expand Up @@ -73,6 +76,7 @@ require (
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd3
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 h1:R9d0v+iobRHSaE4wKUnXFiZp53AL4ED5MzgEMwGTZag=
github.com/bradleyfalzon/ghinstallation/v2 v2.11.0/go.mod h1:0LWKQwOHewXO/1acI6TtyE0Xc4ObDb2rFN7eHBAG71M=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
Expand Down Expand Up @@ -91,12 +93,12 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-vela/types v0.24.0-rc2 h1:sdOtUL+pBjgD+4hFDrHN/8ZFXSICTnl+eYcRP2IZ0Wk=
github.com/go-vela/types v0.24.0-rc2/go.mod h1:YWj6BIapl9Kbj4yHq/fp8jltXdGiwD/gTy1ez32Rzag=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
Expand Down
12 changes: 12 additions & 0 deletions scm/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,16 @@ var Flags = []cli.Flag{
"is behind a Firewall or NAT, or when using something like ngrok to forward webhooks. " +
"(defaults to VELA_ADDR).",
},
&cli.Int64Flag{
EnvVars: []string{"VELA_SCM_APP_ID", "SCM_APP_ID"},
FilePath: "/vela/scm/app_id",
Name: "scm.app.id",
Usage: "(optional & experimental) ID for the GitHub App",
},
&cli.StringFlag{
EnvVars: []string{"VELA_SCM_APP_PRIVATE_KEY", "SCM_APP_PRIVATE_KEY"},
FilePath: "/vela/scm/app_private_key",
Name: "scm.app.private_key",
Usage: "(optional & experimental) path to private key for the GitHub App",
},
}
62 changes: 59 additions & 3 deletions scm/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ package github
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"

"github.com/bradleyfalzon/ghinstallation/v2"
api "github.com/go-vela/server/api/types"
"github.com/google/go-github/v62/github"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
Expand Down Expand Up @@ -44,12 +48,16 @@ type config struct {
WebUIAddress string
// specifies the OAuth scopes to use for the GitHub client
Scopes []string
// optional and experimental
GithubAppID int64
GithubAppPrivateKey string
}

type client struct {
config *config
OAuth *oauth2.Config
AuthReq *github.AuthorizationRequest
config *config
OAuth *oauth2.Config
AuthReq *github.AuthorizationRequest
AppsTransport *ghinstallation.AppsTransport
// https://pkg.go.dev/github.com/sirupsen/logrus#Entry
Logger *logrus.Entry
}
Expand Down Expand Up @@ -108,6 +116,17 @@ func New(opts ...ClientOpt) (*client, error) {
Scopes: githubScopes,
}

if c.config.GithubAppID != 0 && len(c.config.GithubAppPrivateKey) > 0 {
c.Logger.Infof("sourcing private key from path: %s", c.config.GithubAppPrivateKey)
transport, err := ghinstallation.NewAppsTransportKeyFromFile(http.DefaultTransport, c.config.GithubAppID, c.config.GithubAppPrivateKey)
if err != nil {
return nil, err
}

transport.BaseURL = c.config.API
c.AppsTransport = transport
}

return c, nil
}

Expand Down Expand Up @@ -163,3 +182,40 @@ func (c *client) newClientToken(token string) *github.Client {

return github
}

// helper function to return the GitHub App token.
func (c *client) newGithubAppToken(r *api.Repo) *github.Client {
// create a github client based off the existing GitHub App configuration
client, err := github.NewClient(&http.Client{Transport: c.AppsTransport}).WithEnterpriseURLs(c.config.API, c.config.API)
if err != nil {
panic(err)
}

// list all installations (a.k.a. orgs) where the GitHub App is installed
installations, _, err := client.Apps.ListInstallations(context.Background(), &github.ListOptions{})
if err != nil {
panic(err)
}

var id int64
// iterate through the list of installations
for _, install := range installations {
// find the installation that matches the org for the repo
if strings.EqualFold(install.GetAccount().GetLogin(), r.GetOrg()) {
id = install.GetID()
}
}

// failsafe in case the repo does not belong to an org where the GitHub App is installed
if id == 0 {
panic(err)
}

// create installation token for the repo
t, _, err := client.Apps.CreateInstallationToken(context.Background(), id, &github.InstallationTokenOptions{})
if err != nil {
panic(err)
}

return c.newClientToken(t.GetToken())
}
24 changes: 24 additions & 0 deletions scm/github/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,27 @@ func WithScopes(scopes []string) ClientOpt {
return nil
}
}

// WithGithubAppID sets the ID for the GitHub App in the scm client.
func WithGithubAppID(id int64) ClientOpt {
return func(c *client) error {
c.Logger.Trace("configuring ID for GitHub App in github scm client")

// set the ID for the GitHub App in the github client
c.config.GithubAppID = id

return nil
}
}

// WithGithubPrivateKey sets the private key for the GitHub App in the scm client.
func WithGithubPrivateKey(key string) ClientOpt {
return func(c *client) error {
c.Logger.Trace("configuring private key for GitHub App in github scm client")

// set the private key for the GitHub App in the github client
c.config.GithubAppPrivateKey = key

return nil
}
}
72 changes: 72 additions & 0 deletions scm/github/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,3 +660,75 @@ func (c *client) GetBranch(ctx context.Context, r *api.Repo, branch string) (str

return data.GetName(), data.GetCommit().GetSHA(), nil
}

// CreateChecks defines a function that does stuff...
func (c *client) CreateChecks(ctx context.Context, r *api.Repo, commit, step string) (int64, error) {
// create client from GitHub App
client := c.newGithubAppToken(r)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
Function newGithubAppToken->newClientToken should pass the context parameter (contextcheck)


opts := github.CreateCheckRunOptions{
Name: fmt.Sprintf("vela-%s-%s", commit, step),
HeadSHA: commit,
}

check, _, err := client.Checks.CreateCheckRun(ctx, r.GetOrg(), r.GetName(), opts)
if err != nil {
return 0, err
}

return check.GetID(), nil
}

// UpdateChecks defines a function that does stuff...
func (c *client) UpdateChecks(ctx context.Context, r *api.Repo, s *library.Step, commit string) error {
// create client from GitHub App
client := c.newGithubAppToken(r)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
Function newGithubAppToken->newClientToken should pass the context parameter (contextcheck)


var (
conclusion string
status string
)
// set the conclusion and status for the step check depending on what the status of the step is
switch s.GetStatus() {
case constants.StatusPending:
conclusion = "neutral"
status = "queued"
case constants.StatusPendingApproval:
conclusion = "action_required"
status = "queued"
case constants.StatusRunning:
conclusion = "neutral"
status = "in_progress"
case constants.StatusSuccess:
conclusion = "success"
status = "completed"
case constants.StatusFailure:
conclusion = "failure"
status = "completed"
case constants.StatusCanceled:
conclusion = "cancelled"
status = "completed"
case constants.StatusKilled:
conclusion = "cancelled"
status = "completed"
case constants.StatusSkipped:
conclusion = "skipped"
status = "completed"
default:
conclusion = "neutral"
status = "completed"
}

opts := github.UpdateCheckRunOptions{
Name: fmt.Sprintf("vela-%s-%s", commit, s.GetName()),
Conclusion: github.String(conclusion),
Status: github.String(status),
}

_, _, err := client.Checks.UpdateCheckRun(ctx, r.GetOrg(), r.GetName(), s.GetCheckID(), opts)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
s.GetCheckID undefined (type *library.Step has no field or method GetCheckID)) (typecheck)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
s.GetCheckID undefined (type *library.Step has no field or method GetCheckID)) (typecheck)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
s.GetCheckID undefined (type *library.Step has no field or method GetCheckID)) (typecheck)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
s.GetCheckID undefined (type *library.Step has no field or method GetCheckID) (typecheck)

if err != nil {
return err
}

return nil
}
4 changes: 4 additions & 0 deletions scm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ type Service interface {
// a repository file's html_url.
GetHTMLURL(context.Context, *api.User, string, string, string, string) (string, error)

// TODO: add comments
CreateChecks(context.Context, *api.Repo, string, string) (int64, error)
UpdateChecks(context.Context, *api.Repo, *library.Step, string) error

// Webhook SCM Interface Functions

// ProcessWebhook defines a function that
Expand Down
5 changes: 5 additions & 0 deletions scm/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type Setup struct {
WebUIAddress string
// specifies the OAuth scopes to use for the scm client
Scopes []string
// optional and experimental
GithubAppID int64
GithubAppPrivateKey string
}

// Github creates and returns a Vela service capable of
Expand All @@ -55,6 +58,8 @@ func (s *Setup) Github() (Service, error) {
github.WithStatusContext(s.StatusContext),
github.WithWebUIAddress(s.WebUIAddress),
github.WithScopes(s.Scopes),
github.WithGithubAppID(s.GithubAppID),
github.WithGithubPrivateKey(s.GithubAppPrivateKey),
)
}

Expand Down
Loading