github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/packager/composer/oci.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package composer contains functions for composing components within Jackal packages. 5 package composer 6 7 import ( 8 "context" 9 "crypto/sha256" 10 "fmt" 11 "os" 12 "path/filepath" 13 14 "github.com/Racer159/jackal/src/config" 15 "github.com/Racer159/jackal/src/pkg/layout" 16 "github.com/Racer159/jackal/src/pkg/message" 17 "github.com/Racer159/jackal/src/pkg/utils" 18 "github.com/Racer159/jackal/src/pkg/zoci" 19 "github.com/defenseunicorns/pkg/helpers" 20 "github.com/defenseunicorns/pkg/oci" 21 "github.com/mholt/archiver/v3" 22 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 23 ocistore "oras.land/oras-go/v2/content/oci" 24 ) 25 26 func (ic *ImportChain) getRemote(url string) (*zoci.Remote, error) { 27 if ic.remote != nil { 28 return ic.remote, nil 29 } 30 var err error 31 ic.remote, err = zoci.NewRemote(url, zoci.PlatformForSkeleton()) 32 if err != nil { 33 return nil, err 34 } 35 _, err = ic.remote.ResolveRoot(context.TODO()) 36 if err != nil { 37 return nil, fmt.Errorf("published skeleton package for %q does not exist: %w", url, err) 38 } 39 return ic.remote, nil 40 } 41 42 // ContainsOCIImport returns true if the import chain contains a remote import 43 func (ic *ImportChain) ContainsOCIImport() bool { 44 // only the 2nd to last node may have a remote import 45 return ic.tail.prev != nil && ic.tail.prev.Import.URL != "" 46 } 47 48 func (ic *ImportChain) fetchOCISkeleton() error { 49 if !ic.ContainsOCIImport() { 50 return nil 51 } 52 node := ic.tail.prev 53 remote, err := ic.getRemote(node.Import.URL) 54 if err != nil { 55 return err 56 } 57 58 ctx := context.TODO() 59 manifest, err := remote.FetchRoot(ctx) 60 if err != nil { 61 return err 62 } 63 64 name := node.ImportName() 65 66 componentDesc := manifest.Locate(filepath.Join(layout.ComponentsDir, fmt.Sprintf("%s.tar", name))) 67 68 cache := filepath.Join(config.GetAbsCachePath(), "oci") 69 if err := helpers.CreateDirectory(cache, helpers.ReadWriteExecuteUser); err != nil { 70 return err 71 } 72 73 var tb, dir string 74 75 // if there is not a tarball to fetch, create a directory named based upon 76 // the import url and the component name 77 if oci.IsEmptyDescriptor(componentDesc) { 78 h := sha256.New() 79 h.Write([]byte(node.Import.URL + name)) 80 id := fmt.Sprintf("%x", h.Sum(nil)) 81 82 dir = filepath.Join(cache, "dirs", id) 83 84 message.Debug("creating empty directory for remote component:", filepath.Join("<jackal-cache>", "oci", "dirs", id)) 85 } else { 86 tb = filepath.Join(cache, "blobs", "sha256", componentDesc.Digest.Encoded()) 87 dir = filepath.Join(cache, "dirs", componentDesc.Digest.Encoded()) 88 89 store, err := ocistore.New(cache) 90 if err != nil { 91 return err 92 } 93 94 ctx := context.TODO() 95 // ensure the tarball is in the cache 96 exists, err := store.Exists(ctx, componentDesc) 97 if err != nil { 98 return err 99 } else if !exists { 100 doneSaving := make(chan error) 101 successText := fmt.Sprintf("Pulling %q", helpers.OCIURLPrefix+remote.Repo().Reference.String()) 102 go utils.RenderProgressBarForLocalDirWrite(cache, componentDesc.Size, doneSaving, "Pulling", successText) 103 err = remote.CopyToTarget(ctx, []ocispec.Descriptor{componentDesc}, store, remote.GetDefaultCopyOpts()) 104 doneSaving <- err 105 <-doneSaving 106 if err != nil { 107 return err 108 } 109 110 } 111 } 112 113 if err := helpers.CreateDirectory(dir, helpers.ReadWriteExecuteUser); err != nil { 114 return err 115 } 116 117 cwd, err := os.Getwd() 118 if err != nil { 119 return err 120 } 121 rel, err := filepath.Rel(cwd, dir) 122 if err != nil { 123 return err 124 } 125 // the tail node is the only node whose relativeToHead is based solely upon cwd<->cache 126 // contrary to the other nodes, which are based upon the previous node 127 ic.tail.relativeToHead = rel 128 129 if oci.IsEmptyDescriptor(componentDesc) { 130 // nothing was fetched, nothing to extract 131 return nil 132 } 133 134 tu := archiver.Tar{ 135 OverwriteExisting: true, 136 // removes /<component-name>/ from the paths 137 StripComponents: 1, 138 } 139 return tu.Unarchive(tb, dir) 140 }