Merge pull request #2388 from cli/gh-token

Add support for GH_TOKEN and GH_ENTERPRISE_TOKEN
This commit is contained in:
Sam 2020-11-16 08:27:50 +03:00 committed by GitHub
commit 116d5815b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 188 additions and 57 deletions

View file

@ -136,17 +136,12 @@ 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)
}
if cmd != nil && cmdutil.IsAuthCheckEnabled(cmd) && !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 auth token environment variables, if preferred.")
os.Exit(4)
}
rootCmd.SetArgs(expandedArgs)
@ -248,7 +243,7 @@ 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")
token, _ := config.AuthTokenFromEnv(ghinstance.Default())
if token == "" {
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) != "" {
token, _ := AuthTokenFromEnv(ghinstance.Default())
if (err != nil || !hasDefault) && token != "" {
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 token, env := AuthTokenFromEnv(hostname); token != "" {
return token, env, nil
}
}
@ -57,15 +55,26 @@ 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 token, env := AuthTokenFromEnv(hostname); token != "" {
return fmt.Errorf("read-only token in %s cannot be modified", env)
}
}
return c.Config.CheckWriteable(hostname, key)
}
func AuthTokenFromEnv(hostname string) (string, string) {
if ghinstance.IsEnterprise(hostname) {
if token := os.Getenv(GH_ENTERPRISE_TOKEN); token != "" {
return token, GH_ENTERPRISE_TOKEN
}
return os.Getenv(GITHUB_ENTERPRISE_TOKEN), GITHUB_ENTERPRISE_TOKEN
}
if token := os.Getenv(GH_TOKEN); token != "" {
return token, GH_TOKEN
}
return os.Getenv(GITHUB_TOKEN), GITHUB_TOKEN
}

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,29 @@ 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)
os.Setenv("GH_TOKEN", tt.GH_TOKEN)
os.Setenv("GH_ENTERPRISE_TOKEN", tt.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 precedence): 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 precedence): 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 precedence): 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.