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 }