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

     1  package buildpack
     2  
     3  import (
     4  	"archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/buildpacks/lifecycle/buildpack"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/buildpacks/pack/internal/style"
    16  	"github.com/buildpacks/pack/pkg/archive"
    17  	"github.com/buildpacks/pack/pkg/logging"
    18  )
    19  
    20  type BuildModuleWriter struct {
    21  	logger  logging.Logger
    22  	factory archive.TarWriterFactory
    23  }
    24  
    25  // NewBuildModuleWriter creates a BuildModule writer
    26  func NewBuildModuleWriter(logger logging.Logger, factory archive.TarWriterFactory) *BuildModuleWriter {
    27  	return &BuildModuleWriter{
    28  		logger:  logger,
    29  		factory: factory,
    30  	}
    31  }
    32  
    33  // NToLayerTar creates a tar file containing the all the Buildpacks given, but excluding the ones which FullName() is
    34  // in the exclude list. It returns the path to the tar file, the list of Buildpacks that were excluded, and any error
    35  func (b *BuildModuleWriter) NToLayerTar(tarPath, filename string, modules []BuildModule, exclude map[string]struct{}) (string, []BuildModule, error) {
    36  	layerTar := filepath.Join(tarPath, fmt.Sprintf("%s.tar", filename))
    37  	tarFile, err := os.Create(layerTar)
    38  	b.logger.Debugf("creating file %s", style.Symbol(layerTar))
    39  	if err != nil {
    40  		return "", nil, errors.Wrap(err, "create file for tar")
    41  	}
    42  
    43  	defer tarFile.Close()
    44  	tw := b.factory.NewWriter(tarFile)
    45  	defer tw.Close()
    46  
    47  	parentFolderAdded := map[string]bool{}
    48  	duplicated := map[string]bool{}
    49  
    50  	var buildModuleExcluded []BuildModule
    51  	for _, module := range modules {
    52  		if _, ok := exclude[module.Descriptor().Info().FullName()]; !ok {
    53  			if !duplicated[module.Descriptor().Info().FullName()] {
    54  				duplicated[module.Descriptor().Info().FullName()] = true
    55  				b.logger.Debugf("adding %s", style.Symbol(module.Descriptor().Info().FullName()))
    56  
    57  				if err := b.writeBuildModuleToTar(tw, module, &parentFolderAdded); err != nil {
    58  					return "", nil, errors.Wrapf(err, "adding %s", style.Symbol(module.Descriptor().Info().FullName()))
    59  				}
    60  				rootPath := processRootPath(module)
    61  				if !parentFolderAdded[rootPath] {
    62  					parentFolderAdded[rootPath] = true
    63  				}
    64  			} else {
    65  				b.logger.Debugf("skipping %s, it was already added", style.Symbol(module.Descriptor().Info().FullName()))
    66  			}
    67  		} else {
    68  			b.logger.Debugf("excluding %s from being flattened", style.Symbol(module.Descriptor().Info().FullName()))
    69  			buildModuleExcluded = append(buildModuleExcluded, module)
    70  		}
    71  	}
    72  
    73  	b.logger.Debugf("%s was created successfully", style.Symbol(layerTar))
    74  	return layerTar, buildModuleExcluded, nil
    75  }
    76  
    77  // writeBuildModuleToTar writes the content of the given tar file into the writer, skipping the folders that were already added
    78  func (b *BuildModuleWriter) writeBuildModuleToTar(tw archive.TarWriter, module BuildModule, parentFolderAdded *map[string]bool) error {
    79  	var (
    80  		rc  io.ReadCloser
    81  		err error
    82  	)
    83  
    84  	if rc, err = module.Open(); err != nil {
    85  		return err
    86  	}
    87  	defer rc.Close()
    88  
    89  	tr := tar.NewReader(rc)
    90  
    91  	for {
    92  		header, err := tr.Next()
    93  		if err == io.EOF {
    94  			break
    95  		}
    96  		if err != nil {
    97  			return errors.Wrap(err, "failed to get next tar entry")
    98  		}
    99  
   100  		if (*parentFolderAdded)[header.Name] {
   101  			b.logger.Debugf("folder %s was already added, skipping it", style.Symbol(header.Name))
   102  			continue
   103  		}
   104  
   105  		err = tw.WriteHeader(header)
   106  		if err != nil {
   107  			return errors.Wrapf(err, "failed to write header for '%s'", header.Name)
   108  		}
   109  
   110  		_, err = io.Copy(tw, tr)
   111  		if err != nil {
   112  			return errors.Wrapf(err, "failed to write contents to '%s'", header.Name)
   113  		}
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  func processRootPath(module BuildModule) string {
   120  	var bpFolder string
   121  	switch module.Descriptor().Kind() {
   122  	case buildpack.KindBuildpack:
   123  		bpFolder = "buildpacks"
   124  	case buildpack.KindExtension:
   125  		bpFolder = "extensions"
   126  	default:
   127  		bpFolder = "buildpacks"
   128  	}
   129  	bpInfo := module.Descriptor().Info()
   130  	rootPath := path.Join("/cnb", bpFolder, strings.ReplaceAll(bpInfo.ID, "/", "_"))
   131  	return rootPath
   132  }