get.porter.sh/porter@v1.3.0/pkg/cache/cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"get.porter.sh/porter/pkg"
    10  	"get.porter.sh/porter/pkg/cnab"
    11  	configadapter "get.porter.sh/porter/pkg/cnab/config-adapter"
    12  	"get.porter.sh/porter/pkg/config"
    13  	"get.porter.sh/porter/pkg/encoding"
    14  	"github.com/opencontainers/go-digest"
    15  )
    16  
    17  type BundleCache interface {
    18  	FindBundle(tag cnab.OCIReference) (bun CachedBundle, found bool, err error)
    19  	StoreBundle(bundleRef cnab.BundleReference) (CachedBundle, error)
    20  	GetCacheDir() (string, error)
    21  }
    22  
    23  var _ BundleCache = &Cache{}
    24  
    25  type Cache struct {
    26  	*config.Config
    27  }
    28  
    29  func New(cfg *config.Config) BundleCache {
    30  	return &Cache{
    31  		Config: cfg,
    32  	}
    33  }
    34  
    35  // FindBundle looks for a given bundle tag in the Porter bundle cache and
    36  // returns the path to the bundle if it exists. If it is not found, an
    37  // empty string and the boolean false value are returned. If the bundle is found,
    38  // and a relocation mapping file is present, it will be returned as well. If the relocation
    39  // is not found, an empty string is returned.
    40  func (c *Cache) FindBundle(ref cnab.OCIReference) (CachedBundle, bool, error) {
    41  	cb := CachedBundle{}
    42  	cb.Reference = ref
    43  
    44  	cacheDir, err := c.GetCacheDir()
    45  	if err != nil {
    46  		return CachedBundle{}, false, err
    47  	}
    48  	cb.SetCacheDir(cacheDir)
    49  
    50  	found, err := cb.Load(c.Context)
    51  	if err != nil {
    52  		return CachedBundle{}, false, err
    53  	}
    54  	if !found {
    55  		return CachedBundle{}, false, nil
    56  	}
    57  	return cb, true, nil
    58  
    59  }
    60  
    61  // StoreBundle will write a given bundle to the bundle cache, in a location derived
    62  // from the bundleTag. If a relocation mapping is provided, it will be stored along side
    63  // the bundle. If successful, returns the path to the bundle, along with the path to a
    64  // relocation mapping, if provided. Otherwise, returns an error.
    65  func (c *Cache) StoreBundle(bundleRef cnab.BundleReference) (CachedBundle, error) {
    66  	cb := CachedBundle{BundleReference: bundleRef}
    67  
    68  	cacheDir, err := c.GetCacheDir()
    69  	if err != nil {
    70  		return CachedBundle{}, err
    71  	}
    72  	cb.SetCacheDir(cacheDir)
    73  
    74  	// Remove any previously cached bundle files
    75  	err = c.FileSystem.RemoveAll(cb.cacheDir)
    76  	if err != nil {
    77  		return CachedBundle{}, fmt.Errorf("cannot remove existing cache directory %s: %w", cb.BundlePath, err)
    78  	}
    79  
    80  	cb.BundlePath = cb.BuildBundlePath()
    81  	err = c.FileSystem.MkdirAll(filepath.Dir(cb.BundlePath), pkg.FileModeDirectory)
    82  	if err != nil {
    83  		return CachedBundle{}, fmt.Errorf("unable to create cache directory: %w", err)
    84  	}
    85  
    86  	f, err := c.FileSystem.OpenFile(cb.BundlePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, pkg.FileModeWritable)
    87  	if err != nil {
    88  		return CachedBundle{}, fmt.Errorf("error creating cnab/bundle.json for %s: %w", cb.Reference, err)
    89  	}
    90  	defer f.Close()
    91  
    92  	_, err = cb.Definition.WriteTo(f)
    93  	if err != nil {
    94  		return CachedBundle{}, fmt.Errorf("error writing to cnab/bundle.json for %s: %w", cb.Reference, err)
    95  	}
    96  
    97  	err = c.cacheMetadata(&cb)
    98  	if err != nil {
    99  		return CachedBundle{}, err
   100  	}
   101  
   102  	err = c.cacheManifest(&cb)
   103  	if err != nil {
   104  		return CachedBundle{}, err
   105  	}
   106  
   107  	// we wrote the bundle, now lets store a relocation mapping in cnab/ and return the path
   108  	if len(cb.RelocationMap) > 0 {
   109  		cb.RelocationFilePath = cb.BuildRelocationFilePath()
   110  		f, err = c.FileSystem.OpenFile(cb.RelocationFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, pkg.FileModeWritable)
   111  		if err != nil {
   112  			return CachedBundle{}, fmt.Errorf("error creating cnab/relocation-mapping.json for %s: %w", cb.Reference, err)
   113  		}
   114  		defer f.Close()
   115  
   116  		b, err := json.Marshal(cb.RelocationMap)
   117  		if err != nil {
   118  			return CachedBundle{}, fmt.Errorf("couldn't marshall relocation mapping for %s: %w", cb.Reference, err)
   119  		}
   120  
   121  		_, err = f.Write(b)
   122  		if err != nil {
   123  			return CachedBundle{}, fmt.Errorf("couldn't write relocation mapping for %s: %w", cb.Reference, err)
   124  		}
   125  
   126  	}
   127  
   128  	return cb, nil
   129  }
   130  
   131  // cacheMetadata stores additional metadata about the bundle.
   132  func (c *Cache) cacheMetadata(cb *CachedBundle) error {
   133  	meta := Metadata{
   134  		Reference: cb.Reference,
   135  		Digest:    cb.Digest,
   136  	}
   137  	path := cb.BuildMetadataPath()
   138  	return encoding.MarshalFile(c.FileSystem, path, meta)
   139  }
   140  
   141  // Metadata associated with a cached bundle.
   142  type Metadata struct {
   143  	Reference cnab.OCIReference `json:"reference"`
   144  	Digest    digest.Digest     `json:"digest"`
   145  }
   146  
   147  // cacheManifest extracts the porter.yaml from the bundle, if present and caches it
   148  // in the same cache directory as the rest of the bundle.
   149  func (c *Cache) cacheManifest(cb *CachedBundle) error {
   150  	if cb.Definition.IsPorterBundle() {
   151  		stamp, err := configadapter.LoadStamp(cb.Definition)
   152  		if err != nil {
   153  			fmt.Fprintf(c.Err, "WARNING: Bundle %s was created by porter but could not load the Porter stamp. This may be because it was created by an older version of Porter.\n", cb.Reference)
   154  			return nil
   155  		}
   156  
   157  		if stamp.EncodedManifest == "" {
   158  			fmt.Fprintf(c.Err, "WARNING: Bundle %s was created by porter but could not find a porter manifest embedded. This may be because it was created by an older version of Porter.\n", cb.Reference)
   159  			return nil
   160  		}
   161  
   162  		cb.ManifestPath = cb.BuildManifestPath()
   163  		err = stamp.WriteManifest(c.Context, cb.ManifestPath)
   164  		if err != nil {
   165  			return fmt.Errorf("error writing porter.yaml for %s: %w", cb.Reference, err)
   166  		}
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  func (c *Cache) GetCacheDir() (string, error) {
   173  	home, err := c.GetHomeDir()
   174  	if err != nil {
   175  		return "", err
   176  	}
   177  	return filepath.Join(home, "cache"), nil
   178  }