check SSH key existence before uploading
This commit is contained in:
parent
82662685e3
commit
f21f1ca4ef
7 changed files with 130 additions and 37 deletions
|
|
@ -200,7 +200,7 @@ func Login(opts *LoginOptions) error {
|
|||
}
|
||||
|
||||
if keyToUpload != "" {
|
||||
err := sshKeyUpload(httpClient, hostname, keyToUpload, keyTitle)
|
||||
err := sshKeyUpload(httpClient, hostname, keyToUpload, keyTitle, opts.IO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -223,14 +223,14 @@ func scopesSentence(scopes []string, isEnterprise bool) string {
|
|||
return strings.Join(quoted, ", ")
|
||||
}
|
||||
|
||||
func sshKeyUpload(httpClient *http.Client, hostname, keyFile string, title string) error {
|
||||
func sshKeyUpload(httpClient *http.Client, hostname, keyFile string, title string, ios *iostreams.IOStreams) error {
|
||||
f, err := os.Open(keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return add.SSHKeyUpload(httpClient, hostname, f, title)
|
||||
return add.SSHKeyUpload(httpClient, hostname, f, title, false, ios)
|
||||
}
|
||||
|
||||
func getCurrentLogin(httpClient httpClient, hostname, authToken string) (string, error) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ func TestLogin_ssh(t *testing.T) {
|
|||
tr.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{ "login": "monalisa" }}}`))
|
||||
tr.Register(
|
||||
httpmock.REST("GET", "api/v3/user/keys"),
|
||||
httpmock.StringResponse(`[]`))
|
||||
tr.Register(
|
||||
httpmock.REST("POST", "api/v3/user/keys"),
|
||||
httpmock.StringResponse(`{}`))
|
||||
|
|
@ -78,7 +81,7 @@ func TestLogin_ssh(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, expected, args)
|
||||
// simulate that the public key file has been generated
|
||||
_ = os.WriteFile(keyFile+".pub", []byte("PUBKEY"), 0600)
|
||||
_ = os.WriteFile(keyFile+".pub", []byte("PUBKEY asdf"), 0600)
|
||||
})
|
||||
|
||||
cfg := tinyConfig{}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package add
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -79,14 +78,11 @@ func runAdd(opts *AddOptions) error {
|
|||
|
||||
hostname, _ := cfg.Authentication().DefaultHost()
|
||||
|
||||
err = SSHKeyUpload(httpClient, hostname, keyReader, opts.Title)
|
||||
err = SSHKeyUpload(httpClient, hostname, keyReader, opts.Title, true, opts.IO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
cs := opts.IO.ColorScheme()
|
||||
fmt.Fprintf(opts.IO.ErrOut, "%s Public key added to your account\n", cs.SuccessIcon())
|
||||
}
|
||||
// SSHKeyUpload prints the success message.
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,33 +11,94 @@ import (
|
|||
)
|
||||
|
||||
func Test_runAdd(t *testing.T) {
|
||||
ios, stdin, stdout, stderr := iostreams.Test()
|
||||
ios.SetStdinTTY(false)
|
||||
ios.SetStdoutTTY(true)
|
||||
ios.SetStderrTTY(true)
|
||||
tests := []struct {
|
||||
name string
|
||||
stdin string
|
||||
opts AddOptions
|
||||
httpStubs func(*httpmock.Registry)
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid key format, not already in use",
|
||||
stdin: "ssh-ed25519 asdf",
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "user/keys"),
|
||||
httpmock.StringResponse("[]"))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "user/keys"),
|
||||
httpmock.RESTPayload(200, ``, func(payload map[string]interface{}) {
|
||||
assert.Contains(t, payload, "key")
|
||||
assert.Empty(t, payload["title"])
|
||||
}))
|
||||
},
|
||||
wantStdout: "",
|
||||
wantStderr: "✓ Public key added to your account\n",
|
||||
wantErrMsg: "",
|
||||
opts: AddOptions{KeyFile: "-"},
|
||||
},
|
||||
{
|
||||
name: "valid key format, already in use",
|
||||
stdin: "ssh-ed25519 asdf title",
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "user/keys"),
|
||||
httpmock.StringResponse(`[
|
||||
{
|
||||
"id": 1,
|
||||
"key": "ssh-ed25519 asdf",
|
||||
"title": "anything"
|
||||
}
|
||||
]`))
|
||||
},
|
||||
wantStdout: "",
|
||||
wantStderr: "✓ Public key already exists on your account\n",
|
||||
wantErrMsg: "",
|
||||
opts: AddOptions{KeyFile: "-"},
|
||||
},
|
||||
{
|
||||
name: "invalid key format",
|
||||
stdin: "ssh-ed25519",
|
||||
wantStdout: "",
|
||||
wantStderr: "X Error: provided key is not in a valid format\n",
|
||||
wantErrMsg: "SilentError",
|
||||
opts: AddOptions{KeyFile: "-"},
|
||||
},
|
||||
}
|
||||
|
||||
stdin.WriteString("PUBKEY")
|
||||
for _, tt := range tests {
|
||||
ios, stdin, stdout, stderr := iostreams.Test()
|
||||
ios.SetStdinTTY(false)
|
||||
ios.SetStdoutTTY(true)
|
||||
ios.SetStderrTTY(true)
|
||||
|
||||
tr := httpmock.Registry{}
|
||||
defer tr.Verify(t)
|
||||
stdin.WriteString(tt.stdin)
|
||||
|
||||
tr.Register(
|
||||
httpmock.REST("POST", "user/keys"),
|
||||
httpmock.StringResponse(`{}`))
|
||||
reg := &httpmock.Registry{}
|
||||
|
||||
err := runAdd(&AddOptions{
|
||||
IO: ios,
|
||||
Config: func() (config.Config, error) {
|
||||
tt.opts.IO = ios
|
||||
tt.opts.HTTPClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
}
|
||||
tt.opts.Config = func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
},
|
||||
HTTPClient: func() (*http.Client, error) {
|
||||
return &http.Client{Transport: &tr}, nil
|
||||
},
|
||||
KeyFile: "-",
|
||||
Title: "my sacred key",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "", stdout.String())
|
||||
assert.Equal(t, "✓ Public key added to your account\n", stderr.String())
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer reg.Verify(t)
|
||||
err := runAdd(&tt.opts)
|
||||
if tt.wantErrMsg != "" {
|
||||
assert.Equal(t, tt.wantErrMsg, err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||
assert.Equal(t, tt.wantStderr, stderr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,21 +3,50 @@ package add
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/pkg/cmd/ssh-key/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
)
|
||||
|
||||
func SSHKeyUpload(httpClient *http.Client, hostname string, keyFile io.Reader, title string) error {
|
||||
func SSHKeyUpload(httpClient *http.Client, hostname string, keyFile io.Reader, title string, printSuccessMsgs bool, ios *iostreams.IOStreams) error {
|
||||
url := ghinstance.RESTPrefix(hostname) + "user/keys"
|
||||
cs := ios.ColorScheme()
|
||||
|
||||
keyBytes, err := io.ReadAll(keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userKey := string(keyBytes)
|
||||
splitKey := strings.Fields(userKey)
|
||||
if len(splitKey) < 2 {
|
||||
fmt.Fprintf(ios.ErrOut, "%s Error: provided key is not in a valid format\n", cs.FailureIcon())
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
userKey = splitKey[0] + " " + splitKey[1]
|
||||
|
||||
keys, err := shared.UserKeys(httpClient, hostname, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
if k.Key == userKey {
|
||||
if printSuccessMsgs && ios.IsStdoutTTY() {
|
||||
fmt.Fprintf(ios.ErrOut, "%s Public key already exists on your account\n", cs.SuccessIcon())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
payload := map[string]string{
|
||||
"title": title,
|
||||
"key": string(keyBytes),
|
||||
|
|
@ -48,5 +77,8 @@ func SSHKeyUpload(httpClient *http.Client, hostname string, keyFile io.Reader, t
|
|||
return err
|
||||
}
|
||||
|
||||
if printSuccessMsgs {
|
||||
fmt.Fprintf(ios.ErrOut, "%s Public key added to your account\n", cs.SuccessIcon())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/ssh-key/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
|
|
@ -55,7 +56,7 @@ func listRun(opts *ListOptions) error {
|
|||
|
||||
host, _ := cfg.Authentication().DefaultHost()
|
||||
|
||||
sshKeys, err := userKeys(apiClient, host, "")
|
||||
sshKeys, err := shared.UserKeys(apiClient, host, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package list
|
||||
package shared
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
|
@ -18,7 +18,7 @@ type sshKey struct {
|
|||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func userKeys(httpClient *http.Client, host, userHandle string) ([]sshKey, error) {
|
||||
func UserKeys(httpClient *http.Client, host, userHandle string) ([]sshKey, error) {
|
||||
resource := "user/keys"
|
||||
if userHandle != "" {
|
||||
resource = fmt.Sprintf("users/%s/keys", userHandle)
|
||||
Loading…
Add table
Add a link
Reference in a new issue