github.com/paketoio/libpak@v1.3.1/layer.go (about)

     1  /*
     2   * Copyright 2018-2020 the original author or authors.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *      https://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package libpak
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  
    25  	"github.com/buildpacks/libcnb"
    26  	"github.com/heroku/color"
    27  	"github.com/mitchellh/mapstructure"
    28  	"github.com/paketoio/libpak/bard"
    29  )
    30  
    31  // LayerContributor is a helper for implementing a libcnb.LayerContributor in order to get consistent logging and
    32  // avoidance.
    33  type LayerContributor struct {
    34  
    35  	// ExpectedMetadata is the metadata to compare against any existing layer metadata.
    36  	ExpectedMetadata map[string]interface{}
    37  
    38  	// Logger is the logger to use.
    39  	Logger bard.Logger
    40  
    41  	// Name is the user readable name of the contribution.
    42  	Name string
    43  }
    44  
    45  // NewLayerContributor creates a new instance.
    46  func NewLayerContributor(name string, expectedMetadata map[string]interface{}) LayerContributor {
    47  	return LayerContributor{
    48  		ExpectedMetadata: expectedMetadata,
    49  		Logger:           bard.NewLogger(os.Stdout),
    50  		Name:             name,
    51  	}
    52  }
    53  
    54  // LayerFunc is a callback function that is invoked when a layer needs to be contributed.
    55  type LayerFunc func() (libcnb.Layer, error)
    56  
    57  // Contribute is the function to call when implementing your libcnb.LayerContributor.
    58  func (l *LayerContributor) Contribute(layer libcnb.Layer, f LayerFunc) (libcnb.Layer, error) {
    59  	expected := reflect.New(reflect.TypeOf(l.ExpectedMetadata))
    60  	expected.Elem().Set(reflect.ValueOf(l.ExpectedMetadata))
    61  
    62  	actual := reflect.New(reflect.TypeOf(l.ExpectedMetadata)).Interface()
    63  	if err := mapstructure.Decode(layer.Metadata, &actual); err != nil {
    64  		return libcnb.Layer{}, fmt.Errorf("unable to decode metadata into %s: %w", reflect.TypeOf(l.ExpectedMetadata), err)
    65  	}
    66  
    67  	if reflect.DeepEqual(expected.Interface(), actual) {
    68  		l.Logger.Header("%s: %s cached layer", color.BlueString(l.Name), color.GreenString("Reusing"))
    69  		return layer, nil
    70  	}
    71  
    72  	l.Logger.Header("%s: %s to layer", color.BlueString(l.Name), color.YellowString("Contributing"))
    73  
    74  	if err := os.RemoveAll(layer.Path); err != nil {
    75  		return libcnb.Layer{}, fmt.Errorf("unable to remove existing layer directory %s: %w", layer.Path, err)
    76  	}
    77  
    78  	if err := os.MkdirAll(layer.Path, 0755); err != nil {
    79  		return libcnb.Layer{}, fmt.Errorf("unable to create layer directory %s: %w", layer.Path, err)
    80  	}
    81  
    82  	layer, err := f()
    83  	if err != nil {
    84  		return libcnb.Layer{}, err
    85  	}
    86  
    87  	if err := mapstructure.Decode(l.ExpectedMetadata, &layer.Metadata); err != nil {
    88  		return libcnb.Layer{}, fmt.Errorf("unable to encode metadata into %+v: %w", l.ExpectedMetadata, err)
    89  	}
    90  
    91  	return layer, nil
    92  }
    93  
    94  // DependencyLayerContributor is a helper for implementing a libcnb.LayerContributor for a BuildpackDependency in order
    95  // to get consistent logging and avoidance.
    96  type DependencyLayerContributor struct {
    97  
    98  	// Dependency is the dependency being contributed.
    99  	Dependency BuildpackDependency
   100  
   101  	// DependencyCache is the cache to use to get the dependency.
   102  	DependencyCache DependencyCache
   103  
   104  	// LayerContributor is the contained LayerContributor used for the actual contribution.
   105  	LayerContributor LayerContributor
   106  }
   107  
   108  // NewDependencyLayerContributor creates a new instance and adds the dependency to the Buildpack Plan.
   109  func NewDependencyLayerContributor(dependency BuildpackDependency, cache DependencyCache, plan *libcnb.BuildpackPlan) DependencyLayerContributor {
   110  	plan.Entries = append(plan.Entries, libcnb.BuildpackPlanEntry{
   111  		Name:    dependency.ID,
   112  		Version: dependency.Version,
   113  		Metadata: map[string]interface{}{
   114  			"name":     dependency.Name,
   115  			"uri":      dependency.URI,
   116  			"sha256":   dependency.SHA256,
   117  			"stacks":   dependency.Stacks,
   118  			"licenses": dependency.Licenses,
   119  		},
   120  	})
   121  
   122  	expected := map[string]interface{}{
   123  		"id":       dependency.ID,
   124  		"name":     dependency.Name,
   125  		"version":  dependency.Version,
   126  		"uri":      dependency.URI,
   127  		"sha256":   dependency.SHA256,
   128  		"stacks":   dependency.Stacks,
   129  		"licenses": []map[string]interface{}{},
   130  	}
   131  	for _, l := range dependency.Licenses {
   132  		expected["licenses"] = append(expected["licenses"].([]map[string]interface{}), map[string]interface{}{
   133  			"type": l.Type,
   134  			"uri":  l.URI,
   135  		})
   136  	}
   137  
   138  	return DependencyLayerContributor{
   139  		Dependency:       dependency,
   140  		DependencyCache:  cache,
   141  		LayerContributor: NewLayerContributor(fmt.Sprintf("%s %s", dependency.Name, dependency.Version), expected),
   142  	}
   143  }
   144  
   145  // DependencyLayerFunc is a callback function that is invoked when a dependency needs to be contributed.
   146  type DependencyLayerFunc func(artifact *os.File) (libcnb.Layer, error)
   147  
   148  // Contribute is the function to call whe implementing your libcnb.LayerContributor.
   149  func (d *DependencyLayerContributor) Contribute(layer libcnb.Layer, f DependencyLayerFunc) (libcnb.Layer, error) {
   150  	return d.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) {
   151  		artifact, err := d.DependencyCache.Artifact(d.Dependency)
   152  		if err != nil {
   153  			return libcnb.Layer{}, fmt.Errorf("unable to get dependency %s: %w", d.Dependency.ID, err)
   154  		}
   155  
   156  		return f(artifact)
   157  	})
   158  }
   159  
   160  // HelperLayerContributor is a helper for implementing a libcnb.LayerContributor for a buildpack helper application in
   161  // order to get consistent logging and avoidance.
   162  type HelperLayerContributor struct {
   163  
   164  	// Path is the path to the helper application.
   165  	Path string
   166  
   167  	// LayerContributor is the contained LayerContributor used for the actual contribution.
   168  	LayerContributor LayerContributor
   169  }
   170  
   171  // NewHelperLayerContributor creates a new instance and adds the helper to the Buildpack Plan.
   172  func NewHelperLayerContributor(path string, name string, info libcnb.BuildpackInfo, plan *libcnb.BuildpackPlan) HelperLayerContributor {
   173  
   174  	plan.Entries = append(plan.Entries, libcnb.BuildpackPlanEntry{
   175  		Name:    filepath.Base(path),
   176  		Version: info.Version,
   177  		Metadata: map[string]interface{}{
   178  			"buildpack-id":      info.ID,
   179  			"buildpack-version": info.Version,
   180  		},
   181  	})
   182  
   183  	expected := map[string]interface{}{
   184  		"id":        info.ID,
   185  		"name":      info.Name,
   186  		"version":   info.Version,
   187  		"clear-env": info.ClearEnvironment,
   188  	}
   189  
   190  	return HelperLayerContributor{
   191  		Path:             path,
   192  		LayerContributor: NewLayerContributor(fmt.Sprintf("%s %s", name, info.Version), expected),
   193  	}
   194  }
   195  
   196  // DependencyLayerFunc is a callback function that is invoked when a helper needs to be contributed.
   197  type HelperLayerFunc func(artifact *os.File) (libcnb.Layer, error)
   198  
   199  // Contribute is the function to call whe implementing your libcnb.LayerContributor.
   200  func (h *HelperLayerContributor) Contribute(layer libcnb.Layer, f HelperLayerFunc) (libcnb.Layer, error) {
   201  	return h.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) {
   202  		in, err := os.Open(h.Path)
   203  		if err != nil {
   204  			return libcnb.Layer{}, fmt.Errorf("unable to open %s: %w", h.Path, err)
   205  		}
   206  
   207  		return f(in)
   208  	})
   209  }