Skip to content

Commit 96f08cc

Browse files
committed
v0.0.35: improved error handling, nss bugfix, & WSL detection
1 parent 42c4cbc commit 96f08cc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+671
-410
lines changed

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,18 @@ brew upgrade anchordotdev/tap/anchor
4646

4747
### Windows
4848

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

51-
### Chocolatey
51+
### Winget
5252

5353
Install:
5454
```
55-
chocolatey install anchor --version=0.0.22
55+
winget install anchor
56+
```
57+
58+
Upgrade:
59+
```
60+
winget upgrade anchor
5661
```
5762

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

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

api/api.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

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

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

127+
requestId := res.Header.Get("X-Request-Id")
128+
126129
switch res.StatusCode {
127130
case http.StatusOK:
128131
var patTokens *AuthCliPatTokensResponse
129132
if err = json.NewDecoder(res.Body).Decode(&patTokens); err != nil {
130133
return "", err
131134
}
132135
return patTokens.PatToken, nil
136+
case http.StatusServiceUnavailable:
137+
return "", ErrTransient
133138
case http.StatusBadRequest:
134139
var errorsRes *Error
135140
if err = json.NewDecoder(res.Body).Decode(&errorsRes); err != nil {
136141
return "", err
137142
}
138143
switch errorsRes.Type {
139144
case "urn:anchordev:api:cli-auth:authorization-pending":
140-
return "", nil
145+
return "", ErrTransient
141146
case "urn:anchordev:api:cli-auth:expired-device-code":
142147
return "", fmt.Errorf("Your authorization request has expired, please try again.")
143148
case "urn:anchordev:api:cli-auth:incorrect-device-code":
144149
return "", fmt.Errorf("Your authorization request was not found, please try again.")
145150
default:
146-
return "", fmt.Errorf("unexpected error: %s", errorsRes.Detail)
151+
return "", fmt.Errorf("request [%s]: unexpected error: %s", requestId, errorsRes.Detail)
147152
}
148153
default:
149-
return "", fmt.Errorf("unexpected response code: %d", res.StatusCode)
154+
return "", fmt.Errorf("request [%s]: unexpected response code: %d", requestId, res.StatusCode)
150155
}
151156
}
152157

@@ -262,7 +267,8 @@ func (s *Session) get(ctx context.Context, path string, out any) error {
262267
if err = json.NewDecoder(res.Body).Decode(&errorsRes); err != nil {
263268
return err
264269
}
265-
return fmt.Errorf("%w: %s", StatusCodeError(res.StatusCode), errorsRes.Title)
270+
requestId := res.Header.Get("X-Request-Id")
271+
return fmt.Errorf("request [%s]: %w: %s", requestId, StatusCodeError(res.StatusCode), errorsRes.Title)
266272
}
267273
return json.NewDecoder(res.Body).Decode(out)
268274
}
@@ -292,7 +298,8 @@ func (s *Session) post(ctx context.Context, path string, in, out any) error {
292298
if err = json.NewDecoder(res.Body).Decode(&errorsRes); err != nil {
293299
return err
294300
}
295-
return fmt.Errorf("%w: %s", StatusCodeError(res.StatusCode), errorsRes.Title)
301+
requestId := res.Header.Get("X-Request-Id")
302+
return fmt.Errorf("request [%s]: %w: %s", requestId, StatusCodeError(res.StatusCode), errorsRes.Title)
296303
}
297304
return json.NewDecoder(res.Body).Decode(out)
298305
}
@@ -326,14 +333,16 @@ func (r responseChecker) RoundTrip(req *http.Request) (*http.Response, error) {
326333
return nil, fmt.Errorf("request error %s %s: %w", req.Method, req.URL.Path, err)
327334
}
328335

336+
requestId := res.Header.Get("X-Request-Id")
337+
329338
switch res.StatusCode {
330339
case http.StatusForbidden:
331340
return nil, ErrSignedOut
332341
case http.StatusInternalServerError:
333-
return nil, fmt.Errorf("request failed: %w", err)
342+
return nil, fmt.Errorf("request [%s] failed: %w", requestId, err)
334343
}
335344
if contentType := res.Header.Get("Content-Type"); !jsonMediaTypes.Matches(contentType) {
336-
return nil, fmt.Errorf("non-json response received: %q: %w", contentType, err)
345+
return nil, fmt.Errorf("request [%s]: %d response, expected json content-type, got: %q", requestId, res.StatusCode, contentType)
337346
}
338347
return res, nil
339348
}

