Skip to content

Commit

Permalink
enhance: cleanup and organization
Browse files Browse the repository at this point in the history
  • Loading branch information
plyr4 committed Oct 25, 2024
1 parent 9df0c04 commit 9654dbc
Show file tree
Hide file tree
Showing 13 changed files with 200 additions and 166 deletions.
2 changes: 1 addition & 1 deletion cmd/vela-server/scm.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,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)
}
8 changes: 1 addition & 7 deletions compiler/native/native.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ func (c *client) WithLabels(labels []string) compiler.Engine {
return c
}

// WithNetrc sets the netrc in the Engine.
func (c *client) WithNetrc(n string) compiler.Engine {
c.netrc = &n

Expand All @@ -248,10 +249,3 @@ func (c *client) WithSCM(_scm scm.Service) compiler.Engine {

return c
}

// // WithGit sets the git configurations in the Engine.
// func (c *client) WithGit(g *yaml.Git) compiler.Engine {
// c.git = g

// return c
// }
136 changes: 136 additions & 0 deletions scm/github/app_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: Apache-2.0

package github

import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"net/http"
"strings"

"github.com/google/go-github/v65/github"

api "github.com/go-vela/server/api/types"
)

// NewGitHubAppTransport creates a new GitHub App transport for authenticating as the GitHub App.
func NewGitHubAppTransport(appID int64, privateKey, baseUrl string) (*AppsTransport, error) {
decodedPEM, err := base64.StdEncoding.DecodeString(privateKey)
if err != nil {
return nil, fmt.Errorf("error decoding base64: %w", err)
}

block, _ := pem.Decode(decodedPEM)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the key")
}

_privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse RSA private key: %w", err)
}

transport := NewAppsTransportFromPrivateKey(http.DefaultTransport, appID, _privateKey)
transport.BaseURL = baseUrl

return transport, nil
}

// ValidateGitHubApp ensures the GitHub App configuration is valid.
func (c *client) ValidateGitHubApp(ctx context.Context) error {
client, err := c.newGithubAppClient()
if err != nil {
return fmt.Errorf("error creating github app client: %w", err)
}

app, _, err := client.Apps.Get(ctx, "")
if err != nil {
return fmt.Errorf("error getting github app: %w", err)
}

perms := app.GetPermissions()
if len(perms.GetContents()) == 0 ||
(perms.GetContents() != "read" && perms.GetContents() != "write") {
return fmt.Errorf("github app requires contents:read permissions, found: %s", perms.GetContents())
}

if len(perms.GetChecks()) == 0 ||
perms.GetChecks() != "write" {
return fmt.Errorf("github app requires checks:write permissions, found: %s", perms.GetChecks())
}

return nil
}

// newGithubAppClient returns the GitHub App client for authenticating as the GitHub App itself using the RoundTripper.
func (c *client) newGithubAppClient() (*github.Client, error) {
// todo: create transport using context to apply tracing
// 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 {
return nil, err
}

return client, nil
}

