Skip to content

Commit

Permalink
v0.0.39 multi-org lcl; misc fixes and refinements
Browse files Browse the repository at this point in the history
  • Loading branch information
geemus committed Jul 30, 2024
1 parent 38a58bc commit caba8dc
Show file tree
Hide file tree
Showing 84 changed files with 5,122 additions and 882 deletions.
186 changes: 119 additions & 67 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,18 @@ type Session struct {
func NewClient(ctx context.Context, cfg *cli.Config) (*Session, error) {
anc := &Session{
Client: &http.Client{
Transport: urlRewriter{
RoundTripper: responseChecker{
RoundTripper: userAgentSetter{
RoundTripper: preferSetter{
cfg: cfg,
RoundTripper: new(http.Transport),
},
},
Transport: Middlewares{
urlRewriter{
url: cfg.API.URL,
},
URL: cfg.API.URL,
},
responseChecker,
userAgentSetter,
preferSetter{
cfg: cfg,
},
autoRetrier,
}.RoundTripper(new(http.Transport)),
},

cfg: cfg,
}

Expand Down Expand Up @@ -271,12 +270,12 @@ func getOrgServicesPath(orgSlug string) string {
return "/orgs/" + url.QueryEscape(orgSlug) + "/services"
}

func (s *Session) GetOrgServices(ctx context.Context, orgSlug string) ([]Service, error) {
func (s *Session) GetOrgServices(ctx context.Context, orgSlug string, filters ...Filter[Service]) ([]Service, error) {
var svc Services
if err := s.get(ctx, getOrgServicesPath(orgSlug), &svc); err != nil {
return nil, err
}
return svc.Items, nil
return Filters[Service](filters).Apply(svc.Items), nil
}

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

type responseChecker struct {
http.RoundTripper
type Middleware interface {
RoundTripper(next http.RoundTripper) http.RoundTripper
}

type Middlewares []Middleware

func (m Middlewares) RoundTripper(tport *http.Transport) http.RoundTripper {
rm := slices.Clone(m)
slices.Reverse(rm)

var next http.RoundTripper = tport
for _, mw := range rm {
next = mw.RoundTripper(next)
}
return next
}

type RoundTripFunc func(*http.Request) (*http.Response, error)

func (fn RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return fn(req)
}

type MiddlewareFunc func(next http.RoundTripper) http.RoundTripper

func (fn MiddlewareFunc) RoundTripper(next http.RoundTripper) http.RoundTripper {
return fn(next)
}

var jsonMediaTypes = mediaTypes{
"application/json",
"application/problem+json",
}

func (r responseChecker) RoundTrip(req *http.Request) (*http.Response, error) {
res, err := r.RoundTripper.RoundTrip(req)
if err != nil {
return nil, fmt.Errorf("request error %s %s: %w", req.Method, req.URL.Path, err)
}
var responseChecker = MiddlewareFunc(func(next http.RoundTripper) http.RoundTripper {
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
res, err := next.RoundTrip(req)
if err != nil {
return nil, fmt.Errorf("request error %s %s: %w", req.Method, req.URL.Path, err)
}

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

switch res.StatusCode {
case http.StatusForbidden:
return nil, ErrSignedOut
case http.StatusInternalServerError:
return nil, fmt.Errorf("request [%s] failed: 500 Internal Server Error", requestId)
}
if contentType := res.Header.Get("Content-Type"); !jsonMediaTypes.Matches(contentType) {
return nil, fmt.Errorf("request [%s]: %d response, expected json content-type, got: %q", requestId, res.StatusCode, contentType)
}
return res, nil
}
switch res.StatusCode {
case http.StatusForbidden:
return nil, ErrSignedOut
case http.StatusInternalServerError:
return nil, fmt.Errorf("request [%s] failed: 500 Internal Server Error", requestId)
}
if contentType := res.Header.Get("Content-Type"); !jsonMediaTypes.Matches(contentType) {
return nil, fmt.Errorf("request [%s]: %d response, expected json content-type, got: %q", requestId, res.StatusCode, contentType)
}
return res, nil
})
})

type urlRewriter struct {
http.RoundTripper

URL string
url string
}

func (r urlRewriter) RoundTrip(req *http.Request) (*http.Response, error) {
u, err := url.Parse(r.URL)
if err != nil {
return nil, err
}
req.URL = u.JoinPath(req.URL.Path)
func (r urlRewriter) RoundTripper(next http.RoundTripper) http.RoundTripper {
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
u, err := url.Parse(r.url)
if err != nil {
return nil, err
}
req.URL = u.JoinPath(req.URL.Path)

return r.RoundTripper.RoundTrip(req)
return next.RoundTrip(req)
})
}

type preferSetter struct {
http.RoundTripper

cfg *cli.Config
}

func (s preferSetter) RoundTrip(req *http.Request) (*http.Response, error) {
path := req.URL.Path
func (s preferSetter) RoundTripper(next http.RoundTripper) http.RoundTripper {
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
path := req.URL.Path

var value []string
var value []string

if s.cfg.Test.Prefer[path].Code != 0 {
value = append(value, fmt.Sprintf("code=%d", s.cfg.Test.Prefer[path].Code))
}
if s.cfg.Test.Prefer[path].Code != 0 {
value = append(value, fmt.Sprintf("code=%d", s.cfg.Test.Prefer[path].Code))
}

if s.cfg.Test.Prefer[path].Dynamic {
value = append(value, fmt.Sprintf("dynamic=%t", s.cfg.Test.Prefer[path].Dynamic))
}
if s.cfg.Test.Prefer[path].Dynamic {
value = append(value, fmt.Sprintf("dynamic=%t", s.cfg.Test.Prefer[path].Dynamic))
}

if s.cfg.Test.Prefer[path].Example != "" {
value = append(value, fmt.Sprintf("example=%s", s.cfg.Test.Prefer[path].Example))
}
if s.cfg.Test.Prefer[path].Example != "" {
value = append(value, fmt.Sprintf("example=%s", s.cfg.Test.Prefer[path].Example))
}

if len(value) > 0 {
req.Header.Set("Prefer", strings.Join(value, " "))
}
if len(value) > 0 {
req.Header.Set("Prefer", strings.Join(value, " "))
}

return s.RoundTripper.RoundTrip(req)
return next.RoundTrip(req)
})
}

type userAgentSetter struct {
http.RoundTripper
}
var userAgentSetter = MiddlewareFunc(func(next http.RoundTripper) http.RoundTripper {
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", cli.UserAgent())

func (s userAgentSetter) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", cli.UserAgent())
return next.RoundTrip(req)
})
})

