github.com/YousefHaggyHeroku/pack@v1.5.5/internal/buildpackage/builder.go (about) 1 package buildpackage 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "io" 7 "io/ioutil" 8 "os" 9 10 "github.com/buildpacks/imgutil/layer" 11 12 "github.com/buildpacks/imgutil" 13 v1 "github.com/google/go-containerregistry/pkg/v1" 14 "github.com/google/go-containerregistry/pkg/v1/empty" 15 "github.com/google/go-containerregistry/pkg/v1/layout" 16 "github.com/google/go-containerregistry/pkg/v1/mutate" 17 "github.com/google/go-containerregistry/pkg/v1/tarball" 18 "github.com/pkg/errors" 19 20 "github.com/YousefHaggyHeroku/pack/internal/archive" 21 "github.com/YousefHaggyHeroku/pack/internal/dist" 22 "github.com/YousefHaggyHeroku/pack/internal/stack" 23 "github.com/YousefHaggyHeroku/pack/internal/style" 24 ) 25 26 type ImageFactory interface { 27 NewImage(repoName string, local bool) (imgutil.Image, error) 28 } 29 30 type WorkableImage interface { 31 SetOS(string) error 32 SetLabel(string, string) error 33 AddLayerWithDiffID(path, diffID string) error 34 } 35 36 type layoutImage struct { 37 v1.Image 38 } 39 40 func (i *layoutImage) SetLabel(key string, val string) error { 41 configFile, err := i.ConfigFile() 42 if err != nil { 43 return err 44 } 45 config := *configFile.Config.DeepCopy() 46 if config.Labels == nil { 47 config.Labels = map[string]string{} 48 } 49 config.Labels[key] = val 50 i.Image, err = mutate.Config(i.Image, config) 51 return err 52 } 53 54 func (i *layoutImage) SetOS(osVal string) error { 55 configFile, err := i.ConfigFile() 56 if err != nil { 57 return err 58 } 59 configFile.OS = osVal 60 i.Image, err = mutate.ConfigFile(i.Image, configFile) 61 return err 62 } 63 64 func (i *layoutImage) AddLayerWithDiffID(path, _ string) error { 65 tarLayer, err := tarball.LayerFromFile(path, tarball.WithCompressionLevel(gzip.DefaultCompression)) 66 if err != nil { 67 return err 68 } 69 i.Image, err = mutate.AppendLayers(i.Image, tarLayer) 70 if err != nil { 71 return errors.Wrap(err, "add layer") 72 } 73 return nil 74 } 75 76 type PackageBuilder struct { 77 buildpack dist.Buildpack 78 dependencies []dist.Buildpack 79 imageFactory ImageFactory 80 } 81 82 func NewBuilder(imageFactory ImageFactory) *PackageBuilder { 83 return &PackageBuilder{ 84 imageFactory: imageFactory, 85 } 86 } 87 88 func (b *PackageBuilder) SetBuildpack(buildpack dist.Buildpack) { 89 b.buildpack = buildpack 90 } 91 92 func (b *PackageBuilder) AddDependency(buildpack dist.Buildpack) { 93 b.dependencies = append(b.dependencies, buildpack) 94 } 95 96 func (b *PackageBuilder) finalizeImage(image WorkableImage, imageOS, tmpDir string) error { 97 if err := dist.SetLabel(image, MetadataLabel, &Metadata{ 98 BuildpackInfo: b.buildpack.Descriptor().Info, 99 Stacks: b.resolvedStacks(), 100 }); err != nil { 101 return err 102 } 103 104 if err := image.SetOS(imageOS); err != nil { 105 return err 106 } 107 108 if imageOS == "windows" { 109 if err := addWindowsShimBaseLayer(image, tmpDir); err != nil { 110 return err 111 } 112 } 113 114 bpLayers := dist.BuildpackLayers{} 115 for _, bp := range append(b.dependencies, b.buildpack) { 116 bpLayerTar, err := dist.BuildpackToLayerTar(tmpDir, bp) 117 if err != nil { 118 return err 119 } 120 121 diffID, err := dist.LayerDiffID(bpLayerTar) 122 if err != nil { 123 return errors.Wrapf(err, 124 "getting content hashes for buildpack %s", 125 style.Symbol(bp.Descriptor().Info.FullName()), 126 ) 127 } 128 129 if err := image.AddLayerWithDiffID(bpLayerTar, diffID.String()); err != nil { 130 return errors.Wrapf(err, "adding layer tar for buildpack %s", style.Symbol(bp.Descriptor().Info.FullName())) 131 } 132 133 dist.AddBuildpackToLayersMD(bpLayers, bp.Descriptor(), diffID.String()) 134 } 135 136 if err := dist.SetLabel(image, dist.BuildpackLayersLabel, bpLayers); err != nil { 137 return err 138 } 139 140 return nil 141 } 142 143 func addWindowsShimBaseLayer(image WorkableImage, tmpDir string) error { 144 baseLayerFile, err := ioutil.TempFile(tmpDir, "windows-baselayer") 145 if err != nil { 146 return err 147 } 148 defer baseLayerFile.Close() 149 150 baseLayer, err := layer.WindowsBaseLayer() 151 if err != nil { 152 return err 153 } 154 155 if _, err := io.Copy(baseLayerFile, baseLayer); err != nil { 156 return err 157 } 158 159 if err := baseLayerFile.Close(); err != nil { 160 return err 161 } 162 163 baseLayerPath := baseLayerFile.Name() 164 diffID, err := dist.LayerDiffID(baseLayerPath) 165 if err != nil { 166 return err 167 } 168 169 if err := image.AddLayerWithDiffID(baseLayerPath, diffID.String()); err != nil { 170 return err 171 } 172 173 return nil 174 } 175 176 func (b *PackageBuilder) validate() error { 177 if b.buildpack == nil { 178 return errors.New("buildpack must be set") 179 } 180 181 if err := validateBuildpacks(b.buildpack, b.dependencies); err != nil { 182 return err 183 } 184 185 if len(b.resolvedStacks()) == 0 { 186 return errors.Errorf("no compatible stacks among provided buildpacks") 187 } 188 189 return nil 190 } 191 192 func (b *PackageBuilder) resolvedStacks() []dist.Stack { 193 stacks := b.buildpack.Descriptor().Stacks 194 for _, bp := range b.dependencies { 195 bpd := bp.Descriptor() 196 197 if len(stacks) == 0 { 198 stacks = bpd.Stacks 199 } else if len(bpd.Stacks) > 0 { // skip over "meta-buildpacks" 200 stacks = stack.MergeCompatible(stacks, bpd.Stacks) 201 } 202 } 203 204 return stacks 205 } 206 207 func (b *PackageBuilder) SaveAsFile(path, imageOS string) error { 208 if err := b.validate(); err != nil { 209 return err 210 } 211 212 layoutImage := &layoutImage{ 213 Image: empty.Image, 214 } 215 216 tmpDir, err := ioutil.TempDir("", "package-buildpack") 217 if err != nil { 218 return err 219 } 220 defer os.RemoveAll(tmpDir) 221 222 if err := b.finalizeImage(layoutImage, imageOS, tmpDir); err != nil { 223 return err 224 } 225 226 layoutDir, err := ioutil.TempDir(tmpDir, "oci-layout") 227 if err != nil { 228 return errors.Wrap(err, "creating oci-layout temp dir") 229 } 230 231 p, err := layout.Write(layoutDir, empty.Index) 232 if err != nil { 233 return errors.Wrap(err, "writing index") 234 } 235 236 if err := p.AppendImage(layoutImage); err != nil { 237 return errors.Wrap(err, "writing layout") 238 } 239 240 outputFile, err := os.Create(path) 241 if err != nil { 242 return errors.Wrap(err, "creating output file") 243 } 244 defer outputFile.Close() 245 246 tw := tar.NewWriter(outputFile) 247 defer tw.Close() 248 249 return archive.WriteDirToTar(tw, layoutDir, "/", 0, 0, 0755, true, nil) 250 } 251 252 func (b *PackageBuilder) SaveAsImage(repoName string, publish bool, imageOS string) (imgutil.Image, error) { 253 if err := b.validate(); err != nil { 254 return nil, err 255 } 256 257 image, err := b.imageFactory.NewImage(repoName, !publish) 258 if err != nil { 259 return nil, errors.Wrapf(err, "creating image") 260 } 261 262 tmpDir, err := ioutil.TempDir("", "package-buildpack") 263 if err != nil { 264 return nil, err 265 } 266 defer os.RemoveAll(tmpDir) 267 268 if err := b.finalizeImage(image, imageOS, tmpDir); err != nil { 269 return nil, err 270 } 271 272 if err := image.Save(); err != nil { 273 return nil, err 274 } 275 276 return image, nil 277 } 278 279 func validateBuildpacks(mainBP dist.Buildpack, depBPs []dist.Buildpack) error { 280 depsWithRefs := map[string][]dist.BuildpackInfo{} 281 282 for _, bp := range depBPs { 283 depsWithRefs[bp.Descriptor().Info.FullName()] = nil 284 } 285 286 for _, bp := range append([]dist.Buildpack{mainBP}, depBPs...) { // List of everything 287 bpd := bp.Descriptor() 288 for _, orderEntry := range bpd.Order { 289 for _, groupEntry := range orderEntry.Group { 290 if _, ok := depsWithRefs[groupEntry.BuildpackInfo.FullName()]; !ok { 291 return errors.Errorf( 292 "buildpack %s references buildpack %s which is not present", 293 style.Symbol(bpd.Info.FullName()), 294 style.Symbol(groupEntry.FullName()), 295 ) 296 } 297 298 depsWithRefs[groupEntry.BuildpackInfo.FullName()] = append(depsWithRefs[groupEntry.BuildpackInfo.FullName()], bpd.Info) 299 } 300 } 301 } 302 303 for bp, refs := range depsWithRefs { 304 if len(refs) == 0 { 305 return errors.Errorf( 306 "buildpack %s is not used by buildpack %s", 307 style.Symbol(bp), 308 style.Symbol(mainBP.Descriptor().Info.FullName()), 309 ) 310 } 311 } 312 313 return nil 314 }