github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/client.go (about)

     1  /*
     2  Package client provides all the functionally provided by pack as a library through a go api.
     3  
     4  # Prerequisites
     5  
     6  In order to use most functionality, you will need an OCI runtime such as Docker or podman installed.
     7  
     8  # References
     9  
    10  This package provides functionality to create and manipulate all artifacts outlined in the Cloud Native Buildpacks specification.
    11  An introduction to these artifacts and their usage can be found at https://buildpacks.io/docs/.
    12  
    13  The formal specification of the pack platform provides can be found at: https://github.com/buildpacks/spec.
    14  */
    15  package client
    16  
    17  import (
    18  	"context"
    19  	"os"
    20  	"path/filepath"
    21  
    22  	"github.com/buildpacks/imgutil"
    23  	"github.com/buildpacks/imgutil/local"
    24  	"github.com/buildpacks/imgutil/remote"
    25  	dockerClient "github.com/docker/docker/client"
    26  	"github.com/google/go-containerregistry/pkg/authn"
    27  	"github.com/pkg/errors"
    28  
    29  	"github.com/buildpacks/pack"
    30  	"github.com/buildpacks/pack/internal/build"
    31  	iconfig "github.com/buildpacks/pack/internal/config"
    32  	"github.com/buildpacks/pack/internal/style"
    33  	"github.com/buildpacks/pack/pkg/blob"
    34  	"github.com/buildpacks/pack/pkg/buildpack"
    35  	"github.com/buildpacks/pack/pkg/image"
    36  	"github.com/buildpacks/pack/pkg/index"
    37  	"github.com/buildpacks/pack/pkg/logging"
    38  )
    39  
    40  const (
    41  	// Env variable to set the root folder for manifest list local storage
    42  	xdgRuntimePath = "XDG_RUNTIME_DIR"
    43  )
    44  
    45  //go:generate mockgen -package testmocks -destination ../testmocks/mock_docker_client.go github.com/docker/docker/client CommonAPIClient
    46  
    47  //go:generate mockgen -package testmocks -destination ../testmocks/mock_image_fetcher.go github.com/buildpacks/pack/pkg/client ImageFetcher
    48  
    49  // ImageFetcher is an interface representing the ability to fetch local and remote images.
    50  type ImageFetcher interface {
    51  	// Fetch fetches an image by resolving it both remotely and locally depending on provided parameters.
    52  	// The pull behavior is dictated by the pullPolicy, which can have the following behavior
    53  	//   - PullNever: try to use the daemon to return a `local.Image`.
    54  	//   - PullIfNotPResent: try look to use the daemon to return a `local.Image`, if none is found  fetch a remote image.
    55  	//   - PullAlways: it will only try to fetch a remote image.
    56  	//
    57  	// These PullPolicies that these interact with the daemon argument.
    58  	// PullIfNotPresent and daemon = false, gives us the same behavior as PullAlways.
    59  	// There is a single invalid configuration, PullNever and daemon = false, this will always fail.
    60  	Fetch(ctx context.Context, name string, options image.FetchOptions) (imgutil.Image, error)
    61  
    62  	// CheckReadAccess verifies if an image is accessible with read permissions
    63  	// When FetchOptions.Daemon is true and the image doesn't exist in the daemon,
    64  	// the behavior is dictated by the pull policy, which can have the following behavior
    65  	//   - PullNever: returns false
    66  	//   - PullAlways Or PullIfNotPresent: it will check read access for the remote image.
    67  	// When FetchOptions.Daemon is false it will check read access for the remote image.
    68  	CheckReadAccess(repo string, options image.FetchOptions) bool
    69  }
    70  
    71  //go:generate mockgen -package testmocks -destination ../testmocks/mock_blob_downloader.go github.com/buildpacks/pack/pkg/client BlobDownloader
    72  
    73  // BlobDownloader is an interface for collecting both remote and local assets as blobs.
    74  type BlobDownloader interface {
    75  	// Download collects both local and remote assets and provides a blob object
    76  	// used to read asset contents.
    77  	Download(ctx context.Context, pathOrURI string) (blob.Blob, error)
    78  }
    79  
    80  //go:generate mockgen -package testmocks -destination ../testmocks/mock_image_factory.go github.com/buildpacks/pack/pkg/client ImageFactory
    81  
    82  // ImageFactory is an interface representing the ability to create a new OCI image.
    83  type ImageFactory interface {
    84  	// NewImage initializes an image object with required settings so that it
    85  	// can be written either locally or to a registry.
    86  	NewImage(repoName string, local bool, imageOS string) (imgutil.Image, error)
    87  }
    88  
    89  //go:generate mockgen -package testmocks -destination ../testmocks/mock_index_factory.go github.com/buildpacks/pack/pkg/client IndexFactory
    90  
    91  // IndexFactory is an interface representing the ability to create a ImageIndex/ManifestList.
    92  type IndexFactory interface {
    93  	// Exists return true if the given index exits in the local storage
    94  	Exists(repoName string) bool
    95  	// CreateIndex creates ManifestList locally
    96  	CreateIndex(repoName string, opts ...imgutil.IndexOption) (imgutil.ImageIndex, error)
    97  	// LoadIndex loads ManifestList from local storage with the given name
    98  	LoadIndex(reponame string, opts ...imgutil.IndexOption) (imgutil.ImageIndex, error)
    99  	// FetchIndex fetches ManifestList from Registry with the given name
   100  	FetchIndex(name string, opts ...imgutil.IndexOption) (imgutil.ImageIndex, error)
   101  	// FindIndex will find Index locally then on remote
   102  	FindIndex(name string, opts ...imgutil.IndexOption) (imgutil.ImageIndex, error)
   103  }
   104  
   105  //go:generate mockgen -package testmocks -destination ../testmocks/mock_buildpack_downloader.go github.com/buildpacks/pack/pkg/client BuildpackDownloader
   106  
   107  // BuildpackDownloader is an interface for downloading and extracting buildpacks from various sources
   108  type BuildpackDownloader interface {
   109  	// Download parses a buildpack URI and downloads the buildpack and any dependencies buildpacks from the appropriate source
   110  	Download(ctx context.Context, buildpackURI string, opts buildpack.DownloadOptions) (buildpack.BuildModule, []buildpack.BuildModule, error)
   111  }
   112  
   113  // Client is an orchestration object, it contains all parameters needed to
   114  // build an app image using Cloud Native Buildpacks.
   115  // All settings on this object should be changed through ClientOption functions.
   116  type Client struct {
   117  	logger logging.Logger
   118  	docker DockerClient
   119  
   120  	keychain            authn.Keychain
   121  	imageFactory        ImageFactory
   122  	imageFetcher        ImageFetcher
   123  	indexFactory        IndexFactory
   124  	downloader          BlobDownloader
   125  	lifecycleExecutor   LifecycleExecutor
   126  	buildpackDownloader BuildpackDownloader
   127  
   128  	experimental    bool
   129  	registryMirrors map[string]string
   130  	version         string
   131  }
   132  
   133  // Option is a type of function that mutate settings on the client.
   134  // Values in these functions are set through currying.
   135  type Option func(c *Client)
   136  
   137  // WithLogger supply your own logger.
   138  func WithLogger(l logging.Logger) Option {
   139  	return func(c *Client) {
   140  		c.logger = l
   141  	}
   142  }
   143  
   144  // WithImageFactory supply your own image factory.
   145  func WithImageFactory(f ImageFactory) Option {
   146  	return func(c *Client) {
   147  		c.imageFactory = f
   148  	}
   149  }
   150  
   151  // WithIndexFactory supply your own index factory
   152  func WithIndexFactory(f IndexFactory) Option {
   153  	return func(c *Client) {
   154  		c.indexFactory = f
   155  	}
   156  }
   157  
   158  // WithFetcher supply your own Fetcher.
   159  // A Fetcher retrieves both local and remote images to make them available.
   160  func WithFetcher(f ImageFetcher) Option {
   161  	return func(c *Client) {
   162  		c.imageFetcher = f
   163  	}
   164  }
   165  
   166  // WithDownloader supply your own downloader.
   167  // A Downloader is used to gather buildpacks from both remote urls, or local sources.
   168  func WithDownloader(d BlobDownloader) Option {
   169  	return func(c *Client) {
   170  		c.downloader = d
   171  	}
   172  }
   173  
   174  // WithBuildpackDownloader supply your own BuildpackDownloader.
   175  // A BuildpackDownloader is used to gather buildpacks from both remote urls, or local sources.
   176  func WithBuildpackDownloader(d BuildpackDownloader) Option {
   177  	return func(c *Client) {
   178  		c.buildpackDownloader = d
   179  	}
   180  }
   181  
   182  // Deprecated: use WithDownloader instead.
   183  //
   184  // WithCacheDir supply your own cache directory.
   185  func WithCacheDir(path string) Option {
   186  	return func(c *Client) {
   187  		c.downloader = blob.NewDownloader(c.logger, path)
   188  	}
   189  }
   190  
   191  // WithDockerClient supply your own docker client.
   192  func WithDockerClient(docker DockerClient) Option {
   193  	return func(c *Client) {
   194  		c.docker = docker
   195  	}
   196  }
   197  
   198  // WithExperimental sets whether experimental features should be enabled.
   199  func WithExperimental(experimental bool) Option {
   200  	return func(c *Client) {
   201  		c.experimental = experimental
   202  	}
   203  }
   204  
   205  // WithRegistryMirrors sets mirrors to pull images from.
   206  func WithRegistryMirrors(registryMirrors map[string]string) Option {
   207  	return func(c *Client) {
   208  		c.registryMirrors = registryMirrors
   209  	}
   210  }
   211  
   212  // WithKeychain sets keychain of credentials to image registries
   213  func WithKeychain(keychain authn.Keychain) Option {
   214  	return func(c *Client) {
   215  		c.keychain = keychain
   216  	}
   217  }
   218  
   219  const DockerAPIVersion = "1.38"
   220  
   221  // NewClient allocates and returns a Client configured with the specified options.
   222  func NewClient(opts ...Option) (*Client, error) {
   223  	client := &Client{
   224  		version:  pack.Version,
   225  		keychain: authn.DefaultKeychain,
   226  	}
   227  
   228  	for _, opt := range opts {
   229  		opt(client)
   230  	}
   231  
   232  	if client.logger == nil {
   233  		client.logger = logging.NewSimpleLogger(os.Stderr)
   234  	}
   235  
   236  	if client.docker == nil {
   237  		var err error
   238  		client.docker, err = dockerClient.NewClientWithOpts(
   239  			dockerClient.FromEnv,
   240  			dockerClient.WithVersion(DockerAPIVersion),
   241  		)
   242  		if err != nil {
   243  			return nil, errors.Wrap(err, "creating docker client")
   244  		}
   245  	}
   246  
   247  	if client.downloader == nil {
   248  		packHome, err := iconfig.PackHome()
   249  		if err != nil {
   250  			return nil, errors.Wrap(err, "getting pack home")
   251  		}
   252  		client.downloader = blob.NewDownloader(client.logger, filepath.Join(packHome, "download-cache"))
   253  	}
   254  
   255  	if client.imageFetcher == nil {
   256  		client.imageFetcher = image.NewFetcher(client.logger, client.docker, image.WithRegistryMirrors(client.registryMirrors), image.WithKeychain(client.keychain))
   257  	}
   258  
   259  	if client.imageFactory == nil {
   260  		client.imageFactory = &imageFactory{
   261  			dockerClient: client.docker,
   262  			keychain:     client.keychain,
   263  		}
   264  	}
   265  
   266  	if client.indexFactory == nil {
   267  		packHome, err := iconfig.PackHome()
   268  		if err != nil {
   269  			return nil, errors.Wrap(err, "getting pack home")
   270  		}
   271  		indexRootStoragePath := filepath.Join(packHome, "manifests")
   272  
   273  		if xdgPath, ok := os.LookupEnv(xdgRuntimePath); ok {
   274  			indexRootStoragePath = xdgPath
   275  		}
   276  		client.indexFactory = index.NewIndexFactory(client.keychain, indexRootStoragePath)
   277  	}
   278  
   279  	if client.buildpackDownloader == nil {
   280  		client.buildpackDownloader = buildpack.NewDownloader(
   281  			client.logger,
   282  			client.imageFetcher,
   283  			client.downloader,
   284  			&registryResolver{
   285  				logger: client.logger,
   286  			},
   287  		)
   288  	}
   289  
   290  	client.lifecycleExecutor = build.NewLifecycleExecutor(client.logger, client.docker)
   291  
   292  	return client, nil
   293  }
   294  
   295  type registryResolver struct {
   296  	logger logging.Logger
   297  }
   298  
   299  func (r *registryResolver) Resolve(registryName, bpName string) (string, error) {
   300  	cache, err := getRegistry(r.logger, registryName)
   301  	if err != nil {
   302  		return "", errors.Wrapf(err, "lookup registry %s", style.Symbol(registryName))
   303  	}
   304  
   305  	regBuildpack, err := cache.LocateBuildpack(bpName)
   306  	if err != nil {
   307  		return "", errors.Wrapf(err, "lookup buildpack %s", style.Symbol(bpName))
   308  	}
   309  
   310  	return regBuildpack.Address, nil
   311  }
   312  
   313  type imageFactory struct {
   314  	dockerClient local.DockerClient
   315  	keychain     authn.Keychain
   316  }
   317  
   318  func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (imgutil.Image, error) {
   319  	platform := imgutil.Platform{OS: imageOS}
   320  
   321  	if daemon {
   322  		return local.NewImage(repoName, f.dockerClient, local.WithDefaultPlatform(platform))
   323  	}
   324  
   325  	return remote.NewImage(repoName, f.keychain, remote.WithDefaultPlatform(platform))
   326  }