return s.RoundTripper.RoundTrip(req)
}
var autoRetrier = MiddlewareFunc(func(next http.RoundTripper) http.RoundTripper {
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
res, err := next.RoundTrip(req)
if res == nil {
return res, err
}

switch res.StatusCode {
case http.StatusBadGateway, http.StatusServiceUnavailable:
// TODO: configure a backoff/sleep here?
return next.RoundTrip(req)
default:
return res, err
}
})
})

type mediaTypes []string

Expand Down Expand Up @@ -472,3 +513,14 @@ func gnomeKeyringMissing(cfg *cli.Config) bool {
}
return true
}

type Filter[T any] func(s []T) []T

type Filters[T any] []Filter[T]

func (f Filters[T]) Apply(s []T) []T {
for _, fn := range f {
s = fn(s)
}
return s
}
36 changes: 26 additions & 10 deletions api/apitest/apitest.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ type Server struct {
URL string
RailsPort string

proxy bool
verbose bool

stopfn func()
waitfn func()
}
Expand All @@ -44,10 +41,30 @@ func (s *Server) Close() {
s.waitfn()
}

func (s *Server) IsMock() bool {
return !proxy
}

func (s *Server) IsProxy() bool {
return proxy
}

func (s *Server) RecreateUser(username string) error {
if !s.IsProxy() {
return nil
}

cmd := exec.Command("script/clitest-recreate-user", username)
cmd.Dir = s.RootDir

_, err := cmd.Output()
if err != nil {
return err
}

return nil
}

func (s *Server) GeneratePAT(email string) (string, error) {
if !s.IsProxy() {
return "test-token", nil
Expand Down Expand Up @@ -97,7 +114,7 @@ func (s *Server) StartMock(ctx context.Context) error {

s.URL = "http://" + host + ":" + port + "/v0"
s.stopfn = stopfn
s.waitfn = func() { waitfn() }
s.waitfn = func() { _ = waitfn() }

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

addrRails, waitRails, err := s.startRails(ctx)
if err != nil {
lock.Unlock()
_ = lock.Unlock()
stopfn()
return err
}
Expand All @@ -131,7 +148,7 @@ func (s *Server) StartProxy(ctx context.Context) error {

addr, waitPrism, err := s.startProxy(ctx, host+":"+port)
if err != nil {
lock.Unlock()
_ = lock.Unlock()
stopfn()
return err
}
Expand All @@ -142,7 +159,7 @@ func (s *Server) StartProxy(ctx context.Context) error {
group.Go(func() error { return s.waitTCP(addrRails) })

if err := group.Wait(); err != nil {
lock.Unlock()
_ = lock.Unlock()
stopfn()
return err
}
Expand All @@ -165,9 +182,8 @@ func (s *Server) StartProxy(ctx context.Context) error {
s.URL = "http://" + host + ":" + port + "/v0"
s.stopfn = stopfn
s.waitfn = func() {
defer lock.Unlock()

group.Wait()
_ = group.Wait()
_ = lock.Unlock()
}

return nil
Expand Down
2 changes: 1 addition & 1 deletion api/openapi.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions api/openapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package api

import "slices"

func (o Organization) Key() string { return o.Apid }
func (o Organization) String() string { return o.Name }
func (o Organization) Plural() string { return "organizations" }
func (o Organization) Singular() string { return "organization" }

func (r Realm) Key() string { return r.Apid }
func (r Realm) String() string { return r.Name }
func (r Realm) Plural() string { return "realms" }
func (r Realm) Singular() string { return "realm" }

func (s Service) Key() string { return s.Slug }
func (s Service) String() string { return s.Name }
func (s Service) Plural() string { return "services" }
func (s Service) Singular() string { return "service" }

func NonDiagnosticServices(s []Service) []Service {
return slices.DeleteFunc(s, func(svc Service) bool {
return svc.ServerType == ServiceServerTypeDiagnostic
})
}

var _ Filter[Service] = NonDiagnosticServices
3 changes: 0 additions & 3 deletions auth/signin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@ func TestCmdAuthSignin(t *testing.T) {
func TestSignIn(t *testing.T) {
t.Run("cli-auth-success", func(t *testing.T) {
t.Skip("cli auth test not yet implemented")
return
})

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

t.Run("invalid-config-token", func(t *testing.T) {
t.Skip("cli auth test not yet implemented")
return
})
}
2 changes: 2 additions & 0 deletions auth/testdata/TestSignout/signed-out.golden
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
─── SignOutHeader ──────────────────────────────────────────────────────────────

# Signout from Anchor.dev `anchor auth signout`
─── SignOutSignedOut ───────────────────────────────────────────────────────────

# Signout from Anchor.dev `anchor auth signout`
- Not signed in.
| Run `anchor auth signin` to sign in.
Loading

0 comments on commit caba8dc

Please sign in to comment.