github.com/jaylevin/jenkins-library@v1.230.4/pkg/cnbutils/bindings/bindings.go (about) 1 // Package bindings provides utility function to create buildpack bindings folder structures 2 package bindings 3 4 import ( 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "path/filepath" 9 "strings" 10 11 "github.com/pkg/errors" 12 13 "github.com/SAP/jenkins-library/pkg/cnbutils" 14 piperhttp "github.com/SAP/jenkins-library/pkg/http" 15 "github.com/mitchellh/mapstructure" 16 ) 17 18 type binding struct { 19 Type string `json:"type"` 20 Key string `json:"key"` 21 Content *string `json:"content,omitempty"` 22 File *string `json:"file,omitempty"` 23 FromURL *string `json:"fromUrl,omitempty"` 24 } 25 26 // Return error if: 27 // 1. Content is set + File or FromURL 28 // 2. File is set + FromURL or Content 29 // 3. FromURL is set + File or Content 30 // 4. Everything is set 31 func (b *binding) validate() error { 32 if !validName(b.Key) { 33 return fmt.Errorf("invalid key: '%s'", b.Key) 34 } 35 36 if b.Content == nil && b.File == nil && b.FromURL == nil { 37 return errors.New("one of 'file', 'content' or 'fromUrl' properties must be specified for binding") 38 } 39 40 onlyOneSet := (b.Content != nil && b.File == nil && b.FromURL == nil) || 41 (b.Content == nil && b.File != nil && b.FromURL == nil) || 42 (b.Content == nil && b.File == nil && b.FromURL != nil) 43 44 if !onlyOneSet { 45 return errors.New("only one of 'content', 'file' or 'fromUrl' can be set for a binding") 46 } 47 48 return nil 49 } 50 51 type bindings map[string]binding 52 53 // ProcessBindings creates the given bindings in the platform directory 54 func ProcessBindings(utils cnbutils.BuildUtils, httpClient piperhttp.Sender, platformPath string, bindings map[string]interface{}) error { 55 56 typedBindings, err := toTyped(bindings) 57 if err != nil { 58 return errors.Wrap(err, "failed to convert map to struct") 59 } 60 61 for name, binding := range typedBindings { 62 err = processBinding(utils, httpClient, platformPath, name, binding) 63 if err != nil { 64 return err 65 } 66 } 67 68 return nil 69 } 70 71 func processBinding(utils cnbutils.BuildUtils, httpClient piperhttp.Sender, platformPath string, name string, binding binding) error { 72 err := validateBinding(name, binding) 73 if err != nil { 74 return err 75 } 76 77 bindingDir := filepath.Join(platformPath, "bindings", name) 78 err = utils.MkdirAll(bindingDir, 0755) 79 if err != nil { 80 return errors.Wrap(err, "failed to create binding directory") 81 } 82 83 err = utils.FileWrite(filepath.Join(bindingDir, "type"), []byte(binding.Type), 0644) 84 if err != nil { 85 return errors.Wrap(err, "failed to write the 'type' binding file") 86 } 87 88 if binding.File != nil { 89 _, err = utils.Copy(*binding.File, filepath.Join(bindingDir, binding.Key)) 90 if err != nil { 91 return errors.Wrap(err, "failed to copy binding file") 92 } 93 } else { 94 var bindingContent []byte 95 96 if binding.Content == nil { 97 response, err := httpClient.SendRequest(http.MethodGet, *binding.FromURL, nil, nil, nil) 98 if err != nil { 99 return errors.Wrap(err, "failed to load binding from url") 100 } 101 102 bindingContent, err = ioutil.ReadAll(response.Body) 103 defer response.Body.Close() 104 if err != nil { 105 return errors.Wrap(err, "error reading response") 106 } 107 } else { 108 bindingContent = []byte(*binding.Content) 109 } 110 111 err = utils.FileWrite(filepath.Join(bindingDir, binding.Key), bindingContent, 0644) 112 if err != nil { 113 return errors.Wrap(err, "failed to write binding") 114 } 115 } 116 117 return nil 118 } 119 120 func validateBinding(name string, binding binding) error { 121 if !validName(name) { 122 return fmt.Errorf("invalid binding name: '%s'", name) 123 } 124 125 return binding.validate() 126 } 127 128 func toTyped(rawData interface{}) (bindings, error) { 129 var typedBindings bindings 130 131 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 132 ErrorUnused: true, 133 Result: &typedBindings, 134 }) 135 if err != nil { 136 return nil, err 137 } 138 err = decoder.Decode(rawData) 139 if err != nil { 140 return nil, err 141 } 142 143 return typedBindings, nil 144 } 145 146 func validName(name string) bool { 147 if name == "" || name == "." || name == ".." { 148 return false 149 } 150 151 return !strings.ContainsAny(name, "/") 152 }