auth/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (c Client) Perform(ctx context.Context, drv *ui.Driver) (*api.Session, erro
4444

4545
if errors.Is(newClientErr, api.ErrSignedOut) || errors.Is(userInfoErr, api.ErrSignedOut) {
4646
if c.Hint == nil {
47-
c.Hint = &models.SignInHint{}
47+
c.Hint = models.SignInHint
4848
}
4949
cmd := &SignIn{
5050
Hint: c.Hint,

auth/models/signin.go

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,21 @@ import (
99
tea "github.com/charmbracelet/bubbletea"
1010
)
1111

12-
type SignInHeader struct{}
13-
14-
func (SignInHeader) Init() tea.Cmd { return nil }
15-
16-
func (m *SignInHeader) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil }
17-
18-
func (m *SignInHeader) View() string {
19-
var b strings.Builder
20-
fmt.Fprintln(&b, ui.Header(fmt.Sprintf("Signin to Anchor.dev %s", ui.Whisper("`anchor auth signin`"))))
21-
return b.String()
22-
}
23-
24-
type SignInHint struct{}
25-
26-
func (SignInHint) Init() tea.Cmd { return nil }
27-
28-
func (m SignInHint) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil }
12+
var (
13+
SignInHeader = ui.Section{
14+
Name: "SignInHeader",
15+
Model: ui.MessageLines{
16+
ui.Header(fmt.Sprintf("Signin to Anchor.dev %s", ui.Whisper("`anchor auth signin`"))),
17+
},
18+
}
2919

30-
func (m SignInHint) View() string {
31-
var b strings.Builder
32-
fmt.Fprintln(&b, ui.StepHint("Please sign up or sign in with your Anchor account."))
33-
return b.String()
34-
}
20+
SignInHint = ui.Section{
21+
Name: "SignInHint",
22+
Model: ui.MessageLines{
23+
ui.StepHint("Please sign up or sign in with your Anchor account."),
24+
},
25+
}
26+
)
3527

3628
type SignInPrompt struct {
3729
ConfirmCh chan<- struct{}

auth/models/signout.go

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,22 @@ package models
22

33
import (
44
"fmt"
5-
"strings"
65

76
"github.com/anchordotdev/cli/ui"
8-
tea "github.com/charmbracelet/bubbletea"
97
)
108

11-
type SignOutHeader struct{}
12-
13-
func (SignOutHeader) Init() tea.Cmd { return nil }
14-
15-
func (m *SignOutHeader) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil }
16-
17-
func (m *SignOutHeader) View() string {
18-
var b strings.Builder
19-
fmt.Fprintln(&b, ui.Header(fmt.Sprintf("Signout from Anchor.dev %s", ui.Whisper("`anchor auth signout`"))))
20-
return b.String()
21-
}
22-
23-
type SignOutSuccess struct{}
24-
25-
func (SignOutSuccess) Init() tea.Cmd { return nil }
26-
27-
func (m *SignOutSuccess) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil }
28-
29-
func (m *SignOutSuccess) View() string {
30-
var b strings.Builder
31-
fmt.Fprintln(&b, ui.StepDone("Signed out."))
32-
return b.String()
33-
}
9+
var (
10+
SignOutHeader = ui.Section{
11+
Name: "SignOutHeader",
12+
Model: ui.MessageLines{
13+
ui.Header(fmt.Sprintf("Signout from Anchor.dev %s", ui.Whisper("`anchor auth signout`"))),
14+
},
15+
}
16+
17+
SignOutSuccess = ui.Section{
18+
Name: "SignOutSuccess",
19+
Model: ui.MessageLines{
20+
ui.StepDone("Signed out."),
21+
},
22+
}
23+
)

auth/models/whoami.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,11 @@ import (
99
tea "github.com/charmbracelet/bubbletea"
1010
)
1111

