github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/buildpack/downloader.go (about) 1 package buildpack 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/pkg/errors" 8 9 "github.com/buildpacks/imgutil" 10 11 "github.com/buildpacks/pack/internal/layer" 12 "github.com/buildpacks/pack/internal/paths" 13 "github.com/buildpacks/pack/internal/style" 14 "github.com/buildpacks/pack/pkg/blob" 15 "github.com/buildpacks/pack/pkg/dist" 16 "github.com/buildpacks/pack/pkg/image" 17 ) 18 19 type Logger interface { 20 Debug(msg string) 21 Debugf(fmt string, v ...interface{}) 22 Info(msg string) 23 Infof(fmt string, v ...interface{}) 24 Warn(msg string) 25 Warnf(fmt string, v ...interface{}) 26 Error(msg string) 27 Errorf(fmt string, v ...interface{}) 28 } 29 30 type ImageFetcher interface { 31 Fetch(ctx context.Context, name string, options image.FetchOptions) (imgutil.Image, error) 32 CheckReadAccess(repo string, options image.FetchOptions) bool 33 } 34 35 type Downloader interface { 36 Download(ctx context.Context, pathOrURI string) (blob.Blob, error) 37 } 38 39 //go:generate mockgen -package testmocks -destination ../testmocks/mock_registry_resolver.go github.com/buildpacks/pack/pkg/buildpack RegistryResolver 40 41 type RegistryResolver interface { 42 Resolve(registryName, bpURI string) (string, error) 43 } 44 45 type buildpackDownloader struct { 46 logger Logger 47 imageFetcher ImageFetcher 48 downloader Downloader 49 registryResolver RegistryResolver 50 } 51 52 func NewDownloader(logger Logger, imageFetcher ImageFetcher, downloader Downloader, registryResolver RegistryResolver) *buildpackDownloader { //nolint:revive,gosimple 53 return &buildpackDownloader{ 54 logger: logger, 55 imageFetcher: imageFetcher, 56 downloader: downloader, 57 registryResolver: registryResolver, 58 } 59 } 60 61 type DownloadOptions struct { 62 // Buildpack registry name. Defines where all registry buildpacks will be pulled from. 63 RegistryName string 64 65 // The base directory to use to resolve relative assets 66 RelativeBaseDir string 67 68 // The OS of the builder image 69 ImageOS string 70 71 // The OS/Architecture to download 72 Platform string 73 74 // Deprecated: the older alternative to buildpack URI 75 ImageName string 76 77 // The kind of module to download (valid values: "buildpack", "extension"). Defaults to "buildpack". 78 ModuleKind string 79 80 Daemon bool 81 82 PullPolicy image.PullPolicy 83 } 84 85 func (c *buildpackDownloader) Download(ctx context.Context, moduleURI string, opts DownloadOptions) (BuildModule, []BuildModule, error) { 86 kind := KindBuildpack 87 if opts.ModuleKind == KindExtension { 88 kind = KindExtension 89 } 90 91 var err error 92 var locatorType LocatorType 93 if moduleURI == "" && opts.ImageName != "" { 94 c.logger.Warn("The 'image' key is deprecated. Use 'uri=\"docker://...\"' instead.") 95 moduleURI = opts.ImageName 96 locatorType = PackageLocator 97 } else { 98 locatorType, err = GetLocatorType(moduleURI, opts.RelativeBaseDir, []dist.ModuleInfo{}) 99 if err != nil { 100 return nil, nil, err 101 } 102 } 103 var mainBP BuildModule 104 var depBPs []BuildModule 105 switch locatorType { 106 case PackageLocator: 107 imageName := ParsePackageLocator(moduleURI) 108 c.logger.Debugf("Downloading %s from image: %s", kind, style.Symbol(imageName)) 109 mainBP, depBPs, err = extractPackaged(ctx, kind, imageName, c.imageFetcher, image.FetchOptions{ 110 Daemon: opts.Daemon, 111 PullPolicy: opts.PullPolicy, 112 Platform: opts.Platform, 113 }) 114 if err != nil { 115 return nil, nil, errors.Wrapf(err, "extracting from registry %s", style.Symbol(moduleURI)) 116 } 117 case RegistryLocator: 118 c.logger.Debugf("Downloading %s from registry: %s", kind, style.Symbol(moduleURI)) 119 address, err := c.registryResolver.Resolve(opts.RegistryName, moduleURI) 120 if err != nil { 121 return nil, nil, errors.Wrapf(err, "locating in registry: %s", style.Symbol(moduleURI)) 122 } 123 124 mainBP, depBPs, err = extractPackaged(ctx, kind, address, c.imageFetcher, image.FetchOptions{ 125 Daemon: opts.Daemon, 126 PullPolicy: opts.PullPolicy, 127 Platform: opts.Platform, 128 }) 129 if err != nil { 130 return nil, nil, errors.Wrapf(err, "extracting from registry %s", style.Symbol(moduleURI)) 131 } 132 case URILocator: 133 moduleURI, err = paths.FilePathToURI(moduleURI, opts.RelativeBaseDir) 134 if err != nil { 135 return nil, nil, errors.Wrapf(err, "making absolute: %s", style.Symbol(moduleURI)) 136 } 137 138 c.logger.Debugf("Downloading %s from URI: %s", kind, style.Symbol(moduleURI)) 139 140 blob, err := c.downloader.Download(ctx, moduleURI) 141 if err != nil { 142 return nil, nil, errors.Wrapf(err, "downloading %s from %s", kind, style.Symbol(moduleURI)) 143 } 144 145 mainBP, depBPs, err = decomposeBlob(blob, kind, opts.ImageOS, c.logger) 146 if err != nil { 147 return nil, nil, errors.Wrapf(err, "extracting from %s", style.Symbol(moduleURI)) 148 } 149 default: 150 return nil, nil, fmt.Errorf("error reading %s: invalid locator: %s", moduleURI, locatorType) 151 } 152 return mainBP, depBPs, nil 153 } 154 155 // decomposeBlob decomposes a buildpack or extension blob into the main module (order buildpack or extension) and 156 // (for buildpack blobs) its dependent buildpacks. 157 func decomposeBlob(blob blob.Blob, kind string, imageOS string, logger Logger) (mainModule BuildModule, depModules []BuildModule, err error) { 158 isOCILayout, err := IsOCILayoutBlob(blob) 159 if err != nil { 160 return mainModule, depModules, errors.Wrapf(err, "inspecting %s blob", kind) 161 } 162 163 if isOCILayout { 164 mainModule, depModules, err = fromOCILayoutBlob(blob, kind) 165 if err != nil { 166 return mainModule, depModules, errors.Wrapf(err, "extracting %ss", kind) 167 } 168 } else { 169 layerWriterFactory, err := layer.NewWriterFactory(imageOS) 170 if err != nil { 171 return mainModule, depModules, errors.Wrapf(err, "get tar writer factory for OS %s", style.Symbol(imageOS)) 172 } 173 174 if kind == KindExtension { 175 mainModule, err = FromExtensionRootBlob(blob, layerWriterFactory, logger) 176 } else { 177 mainModule, err = FromBuildpackRootBlob(blob, layerWriterFactory, logger) 178 } 179 if err != nil { 180 return mainModule, depModules, errors.Wrapf(err, "reading %s", kind) 181 } 182 } 183 184 return mainModule, depModules, nil 185 } 186 187 func fromOCILayoutBlob(blob blob.Blob, kind string) (mainModule BuildModule, depModules []BuildModule, err error) { 188 switch kind { 189 case KindBuildpack: 190 mainModule, depModules, err = BuildpacksFromOCILayoutBlob(blob) 191 case KindExtension: 192 mainModule, err = ExtensionsFromOCILayoutBlob(blob) 193 default: 194 return nil, nil, fmt.Errorf("unknown module kind: %s", kind) 195 } 196 if err != nil { 197 return nil, nil, err 198 } 199 return mainModule, depModules, nil 200 } 201 202 func extractPackaged(ctx context.Context, kind string, pkgImageRef string, fetcher ImageFetcher, fetchOptions image.FetchOptions) (mainModule BuildModule, depModules []BuildModule, err error) { 203 pkgImage, err := fetcher.Fetch(ctx, pkgImageRef, fetchOptions) 204 if err != nil { 205 return nil, nil, errors.Wrapf(err, "fetching image") 206 } 207 208 switch kind { 209 case KindBuildpack: 210 mainModule, depModules, err = extractBuildpacks(pkgImage) 211 case KindExtension: 212 mainModule, err = extractExtensions(pkgImage) 213 default: 214 return nil, nil, fmt.Errorf("unknown module kind: %s", kind) 215 } 216 if err != nil { 217 return nil, nil, errors.Wrapf(err, "extracting %ss from %s", kind, style.Symbol(pkgImageRef)) 218 } 219 return mainModule, depModules, nil 220 }