mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-30 05:59:39 +02:00
feat(cnbBuild): support for fetching binding content from url (#3388)
Co-authored-by: I546443 <sumit.kulhadia@sap.com> Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
This commit is contained in:
parent
9a387561b2
commit
81fa0ee2d8
@ -288,7 +288,7 @@ func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bindings.ProcessBindings(utils, platformPath, config.Bindings)
|
err = bindings.ProcessBindings(utils, httpClient, platformPath, config.Bindings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.SetErrorCategory(log.ErrorConfiguration)
|
log.SetErrorCategory(log.ErrorConfiguration)
|
||||||
return errors.Wrap(err, "failed process bindings")
|
return errors.Wrap(err, "failed process bindings")
|
||||||
|
@ -2,12 +2,16 @@
|
|||||||
package bindings
|
package bindings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/SAP/jenkins-library/pkg/cnbutils"
|
"github.com/SAP/jenkins-library/pkg/cnbutils"
|
||||||
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,20 +20,46 @@ type binding struct {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Content *string `json:"content,omitempty"`
|
Content *string `json:"content,omitempty"`
|
||||||
File *string `json:"file,omitempty"`
|
File *string `json:"file,omitempty"`
|
||||||
|
FromURL *string `json:"fromUrl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return error if:
|
||||||
|
// 1. Content is set + File or FromURL
|
||||||
|
// 2. File is set + FromURL or Content
|
||||||
|
// 3. FromURL is set + File or Content
|
||||||
|
// 4. Everything is set
|
||||||
|
func (b *binding) validate() error {
|
||||||
|
if !validName(b.Key) {
|
||||||
|
return fmt.Errorf("invalid key: '%s'", b.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Content == nil && b.File == nil && b.FromURL == nil {
|
||||||
|
return errors.New("one of 'file', 'content' or 'fromUrl' properties must be specified for binding")
|
||||||
|
}
|
||||||
|
|
||||||
|
onlyOneSet := (b.Content != nil && b.File == nil && b.FromURL == nil) ||
|
||||||
|
(b.Content == nil && b.File != nil && b.FromURL == nil) ||
|
||||||
|
(b.Content == nil && b.File == nil && b.FromURL != nil)
|
||||||
|
|
||||||
|
if !onlyOneSet {
|
||||||
|
return errors.New("only one of 'content', 'file' or 'fromUrl' can be set for a binding")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type bindings map[string]binding
|
type bindings map[string]binding
|
||||||
|
|
||||||
// ProcessBindings creates the given bindings in the platform directory
|
// ProcessBindings creates the given bindings in the platform directory
|
||||||
func ProcessBindings(utils cnbutils.BuildUtils, platformPath string, bindings map[string]interface{}) error {
|
func ProcessBindings(utils cnbutils.BuildUtils, httpClient piperhttp.Sender, platformPath string, bindings map[string]interface{}) error {
|
||||||
|
|
||||||
typedBindings, err := toTyped(bindings)
|
typedBindings, err := toTyped(bindings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to convert map to struct")
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, binding := range typedBindings {
|
for name, binding := range typedBindings {
|
||||||
err = processBinding(utils, platformPath, name, binding)
|
err = processBinding(utils, httpClient, platformPath, name, binding)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -38,7 +68,7 @@ func ProcessBindings(utils cnbutils.BuildUtils, platformPath string, bindings ma
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processBinding(utils cnbutils.BuildUtils, platformPath string, name string, binding binding) error {
|
func processBinding(utils cnbutils.BuildUtils, httpClient piperhttp.Sender, platformPath string, name string, binding binding) error {
|
||||||
err := validateBinding(name, binding)
|
err := validateBinding(name, binding)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -47,25 +77,43 @@ func processBinding(utils cnbutils.BuildUtils, platformPath string, name string,
|
|||||||
bindingDir := filepath.Join(platformPath, "bindings", name)
|
bindingDir := filepath.Join(platformPath, "bindings", name)
|
||||||
err = utils.MkdirAll(bindingDir, 0755)
|
err = utils.MkdirAll(bindingDir, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to create binding directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = utils.FileWrite(filepath.Join(bindingDir, "type"), []byte(binding.Type), 0644)
|
err = utils.FileWrite(filepath.Join(bindingDir, "type"), []byte(binding.Type), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to write the 'type' binding file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if binding.Content != nil {
|
if binding.File != nil {
|
||||||
err = utils.FileWrite(filepath.Join(bindingDir, binding.Key), []byte(*binding.Content), 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = utils.Copy(*binding.File, filepath.Join(bindingDir, binding.Key))
|
_, err = utils.Copy(*binding.File, filepath.Join(bindingDir, binding.Key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to copy binding file")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var bindingContent []byte
|
||||||
|
|
||||||
|
if binding.Content == nil {
|
||||||
|
response, err := httpClient.SendRequest(http.MethodGet, *binding.FromURL, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to load binding from url")
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingContent, err = ioutil.ReadAll(response.Body)
|
||||||
|
defer response.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error reading response")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bindingContent = []byte(*binding.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = utils.FileWrite(filepath.Join(bindingDir, binding.Key), bindingContent, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to write binding")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,14 +122,7 @@ func validateBinding(name string, binding binding) error {
|
|||||||
return fmt.Errorf("invalid binding name: '%s'", name)
|
return fmt.Errorf("invalid binding name: '%s'", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !validName(binding.Key) {
|
return binding.validate()
|
||||||
return fmt.Errorf("invalid key: '%s'", binding.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (binding.Content == nil && binding.File == nil) || (binding.Content != nil && binding.File != nil) {
|
|
||||||
return errors.New("either 'file' or 'content' property must be specified for binding")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toTyped(rawData interface{}) (bindings, error) {
|
func toTyped(rawData interface{}) (bindings, error) {
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package bindings_test
|
package bindings_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/SAP/jenkins-library/pkg/cnbutils"
|
"github.com/SAP/jenkins-library/pkg/cnbutils"
|
||||||
"github.com/SAP/jenkins-library/pkg/cnbutils/bindings"
|
"github.com/SAP/jenkins-library/pkg/cnbutils/bindings"
|
||||||
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||||
"github.com/SAP/jenkins-library/pkg/mock"
|
"github.com/SAP/jenkins-library/pkg/mock"
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProcessBindings(t *testing.T) {
|
func TestProcessBindings(t *testing.T) {
|
||||||
|
|
||||||
var mockUtils = func() cnbutils.MockUtils {
|
var mockUtils = func() cnbutils.MockUtils {
|
||||||
var utils = cnbutils.MockUtils{
|
var utils = cnbutils.MockUtils{
|
||||||
FilesMock: &mock.FilesMock{},
|
FilesMock: &mock.FilesMock{},
|
||||||
@ -21,7 +23,12 @@ func TestProcessBindings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("writes bindings to files", func(t *testing.T) {
|
t.Run("writes bindings to files", func(t *testing.T) {
|
||||||
var utils = mockUtils()
|
var utils = mockUtils()
|
||||||
err := bindings.ProcessBindings(utils, "/tmp/platform", map[string]interface{}{
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
httpmock.RegisterResponder(http.MethodGet, "http://test-url.com/binding", httpmock.NewStringResponder(200, "from url content"))
|
||||||
|
client := &piperhttp.Client{}
|
||||||
|
client.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
|
||||||
|
err := bindings.ProcessBindings(utils, client, "/tmp/platform", map[string]interface{}{
|
||||||
"a": map[string]interface{}{
|
"a": map[string]interface{}{
|
||||||
"key": "inline.yaml",
|
"key": "inline.yaml",
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
@ -32,6 +39,11 @@ func TestProcessBindings(t *testing.T) {
|
|||||||
"type": "file",
|
"type": "file",
|
||||||
"file": "/tmp/somefile.yaml",
|
"file": "/tmp/somefile.yaml",
|
||||||
},
|
},
|
||||||
|
"c": map[string]interface{}{
|
||||||
|
"key": "from-url.yaml",
|
||||||
|
"type": "url",
|
||||||
|
"fromUrl": "http://test-url.com/binding",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if assert.NoError(t, err) {
|
if assert.NoError(t, err) {
|
||||||
@ -57,12 +69,26 @@ func TestProcessBindings(t *testing.T) {
|
|||||||
assert.Equal(t, string(content), "file")
|
assert.Equal(t, string(content), "file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if assert.True(t, utils.HasFile("/tmp/platform/bindings/c/type")) {
|
||||||
|
content, err := utils.FileRead("/tmp/platform/bindings/c/type")
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.Equal(t, string(content), "url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert.True(t, utils.HasFile("/tmp/platform/bindings/c/from-url.yaml")) {
|
||||||
|
content, err := utils.FileRead("/tmp/platform/bindings/c/from-url.yaml")
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.Equal(t, string(content), "from url content")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails with the name being invalid", func(t *testing.T) {
|
t.Run("fails with the name being invalid", func(t *testing.T) {
|
||||||
var utils = mockUtils()
|
var utils = mockUtils()
|
||||||
err := bindings.ProcessBindings(utils, "/tmp/platform", map[string]interface{}{
|
err := bindings.ProcessBindings(utils, &piperhttp.Client{}, "/tmp/platform", map[string]interface{}{
|
||||||
"..": map[string]interface{}{
|
"..": map[string]interface{}{
|
||||||
"key": "inline.yaml",
|
"key": "inline.yaml",
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
@ -77,7 +103,7 @@ func TestProcessBindings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("fails with the key being invalid", func(t *testing.T) {
|
t.Run("fails with the key being invalid", func(t *testing.T) {
|
||||||
var utils = mockUtils()
|
var utils = mockUtils()
|
||||||
err := bindings.ProcessBindings(utils, "/tmp/platform", map[string]interface{}{
|
err := bindings.ProcessBindings(utils, &piperhttp.Client{}, "/tmp/platform", map[string]interface{}{
|
||||||
"binding": map[string]interface{}{
|
"binding": map[string]interface{}{
|
||||||
"key": "test/test.yaml",
|
"key": "test/test.yaml",
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
@ -92,7 +118,7 @@ func TestProcessBindings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("fails with both content and file being specified", func(t *testing.T) {
|
t.Run("fails with both content and file being specified", func(t *testing.T) {
|
||||||
var utils = mockUtils()
|
var utils = mockUtils()
|
||||||
err := bindings.ProcessBindings(utils, "/tmp/platform", map[string]interface{}{
|
err := bindings.ProcessBindings(utils, &piperhttp.Client{}, "/tmp/platform", map[string]interface{}{
|
||||||
"binding": map[string]interface{}{
|
"binding": map[string]interface{}{
|
||||||
"key": "test.yaml",
|
"key": "test.yaml",
|
||||||
"type": "both",
|
"type": "both",
|
||||||
@ -102,13 +128,13 @@ func TestProcessBindings(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Equal(t, "either 'file' or 'content' property must be specified for binding", err.Error())
|
assert.Equal(t, "only one of 'content', 'file' or 'fromUrl' can be set for a binding", err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails with no content or file being specified", func(t *testing.T) {
|
t.Run("fails with no content or file being specified", func(t *testing.T) {
|
||||||
var utils = mockUtils()
|
var utils = mockUtils()
|
||||||
err := bindings.ProcessBindings(utils, "/tmp/platform", map[string]interface{}{
|
err := bindings.ProcessBindings(utils, &piperhttp.Client{}, "/tmp/platform", map[string]interface{}{
|
||||||
"binding": map[string]interface{}{
|
"binding": map[string]interface{}{
|
||||||
"key": "test.yaml",
|
"key": "test.yaml",
|
||||||
"type": "none",
|
"type": "none",
|
||||||
@ -116,25 +142,25 @@ func TestProcessBindings(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Equal(t, "either 'file' or 'content' property must be specified for binding", err.Error())
|
assert.Equal(t, "one of 'file', 'content' or 'fromUrl' properties must be specified for binding", err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails with not a map", func(t *testing.T) {
|
t.Run("fails with not a map", func(t *testing.T) {
|
||||||
var utils = mockUtils()
|
var utils = mockUtils()
|
||||||
err := bindings.ProcessBindings(utils, "/tmp/platform", map[string]interface{}{
|
err := bindings.ProcessBindings(utils, &piperhttp.Client{}, "/tmp/platform", map[string]interface{}{
|
||||||
"binding": 42,
|
"binding": 42,
|
||||||
})
|
})
|
||||||
|
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Equal(t, "1 error(s) decoding:\n\n* '[binding]' expected a map, got 'int'", err.Error())
|
assert.Equal(t, "failed to convert map to struct: 1 error(s) decoding:\n\n* '[binding]' expected a map, got 'int'", err.Error())
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails with invalid map", func(t *testing.T) {
|
t.Run("fails with invalid map", func(t *testing.T) {
|
||||||
var utils = mockUtils()
|
var utils = mockUtils()
|
||||||
err := bindings.ProcessBindings(utils, "/tmp/platform", map[string]interface{}{
|
err := bindings.ProcessBindings(utils, &piperhttp.Client{}, "/tmp/platform", map[string]interface{}{
|
||||||
"test": map[string]interface{}{
|
"test": map[string]interface{}{
|
||||||
"key": "test.yaml",
|
"key": "test.yaml",
|
||||||
"typo": "test",
|
"typo": "test",
|
||||||
@ -142,7 +168,7 @@ func TestProcessBindings(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Equal(t, "1 error(s) decoding:\n\n* '[test]' has invalid keys: typo", err.Error())
|
assert.Equal(t, "failed to convert map to struct: 1 error(s) decoding:\n\n* '[test]' has invalid keys: typo", err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -162,8 +162,7 @@ spec:
|
|||||||
file: path/to/settings.xml
|
file: path/to/settings.xml
|
||||||
```
|
```
|
||||||
|
|
||||||
or inline
|
inline:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
bindings:
|
bindings:
|
||||||
maven-settings:
|
maven-settings:
|
||||||
@ -171,6 +170,15 @@ spec:
|
|||||||
key: settings.xml
|
key: settings.xml
|
||||||
content: "inline settings.xml"
|
content: "inline settings.xml"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
from url:
|
||||||
|
```yaml
|
||||||
|
bindings:
|
||||||
|
maven-settings:
|
||||||
|
type: maven
|
||||||
|
key: settings.xml
|
||||||
|
fromUrl: https://url-to/setting.xml
|
||||||
|
```
|
||||||
scope:
|
scope:
|
||||||
- PARAMETERS
|
- PARAMETERS
|
||||||
- STAGES
|
- STAGES
|
||||||
|
Loading…
x
Reference in New Issue
Block a user