diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 4e4d8b6..8291c5c 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -67,6 +67,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.TOKEN_INTEGRATION_TESTS }} TOKEN_WITH_NO_SCOPES: ${{ secrets.TOKEN_WITH_NO_SCOPES }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} run: | echo "${{ env.BINARY_PATH }}" make test-integration diff --git a/README.md b/README.md index 619d43d..92c8412 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,9 @@ Use the following environment variables to configure the application: | GITHUB_ACCESS_TOKEN | | GitHub access token. Instruction for creating a token can be found [here](./docs/gh-token.md). If not provided, the owners validating functionality may not work properly. For example, you may reach the API calls quota or, if you are setting GitHub Enterprise base URL, an unauthorized error may occur. | | GITHUB_BASE_URL | `https://api.github.com/` | GitHub base URL for API requests. Defaults to the public GitHub API but can be set to a domain endpoint to use with GitHub Enterprise. | | GITHUB_UPLOAD_URL | `https://uploads.github.com/` | GitHub upload URL for uploading files.

It is taken into account only when `GITHUB_BASE_URL` is also set. If only `GITHUB_BASE_URL` is provided, this parameter defaults to the `GITHUB_BASE_URL` value. | +| GITHUB_APP_ID | | Github App ID for authentication. This replaces the `GITHUB_ACCESS_TOKEN`. Instruction for creating a Github App can be found [here](./docs/gh-token.md) | +| GITHUB_APP_INSTALLATION_ID | | Github App Installation ID. Required when `GITHUB_APP_ID` is set. | +| GITHUB_APP_PRIVATE_KEY | | Github App private key in PEM format. Required when `GITHUB_APP_ID` is set. | | CHECKS | - | List of checks to be executed. By default, all checks are executed. Possible values: `files`,`owners`,`duppatterns`,`syntax`. | | EXPERIMENTAL_CHECKS | - | The comma-separated list of experimental checks that should be executed. By default, all experimental checks are turned off. Possible values: `notowned`. | | CHECK_FAILURE_LEVEL | `warning` | Defines the level on which the application should treat check issues as failures. Defaults to `warning`, which treats both errors and warnings as failures, and exits with error code 3. Possible values are `error` and `warning`. | diff --git a/docs/gh-token.md b/docs/gh-token.md index 1ad42bf..1877cbd 100644 --- a/docs/gh-token.md +++ b/docs/gh-token.md @@ -1,13 +1,17 @@ [← back to docs](./README.md) -# GitHub personal access token +# Github tokens -The [valid_owner.go](./../internal/check/valid_owner.go) check requires the GitHub personal access token for the following reasons: +The [valid_owner.go](./../internal/check/valid_owner.go) check requires the GitHub token for the following reasons: 1. Information about organization teams and their repositories is not publicly available. 2. If you set GitHub Enterprise base URL, an unauthorized error may occur. 3. For unauthenticated requests, the rate limit allows for up to 60 requests per hour. Unauthenticated requests are associated with the originating IP address. In a big organization where you have a lot of calls between your infrastructure server and the GitHub site, it is easy to exceed that quota. +You can either use a personal access token or a Github App. + +## GitHub personal access token + Instructions for creating a token can be found [here](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-token). The minimal scope required for the token is **read-only**, but the definition of this scope differs between public and private repositories. #### Public repositories @@ -23,3 +27,15 @@ For private repositories, select `repo` and `read:org`: ![token-public.png](./assets/token-private.png) The Codeowners Validator source code is available on GitHub. You can always perform a security audit against its code base and build your own version from the source code if your organization is more strict about the software run in its infrastructure. + +## Github App + +Here are the steps to create a Github App and use it for this tool: + +1. [Create a GitHub App](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app). **Note: your app does not need a callback or a webhook URL**. +2. Add a read-only permission to the "Members" item of organization permissions. +3. [Install the app in your organization](https://docs.github.com/en/developers/apps/managing-github-apps/installing-github-apps) +4. Done! To authenticate with your app, you need three environment variables: + 1. `GITHUB_APP_PRIVATE_KEY`: PEM-format key generated when the app is installed. If you lost it, you can regenerate it ([docs](https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#generating-a-private-key)). + 2. `GITHUB_APP_ID`: Found in the app's "About" page (Organization settings -> Developer settings -> Edit button on your app). + 3. `GITHUB_APP_INSTALLATION_ID`: Found in the URL your organization's app install page (Organization settings -> Github Apps -> Configure button on your app). It's the last number in the URL, ex: `https://github.com/organizations/my-org/settings/installations/1234567890`. diff --git a/go.mod b/go.mod index 8c01275..ad65842 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/mszostok/codeowners-validator go 1.17 require ( + github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 + github.com/dustin/go-humanize v1.0.0 github.com/fatih/color v1.13.0 - github.com/google/go-github/v29 v29.0.3 + github.com/google/go-github/v41 v41.0.0 github.com/hashicorp/go-multierror v1.1.1 github.com/mattn/go-zglob v0.0.4-0.20201017022353-70beb5203ba6 github.com/pkg/errors v0.9.1 @@ -24,14 +26,13 @@ require ( gotest.tools v2.2.0+incompatible ) -require github.com/dustin/go-humanize v1.0.0 - require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emirpasic/gods v1.12.0 // indirect + github.com/golang-jwt/jwt/v4 v4.0.0 // indirect github.com/golang/protobuf v1.4.3 // indirect - github.com/google/go-cmp v0.5.4 // indirect - github.com/google/go-querystring v1.0.0 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect diff --git a/go.sum b/go.sum index 10e90f1..51fbb75 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 h1:tXKVfhE7FcSkhkv0UwkLvPDeZ4kz6OXd0PKPlFqf81M= +github.com/bradleyfalzon/ghinstallation/v2 v2.0.4/go.mod h1:B40qPqJxWE0jDZgOR1JmaMy+4AY1eBP+IByOvqyAKp0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -75,6 +77,8 @@ github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -111,12 +115,14 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github/v29 v29.0.3 h1:IktKCTwU//aFHnpA+2SLIi7Oo9uhAzgsdZNbcAqhgdc= -github.com/google/go-github/v29 v29.0.3/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg= +github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg= +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/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -223,6 +229,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/internal/check/package_test.go b/internal/check/package_test.go index f75df2f..d071563 100644 --- a/internal/check/package_test.go +++ b/internal/check/package_test.go @@ -22,7 +22,7 @@ func TestRespectingCanceledContext(t *testing.T) { check.NewFileExist(), check.NewValidSyntax(), check.NewNotOwnedFile(check.NotOwnedFileConfig{}), - must(check.NewValidOwner(check.ValidOwnerConfig{Repository: "org/repo"}, nil)), + must(check.NewValidOwner(check.ValidOwnerConfig{Repository: "org/repo"}, nil, true)), } for _, checker := range checkers { diff --git a/internal/check/valid_owner.go b/internal/check/valid_owner.go index cbe06f5..6858ebd 100644 --- a/internal/check/valid_owner.go +++ b/internal/check/valid_owner.go @@ -9,7 +9,7 @@ import ( "github.com/mszostok/codeowners-validator/internal/ctxutil" - "github.com/google/go-github/v29/github" + "github.com/google/go-github/v41/github" "github.com/pkg/errors" ) @@ -40,6 +40,7 @@ type ValidOwnerConfig struct { // ValidOwner validates each owner type ValidOwner struct { ghClient *github.Client + checkScopes bool orgMembers *map[string]struct{} orgName string orgTeams []*github.Team @@ -50,7 +51,7 @@ type ValidOwner struct { } // NewValidOwner returns new instance of the ValidOwner -func NewValidOwner(cfg ValidOwnerConfig, ghClient *github.Client) (*ValidOwner, error) { +func NewValidOwner(cfg ValidOwnerConfig, ghClient *github.Client, checkScopes bool) (*ValidOwner, error) { split := strings.Split(cfg.Repository, "/") if len(split) != 2 { return nil, errors.Errorf("Wrong repository name. Expected pattern 'owner/repository', got '%s'", cfg.Repository) @@ -63,6 +64,7 @@ func NewValidOwner(cfg ValidOwnerConfig, ghClient *github.Client) (*ValidOwner, return &ValidOwner{ ghClient: ghClient, + checkScopes: checkScopes, orgName: split[0], orgRepoName: split[1], ignOwners: ignOwners, @@ -368,6 +370,12 @@ func (v *ValidOwner) CheckSatisfied(ctx context.Context) error { } } + if !v.checkScopes { + // If the GitHub client uses a GitHub App, the headers won't have scope information. + // TODO: Call the https://api.github.com/app/installations and check if the `permission` field has `"members": "read" + return nil + } + return v.checkRequiredScopes(resp.Header) } diff --git a/internal/check/valid_owner_test.go b/internal/check/valid_owner_test.go index a3a7a7d..6782e3f 100644 --- a/internal/check/valid_owner_test.go +++ b/internal/check/valid_owner_test.go @@ -58,7 +58,7 @@ func TestValidOwnerCheckerIgnoredOwner(t *testing.T) { ownerCheck, err := check.NewValidOwner(check.ValidOwnerConfig{ Repository: "org/repo", IgnoredOwners: []string{"@owner1"}, - }, nil) + }, nil, true) require.NoError(t, err) givenCodeowners := `* @owner1` @@ -106,7 +106,7 @@ func TestValidOwnerCheckerIgnoredOwner(t *testing.T) { Repository: "org/repo", AllowUnownedPatterns: tc.allowUnownedPatterns, IgnoredOwners: []string{"@owner1"}, - }, nil) + }, nil, true) require.NoError(t, err) // when @@ -147,7 +147,7 @@ func TestValidOwnerCheckerOwnersMustBeTeams(t *testing.T) { Repository: "org/repo", AllowUnownedPatterns: tc.allowUnownedPatterns, OwnersMustBeTeams: true, - }, nil) + }, nil, true) require.NoError(t, err) // when diff --git a/internal/github/client.go b/internal/github/client.go index 58f9d8a..9661bf3 100644 --- a/internal/github/client.go +++ b/internal/github/client.go @@ -2,35 +2,77 @@ package github import ( "context" + "errors" "net/http" "time" + "github.com/bradleyfalzon/ghinstallation/v2" + "github.com/mszostok/codeowners-validator/pkg/url" - "github.com/google/go-github/v29/github" + "github.com/google/go-github/v41/github" "golang.org/x/oauth2" ) type ClientConfig struct { - AccessToken string + AccessToken string `envconfig:"optional"` + + AppID int64 `envconfig:"optional"` + AppPrivateKey string `envconfig:"optional"` + AppInstallationID int64 `envconfig:"optional"` + BaseURL string `envconfig:"optional"` UploadURL string `envconfig:"optional"` HTTPRequestTimeout time.Duration `envconfig:"default=30s"` } -func NewClient(ctx context.Context, cfg ClientConfig) (ghClient *github.Client, err error) { +// Validate validates if provided client options are valid. +func (c *ClientConfig) Validate() error { + if c.AccessToken == "" && c.AppID == 0 { + return errors.New("GitHub authorization is required, provide ACCESS_TOKEN or APP_ID") + } + + if c.AccessToken != "" && c.AppID != 0 { + return errors.New("GitHub ACCESS_TOKEN cannot be provided when APP_ID is specified") + } + + if c.AppID != 0 { + if c.AppInstallationID == 0 { + return errors.New("GitHub APP_INSTALLATION_ID is required with APP_ID") + } + if c.AppPrivateKey == "" { + return errors.New("GitHub APP_PRIVATE_KEY is required with APP_ID") + } + } + + return nil +} + +func NewClient(ctx context.Context, cfg *ClientConfig) (ghClient *github.Client, isApp bool, err error) { + if err := cfg.Validate(); err != nil { + return nil, false, err + } + httpClient := http.DefaultClient + if cfg.AccessToken != "" { httpClient = oauth2.NewClient(ctx, oauth2.StaticTokenSource( &oauth2.Token{AccessToken: cfg.AccessToken}, )) + } else if cfg.AppID != 0 { + httpClient, err = createAppInstallationHTTPClient(cfg) + isApp = true + if err != nil { + return + } } httpClient.Timeout = cfg.HTTPRequestTimeout baseURL, uploadURL := cfg.BaseURL, cfg.UploadURL if baseURL == "" { - return github.NewClient(httpClient), nil + ghClient = github.NewClient(httpClient) + return } if uploadURL == "" { // often the baseURL is same as the uploadURL, so we do not require to provide both of them @@ -38,5 +80,16 @@ func NewClient(ctx context.Context, cfg ClientConfig) (ghClient *github.Client, } bURL, uURL := url.CanonicalPath(baseURL), url.CanonicalPath(uploadURL) - return github.NewEnterpriseClient(bURL, uURL, httpClient) + ghClient, err = github.NewEnterpriseClient(bURL, uURL, httpClient) + return +} + +func createAppInstallationHTTPClient(cfg *ClientConfig) (client *http.Client, err error) { + tr := http.DefaultTransport + itr, err := ghinstallation.New(tr, cfg.AppID, cfg.AppInstallationID, []byte(cfg.AppPrivateKey)) + if err != nil { + return nil, err + } + + return &http.Client{Transport: itr}, nil } diff --git a/internal/load/load.go b/internal/load/load.go index ad9bf72..02326de 100644 --- a/internal/load/load.go +++ b/internal/load/load.go @@ -38,12 +38,12 @@ func Checks(ctx context.Context, enabledChecks, experimentalChecks []string) ([] return nil, errors.Wrapf(err, "while loading config for %s", "owners") } - ghClient, err := github.NewClient(ctx, cfg.Github) + ghClient, isApp, err := github.NewClient(ctx, &cfg.Github) if err != nil { return nil, errors.Wrap(err, "while creating GitHub client") } - owners, err := check.NewValidOwner(cfg.OwnerChecker, ghClient) + owners, err := check.NewValidOwner(cfg.OwnerChecker, ghClient, !isApp) if err != nil { return nil, errors.Wrap(err, "while enabling 'owners' checker") } diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index a451816..c30e67b 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -18,6 +18,10 @@ const ( binaryPathEnvName = "BINARY_PATH" codeownersSamplesRepo = "https://github.com/gh-codeowners/codeowners-samples.git" caseInsensitiveOrgCodeownersSamplesRepo = "https://github.com/GitHubCODEOWNERS/codeowners-samples.git" + + gitHubAppId = "190766" + gitHubAppInstallationId = "24938181" + gitHubAppPrivateKeyEnvName = "APP_PRIVATE_KEY" ) var repositories = []struct { @@ -348,6 +352,35 @@ func TestOwnerCheckAuthZAndAuthN(t *testing.T) { }) } +// To update golden file, run: +// TEST=TestGitHubAppAuth APP_PRIVATE_KEY=`cat private-key.pem` UPDATE_GOLDEN=true make test-integration +func TestGitHubAppAuth(t *testing.T) { + t.Parallel() + + // given + repoDir, cleanup := CloneRepo(t, caseInsensitiveOrgCodeownersSamplesRepo, "happy-path") + defer cleanup() + + codeownersCmd := Exec(). + Binary(os.Getenv(binaryPathEnvName)). + WithEnv("REPOSITORY_PATH", repoDir). + WithEnv("CHECKS", "owners"). + WithEnv("OWNER_CHECKER_REPOSITORY", "GitHubCODEOWNERS/codeowners-samples"). + WithEnv("GITHUB_APP_ID", gitHubAppId). + WithEnv("GITHUB_APP_INSTALLATION_ID", gitHubAppInstallationId). + WithEnv("GITHUB_APP_PRIVATE_KEY", os.Getenv(gitHubAppPrivateKeyEnvName)) + + // when + result := codeownersCmd.AwaitResultAtMost(time.Minute) + + // then + assert.Equal(t, 0, result.ExitCode) + + normalizedOutput := normalizeTimeDurations(result.Stdout) + g := goldie.New(t, goldie.WithNameSuffix(".golden.txt")) + g.Assert(t, t.Name(), []byte(normalizedOutput)) +} + func TestMultipleChecksSuccess(t *testing.T) { t.Skip("not implemented yet") } diff --git a/tests/integration/testdata/TestGitHubAppAuth.golden.txt b/tests/integration/testdata/TestGitHubAppAuth.golden.txt new file mode 100644 index 0000000..e93cda6 --- /dev/null +++ b/tests/integration/testdata/TestGitHubAppAuth.golden.txt @@ -0,0 +1,4 @@ +==> Executing Valid Owner Checker () + Check OK + +1 check(s) executed, no failure(s) diff --git a/tests/integration/testdata/TestOwnerCheckAuthZAndAuthN/token_not_specified.golden.txt b/tests/integration/testdata/TestOwnerCheckAuthZAndAuthN/token_not_specified.golden.txt index fddcd69..6b3983b 100644 --- a/tests/integration/testdata/TestOwnerCheckAuthZAndAuthN/token_not_specified.golden.txt +++ b/tests/integration/testdata/TestOwnerCheckAuthZAndAuthN/token_not_specified.golden.txt @@ -1 +1 @@ -time="