...

Source file src/oras.land/oras-go/pkg/registry/remote/auth/cache.go

Documentation: oras.land/oras-go/pkg/registry/remote/auth

     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  }
   159  

View as plain text