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 }