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 ®istryResolver{ 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 }