1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-18 05:18:24 +02:00
Jordi van Liempt 0ba4c2206c
chore(deps): Replace io/ioutil package (#4494)
* update all deprecated ioutil usages

* forgotten changes

* add missing imports

* undo changing comment

* add missing 'os' import

* fix integration test

---------

Co-authored-by: I557621 <jordi.van.liempt@sap.com>
Co-authored-by: Gulom Alimov <gulomjon.alimov@sap.com>
2023-08-16 12:57:04 +02:00

240 lines
6.1 KiB
Go

// Package bindings provides utility function to create buildpack bindings folder structures
package bindings
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
k8sjson "sigs.k8s.io/json"
"github.com/SAP/jenkins-library/pkg/cnbutils"
"github.com/SAP/jenkins-library/pkg/config"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
)
type binding struct {
bindingData `json:",inline"`
Type string `json:"type"`
Data []bindingData `json:"data"`
}
type bindingData struct {
Key string `json:"key"`
Content *string `json:"content,omitempty"`
File *string `json:"file,omitempty"`
FromURL *string `json:"fromUrl,omitempty"`
VaultCredentialKey *string `json:"vaultCredentialKey,omitempty"`
}
type bindings map[string]binding
type bindingContentType int
const (
fileBinding bindingContentType = iota
contentBinding
fromURLBinding
vaultBinding
)
// Return error if:
// 1. Content is set + File or FromURL or VaultCredentialKey
// 2. File is set + FromURL or Content or VaultCredentialKey
// 3. FromURL is set + File or Content or VaultCredentialKey
// 4. VaultCredentialKey is set + File or FromURL or Content
// 5. Everything is set
func (b *bindingData) validate() error {
if !validName(b.Key) {
return fmt.Errorf("invalid key: '%s'", b.Key)
}
if b.Content == nil && b.File == nil && b.FromURL == nil && b.VaultCredentialKey == nil {
return errors.New("one of 'file', 'content', 'fromUrl' or 'vaultCredentialKey' properties must be specified")
}
onlyOneSet := (b.Content != nil && b.File == nil && b.FromURL == nil && b.VaultCredentialKey == nil) ||
(b.Content == nil && b.File != nil && b.FromURL == nil && b.VaultCredentialKey == nil) ||
(b.Content == nil && b.File == nil && b.FromURL != nil && b.VaultCredentialKey == nil) ||
(b.Content == nil && b.File == nil && b.FromURL == nil && b.VaultCredentialKey != nil)
if !onlyOneSet {
return errors.New("only one of 'content', 'file', 'fromUrl' or 'vaultCredentialKey' can be set")
}
return nil
}
func (b *bindingData) bindingContentType() bindingContentType {
if b.File != nil {
return fileBinding
}
if b.Content != nil {
return contentBinding
}
if b.FromURL != nil {
return fromURLBinding
}
return vaultBinding
}
// ProcessBindings creates the given bindings in the platform directory
func ProcessBindings(utils cnbutils.BuildUtils, httpClient piperhttp.Sender, platformPath string, bindings map[string]interface{}) error {
typedBindings, err := toTyped(bindings)
if err != nil {
return errors.Wrap(err, "error while reading bindings")
}
for name, binding := range typedBindings {
if len(binding.Data) == 0 {
return fmt.Errorf("empty binding: '%s'", name)
}
for _, data := range binding.Data {
err = processBinding(utils, httpClient, platformPath, name, binding.Type, data)
if err != nil {
return err
}
}
}
return nil
}
func processBinding(utils cnbutils.BuildUtils, httpClient piperhttp.Sender, platformPath string, name string, bindingType string, data bindingData) error {
err := validateBinding(name, data)
if err != nil {
return err
}
bindingDir := filepath.Join(platformPath, "bindings", name)
err = utils.MkdirAll(bindingDir, 0755)
if err != nil {
return errors.Wrap(err, "failed to create binding directory")
}
err = utils.FileWrite(filepath.Join(bindingDir, "type"), []byte(bindingType), 0644)
if err != nil {
return errors.Wrap(err, "failed to write the 'type' binding file")
}
var bindingContent []byte
switch data.bindingContentType() {
case fileBinding:
bindingContent, err = utils.FileRead(*data.File)
if err != nil {
return errors.Wrap(err, "failed to copy binding file")
}
case contentBinding:
bindingContent = []byte(*data.Content)
case fromURLBinding:
response, err := httpClient.SendRequest(http.MethodGet, *data.FromURL, nil, nil, nil)
if err != nil {
return errors.Wrap(err, "failed to load binding from url")
}
bindingContent, err = io.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
return errors.Wrap(err, "error reading response")
}
case vaultBinding:
envVar := config.VaultCredentialEnvPrefixDefault + config.ConvertEnvVar(*data.VaultCredentialKey)
if bindingContentString, ok := os.LookupEnv(envVar); ok {
bindingContent = []byte(bindingContentString)
} else {
return fmt.Errorf("environment variable %q is not set (required by the %q binding)", envVar, name)
}
}
err = utils.FileWrite(filepath.Join(bindingDir, data.Key), bindingContent, 0644)
if err != nil {
return errors.Wrap(err, "failed to write binding")
}
return nil
}
func validateBinding(name string, data bindingData) error {
if !validName(name) {
return fmt.Errorf("invalid binding name: '%s'", name)
}
err := data.validate()
if err != nil {
return errors.Wrapf(err, "failed to validate binding '%s'", name)
}
return nil
}
func toTyped(rawMap map[string]interface{}) (bindings, error) {
typedBindings := bindings{}
for name, rawBinding := range rawMap {
var b binding
b, err := fromRaw(rawBinding)
if err != nil {
return nil, errors.Wrapf(err, "could not process binding '%s'", name)
}
if b.Key != "" {
b.Data = append(b.Data, bindingData{
Key: b.Key,
Content: b.Content,
File: b.File,
FromURL: b.FromURL,
VaultCredentialKey: b.VaultCredentialKey,
})
}
typedBindings[name] = b
}
return typedBindings, nil
}
func fromRaw(rawData interface{}) (binding, error) {
var new binding
jsonValue, err := json.Marshal(rawData)
if err != nil {
return binding{}, err
}
errs, err := k8sjson.UnmarshalStrict(jsonValue, &new, k8sjson.DisallowUnknownFields)
if err != nil {
return binding{}, err
}
if len(errs) != 0 {
for _, e := range errs {
if err == nil {
err = e
} else {
err = errors.Wrap(err, e.Error())
}
}
err = errors.Wrap(err, "validation error")
return binding{}, err
}
return new, nil
}
func validName(name string) bool {
if name == "" || name == "." || name == ".." {
return false
}
return !strings.ContainsAny(name, "/")
}