// newGithubAppInstallationRepoToken returns the GitHub App installation token.
func (c *client) newGithubAppInstallationRepoToken(ctx context.Context, r *api.Repo, repos []string, permissions *github.InstallationPermissions) (string, error) {
// create a github client based off the existing GitHub App configuration
client, err := c.newGithubAppClient()
if err != nil {
return "", err
}

opts := &github.InstallationTokenOptions{
Repositories: repos,
Permissions: permissions,
}

// if repo has an install ID, use it to create an installation token
if r.GetInstallID() != 0 {
// create installation token for the repo
t, _, err := client.Apps.CreateInstallationToken(ctx, r.GetInstallID(), opts)
if err != nil {
return "", err
}

return t.GetToken(), nil
}

// list all installations (a.k.a. orgs) where the GitHub App is installed
installations, _, err := client.Apps.ListInstallations(ctx, &github.ListOptions{})
if err != nil {
return "", 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 {
return "", errors.New("unable to find installation ID for repo")
}

// create installation token for the repo
t, _, err := client.Apps.CreateInstallationToken(ctx, id, opts)
if err != nil {
return "", err
}

return t.GetToken(), nil
}
4 changes: 2 additions & 2 deletions scm/github/app_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,12 @@ func updateRepoInstallationID(ctx context.Context, webhook *internal.Webhook, r
func (c *client) FinishInstallation(ctx context.Context, request *http.Request, installID int64) (string, error) {
c.Logger.Tracef("finishing GitHub App installation for ID %d", installID)

githubAppClient, err := c.newGithubAppClient(ctx)
client, err := c.newGithubAppClient()
if err != nil {
return "", err
}

install, _, err := githubAppClient.Apps.GetInstallation(ctx, installID)
install, _, err := client.Apps.GetInstallation(ctx, installID)
if err != nil {
return "", err
}
Expand Down
2 changes: 2 additions & 0 deletions scm/github/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package github

import (
"context"
"reflect"
"testing"

Expand All @@ -14,6 +15,7 @@ func TestGitHub_Driver(t *testing.T) {
want := constants.DriverGithub

_service, err := New(
context.Background(),
WithAddress("https://github.com/"),
WithClientID("foo"),
WithClientSecret("bar"),
Expand Down
144 changes: 8 additions & 136 deletions scm/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,16 @@ package github

import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/http/httptrace"
"net/url"
"strings"

"github.com/google/go-github/v65/github"
"github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"golang.org/x/oauth2"

api "github.com/go-vela/server/api/types"
"github.com/go-vela/server/tracing"
)

Expand Down Expand Up @@ -76,7 +69,7 @@ type client struct {
// a GitHub or a GitHub Enterprise instance.
//
//nolint:revive // ignore returning unexported client
func New(opts ...ClientOpt) (*client, error) {
func New(ctx context.Context, opts ...ClientOpt) (*client, error) {
// create new GitHub client
c := new(client)

Expand Down Expand Up @@ -127,47 +120,18 @@ func New(opts ...ClientOpt) (*client, error) {
}

if c.config.AppID != 0 && len(c.config.AppPrivateKey) > 0 {
c.Logger.Infof("reading github app private key with length %d", len(c.config.AppPrivateKey))
c.Logger.Infof("setting up GitHub App integration for App ID %d", c.config.AppID)

decodedPEM, err := base64.StdEncoding.DecodeString(c.config.AppPrivateKey)
transport, err := NewGitHubAppTransport(c.config.AppID, c.config.AppPrivateKey, c.config.API)
if err != nil {
return nil, fmt.Errorf("error decoding base64: %w", err)
}

block, _ := pem.Decode(decodedPEM)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the key")
}

privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse RSA private key: %w", err)
return nil, err
}

transport := NewAppsTransportFromPrivateKey(http.DefaultTransport, c.config.AppID, privateKey)

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

// ensure the github app that was provided is valid
ccc, err := c.newGithubAppClient(context.Background())
if err != nil {
return nil, fmt.Errorf("error creating github app client: %w", err)
}

app, _, err := ccc.Apps.Get(context.Background(), "")
err = c.ValidateGitHubApp(ctx)
if err != nil {
return nil, fmt.Errorf("error getting github app: %w", err)
}

perms := app.GetPermissions()

if len(perms.GetContents()) == 0 || (perms.GetContents() != "read" && perms.GetContents() != "write") {
return nil, fmt.Errorf("github app requires contents:read permissions, found: %s", perms.GetContents())
}

if len(perms.GetChecks()) == 0 || perms.GetChecks() != "write" {
return nil, fmt.Errorf("github app requires checks:write permissions, found: %s", perms.GetChecks())
return nil, err
}
}

Expand All @@ -190,6 +154,7 @@ func NewTest(urls ...string) (*client, error) {
}

return New(
context.Background(),
WithAddress(address),
WithClientID("foo"),
WithClientSecret("bar"),
Expand All @@ -201,7 +166,7 @@ func NewTest(urls ...string) (*client, error) {
)
}

// helper function to return the GitHub OAuth client.
// newClientToken returns the GitHub OAuth client.
func (c *client) newClientToken(ctx context.Context, token string) *github.Client {
// create the token object for the client
ts := oauth2.StaticTokenSource(
Expand Down Expand Up @@ -236,96 +201,3 @@ func (c *client) newClientToken(ctx context.Context, token string) *github.Clien

return github
}

// helper function to return the GitHub App client for authenticating as the GitHub App itself using the RoundTripper.
func (c *client) newGithubAppClient(ctx context.Context) (*github.Client, error) {
// todo: create transport using context to apply tracing
// 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 {
return nil, err
}

return client, nil
}

// helper function to return the GitHub App installation token.
func (c *client) newGithubAppInstallationRepoToken(ctx context.Context, r *api.Repo, repos []string, permissions *github.InstallationPermissions) (string, error) {
// todo: create transport using context to apply tracing
// 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 {
return "", err
}

opts := &github.InstallationTokenOptions{
Repositories: repos,
Permissions: permissions,
}

// if repo has an install ID, use it to create an installation token
if r.GetInstallID() != 0 {
// create installation token for the repo
t, _, err := client.Apps.CreateInstallationToken(context.Background(), r.GetInstallID(), opts)
if err != nil {
return "", err
}

return t.GetToken(), nil
}

// 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 {
return "", 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 {
return "", errors.New("unable to find installation ID for repo")
}

// create installation token for the repo
t, _, err := client.Apps.CreateInstallationToken(context.Background(), id, opts)
if err != nil {
return "", err
}

return t.GetToken(), nil
}

// WithGitHubInstallationPermission takes permissions and applies a new permission if valid.
func WithGitHubInstallationPermission(perms *github.InstallationPermissions, resource, perm string) (*github.InstallationPermissions, error) {
// convert permissions from yaml string
switch strings.ToLower(perm) {
case "read":
case "write":
case "none":
break
default:
return perms, fmt.Errorf("invalid permission value given for %s: %s", resource, perm)
}

// convert resource from yaml string
switch strings.ToLower(resource) {
case "contents":
perms.Contents = github.String(perm)
case "checks":
perms.Checks = github.String(perm)
default:
return perms, fmt.Errorf("invalid permission key given: %s", perm)
}

return perms, nil
}
Loading

0 comments on commit 9654dbc

Please sign in to comment.