Skip to content

Commit 5b34c9a

Browse files
committed
v0.1.5 service probe, version upgrade, and misc fixes
1 parent 531e84e commit 5b34c9a

Some content is hidden

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

52 files changed

+1989
-235
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
shell: bash
5757
run: echo 'flags=--skip homebrew' >> $GITHUB_ENV
5858
- name: Run GoReleaser
59-
uses: goreleaser/goreleaser-action@v5
59+
uses: goreleaser/goreleaser-action@v6
6060
with:
6161
distribution: goreleaser-pro
6262
version: latest
@@ -97,7 +97,7 @@ jobs:
9797
enableCrossOsArchive: true
9898

9999
- name: Run GoReleaser
100-
uses: goreleaser/goreleaser-action@v5
100+
uses: goreleaser/goreleaser-action@v6
101101
with:
102102
distribution: goreleaser-pro
103103
version: latest

api/acme.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package api
2+
3+
import (
4+
"crypto/tls"
5+
"encoding/base64"
6+
"time"
7+
8+
"github.com/anchordotdev/cli"
9+
"golang.org/x/crypto/acme"
10+
"golang.org/x/crypto/acme/autocert"
11+
)
12+
13+
func ProvisionCert(eab *Eab, domains []string, acmeURL string) (*tls.Certificate, error) {
14+
hmacKey, err := base64.URLEncoding.DecodeString(eab.HmacKey)
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
mgr := &autocert.Manager{
20+
Prompt: autocert.AcceptTOS,
21+
HostPolicy: autocert.HostWhitelist(domains...),
22+
Client: &acme.Client{
23+
DirectoryURL: acmeURL,
24+
UserAgent: cli.UserAgent(),
25+
},
26+
ExternalAccountBinding: &acme.ExternalAccountBinding{
27+
KID: eab.Kid,
28+
Key: hmacKey,
29+
},
30+
RenewBefore: 24 * time.Hour,
31+
}
32+
33+
// TODO: switch to using ACME package here, so that extra domains can be sent through for SAN extension
34+
clientHello := &tls.ClientHelloInfo{
35+
ServerName: domains[0],
36+
CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
37+
}
38+
39+
return mgr.GetCertificate(clientHello)
40+
}

api/api.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ var (
2626
ErrGnomeKeyringRequired = fmt.Errorf("gnome-keyring required for secure credential storage: %w", ErrSignedOut)
2727
)
2828

29+
type QueryParam func(url.Values)
30+
31+
type QueryParams []QueryParam
32+
33+
func (q QueryParams) Apply(u *url.URL) {
34+
val := u.Query()
35+
for _, fn := range q {
36+
fn(val)
37+
}
38+
u.RawQuery = val.Encode()
39+
}
40+
2941
// NB: can't call this Client since the name is already taken by an openapi
3042
// generated type. It's more like a session anyways, since it caches some
3143
// current user info.
@@ -222,6 +234,34 @@ func (s *Session) FetchCredentials(ctx context.Context, orgSlug, realmSlug strin
222234
return creds.Items, nil
223235
}
224236

