...

Source file src/github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cache/file.go

Documentation: github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cache

     1  // Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"). You may
     4  // not use this file except in compliance with the License. A copy of the
     5  // License is located at
     6  //
     7  //	http://aws.amazon.com/apache2.0/
     8  //
     9  // or in the "license" file accompanying this file. This file is distributed
    10  // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
    11  // express or implied. See the License for the specific language governing
    12  // permissions and limitations under the License.
    13  
    14  package cache
    15  
    16  import (
    17  	"encoding/json"
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  const registryCacheVersion = "1.0"
    26  
    27  type RegistryCache struct {
    28  	Registries map[string]*AuthEntry
    29  	Version    string
    30  }
    31  
    32  type fileCredentialCache struct {
    33  	path           string
    34  	filename       string
    35  	cachePrefixKey string
    36  	publicCacheKey string
    37  }
    38  
    39  func newRegistryCache() *RegistryCache {
    40  	return &RegistryCache{
    41  		Registries: make(map[string]*AuthEntry),
    42  		Version:    registryCacheVersion,
    43  	}
    44  }
    45  
    46  // NewFileCredentialsCache returns a new file credentials cache.
    47  //
    48  // path is used for temporary files during save, and filename should be a relative filename
    49  // in the same directory where the cache is serialized and deserialized.
    50  //
    51  // cachePrefixKey is used for scoping credentials for a given credential cache (i.e. region and
    52  // accessKey).
    53  func NewFileCredentialsCache(path string, filename string, cachePrefixKey string, publicCacheKey string) CredentialsCache {
    54  	if _, err := os.Stat(path); err != nil {
    55  		os.MkdirAll(path, 0700)
    56  	}
    57  	return &fileCredentialCache{
    58  		path:           path,
    59  		filename:       filename,
    60  		cachePrefixKey: cachePrefixKey,
    61  		publicCacheKey: publicCacheKey,
    62  	}
    63  }
    64  
    65  func (f *fileCredentialCache) Get(registry string) *AuthEntry {
    66  	logrus.WithField("registry", registry).Debug("Checking file cache")
    67  	registryCache := f.init()
    68  	return registryCache.Registries[f.cachePrefixKey+registry]
    69  }
    70  
    71  func (f *fileCredentialCache) GetPublic() *AuthEntry {
    72  	logrus.Debug("Checking file cache for ECR Public")
    73  	registryCache := f.init()
    74  	return registryCache.Registries[f.publicCacheKey]
    75  }
    76  
    77  func (f *fileCredentialCache) Set(registry string, entry *AuthEntry) {
    78  	logrus.
    79  		WithField("registry", registry).
    80  		WithField("service", entry.Service).
    81  		Debug("Saving credentials to file cache")
    82  	registryCache := f.init()
    83  
    84  	key := f.cachePrefixKey + registry
    85  	if entry.Service == ServiceECRPublic {
    86  		key = f.publicCacheKey
    87  	}
    88  	registryCache.Registries[key] = entry
    89  
    90  	err := f.save(registryCache)
    91  	if err != nil {
    92  		logrus.WithError(err).Info("Could not save cache")
    93  	}
    94  }
    95  
    96  // List returns all of the available AuthEntries (regardless of prefix)
    97  func (f *fileCredentialCache) List() []*AuthEntry {
    98  	registryCache := f.init()
    99  
   100  	// optimize allocation for copy
   101  	entries := make([]*AuthEntry, 0, len(registryCache.Registries))
   102  
   103  	for _, entry := range registryCache.Registries {
   104  		entries = append(entries, entry)
   105  	}
   106  
   107  	return entries
   108  }
   109  
   110  func (f *fileCredentialCache) Clear() {
   111  	err := os.Remove(f.fullFilePath())
   112  	if err != nil {
   113  		logrus.WithError(err).Info("Could not clear cache")
   114  	}
   115  }
   116  
   117  func (f *fileCredentialCache) fullFilePath() string {
   118  	return filepath.Join(f.path, f.filename)
   119  }
   120  
   121  // Saves credential cache to disk. This writes to a temporary file first, then moves the file to the config location.
   122  // This eliminates from reading partially written credential files, and reduces (but does not eliminate) concurrent
   123  // file access. There is not guarantee here for handling multiple writes at once since there is no out of process locking.
   124  func (f *fileCredentialCache) save(registryCache *RegistryCache) error {
   125  	file, err := os.CreateTemp(f.path, ".config.json.tmp")
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	buff, err := json.MarshalIndent(registryCache, "", "  ")
   131  	if err != nil {
   132  		file.Close()
   133  		os.Remove(file.Name())
   134  		return err
   135  	}
   136  
   137  	_, err = file.Write(buff)
   138  
   139  	if err != nil {
   140  		file.Close()
   141  		os.Remove(file.Name())
   142  		return err
   143  	}
   144  
   145  	file.Close()
   146  	// note this is only atomic when relying on linux syscalls
   147  	os.Rename(file.Name(), f.fullFilePath())
   148  	return err
   149  }
   150  
   151  func (f *fileCredentialCache) init() *RegistryCache {
   152  	registryCache, err := f.load()
   153  	if err != nil {
   154  		logrus.WithError(err).Info("Could not load existing cache")
   155  		f.Clear()
   156  		registryCache = newRegistryCache()
   157  	}
   158  	return registryCache
   159  }
   160  
   161  // Loading a cache from disk will return errors for malformed or incompatible cache files.
   162  func (f *fileCredentialCache) load() (*RegistryCache, error) {
   163  	registryCache := newRegistryCache()
   164  
   165  	file, err := os.Open(f.fullFilePath())
   166  	if os.IsNotExist(err) {
   167  		return registryCache, nil
   168  	}
   169  
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	defer file.Close()
   175  
   176  	if err = json.NewDecoder(file).Decode(&registryCache); err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	if registryCache.Version != registryCacheVersion {
   181  		return nil, fmt.Errorf("ecr: Registry cache version %#v is not compatible with %#v, ignoring existing cache",
   182  			registryCache.Version,
   183  			registryCacheVersion)
   184  	}
   185  
   186  	// migrate entries
   187  	for key := range registryCache.Registries {
   188  		if registryCache.Registries[key].Service == "" {
   189  			registryCache.Registries[key].Service = ServiceECR
   190  		}
   191  	}
   192  
   193  	return registryCache, nil
   194  }
   195  

View as plain text