diff --git a/pkg/api/client_options.go b/pkg/api/client_options.go index 3464aacc..15643bcb 100644 --- a/pkg/api/client_options.go +++ b/pkg/api/client_options.go @@ -94,8 +94,12 @@ func resolveOptions(opts ClientOptions) (ClientOptions, error) { opts.Host, _ = auth.DefaultHost() } if opts.AuthToken == "" { - opts.AuthToken, _ = auth.TokenForHost(opts.Host) + source := "" + opts.AuthToken, source = auth.TokenForHost(opts.Host) if opts.AuthToken == "" { + if source != "" && source != "default" { + return ClientOptions{}, fmt.Errorf("authentication token not found for host %s: %s", opts.Host, source) + } return ClientOptions{}, fmt.Errorf("authentication token not found for host %s", opts.Host) } } diff --git a/pkg/api/client_options_test.go b/pkg/api/client_options_test.go index 35cfb34c..f02cc9d5 100644 --- a/pkg/api/client_options_test.go +++ b/pkg/api/client_options_test.go @@ -49,6 +49,21 @@ func TestResolveOptions(t *testing.T) { } } +func TestResolveOptions_ReturnsTokenLookupFailureReason(t *testing.T) { + testutils.StubConfig(t, "") + t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_TOKEN", "") + t.Setenv("GH_ENTERPRISE_TOKEN", "") + t.Setenv("GITHUB_ENTERPRISE_TOKEN", "") + t.Setenv("GH_PATH", "/path/to/missing/gh") + + _, err := resolveOptions(ClientOptions{Host: "github.com"}) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "authentication token not found for host github.com") + assert.Contains(t, err.Error(), "failed to run `gh auth token`") +} + func TestOptionsNeedResolution(t *testing.T) { tests := []struct { name string diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 4c54642f..4e35a0d1 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -32,7 +32,7 @@ const ( // host. The source can be either an environment variable, configuration file, or the system // keyring. In the latter case, this shells out to "gh auth token" to obtain the token. // -// Returns "", "default" if no applicable token is found. +// Returns an empty token and a source that can either be "default" or a reason why lookup failed. func TokenForHost(host string) (string, string) { if token, source := TokenFromEnvOrConfig(host); token != "" { return token, source @@ -43,10 +43,14 @@ func TokenForHost(host string) (string, string) { ghExe, _ = safeexec.LookPath("gh") } - if ghExe != "" { - if token, source := tokenFromGh(ghExe, host); token != "" { - return token, source - } + if ghExe == "" { + return "", "gh executable not found in PATH" + } + + if token, source := tokenFromGh(ghExe, host); token != "" { + return token, source + } else if source != "" { + return "", source } return "", defaultSource @@ -101,11 +105,20 @@ func tokenForHost(cfg *config.Config, host string) (string, string) { func tokenFromGh(path string, host string) (string, string) { cmd := exec.Command(path, "auth", "token", "--secure-storage", "--hostname", host) - result, err := cmd.Output() + result, err := cmd.CombinedOutput() if err != nil { - return "", "gh" + details := strings.TrimSpace(string(result)) + if details == "" { + details = err.Error() + } + return "", fmt.Sprintf("failed to run `gh auth token`: %s", details) + } + + token := strings.TrimSpace(string(result)) + if token == "" { + return "", "gh did not return an authentication token" } - return strings.TrimSpace(string(result)), "gh" + return token, "gh" } // KnownHosts retrieves a list of hosts that have corresponding diff --git a/pkg/auth/auth_test.go b/pkg/auth/auth_test.go index f149c365..700d50c5 100644 --- a/pkg/auth/auth_test.go +++ b/pkg/auth/auth_test.go @@ -3,6 +3,7 @@ package auth import ( "testing" + "github.com/cli/go-gh/v2/internal/testutils" "github.com/cli/go-gh/v2/pkg/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -147,6 +148,16 @@ func TestTokenForHost(t *testing.T) { } } +func TestTokenForHost_ReturnsReasonWhenGhCommandFails(t *testing.T) { + testutils.StubConfig(t, testNoHostsConfigData()) + t.Setenv("GH_PATH", "/path/to/missing/gh") + + token, source := TokenForHost("github.com") + + assert.Equal(t, "", token) + assert.Contains(t, source, "failed to run `gh auth token`") +} + func TestDefaultHost(t *testing.T) { tests := []struct { name string @@ -399,8 +410,11 @@ func TestNormalizeHostname(t *testing.T) { } func testNoHostsConfig() *config.Config { - var data = `` - return config.ReadFromString(data) + return config.ReadFromString(testNoHostsConfigData()) +} + +func testNoHostsConfigData() string { + return `` } func testSingleHostConfig() *config.Config {