add ssh-key command
This commit is contained in:
parent
b5366c6ebf
commit
e26a1b98a1
8 changed files with 490 additions and 22 deletions
|
|
@ -203,9 +203,10 @@ func (c Client) HasMinimumScopes(hostname string) error {
|
|||
}
|
||||
|
||||
search := map[string]bool{
|
||||
"repo": false,
|
||||
"read:org": false,
|
||||
"admin:org": false,
|
||||
"repo": false,
|
||||
"read:org": false,
|
||||
"admin:org": false,
|
||||
"read:public_key": false,
|
||||
}
|
||||
for _, s := range strings.Split(scopesHeader, ",") {
|
||||
search[strings.TrimSpace(s)] = true
|
||||
|
|
@ -220,6 +221,10 @@ func (c Client) HasMinimumScopes(hostname string) error {
|
|||
missingScopes = append(missingScopes, "read:org")
|
||||
}
|
||||
|
||||
if !search["read:public_key"] && !search["admin:public_key"] {
|
||||
missingScopes = append(missingScopes, "read:public_key")
|
||||
}
|
||||
|
||||
if len(missingScopes) > 0 {
|
||||
return &MissingScopesError{MissingScopes: missingScopes}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, addition
|
|||
httpClient.Transport = api.VerboseLog(IO.ErrOut, logTraffic, IO.ColorEnabled())(httpClient.Transport)
|
||||
}
|
||||
|
||||
minimumScopes := []string{"repo", "read:org", "gist", "workflow"}
|
||||
minimumScopes := []string{"repo", "read:org", "gist", "workflow", "read:public_key"}
|
||||
scopes := append(minimumScopes, additionalScopes...)
|
||||
|
||||
callbackURI := "http://127.0.0.1/callback"
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
Token: "abc123",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
},
|
||||
wantHosts: "albert.wesker:\n oauth_token: abc123\n",
|
||||
},
|
||||
|
|
@ -221,7 +221,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
Token: "abc456",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("read:org"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("read:org,read:public_key"))
|
||||
},
|
||||
wantErr: `could not validate token: missing required scope 'repo'`,
|
||||
},
|
||||
|
|
@ -243,7 +243,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
Token: "abc456",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,admin:org"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,admin:org,read:public_key"))
|
||||
},
|
||||
wantHosts: "github.com:\n oauth_token: abc456\n",
|
||||
},
|
||||
|
|
@ -274,7 +274,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
} else {
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
}
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
|
|
@ -315,7 +315,7 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
_ = cfg.Set("github.com", "oauth_token", "ghi789")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
|
|
@ -341,7 +341,7 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
as.StubOne(false) // cache credentials
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
|
|
@ -363,7 +363,7 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
as.StubOne(false) // cache credentials
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
|
|
@ -436,7 +436,7 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
} else {
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func Test_statusRun(t *testing.T) {
|
|||
_ = c.Set("github.com", "oauth_token", "abc123")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
|
|
@ -106,8 +106,8 @@ func Test_statusRun(t *testing.T) {
|
|||
_ = c.Set("github.com", "oauth_token", "abc123")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
|
|
@ -124,7 +124,7 @@ func Test_statusRun(t *testing.T) {
|
|||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.StatusStringResponse(400, "no bueno"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
|
|
@ -140,8 +140,8 @@ func Test_statusRun(t *testing.T) {
|
|||
_ = c.Set("github.com", "oauth_token", "abc123")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
|
|
@ -159,8 +159,8 @@ func Test_statusRun(t *testing.T) {
|
|||
_ = c.Set("github.com", "oauth_token", "xyz456")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
|
|
@ -180,8 +180,8 @@ func Test_statusRun(t *testing.T) {
|
|||
_ = c.Set("github.com", "oauth_token", "xyz456")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,read:public_key"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
repoCmd "github.com/cli/cli/pkg/cmd/repo"
|
||||
creditsCmd "github.com/cli/cli/pkg/cmd/repo/credits"
|
||||
secretCmd "github.com/cli/cli/pkg/cmd/secret"
|
||||
sshKeyCmd "github.com/cli/cli/pkg/cmd/ssh-key"
|
||||
versionCmd "github.com/cli/cli/pkg/cmd/version"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -76,6 +77,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
|
|||
cmd.AddCommand(gistCmd.NewCmdGist(f))
|
||||
cmd.AddCommand(completionCmd.NewCmdCompletion(f.IOStreams))
|
||||
cmd.AddCommand(secretCmd.NewCmdSecret(f))
|
||||
cmd.AddCommand(sshKeyCmd.NewCmdSSHKey(f))
|
||||
|
||||
// the `api` command should not inherit any extra HTTP headers
|
||||
bareHTTPCmdFactory := *f
|
||||
|
|
|
|||
152
pkg/cmd/ssh-key/list/list.go
Normal file
152
pkg/cmd/ssh-key/list/list.go
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/utils"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ListOptions struct for list command
|
||||
type ListOptions struct {
|
||||
HTTPClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (config.Config, error)
|
||||
|
||||
ListMsg []string
|
||||
}
|
||||
|
||||
// NewCmdList creates a command for list all SSH Keys
|
||||
func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
|
||||
opts := &ListOptions{
|
||||
HTTPClient: f.HttpClient,
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
|
||||
ListMsg: []string{},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Args: cobra.ExactArgs(0),
|
||||
Short: "Lists currently added ssh keys",
|
||||
Long: heredoc.Doc(`Lists currently added ssh keys.
|
||||
|
||||
This interactive command lists all SSH keys associated with your account
|
||||
`),
|
||||
Example: heredoc.Doc(`
|
||||
$ gh ssh-key list
|
||||
# => lists all ssh keys associated with your account
|
||||
`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
return listRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func listRun(opts *ListOptions) error {
|
||||
apiClient, err := opts.getAPIClient()
|
||||
if err != nil {
|
||||
opts.printTerminal()
|
||||
return err
|
||||
}
|
||||
|
||||
err = opts.hasMinimumScopes(apiClient)
|
||||
if err != nil {
|
||||
opts.printTerminal()
|
||||
return err
|
||||
}
|
||||
|
||||
type keys struct {
|
||||
Title string
|
||||
Key string
|
||||
}
|
||||
|
||||
type result []keys
|
||||
|
||||
rs := result{}
|
||||
body := bytes.NewBufferString("")
|
||||
|
||||
err = apiClient.REST(ghinstance.Default(), "GET", "user/keys", body, &rs)
|
||||
if err != nil {
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("%s: Got %s", utils.RedX(), err))
|
||||
opts.printTerminal()
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range rs {
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("%s %s: %s \n %s: %s", utils.Cyan("✹"), utils.Bold("Name"), r.Title, utils.Bold("SSH-KEY"), r.Key))
|
||||
}
|
||||
|
||||
opts.printTerminal()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts *ListOptions) getAPIClient() (*api.Client, error) {
|
||||
httpClient, err := opts.HTTPClient()
|
||||
if err != nil {
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("%s: %s", utils.RedX(), err))
|
||||
return nil, err
|
||||
}
|
||||
return api.NewClientFromHTTP(httpClient), nil
|
||||
}
|
||||
|
||||
func (opts *ListOptions) hasMinimumScopes(apiClient *api.Client) error {
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("%s: %s", utils.RedX(), err))
|
||||
return err
|
||||
}
|
||||
|
||||
hostname := ghinstance.Default()
|
||||
|
||||
_, tokenSource, _ := cfg.GetWithSource(hostname, "oauth_token")
|
||||
|
||||
// TODO: Implement tests for this case when CheckWriteable function checks filesystem permissions
|
||||
tokenIsWriteable := cfg.CheckWriteable(hostname, "oauth_token") == nil
|
||||
|
||||
err = apiClient.HasMinimumScopes(hostname)
|
||||
|
||||
if err != nil {
|
||||
var missingScopes *api.MissingScopesError
|
||||
if errors.As(err, &missingScopes) {
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("%s: %s", utils.RedX(), err))
|
||||
if tokenIsWriteable {
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("- To request missing scopes, run: %s %s", utils.Bold("gh auth refresh -h"), hostname))
|
||||
}
|
||||
} else {
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("%s: authentication failed", utils.RedX()))
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("- The %s token in %s is no longer valid.", utils.Bold(hostname), utils.Bold(tokenSource)))
|
||||
if tokenIsWriteable {
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("- To re-authenticate, run: %s %s", utils.Bold("gh auth login -h"), utils.Bold(hostname)))
|
||||
opts.ListMsg = append(opts.ListMsg, fmt.Sprintf("- To forget about this host, run: %s %s", utils.Bold("gh auth logout -h"), utils.Bold(hostname)))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts *ListOptions) printTerminal() {
|
||||
stderr := opts.IO.ErrOut
|
||||
for _, line := range opts.ListMsg {
|
||||
fmt.Fprintf(stderr, " %s\n", line)
|
||||
}
|
||||
}
|
||||
289
pkg/cmd/ssh-key/list/list_test.go
Normal file
289
pkg/cmd/ssh-key/list/list_test.go
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
func TestCmdList(t *testing.T) {
|
||||
io, _, _, _ := iostreams.Test()
|
||||
io.SetStdoutTTY(true)
|
||||
io.SetStdinTTY(true)
|
||||
io.SetStderrTTY(true)
|
||||
|
||||
httpFunc := func() (*http.Client, error) { return nil, nil }
|
||||
configFunc := func() (config.Config, error) { return nil, nil }
|
||||
|
||||
type input struct {
|
||||
cli string
|
||||
httpClient func() (*http.Client, error)
|
||||
io *iostreams.IOStreams
|
||||
config func() (config.Config, error)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input input
|
||||
wants ListOptions
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
input: input{
|
||||
cli: "",
|
||||
httpClient: httpFunc,
|
||||
io: io,
|
||||
config: configFunc,
|
||||
},
|
||||
wants: ListOptions{
|
||||
HTTPClient: httpFunc,
|
||||
Config: configFunc,
|
||||
IO: io,
|
||||
ListMsg: []string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := &cmdutil.Factory{
|
||||
HttpClient: tt.input.httpClient,
|
||||
Config: tt.input.config,
|
||||
IOStreams: tt.input.io,
|
||||
}
|
||||
|
||||
argv, err := shlex.Split(tt.input.cli)
|
||||
if err != nil {
|
||||
t.Errorf(`Split() = got %v`, err)
|
||||
}
|
||||
|
||||
var gotOpts *ListOptions
|
||||
cmd := NewCmdList(f, func(opts *ListOptions) error {
|
||||
gotOpts = opts
|
||||
return nil
|
||||
})
|
||||
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
if err != nil {
|
||||
t.Errorf(`ExecuteC() = got %v`, err)
|
||||
}
|
||||
|
||||
if reflect.ValueOf(tt.wants.HTTPClient).Pointer() != reflect.ValueOf(gotOpts.HTTPClient).Pointer() {
|
||||
t.Errorf(`HTTPClient has wrong values`)
|
||||
}
|
||||
|
||||
if reflect.ValueOf(tt.wants.Config).Pointer() != reflect.ValueOf(gotOpts.Config).Pointer() {
|
||||
t.Errorf(`Config has wrong values`)
|
||||
}
|
||||
|
||||
if reflect.ValueOf(tt.wants.IO).Pointer() != reflect.ValueOf(gotOpts.IO).Pointer() {
|
||||
t.Errorf(`IO has wrong values`)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wants.ListMsg, gotOpts.ListMsg) {
|
||||
t.Errorf(`ListMsg has wrong values: want %v, got %v`, tt.wants.ListMsg, gotOpts.ListMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRun(t *testing.T) {
|
||||
type input struct {
|
||||
httpStubs func(*httpmock.Registry)
|
||||
configError bool
|
||||
httpClientError bool
|
||||
hasOauthToken bool
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input input
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "name and corresponding ssh key",
|
||||
input: input{
|
||||
func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "user/keys"),
|
||||
httpmock.StringResponse(`[{"id":1234,"key":"ssh-rsa AAAABbBB123","title":"Mac"}]`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.ScopesResponder("repo,read:org,read:public_key"),
|
||||
)
|
||||
},
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
want: []string{"✹ Name: Mac \n SSH-KEY: ssh-rsa AAAABbBB123"},
|
||||
},
|
||||
{
|
||||
name: "config error",
|
||||
input: input{
|
||||
func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "user/keys"),
|
||||
httpmock.StringResponse(""),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.ScopesResponder("repo,read:org,read:public_key"),
|
||||
)
|
||||
},
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
want: []string{"X: Config error"},
|
||||
},
|
||||
{
|
||||
name: "http client error",
|
||||
input: input{
|
||||
func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "user/keys"),
|
||||
httpmock.StringResponse(""),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.ScopesResponder("repo,read:org,read:public_key"),
|
||||
)
|
||||
},
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
want: []string{"X: HttpClient error"},
|
||||
},
|
||||
{
|
||||
name: "not found on api.github.com/user/keys",
|
||||
input: input{
|
||||
func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "user/keys"),
|
||||
httpmock.StatusStringResponse(http.StatusNotFound, `{"message": "Not Found", "documentation_url": "url"}`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.ScopesResponder("repo,read:org,read:public_key"),
|
||||
)
|
||||
},
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
want: []string{"X: Got HTTP 404 (https://api.github.com/user/keys)"},
|
||||
},
|
||||
{
|
||||
name: "missing scope",
|
||||
input: input{
|
||||
func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "user/keys"),
|
||||
httpmock.StringResponse(`[{"id":1234,"key":"ssh-rsa AAAABbBB123","title":"Mac"}]`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.ScopesResponder(""),
|
||||
)
|
||||
},
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
want: []string{
|
||||
"X: missing required scope 'repo';missing required scope 'read:org';missing required scope 'read:public_key'",
|
||||
"- To request missing scopes, run: gh auth refresh -h github.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "authentication failed",
|
||||
input: input{
|
||||
func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "user/keys"),
|
||||
httpmock.StringResponse(`[{"id":1234,"key":"ssh-rsa AAAABbBB123","title":"Mac"}]`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.StatusStringResponse(http.StatusNotFound, `{"message": "Not Found", "documentation_url": "url"}`),
|
||||
)
|
||||
},
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
want: []string{
|
||||
"X: authentication failed",
|
||||
"- The github.com token in ~/.config/gh/hosts.yml is no longer valid.",
|
||||
"- To re-authenticate, run: gh auth login -h github.com",
|
||||
"- To forget about this host, run: gh auth logout -h github.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
tt.input.httpStubs(reg)
|
||||
|
||||
io, _, _, _ := iostreams.Test()
|
||||
io.SetStdoutTTY(true)
|
||||
io.SetStdinTTY(true)
|
||||
io.SetStderrTTY(true)
|
||||
|
||||
opts := ListOptions{
|
||||
HTTPClient: func() (*http.Client, error) {
|
||||
if tt.input.httpClientError {
|
||||
return nil, errors.New("HttpClient error")
|
||||
}
|
||||
return &http.Client{Transport: reg}, nil
|
||||
},
|
||||
IO: io,
|
||||
Config: func() (config.Config, error) {
|
||||
if tt.input.configError {
|
||||
return nil, errors.New("Config error")
|
||||
}
|
||||
cfg := config.NewBlankConfig()
|
||||
if tt.input.hasOauthToken {
|
||||
err := cfg.Set("github.com", "oauth_token", "abc123")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cfg, nil
|
||||
},
|
||||
}
|
||||
|
||||
err := listRun(&opts)
|
||||
if err != nil && !tt.input.wantErr {
|
||||
t.Errorf("linRun() return error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(opts.ListMsg, tt.want) {
|
||||
t.Errorf("linRun() = want %v, got %v", tt.want, opts.ListMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
20
pkg/cmd/ssh-key/ssh-key.go
Normal file
20
pkg/cmd/ssh-key/ssh-key.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package key
|
||||
|
||||
import (
|
||||
cmdList "github.com/cli/cli/pkg/cmd/ssh-key/list"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCmdSSHKey creates a command for manage SSH Keys
|
||||
func NewCmdSSHKey(f *cmdutil.Factory) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "ssh-key <command>",
|
||||
Short: "Manage SSH keys",
|
||||
Long: "Work with GitHub SSH keys",
|
||||
}
|
||||
|
||||
cmd.AddCommand(cmdList.NewCmdList(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue