-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: use our own Github client library (#73)
Starting to slowly move away from `gobox`, first stop is using our own Github client library. The folder structure is to allow us to support other VCS providers if needed at some point. I also removed `gitauth` because it appeared to be dead code. Added a `cmdexec` package for the purposes of mocking command output.
- Loading branch information
1 parent
eec1af4
commit 220bcf8
Showing
17 changed files
with
588 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (C) 2024 stencil contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package github | ||
|
||
import "errors" | ||
|
||
// ErrNoToken is returned when no token is found in the configured | ||
// credential providers. | ||
type ErrNoToken struct { | ||
errs []error | ||
} | ||
|
||
// Unwrap returns the errors that caused the ErrNoToken error. | ||
func (e ErrNoToken) Unwrap() []error { | ||
return e.errs | ||
} | ||
|
||
// Error returns the error message for ErrNoToken. | ||
func (e ErrNoToken) Error() string { | ||
return errors.Join(e.errs...).Error() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Copyright (C) 2024 stencil contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Package github provides methods for creating a Github client for the | ||
// purposes of interacting with the API. Provides support for retrieving | ||
// authentication tokens from the following sources: | ||
// | ||
// - Environment variables ($GITHUB_TOKEN) | ||
// - Github CLI | ||
package github | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
"github.com/google/go-github/v62/github" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
// defaultProviders is a list of credential providers that are used to | ||
// retrieve a token by default. | ||
var defaultProviders = []provider{ | ||
&envProvider{}, | ||
&ghProvider{}, | ||
} | ||
|
||
// Token returns a valid token from one of the configured credential | ||
// providers. If no token is found, ErrNoToken is returned. | ||
func Token() (string, error) { | ||
token := "" | ||
errors := []error{} | ||
for _, p := range defaultProviders { | ||
var err error | ||
token, err = p.Token() | ||
if err != nil { | ||
errors = append(errors, err) | ||
continue | ||
} | ||
|
||
// Got a token, break out of the loop. | ||
if token != "" { | ||
break | ||
} | ||
} | ||
if token == "" { | ||
return "", ErrNoToken{errors} | ||
} | ||
return token, nil | ||
} | ||
|
||
// New returns a new [github.Client] using credentials from one of the | ||
// configured credential providers. If no token is found, an | ||
// unauthenticated client is returned. | ||
func New() (*github.Client, error) { | ||
token, err := Token() | ||
if err != nil { | ||
return github.NewClient(http.DefaultClient), nil | ||
} | ||
|
||
// Note: background ctx is used here because we don't want the oauth2 | ||
// client to pick up credentials from a provided context. | ||
return github.NewClient(oauth2.NewClient(context.Background(), | ||
oauth2.StaticTokenSource( | ||
&oauth2.Token{AccessToken: token}, | ||
)), | ||
), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Copyright (C) 2024 stencil contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package github | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"testing" | ||
|
||
"go.rgst.io/stencil/internal/testing/cmdexec" | ||
"golang.org/x/oauth2" | ||
"gotest.tools/v3/assert" | ||
) | ||
|
||
// TestEnvOverGH ensures that the GITHUB_TOKEN environment variable | ||
// takes precedence over the token returned by the gh CLI. | ||
func TestEnvOverGH(t *testing.T) { | ||
t.Setenv("GITHUB_TOKEN", "github_token") | ||
|
||
cmdexec.UseMockExecutor(t, cmdexec.NewMockExecutor(&cmdexec.MockCommand{ | ||
Name: "gh", | ||
Args: []string{"auth", "token"}, | ||
Stdout: []byte("gh_token\n"), | ||
})) | ||
|
||
cli, err := New() | ||
assert.NilError(t, err) | ||
token, err := cli.Client().Transport.(*oauth2.Transport).Source.Token() | ||
assert.NilError(t, err) | ||
assert.Equal(t, "github_token", token.AccessToken) | ||
} | ||
|
||
// TestReturnsErrors ensures that New returns underlying provider errors | ||
// and that they can be found. | ||
func TestReturnsErrors(t *testing.T) { | ||
cmdexec.UseMockExecutor(t, cmdexec.NewMockExecutor(&cmdexec.MockCommand{ | ||
Name: "gh", | ||
Args: []string{"auth", "token"}, | ||
Err: fmt.Errorf("bad things happened"), | ||
})) | ||
|
||
token, err := Token() | ||
var tokenErr ErrNoToken | ||
assert.Assert(t, errors.As(err, &tokenErr)) | ||
assert.Assert(t, token == "", "expected token to be empty") | ||
|
||
// Find a GH cli error | ||
var found bool | ||
for _, e := range tokenErr.errs { | ||
if e.Error() == "gh failed: bad things happened (no stderr)" { | ||
found = true | ||
} | ||
} | ||
assert.Assert(t, found, "expected error not found") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Copyright (C) 2024 stencil contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package github | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
) | ||
|
||
// envProvider implements the provider interface using the environment | ||
// variables to retrieve a token. | ||
type envProvider struct{} | ||
|
||
// Token returns a valid token or an error if no token is found. | ||
func (p *envProvider) Token() (string, error) { | ||
envVars := []string{"GITHUB_TOKEN"} | ||
for _, env := range envVars { | ||
if token := os.Getenv(env); token != "" { | ||
return token, nil | ||
} | ||
} | ||
|
||
return "", fmt.Errorf("no token found in environment variables: %v", envVars) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Copyright (C) 2024 stencil contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package github | ||
|
||
import ( | ||
"testing" | ||
|
||
"gotest.tools/v3/assert" | ||
) | ||
|
||
func TestEnvProviderReadsCorrectEnvVar(t *testing.T) { | ||
t.Setenv("GITHUB_TOKEN", "token") | ||
|
||
p := &envProvider{} | ||
token, err := p.Token() | ||
assert.NilError(t, err) | ||
assert.Equal(t, "token", token) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright (C) 2024 stencil contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package github | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os/exec" | ||
"strings" | ||
|
||
"go.rgst.io/stencil/internal/testing/cmdexec" | ||
) | ||
|
||
// ghProvider implements the provider interface using the Github CLI to | ||
// retrieve a token. | ||
type ghProvider struct{} | ||
|
||
// Token returns a valid token or an error if no token is found. | ||
func (p *ghProvider) Token() (string, error) { | ||
cmd := cmdexec.Command("gh", "auth", "token") | ||
token, err := cmd.Output() | ||
if err != nil { | ||
var execErr *exec.ExitError | ||
if errors.As(err, &execErr) { | ||
return "", fmt.Errorf("gh failed: %s (%w)", string(execErr.Stderr), execErr) | ||
} | ||
|
||
return "", fmt.Errorf("gh failed: %w (no stderr)", err) | ||
} | ||
|
||
return strings.TrimSpace(string(token)), nil | ||
} |
Oops, something went wrong.