237+
func getCredentialsURL(orgSlug, realmSlug string) (*url.URL, error) {
238+
return url.Parse(fetchCredentialsPath(orgSlug, realmSlug))
239+
}
240+
241+
func SubCA(apid string) QueryParam {
242+
return func(v url.Values) {
243+
// TODO: v.Set("type", "subca")
244+
v.Set("subject_uid_param", apid)
245+
}
246+
}
247+
248+
func (s *Session) GetCredentials(ctx context.Context, orgSlug, realmSlug string, params ...QueryParam) ([]Credential, error) {
249+
var creds struct {
250+
Items []Credential `json:"items,omitempty"`
251+
}
252+
253+
u, err := getCredentialsURL(orgSlug, realmSlug)
254+
if err != nil {
255+
return nil, err
256+
}
257+
QueryParams(params).Apply(u)
258+
259+
if err := s.get(ctx, u.RequestURI(), &creds); err != nil {
260+
return nil, err
261+
}
262+
return creds.Items, nil
263+
}
264+
225265
func (s *Session) UserInfo(ctx context.Context) (*Root, error) {
226266
if s.userInfo != nil {
227267
return s.userInfo, nil
@@ -293,11 +333,14 @@ func (s *Session) GetService(ctx context.Context, orgSlug, serviceSlug string) (
293333
return &svc, nil
294334
}
295335

296-
func (s *Session) get(ctx context.Context, path string, out any) error {
297-
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
336+
func (s *Session) get(ctx context.Context, uri string, out any) error {
337+
req, err := http.NewRequestWithContext(ctx, "GET", uri, nil)
298338
if err != nil {
299339
return err
300340
}
341+
if req.URL, err = url.Parse(uri); err != nil {
342+
return err
343+
}
301344
req.Header.Set("Content-Type", "application/json")
302345

303346
res, err := s.Do(req)
@@ -426,6 +469,7 @@ func (r urlRewriter) RoundTripper(next http.RoundTripper) http.RoundTripper {
426469
if err != nil {
427470
return nil, err
428471
}
472+
u.RawQuery = req.URL.RawQuery
429473
req.URL = u.JoinPath(req.URL.Path)
430474

431475
return next.RoundTrip(req)

api/openapi.gen.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
"github.com/cli/browser"
15+
"github.com/google/go-github/v54/github"
1516
"github.com/mcuadros/go-defaults"
1617
"github.com/spf13/cobra"
1718
"github.com/spf13/pflag"
@@ -41,10 +42,52 @@ var Version = struct {
4142
Arch: runtime.GOARCH,
4243
}
4344

45+
var LatestRelease *Release
46+
47+
type Release = github.RepositoryRelease
48+
49+
var SkipReleaseCheck = false
50+
4451
func IsDevVersion() bool {
4552
return Version.Version == "dev"
4653
}
4754

55+
func IsFreshLatestRelease(ctx context.Context) (bool, error) {
56+
release, err := getLatestRelease(ctx)
57+
if err != nil {
58+
return true, err
59+
}
60+
61+
return release.PublishedAt != nil && time.Since(release.PublishedAt.Time).Hours() < 24, nil
62+
}
63+
64+
func IsUpgradeable(ctx context.Context) (bool, error) {
65+
release, err := getLatestRelease(ctx)
66+
if err != nil {
67+
return false, err
68+
}
69+
70+
return release.TagName != nil && *release.TagName != ReleaseTagName(), nil
71+
}
72+
73+
func getLatestRelease(ctx context.Context) (*Release, error) {
74+
if LatestRelease != nil {
75+
return LatestRelease, nil
76+
}
77+
78+
release, _, err := github.NewClient(nil).Repositories.GetLatestRelease(ctx, "anchordotdev", "cli")
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
LatestRelease = &Release{
84+
PublishedAt: release.PublishedAt,
85+
TagName: release.TagName,
86+
}
87+
88+
return LatestRelease, nil
89+
}
90+
4891
func UserAgent() string {
4992
return "Anchor CLI " + VersionString()
5093
}

cli_test.go

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"time"
1212

1313
"github.com/anchordotdev/cli"
14-
"github.com/anchordotdev/cli/models"
1514
"github.com/anchordotdev/cli/stacktrace"
1615
_ "github.com/anchordotdev/cli/testflags"
1716
"github.com/anchordotdev/cli/ui"
@@ -169,35 +168,3 @@ func (m *TestHint) View() string {
169168
}
170169

171170
var Timestamp, _ = time.Parse(time.RFC3339Nano, "2024-01-02T15:04:05.987654321Z")
172-
173-
func TestConfigLoadTOMLGolden(t *testing.T) {
174-
ctx, cancel := context.WithCancel(context.Background())
175-
defer cancel()
176-
177-
cfg := new(cli.Config)
178-
cfg.File.Path = "anchor.toml"
179-
cfg.Via.TOML = new(cli.Config)
180-
181-
ctx = cli.ContextWithConfig(ctx, cfg)
182-
183-
cmd := tomlCommand{}
184-
185-
uitest.TestTUIOutput(ctx, t, cmd.UI())
186-
}
187-
188-
type tomlCommand struct{}
189-
190-
func (c tomlCommand) UI() cli.UI {
191-
return cli.UI{
192-
RunTUI: c.run,
193-
}
194-
}
195-
196-
func (*tomlCommand) run(ctx context.Context, drv *ui.Driver) error {
197-
cfg := cli.ConfigFromContext(ctx)
198-
if cfg.Via.TOML != nil {
199-
drv.Activate(ctx, models.ConfigLoaded(cfg.File.Path))
200-
}
201-
202-
return nil
203-
}

cmd.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66

77
"github.com/MakeNowJust/heredoc"
8-
"github.com/anchordotdev/cli/models"
98
"github.com/anchordotdev/cli/stacktrace"
109
"github.com/anchordotdev/cli/ui"
1110
"github.com/spf13/cobra"
@@ -156,6 +155,13 @@ var rootDef = CmdDef{
156155
Args: cobra.NoArgs,
157156
Short: "Fetch Environment Variables for Service",
158157
},
158+
{
159+
Name: "probe",
160+
161+
Use: "probe <service> [flags]",
162+
Args: cobra.NoArgs,
163+
Short: "Probe a service for proper TLS setup & configuration",
164+
},
159165
},
160166
},
161167
{
@@ -206,7 +212,16 @@ var rootDef = CmdDef{
206212

207213
Use: "version",
208214
Args: cobra.NoArgs,
209-
Short: "Show version info",
215+
Short: "Show Version Info",
216+
SubDefs: []CmdDef{
217+
{
218+
Name: "upgrade",
219+
220+
Use: "upgrade",
221+
Args: cobra.NoArgs,
222+
Short: "Check for Upgrade",
223+
},
224+
},
210225
},
211226
},
212227
}
@@ -302,10 +317,6 @@ func NewCmd[T UIer](parent *cobra.Command, name string, fn func(*cobra.Command))
302317
errc <- err
303318
}()
304319

305-
if cfg.Via.TOML != nil {
306-
drv.Activate(ctx, models.ConfigLoaded(cfg.File.Path))
307-
}
308-
309320
if err := stacktrace.CapturePanic(func() error { return t.UI().RunTUI(ctx, drv) }); err != nil {
310321
var uierr ui.Error
311322
if errors.As(err, &uierr) {

0 commit comments

Comments
 (0)