diff --git a/client.go b/client.go index 9b58d3b..0d70356 100644 --- a/client.go +++ b/client.go @@ -12,6 +12,7 @@ import ( "time" "github.com/go-resty/resty/v2" + "github.com/hashicorp/golang-lru/v2/expirable" api "github.com/infisical/go-sdk/packages/api/auth" "github.com/infisical/go-sdk/packages/models" "github.com/infisical/go-sdk/packages/util" @@ -26,6 +27,8 @@ type InfisicalClient struct { mu sync.RWMutex + cache *expirable.LRU[string, interface{}] + httpClient *resty.Client config Config @@ -48,11 +51,12 @@ type InfisicalClientInterface interface { } type Config struct { - SiteUrl string `default:"https://app.infisical.com"` - CaCertificate string - UserAgent string `default:"infisical-go-sdk"` // User-Agent header to be used on requests sent by the SDK. Defaults to `infisical-go-sdk`. Do not modify this unless you have a reason to do so. - AutoTokenRefresh bool `default:"true"` // Wether or not to automatically refresh the auth token after using one of the .Auth() methods. Defaults to `true`. - SilentMode bool `default:"false"` // If enabled, the SDK will not print any warnings to the console. + SiteUrl string `default:"https://app.infisical.com"` + CaCertificate string + UserAgent string `default:"infisical-go-sdk"` // User-Agent header to be used on requests sent by the SDK. Defaults to `infisical-go-sdk`. Do not modify this unless you have a reason to do so. + AutoTokenRefresh bool `default:"true"` // Wether or not to automatically refresh the auth token after using one of the .Auth() methods. Defaults to `true`. + SilentMode bool `default:"false"` // If enabled, the SDK will not print any warnings to the console. + CacheExpiryInSeconds int // Defines how long certain API responses should be cached in memory, in seconds. When set to a positive value, responses from specific fetch API requests (like secret fetching) will be cached for this duration. Set to 0 to disable caching. Defaults to 0. } func setDefaults(cfg *Config) { @@ -123,6 +127,10 @@ func NewInfisicalClient(context context.Context, config Config) InfisicalClientI client.dynamicSecrets = NewDynamicSecrets(client) client.kms = NewKms(client) client.ssh = NewSsh(client) + if config.CacheExpiryInSeconds != 0 { + // hard limit set at 1000 cache items until forced eviction + client.cache = expirable.NewLRU[string, interface{}](1000, nil, time.Second*time.Duration(config.CacheExpiryInSeconds)) + } if config.AutoTokenRefresh { go client.handleTokenLifeCycle(context) diff --git a/go.mod b/go.mod index 51b53ea..4584371 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.27.2 github.com/aws/aws-sdk-go-v2/config v1.27.18 github.com/go-resty/resty/v2 v2.13.1 + github.com/hashicorp/golang-lru/v2 v2.0.7 google.golang.org/api v0.188.0 ) diff --git a/go.sum b/go.sum index 1fbd096..888fdd4 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/packages/api/secrets/list_secrets.go b/packages/api/secrets/list_secrets.go index d6aa98e..ea7d35f 100644 --- a/packages/api/secrets/list_secrets.go +++ b/packages/api/secrets/list_secrets.go @@ -1,16 +1,33 @@ package api import ( + "encoding/json" "fmt" "github.com/go-resty/resty/v2" + "github.com/hashicorp/golang-lru/v2/expirable" "github.com/infisical/go-sdk/packages/errors" + "github.com/infisical/go-sdk/packages/util" ) const callListSecretsV3RawOperation = "CallListSecretsV3Raw" -func CallListSecretsV3(httpClient *resty.Client, request ListSecretsV3RawRequest) (ListSecretsV3RawResponse, error) { - +func CallListSecretsV3(cache *expirable.LRU[string, interface{}], httpClient *resty.Client, request ListSecretsV3RawRequest) (ListSecretsV3RawResponse, error) { + var cacheKey string + + if cache != nil { + reqBytes, err := json.Marshal(request) + if err != nil { + return ListSecretsV3RawResponse{}, err + } + cacheKey = util.ComputeCacheKeyFromBytes(reqBytes) + if cached, found := cache.Get(cacheKey); found { + if response, ok := cached.(ListSecretsV3RawResponse); ok { + return response, nil + } + cache.Remove(cacheKey) + } + } secretsResponse := ListSecretsV3RawResponse{} if request.SecretPath == "" { @@ -37,5 +54,9 @@ func CallListSecretsV3(httpClient *resty.Client, request ListSecretsV3RawRequest return ListSecretsV3RawResponse{}, errors.NewAPIErrorWithResponse(callListSecretsV3RawOperation, res) } + if cache != nil { + cache.Add(cacheKey, secretsResponse) + } + return secretsResponse, nil } diff --git a/packages/models/secrets.go b/packages/models/secrets.go index ee7228c..4d450c8 100644 --- a/packages/models/secrets.go +++ b/packages/models/secrets.go @@ -1,15 +1,21 @@ package models +type SecretMetadata struct { + Key string `json:"key"` + Value string `json:"value"` +} + type Secret struct { - ID string `json:"id"` - Workspace string `json:"workspace"` - Environment string `json:"environment"` - Version int `json:"version"` - Type string `json:"type"` - SecretKey string `json:"secretKey"` - SecretValue string `json:"secretValue"` - SecretComment string `json:"secretComment"` - SecretPath string `json:"secretPath,omitempty"` + ID string `json:"id"` + Workspace string `json:"workspace"` + Environment string `json:"environment"` + Version int `json:"version"` + Type string `json:"type"` + SecretKey string `json:"secretKey"` + SecretValue string `json:"secretValue"` + SecretComment string `json:"secretComment"` + SecretPath string `json:"secretPath,omitempty"` + SecretMetadata []SecretMetadata `json:"secretMetadata"` } type SecretImport struct { diff --git a/packages/util/helper.go b/packages/util/helper.go index d45c345..6605b4b 100644 --- a/packages/util/helper.go +++ b/packages/util/helper.go @@ -2,6 +2,8 @@ package util import ( "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" "os" @@ -103,3 +105,8 @@ func SleepWithContext(ctx context.Context, duration time.Duration) error { return nil } } + +func ComputeCacheKeyFromBytes(bytes []byte) string { + key := sha256.Sum256(bytes) + return hex.EncodeToString(key[:]) +} diff --git a/secrets.go b/secrets.go index 372dfbd..c697956 100644 --- a/secrets.go +++ b/secrets.go @@ -27,7 +27,7 @@ type Secrets struct { } func (s *Secrets) List(options ListSecretsOptions) ([]models.Secret, error) { - res, err := api.CallListSecretsV3(s.client.httpClient, options) + res, err := api.CallListSecretsV3(s.client.cache, s.client.httpClient, options) if err != nil { return nil, err