feat(state): introduce cache e.g. to skip GitHub API requests for the same version information over and over again

This commit is contained in:
Peter 2023-05-02 19:11:12 +02:00
parent fee941a0e4
commit 174ce3f39a
No known key found for this signature in database
9 changed files with 81 additions and 14 deletions

View file

@ -220,7 +220,11 @@ func (a *App) initBasic(ctx context.Context) (err error) {
return err
}
return a.Collection.With(services.WithVault(v), services.WithStateStore(s))
return a.Collection.With(
services.WithVault(v),
services.WithStateStore(s),
services.WithCache(state.NewStateCache(a.appCfg.Cache.TTL, s)),
)
}
func (a *App) RunModule(ctx context.Context, cat modules.Category, name string) error {

View file

@ -29,7 +29,7 @@ func MockContext() *hcl.EvalContext {
string_helpers.RegisterInContext(evalctx)
hclhelpers.RegisterInContext(evalctx)
vaultHelpers.RegisterInContext(evalctx, vaultHelpers.MockGetter("<mocked>"))
github.RegisterInContext(context.Background(), evalctx, nil)
github.RegisterInContext(context.Background(), evalctx, nil, nil)
return evalctx
}
@ -65,7 +65,7 @@ func FullContext(ctx context.Context, parent *hcl.EvalContext, svc *services.Col
evalctx.Variables = make(map[string]cty.Value)
}
github.RegisterInContext(ctx, evalctx, svc.GitHubClient())
github.RegisterInContext(ctx, evalctx, svc.GitHubClient(), svc.Cache())
return evalctx
}

View file

@ -52,6 +52,13 @@ func WithStateStore(store state.Store) CollectionOption {
})
}
func WithCache(cache state.Cache) CollectionOption {
return collectionOptionFunc(func(svc *Collection) error {
svc.cache = cache
return nil
})
}
func WithDockerClientFromEnv(ctx context.Context) CollectionOption {
return collectionOptionFunc(func(svc *Collection) error {
cli, err := client.NewClientWithOpts(
@ -100,6 +107,7 @@ type Collection struct {
dockerClient *client.Client
ignorer *ignore.Ignorer
stateStore state.Store
cache state.Cache
}
func (c *Collection) With(opts ...CollectionOption) error {
@ -116,6 +124,10 @@ func (c *Collection) StateStore() state.Store {
return c.stateStore
}
func (c *Collection) Cache() state.Cache {
return c.cache
}
func (c *Collection) GitHubClient() *gh.Client {
return c.ghClient
}

View file

@ -4,6 +4,8 @@ import (
"context"
"time"
"code.icb4dc0.de/buildr/buildr/modules/state"
"github.com/google/go-github/v50/github"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
@ -11,7 +13,7 @@ import (
const DefaultAPITimeout = 30 * time.Second
func GetLatestReleaseTag(ctx context.Context, cli *github.Client) function.Function {
func GetLatestReleaseTag(ctx context.Context, cli *github.Client, cache state.Cache) function.Function {
return function.New(&function.Spec{
Description: "Get tag of latest release of Github project",
Params: []function.Parameter{
@ -31,6 +33,15 @@ func GetLatestReleaseTag(ctx context.Context, cli *github.Client) function.Funct
owner := args[0].AsString()
repo := args[1].AsString()
cacheKey := state.KeyOfStrings("latest_github_release", owner, repo)
if cache != nil {
val, _, err := cache.Get(ctx, cacheKey)
if err == nil && len(val) > 0 {
return cty.StringVal(string(val)), nil
}
}
if cli == nil {
return cty.StringVal("<mocked>"), nil
}
@ -43,6 +54,8 @@ func GetLatestReleaseTag(ctx context.Context, cli *github.Client) function.Funct
return cty.Value{}, err
}
_ = cache.Set(ctx, cacheKey, []byte(release.GetTagName()))
return cty.StringVal(release.GetTagName()), nil
},
})

View file

@ -3,10 +3,12 @@ package github
import (
"context"
"code.icb4dc0.de/buildr/buildr/modules/state"
"github.com/google/go-github/v50/github"
"github.com/hashicorp/hcl/v2"
)
func RegisterInContext(ctx context.Context, evalCtx *hcl.EvalContext, cli *github.Client) {
evalCtx.Functions["gh_latest_release"] = GetLatestReleaseTag(ctx, cli)
func RegisterInContext(ctx context.Context, evalCtx *hcl.EvalContext, cli *github.Client, cache state.Cache) {
evalCtx.Functions["gh_latest_release"] = GetLatestReleaseTag(ctx, cli, cache)
}

View file

@ -32,3 +32,12 @@ type StoreWriter interface {
type StoreReader interface {
Get(ctx context.Context, key Key) (state []byte, meta Metadata, err error)
}
type CacheWriter interface {
Set(ctx context.Context, key Key, state []byte) error
}
type Cache interface {
CacheWriter
StoreReader
}

View file

@ -12,12 +12,8 @@ func (f EntryOptionFunc) applyToEntry(e *ent.KVEntryCreate) {
f(e)
}
func WithTTL(t time.Time) EntryOption {
func WithTTL(ttl time.Duration) EntryOption {
return EntryOptionFunc(func(e *ent.KVEntryCreate) {
if t.Location() != time.UTC {
t = t.UTC()
}
e.SetTTL(t)
e.SetTTL(time.Now().UTC().Add(ttl))
})
}

View file

@ -0,0 +1,31 @@
package state
import (
"context"
"time"
)
func NewStateCache(ttl time.Duration, store Store) *StateCache {
return &StateCache{
TTL: ttl,
Store: store,
}
}
var _ Cache = (*StateCache)(nil)
type StateCache struct {
TTL time.Duration
Store Store
}
func (s *StateCache) Set(ctx context.Context, key Key, state []byte) error {
return s.Store.Set(ctx, key, state, WithTTL(s.TTL))
}
func (s *StateCache) Get(ctx context.Context, key Key) (state []byte, meta Metadata, err error) {
if s == nil {
return nil, Metadata{}, nil
}
return s.Store.Get(ctx, key)
}

View file

@ -28,7 +28,7 @@ func NewEntStore(ctx context.Context, stateFilePath string) (*EntStore, error) {
_, err = client.KVEntry.
Delete().
Where(kventry.TTLGT(time.Now().UTC())).
Where(kventry.TTLLTE(time.Now().UTC())).
Exec(ctx)
if err != nil {
@ -63,7 +63,7 @@ func (s *EntStore) Get(ctx context.Context, key Key) (state []byte, meta Metadat
return nil, Metadata{}, err
}
if kvEntry.TTL != nil && kvEntry.TTL.After(time.Now().UTC()) {
if kvEntry.TTL != nil && kvEntry.TTL.Before(time.Now().UTC()) {
return nil, Metadata{}, nil
}