Skip to content

Commit bce0148

Browse files
committed
v0.0.30: improved windows and headless linux support
1 parent 2fe6f49 commit bce0148

7 files changed

Lines changed: 155 additions & 88 deletions

File tree

api/api.go

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

2323
var (
2424
ErrSignedOut = errors.New("sign in required")
25-
ErrGnomeKeyringRequired = errors.New("gnome-keyring is required for secure credential storage.\n ! Please install with your host package manager")
25+
ErrGnomeKeyringRequired = fmt.Errorf("gnome-keyring required for secure credential storage: %w", ErrSignedOut)
2626
)
2727

2828
// NB: can't call this Client since the name is already taken by an openapi
@@ -64,23 +64,15 @@ func NewClient(cfg *cli.Config) (*Session, error) {
6464

6565
if apiToken, err = kr.Get(keyring.APIToken); err == keyring.ErrNotFound {
6666
return anc, ErrSignedOut
67-
} else if err != nil {
68-
if runtime.GOOS == "linux" {
69-
_, err := exec.LookPath("gnome-keyring-daemon")
70-
if err != nil {
71-
_, err := exec.LookPath("apt-get")
72-
if err == nil {
73-
return nil, fmt.Errorf("%w:\n sudo apt-get install gnome-keyring", ErrGnomeKeyringRequired)
74-
}
75-
_, err = exec.LookPath("yum")
76-
if err == nil {
77-
return nil, fmt.Errorf("%w:\n sudo yum install gnome-keyring", ErrGnomeKeyringRequired)
78-
}
79-
return nil, fmt.Errorf("%w.", ErrGnomeKeyringRequired)
80-
}
67+
}
68+
if err != nil {
69+
if gnomeKeyringMissing() {
70+
return anc, ErrGnomeKeyringRequired
8171
}
72+
8273
return nil, fmt.Errorf("reading PAT token from keyring failed: %w", err)
8374
}
75+
8476
if !strings.HasPrefix(apiToken, "ap0_") || len(apiToken) != 64 {
8577
return nil, fmt.Errorf("read invalid PAT token from keyring")
8678
}
@@ -390,3 +382,13 @@ const NotFoundErr = StatusCodeError(http.StatusNotFound)
390382

391383
func (err StatusCodeError) StatusCode() int { return int(err) }
392384
func (err StatusCodeError) Error() string { return fmt.Sprintf("unexpected %d status response", err) }
385+
386+
func gnomeKeyringMissing() bool {
387+
if runtime.GOOS != "linux" {
388+
return false
389+
}
390+
if path, _ := exec.LookPath("gnome-keyring-daemon"); path != "" {
391+
return false
392+
}
393+
return true
394+
}

auth/models/signin.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,23 @@ func (m *SignInChecker) View() string {
111111
}
112112
return b.String()
113113
}
114+
115+
type KeyringUnavailable struct {
116+
ShowGnomeKeyringHint bool
117+
}
118+
119+
func (m *KeyringUnavailable) Init() tea.Cmd { return nil }
120+
121+
func (m *KeyringUnavailable) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil }
122+
123+
func (m *KeyringUnavailable) View() string {
124+
var b strings.Builder
125+
fmt.Fprintln(&b, ui.Warning("Unable to access keyring, credentials will not be stored."))
126+
127+
if m.ShowGnomeKeyringHint {
128+
fmt.Fprintln(&b, ui.StepHint("gnome-keyring is required for secure credential storage."))
129+
fmt.Fprintln(&b, ui.StepHint("Please install with your host package manager"))
130+
}
131+
132+
return b.String()
133+
}

auth/signin.go

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ package auth
22

