Add support for GH_TOKEN and GH_ENTERPRISE_TOKEN

This commit is contained in:
Sam Coe 2020-11-09 15:02:31 +03:00
parent 6e1e62f496
commit a79a0bbfd7
No known key found for this signature in database
GPG key ID: 8E322C20F811D086
5 changed files with 215 additions and 60 deletions

View file

@ -136,17 +136,15 @@ func main() {
cs := cmdFactory.IOStreams.ColorScheme()
authCheckEnabled := os.Getenv("GITHUB_TOKEN") == "" &&
os.Getenv("GITHUB_ENTERPRISE_TOKEN") == "" &&
cmd != nil && cmdutil.IsAuthCheckEnabled(cmd)
if authCheckEnabled {
if !cmdutil.CheckAuth(cfg) {
fmt.Fprintln(stderr, cs.Bold("Welcome to GitHub CLI!"))
fmt.Fprintln(stderr)
fmt.Fprintln(stderr, "To authenticate, please run `gh auth login`.")
fmt.Fprintln(stderr, "You can also set the GITHUB_TOKEN environment variable, if preferred.")
os.Exit(4)
}
// _, _, tokenPresent := config.AuthTokenFromEnv(ghinstance.OverridableDefault())
// authCheckEnabled := !tokenPresent && cmd != nil && cmdutil.IsAuthCheckEnabled(cmd)
authCheckEnabled := cmd != nil && cmdutil.IsAuthCheckEnabled(cmd)
if authCheckEnabled && !cmdutil.CheckAuth(cfg) {
fmt.Fprintln(stderr, cs.Bold("Welcome to GitHub CLI!"))
fmt.Fprintln(stderr)
fmt.Fprintln(stderr, "To authenticate, please run `gh auth login`.")
fmt.Fprintln(stderr, "You can also set the one of the oauth token environment variables, if preferred.")
os.Exit(4)
}
rootCmd.SetArgs(expandedArgs)
@ -248,8 +246,8 @@ func basicClient(currentVersion string) (*api.Client, error) {
}
opts = append(opts, api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", currentVersion)))
token := os.Getenv("GITHUB_TOKEN")
if token == "" {
token, _, ok := config.AuthTokenFromEnv(ghinstance.Default())
if !ok {
if c, err := config.ParseDefaultConfig(); err == nil {
token, _ = c.Get(ghinstance.Default(), "oauth_token")
}

View file

@ -8,7 +8,9 @@ import (
)
const (
GH_TOKEN = "GH_TOKEN"
GITHUB_TOKEN = "GITHUB_TOKEN"
GH_ENTERPRISE_TOKEN = "GH_ENTERPRISE_TOKEN"
GITHUB_ENTERPRISE_TOKEN = "GITHUB_ENTERPRISE_TOKEN"
)
@ -28,7 +30,8 @@ func (c *envConfig) Hosts() ([]string, error) {
hasDefault = true
}
}
if (err != nil || !hasDefault) && os.Getenv(GITHUB_TOKEN) != "" {
_, _, found := AuthTokenFromEnv(ghinstance.Default())
if (err != nil || !hasDefault) && found {
hosts = append([]string{ghinstance.Default()}, hosts...)
return hosts, nil
}
@ -42,13 +45,8 @@ func (c *envConfig) Get(hostname, key string) (string, error) {
func (c *envConfig) GetWithSource(hostname, key string) (string, string, error) {
if hostname != "" && key == "oauth_token" {
envName := GITHUB_TOKEN
if ghinstance.IsEnterprise(hostname) {
envName = GITHUB_ENTERPRISE_TOKEN
}
if value := os.Getenv(envName); value != "" {
return value, envName, nil
if value, name, found := AuthTokenFromEnv(hostname); found {
return value, name, nil
}
}
@ -57,15 +55,28 @@ func (c *envConfig) GetWithSource(hostname, key string) (string, string, error)
func (c *envConfig) CheckWriteable(hostname, key string) error {
if hostname != "" && key == "oauth_token" {
envName := GITHUB_TOKEN
if ghinstance.IsEnterprise(hostname) {
envName = GITHUB_ENTERPRISE_TOKEN
}
if os.Getenv(envName) != "" {
return fmt.Errorf("read-only token in %s cannot be modified", envName)
if _, name, found := AuthTokenFromEnv(hostname); found {
return fmt.Errorf("read-only token in %s cannot be modified", name)
}
}
return c.Config.CheckWriteable(hostname, key)
}
func AuthTokenFromEnv(hostname string) (string, string, bool) {
if ghinstance.IsEnterprise(hostname) {
if token, found := os.LookupEnv(GH_ENTERPRISE_TOKEN); found {
return token, GH_ENTERPRISE_TOKEN, found
}
token, found := os.LookupEnv(GITHUB_ENTERPRISE_TOKEN)
return token, GITHUB_ENTERPRISE_TOKEN, found
}
if token, found := os.LookupEnv(GH_TOKEN); found {
return token, GH_TOKEN, found
}
token, found := os.LookupEnv(GITHUB_TOKEN)
return token, GITHUB_TOKEN, found
}

View file

@ -11,9 +11,13 @@ import (
func TestInheritEnv(t *testing.T) {
orig_GITHUB_TOKEN := os.Getenv("GITHUB_TOKEN")
orig_GITHUB_ENTERPRISE_TOKEN := os.Getenv("GITHUB_ENTERPRISE_TOKEN")
orig_GH_TOKEN := os.Getenv("GH_TOKEN")
orig_GH_ENTERPRISE_TOKEN := os.Getenv("GH_ENTERPRISE_TOKEN")
t.Cleanup(func() {
os.Setenv("GITHUB_TOKEN", orig_GITHUB_TOKEN)
os.Setenv("GITHUB_ENTERPRISE_TOKEN", orig_GITHUB_ENTERPRISE_TOKEN)
os.Setenv("GH_TOKEN", orig_GH_TOKEN)
os.Setenv("GH_ENTERPRISE_TOKEN", orig_GH_ENTERPRISE_TOKEN)
})
type wants struct {
@ -28,15 +32,15 @@ func TestInheritEnv(t *testing.T) {
baseConfig string
GITHUB_TOKEN string
GITHUB_ENTERPRISE_TOKEN string
GH_TOKEN string
GH_ENTERPRISE_TOKEN string
hostname string
wants wants
}{
{
name: "blank",
baseConfig: ``,
GITHUB_TOKEN: "",
GITHUB_ENTERPRISE_TOKEN: "",
hostname: "github.com",
name: "blank",
baseConfig: ``,
hostname: "github.com",
wants: wants{
hosts: []string(nil),
token: "",
@ -45,11 +49,10 @@ func TestInheritEnv(t *testing.T) {
},
},
{
name: "GITHUB_TOKEN over blank config",
baseConfig: ``,
GITHUB_TOKEN: "OTOKEN",
GITHUB_ENTERPRISE_TOKEN: "",
hostname: "github.com",
name: "GITHUB_TOKEN over blank config",
baseConfig: ``,
GITHUB_TOKEN: "OTOKEN",
hostname: "github.com",
wants: wants{
hosts: []string{"github.com"},
token: "OTOKEN",
@ -58,11 +61,34 @@ func TestInheritEnv(t *testing.T) {
},
},
{
name: "GITHUB_TOKEN not applicable to GHE",
baseConfig: ``,
GITHUB_TOKEN: "OTOKEN",
GITHUB_ENTERPRISE_TOKEN: "",
hostname: "example.org",
name: "GH_TOKEN over blank config",
baseConfig: ``,
GH_TOKEN: "OTOKEN",
hostname: "github.com",
wants: wants{
hosts: []string{"github.com"},
token: "OTOKEN",
source: "GH_TOKEN",
writeable: false,
},
},
{
name: "GITHUB_TOKEN not applicable to GHE",
baseConfig: ``,
GITHUB_TOKEN: "OTOKEN",
hostname: "example.org",
wants: wants{
hosts: []string{"github.com"},
token: "",
source: "~/.config/gh/config.yml",
writeable: true,
},
},
{
name: "GH_TOKEN not applicable to GHE",
baseConfig: ``,
GH_TOKEN: "OTOKEN",
hostname: "example.org",
wants: wants{
hosts: []string{"github.com"},
token: "",
@ -73,7 +99,6 @@ func TestInheritEnv(t *testing.T) {
{
name: "GITHUB_ENTERPRISE_TOKEN over blank config",
baseConfig: ``,
GITHUB_TOKEN: "",
GITHUB_ENTERPRISE_TOKEN: "ENTOKEN",
hostname: "example.org",
wants: wants{
@ -83,6 +108,18 @@ func TestInheritEnv(t *testing.T) {
writeable: false,
},
},
{
name: "GH_ENTERPRISE_TOKEN over blank config",
baseConfig: ``,
GH_ENTERPRISE_TOKEN: "ENTOKEN",
hostname: "example.org",
wants: wants{
hosts: []string(nil),
token: "ENTOKEN",
source: "GH_ENTERPRISE_TOKEN",
writeable: false,
},
},
{
name: "token from file",
baseConfig: heredoc.Doc(`
@ -90,9 +127,7 @@ func TestInheritEnv(t *testing.T) {
github.com:
oauth_token: OTOKEN
`),
GITHUB_TOKEN: "",
GITHUB_ENTERPRISE_TOKEN: "",
hostname: "github.com",
hostname: "github.com",
wants: wants{
hosts: []string{"github.com"},
token: "OTOKEN",
@ -107,9 +142,8 @@ func TestInheritEnv(t *testing.T) {
github.com:
oauth_token: OTOKEN
`),
GITHUB_TOKEN: "ENVTOKEN",
GITHUB_ENTERPRISE_TOKEN: "",
hostname: "github.com",
GITHUB_TOKEN: "ENVTOKEN",
hostname: "github.com",
wants: wants{
hosts: []string{"github.com"},
token: "ENVTOKEN",
@ -117,6 +151,80 @@ func TestInheritEnv(t *testing.T) {
writeable: false,
},
},
{
name: "GH_TOKEN shadows token from file",
baseConfig: heredoc.Doc(`
hosts:
github.com:
oauth_token: OTOKEN
`),
GH_TOKEN: "ENVTOKEN",
hostname: "github.com",
wants: wants{
hosts: []string{"github.com"},
token: "ENVTOKEN",
source: "GH_TOKEN",
writeable: false,
},
},
{
name: "GITHUB_ENTERPRISE_TOKEN shadows token from file",
baseConfig: heredoc.Doc(`
hosts:
example.org:
oauth_token: OTOKEN
`),
GITHUB_ENTERPRISE_TOKEN: "ENVTOKEN",
hostname: "example.org",
wants: wants{
hosts: []string{"example.org"},
token: "ENVTOKEN",
source: "GITHUB_ENTERPRISE_TOKEN",
writeable: false,
},
},
{
name: "GH_ENTERPRISE_TOKEN shadows token from file",
baseConfig: heredoc.Doc(`
hosts:
example.org:
oauth_token: OTOKEN
`),
GH_ENTERPRISE_TOKEN: "ENVTOKEN",
hostname: "example.org",
wants: wants{
hosts: []string{"example.org"},
token: "ENVTOKEN",
source: "GH_ENTERPRISE_TOKEN",
writeable: false,
},
},
{
name: "GH_TOKEN shadows token from GITHUB_TOKEN",
baseConfig: ``,
GH_TOKEN: "GHTOKEN",
GITHUB_TOKEN: "GITHUBTOKEN",
hostname: "github.com",
wants: wants{
hosts: []string{"github.com"},
token: "GHTOKEN",
source: "GH_TOKEN",
writeable: false,
},
},
{
name: "GH_ENTERPRISE_TOKEN shadows token from GITHUB_ENTERPRISE_TOKEN",
baseConfig: ``,
GH_ENTERPRISE_TOKEN: "GHTOKEN",
GITHUB_ENTERPRISE_TOKEN: "GITHUBTOKEN",
hostname: "example.org",
wants: wants{
hosts: []string(nil),
token: "GHTOKEN",
source: "GH_ENTERPRISE_TOKEN",
writeable: false,
},
},
{
name: "GITHUB_TOKEN adds host entry",
baseConfig: heredoc.Doc(`
@ -124,9 +232,8 @@ func TestInheritEnv(t *testing.T) {
example.org:
oauth_token: OTOKEN
`),
GITHUB_TOKEN: "ENVTOKEN",
GITHUB_ENTERPRISE_TOKEN: "",
hostname: "github.com",
GITHUB_TOKEN: "ENVTOKEN",
hostname: "github.com",
wants: wants{
hosts: []string{"github.com", "example.org"},
token: "ENVTOKEN",
@ -134,11 +241,48 @@ func TestInheritEnv(t *testing.T) {
writeable: false,
},
},
{
name: "GH_TOKEN adds host entry",
baseConfig: heredoc.Doc(`
hosts:
example.org:
oauth_token: OTOKEN
`),
GH_TOKEN: "ENVTOKEN",
hostname: "github.com",
wants: wants{
hosts: []string{"github.com", "example.org"},
token: "ENVTOKEN",
source: "GH_TOKEN",
writeable: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Setenv("GITHUB_TOKEN", tt.GITHUB_TOKEN)
os.Setenv("GITHUB_ENTERPRISE_TOKEN", tt.GITHUB_ENTERPRISE_TOKEN)
if tt.GITHUB_TOKEN != "" {
os.Setenv("GITHUB_TOKEN", tt.GITHUB_TOKEN)
} else {
os.Unsetenv("GITHUB_TOKEN")
}
if tt.GITHUB_ENTERPRISE_TOKEN != "" {
os.Setenv("GITHUB_ENTERPRISE_TOKEN", tt.GITHUB_ENTERPRISE_TOKEN)
} else {
os.Unsetenv("GITHUB_ENTERPRISE_TOKEN")
}
if tt.GH_TOKEN != "" {
os.Setenv("GH_TOKEN", tt.GH_TOKEN)
} else {
os.Unsetenv("GH_TOKEN")
}
if tt.GH_ENTERPRISE_TOKEN != "" {
os.Setenv("GH_ENTERPRISE_TOKEN", tt.GH_ENTERPRISE_TOKEN)
} else {
os.Unsetenv("GH_ENTERPRISE_TOKEN")
}
baseCfg := NewFromString(tt.baseConfig)
cfg := InheritEnv(baseCfg)

View file

@ -119,9 +119,9 @@ original query accepts an '$endCursor: String' variable and that it fetches the
`),
Annotations: map[string]string{
"help:environment": heredoc.Doc(`
GITHUB_TOKEN: an authentication token for github.com API requests.
GH_TOKEN, GITHUB_TOKEN (in order of procedence): an authentication token for github.com API requests.
GITHUB_ENTERPRISE_TOKEN: an authentication token for API requests to GitHub Enterprise.
GH_ENTERPRISE_TOKEN, GITHUB_ENTERPRISE_TOKEN (in order of procedence): an authentication token for API requests to GitHub Enterprise.
GH_HOST: make the request to a GitHub host other than github.com.
`),

View file

@ -9,10 +9,12 @@ var HelpTopics = map[string]map[string]string{
"environment": {
"short": "Environment variables that can be used with gh",
"long": heredoc.Doc(`
GITHUB_TOKEN: an authentication token for github.com API requests. Setting this avoids
being prompted to authenticate and takes precedence over previously stored credentials.
GH_TOKEN, GITHUB_TOKEN (in order of procedence): an authentication token for github.com
API requests. Setting this avoids being prompted to authenticate and takes precedence over
previously stored credentials.
GITHUB_ENTERPRISE_TOKEN: an authentication token for API requests to GitHub Enterprise.
GH_ENTERPRISE_TOKEN, GITHUB_ENTERPRISE_TOKEN (in order of precedence): an authentication
token for API requests to GitHub Enterprise.
GH_REPO: specify the GitHub repository in the "[HOST/]OWNER/REPO" format for commands
that otherwise operate on a local repository.