feat: add end 2 end tests

This commit is contained in:
Jon Lundy 2023-01-25 10:35:09 -07:00
parent f9a088269c
commit 0f504a98e9
Signed by untrusted user who does not match committer: xuu
GPG Key ID: C63E6D61F3035024
13 changed files with 268 additions and 230 deletions

46
app/webfinger/client.go Normal file
View File

@ -0,0 +1,46 @@
package webfinger
import (
"crypto/ed25519"
"encoding/base64"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/oklog/ulid/v2"
)
var (
defaultExpire = 30 * time.Minute
defaultIssuer = "sour.is/webfinger"
defaultAudience = "sour.is/webfinger"
)
func NewSignedRequest(jrd *JRD, key ed25519.PrivateKey) (string, error) {
type claims struct {
PubKey string `json:"pub"`
*JRD
jwt.RegisteredClaims
}
pub := []byte(key.Public().(ed25519.PublicKey))
j := claims{
PubKey: enc(pub),
JRD: jrd.CloneValues(),
RegisteredClaims: jwt.RegisteredClaims{
ID: ulid.Make().String(),
Subject: jrd.Subject,
Audience: jwt.ClaimStrings{defaultAudience},
ExpiresAt: jwt.NewNumericDate(time.Now().Add(defaultExpire)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: defaultIssuer,
},
}
j.JRD.Subject = "" // move subject into registered claims.
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, &j)
return token.SignedString(key)
}
func enc(b []byte) string {
return base64.RawURLEncoding.EncodeToString(b)
}

View File

@ -32,6 +32,19 @@ type JRD struct {
event.AggregateRoot `yaml:"-"` event.AggregateRoot `yaml:"-"`
} }
func (a *JRD) CloneValues() *JRD {
m := make(map[string]*string, len(a.Properties))
for k,v := range a.Properties {
m[k] = v
}
return &JRD{
Subject: a.Subject,
Aliases: append([]string{}, a.Aliases...),
Properties: m,
Links: append([]*Link{}, a.Links...),
}
}
var _ event.Aggregate = (*JRD)(nil) var _ event.Aggregate = (*JRD)(nil)
// Link is a link to a related resource. // Link is a link to a related resource.

View File

