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  }