github.com/argoproj/argo-cd/v3@v3.2.1/reposerver/cache/cache.go (about) 1 package cache 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "hash/fnv" 9 "math" 10 "sort" 11 "strings" 12 "time" 13 14 "github.com/argoproj/gitops-engine/pkg/utils/text" 15 "github.com/go-git/go-git/v5/plumbing" 16 log "github.com/sirupsen/logrus" 17 "github.com/spf13/cobra" 18 19 appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 20 "github.com/argoproj/argo-cd/v3/reposerver/apiclient" 21 cacheutil "github.com/argoproj/argo-cd/v3/util/cache" 22 "github.com/argoproj/argo-cd/v3/util/env" 23 "github.com/argoproj/argo-cd/v3/util/hash" 24 ) 25 26 var ( 27 ErrCacheMiss = cacheutil.ErrCacheMiss 28 ErrCacheKeyLocked = cacheutil.ErrCacheKeyLocked 29 ) 30 31 type Cache struct { 32 cache *cacheutil.Cache 33 repoCacheExpiration time.Duration 34 revisionCacheExpiration time.Duration 35 revisionCacheLockTimeout time.Duration 36 } 37 38 // ClusterRuntimeInfo holds cluster runtime information 39 type ClusterRuntimeInfo interface { 40 // GetApiVersions returns supported api versions 41 GetApiVersions() []string 42 // GetKubeVersion returns cluster API version 43 GetKubeVersion() string 44 } 45 46 // CachedManifestResponse represents a cached result of a previous manifest generation operation, including the caching 47 // of a manifest generation error, plus additional information on previous failures 48 type CachedManifestResponse struct { 49 // NOTE: When adding fields to this struct, you MUST also update shallowCopy() 50 51 CacheEntryHash string `json:"cacheEntryHash"` 52 ManifestResponse *apiclient.ManifestResponse `json:"manifestResponse"` 53 MostRecentError string `json:"mostRecentError"` 54 FirstFailureTimestamp int64 `json:"firstFailureTimestamp"` 55 NumberOfConsecutiveFailures int `json:"numberOfConsecutiveFailures"` 56 NumberOfCachedResponsesReturned int `json:"numberOfCachedResponsesReturned"` 57 } 58 59 func NewCache(cache *cacheutil.Cache, repoCacheExpiration time.Duration, revisionCacheExpiration time.Duration, revisionCacheLockTimeout time.Duration) *Cache { 60 return &Cache{cache, repoCacheExpiration, revisionCacheExpiration, revisionCacheLockTimeout} 61 } 62 63 func AddCacheFlagsToCmd(cmd *cobra.Command, opts ...cacheutil.Options) func() (*Cache, error) { 64 var repoCacheExpiration time.Duration 65 var revisionCacheExpiration time.Duration 66 var revisionCacheLockTimeout time.Duration 67 68 cmd.Flags().DurationVar(&repoCacheExpiration, "repo-cache-expiration", env.ParseDurationFromEnv("ARGOCD_REPO_CACHE_EXPIRATION", 24*time.Hour, 0, math.MaxInt64), "Cache expiration for repo state, incl. app lists, app details, manifest generation, revision meta-data") 69 cmd.Flags().DurationVar(&revisionCacheExpiration, "revision-cache-expiration", env.ParseDurationFromEnv("ARGOCD_RECONCILIATION_TIMEOUT", 3*time.Minute, 0, math.MaxInt64), "Cache expiration for cached revision") 70 cmd.Flags().DurationVar(&revisionCacheLockTimeout, "revision-cache-lock-timeout", env.ParseDurationFromEnv("ARGOCD_REVISION_CACHE_LOCK_TIMEOUT", 10*time.Second, 0, math.MaxInt64), "Cache TTL for locks to prevent duplicate requests on revisions, set to 0 to disable") 71 72 repoFactory := cacheutil.AddCacheFlagsToCmd(cmd, opts...) 73 74 return func() (*Cache, error) { 75 cache, err := repoFactory() 76 if err != nil { 77 return nil, fmt.Errorf("error adding cache flags to cmd: %w", err) 78 } 79 return NewCache(cache, repoCacheExpiration, revisionCacheExpiration, revisionCacheLockTimeout), nil 80 } 81 } 82 83 type refTargetForCacheKey struct { 84 RepoURL string `json:"repoURL"` 85 Project string `json:"project"` 86 TargetRevision string `json:"targetRevision"` 87 Chart string `json:"chart"` 88 } 89 90 func refTargetForCacheKeyFromRefTarget(refTarget *appv1.RefTarget) refTargetForCacheKey { 91 return refTargetForCacheKey{ 92 RepoURL: refTarget.Repo.Repo, 93 Project: refTarget.Repo.Project, 94 TargetRevision: refTarget.TargetRevision, 95 Chart: refTarget.Chart, 96 } 97 } 98 99 type refTargetRevisionMappingForCacheKey map[string]refTargetForCacheKey 100 101 func getRefTargetRevisionMappingForCacheKey(refTargetRevisionMapping appv1.RefTargetRevisionMapping) refTargetRevisionMappingForCacheKey { 102 res := make(refTargetRevisionMappingForCacheKey) 103 for k, v := range refTargetRevisionMapping { 104 res[k] = refTargetForCacheKeyFromRefTarget(v) 105 } 106 return res 107 } 108 109 func appSourceKey(appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, refSourceCommitSHAs ResolvedRevisions) uint32 { 110 return hash.FNVa(appSourceKeyJSON(appSrc, srcRefs, refSourceCommitSHAs)) 111 } 112 113 // ResolvedRevisions is a map of "normalized git URL" -> "git commit SHA". When one source references another source, 114 // the referenced source revision may change, for example, when someone pushes a commit to the referenced branch. This 115 // map lets us keep track of the current revision for each referenced source. 116 type ResolvedRevisions map[string]string 117 118 type appSourceKeyStruct struct { 119 AppSrc *appv1.ApplicationSource `json:"appSrc"` 120 SrcRefs refTargetRevisionMappingForCacheKey `json:"srcRefs"` 121 ResolvedRevisions ResolvedRevisions `json:"resolvedRevisions,omitempty"` 122 } 123 124 func appSourceKeyJSON(appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, refSourceCommitSHAs ResolvedRevisions) string { 125 appSrc = appSrc.DeepCopy() 126 if !appSrc.IsHelm() { 127 appSrc.RepoURL = "" // superseded by commitSHA 128 appSrc.TargetRevision = "" // superseded by commitSHA 129 } 130 appSrcStr, _ := json.Marshal(appSourceKeyStruct{ 131 AppSrc: appSrc, 132 SrcRefs: getRefTargetRevisionMappingForCacheKey(srcRefs), 133 ResolvedRevisions: refSourceCommitSHAs, 134 }) 135 return string(appSrcStr) 136 } 137 138 func clusterRuntimeInfoKey(info ClusterRuntimeInfo) uint32 { 139 if info == nil { 140 return 0 141 } 142 key := clusterRuntimeInfoKeyUnhashed(info) 143 return hash.FNVa(key) 144 } 145 146 // clusterRuntimeInfoKeyUnhashed gets the cluster runtime info for a cache key, but does not hash the info. Does not 147 // check if info is nil, the caller must do that. 148 func clusterRuntimeInfoKeyUnhashed(info ClusterRuntimeInfo) string { 149 apiVersions := info.GetApiVersions() 150 sort.Slice(apiVersions, func(i, j int) bool { 151 return apiVersions[i] < apiVersions[j] 152 }) 153 return info.GetKubeVersion() + "|" + strings.Join(apiVersions, ",") 154 } 155 156 func listApps(repoURL, revision string) string { 157 return fmt.Sprintf("ldir|%s|%s", repoURL, revision) 158 } 159 160 func (c *Cache) ListApps(repoURL, revision string) (map[string]string, error) { 161 res := make(map[string]string) 162 err := c.cache.GetItem(listApps(repoURL, revision), &res) 163 return res, err 164 } 165 166 func (c *Cache) SetApps(repoURL, revision string, apps map[string]string) error { 167 return c.cache.SetItem( 168 listApps(repoURL, revision), 169 apps, 170 &cacheutil.CacheActionOpts{ 171 Expiration: c.repoCacheExpiration, 172 Delete: apps == nil, 173 }) 174 } 175 176 func helmIndexRefsKey(repo string) string { 177 return "helm-index|" + repo 178 } 179 180 func ociTagsKey(repo string) string { 181 return "oci-tags|" + repo 182 } 183 184 // SetHelmIndex stores helm repository index.yaml content to cache 185 func (c *Cache) SetHelmIndex(repo string, indexData []byte) error { 186 if indexData == nil { 187 // Logged as warning upstream 188 return errors.New("helm index data is nil, skipping cache") 189 } 190 return c.cache.SetItem( 191 helmIndexRefsKey(repo), 192 indexData, 193 &cacheutil.CacheActionOpts{Expiration: c.revisionCacheExpiration}) 194 } 195 196 // GetHelmIndex retrieves helm repository index.yaml content from cache 197 func (c *Cache) GetHelmIndex(repo string, indexData *[]byte) error { 198 return c.cache.GetItem(helmIndexRefsKey(repo), indexData) 199 } 200 201 // SetOCITags stores oci image tags to cache 202 func (c *Cache) SetOCITags(repo string, indexData []byte) error { 203 if indexData == nil { 204 // Logged as warning upstream 205 return errors.New("oci index data is nil, skipping cache") 206 } 207 return c.cache.SetItem( 208 ociTagsKey(repo), 209 indexData, 210 &cacheutil.CacheActionOpts{Expiration: c.revisionCacheExpiration}) 211 } 212 213 // GetOCITags retrieves oci image tags from cache 214 func (c *Cache) GetOCITags(repo string, indexData *[]byte) error { 215 return c.cache.GetItem(ociTagsKey(repo), indexData) 216 } 217 218 func gitRefsKey(repo string) string { 219 return "git-refs|" + repo 220 } 221 222 // SetGitReferences saves resolved Git repository references to cache 223 func (c *Cache) SetGitReferences(repo string, references []*plumbing.Reference) error { 224 var input [][2]string 225 for i := range references { 226 input = append(input, references[i].Strings()) 227 } 228 return c.cache.SetItem(gitRefsKey(repo), input, &cacheutil.CacheActionOpts{Expiration: c.revisionCacheExpiration}) 229 } 230 231 // Converts raw cache items to plumbing.Reference objects 232 func GitRefCacheItemToReferences(cacheItem [][2]string) *[]*plumbing.Reference { 233 var res []*plumbing.Reference 234 for i := range cacheItem { 235 // Skip empty data 236 if cacheItem[i][0] != "" || cacheItem[i][1] != "" { 237 res = append(res, plumbing.NewReferenceFromStrings(cacheItem[i][0], cacheItem[i][1])) 238 } 239 } 240 return &res 241 } 242 243 // TryLockGitRefCache attempts to lock the key for the Git repository references if the key doesn't exist, returns the value of 244 // GetGitReferences after calling the SET 245 func (c *Cache) TryLockGitRefCache(repo string, lockId string, references *[]*plumbing.Reference) (string, error) { 246 // This try set with DisableOverwrite is important for making sure that only one process is able to claim ownership 247 // A normal get + set, or just set would cause ownership to go to whoever the last writer was, and during race conditions 248 // leads to duplicate requests 249 err := c.cache.SetItem(gitRefsKey(repo), [][2]string{{cacheutil.CacheLockedValue, lockId}}, &cacheutil.CacheActionOpts{ 250 Expiration: c.revisionCacheLockTimeout, 251 DisableOverwrite: true, 252 }) 253 if err != nil { 254 // Log but ignore this error since we'll want to retry, failing to obtain the lock should not throw an error 255 log.Errorf("Error attempting to acquire git references cache lock: %v", err) 256 } 257 return c.GetGitReferences(repo, references) 258 } 259 260 // Retrieves the cache item for git repo references. Returns foundLockId, error 261 func (c *Cache) GetGitReferences(repo string, references *[]*plumbing.Reference) (string, error) { 262 var input [][2]string 263 err := c.cache.GetItem(gitRefsKey(repo), &input) 264 valueExists := len(input) > 0 && len(input[0]) > 0 265 switch { 266 // Unexpected Error 267 case err != nil && !errors.Is(err, ErrCacheMiss): 268 log.Errorf("Error attempting to retrieve git references from cache: %v", err) 269 return "", err 270 // Value is set 271 case valueExists && input[0][0] != cacheutil.CacheLockedValue: 272 *references = *GitRefCacheItemToReferences(input) 273 return "", nil 274 // Key is locked 275 case valueExists: 276 return input[0][1], nil 277 // No key or empty key 278 default: 279 return "", nil 280 } 281 } 282 283 // GetOrLockGitReferences retrieves the git references if they exist, otherwise creates a lock and returns so the caller can populate the cache 284 // Returns isLockOwner, localLockId, error 285 func (c *Cache) GetOrLockGitReferences(repo string, lockId string, references *[]*plumbing.Reference) (string, error) { 286 // Value matches the ttl on the lock in TryLockGitRefCache 287 waitUntil := time.Now().Add(c.revisionCacheLockTimeout) 288 // Wait only the maximum amount of time configured for the lock 289 // if the configured time is zero then the for loop will never run and instead act as the owner immediately 290 for time.Now().Before(waitUntil) { 291 // Get current cache state 292 if foundLockId, err := c.GetGitReferences(repo, references); foundLockId == lockId || err != nil || (references != nil && len(*references) > 0) { 293 return foundLockId, err 294 } 295 if foundLockId, err := c.TryLockGitRefCache(repo, lockId, references); foundLockId == lockId || err != nil || (references != nil && len(*references) > 0) { 296 return foundLockId, err 297 } 298 time.Sleep(1 * time.Second) 299 } 300 // If configured time is 0 then this is expected 301 if c.revisionCacheLockTimeout > 0 { 302 log.Debug("Repository cache was unable to acquire lock or valid data within timeout") 303 } 304 // Timeout waiting for lock 305 return lockId, nil 306 } 307 308 // UnlockGitReferences unlocks the key for the Git repository references if needed 309 func (c *Cache) UnlockGitReferences(repo string, lockId string) error { 310 var input [][2]string 311 var err error 312 if err = c.cache.GetItem(gitRefsKey(repo), &input); err == nil && 313 input != nil && 314 len(input) > 0 && 315 len(input[0]) > 1 && 316 input[0][0] == cacheutil.CacheLockedValue && 317 input[0][1] == lockId { 318 // We have the lock, so remove it 319 return c.cache.SetItem(gitRefsKey(repo), input, &cacheutil.CacheActionOpts{Delete: true}) 320 } 321 return err 322 } 323 324 // refSourceCommitSHAs is a list of resolved revisions for each ref source. This allows us to invalidate the cache 325 // when someone pushes a commit to a source which is referenced from the main source (the one referred to by `revision`). 326 func manifestCacheKey(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, namespace string, trackingMethod string, appLabelKey string, appName string, info ClusterRuntimeInfo, refSourceCommitSHAs ResolvedRevisions, installationID string) string { 327 // TODO: this function is getting unwieldy. We should probably consolidate some of this stuff into a struct. For 328 // example, revision could be part of ResolvedRevisions. And srcRefs is probably redundant now that 329 // refSourceCommitSHAs has been added. We don't need to know the _target_ revisions of the referenced sources 330 // when the _resolved_ revisions are already part of the key. 331 trackingKey := trackingKey(appLabelKey, trackingMethod) 332 key := fmt.Sprintf("mfst|%s|%s|%s|%s|%d", trackingKey, appName, revision, namespace, appSourceKey(appSrc, srcRefs, refSourceCommitSHAs)+clusterRuntimeInfoKey(info)) 333 if installationID != "" { 334 key = fmt.Sprintf("%s|%s", key, installationID) 335 } 336 return key 337 } 338 339 func trackingKey(appLabelKey string, trackingMethod string) string { 340 trackingKey := appLabelKey 341 if text.FirstNonEmpty(trackingMethod, string(appv1.TrackingMethodLabel)) != string(appv1.TrackingMethodLabel) { 342 trackingKey = trackingMethod + ":" + trackingKey 343 } 344 return trackingKey 345 } 346 347 // LogDebugManifestCacheKeyFields logs all the information included in a manifest cache key. It's intended to be run 348 // before every manifest cache operation to help debug cache misses. 349 func LogDebugManifestCacheKeyFields(message string, reason string, revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, refSourceCommitSHAs ResolvedRevisions) { 350 if log.IsLevelEnabled(log.DebugLevel) { 351 log.WithFields(log.Fields{ 352 "revision": revision, 353 "appSrc": appSourceKeyJSON(appSrc, srcRefs, refSourceCommitSHAs), 354 "namespace": namespace, 355 "trackingKey": trackingKey(appLabelKey, trackingMethod), 356 "appName": appName, 357 "clusterInfo": clusterRuntimeInfoKeyUnhashed(clusterInfo), 358 "reason": reason, 359 }).Debug(message) 360 } 361 } 362 363 func (c *Cache) SetNewRevisionManifests(newRevision string, revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, refSourceCommitSHAs ResolvedRevisions, installationID string) error { 364 oldKey := manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID) 365 newKey := manifestCacheKey(newRevision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID) 366 return c.cache.RenameItem(oldKey, newKey, c.repoCacheExpiration) 367 } 368 369 func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions, installationID string) error { 370 err := c.cache.GetItem(manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID), res) 371 if err != nil { 372 return err 373 } 374 375 hash, err := res.generateCacheEntryHash() 376 if err != nil { 377 return fmt.Errorf("unable to generate hash value: %w", err) 378 } 379 380 // If cached result does not have manifests or the expected hash of the cache entry does not match the actual hash value... 381 if hash != res.CacheEntryHash || res.ManifestResponse == nil && res.MostRecentError == "" { 382 log.Warnf("Manifest hash did not match expected value or cached manifests response is empty, treating as a cache miss: %s", appName) 383 384 LogDebugManifestCacheKeyFields("deleting manifests cache", "manifest hash did not match or cached response is empty", revision, appSrc, srcRefs, clusterInfo, namespace, trackingMethod, appLabelKey, appName, refSourceCommitSHAs) 385 386 err = c.DeleteManifests(revision, appSrc, srcRefs, clusterInfo, namespace, trackingMethod, appLabelKey, appName, refSourceCommitSHAs, installationID) 387 if err != nil { 388 return fmt.Errorf("unable to delete manifest after hash mismatch: %w", err) 389 } 390 391 // Treat hash mismatches as cache misses, so that the underlying resource is reacquired 392 return ErrCacheMiss 393 } 394 395 // The expected hash matches the actual hash, so remove the hash from the returned value 396 res.CacheEntryHash = "" 397 398 if res.ManifestResponse != nil { 399 // cached manifest response might be reused across different revisions, so we need to assume that the revision is the one we are looking for 400 res.ManifestResponse.Revision = revision 401 } 402 403 return nil 404 } 405 406 func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions, installationID string) error { 407 // Generate and apply the cache entry hash, before writing 408 if res != nil { 409 res = res.shallowCopy() 410 hash, err := res.generateCacheEntryHash() 411 if err != nil { 412 return fmt.Errorf("unable to generate hash value: %w", err) 413 } 414 res.CacheEntryHash = hash 415 } 416 417 return c.cache.SetItem( 418 manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID), 419 res, 420 &cacheutil.CacheActionOpts{ 421 Expiration: c.repoCacheExpiration, 422 Delete: res == nil, 423 }) 424 } 425 426 func (c *Cache) DeleteManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace, trackingMethod, appLabelKey, appName string, refSourceCommitSHAs ResolvedRevisions, installationID string) error { 427 return c.cache.SetItem( 428 manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID), 429 "", 430 &cacheutil.CacheActionOpts{Delete: true}) 431 } 432 433 func appDetailsCacheKey(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, trackingMethod appv1.TrackingMethod, refSourceCommitSHAs ResolvedRevisions) string { 434 if trackingMethod == "" { 435 trackingMethod = appv1.TrackingMethodLabel 436 } 437 return fmt.Sprintf("appdetails|%s|%d|%s", revision, appSourceKey(appSrc, srcRefs, refSourceCommitSHAs), trackingMethod) 438 } 439 440 func (c *Cache) GetAppDetails(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, res *apiclient.RepoAppDetailsResponse, trackingMethod appv1.TrackingMethod, refSourceCommitSHAs ResolvedRevisions) error { 441 return c.cache.GetItem(appDetailsCacheKey(revision, appSrc, srcRefs, trackingMethod, refSourceCommitSHAs), res) 442 } 443 444 func (c *Cache) SetAppDetails(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, res *apiclient.RepoAppDetailsResponse, trackingMethod appv1.TrackingMethod, refSourceCommitSHAs ResolvedRevisions) error { 445 return c.cache.SetItem( 446 appDetailsCacheKey(revision, appSrc, srcRefs, trackingMethod, refSourceCommitSHAs), 447 res, 448 &cacheutil.CacheActionOpts{ 449 Expiration: c.repoCacheExpiration, 450 Delete: res == nil, 451 }) 452 } 453 454 func revisionMetadataKey(repoURL, revision string) string { 455 return fmt.Sprintf("revisionmetadata|%s|%s", repoURL, revision) 456 } 457 458 func (c *Cache) GetRevisionMetadata(repoURL, revision string) (*appv1.RevisionMetadata, error) { 459 item := &appv1.RevisionMetadata{} 460 return item, c.cache.GetItem(revisionMetadataKey(repoURL, revision), item) 461 } 462 463 func (c *Cache) SetRevisionMetadata(repoURL, revision string, item *appv1.RevisionMetadata) error { 464 return c.cache.SetItem( 465 revisionMetadataKey(repoURL, revision), 466 item, 467 &cacheutil.CacheActionOpts{Expiration: c.repoCacheExpiration}) 468 } 469 470 func revisionChartDetailsKey(repoURL, chart, revision string) string { 471 return fmt.Sprintf("chartdetails|%s|%s|%s", repoURL, chart, revision) 472 } 473 474 func (c *Cache) GetRevisionChartDetails(repoURL, chart, revision string) (*appv1.ChartDetails, error) { 475 item := &appv1.ChartDetails{} 476 return item, c.cache.GetItem(revisionChartDetailsKey(repoURL, chart, revision), item) 477 } 478 479 func (c *Cache) SetRevisionChartDetails(repoURL, chart, revision string, item *appv1.ChartDetails) error { 480 return c.cache.SetItem( 481 revisionChartDetailsKey(repoURL, chart, revision), 482 item, 483 &cacheutil.CacheActionOpts{Expiration: c.repoCacheExpiration}) 484 } 485 486 func gitFilesKey(repoURL, revision, pattern string) string { 487 return fmt.Sprintf("gitfiles|%s|%s|%s", repoURL, revision, pattern) 488 } 489 490 func (c *Cache) SetGitFiles(repoURL, revision, pattern string, files map[string][]byte) error { 491 return c.cache.SetItem( 492 gitFilesKey(repoURL, revision, pattern), 493 &files, 494 &cacheutil.CacheActionOpts{Expiration: c.repoCacheExpiration}) 495 } 496 497 func (c *Cache) GetGitFiles(repoURL, revision, pattern string) (map[string][]byte, error) { 498 var item map[string][]byte 499 err := c.cache.GetItem(gitFilesKey(repoURL, revision, pattern), &item) 500 return item, err 501 } 502 503 func gitDirectoriesKey(repoURL, revision string) string { 504 return fmt.Sprintf("gitdirs|%s|%s", repoURL, revision) 505 } 506 507 func (c *Cache) SetGitDirectories(repoURL, revision string, directories []string) error { 508 return c.cache.SetItem( 509 gitDirectoriesKey(repoURL, revision), 510 &directories, 511 &cacheutil.CacheActionOpts{Expiration: c.repoCacheExpiration}) 512 } 513 514 func (c *Cache) GetGitDirectories(repoURL, revision string) ([]string, error) { 515 var item []string 516 err := c.cache.GetItem(gitDirectoriesKey(repoURL, revision), &item) 517 return item, err 518 } 519 520 func (cmr *CachedManifestResponse) shallowCopy() *CachedManifestResponse { 521 if cmr == nil { 522 return nil 523 } 524 525 return &CachedManifestResponse{ 526 CacheEntryHash: cmr.CacheEntryHash, 527 FirstFailureTimestamp: cmr.FirstFailureTimestamp, 528 ManifestResponse: cmr.ManifestResponse, 529 MostRecentError: cmr.MostRecentError, 530 NumberOfCachedResponsesReturned: cmr.NumberOfCachedResponsesReturned, 531 NumberOfConsecutiveFailures: cmr.NumberOfConsecutiveFailures, 532 } 533 } 534 535 func (cmr *CachedManifestResponse) generateCacheEntryHash() (string, error) { 536 // Copy, then remove the old hash 537 shallowCopy := cmr.shallowCopy() 538 shallowCopy.CacheEntryHash = "" 539 540 // Hash the JSON representation into a base-64-encoded FNV 64a (we don't need a cryptographic hash algorithm, since this is only for detecting data corruption) 541 bytes, err := json.Marshal(shallowCopy) 542 if err != nil { 543 return "", err 544 } 545 h := fnv.New64a() 546 _, err = h.Write(bytes) 547 if err != nil { 548 return "", err 549 } 550 fnvHash := h.Sum(nil) 551 return base64.URLEncoding.EncodeToString(fnvHash), nil 552 }