33
import (
44
"context"
5-
"encoding/json"
65
"errors"
7-
"fmt"
8-
"net/http"
96
"time"
107

118
"github.com/atotto/clipboard"
@@ -17,6 +14,7 @@ import (
1714
"github.com/anchordotdev/cli/api"
1815
"github.com/anchordotdev/cli/auth/models"
1916
"github.com/anchordotdev/cli/keyring"
17+
cliModels "github.com/anchordotdev/cli/models"
2018
"github.com/anchordotdev/cli/ui"
2119
)
2220

@@ -49,7 +47,7 @@ func (s *SignIn) RunTUI(ctx context.Context, drv *ui.Driver) error {
4947
drv.Activate(ctx, s.Hint)
5048

5149
anc, err := api.NewClient(cfg)
52-
if err != nil && err != api.ErrSignedOut {
50+
if err != nil && !errors.Is(err, api.ErrSignedOut) {
5351
return err
5452
}
5553

@@ -78,7 +76,7 @@ func (s *SignIn) RunTUI(ctx context.Context, drv *ui.Driver) error {
7876
}
7977

8078
if err := browser.OpenURL(codes.VerificationUri); err != nil {
81-
return err
79+
drv.Activate(ctx, &cliModels.Browserless{Url: codes.VerificationUri})
8280
}
8381

8482
drv.Activate(ctx, new(models.SignInChecker))
@@ -95,38 +93,24 @@ func (s *SignIn) RunTUI(ctx context.Context, drv *ui.Driver) error {
9593
}
9694
cfg.API.Token = patToken
9795

98-
userInfo, err := fetchUserInfo(cfg)
96+
anc, err = api.NewClient(cfg)
97+
if err != nil {
98+
return err
99+
}
100+
101+
userInfo, err := anc.UserInfo(ctx)
99102
if err != nil {
100103
return err
101104
}
102105

103106
kr := keyring.Keyring{Config: cfg}
104107
if err := kr.Set(keyring.APIToken, cfg.API.Token); err != nil {
105-
return err
108+
drv.Activate(ctx, &models.KeyringUnavailable{
109+
ShowGnomeKeyringHint: errors.Is(err, api.ErrGnomeKeyringRequired),
110+
})
106111
}
107112

108113
drv.Send(models.UserSignInMsg(userInfo.Whoami))
109114

110115
return nil
111116
}
112-
113-
func fetchUserInfo(cfg *cli.Config) (*api.Root, error) {
114-
anc, err := api.NewClient(cfg)
115-
if err != nil {
116-
return nil, err
117-
}
118-
119-
res, err := anc.Get("")
120-
if err != nil {
121-
return nil, err
122-
}
123-
if res.StatusCode != http.StatusOK {
124-
return nil, fmt.Errorf("unexpected response code: %d", res.StatusCode)
125-
}
126-
127-
var userInfo *api.Root
128-
if err := json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
129-
return nil, err
130-
}
131-
return userInfo, nil
132-
}

cli.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,11 @@ func init() {
159159
joinedGoPaths := strings.Join(goPaths, ",<gopath>,") + ",<gopath>"
160160
replacements := strings.Split(joinedGoPaths, ",")
161161
replacements = append(replacements, runtime.GOROOT(), "<goroot>")
162-
162+
163163
if pwd, _ := os.Getwd(); pwd != "" {
164164
replacements = append(replacements, pwd, "<pwd>")
165165
}
166-
166+
167167
stackPathReplacer = strings.NewReplacer(replacements...)
168168
}
169169

@@ -212,7 +212,7 @@ func ReportError(ctx context.Context, drv *ui.Driver, cmd *cobra.Command, args [
212212
if stack != "" {
213213
fmt.Fprintf(&body, "**Stack:**\n```\n%s\n```\n", normalizeStack(stack))
214214
}
215-
fmt.Fprintf(&body, "**Stdout:**\n```\n%s\n```\n", strings.TrimRight(string(drv.FinalOut()), "\n"))
215+
fmt.Fprintf(&body, "**Stdout:**\n```\n%s\n```\n", strings.TrimRight(string(drv.LastView), "\n"))
216216
q.Add("body", body.String())
217217

218218
reportErrorConfirmCh := make(chan struct{})

cmd/anchor/.goreleaser.yaml

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ builds:
1717
- CGO_ENABLED=0
1818
id: linux
1919
goos: [linux]
20-
goarch: ['amd64','arm64','386']
20+
goarch: ["amd64", "arm64", "386"]
2121
- <<: *build_defaults
2222
id: macos
2323
goos: [darwin]
24-
goarch: ['amd64','arm64']
24+
goarch: ["amd64", "arm64"]
2525
- <<: *build_defaults
2626
id: windows
2727
goos: [windows]
28-
goarch: ['amd64','386']
28+
goarch: ["amd64", "386"]
2929

3030
archives:
3131
- id: linux
@@ -64,39 +64,87 @@ brews:
6464
assert_match "anchor is a command line interface for the Anchor certificate management platform.", shell_output("#{bin}/anchor")
6565
6666
msi:
67-
- name: "msi-builder-{{ .MsiArch }}"
68-
id: anchor-msi
69-
ids:
70-
- windows
71-
wxs: ./windows/app.wxs
72-
extra_files:
73-
- ./windows/als2.ico
74-
- ../../LICENSE
75-
mod_timestamp: "{{ .CommitTimestamp }}"
67+
- name: "msi-builder-{{ .MsiArch }}"
68+
id: anchor-msi
69+
ids:
70+
- windows
71+
wxs: ./windows/app.wxs
72+
extra_files:
73+
- ./windows/als2.ico
74+
- ../../LICENSE
75+
mod_timestamp: "{{ .CommitTimestamp }}"
7676

7777
chocolateys:
78-
- name: anchor
79-
ids:
80-
- anchor-msi
81-
package_source_url: https://github.com/anchordotdev/cli
82-
owners: Anchor Security, Inc.
83-
title: Anchor CLI
84-
authors: Anchor
85-
project_url: https://anchor.dev
86-
use: msi
87-
url_template: "https://github.com/anchordotdev/cli/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
88-
copyright: 2024 Anchor Security, Inc.
89-
license_url: https://raw.githubusercontent.com/anchordotdev/cli/main/LICENSE
90-
require_license_acceptance: false
91-
project_source_url: https://github.com/anchordotdev/cli
92-
docs_url: https://anchor.dev/docs
93-
bug_tracker_url: https://github.com/anchordotdev/cli/issues
94-
icon_url: https://anchor.dev/images/als2.png
95-
tags: "security tls ssl certificates localhost https cryptography encryption acme cli x509 X.509"
96-
summary: "Command-line tools for Anchor.dev"
97-
description: |
78+
- name: anchor
79+
ids:
80+
- anchor-msi
81+
package_source_url: https://github.com/anchordotdev/cli
82+
owners: Anchor Security, Inc.
83+
title: Anchor CLI
84+
authors: Anchor
85+
project_url: https://anchor.dev
86+
use: msi
87+
url_template: "https://github.com/anchordotdev/cli/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
88+
copyright: 2024 Anchor Security, Inc.
89+
license_url: https://raw.githubusercontent.com/anchordotdev/cli/main/LICENSE
90+
require_license_acceptance: false
91+
project_source_url: https://github.com/anchordotdev/cli
92+
docs_url: https://anchor.dev/docs
93+
bug_tracker_url: https://github.com/anchordotdev/cli/issues
94+
icon_url: https://anchor.dev/images/als2.png
95+
tags: "security tls ssl certificates localhost https cryptography encryption acme cli x509 X.509"
96+
summary: "Command-line tools for Anchor.dev"
97+
description: |
9898
anchor is a command line interface for the Anchor certificate management platform.
9999
It provides a developer friendly interface for certificate management.
100-
release_notes: "https://github.com/anchordotdev/cli/releases/tag/v{{ .Version }}"
101-
api_key: "{{ .Env.CHOCOLATEY_API_KEY }}"
102-
source_repo: "https://push.chocolatey.org/"
100+
release_notes: "https://github.com/anchordotdev/cli/releases/tag/v{{ .Version }}"
101+
api_key: "{{ .Env.CHOCOLATEY_API_KEY }}"
102+
source_repo: "https://push.chocolatey.org/"
103+
104+
winget:
105+
- name: cli
106+
publisher: Anchor
107+
short_description: "Command-line tools for Anchor.dev"
108+
license: mit
109+
publisher_url: https://anchor.dev
110+
publisher_support_url: https://github.com/anchordotdev/cli/issues
111+
package_identifier: Anchor.cli
112+
ids:
113+
- anchor-msi
114+
use: msi
115+
product_code: C5F94F62-E3CC-48E2-AB3C-4DED8C6099E9
116+
url_template: "https://github.com/anchordotdev/cli/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
117+
homepage: https://anchor.dev
118+
description: |
119+
anchor is a command line interface for the Anchor certificate management platform.
120+
It provides a developer friendly interface for certificate management.
121+
license_url: https://raw.githubusercontent.com/anchordotdev/cli/main/LICENSE
122+
copyright: 2024 Anchor Security, Inc.
123+
release_notes: "{{.Changelog}}"
124+
release_notes_url: "https://github.com/anchordotdev/cli/releases/tag/v{{ .Version }}"
125+
tags:
126+
[
127+
"security",
128+
"tls",
129+
"ssl",
130+
"certificates",
131+
"localhost",
132+
"https",
133+
"cryptography",
134+
"encryption",
135+
"acme",
136+
"cli",
137+
"x509",
138+
"X.509",
139+
]
140+
repository:
141+
owner: anchordotdev
142+
name: winget-pkgs
143+
branch: "{{.ProjectName}}-{{.Version}}"
144+
pull_request:
145+
enabled: true
146+
check_boxes: true
147+
base:
148+
owner: microsoft
149+
name: winget-pkgs
150+
branch: master

ui/driver.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type Driver struct {
4141
Out io.Reader
4242
out io.ReadWriter
4343
test bool
44-
lastView string
44+
LastView string
4545
}
4646

4747
func NewDriverTest(ctx context.Context) *Driver {
@@ -172,12 +172,12 @@ func (d *Driver) View() string {
172172
out += mdl.View()
173173
}
174174
normalizedOut := spinnerReplacer.Replace(out)
175-
if out != "" && normalizedOut != d.lastView {
175+
if out != "" && normalizedOut != d.LastView {
176176
separator := fmt.Sprintf("─── %s ", reflect.TypeOf(d.active).Elem().Name())
177177
separator = separator + strings.Repeat("─", 80-utf8.RuneCountInString(separator))
178178
fmt.Fprintln(d.out, separator)
179179
fmt.Fprint(d.out, normalizedOut)
180-
d.lastView = normalizedOut
180+
d.LastView = normalizedOut
181181
}
182182
return out
183183
}

ui/styles.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ var (
1919
Skip = header.Copy().Faint(true).SetString("# Skipped:").Render
2020
Hint = hint.Copy().Render
2121
Underline = lipgloss.NewStyle().Underline(true).Render
22+
Warning = header.Copy().SetString(bgBanana(fgMidnight("!")) + fgBanana(" Warning:")).Render
2223

2324
// https://github.com/charmbracelet/lipgloss/blob/v0.9.1/style.go#L149
2425

@@ -28,19 +29,31 @@ var (
2829
StepInProgress = lipgloss.NewStyle().SetString(" *").Render
2930
StepPrompt = lipgloss.NewStyle().SetString(" " + Prompt.Render("?")).Render
3031

32+
Announce = lipgloss.NewStyle().Background(colorBrandSecondary).Render
33+
bgBanana = lipgloss.NewStyle().Background(colorBanana).Render
34+
3135
Accentuate = lipgloss.NewStyle().Italic(true).Render
3236
Action = lipgloss.NewStyle().Bold(true).Foreground(colorBrandPrimary).Render
33-
Announce = lipgloss.NewStyle().Background(colorBrandSecondary).Render
3437
Error = lipgloss.NewStyle().Bold(true).Foreground(colorDanger).Render
3538
Emphasize = lipgloss.NewStyle().Bold(true).Render
3639
EmphasizeUnderline = lipgloss.NewStyle().Bold(true).Underline(true).Render
3740
Titlize = lipgloss.NewStyle().Bold(true).Render
3841
URL = lipgloss.NewStyle().Faint(true).Underline(true).Render
3942
Whisper = lipgloss.NewStyle().Faint(true).Render
4043

41-
colorBrandPrimary = lipgloss.Color("#ff6000")
42-
colorBrandSecondary = lipgloss.Color("#7000ff")
43-
colorDanger = lipgloss.Color("#E63757")
44+
fgBanana = lipgloss.NewStyle().Foreground(colorBanana).Render
45+
fgMidnight = lipgloss.NewStyle().Foreground(colorMidnight).Render
46+
47+
colorBrandPrimary = colorMandarin
48+
colorBrandSecondary = colorGrape
49+
colorDanger = colorApple
50+
51+
// Brand Palette
52+
colorMidnight = lipgloss.Color("#110C18")
53+
colorGrape = lipgloss.Color("#60539E")
54+
colorMandarin = lipgloss.Color("#FF6000")
55+
colorApple = lipgloss.Color("#CE4433")
56+
colorBanana = lipgloss.Color("#FBF5AC")
4457

4558
Prompt = lipgloss.NewStyle().Foreground(colorBrandPrimary)
4659

0 commit comments

Comments
 (0)