Skip to content

Commit caba8dc

Browse files
committed
v0.0.39 multi-org lcl; misc fixes and refinements
1 parent 38a58bc commit caba8dc

84 files changed

Lines changed: 5122 additions & 882 deletions

File tree

Some content is hidden

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

api/api.go

Lines changed: 119 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,18 @@ type Session struct {
4242
func NewClient(ctx context.Context, cfg *cli.Config) (*Session, error) {
4343
anc := &Session{
4444
Client: &http.Client{
45-
Transport: urlRewriter{
46-
RoundTripper: responseChecker{
47-
RoundTripper: userAgentSetter{
48-
RoundTripper: preferSetter{
49-
cfg: cfg,
50-
RoundTripper: new(http.Transport),
51-
},
52-
},
45+
Transport: Middlewares{
46+
urlRewriter{
47+
url: cfg.API.URL,
5348
},
54-
URL: cfg.API.URL,
55-
},
49+
responseChecker,
50+
userAgentSetter,
51+
preferSetter{
52+
cfg: cfg,
53+
},
54+
autoRetrier,
55+
}.RoundTripper(new(http.Transport)),
5656
},
57-
5857
cfg: cfg,
5958
}
6059

@@ -271,12 +270,12 @@ func getOrgServicesPath(orgSlug string) string {
271270
return "/orgs/" + url.QueryEscape(orgSlug) + "/services"
272271
}
273272

274-
func (s *Session) GetOrgServices(ctx context.Context, orgSlug string) ([]Service, error) {
273+
func (s *Session) GetOrgServices(ctx context.Context, orgSlug string, filters ...Filter[Service]) ([]Service, error) {
275274
var svc Services
276275
if err := s.get(ctx, getOrgServicesPath(orgSlug), &svc); err != nil {
277276
return nil, err
278277
}
279-
return svc.Items, nil
278+
return Filters[Service](filters).Apply(svc.Items), nil
280279
}
281280

282281
func getServicePath(orgSlug, serviceSlug string) string {
@@ -361,90 +360,132 @@ func (r basicAuther) RoundTrip(req *http.Request) (*http.Response, error) {
361360
return r.RoundTripper.RoundTrip(req)
362361
}
363362

364-
type responseChecker struct {
365-
http.RoundTripper
363+
type Middleware interface {
364+
RoundTripper(next http.RoundTripper) http.RoundTripper
365+
}
366+
367+
type Middlewares []Middleware
368+
369+
func (m Middlewares) RoundTripper(tport *http.Transport) http.RoundTripper {
370+
rm := slices.Clone(m)
371+
slices.Reverse(rm)
372+
373+
var next http.RoundTripper = tport
374+
for _, mw := range rm {
375+
next = mw.RoundTripper(next)
376+
}
377+
return next
378+
}
379+
380+
type RoundTripFunc func(*http.Request) (*http.Response, error)
381+
382+
func (fn RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
383+
return fn(req)
384+
}
385+
386+
type MiddlewareFunc func(next http.RoundTripper) http.RoundTripper
387+
388+
func (fn MiddlewareFunc) RoundTripper(next http.RoundTripper) http.RoundTripper {
389+
return fn(next)
366390
}
367391

368392
var jsonMediaTypes = mediaTypes{
369393
"application/json",
370394
"application/problem+json",
371395
}
372396

373-
func (r responseChecker) RoundTrip(req *http.Request) (*http.Response, error) {
374-
res, err := r.RoundTripper.RoundTrip(req)
375-
if err != nil {
376-
return nil, fmt.Errorf("request error %s %s: %w", req.Method, req.URL.Path, err)
377-
}
397+
var responseChecker = MiddlewareFunc(func(next http.RoundTripper) http.RoundTripper {
398+
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
399+
res, err := next.RoundTrip(req)
400+
if err != nil {
401+
return nil, fmt.Errorf("request error %s %s: %w", req.Method, req.URL.Path, err)
402+
}
378403

379-
requestId := res.Header.Get("X-Request-Id")
404+
requestId := res.Header.Get("X-Request-Id")
380405

381-
switch res.StatusCode {
382-
case http.StatusForbidden:
383-
return nil, ErrSignedOut
384-
case http.StatusInternalServerError:
385-
return nil, fmt.Errorf("request [%s] failed: 500 Internal Server Error", requestId)
386-
}
387-
if contentType := res.Header.Get("Content-Type"); !jsonMediaTypes.Matches(contentType) {
388-
return nil, fmt.Errorf("request [%s]: %d response, expected json content-type, got: %q", requestId, res.StatusCode, contentType)
389-
}
390-
return res, nil
391-
}
406+
switch res.StatusCode {
407+
case http.StatusForbidden:
408+
return nil, ErrSignedOut
409+
case http.StatusInternalServerError:
410+
return nil, fmt.Errorf("request [%s] failed: 500 Internal Server Error", requestId)
411+
}
412+
if contentType := res.Header.Get("Content-Type"); !jsonMediaTypes.Matches(contentType) {
413+
return nil, fmt.Errorf("request [%s]: %d response, expected json content-type, got: %q", requestId, res.StatusCode, contentType)
414+
}
415+
return res, nil
416+
})
417+
})
392418

393419
type urlRewriter struct {
394-
http.RoundTripper
395-
396-
URL string
420+
url string
397421
}
398422

399-
func (r urlRewriter) RoundTrip(req *http.Request) (*http.Response, error) {
400-
u, err := url.Parse(r.URL)
401-
if err != nil {
402-
return nil, err
403-
}
404-
req.URL = u.JoinPath(req.URL.Path)
423+
func (r urlRewriter) RoundTripper(next http.RoundTripper) http.RoundTripper {
424+
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
425+
u, err := url.Parse(r.url)
426+
if err != nil {
427+
return nil, err
428+
}
429+
req.URL = u.JoinPath(req.URL.Path)
405430

406-
return r.RoundTripper.RoundTrip(req)
431+
return next.RoundTrip(req)
432+
})
407433
}
408434

409435
type preferSetter struct {
410-
http.RoundTripper
411-
412436
cfg *cli.Config
413437
}
414438

415-
func (s preferSetter) RoundTrip(req *http.Request) (*http.Response, error) {
416-
path := req.URL.Path
439+
func (s preferSetter) RoundTripper(next http.RoundTripper) http.RoundTripper {
440+
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
441+
path := req.URL.Path
417442

418-
var value []string
443+
var value []string
419444

420-
if s.cfg.Test.Prefer[path].Code != 0 {
421-
value = append(value, fmt.Sprintf("code=%d", s.cfg.Test.Prefer[path].Code))
422-
}
445+
if s.cfg.Test.Prefer[path].Code != 0 {
446+
value = append(value, fmt.Sprintf("code=%d", s.cfg.Test.Prefer[path].Code))
447+
}
423448

424-
if s.cfg.Test.Prefer[path].Dynamic {
425-
value = append(value, fmt.Sprintf("dynamic=%t", s.cfg.Test.Prefer[path].Dynamic))
426-
}
449+
if s.cfg.Test.Prefer[path].Dynamic {
450+
value = append(value, fmt.Sprintf("dynamic=%t", s.cfg.Test.Prefer[path].Dynamic))
451+
}
427452

428-
if s.cfg.Test.Prefer[path].Example != "" {
429-
value = append(value, fmt.Sprintf("example=%s", s.cfg.Test.Prefer[path].Example))
430-
}
453+
if s.cfg.Test.Prefer[path].Example != "" {
454+
value = append(value, fmt.Sprintf("example=%s", s.cfg.Test.Prefer[path].Example))
455+
}
431456

432-
if len(value) > 0 {
433-
req.Header.Set("Prefer", strings.Join(value, " "))
434-
}
457+
if len(value) > 0 {
458+
req.Header.Set("Prefer", strings.Join(value, " "))
459+
}
435460

436-
return s.RoundTripper.RoundTrip(req)
461+
return next.RoundTrip(req)
462+
})
437463
}
438464

439-
type userAgentSetter struct {
440-
http.RoundTripper
441-
}
465+
var userAgentSetter = MiddlewareFunc(func(next http.RoundTripper) http.RoundTripper {
466+
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
467+
req.Header.Set("User-Agent", cli.UserAgent())
442468

443-
func (s userAgentSetter) RoundTrip(req *http.Request) (*http.Response, error) {
444-
req.Header.Set("User-Agent", cli.UserAgent())
469+
return next.RoundTrip(req)
470+
})
471+
})
445472

446-
return s.RoundTripper.RoundTrip(req)
447-
}
473+
var autoRetrier = MiddlewareFunc(func(next http.RoundTripper) http.RoundTripper {
474+
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
475+
res, err := next.RoundTrip(req)
476+
if res == nil {
477+
return res, err
478+
}
479+
480+
switch res.StatusCode {
481+
case http.StatusBadGateway, http.StatusServiceUnavailable:
482+
// TODO: configure a backoff/sleep here?
483+
return next.RoundTrip(req)
484+
default:
485+
return res, err
486+
}
487+
})
488+
})
448489

