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 }