12-
type WhoAmIHeader struct{}
13-
14-
func (m *WhoAmIHeader) Init() tea.Cmd { return nil }
15-
16-
func (m *WhoAmIHeader) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil }
17-
18-
func (m *WhoAmIHeader) View() string {
19-
var b strings.Builder
20-
fmt.Fprintln(&b, ui.Header(fmt.Sprintf("Identify Current Anchor.dev Account %s", ui.Whisper("`anchor auth whoami`"))))
21-
return b.String()
12+
var WhoAmIHeader = ui.Section{
13+
Name: "WhoAmIHeader",
14+
Model: ui.MessageLines{
15+
ui.Header(fmt.Sprintf("Identify Current Anchor.dev Account %s", ui.Whisper("`anchor auth whoami`"))),
16+
},
2217
}
2318

2419
type WhoAmIChecker struct {

auth/signin.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ func (s SignIn) UI() cli.UI {
3939
func (s *SignIn) RunTUI(ctx context.Context, drv *ui.Driver) error {
4040
cfg := cli.ConfigFromContext(ctx)
4141

42-
drv.Activate(ctx, &models.SignInHeader{})
42+
drv.Activate(ctx, models.SignInHeader)
4343

4444
if s.Hint == nil {
45-
s.Hint = &models.SignInHint{}
45+
s.Hint = models.SignInHint
4646
}
4747
drv.Activate(ctx, s.Hint)
4848

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

8484
var patToken string
8585
for patToken == "" {
86-
if patToken, err = anc.CreatePATToken(ctx, codes.DeviceCode); err != nil {
86+
if patToken, err = anc.CreatePATToken(ctx, codes.DeviceCode); err != nil && err != api.ErrTransient {
8787
return err
8888
}
8989

auth/signout.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ func (s SignOut) UI() cli.UI {
2323
func (s *SignOut) runTUI(ctx context.Context, drv *ui.Driver) error {
2424
cfg := cli.ConfigFromContext(ctx)
2525

26-
drv.Activate(ctx, &models.SignOutHeader{})
26+
drv.Activate(ctx, models.SignOutHeader)
2727

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

3131
if err == nil {
32-
drv.Activate(ctx, &models.SignOutSuccess{})
32+
drv.Activate(ctx, models.SignOutSuccess)
3333
}
3434

3535
return err

auth/whoami.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (c WhoAmI) UI() cli.UI {
2727
func (c *WhoAmI) runTUI(ctx context.Context, drv *ui.Driver) error {
2828
cfg := cli.ConfigFromContext(ctx)
2929

30-
drv.Activate(ctx, &models.WhoAmIHeader{})
30+
drv.Activate(ctx, models.WhoAmIHeader)
3131
drv.Activate(ctx, &models.WhoAmIChecker{})
3232

3333
anc, err := api.NewClient(cfg)

cli.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import (
44
"context"
55
"fmt"
66
"go/build"
7+
"io/fs"
78
"net/url"
89
"os"
910
"regexp"
1011
"runtime"
1112
"strings"
13+
"time"
1214

1315
"github.com/anchordotdev/cli/models"
1416
"github.com/anchordotdev/cli/ui"
@@ -17,6 +19,8 @@ import (
1719
"github.com/spf13/pflag"
1820
)
1921

22+
var Executable string
23+
2024
var Version = struct {
2125
Version, Commit, Date string
2226

@@ -120,6 +124,12 @@ type Config struct {
120124
}
121125

122126
Version struct{} `cmd:"version"`
127+
128+
// values used for testing
129+
130+
GOOS string `desc:"change OS identifier in tests"`
131+
ProcFS fs.FS `desc:"change the proc filesystem in tests"`
132+
Timestamp time.Time `desc:"timestamp to use/display in tests"`
123133
}
124134

125135
type UI struct {
@@ -205,10 +215,25 @@ func ReportError(ctx context.Context, drv *ui.Driver, cmd *cobra.Command, args [
205215
fmt.Fprintf(&body, "\n")
206216
fmt.Fprintf(&body, "---\n")
207217
fmt.Fprintf(&body, "\n")
208-
fmt.Fprintf(&body, "**Command:** `%s`\n", cmd.CalledAs())
218+
fmt.Fprintf(&body, "**Command:** `%s`\n", cmd.CommandPath())
219+
var executable string
220+
if Executable != "" {
221+
executable = Executable
222+
} else {
223+
executable, _ = os.Executable()
224+
}
225+
if executable != "" {
226+
fmt.Fprintf(&body, "**Executable:** `%s`\n", executable)
227+
}
209228
fmt.Fprintf(&body, "**Version:** `%s`\n", VersionString())
210229
fmt.Fprintf(&body, "**Arguments:** `[%s]`\n", strings.Join(args, ", "))
211230
fmt.Fprintf(&body, "**Flags:** `[%s]`\n", strings.Join(flags, ", "))
231+
232+
timestamp := cfg.Timestamp
233+
if timestamp.IsZero() {
234+
timestamp = time.Now().UTC()
235+
}
236+
fmt.Fprintf(&body, "**Timestamp:** `%s`\n", timestamp.Format(time.RFC3339Nano))
212237
if stack != "" {
213238
fmt.Fprintf(&body, "**Stack:**\n```\n%s\n```\n", normalizeStack(stack))
214239
}

0 commit comments

Comments
 (0)