diff --git a/pkg/cmd/auth/auth.go b/pkg/cmd/auth/auth.go index c3df8486d..9b344a636 100644 --- a/pkg/cmd/auth/auth.go +++ b/pkg/cmd/auth/auth.go @@ -7,6 +7,7 @@ import ( authRefreshCmd "github.com/cli/cli/v2/pkg/cmd/auth/refresh" authSetupGitCmd "github.com/cli/cli/v2/pkg/cmd/auth/setupgit" authStatusCmd "github.com/cli/cli/v2/pkg/cmd/auth/status" + authTokenCmd "github.com/cli/cli/v2/pkg/cmd/auth/token" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/spf13/cobra" ) @@ -28,6 +29,7 @@ func NewCmdAuth(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(authRefreshCmd.NewCmdRefresh(f, nil)) cmd.AddCommand(gitCredentialCmd.NewCmdCredential(f, nil)) cmd.AddCommand(authSetupGitCmd.NewCmdSetupGit(f, nil)) + cmd.AddCommand(authTokenCmd.NewCmdToken(f, nil)) return cmd } diff --git a/pkg/cmd/auth/token/token.go b/pkg/cmd/auth/token/token.go new file mode 100644 index 000000000..9dcc65e8d --- /dev/null +++ b/pkg/cmd/auth/token/token.go @@ -0,0 +1,65 @@ +package token + +import ( + "fmt" + + "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/ghinstance" + "github.com/cli/cli/v2/pkg/cmdutil" + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/spf13/cobra" +) + +type TokenOptions struct { + IO *iostreams.IOStreams + Config func() (config.Config, error) + + Hostname string +} + +func NewCmdToken(f *cmdutil.Factory, runF func(*TokenOptions) error) *cobra.Command { + opts := &TokenOptions{ + IO: f.IOStreams, + Config: f.Config, + } + + cmd := &cobra.Command{ + Use: "token", + Short: "Print the auth token gh is configured to use", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + if runF != nil { + return runF(opts) + } + + return tokenRun(opts) + }, + } + + cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The hostname of the GitHub instance authenticated with") + + return cmd +} + +func tokenRun(opts *TokenOptions) error { + hostname := opts.Hostname + if hostname == "" { + hostname = ghinstance.Default() + } + + cfg, err := opts.Config() + if err != nil { + return err + } + + key := "oauth_token" + val, err := cfg.GetOrDefault(hostname, key) + if err != nil { + return fmt.Errorf("no oauth token") + } + + if val != "" { + fmt.Fprintf(opts.IO.Out, "%s\n", val) + } + return nil +} diff --git a/pkg/cmd/auth/token/token_test.go b/pkg/cmd/auth/token/token_test.go new file mode 100644 index 000000000..22d13a9a5 --- /dev/null +++ b/pkg/cmd/auth/token/token_test.go @@ -0,0 +1,139 @@ +package token + +import ( + "bytes" + "testing" + + "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/pkg/cmdutil" + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/google/shlex" + "github.com/stretchr/testify/assert" +) + +func TestNewCmdToken(t *testing.T) { + tests := []struct { + name string + input string + output TokenOptions + wantErr bool + wantErrMsg string + }{ + { + name: "no flags", + input: "", + output: TokenOptions{}, + }, + { + name: "with hostname", + input: "--hostname github.mycompany.com", + output: TokenOptions{Hostname: "github.mycompany.com"}, + }, + { + name: "with shorthand hostname", + input: "-h github.mycompany.com", + output: TokenOptions{Hostname: "github.mycompany.com"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ios, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + IOStreams: ios, + Config: func() (config.Config, error) { + cfg := config.NewBlankConfig() + return cfg, nil + }, + } + argv, err := shlex.Split(tt.input) + assert.NoError(t, err) + + var cmdOpts *TokenOptions + cmd := NewCmdToken(f, func(opts *TokenOptions) error { + cmdOpts = opts + return nil + }) + // TODO cobra hack-around + cmd.Flags().BoolP("help", "x", false, "") + + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + + _, err = cmd.ExecuteC() + if tt.wantErr { + assert.Error(t, err) + assert.EqualError(t, err, tt.wantErrMsg) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.output.Hostname, cmdOpts.Hostname) + }) + } +} + +func Test_tokenRun(t *testing.T) { + tests := []struct { + name string + opts TokenOptions + wantStdout string + wantErr bool + wantErrMsg string + }{ + { + name: "token", + opts: TokenOptions{ + Config: func() (config.Config, error) { + cfg := config.NewBlankConfig() + cfg.Set("github.com", "oauth_token", "gho_ABCDEFG") + return cfg, nil + }, + }, + wantStdout: "gho_ABCDEFG\n", + }, + { + name: "token by hostname", + opts: TokenOptions{ + Config: func() (config.Config, error) { + cfg := config.NewBlankConfig() + cfg.Set("github.com", "oauth_token", "gho_ABCDEFG") + cfg.Set("github.mycompany.com", "oauth_token", "gho_1234567") + return cfg, nil + }, + Hostname: "github.mycompany.com", + }, + wantStdout: "gho_1234567\n", + }, + { + name: "no token", + opts: TokenOptions{ + Config: func() (config.Config, error) { + cfg := config.NewBlankConfig() + return cfg, nil + }, + }, + wantErr: true, + wantErrMsg: "no oauth token", + }, + } + + for _, tt := range tests { + ios, _, stdout, _ := iostreams.Test() + tt.opts.IO = ios + + t.Run(tt.name, func(t *testing.T) { + err := tokenRun(&tt.opts) + if tt.wantErr { + assert.Error(t, err) + assert.EqualError(t, err, tt.wantErrMsg) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wantStdout, stdout.String()) + }) + } +}