lifecycle/buildpack/layers.go

196 lines
4.5 KiB
Go

package buildpack
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"github.com/BurntSushi/toml"
"github.com/pkg/errors"
"github.com/buildpacks/lifecycle/launch"
"github.com/buildpacks/lifecycle/log"
)
type LayersDir struct {
Path string
layers []Layer
name string
Buildpack GroupElement
Store *StoreTOML
}
func ReadLayersDir(layersDir string, bp GroupElement, logger log.Logger) (LayersDir, error) {
path := filepath.Join(layersDir, launch.EscapeID(bp.ID))
logger.Debugf("Reading buildpack directory: %s", path)
bpDir := LayersDir{
name: bp.ID,
Path: path,
layers: []Layer{},
Buildpack: bp,
}
fis, err := os.ReadDir(path)
if err != nil && !os.IsNotExist(err) {
return LayersDir{}, err
}
names := map[string]struct{}{}
var tomls []string
for _, fi := range fis {
logger.Debugf("Reading buildpack directory item: %s", fi.Name())
if fi.IsDir() {
bpDir.layers = append(bpDir.layers, *bpDir.NewLayer(fi.Name(), bp.API, logger))
names[fi.Name()] = struct{}{}
continue
}
if strings.HasSuffix(fi.Name(), ".toml") {
tomls = append(tomls, filepath.Join(path, fi.Name()))
}
}
for _, tf := range tomls {
name := strings.TrimSuffix(filepath.Base(tf), ".toml")
if name == "store" {
var bpStore StoreTOML
_, err := toml.DecodeFile(tf, &bpStore)
if err != nil {
return LayersDir{}, errors.Wrapf(err, "failed decoding store.toml for buildpack %q", bp.ID)
}
bpDir.Store = &bpStore
continue
}
if name == "launch" {
// don't treat launch.toml as a layer
continue
}
if name == "build" {
continue
}
if _, ok := names[name]; !ok {
bpDir.layers = append(bpDir.layers, *bpDir.NewLayer(name, bp.API, logger))
}
}
sort.Slice(bpDir.layers, func(i, j int) bool {
return bpDir.layers[i].identifier < bpDir.layers[j].identifier
})
return bpDir, nil
}
func (d *LayersDir) FindLayers(f func(layer Layer) bool) []Layer {
var selectedLayers []Layer
for _, l := range d.layers {
if f(l) {
selectedLayers = append(selectedLayers, l)
}
}
return selectedLayers
}
func MadeLaunch(l Layer) bool {
md, err := l.Read()
return err == nil && md.Launch
}
func MadeCached(l Layer) bool {
md, err := l.Read()
return err == nil && md.Cache
}
func Malformed(l Layer) bool {
_, err := l.Read()
return err != nil
}
func (d *LayersDir) NewLayer(name, buildpackAPI string, logger log.Logger) *Layer {
return &Layer{
layerDir: layerDir{
path: filepath.Join(d.Path, name),
identifier: fmt.Sprintf("%s:%s", d.Buildpack.ID, name),
},
api: buildpackAPI,
logger: logger,
}
}
type Layer struct { // FIXME: need to refactor so api and logger won't be part of this struct
layerDir
api string
logger log.Logger
}
type layerDir struct {
// identifier takes the form "buildpack-id:layer-name" and is used for
// sorting layers,
// logging information about a layer, and
// creating the temporary tar file that is the basis of the layer.
identifier string
// path takes the form <layers>/<buildpack-id>/<layer-name>
path string
}
func (l *Layer) Name() string {
return filepath.Base(l.path)
}
func (l *Layer) HasLocalContents() bool {
_, err := os.ReadDir(l.path)
return !os.IsNotExist(err)
}
func (l *Layer) Identifier() string {
return l.identifier
}
func (l *Layer) Path() string {
return l.path
}
func (l *Layer) Read() (LayerMetadata, error) {
tomlPath := l.Path() + ".toml"
layerMetadataFile, err := DecodeLayerMetadataFile(tomlPath, l.api, l.logger)
if err != nil {
return LayerMetadata{}, err
}
var sha string
shaBytes, err := os.ReadFile(l.Path() + ".sha")
if err != nil && !os.IsNotExist(err) { // if the sha file doesn't exist, an empty sha will be returned
return LayerMetadata{}, err
}
if err == nil {
sha = string(shaBytes)
}
return LayerMetadata{SHA: sha, LayerMetadataFile: layerMetadataFile}, nil
}
func (l *Layer) Remove() error {
if err := os.RemoveAll(l.path); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.Remove(l.path + ".sha"); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.Remove(l.path + ".toml"); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
func (l *Layer) WriteMetadata(metadata LayerMetadataFile) error {
path := l.path + ".toml"
if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
return err
}
return EncodeLayerMetadataFile(metadata, path, l.api)
}
func (l *Layer) WriteSha(sha string) error {
if err := os.WriteFile(l.path+".sha", []byte(sha), 0666); err != nil {
return err
} // #nosec G306
return nil
}