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

     1  package buildpack
     2  
     3  import (
     4  	"io"
     5  	"strings"
     6  	"sync"
     7  
     8  	"github.com/pkg/errors"
     9  
    10  	"github.com/buildpacks/pack/internal/style"
    11  	"github.com/buildpacks/pack/pkg/dist"
    12  )
    13  
    14  type Package interface {
    15  	Label(name string) (value string, err error)
    16  	GetLayer(diffID string) (io.ReadCloser, error)
    17  }
    18  
    19  type syncPkg struct {
    20  	mu  sync.Mutex
    21  	pkg Package
    22  }
    23  
    24  func (s *syncPkg) Label(name string) (value string, err error) {
    25  	s.mu.Lock()
    26  	defer s.mu.Unlock()
    27  	return s.pkg.Label(name)
    28  }
    29  
    30  func (s *syncPkg) GetLayer(diffID string) (io.ReadCloser, error) {
    31  	s.mu.Lock()
    32  	defer s.mu.Unlock()
    33  	return s.pkg.GetLayer(diffID)
    34  }
    35  
    36  // extractBuildpacks when provided a flattened buildpack package containing N buildpacks,
    37  // will return N modules: 1 module with a single tar containing ALL N buildpacks, and N-1 modules with empty tar files.
    38  func extractBuildpacks(pkg Package) (mainBP BuildModule, depBPs []BuildModule, err error) {
    39  	pkg = &syncPkg{pkg: pkg}
    40  	md := &Metadata{}
    41  	if found, err := dist.GetLabel(pkg, MetadataLabel, md); err != nil {
    42  		return nil, nil, err
    43  	} else if !found {
    44  		return nil, nil, errors.Errorf(
    45  			"could not find label %s",
    46  			style.Symbol(MetadataLabel),
    47  		)
    48  	}
    49  
    50  	pkgLayers := dist.ModuleLayers{}
    51  	ok, err := dist.GetLabel(pkg, dist.BuildpackLayersLabel, &pkgLayers)
    52  	if err != nil {
    53  		return nil, nil, err
    54  	}
    55  
    56  	if !ok {
    57  		return nil, nil, errors.Errorf(
    58  			"could not find label %s",
    59  			style.Symbol(dist.BuildpackLayersLabel),
    60  		)
    61  	}
    62  
    63  	// Example `dist.ModuleLayers{}`:
    64  	//
    65  	//{
    66  	//  "samples/hello-moon": {
    67  	//    "0.0.1": {
    68  	//      "api": "0.2",
    69  	//      "stacks": [
    70  	//        {
    71  	//          "id": "*"
    72  	//        }
    73  	//      ],
    74  	//      "layerDiffID": "sha256:37ab46923c181aa5fb27c9a23479a38aec2679237f35a0ea4115e5ae81a17bba",
    75  	//      "homepage": "https://github.com/buildpacks/samples/tree/main/buildpacks/hello-moon",
    76  	//      "name": "Hello Moon Buildpack"
    77  	//    }
    78  	//  }
    79  	//}
    80  
    81  	// If the package is a flattened buildpack, the first buildpack in the package returns all the tar content,
    82  	// and subsequent buildpacks return an empty tar.
    83  	var processedDiffIDs = make(map[string]bool)
    84  	for bpID, v := range pkgLayers {
    85  		for bpVersion, bpInfo := range v {
    86  			desc := dist.BuildpackDescriptor{
    87  				WithAPI: bpInfo.API,
    88  				WithInfo: dist.ModuleInfo{
    89  					ID:       bpID,
    90  					Version:  bpVersion,
    91  					Homepage: bpInfo.Homepage,
    92  					Name:     bpInfo.Name,
    93  				},
    94  				WithStacks:  bpInfo.Stacks,
    95  				WithTargets: bpInfo.Targets,
    96  				WithOrder:   bpInfo.Order,
    97  			}
    98  
    99  			diffID := bpInfo.LayerDiffID // Allow use in closure
   100  
   101  			var openerFunc func() (io.ReadCloser, error)
   102  			if _, ok := processedDiffIDs[diffID]; ok {
   103  				// We already processed a layer with this diffID, so the module must be flattened;
   104  				// return an empty reader to avoid multiple tars with the same content.
   105  				openerFunc = func() (io.ReadCloser, error) {
   106  					return io.NopCloser(strings.NewReader("")), nil
   107  				}
   108  			} else {
   109  				openerFunc = func() (io.ReadCloser, error) {
   110  					rc, err := pkg.GetLayer(diffID)
   111  					if err != nil {
   112  						return nil, errors.Wrapf(err,
   113  							"extracting buildpack %s layer (diffID %s)",
   114  							style.Symbol(desc.Info().FullName()),
   115  							style.Symbol(diffID),
   116  						)
   117  					}
   118  					return rc, nil
   119  				}
   120  				processedDiffIDs[diffID] = true
   121  			}
   122  
   123  			b := &openerBlob{
   124  				opener: openerFunc,
   125  			}
   126  
   127  			if desc.Info().Match(md.ModuleInfo) { // Current module is the order buildpack of the package
   128  				mainBP = FromBlob(&desc, b)
   129  			} else {
   130  				depBPs = append(depBPs, FromBlob(&desc, b))
   131  			}
   132  		}
   133  	}
   134  
   135  	return mainBP, depBPs, nil
   136  }
   137  
   138  func extractExtensions(pkg Package) (mainExt BuildModule, err error) {
   139  	pkg = &syncPkg{pkg: pkg}
   140  	md := &Metadata{}
   141  	if found, err := dist.GetLabel(pkg, MetadataLabel, md); err != nil {
   142  		return nil, err
   143  	} else if !found {
   144  		return nil, errors.Errorf(
   145  			"could not find label %s",
   146  			style.Symbol(MetadataLabel),
   147  		)
   148  	}
   149  
   150  	pkgLayers := dist.ModuleLayers{}
   151  	ok, err := dist.GetLabel(pkg, dist.ExtensionLayersLabel, &pkgLayers)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	if !ok {
   157  		return nil, errors.Errorf(
   158  			"could not find label %s",
   159  			style.Symbol(dist.ExtensionLayersLabel),
   160  		)
   161  	}
   162  	for extID, v := range pkgLayers {
   163  		for extVersion, extInfo := range v {
   164  			desc := dist.ExtensionDescriptor{
   165  				WithAPI: extInfo.API,
   166  				WithInfo: dist.ModuleInfo{
   167  					ID:       extID,
   168  					Version:  extVersion,
   169  					Homepage: extInfo.Homepage,
   170  					Name:     extInfo.Name,
   171  				},
   172  			}
   173  
   174  			diffID := extInfo.LayerDiffID // Allow use in closure
   175  			b := &openerBlob{
   176  				opener: func() (io.ReadCloser, error) {
   177  					rc, err := pkg.GetLayer(diffID)
   178  					if err != nil {
   179  						return nil, errors.Wrapf(err,
   180  							"extracting extension %s layer (diffID %s)",
   181  							style.Symbol(desc.Info().FullName()),
   182  							style.Symbol(diffID),
   183  						)
   184  					}
   185  					return rc, nil
   186  				},
   187  			}
   188  
   189  			mainExt = FromBlob(&desc, b)
   190  		}
   191  	}
   192  	return mainExt, nil
   193  }
   194  
   195  type openerBlob struct {
   196  	opener func() (io.ReadCloser, error)
   197  }
   198  
   199  func (b *openerBlob) Open() (io.ReadCloser, error) {
   200  	return b.opener()
   201  }