@ -20,8 +20,9 @@ import (
) )
type service struct { type service struct {
es *ev.EventStore es *ev.EventStore
self set.Set[string] self set.Set[string]
cache func(string) bool
} }
type Option interface { type Option interface {
@ -34,6 +35,12 @@ func (o WithHostnames) ApplyWebfinger(s *service) {
s.self = set.New(o...) s.self = set.New(o...)
} }
type WithCache func(string) bool
func (o WithCache) ApplyWebfinger(s *service) {
s.cache = o
}
func New(ctx context.Context, es *ev.EventStore, opts ...Option) (*service, error) { func New(ctx context.Context, es *ev.EventStore, opts ...Option) (*service, error) {
ctx, span := lg.Span(ctx) ctx, span := lg.Span(ctx)
defer span.End() defer span.End()
@ -91,10 +98,9 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Body.Close() r.Body.Close()
type claims struct { type claims struct {
Subject string `json:"sub"` PubKey string `json:"pub"`
PubKey string `json:"pub"`
*JRD *JRD
jwt.StandardClaims jwt.RegisteredClaims
} }
token, err := jwt.ParseWithClaims( token, err := jwt.ParseWithClaims(
@ -106,8 +112,7 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return nil, fmt.Errorf("wrong type of claim") return nil, fmt.Errorf("wrong type of claim")
} }
c.JRD.Subject = c.Subject c.JRD.Subject = c.RegisteredClaims.Subject
c.StandardClaims.Subject = c.Subject
c.SetProperty(NSpubkey, &c.PubKey) c.SetProperty(NSpubkey, &c.PubKey)
@ -134,7 +139,17 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
a, err := ev.Upsert(ctx, s.es, StreamID(c.Subject), func(ctx context.Context, a *JRD) error { if c.ID != "" && s.cache != nil {
if ok := s.cache(c.ID); ok {
w.WriteHeader(http.StatusAlreadyReported)
fmt.Fprint(w, http.StatusText(http.StatusAlreadyReported))
span.AddEvent("already seen ID")
return
}
}
a, err := ev.Upsert(ctx, s.es, StreamID(c.JRD.Subject), func(ctx context.Context, a *JRD) error {
var auth *JRD var auth *JRD
// does the target have a pubkey for self auth? // does the target have a pubkey for self auth?
@ -211,9 +226,16 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
resource := r.URL.Query().Get("resource") resource := r.URL.Query().Get("resource")
rels := r.URL.Query()["rel"] rels := r.URL.Query()["rel"]
if u := Parse(resource); u != nil && !s.self.Has(u.URL.Hostname()) { if resource == "" {
w.WriteHeader(http.StatusBadRequest)
return
}
host, _ := splitHostPort(r.Host)
if u := Parse(resource); u != nil && !s.self.Has(host) {
redirect := &url.URL{} redirect := &url.URL{}
redirect.Scheme = "https" redirect.Scheme = u.URL.Scheme
redirect.Host = u.URL.Host redirect.Host = u.URL.Host
redirect.RawQuery = r.URL.RawQuery redirect.RawQuery = r.URL.RawQuery
redirect.Path = "/.well-known/webfinger" redirect.Path = "/.well-known/webfinger"
@ -279,3 +301,31 @@ func dec(s string) ([]byte, error) {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
return base64.RawURLEncoding.DecodeString(s) return base64.RawURLEncoding.DecodeString(s)
} }
func splitHostPort(hostPort string) (host, port string) {
host = hostPort
colon := strings.LastIndexByte(host, ':')
if colon != -1 && validOptionalPort(host[colon:]) {
host, port = host[:colon], host[colon+1:]
}
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return
}
func validOptionalPort(port string) bool {
if port == "" {
return true
}
if port[0] != ':' {
return false
}
for _, b := range port[1:] {
if b < '0' || b > '9' {
return false
}
}
return true
}

View File

@ -4,7 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/patrickmn/go-cache"
"github.com/sour-is/ev" "github.com/sour-is/ev"
"github.com/sour-is/ev/app/webfinger" "github.com/sour-is/ev/app/webfinger"
"github.com/sour-is/ev/internal/lg" "github.com/sour-is/ev/internal/lg"
@ -13,6 +15,11 @@ import (
"github.com/sour-is/ev/pkg/slice" "github.com/sour-is/ev/pkg/slice"
) )
var (
defaultExpire = 3 * time.Minute
cleanupInterval = 10 * time.Minute
)
var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error { var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error {
ctx, span := lg.Span(ctx) ctx, span := lg.Span(ctx)
defer span.End() defer span.End()
@ -23,12 +30,17 @@ var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error
return fmt.Errorf("*es.EventStore not found in services") return fmt.Errorf("*es.EventStore not found in services")
} }
wf, err := webfinger.New( cache := cache.New(defaultExpire, cleanupInterval)
ctx, var withCache webfinger.WithCache = (func(s string) bool {
eventstore, if _, ok := cache.Get(s); ok {
webfinger.WithHostnames( return true
strings.Fields(env.Default("WEBFINGER_DOMAINS", "sour.is")), }
)) cache.SetDefault(s, true)
return false
})
var withHostnames webfinger.WithHostnames = strings.Fields(env.Default("WEBFINGER_DOMAINS", "sour.is"))
wf, err := webfinger.New(ctx, eventstore, withCache, withHostnames)
if err != nil { if err != nil {
span.RecordError(err) span.RecordError(err)
return err return err

View File

@ -21,17 +21,21 @@ func main() {
<-ctx.Done() <-ctx.Done()
defer cancel() // restore interrupt function defer cancel() // restore interrupt function
}() }()
if err := Run(ctx); err != nil {
log.Fatal(err)
os.Exit(1)
}
}
func Run(ctx context.Context) error {
svc := &service.Harness{} svc := &service.Harness{}
ctx, stop := lg.Init(ctx, appName) ctx, stop := lg.Init(ctx, appName)
svc.OnStop(stop) svc.OnStop(stop)
svc.Add(lg.NewHTTP(ctx)) svc.Add(lg.NewHTTP(ctx))
svc.Setup(ctx, apps.Apps()...) svc.Setup(ctx, apps.Apps()...)
// Run application // Run application
if err := svc.Run(ctx, appName, version); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := svc.Run(ctx, appName, version); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err) return err
} }
return nil
} }

View File

@ -14,10 +14,8 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/docopt/docopt-go" "github.com/docopt/docopt-go"
"github.com/golang-jwt/jwt"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/sour-is/ev/app/webfinger" "github.com/sour-is/ev/app/webfinger"
@ -127,23 +125,14 @@ func run(opts opts) error {
if err != nil { if err != nil {
return err return err
} }
bkey := []byte(key.Public().(ed25519.PublicKey))
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{ jrd := &webfinger.JRD{Subject: opts.Subject}
"sub": opts.Subject, token, err := webfinger.NewSignedRequest(jrd, key)
"subject": opts.Subject,
"pub": enc(bkey),
"exp": time.Now().Add(90 * time.Minute).Unix(),
"iat": time.Now().Unix(),
"aud": "webfinger",
"iss": "sour.is-webfingerCLI",
})
aToken, err := token.SignedString(key)
if err != nil { if err != nil {
return err return err
} }
body := strings.NewReader(aToken) body := strings.NewReader(token)
req, err := http.NewRequest(http.MethodDelete, url.String(), body) req, err := http.NewRequest(http.MethodDelete, url.String(), body)
if err != nil { if err != nil {
return err return err
@ -173,7 +162,6 @@ func run(opts opts) error {
if err != nil { if err != nil {
return err return err
} }
bkey := []byte(key.Public().(ed25519.PublicKey))
fmt.Fprintln(os.Stderr, opts.File) fmt.Fprintln(os.Stderr, opts.File)
fp, err := os.Open(opts.File) fp, err := os.Open(opts.File)
@ -182,40 +170,20 @@ func run(opts opts) error {
} }
y := yaml.NewDecoder(fp) y := yaml.NewDecoder(fp)
type claims struct {
Subject string `json:"sub"`
PubKey string `json:"pub"`
*webfinger.JRD
jwt.StandardClaims
}
for err == nil { for err == nil {
j := claims{ jrd := &webfinger.JRD{}
PubKey: enc(bkey),
JRD: &webfinger.JRD{},
StandardClaims: jwt.StandardClaims{
Audience: "sour.is-webfinger",
ExpiresAt: time.Now().Add(30 * time.Minute).Unix(),
IssuedAt: time.Now().Unix(),
Issuer: "sour.is-webfingerCLI",
},
}
err = y.Decode(j.JRD) err = y.Decode(jrd)
if err != nil { if err != nil {
break break
} }
j.Subject = j.JRD.Subject token, err := webfinger.NewSignedRequest(jrd, key)
j.StandardClaims.Subject = j.JRD.Subject
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, &j)
aToken, err := token.SignedString(key)
if err != nil { if err != nil {
return err return err
} }
body := strings.NewReader(aToken) body := strings.NewReader(token)
req, err := http.NewRequest(http.MethodPut, url.String(), body) req, err := http.NewRequest(http.MethodPut, url.String(), body)
if err != nil { if err != nil {

View File

@ -1,32 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/sour-is/ev"
"github.com/sour-is/ev/app/webfinger"
"github.com/sour-is/ev/internal/lg"
"github.com/sour-is/ev/pkg/service"
"github.com/sour-is/ev/pkg/slice"
)
var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error {
ctx, span := lg.Span(ctx)
defer span.End()
span.AddEvent("Enable WebFinger")
eventstore, ok := slice.Find[*ev.EventStore](svc.Services...)
if !ok {
return fmt.Errorf("*es.EventStore not found in services")
}
wf, err := webfinger.New(ctx, eventstore)
if err != nil {
span.RecordError(err)
return err
}
svc.Add(wf)
return nil
})

View File

@ -0,0 +1 @@
../ev/app.webfinger.go

View File

@ -1,37 +0,0 @@
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"github.com/sour-is/ev/internal/lg"
"github.com/sour-is/ev/pkg/service"
)
var apps service.Apps
var appName, version = service.AppName()
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
go func() {
<-ctx.Done()
defer cancel() // restore interrupt function
}()
svc := &service.Harness{}
ctx, stop := lg.Init(ctx, appName)
svc.OnStop(stop)
svc.Add(lg.NewHTTP(ctx))
svc.Setup(ctx, apps.Apps()...)
// Run application
if err := svc.Run(ctx, appName, version); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}

1
cmd/webfinger/main.go Symbolic link
View File

@ -0,0 +1 @@
../ev/main.go

View File

@ -1,53 +0,0 @@
package main
import (
"context"
"github.com/sour-is/ev"
"github.com/sour-is/ev/internal/lg"
"github.com/sour-is/ev/pkg/env"
"github.com/sour-is/ev/pkg/es"
diskstore "github.com/sour-is/ev/pkg/es/driver/disk-store"
memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
"github.com/sour-is/ev/pkg/es/driver/projecter"
resolvelinks "github.com/sour-is/ev/pkg/es/driver/resolve-links"
"github.com/sour-is/ev/pkg/es/driver/streamer"
"github.com/sour-is/ev/pkg/es/event"
"github.com/sour-is/ev/pkg/service"
"go.uber.org/multierr"
)
var _ = apps.Register(10, func(ctx context.Context, svc *service.Harness) error {
ctx, span := lg.Span(ctx)
defer span.End()
// setup eventstore
err := multierr.Combine(
ev.Init(ctx),
event.Init(ctx),
diskstore.Init(ctx),
memstore.Init(ctx),
)
if err != nil {
span.RecordError(err)
return err
}
eventstore, err := ev.Open(
ctx,
env.Default("EV_DATA", "mem:"),
resolvelinks.New(),
streamer.New(ctx),
projecter.New(
ctx,
projecter.DefaultProjection,
),
)
if err != nil {
span.RecordError(err)
return err
}
svc.Add(eventstore, &es.EventStore{EventStore: eventstore})
return nil
})

1
cmd/webfinger/svc.es.go Symbolic link
View File

@ -0,0 +1 @@
../ev/svc.es.go

View File

@ -1,46 +0,0 @@
package main
import (
"context"
"log"
"net/http"
"strings"
"github.com/rs/cors"
"github.com/sour-is/ev/internal/lg"
"github.com/sour-is/ev/pkg/env"
"github.com/sour-is/ev/pkg/mux"
"github.com/sour-is/ev/pkg/service"
"github.com/sour-is/ev/pkg/slice"
)
var _ = apps.Register(20, func(ctx context.Context, svc *service.Harness) error {
s := &http.Server{}
svc.Add(s)
mux := mux.New()
s.Handler = cors.AllowAll().Handler(mux)
// s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// log.Println(r.URL.Path)
// mux.ServeHTTP(w, r)
// })
s.Addr = env.Default("EV_HTTP", ":8080")
if strings.HasPrefix(s.Addr, ":") {
s.Addr = "[::]" + s.Addr
}
svc.OnStart(func(ctx context.Context) error {
_, span := lg.Span(ctx)
defer span.End()
log.Print("Listen on ", s.Addr)
span.AddEvent("begin listen and serve on " + s.Addr)
mux.Add(slice.FilterType[interface{ RegisterHTTP(*http.ServeMux) }](svc.Services...)...)
return s.ListenAndServe()
})
svc.OnStop(s.Shutdown)
return nil
})

1
cmd/webfinger/svc.http.go Symbolic link
View File

@ -0,0 +1 @@
../ev/svc.http.go

View File

@ -0,0 +1,108 @@
package main
import (
"context"
"crypto/ed25519"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"testing"
"github.com/matryer/is"
"github.com/sour-is/ev/app/webfinger"
"golang.org/x/sync/errgroup"
)
func TestMain(m *testing.M) {
data, err := os.MkdirTemp("", "data*")
if err != nil {
fmt.Printf("error creating data dir: %s\n", err)
os.Exit(1)
}
defer os.RemoveAll(data)
os.Setenv("EV_HTTP", "[::1]:61234")
os.Setenv("WEBFINGER_DOMAINS", "::1")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
wg, ctx := errgroup.WithContext(ctx)
wg.Go(func() error {
// Run application
if err := Run(ctx); err != nil {
return err
}
return nil
})
wg.Go(func() error {
m.Run()
cancel()
return nil
})
if err := wg.Wait(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func TestGetHTTP(t *testing.T) {
is := is.New(t)
res, err := http.DefaultClient.Get("http://[::1]:61234/.well-known/webfinger")
is.NoErr(err)
is.Equal(res.StatusCode, http.StatusBadRequest)
}
func TestCreateResource(t *testing.T) {
is := is.New(t)
_, priv, err := ed25519.GenerateKey(nil)
is.NoErr(err)
jrd := &webfinger.JRD{
Subject: "me@sour.is",
Properties: map[string]*string{
"foo": ptr("bar"),
},
}
// create
token, err := webfinger.NewSignedRequest(jrd, priv)
is.NoErr(err)
req, err := http.NewRequest(http.MethodPut, "http://[::1]:61234/.well-known/webfinger", strings.NewReader(token))
is.NoErr(err)
res, err := http.DefaultClient.Do(req)
is.NoErr(err)
is.Equal(res.StatusCode, http.StatusCreated)
// repeat
req, err = http.NewRequest(http.MethodPut, "http://[::1]:61234/.well-known/webfinger", strings.NewReader(token))
is.NoErr(err)
res, err = http.DefaultClient.Do(req)
is.NoErr(err)
is.Equal(res.StatusCode, http.StatusAlreadyReported)
// fetch
req, err = http.NewRequest(http.MethodGet, "http://[::1]:61234/.well-known/webfinger?resource=me@sour.is", nil)
is.NoErr(err)
res, err = http.DefaultClient.Do(req)
is.NoErr(err)
is.Equal(res.StatusCode, http.StatusOK)
resJRD := &webfinger.JRD{}
err = json.NewDecoder(res.Body).Decode(resJRD)
is.NoErr(err)
is.Equal(jrd.Subject, resJRD.Subject)
}
func ptr[T any](t T) *T { return &t }

3
go.mod
View File

@ -24,6 +24,8 @@ require (
require github.com/tj/go-semver v1.0.0 require github.com/tj/go-semver v1.0.0
require github.com/patrickmn/go-cache v2.1.0+incompatible
require ( require (
git.mills.io/prologic/msgbus v0.1.19 // indirect git.mills.io/prologic/msgbus v0.1.19 // indirect
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 // indirect github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 // indirect
@ -101,7 +103,6 @@ require (
require ( require (
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v4 v4.4.3 github.com/golang-jwt/jwt/v4 v4.4.3
github.com/keys-pub/keys v0.1.22 github.com/keys-pub/keys v0.1.22
github.com/matryer/is v1.4.0 github.com/matryer/is v1.4.0

4
go.sum
View File

@ -155,8 +155,6 @@ github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -328,6 +326,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0= github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0=