github.com/nikkelma/oras-project_oras-go@v1.1.1-0.20220201001104-a75f6a419090/pkg/registry/remote/auth/cache.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 package auth 16 17 import ( 18 "context" 19 "strings" 20 "sync" 21 22 errdef "oras.land/oras-go/pkg/content" 23 "oras.land/oras-go/pkg/registry/remote/internal/syncutil" 24 ) 25 26 // DefaultCache is the sharable cache used by DefaultClient. 27 var DefaultCache Cache = NewCache() 28 29 // Cache caches the auth-scheme and auth-token for the "Authorization" header in 30 // accessing the remote registry. 31 // Precisely, the header is `Authorization: auth-scheme auth-token`. 32 // The `auth-token` is a generic term as `token68` in RFC 7235 section 2.1. 33 type Cache interface { 34 // GetScheme returns the auth-scheme part cached for the given registry. 35 // A single registry is assumed to have a consistent scheme. 36 // If a registry has different schemes per path, the auth client is still 37 // workable. However, the cache may not be effective as the cache cannot 38 // correctly guess the scheme. 39 GetScheme(ctx context.Context, registry string) (Scheme, error) 40 41 // GetToken returns the auth-token part cached for the given registry of a 42 // given scheme. 43 // The underlying implementation MAY cache the token for all schemes for the 44 // given registry. 45 GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) 46 47 // Set fetches the token using the given fetch function and caches the token 48 // for the given scheme with the given key for the given registry. 49 // The return values of the fetch function is returned by this function. 50 // The underlying implementation MAY combine the fetch operation if the Set 51 // function is invoked multiple times at the same time. 52 Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) 53 } 54 55 // cacheEntry is a cache entry for a single registry. 56 type cacheEntry struct { 57 scheme Scheme 58 tokens sync.Map // map[string]string 59 } 60 61 // concurrentCache is a cache suitable for concurrent invocation. 62 type concurrentCache struct { 63 status sync.Map // map[string]*syncutil.Once 64 cache sync.Map // map[string]*cacheEntry 65 } 66 67 // NewCache creates a new go-routine safe cache instance. 68 func NewCache() Cache { 69 return &concurrentCache{} 70 } 71 72 // GetScheme returns the auth-scheme part cached for the given registry. 73 func (cc *concurrentCache) GetScheme(ctx context.Context, registry string) (Scheme, error) { 74 entry, ok := cc.cache.Load(registry) 75 if !ok { 76 return SchemeUnknown, errdef.ErrNotFound 77 } 78 return entry.(*cacheEntry).scheme, nil 79 } 80 81 // GetToken returns the auth-token part cached for the given registry of a given 82 // scheme. 83 func (cc *concurrentCache) GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) { 84 entryValue, ok := cc.cache.Load(registry) 85 if !ok { 86 return "", errdef.ErrNotFound 87 } 88 entry := entryValue.(*cacheEntry) 89 if entry.scheme != scheme { 90 return "", errdef.ErrNotFound 91 } 92 if token, ok := entry.tokens.Load(key); ok { 93 return token.(string), nil 94 } 95 return "", errdef.ErrNotFound 96 } 97 98 // Set fetches the token using the given fetch function and caches the token 99 // for the given scheme with the given key for the given registry. 100 // Set combines the fetch operation if the Set is invoked multiple times at the 101 // same time. 102 func (cc *concurrentCache) Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) { 103 // fetch token 104 statusKey := strings.Join([]string{ 105 registry, 106 scheme.String(), 107 key, 108 }, " ") 109 statusValue, _ := cc.status.LoadOrStore(statusKey, syncutil.NewOnce()) 110 fetchOnce := statusValue.(*syncutil.Once) 111 fetchedFirst, result, err := fetchOnce.Do(ctx, func() (interface{}, error) { 112 return fetch(ctx) 113 }) 114 if fetchedFirst { 115 cc.status.Delete(statusKey) 116 } 117 if err != nil { 118 return "", err 119 } 120 token := result.(string) 121 if !fetchedFirst { 122 return token, nil 123 } 124 125 // cache token 126 newEntry := &cacheEntry{ 127 scheme: scheme, 128 } 129 entryValue, exists := cc.cache.LoadOrStore(registry, newEntry) 130 entry := entryValue.(*cacheEntry) 131 if exists && entry.scheme != scheme { 132 // there is a scheme change, which is not expected in most scenarios. 133 // force invalidating all previous cache. 134 entry = newEntry 135 cc.cache.Store(registry, entry) 136 } 137 entry.tokens.Store(key, token) 138 139 return token, nil 140 } 141 142 // noCache is a cache implementation that does not do cache at all. 143 type noCache struct{} 144 145 // GetScheme always returns not found error as it has no cache. 146 func (noCache) GetScheme(ctx context.Context, registry string) (Scheme, error) { 147 return SchemeUnknown, errdef.ErrNotFound 148 } 149 150 // GetToken always returns not found error as it has no cache. 151 func (noCache) GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) { 152 return "", errdef.ErrNotFound 153 } 154 155 // Set calls fetch directly without caching. 156 func (noCache) Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) { 157 return fetch(ctx) 158 }