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

     1  package cache
     2  
     3  import (
     4  	"crypto/md5"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"path/filepath"
     9  
    10  	"get.porter.sh/porter/pkg/cnab"
    11  	"get.porter.sh/porter/pkg/config"
    12  	"get.porter.sh/porter/pkg/encoding"
    13  	"get.porter.sh/porter/pkg/portercontext"
    14  	"github.com/cnabio/cnab-to-oci/relocation"
    15  )
    16  
    17  // CachedBundle represents a bundle pulled from a registry that has been cached to
    18  // the filesystem.
    19  type CachedBundle struct {
    20  	// cacheDir is the cache directory for the bundle (not the general cache directory)
    21  	cacheDir string
    22  
    23  	// BundleReference contains common bundle metadata, such as the definition.
    24  	cnab.BundleReference
    25  
    26  	// BundlePath is the location of the bundle.json in the cache.
    27  	BundlePath string
    28  
    29  	// ManifestPath is the optional location of the porter.yaml in the cache.
    30  	ManifestPath string
    31  
    32  	// RelocationFilePath is the optional location of the relocation file in the cache.
    33  	RelocationFilePath string
    34  }
    35  
    36  // GetBundleID is the unique ID of the cached bundle.
    37  func (cb *CachedBundle) GetBundleID() string {
    38  	// hash the tag, tags have characters that won't work as part of a path
    39  	// so hashing here to get a path friendly name
    40  	bid := md5.Sum([]byte(cb.Reference.String()))
    41  	return hex.EncodeToString(bid[:])
    42  }
    43  
    44  // SetCacheDir sets the bundle specific cache directory based on the given Porter cache directory.
    45  func (cb *CachedBundle) SetCacheDir(porterCacheDir string) {
    46  	cb.cacheDir = filepath.Join(porterCacheDir, cb.GetBundleID())
    47  }
    48  
    49  // BuildBundlePath generates the potential location of the bundle.json, if it existed.
    50  func (cb *CachedBundle) BuildBundlePath() string {
    51  	return filepath.Join(cb.cacheDir, "cnab", "bundle.json")
    52  }
    53  
    54  // BuildRelocationFilePath generates the potential location of the relocation file, if it existed.
    55  func (cb *CachedBundle) BuildRelocationFilePath() string {
    56  	return filepath.Join(cb.cacheDir, "cnab", "relocation-mapping.json")
    57  }
    58  
    59  // BuildManifestPath generates the potential location of the manifest, if it existed.
    60  func (cb *CachedBundle) BuildManifestPath() string {
    61  	return filepath.Join(cb.cacheDir, config.Name)
    62  }
    63  
    64  // BuildMetadataPath generates the location of the cache metadata.
    65  func (cb *CachedBundle) BuildMetadataPath() string {
    66  	return filepath.Join(cb.cacheDir, "metadata.json")
    67  }
    68  
    69  // Load starts from the bundle tag, and hydrates the cached bundle from the cache.
    70  func (cb *CachedBundle) Load(cxt *portercontext.Context) (bool, error) {
    71  	// Check that the bundle exists
    72  	cb.BundlePath = cb.BuildBundlePath()
    73  	metaPath := cb.BuildMetadataPath()
    74  	metaExists, err := cxt.FileSystem.Exists(metaPath)
    75  	if err != nil {
    76  		return false, fmt.Errorf("unable to access bundle metadata %s at %s: %w", cb.Reference, metaPath, err)
    77  	}
    78  	if !metaExists {
    79  		// consider this a miss, recache with the metadata
    80  		return false, nil
    81  	}
    82  	var meta Metadata
    83  	err = encoding.UnmarshalFile(cxt.FileSystem, metaPath, &meta)
    84  	if err != nil {
    85  		return false, fmt.Errorf("unable to parse cached bundle metadata %s at %s: %w", cb.Reference, metaPath, err)
    86  	}
    87  	cb.Digest = meta.Digest
    88  
    89  	// Check for the optional relocation mapping next to it
    90  	reloPath := cb.BuildRelocationFilePath()
    91  	reloExists, err := cxt.FileSystem.Exists(reloPath)
    92  	if err != nil {
    93  		return true, fmt.Errorf("unable to read relocation mapping %s at %s: %w", cb.Reference, reloPath, err)
    94  	}
    95  	if reloExists {
    96  		cb.RelocationFilePath = reloPath
    97  	}
    98  
    99  	// Check for the optional manifest
   100  	manifestPath := cb.BuildManifestPath()
   101  	manifestExists, err := cxt.FileSystem.Exists(manifestPath)
   102  	if err != nil {
   103  		return true, fmt.Errorf("unable to read manifest %s at %s: %w", cb.Reference, manifestPath, err)
   104  	}
   105  	if manifestExists {
   106  		cb.ManifestPath = manifestPath
   107  	}
   108  
   109  	bun, err := cnab.LoadBundle(cxt, cb.BundlePath)
   110  	if err != nil {
   111  		return true, fmt.Errorf("unable to parse cached bundle file at %s: %w", cb.BundlePath, err)
   112  	}
   113  	cb.Definition = bun
   114  
   115  	if cb.RelocationFilePath != "" {
   116  		data, err := cxt.FileSystem.ReadFile(cb.RelocationFilePath)
   117  		if err != nil {
   118  			return true, fmt.Errorf("unable to read cached relocation file at %s: %w", cb.RelocationFilePath, err)
   119  		}
   120  
   121  		reloMap := relocation.ImageRelocationMap{}
   122  		err = json.Unmarshal(data, &reloMap)
   123  		if err != nil {
   124  			return true, fmt.Errorf("unable to parse cached relocation file at %s: %w", cb.RelocationFilePath, err)
   125  		}
   126  		cb.RelocationMap = reloMap
   127  	}
   128  
   129  	return true, nil
   130  }