func Expired(currentTime time.Time, creationTimestamp int64, ttl time.Duration, jitterMaxPercentage uint) bool {
	rand.Seed(time.Now().UTC().UnixNano())
	jitterPercentage := float64(jitterMaxPercentage) / 100
	randomFactor := ((rand.Float64() * 2) - 1)
	ttlMultiplier := (jitterPercentage * randomFactor) + 1
	ttlWithJitter := time.Duration(ttl.Seconds()*ttlMultiplier) * time.Second
	return currentTime.Sub(time.Unix(creationTimestamp, 0)) > ttlWithJitter
}
Expired checks, if for a given current time, object creation timestamp and TTL, object should be
considered as expired (TTL has been exceeded).

If jitter max percentage is not zero, TTL will be either increased or decreased randomly by maximum of selected
TTL percentage. This allows to distribute cache expiration in time to avoid all caches to expire at the same time
in multiple clients, e.g. in distributed systems. As an example:

For a TTL of 100 and jitter max percentage of 20, TTL will be within range of 80-120.

If jitter max percentage is 0, TTL remains as given.
package client
import (
	"math/rand"
	"net/http"
	"time"
	"github.com/newrelic/infra-integrations-sdk/log"
	"github.com/pkg/errors"
	"github.com/newrelic/nri-kubernetes/v2/src/storage"
)
type DiscoveryCacher struct {
	DiscoveryCacherConfig
	Discoverer Discoverer
	Compose    Composer
	Decompose  Decomposer
	CachedDataPtr interface{}
	StorageKey string
}
type DiscoveryCacherConfig struct {
	Storage   storage.Storage
	TTL       time.Duration
	TTLJitter uint
	Logger    log.Logger
}
type Decomposer func(source HTTPClient) (interface{}, error)
type Composer func(source interface{}, cacher *DiscoveryCacher, timeout time.Duration) (HTTPClient, error)
func (d *DiscoveryCacher) Discover(timeout time.Duration) (HTTPClient, error) {
	creationTimestamp, err := d.Storage.Read(d.StorageKey, d.CachedDataPtr)
	if err != nil {
		d.Logger.Debugf("Cached %q not found. Triggering discovery process", d.StorageKey)
		return d.discoverAndCache(timeout)
	}
	d.Logger.Debugf("Found cached copy of %q stored at %s", d.StorageKey, time.Unix(creationTimestamp, 0))
	if Expired(time.Now(), creationTimestamp, d.TTL, d.TTLJitter) {
		d.Logger.Debugf("Cached copy of %q expired. Refreshing", d.StorageKey)
		return d.discoverAndCache(timeout)
	}
	wrappedClient, err := d.Compose(d.CachedDataPtr, d, timeout)
	if err != nil {
		return nil, err
	}
	return d.wrap(wrappedClient, timeout), nil
}
func (d *DiscoveryCacher) discoverAndCache(timeout time.Duration) (HTTPClient, error) {
	client, err := d.Discoverer.Discover(timeout)
	if err != nil {
		return nil, err
	}
	toCache, err := d.Decompose(client)
	if err == nil {
		err = d.Storage.Write(d.StorageKey, toCache)
	}
	if err != nil {
		d.Logger.Warnf("Could not store %q in the cache: %v", d.StorageKey, err)
	}
	return d.wrap(client, timeout), nil
}