449490
type mediaTypes []string
450491

@@ -472,3 +513,14 @@ func gnomeKeyringMissing(cfg *cli.Config) bool {
472513
}
473514
return true
474515
}
516+
517+
type Filter[T any] func(s []T) []T
518+
519+
type Filters[T any] []Filter[T]
520+
521+
func (f Filters[T]) Apply(s []T) []T {
522+
for _, fn := range f {
523+
s = fn(s)
524+
}
525+
return s
526+
}

api/apitest/apitest.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ type Server struct {
3232
URL string
3333
RailsPort string
3434

35-
proxy bool
36-
verbose bool
37-
3835
stopfn func()
3936
waitfn func()
4037
}
@@ -44,10 +41,30 @@ func (s *Server) Close() {
4441
s.waitfn()
4542
}
4643

44+
func (s *Server) IsMock() bool {
45+
return !proxy
46+
}
47+
4748
func (s *Server) IsProxy() bool {
4849
return proxy
4950
}
5051

52+
func (s *Server) RecreateUser(username string) error {
53+
if !s.IsProxy() {
54+
return nil
55+
}
56+
57+
cmd := exec.Command("script/clitest-recreate-user", username)
58+
cmd.Dir = s.RootDir
59+
60+
_, err := cmd.Output()
61+
if err != nil {
62+
return err
63+
}
64+
65+
return nil
66+
}
67+
5168
func (s *Server) GeneratePAT(email string) (string, error) {
5269
if !s.IsProxy() {
5370
return "test-token", nil
@@ -97,7 +114,7 @@ func (s *Server) StartMock(ctx context.Context) error {
97114

98115
s.URL = "http://" + host + ":" + port + "/v0"
99116
s.stopfn = stopfn
100-
s.waitfn = func() { waitfn() }
117+
s.waitfn = func() { _ = waitfn() }
101118

102119
return nil
103120
}
@@ -113,7 +130,7 @@ func (s *Server) StartProxy(ctx context.Context) error {
113130

114131
addrRails, waitRails, err := s.startRails(ctx)
115132
if err != nil {
116-
lock.Unlock()
133+
_ = lock.Unlock()
117134
stopfn()
118135
return err
119136
}
@@ -131,7 +148,7 @@ func (s *Server) StartProxy(ctx context.Context) error {
131148

132149
addr, waitPrism, err := s.startProxy(ctx, host+":"+port)
133150
if err != nil {
134-
lock.Unlock()
151+
_ = lock.Unlock()
135152
stopfn()
136153
return err
137154
}
@@ -142,7 +159,7 @@ func (s *Server) StartProxy(ctx context.Context) error {
142159
group.Go(func() error { return s.waitTCP(addrRails) })
143160

144161
if err := group.Wait(); err != nil {
145-
lock.Unlock()
162+
_ = lock.Unlock()
146163
stopfn()
147164
return err
148165
}
@@ -165,9 +182,8 @@ func (s *Server) StartProxy(ctx context.Context) error {
165182
s.URL = "http://" + host + ":" + port + "/v0"
166183
s.stopfn = stopfn
167184
s.waitfn = func() {
168-
defer lock.Unlock()
169-
170-
group.Wait()
185+
_ = group.Wait()
186+
_ = lock.Unlock()
171187
}
172188

173189
return nil

api/openapi.gen.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/openapi.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package api
2+
3+
import "slices"
4+
5+
func (o Organization) Key() string { return o.Apid }
6+
func (o Organization) String() string { return o.Name }
7+
func (o Organization) Plural() string { return "organizations" }
8+
func (o Organization) Singular() string { return "organization" }
9+
10+
func (r Realm) Key() string { return r.Apid }
11+
func (r Realm) String() string { return r.Name }
12+
func (r Realm) Plural() string { return "realms" }
13+
func (r Realm) Singular() string { return "realm" }
14+
15+
func (s Service) Key() string { return s.Slug }
16+
func (s Service) String() string { return s.Name }
17+
func (s Service) Plural() string { return "services" }
18+
func (s Service) Singular() string { return "service" }
19+
20+
func NonDiagnosticServices(s []Service) []Service {
21+
return slices.DeleteFunc(s, func(svc Service) bool {
22+
return svc.ServerType == ServiceServerTypeDiagnostic
23+
})
24+
}
25+
26+
var _ Filter[Service] = NonDiagnosticServices

auth/signin_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@ func TestCmdAuthSignin(t *testing.T) {
1515
func TestSignIn(t *testing.T) {
1616
t.Run("cli-auth-success", func(t *testing.T) {
1717
t.Skip("cli auth test not yet implemented")
18-
return
1918
})
2019

2120
t.Run("valid-config-token", func(t *testing.T) {
2221
t.Skip("cli auth test not yet implemented")
23-
return
2422
})
2523

2624
t.Run("invalid-config-token", func(t *testing.T) {
2725
t.Skip("cli auth test not yet implemented")
28-
return
2926
})
3027
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
─── SignOutHeader ──────────────────────────────────────────────────────────────
2+
23
# Signout from Anchor.dev `anchor auth signout`
34
─── SignOutSignedOut ───────────────────────────────────────────────────────────
5+
46
# Signout from Anchor.dev `anchor auth signout`
57
- Not signed in.
68
| Run `anchor auth signin` to sign in.

0 commit comments

Comments
 (0)