From cb5a0e7bbf2f607a4a28a9a1cc7bc508926773b6 Mon Sep 17 00:00:00 2001 From: Gavin Williams Date: Fri, 23 Sep 2022 16:50:43 +0100 Subject: [PATCH 01/10] Add support for authentication as a GitHub App This commit adds support for authentication as a GitHub App. This is beneficial in several ways, including: * Increased rate limits * Better separation of access * Finer grained control over access * Removes the need for a bot or service account As part of these changes, have added the `github.com/bradleyfalzon/ghinstallation/v2` module and it's associated dependencies. Added several new configuration fields: * `UseGitHubApp` - Boolean flag signalling if user wants to auth as a GitHub App * `PrivateKeyFile` - Filename for RSA Private Key generated for GitHub App * `AppID` - GitHub App application numerical identifier * `InstallationID` - GitHub App installation numerical identifier Upgrade to golang `1.16` Add some tests for the `Source` model. Add support for both `private_key` and `private_key_file` intputs. This enables either reading the private key from a file, or providing the contents of the private key. Expanded testing to cover additional scenarios. Also rename `AppID` to `ApplicationID`. add manifest yaml bump remove unused travis use real image tags in dockefile, update go.mod update go sum comment xoauth basic comment xoauth basic comment xoauth basic comment xoauth basic add git credentail helper fix dockerfile add credential helper sds sds --- Dockerfile | 21 +++------ README.md | 1 + git.go | 6 ++- github.go | 39 ++++++++++++---- go.mod | 3 ++ go.sum | 6 +++ manifest.yaml | 2 + models.go | 20 ++++++++- models_test.go | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 190 insertions(+), 26 deletions(-) create mode 100644 manifest.yaml create mode 100644 models_test.go diff --git a/Dockerfile b/Dockerfile index 96035cba..3af8a339 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,4 @@ -ARG golang -ARG alpine - - - -FROM ${golang} AS builder - +FROM public.ecr.aws/docker/library/golang:1.22 as builder ENV DEBIAN_FRONTEND=noninteractive RUN apt-get -y -qq update \ && apt-get -y -qq install "make" @@ -12,22 +6,21 @@ RUN apt-get -y -qq update \ ADD . /go/src/github.com/telia-oss/github-pr-resource WORKDIR /go/src/github.com/telia-oss/github-pr-resource -RUN go version \ - && make all +RUN go version && make all - - -FROM ${alpine} AS resource +FROM public.ecr.aws/docker/library/alpine:3.21.3 AS resource RUN apk add --update --no-cache \ git \ git-lfs \ + curl \ openssh \ git-crypt COPY scripts/askpass.sh /usr/local/bin/askpass.sh +ENV GITHUB_APP_CRED_HELPER_VERSION="v0.3.2" +ENV BIN_PATH_TARGET=/usr/local/bin +RUN curl -L https://github.com/bdellegrazie/git-credential-github-app/releases/download/${GITHUB_APP_CRED_HELPER_VERSION}/git-credential-github-app_${GITHUB_APP_CRED_HELPER_VERSION}_Linux_x86_64.tar.gz | tar zxv -C ${BIN_PATH_TARGET} COPY --from=builder /go/src/github.com/telia-oss/github-pr-resource/build /opt/resource RUN chmod +x /opt/resource/* - - FROM resource LABEL MAINTAINER=cloudfoundry-community diff --git a/README.md b/README.md index 461d6fcb..1c174557 100644 --- a/README.md +++ b/README.md @@ -312,3 +312,4 @@ Here is the list of changes: Note that if you are migrating from the original resource on a Concourse version prior to `v5.0.0`, you might see an error `failed to unmarshal request: json: unknown field "ref"`. The solution is to rename the resource so that the history is wiped. See telia-oss/github-pr-resource#64 for details. + diff --git a/git.go b/git.go index c4b45208..6392ef68 100644 --- a/git.go +++ b/git.go @@ -75,7 +75,8 @@ func (g *GitClient) Init(branch string) error { if err := g.command("git", "config", "--global", "user.email", "concourse@local").Run(); err != nil { return fmt.Errorf("failed to configure git email: %s", err) } - if err := g.command("git", "config", "--global", "url.https://x-oauth-basic@github.com/.insteadOf", "git@github.com:").Run(); err != nil { + fmt.Println("SDS") + if err := g.command("git", "config", "credential.https://github.com.helper","'!git-credential-github-app --appId ((github/concourse-app-id)) -organization ((github/concourse-app-organization-name)) -username x-access-token'").Run(); err != nil { return fmt.Errorf("failed to configure github url: %s", err) } if err := g.command("git", "config", "--global", "url.https://.insteadOf", "git://").Run(); err != nil { @@ -91,6 +92,7 @@ func (g *GitClient) Pull(uri, branch string, depth int, submodules bool, fetchTa return err } + if err := g.command("git", "remote", "add", "origin", endpoint).Run(); err != nil { return fmt.Errorf("setting 'origin' remote to '%s' failed: %s", endpoint, err) } @@ -232,6 +234,6 @@ func (g *GitClient) Endpoint(uri string) (string, error) { if err != nil { return "", fmt.Errorf("failed to parse commit url: %s", err) } - endpoint.User = url.UserPassword("x-oauth-basic", g.AccessToken) + //endpoint.User = url.UserPassword("x-oauth-basic", g.AccessToken) return endpoint.String(), nil } diff --git a/github.go b/github.go index c1410aa9..be25f7ba 100644 --- a/github.go +++ b/github.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" + "github.com/bradleyfalzon/ghinstallation/v2" "github.com/google/go-github/v61/github" "github.com/shurcooL/githubv4" "golang.org/x/oauth2" @@ -46,23 +47,45 @@ func NewGithubClient(s *Source) (*GithubClient, error) { return nil, err } + // We need a transport that we can update if using GitHub App authentication + transport := http.DefaultTransport.(*http.Transport).Clone() + // Skip SSL verification for self-signed certificates // source: https://github.com/google/go-github/pull/598#issuecomment-333039238 var ctx context.Context if s.SkipSSLVerification { - insecureClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - } + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + insecureClient := &http.Client{Transport: transport} ctx = context.WithValue(context.TODO(), oauth2.HTTPClient, insecureClient) } else { ctx = context.TODO() } - client := oauth2.NewClient(ctx, oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: s.AccessToken}, - )) + var client *http.Client + if s.UseGitHubApp { + var ghAppInstallationTransport *ghinstallation.Transport + if s.PrivateKeyFile != "" { + ghAppInstallationTransport, err = ghinstallation.NewKeyFromFile(transport, s.ApplicationID, s.InstallationID, s.PrivateKeyFile) + if err != nil { + return nil, fmt.Errorf("failed to generate application installation access token using private key file: %s", err) + } + } else { + ghAppInstallationTransport, err = ghinstallation.New(transport, s.ApplicationID, s.InstallationID, []byte(s.PrivateKey)) + if err != nil { + return nil, fmt.Errorf("failed to generate application installation access token using private key: %s", err) + } + } + + // Client using ghinstallation transport + client = &http.Client{ + Transport: ghAppInstallationTransport, + } + } else { + // Client using oauth2 wrapper + client = oauth2.NewClient(ctx, oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: s.AccessToken}, + )) + } var v3 *github.Client if s.V3Endpoint != "" { diff --git a/go.mod b/go.mod index d0f34145..ed26db26 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.0 toolchain go1.23.7 require ( + github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 github.com/google/go-github/v61 v61.0.0 github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 @@ -14,6 +15,8 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/google/go-github/v69 v69.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index cef66eec..ba6c0066 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,17 @@ +github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 h1:0D4vKCHOvYrDU8u61TnE2JfNT4VRrBLphmxtqazTO+M= +github.com/bradleyfalzon/ghinstallation/v2 v2.14.0/go.mod h1:LOVmdZYVZ8jqdr4n9wWm1ocDiMz9IfMGfRkaYC1a52A= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go= github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY= +github.com/google/go-github/v69 v69.0.0 h1:YnFvZ3pEIZF8KHmI8xyQQe3mYACdkhnaTV2hr7CP2/w= +github.com/google/go-github/v69 v69.0.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= diff --git a/manifest.yaml b/manifest.yaml new file mode 100644 index 00000000..c9128adc --- /dev/null +++ b/manifest.yaml @@ -0,0 +1,2 @@ +name: github-pr-resource +version: 0.0.1 diff --git a/models.go b/models.go index 6695308f..ee1cb353 100644 --- a/models.go +++ b/models.go @@ -29,12 +29,28 @@ type Source struct { States []githubv4.PullRequestState `json:"states"` TrustedTeams []string `json:"trusted_teams"` TrustedUsers []string `json:"trusted_users"` + UseGitHubApp bool `json:"use_github_app"` + PrivateKey string `json:"private_key"` + PrivateKeyFile string `json:"private_key_file"` + ApplicationID int64 `json:"application_id"` + InstallationID int64 `json:"installation_id"` } // Validate the source configuration. func (s *Source) Validate() error { - if s.AccessToken == "" { - return errors.New("access_token must be set") + if s.AccessToken == "" && !s.UseGitHubApp { + return errors.New("access_token must be set if not using GitHub App authentication") + } + if s.UseGitHubApp { + if s.PrivateKey == "" && s.PrivateKeyFile == "" { + return errors.New("Either private_key or private_key_file should be supplied if using GitHub App authentication") + } + if s.ApplicationID == 0 || s.InstallationID == 0 { + return errors.New("application_id and installation_id must be set if using GitHub App authentication") + } + if s.AccessToken != "" { + return errors.New("access_token is not required when using GitHub App authentication") + } } if s.Repository == "" { return errors.New("repository must be set") diff --git a/models_test.go b/models_test.go new file mode 100644 index 00000000..f1e25c9b --- /dev/null +++ b/models_test.go @@ -0,0 +1,118 @@ +package resource_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + resource "github.com/telia-oss/github-pr-resource" +) + +func TestSource(t *testing.T) { + tests := []struct { + description string + source resource.Source + wantErr string + }{ + { + description: "validate passes", + source: resource.Source{ + AccessToken: "123456", + Repository: "test/test", + }, + }, + { + description: "should have an access_token", + source: resource.Source{ + Repository: "test/test", + }, + wantErr: "access_token must be set if not using GitHub App authentication", + }, + { + description: "should have a repository", + source: resource.Source{ + AccessToken: "123456", + }, + wantErr: "repository must be set", + }, + { + description: "should support GitHub App authentication", + source: resource.Source{ + Repository: "test/test", + UseGitHubApp: true, + PrivateKey: "key.pem", + ApplicationID: 123456, + InstallationID: 1, + }, + }, + { + description: "requires a private_key or private_key_file GitHub App configuration values", + source: resource.Source{ + Repository: "test/test", + UseGitHubApp: true, + ApplicationID: 123456, + InstallationID: 1, + }, + wantErr: "Either private_key or private_key_file should be supplied if using GitHub App authentication", + }, + { + description: "requires an application_id and installation_id GitHub App configuration values", + source: resource.Source{ + Repository: "test/test", + UseGitHubApp: true, + PrivateKey: "key.pem", + ApplicationID: 123456, + }, + wantErr: "application_id and installation_id must be set if using GitHub App authentication", + }, + { + description: "should not have an access_token when using GitHub App authentication", + source: resource.Source{ + Repository: "test/test", + UseGitHubApp: true, + PrivateKey: "key.pem", + ApplicationID: 123456, + InstallationID: 1, + AccessToken: "123456", + }, + wantErr: "access_token is not required when using GitHub App authentication", + }, + { + description: "requires v3_endpoint when v4_endpoint is set", + source: resource.Source{ + AccessToken: "123456", + Repository: "test/test", + V3Endpoint: "https://github.com/v3", + }, + wantErr: "v4_endpoint must be set together with v3_endpoint", + }, + { + description: "requires v4_endpoint when v3_endpoint is set", + source: resource.Source{ + AccessToken: "123456", + Repository: "test/test", + V4Endpoint: "https://github.com/v4", + }, + wantErr: "v3_endpoint must be set together with v4_endpoint", + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + err := tc.source.Validate() + + if tc.wantErr != "" { + if err == nil { + t.Logf("Expected error '%s', got nothing", tc.wantErr) + t.Fail() + } + assert.EqualError(t, err, tc.wantErr, fmt.Sprintf("Expected '%s', got '%s'", tc.wantErr, err)) + } + + if tc.wantErr == "" && err != nil { + t.Logf("Got an error when none expected: %s", err) + t.Fail() + } + }) + } +} From e5100765c256d629b2eb1e2d95719cc99de236d8 Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Wed, 12 Mar 2025 11:20:56 -0400 Subject: [PATCH 02/10] github app support in git clone remove run e remove run e fix gofmt fix gofmt fix gofmt --- git.go | 39 +++++++++++++++++++++++++++++---------- github.go | 13 +++---------- models.go | 7 +++++-- models_test.go | 2 ++ 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/git.go b/git.go index 6392ef68..c1c10228 100644 --- a/git.go +++ b/git.go @@ -36,17 +36,24 @@ func NewGitClient(source *Source, dir string, output io.Writer) (*GitClient, err os.Setenv("GIT_LFS_SKIP_SMUDGE", "true") } return &GitClient{ - AccessToken: source.AccessToken, - Directory: dir, - Output: output, + AccessToken: source.AccessToken, + PrivateKey: source.PrivateKey, + UseGithubApp: source.UseGitHubApp, + ApplicationID: source.ApplicationID, + Directory: dir, + Output: output, }, nil } // GitClient ... type GitClient struct { - AccessToken string - Directory string - Output io.Writer + AccessToken string + UseGithubApp bool + Directory string + ApplicationID int64 + GithubOrganziation string + PrivateKey string + Output io.Writer } func (g *GitClient) command(name string, arg ...string) *exec.Cmd { @@ -58,6 +65,8 @@ func (g *GitClient) command(name string, arg ...string) *exec.Cmd { cmd.Env = append(cmd.Env, "X_OAUTH_BASIC_TOKEN="+g.AccessToken, "GIT_ASKPASS=/usr/local/bin/askpass.sh") + fmt.Fprint(os.Stderr, fmt.Sprintf("\n%s %v", name, arg)) + return cmd } @@ -75,9 +84,20 @@ func (g *GitClient) Init(branch string) error { if err := g.command("git", "config", "--global", "user.email", "concourse@local").Run(); err != nil { return fmt.Errorf("failed to configure git email: %s", err) } - fmt.Println("SDS") - if err := g.command("git", "config", "credential.https://github.com.helper","'!git-credential-github-app --appId ((github/concourse-app-id)) -organization ((github/concourse-app-organization-name)) -username x-access-token'").Run(); err != nil { - return fmt.Errorf("failed to configure github url: %s", err) + + if g.UseGithubApp { + filePath := "/tmp/git-resource-private-key" + err := ioutil.WriteFile(filePath, []byte(g.PrivateKey), 0600) + if err != nil { + fmt.Println("Error writing private key:", err) + os.Exit(1) + } + + helperStr := fmt.Sprintf("!git-credential-github-app --appId %d -organization %s -username x-access-token -privateKeyFile /tmp/git-resource-private-key", g.ApplicationID, g.GithubOrganziation) + fmt.Fprint(os.Stderr, "\nsds helperStr", helperStr) + if err := g.command("git", "config", "credential.https://github.com.helper", helperStr).Run(); err != nil { + return fmt.Errorf("failed to configure github url: %s", err) + } } if err := g.command("git", "config", "--global", "url.https://.insteadOf", "git://").Run(); err != nil { return fmt.Errorf("failed to configure github url: %s", err) @@ -92,7 +112,6 @@ func (g *GitClient) Pull(uri, branch string, depth int, submodules bool, fetchTa return err } - if err := g.command("git", "remote", "add", "origin", endpoint).Run(); err != nil { return fmt.Errorf("setting 'origin' remote to '%s' failed: %s", endpoint, err) } diff --git a/github.go b/github.go index be25f7ba..c56e6bd4 100644 --- a/github.go +++ b/github.go @@ -64,16 +64,9 @@ func NewGithubClient(s *Source) (*GithubClient, error) { var client *http.Client if s.UseGitHubApp { var ghAppInstallationTransport *ghinstallation.Transport - if s.PrivateKeyFile != "" { - ghAppInstallationTransport, err = ghinstallation.NewKeyFromFile(transport, s.ApplicationID, s.InstallationID, s.PrivateKeyFile) - if err != nil { - return nil, fmt.Errorf("failed to generate application installation access token using private key file: %s", err) - } - } else { - ghAppInstallationTransport, err = ghinstallation.New(transport, s.ApplicationID, s.InstallationID, []byte(s.PrivateKey)) - if err != nil { - return nil, fmt.Errorf("failed to generate application installation access token using private key: %s", err) - } + ghAppInstallationTransport, err = ghinstallation.New(transport, s.ApplicationID, s.InstallationID, []byte(s.PrivateKey)) + if err != nil { + return nil, fmt.Errorf("failed to generate application installation access token using private key: %s", err) } // Client using ghinstallation transport diff --git a/models.go b/models.go index ee1cb353..b98f72c8 100644 --- a/models.go +++ b/models.go @@ -30,8 +30,8 @@ type Source struct { TrustedTeams []string `json:"trusted_teams"` TrustedUsers []string `json:"trusted_users"` UseGitHubApp bool `json:"use_github_app"` + GithubOrganziation string `json:"github_organization"` PrivateKey string `json:"private_key"` - PrivateKeyFile string `json:"private_key_file"` ApplicationID int64 `json:"application_id"` InstallationID int64 `json:"installation_id"` } @@ -42,12 +42,15 @@ func (s *Source) Validate() error { return errors.New("access_token must be set if not using GitHub App authentication") } if s.UseGitHubApp { - if s.PrivateKey == "" && s.PrivateKeyFile == "" { + if s.PrivateKey == "" { return errors.New("Either private_key or private_key_file should be supplied if using GitHub App authentication") } if s.ApplicationID == 0 || s.InstallationID == 0 { return errors.New("application_id and installation_id must be set if using GitHub App authentication") } + if len(s.GithubOrganziation) == 0 { + return errors.New("github_organization must be set if using GitHub App authentication") + } if s.AccessToken != "" { return errors.New("access_token is not required when using GitHub App authentication") } diff --git a/models_test.go b/models_test.go index f1e25c9b..51802d57 100644 --- a/models_test.go +++ b/models_test.go @@ -39,6 +39,7 @@ func TestSource(t *testing.T) { description: "should support GitHub App authentication", source: resource.Source{ Repository: "test/test", + GithubOrganziation: "test", UseGitHubApp: true, PrivateKey: "key.pem", ApplicationID: 123456, @@ -70,6 +71,7 @@ func TestSource(t *testing.T) { source: resource.Source{ Repository: "test/test", UseGitHubApp: true, + GithubOrganziation: "test", PrivateKey: "key.pem", ApplicationID: 123456, InstallationID: 1, From a76e2d30edc06883408bfe46a789364e27ec61b2 Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Wed, 12 Mar 2025 18:39:18 -0400 Subject: [PATCH 03/10] fix github org --- README.md | 1 - git.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c174557..461d6fcb 100644 --- a/README.md +++ b/README.md @@ -312,4 +312,3 @@ Here is the list of changes: Note that if you are migrating from the original resource on a Concourse version prior to `v5.0.0`, you might see an error `failed to unmarshal request: json: unknown field "ref"`. The solution is to rename the resource so that the history is wiped. See telia-oss/github-pr-resource#64 for details. - diff --git a/git.go b/git.go index c1c10228..5e4fc7f6 100644 --- a/git.go +++ b/git.go @@ -40,6 +40,7 @@ func NewGitClient(source *Source, dir string, output io.Writer) (*GitClient, err PrivateKey: source.PrivateKey, UseGithubApp: source.UseGitHubApp, ApplicationID: source.ApplicationID, + GithubOrganziation: source.GithubOrganziation, Directory: dir, Output: output, }, nil From 8dc473aab6ae43bdd3d502a81e6ee57bafc63d27 Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Thu, 13 Mar 2025 09:27:13 -0400 Subject: [PATCH 04/10] disable access token when using github app remove logger --- git.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/git.go b/git.go index 5e4fc7f6..e4f543f4 100644 --- a/git.go +++ b/git.go @@ -36,13 +36,13 @@ func NewGitClient(source *Source, dir string, output io.Writer) (*GitClient, err os.Setenv("GIT_LFS_SKIP_SMUDGE", "true") } return &GitClient{ - AccessToken: source.AccessToken, - PrivateKey: source.PrivateKey, - UseGithubApp: source.UseGitHubApp, - ApplicationID: source.ApplicationID, + AccessToken: source.AccessToken, + PrivateKey: source.PrivateKey, + UseGithubApp: source.UseGitHubApp, + ApplicationID: source.ApplicationID, GithubOrganziation: source.GithubOrganziation, - Directory: dir, - Output: output, + Directory: dir, + Output: output, }, nil } @@ -63,8 +63,12 @@ func (g *GitClient) command(name string, arg ...string) *exec.Cmd { cmd.Stdout = g.Output cmd.Stderr = g.Output cmd.Env = os.Environ() + if !g.UseGithubApp { + cmd.Env = append(cmd.Env, + "X_OAUTH_BASIC_TOKEN="+g.AccessToken) + } + cmd.Env = append(cmd.Env, - "X_OAUTH_BASIC_TOKEN="+g.AccessToken, "GIT_ASKPASS=/usr/local/bin/askpass.sh") fmt.Fprint(os.Stderr, fmt.Sprintf("\n%s %v", name, arg)) @@ -95,7 +99,6 @@ func (g *GitClient) Init(branch string) error { } helperStr := fmt.Sprintf("!git-credential-github-app --appId %d -organization %s -username x-access-token -privateKeyFile /tmp/git-resource-private-key", g.ApplicationID, g.GithubOrganziation) - fmt.Fprint(os.Stderr, "\nsds helperStr", helperStr) if err := g.command("git", "config", "credential.https://github.com.helper", helperStr).Run(); err != nil { return fmt.Errorf("failed to configure github url: %s", err) } @@ -254,6 +257,9 @@ func (g *GitClient) Endpoint(uri string) (string, error) { if err != nil { return "", fmt.Errorf("failed to parse commit url: %s", err) } - //endpoint.User = url.UserPassword("x-oauth-basic", g.AccessToken) + if !g.UseGithubApp { + endpoint.User = url.UserPassword("x-oauth-basic", g.AccessToken) + } + return endpoint.String(), nil } From 4834f5912e1d31c357f41b642347aeb70d570595 Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Thu, 13 Mar 2025 11:55:12 -0400 Subject: [PATCH 05/10] add github app docs to readme restore oauth basic remove private key file reference typo punctuation Update Golang from 1.22.10 to 1.23.7 and its dependencies --- README.md | 5 +++++ git.go | 6 +++++- models.go | 6 +++--- models_test.go | 26 +++++++++++++------------- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 461d6fcb..58a54d78 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,11 @@ automated tests. | `labels` | No | `["bug", "enhancement"]` | The labels on the PR. The pipeline will only trigger on pull requests having at least one of the specified labels. | | `disable_git_lfs` | No | `true` | Disable Git LFS, skipping an attempt to convert pointers of files tracked into their corresponding objects when checked out into a working copy. | | `states` | No | `["OPEN", "MERGED"]` | The PR states to select (`OPEN`, `MERGED` or `CLOSED`). The pipeline will only trigger on pull requests matching one of the specified states. Default is ["OPEN"]. | +| `use_github_app` | No | `false` | Whether to authenticate using a github app or not. +| `github_organization` | No | `Vault-tec` | Which Github organization your github app is in. +| `private_key` | No | `-----BEGIN RSA...` | Private key for your github app. +| `installation_id` | No | `12356` | Installation id for your github app. +| `application_id` | No | `12356` | Application id for your github app. **Notes:** diff --git a/git.go b/git.go index e4f543f4..673cde39 100644 --- a/git.go +++ b/git.go @@ -40,7 +40,7 @@ func NewGitClient(source *Source, dir string, output io.Writer) (*GitClient, err PrivateKey: source.PrivateKey, UseGithubApp: source.UseGitHubApp, ApplicationID: source.ApplicationID, - GithubOrganziation: source.GithubOrganziation, + GithubOrganziation: source.GithubOrganization, Directory: dir, Output: output, }, nil @@ -102,6 +102,10 @@ func (g *GitClient) Init(branch string) error { if err := g.command("git", "config", "credential.https://github.com.helper", helperStr).Run(); err != nil { return fmt.Errorf("failed to configure github url: %s", err) } + } else { + if err := g.command("git", "config", "url.https://x-oauth-basic@github.com/.insteadOf", "git@github.com:").Run(); err != nil { + return fmt.Errorf("failed to configure github url: %s", err) + } } if err := g.command("git", "config", "--global", "url.https://.insteadOf", "git://").Run(); err != nil { return fmt.Errorf("failed to configure github url: %s", err) diff --git a/models.go b/models.go index b98f72c8..1143d761 100644 --- a/models.go +++ b/models.go @@ -30,7 +30,7 @@ type Source struct { TrustedTeams []string `json:"trusted_teams"` TrustedUsers []string `json:"trusted_users"` UseGitHubApp bool `json:"use_github_app"` - GithubOrganziation string `json:"github_organization"` + GithubOrganization string `json:"github_organization"` PrivateKey string `json:"private_key"` ApplicationID int64 `json:"application_id"` InstallationID int64 `json:"installation_id"` @@ -43,12 +43,12 @@ func (s *Source) Validate() error { } if s.UseGitHubApp { if s.PrivateKey == "" { - return errors.New("Either private_key or private_key_file should be supplied if using GitHub App authentication") + return errors.New("private_key is required for GitHub App authentication") } if s.ApplicationID == 0 || s.InstallationID == 0 { return errors.New("application_id and installation_id must be set if using GitHub App authentication") } - if len(s.GithubOrganziation) == 0 { + if len(s.GithubOrganization) == 0 { return errors.New("github_organization must be set if using GitHub App authentication") } if s.AccessToken != "" { diff --git a/models_test.go b/models_test.go index 51802d57..9e33b3d5 100644 --- a/models_test.go +++ b/models_test.go @@ -38,23 +38,23 @@ func TestSource(t *testing.T) { { description: "should support GitHub App authentication", source: resource.Source{ - Repository: "test/test", + Repository: "test/test", GithubOrganziation: "test", - UseGitHubApp: true, - PrivateKey: "key.pem", - ApplicationID: 123456, - InstallationID: 1, + UseGitHubApp: true, + PrivateKey: "key.pem", + ApplicationID: 123456, + InstallationID: 1, }, }, { - description: "requires a private_key or private_key_file GitHub App configuration values", + description: "private_key App configuration values", source: resource.Source{ Repository: "test/test", UseGitHubApp: true, ApplicationID: 123456, InstallationID: 1, }, - wantErr: "Either private_key or private_key_file should be supplied if using GitHub App authentication", + wantErr: "private_key is required for GitHub App authentication", }, { description: "requires an application_id and installation_id GitHub App configuration values", @@ -69,13 +69,13 @@ func TestSource(t *testing.T) { { description: "should not have an access_token when using GitHub App authentication", source: resource.Source{ - Repository: "test/test", - UseGitHubApp: true, + Repository: "test/test", + UseGitHubApp: true, GithubOrganziation: "test", - PrivateKey: "key.pem", - ApplicationID: 123456, - InstallationID: 1, - AccessToken: "123456", + PrivateKey: "key.pem", + ApplicationID: 123456, + InstallationID: 1, + AccessToken: "123456", }, wantErr: "access_token is not required when using GitHub App authentication", }, From ace00a2072378d0e9301ef59cc52fe3d64c62a12 Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Mon, 17 Mar 2025 13:34:54 -0400 Subject: [PATCH 06/10] revert changes to dockerfile - update github app credential helper --- Dockerfile | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3af8a339..53973a37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,10 @@ -FROM public.ecr.aws/docker/library/golang:1.22 as builder +ARG golang +ARG alpine + + + +FROM ${golang} AS builder + ENV DEBIAN_FRONTEND=noninteractive RUN apt-get -y -qq update \ && apt-get -y -qq install "make" @@ -6,21 +12,25 @@ RUN apt-get -y -qq update \ ADD . /go/src/github.com/telia-oss/github-pr-resource WORKDIR /go/src/github.com/telia-oss/github-pr-resource -RUN go version && make all +RUN go version \ + && make all + -FROM public.ecr.aws/docker/library/alpine:3.21.3 AS resource + +FROM ${alpine} AS resource RUN apk add --update --no-cache \ git \ git-lfs \ - curl \ openssh \ git-crypt COPY scripts/askpass.sh /usr/local/bin/askpass.sh -ENV GITHUB_APP_CRED_HELPER_VERSION="v0.3.2" +COPY --from=builder /go/src/github.com/telia-oss/github-pr-resource/build /opt/resource +RUN chmod +x /opt/resource/* +ENV GITHUB_APP_CRED_HELPER_VERSION="v0.3.3" ENV BIN_PATH_TARGET=/usr/local/bin RUN curl -L https://github.com/bdellegrazie/git-credential-github-app/releases/download/${GITHUB_APP_CRED_HELPER_VERSION}/git-credential-github-app_${GITHUB_APP_CRED_HELPER_VERSION}_Linux_x86_64.tar.gz | tar zxv -C ${BIN_PATH_TARGET} COPY --from=builder /go/src/github.com/telia-oss/github-pr-resource/build /opt/resource -RUN chmod +x /opt/resource/* + FROM resource LABEL MAINTAINER=cloudfoundry-community From d53c7576bdf7768fe315fefc00eb13ed4be5df6a Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Mon, 17 Mar 2025 13:43:08 -0400 Subject: [PATCH 07/10] invert condition --- git.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/git.go b/git.go index 673cde39..cbb05067 100644 --- a/git.go +++ b/git.go @@ -89,9 +89,14 @@ func (g *GitClient) Init(branch string) error { if err := g.command("git", "config", "--global", "user.email", "concourse@local").Run(); err != nil { return fmt.Errorf("failed to configure git email: %s", err) } - - if g.UseGithubApp { - filePath := "/tmp/git-resource-private-key" + if err := g.command("git", "config", "--global", "url.https://.insteadOf", "git://").Run(); err != nil { + return fmt.Errorf("failed to configure github url: %s", err) + } + if !g.UseGithubApp { + if err := g.command("git", "config", "url.https://x-oauth-basic@github.com/.insteadOf", "git@github.com:").Run(); err != nil { + return fmt.Errorf("failed to configure github url: %s", err) + } + } else { err := ioutil.WriteFile(filePath, []byte(g.PrivateKey), 0600) if err != nil { fmt.Println("Error writing private key:", err) @@ -107,9 +112,6 @@ func (g *GitClient) Init(branch string) error { return fmt.Errorf("failed to configure github url: %s", err) } } - if err := g.command("git", "config", "--global", "url.https://.insteadOf", "git://").Run(); err != nil { - return fmt.Errorf("failed to configure github url: %s", err) - } return nil } From 79f07a7e25d12efd63311a490df17ec9d1a03756 Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Mon, 17 Mar 2025 13:44:03 -0400 Subject: [PATCH 08/10] invert condition --- github.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/github.go b/github.go index c56e6bd4..d68337cb 100644 --- a/github.go +++ b/github.go @@ -62,7 +62,12 @@ func NewGithubClient(s *Source) (*GithubClient, error) { } var client *http.Client - if s.UseGitHubApp { + if !s.UseGitHubApp { + // Client using oauth2 wrapper + client = oauth2.NewClient(ctx, oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: s.AccessToken}, + )) + } else { var ghAppInstallationTransport *ghinstallation.Transport ghAppInstallationTransport, err = ghinstallation.New(transport, s.ApplicationID, s.InstallationID, []byte(s.PrivateKey)) if err != nil { @@ -73,11 +78,6 @@ func NewGithubClient(s *Source) (*GithubClient, error) { client = &http.Client{ Transport: ghAppInstallationTransport, } - } else { - // Client using oauth2 wrapper - client = oauth2.NewClient(ctx, oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: s.AccessToken}, - )) } var v3 *github.Client From 1249e3e454bccdea5d7c97009e9be9ec4b74e0b3 Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Mon, 17 Mar 2025 13:50:03 -0400 Subject: [PATCH 09/10] formatted selection --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 58a54d78..e8f7074c 100644 --- a/README.md +++ b/README.md @@ -51,16 +51,16 @@ automated tests. | `required_review_approvals` | No | `2` | Disable triggering of the resource if the pull request does not have at least `X` approved review(s). | | `trusted_teams` | No | `["wg-cf-on-k8s-bots"]` | PRs from members of the trusted teams always trigger the resource regardless of the PR approval status. | | `trusted_users` | No | `["dependabot"]` | PRs from trusted users always trigger the resource regardless of the PR approval status. | -| `git_crypt_key` | No | `AEdJVENSWVBUS0VZAAAAA...` | Base64 encoded git-crypt key. Setting this will unlock / decrypt the repository with git-crypt. To get the key simply execute `git-crypt export-key -- - | base64` in an encrypted repository. | +| `git_crypt_key` | No | `AEdJVENSWVBUS0VZAAAAA...` | Base64 encoded git-crypt key. Setting this will unlock / decrypt the repository with git-crypt. To get the key simply execute `git-crypt export-key -- - | base64` in an encrypted repository. | | `base_branch` | No | `master` | Name of a branch. The pipeline will only trigger on pull requests against the specified branch. | | `labels` | No | `["bug", "enhancement"]` | The labels on the PR. The pipeline will only trigger on pull requests having at least one of the specified labels. | | `disable_git_lfs` | No | `true` | Disable Git LFS, skipping an attempt to convert pointers of files tracked into their corresponding objects when checked out into a working copy. | | `states` | No | `["OPEN", "MERGED"]` | The PR states to select (`OPEN`, `MERGED` or `CLOSED`). The pipeline will only trigger on pull requests matching one of the specified states. Default is ["OPEN"]. | -| `use_github_app` | No | `false` | Whether to authenticate using a github app or not. -| `github_organization` | No | `Vault-tec` | Which Github organization your github app is in. -| `private_key` | No | `-----BEGIN RSA...` | Private key for your github app. -| `installation_id` | No | `12356` | Installation id for your github app. -| `application_id` | No | `12356` | Application id for your github app. +| `use_github_app` | No | `false` | Whether to authenticate using a github app or not. | +| `github_organization` | No | `Vault-tec` | Which Github organization your github app is in. | +| `private_key` | No | `-----BEGIN RSA...` | Private key for your github app. | +| `installation_id` | No | `12356` | Installation id for your github app. | +| `application_id` | No | `12356` | Application id for your github app. | **Notes:** From f05bdfd4b29627768567274756db4cd4402ec4e5 Mon Sep 17 00:00:00 2001 From: Scott Schulthess Date: Mon, 17 Mar 2025 13:53:02 -0400 Subject: [PATCH 10/10] fix github organization remove unneeded manifest.yaml --- git.go | 6 +++--- manifest.yaml | 2 -- models_test.go | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 manifest.yaml diff --git a/git.go b/git.go index cbb05067..290b757a 100644 --- a/git.go +++ b/git.go @@ -40,7 +40,7 @@ func NewGitClient(source *Source, dir string, output io.Writer) (*GitClient, err PrivateKey: source.PrivateKey, UseGithubApp: source.UseGitHubApp, ApplicationID: source.ApplicationID, - GithubOrganziation: source.GithubOrganization, + GithubOrganization: source.GithubOrganization, Directory: dir, Output: output, }, nil @@ -52,7 +52,7 @@ type GitClient struct { UseGithubApp bool Directory string ApplicationID int64 - GithubOrganziation string + GithubOrganization string PrivateKey string Output io.Writer } @@ -103,7 +103,7 @@ func (g *GitClient) Init(branch string) error { os.Exit(1) } - helperStr := fmt.Sprintf("!git-credential-github-app --appId %d -organization %s -username x-access-token -privateKeyFile /tmp/git-resource-private-key", g.ApplicationID, g.GithubOrganziation) + helperStr := fmt.Sprintf("!git-credential-github-app --appId %d -organization %s -username x-access-token -privateKeyFile /tmp/git-resource-private-key", g.ApplicationID, g.GithubOrganization) if err := g.command("git", "config", "credential.https://github.com.helper", helperStr).Run(); err != nil { return fmt.Errorf("failed to configure github url: %s", err) } diff --git a/manifest.yaml b/manifest.yaml deleted file mode 100644 index c9128adc..00000000 --- a/manifest.yaml +++ /dev/null @@ -1,2 +0,0 @@ -name: github-pr-resource -version: 0.0.1 diff --git a/models_test.go b/models_test.go index 9e33b3d5..faebb1b0 100644 --- a/models_test.go +++ b/models_test.go @@ -39,7 +39,7 @@ func TestSource(t *testing.T) { description: "should support GitHub App authentication", source: resource.Source{ Repository: "test/test", - GithubOrganziation: "test", + GithubOrganization: "test", UseGitHubApp: true, PrivateKey: "key.pem", ApplicationID: 123456, @@ -71,7 +71,7 @@ func TestSource(t *testing.T) { source: resource.Source{ Repository: "test/test", UseGitHubApp: true, - GithubOrganziation: "test", + GithubOrganization: "test", PrivateKey: "key.pem", ApplicationID: 123456, InstallationID: 1,