Skip to content

Commit

Permalink
v0.0.35: improved error handling, nss bugfix, & WSL detection
Browse files Browse the repository at this point in the history
  • Loading branch information
benburkert committed May 14, 2024
1 parent 42c4cbc commit 96f08cc
Show file tree
Hide file tree
Showing 41 changed files with 671 additions and 410 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,18 @@ brew upgrade anchordotdev/tap/anchor

### Windows

Available via [Chocolatey][] or as a downloadable binary from the [releases page][].
Available via [Winget][] or as a downloadable binary from the [releases page][].

### Chocolatey
### Winget

Install:
```
chocolatey install anchor --version=0.0.22
winget install anchor
```

Upgrade:
```
winget upgrade anchor
```

### Install from source
Expand All @@ -62,7 +67,7 @@ Install:
go install github.com/anchordotdev/cli/cmd/anchor@latest
```

[Chocolatey]: https://community.chocolatey.org/
[Winget]: https://learn.microsoft.com/en-us/windows/package-manager/winget/
[Homebrew]: https://brew.sh
[releases page]: https://github.com/anchordotdev/cli/releases/latest

Expand Down
23 changes: 16 additions & 7 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

var (
ErrSignedOut = errors.New("sign in required")
ErrTransient = errors.New("transient error encountered, please retry")
ErrGnomeKeyringRequired = fmt.Errorf("gnome-keyring required for secure credential storage: %w", ErrSignedOut)
)

Expand Down Expand Up @@ -123,30 +124,34 @@ func (s *Session) CreatePATToken(ctx context.Context, deviceCode string) (string
return "", err
}

requestId := res.Header.Get("X-Request-Id")

switch res.StatusCode {
case http.StatusOK:
var patTokens *AuthCliPatTokensResponse
if err = json.NewDecoder(res.Body).Decode(&patTokens); err != nil {
return "", err
}
return patTokens.PatToken, nil
case http.StatusServiceUnavailable:
return "", ErrTransient
case http.StatusBadRequest:
var errorsRes *Error
if err = json.NewDecoder(res.Body).Decode(&errorsRes); err != nil {
return "", err
}
switch errorsRes.Type {
case "urn:anchordev:api:cli-auth:authorization-pending":
return "", nil
return "", ErrTransient
case "urn:anchordev:api:cli-auth:expired-device-code":
return "", fmt.Errorf("Your authorization request has expired, please try again.")
case "urn:anchordev:api:cli-auth:incorrect-device-code":
return "", fmt.Errorf("Your authorization request was not found, please try again.")
default:
return "", fmt.Errorf("unexpected error: %s", errorsRes.Detail)
return "", fmt.Errorf("request [%s]: unexpected error: %s", requestId, errorsRes.Detail)
}
default:
return "", fmt.Errorf("unexpected response code: %d", res.StatusCode)
return "", fmt.Errorf("request [%s]: unexpected response code: %d", requestId, res.StatusCode)
}
}

Expand Down Expand Up @@ -262,7 +267,8 @@ func (s *Session) get(ctx context.Context, path string, out any) error {
if err = json.NewDecoder(res.Body).Decode(&errorsRes); err != nil {
return err
}
return fmt.Errorf("%w: %s", StatusCodeError(res.StatusCode), errorsRes.Title)
requestId := res.Header.Get("X-Request-Id")
return fmt.Errorf("request [%s]: %w: %s", requestId, StatusCodeError(res.StatusCode), errorsRes.Title)
}
return json.NewDecoder(res.Body).Decode(out)
}
Expand Down Expand Up @@ -292,7 +298,8 @@ func (s *Session) post(ctx context.Context, path string, in, out any) error {
if err = json.NewDecoder(res.Body).Decode(&errorsRes); err != nil {
return err
}
return fmt.Errorf("%w: %s", StatusCodeError(res.StatusCode), errorsRes.Title)
requestId := res.Header.Get("X-Request-Id")
return fmt.Errorf("request [%s]: %w: %s", requestId, StatusCodeError(res.StatusCode), errorsRes.Title)
}
return json.NewDecoder(res.Body).Decode(out)
}
Expand Down Expand Up @@ -326,14 +333,16 @@ func (r responseChecker) RoundTrip(req *http.Request) (*http.Response, error) {
return nil, fmt.Errorf("request error %s %s: %w", req.Method, req.URL.Path, err)
}

requestId := res.Header.Get("X-Request-Id")

switch res.StatusCode {
case http.StatusForbidden:
return nil, ErrSignedOut
case http.StatusInternalServerError:
return nil, fmt.Errorf("request failed: %w", err)
return nil, fmt.Errorf("request [%s] failed: %w", requestId, err)
}
if contentType := res.Header.Get("Content-Type"); !jsonMediaTypes.Matches(contentType) {
return nil, fmt.Errorf("non-json response received: %q: %w", contentType, err)
return nil, fmt.Errorf("request [%s]: %d response, expected json content-type, got: %q", requestId, res.StatusCode, contentType)
}
return res, nil
}
Expand Down
2 changes: 1 addition & 1 deletion auth/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (c Client) Perform(ctx context.Context, drv *ui.Driver) (*api.Session, erro

if errors.Is(newClientErr, api.ErrSignedOut) || errors.Is(userInfoErr, api.ErrSignedOut) {
if c.Hint == nil {
c.Hint = &models.SignInHint{}
c.Hint = models.SignInHint
}
cmd := &SignIn{
Hint: c.Hint,
Expand Down
36 changes: 14 additions & 22 deletions auth/models/signin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,21 @@ import (
tea "github.com/charmbracelet/bubbletea"
)

type SignInHeader struct{}

func (SignInHeader) Init() tea.Cmd { return nil }

func (m *SignInHeader) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil }

func (m *SignInHeader) View() string {
var b strings.Builder
fmt.Fprintln(&b, ui.Header(fmt.Sprintf("Signin to Anchor.dev %s", ui.Whisper("`anchor auth signin`"))))
return b.String()
}

type SignInHint struct{}

func (SignInHint) Init() tea.Cmd { return nil }

func (m SignInHint) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil }
var (
SignInHeader = ui.Section{
Name: "SignInHeader",
Model: ui.MessageLines{
ui.Header(fmt.Sprintf("Signin to Anchor.dev %s", ui.Whisper("`anchor auth signin`"))),
},
}

func (m SignInHint) View() string {
var b strings.Builder
fmt.Fprintln(&b, ui.StepHint("Please sign up or sign in with your Anchor account."))
return b.String()
}
SignInHint = ui.Section{
Name: "SignInHint",
Model: ui.MessageLines{
ui.StepHint("Please sign up or sign in with your Anchor account."),
},
}
)

type SignInPrompt struct {
ConfirmCh chan<- struct{}
Expand Down
40 changes: 15 additions & 25 deletions auth/models/signout.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,22 @@ package models

import (
"fmt"
"strings"

"github.com/anchordotdev/cli/ui"
tea "github.com/charmbracelet/bubbletea"
)

type SignOutHeader struct{}

func (SignOutHeader) Init() tea.Cmd { return nil }

func (m *SignOutHeader) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil }

func (m *SignOutHeader) View() string {
var b strings.Builder
fmt.Fprintln(&b, ui.Header(fmt.Sprintf("Signout from Anchor.dev %s", ui.Whisper("`anchor auth signout`"))))
return b.String()
}

type SignOutSuccess struct{}

func (SignOutSuccess) Init() tea.Cmd { return nil }

func (m *SignOutSuccess) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil }

func (m *SignOutSuccess) View() string {
var b strings.Builder
fmt.Fprintln(&b, ui.StepDone("Signed out."))
return b.String()
}
var (
SignOutHeader = ui.Section{
Name: "SignOutHeader",
Model: ui.MessageLines{
ui.Header(fmt.Sprintf("Signout from Anchor.dev %s", ui.Whisper("`anchor auth signout`"))),
},
}

SignOutSuccess = ui.Section{
Name: "SignOutSuccess",
Model: ui.MessageLines{
ui.StepDone("Signed out."),
},
}
)
15 changes: 5 additions & 10 deletions auth/models/whoami.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,11 @@ import (
tea "github.com/charmbracelet/bubbletea"
)

type WhoAmIHeader struct{}

func (m *WhoAmIHeader) Init() tea.Cmd { return nil }

func (m *WhoAmIHeader) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil }

func (m *WhoAmIHeader) View() string {
var b strings.Builder
fmt.Fprintln(&b, ui.Header(fmt.Sprintf("Identify Current Anchor.dev Account %s", ui.Whisper("`anchor auth whoami`"))))
return b.String()
var WhoAmIHeader = ui.Section{
Name: "WhoAmIHeader",
Model: ui.MessageLines{
ui.Header(fmt.Sprintf("Identify Current Anchor.dev Account %s", ui.Whisper("`anchor auth whoami`"))),
},
}

type WhoAmIChecker struct {
Expand Down
6 changes: 3 additions & 3 deletions auth/signin.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ func (s SignIn) UI() cli.UI {
func (s *SignIn) RunTUI(ctx context.Context, drv *ui.Driver) error {
cfg := cli.ConfigFromContext(ctx)

drv.Activate(ctx, &models.SignInHeader{})
drv.Activate(ctx, models.SignInHeader)

if s.Hint == nil {
s.Hint = &models.SignInHint{}
s.Hint = models.SignInHint
}
drv.Activate(ctx, s.Hint)

Expand Down Expand Up @@ -83,7 +83,7 @@ func (s *SignIn) RunTUI(ctx context.Context, drv *ui.Driver) error {

var patToken string
for patToken == "" {
if patToken, err = anc.CreatePATToken(ctx, codes.DeviceCode); err != nil {
if patToken, err = anc.CreatePATToken(ctx, codes.DeviceCode); err != nil && err != api.ErrTransient {
return err
}

Expand Down
4 changes: 2 additions & 2 deletions auth/signout.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ func (s SignOut) UI() cli.UI {
func (s *SignOut) runTUI(ctx context.Context, drv *ui.Driver) error {
cfg := cli.ConfigFromContext(ctx)

drv.Activate(ctx, &models.SignOutHeader{})
drv.Activate(ctx, models.SignOutHeader)

kr := keyring.Keyring{Config: cfg}
err := kr.Delete(keyring.APIToken)

if err == nil {
drv.Activate(ctx, &models.SignOutSuccess{})
drv.Activate(ctx, models.SignOutSuccess)
}

return err
Expand Down
2 changes: 1 addition & 1 deletion auth/whoami.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (c WhoAmI) UI() cli.UI {
func (c *WhoAmI) runTUI(ctx context.Context, drv *ui.Driver) error {
cfg := cli.ConfigFromContext(ctx)

drv.Activate(ctx, &models.WhoAmIHeader{})
drv.Activate(ctx, models.WhoAmIHeader)
drv.Activate(ctx, &models.WhoAmIChecker{})

anc, err := api.NewClient(cfg)
Expand Down
27 changes: 26 additions & 1 deletion cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"context"
"fmt"
"go/build"
"io/fs"
"net/url"
"os"
"regexp"
"runtime"
"strings"
"time"

"github.com/anchordotdev/cli/models"
"github.com/anchordotdev/cli/ui"
Expand All @@ -17,6 +19,8 @@ import (
"github.com/spf13/pflag"
)

var Executable string

var Version = struct {
Version, Commit, Date string

Expand Down Expand Up @@ -120,6 +124,12 @@ type Config struct {
}

Version struct{} `cmd:"version"`

// values used for testing

GOOS string `desc:"change OS identifier in tests"`
ProcFS fs.FS `desc:"change the proc filesystem in tests"`
Timestamp time.Time `desc:"timestamp to use/display in tests"`
}

type UI struct {
Expand Down Expand Up @@ -205,10 +215,25 @@ func ReportError(ctx context.Context, drv *ui.Driver, cmd *cobra.Command, args [
fmt.Fprintf(&body, "\n")
fmt.Fprintf(&body, "---\n")
fmt.Fprintf(&body, "\n")
fmt.Fprintf(&body, "**Command:** `%s`\n", cmd.CalledAs())
fmt.Fprintf(&body, "**Command:** `%s`\n", cmd.CommandPath())
var executable string
if Executable != "" {
executable = Executable
} else {
executable, _ = os.Executable()
}
if executable != "" {
fmt.Fprintf(&body, "**Executable:** `%s`\n", executable)
}
fmt.Fprintf(&body, "**Version:** `%s`\n", VersionString())
fmt.Fprintf(&body, "**Arguments:** `[%s]`\n", strings.Join(args, ", "))
fmt.Fprintf(&body, "**Flags:** `[%s]`\n", strings.Join(flags, ", "))

timestamp := cfg.Timestamp
if timestamp.IsZero() {
timestamp = time.Now().UTC()
}
fmt.Fprintf(&body, "**Timestamp:** `%s`\n", timestamp.Format(time.RFC3339Nano))
if stack != "" {
fmt.Fprintf(&body, "**Stack:**\n```\n%s\n```\n", normalizeStack(stack))
}
Expand Down

0 comments on commit 96f08cc

Please sign in to comment.