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

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  
     8  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
     9  
    10  	"github.com/buildpacks/pack/internal/style"
    11  	"github.com/buildpacks/pack/pkg/buildpack"
    12  	"github.com/buildpacks/pack/pkg/dist"
    13  	"github.com/buildpacks/pack/pkg/image"
    14  )
    15  
    16  type BuildpackInfo struct {
    17  	BuildpackMetadata buildpack.Metadata
    18  	Buildpacks        []dist.ModuleInfo
    19  	Order             dist.Order
    20  	BuildpackLayers   dist.ModuleLayers
    21  	Location          buildpack.LocatorType
    22  }
    23  
    24  type InspectBuildpackOptions struct {
    25  	BuildpackName string
    26  	Daemon        bool
    27  	Registry      string
    28  }
    29  
    30  type ImgWrapper struct {
    31  	v1.ImageConfig
    32  }
    33  
    34  func (iw ImgWrapper) Label(name string) (string, error) {
    35  	return iw.Labels[name], nil
    36  }
    37  
    38  func (c *Client) InspectBuildpack(opts InspectBuildpackOptions) (*BuildpackInfo, error) {
    39  	locatorType, err := buildpack.GetLocatorType(opts.BuildpackName, "", []dist.ModuleInfo{})
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	var layersMd dist.ModuleLayers
    44  	var buildpackMd buildpack.Metadata
    45  
    46  	switch locatorType {
    47  	case buildpack.RegistryLocator:
    48  		buildpackMd, layersMd, err = metadataFromRegistry(c, opts.BuildpackName, opts.Registry)
    49  	case buildpack.PackageLocator:
    50  		buildpackMd, layersMd, err = metadataFromImage(c, opts.BuildpackName, opts.Daemon)
    51  	case buildpack.URILocator:
    52  		buildpackMd, layersMd, err = metadataFromArchive(c.downloader, opts.BuildpackName)
    53  	default:
    54  		return nil, fmt.Errorf("unable to handle locator %q: for buildpack %q", locatorType, opts.BuildpackName)
    55  	}
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return &BuildpackInfo{
    61  		BuildpackMetadata: buildpackMd,
    62  		BuildpackLayers:   layersMd,
    63  		Order:             extractOrder(buildpackMd),
    64  		Buildpacks:        extractBuildpacks(layersMd),
    65  		Location:          locatorType,
    66  	}, nil
    67  }
    68  
    69  func metadataFromRegistry(client *Client, name, registry string) (buildpackMd buildpack.Metadata, layersMd dist.ModuleLayers, err error) {
    70  	registryCache, err := getRegistry(client.logger, registry)
    71  	if err != nil {
    72  		return buildpack.Metadata{}, dist.ModuleLayers{}, fmt.Errorf("invalid registry %s: %q", registry, err)
    73  	}
    74  
    75  	registryBp, err := registryCache.LocateBuildpack(name)
    76  	if err != nil {
    77  		return buildpack.Metadata{}, dist.ModuleLayers{}, fmt.Errorf("unable to find %s in registry: %q", style.Symbol(name), err)
    78  	}
    79  	buildpackMd, layersMd, err = metadataFromImage(client, registryBp.Address, false)
    80  	if err != nil {
    81  		return buildpack.Metadata{}, dist.ModuleLayers{}, fmt.Errorf("error pulling registry specified image: %s", err)
    82  	}
    83  	return buildpackMd, layersMd, nil
    84  }
    85  
    86  func metadataFromArchive(downloader BlobDownloader, path string) (buildpackMd buildpack.Metadata, layersMd dist.ModuleLayers, err error) {
    87  	imgBlob, err := downloader.Download(context.Background(), path)
    88  	if err != nil {
    89  		return buildpack.Metadata{}, dist.ModuleLayers{}, fmt.Errorf("unable to download archive: %q", err)
    90  	}
    91  
    92  	config, err := buildpack.ConfigFromOCILayoutBlob(imgBlob)
    93  	if err != nil {
    94  		return buildpack.Metadata{}, dist.ModuleLayers{}, fmt.Errorf("unable to fetch config from buildpack blob: %q", err)
    95  	}
    96  	wrapper := ImgWrapper{config}
    97  
    98  	if _, err := dist.GetLabel(wrapper, dist.BuildpackLayersLabel, &layersMd); err != nil {
    99  		return buildpack.Metadata{}, dist.ModuleLayers{}, err
   100  	}
   101  
   102  	if _, err := dist.GetLabel(wrapper, buildpack.MetadataLabel, &buildpackMd); err != nil {
   103  		return buildpack.Metadata{}, dist.ModuleLayers{}, err
   104  	}
   105  	return buildpackMd, layersMd, nil
   106  }
   107  
   108  func metadataFromImage(client *Client, name string, daemon bool) (buildpackMd buildpack.Metadata, layersMd dist.ModuleLayers, err error) {
   109  	imageName := buildpack.ParsePackageLocator(name)
   110  	img, err := client.imageFetcher.Fetch(context.Background(), imageName, image.FetchOptions{Daemon: daemon, PullPolicy: image.PullNever})
   111  	if err != nil {
   112  		return buildpack.Metadata{}, dist.ModuleLayers{}, err
   113  	}
   114  	if _, err := dist.GetLabel(img, dist.BuildpackLayersLabel, &layersMd); err != nil {
   115  		return buildpack.Metadata{}, dist.ModuleLayers{}, fmt.Errorf("unable to get image label %s: %q", dist.BuildpackLayersLabel, err)
   116  	}
   117  
   118  	if _, err := dist.GetLabel(img, buildpack.MetadataLabel, &buildpackMd); err != nil {
   119  		return buildpack.Metadata{}, dist.ModuleLayers{}, fmt.Errorf("unable to get image label %s: %q", buildpack.MetadataLabel, err)
   120  	}
   121  	return buildpackMd, layersMd, nil
   122  }
   123  
   124  func extractOrder(buildpackMd buildpack.Metadata) dist.Order {
   125  	return dist.Order{
   126  		{
   127  			Group: []dist.ModuleRef{
   128  				{
   129  					ModuleInfo: buildpackMd.ModuleInfo,
   130  				},
   131  			},
   132  		},
   133  	}
   134  }
   135  
   136  func extractBuildpacks(layersMd dist.ModuleLayers) []dist.ModuleInfo {
   137  	result := []dist.ModuleInfo{}
   138  	buildpackSet := map[*dist.ModuleInfo]bool{}
   139  
   140  	for buildpackID, buildpackMap := range layersMd {
   141  		for version, layerInfo := range buildpackMap {
   142  			bp := dist.ModuleInfo{
   143  				ID:       buildpackID,
   144  				Name:     layerInfo.Name,
   145  				Version:  version,
   146  				Homepage: layerInfo.Homepage,
   147  			}
   148  			buildpackSet[&bp] = true
   149  		}
   150  	}
   151  
   152  	for currentBuildpack := range buildpackSet {
   153  		result = append(result, *currentBuildpack)
   154  	}
   155  
   156  	sort.Slice(result, func(i int, j int) bool {
   157  		switch {
   158  		case result[i].ID < result[j].ID:
   159  			return true
   160  		case result[i].ID == result[j].ID:
   161  			return result[i].Version < result[j].Version
   162  		default:
   163  			return false
   164  		}
   165  	})
   166  	return result
   167  }