Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Prompt confirm deletion with key title
  • Loading branch information
nsmag committed Oct 5, 2022
commit 9a996c130af2ea3416a6d06c4483f9b9046a4f2f
20 changes: 15 additions & 5 deletions pkg/cmd/ssh-key/delete/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,28 @@ func deleteRun(opts *DeleteOptions) error {
return err
}

host, _ := cfg.DefaultHost()
key, err := getSSHKey(httpClient, host, opts.KeyID)
if err != nil {
var httpErr api.HTTPError
if errors.As(err, &httpErr) && httpErr.StatusCode == 404 {
return fmt.Errorf("unable to delete SSH key id %s: either the SSH key is not found or it is not owned by you", opts.KeyID)
}

return err
}

if !opts.Confirmed {
confirmed, err := opts.Prompter.Confirm("Confirm deletion:", true)
confirmTitle, err := opts.Prompter.Input(fmt.Sprintf("Type \"%s\" to confirm deletion:", key.Title), "")

if err != nil {
return err
}
if !confirmed {
if confirmTitle != key.Title {
return cmdutil.CancelError
}
}

host, _ := cfg.DefaultHost()

err = deleteSSHKey(httpClient, host, opts.KeyID)
if err != nil {
var httpErr api.HTTPError
Expand All @@ -88,7 +98,7 @@ func deleteRun(opts *DeleteOptions) error {

if opts.IO.IsStdoutTTY() {
cs := opts.IO.ColorScheme()
fmt.Fprintf(opts.IO.Out, "%s Deleted SSH key id %s from your account\n", cs.SuccessIcon(), opts.KeyID)
fmt.Fprintf(opts.IO.Out, "%s SSH key \"%s\" (%s) deleted from your account\n", cs.SuccessIcon(), key.Title, opts.KeyID)
}
return nil
}
34 changes: 18 additions & 16 deletions pkg/cmd/ssh-key/delete/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func TestNewCmdDelete(t *testing.T) {
}

func Test_deleteRun(t *testing.T) {
keyResp := "{\"title\":\"My Key\"}"
tests := []struct {
name string
tty bool
Expand All @@ -121,26 +122,30 @@ func Test_deleteRun(t *testing.T) {
tty: true,
opts: DeleteOptions{KeyID: "123"},
prompterStubs: func(pm *prompter.PrompterMock) {
pm.ConfirmFunc = func(s string, b bool) (bool, error) {
return true, nil
pm.InputFunc = func(s1 string, s2 string) (string, error) {
return "My Key", nil
}
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "user/keys/123"), httpmock.StatusStringResponse(204, "{}"))
reg.Register(httpmock.REST("GET", "user/keys/123"), httpmock.StatusStringResponse(200, keyResp))
reg.Register(httpmock.REST("DELETE", "user/keys/123"), httpmock.StatusStringResponse(204, ""))
},
wantErr: false,
wantStdout: "✓ Deleted SSH key id 123 from your account\n",
wantStdout: "✓ SSH key \"My Key\" (123) deleted from your account\n",
wantErrMsg: "",
},
{
name: "cancel delete tty",
tty: true,
opts: DeleteOptions{KeyID: "123"},
prompterStubs: func(pm *prompter.PrompterMock) {
pm.ConfirmFunc = func(s string, b bool) (bool, error) {
return false, nil
pm.InputFunc = func(s1 string, s2 string) (string, error) {
return "", nil
}
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("GET", "user/keys/123"), httpmock.StatusStringResponse(200, keyResp))
},
wantErr: true,
wantStdout: "",
wantErrMsg: "CancelError",
Expand All @@ -150,23 +155,19 @@ func Test_deleteRun(t *testing.T) {
tty: true,
opts: DeleteOptions{KeyID: "123", Confirmed: true},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "user/keys/123"), httpmock.StatusStringResponse(204, "{}"))
reg.Register(httpmock.REST("GET", "user/keys/123"), httpmock.StatusStringResponse(200, keyResp))
reg.Register(httpmock.REST("DELETE", "user/keys/123"), httpmock.StatusStringResponse(204, ""))
},
wantErr: false,
wantStdout: "✓ Deleted SSH key id 123 from your account\n",
wantStdout: "✓ SSH key \"My Key\" (123) deleted from your account\n",
wantErrMsg: "",
},
{
name: "not found tty",
tty: true,
opts: DeleteOptions{KeyID: "123"},
prompterStubs: func(pm *prompter.PrompterMock) {
pm.ConfirmFunc = func(s string, b bool) (bool, error) {
return true, nil
}
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "user/keys/123"), httpmock.StatusStringResponse(404, "{}"))
reg.Register(httpmock.REST("GET", "user/keys/123"), httpmock.StatusStringResponse(404, ""))
},
wantErr: true,
wantStdout: "",
Expand All @@ -176,7 +177,8 @@ func Test_deleteRun(t *testing.T) {
name: "delete no tty",
opts: DeleteOptions{KeyID: "123", Confirmed: true},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "user/keys/123"), httpmock.StatusStringResponse(204, "{}"))
reg.Register(httpmock.REST("GET", "user/keys/123"), httpmock.StatusStringResponse(200, keyResp))
reg.Register(httpmock.REST("DELETE", "user/keys/123"), httpmock.StatusStringResponse(204, ""))
},
wantErr: false,
wantStdout: "",
Expand All @@ -186,7 +188,7 @@ func Test_deleteRun(t *testing.T) {
name: "not found no tty",
opts: DeleteOptions{KeyID: "123", Confirmed: true},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "user/keys/123"), httpmock.StatusStringResponse(404, "{}"))
reg.Register(httpmock.REST("GET", "user/keys/123"), httpmock.StatusStringResponse(404, ""))
},
wantErr: true,
wantStdout: "",
Expand Down
37 changes: 37 additions & 0 deletions pkg/cmd/ssh-key/delete/http.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package delete

import (
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghinstance"
)

type sshKey struct {
Title string
}

func deleteSSHKey(httpClient *http.Client, host string, keyID string) error {
url := fmt.Sprintf("%suser/keys/%s", ghinstance.RESTPrefix(host), keyID)
req, err := http.NewRequest("DELETE", url, nil)
Expand All @@ -27,3 +33,34 @@ func deleteSSHKey(httpClient *http.Client, host string, keyID string) error {

return nil
}

func getSSHKey(httpClient *http.Client, host string, keyID string) (*sshKey, error) {
url := fmt.Sprintf("%suser/keys/%s", ghinstance.RESTPrefix(host), keyID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}

resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode > 299 {
return nil, api.HandleHTTPError(resp)
}

b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var key sshKey
err = json.Unmarshal(b, &key)
if err != nil {
return nil, err
}

return &key, nil
}