check SSH key existence before uploading

This commit is contained in:
vaindil 2023-03-29 17:23:22 -04:00
parent 82662685e3
commit f21f1ca4ef
7 changed files with 130 additions and 37 deletions

View file

@ -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) {

View file

@ -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{}

View file

@ -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
}

View file

@ -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())
})
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)