mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
Bugfix: Convert int/float parameters (#1837)
This commit is contained in:
parent
97737a2d8d
commit
7f4fab762d
73
cmd/piper.go
73
cmd/piper.go
@ -7,6 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/SAP/jenkins-library/pkg/config"
|
"github.com/SAP/jenkins-library/pkg/config"
|
||||||
@ -88,8 +89,8 @@ func Execute() {
|
|||||||
rootCmd.AddCommand(GctsCreateRepositoryCommand())
|
rootCmd.AddCommand(GctsCreateRepositoryCommand())
|
||||||
rootCmd.AddCommand(GctsExecuteABAPUnitTestsCommand())
|
rootCmd.AddCommand(GctsExecuteABAPUnitTestsCommand())
|
||||||
rootCmd.AddCommand(GctsDeployCommand())
|
rootCmd.AddCommand(GctsDeployCommand())
|
||||||
rootCmd.AddCommand(MalwareExecuteScanCommand())
|
|
||||||
rootCmd.AddCommand(GctsRollbackCommand())
|
rootCmd.AddCommand(GctsRollbackCommand())
|
||||||
|
rootCmd.AddCommand(MalwareExecuteScanCommand())
|
||||||
rootCmd.AddCommand(WhitesourceExecuteScanCommand())
|
rootCmd.AddCommand(WhitesourceExecuteScanCommand())
|
||||||
rootCmd.AddCommand(GctsCloneRepositoryCommand())
|
rootCmd.AddCommand(GctsCloneRepositoryCommand())
|
||||||
rootCmd.AddCommand(JsonApplyPatchCommand())
|
rootCmd.AddCommand(JsonApplyPatchCommand())
|
||||||
@ -209,6 +210,8 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errIncompatibleTypes = fmt.Errorf("incompatible types")
|
||||||
|
|
||||||
func checkTypes(config map[string]interface{}, options interface{}) map[string]interface{} {
|
func checkTypes(config map[string]interface{}, options interface{}) map[string]interface{} {
|
||||||
optionsType := getStepOptionsStructType(options)
|
optionsType := getStepOptionsStructType(options)
|
||||||
|
|
||||||
@ -219,40 +222,82 @@ func checkTypes(config map[string]interface{}, options interface{}) map[string]i
|
|||||||
}
|
}
|
||||||
|
|
||||||
paramValueType := reflect.ValueOf(config[paramName])
|
paramValueType := reflect.ValueOf(config[paramName])
|
||||||
if paramValueType.Kind() != reflect.String {
|
if optionsField.Type.Kind() == paramValueType.Kind() {
|
||||||
// Type check is limited to strings at the moment
|
// Types already match, nothing to do
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
paramValue := paramValueType.String()
|
var typeError error = nil
|
||||||
logWarning := true
|
|
||||||
|
|
||||||
switch optionsField.Type.Kind() {
|
switch paramValueType.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
// Types match, ignore
|
typeError = convertValueFromString(config, optionsField, paramName, paramValueType.String())
|
||||||
logWarning = false
|
case reflect.Float32, reflect.Float64:
|
||||||
|
typeError = convertValueFromFloat(config, optionsField, paramName, paramValueType.Float())
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
typeError = convertValueFromInt(config, optionsField, paramName, paramValueType.Int())
|
||||||
|
default:
|
||||||
|
typeError = errIncompatibleTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeError != nil {
|
||||||
|
log.Entry().WithError(typeError).Fatalf(
|
||||||
|
"config value for '%s' is of unexpected type %s, expected %s",
|
||||||
|
paramName, paramValueType.Kind(), optionsField.Type.Kind())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertValueFromString(config map[string]interface{}, optionsField *reflect.StructField, paramName, paramValue string) error {
|
||||||
|
switch optionsField.Type.Kind() {
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
// Could do automatic conversion for those types in theory,
|
// Could do automatic conversion for those types in theory,
|
||||||
// but that might obscure what really happens in error cases.
|
// but that might obscure what really happens in error cases.
|
||||||
log.Entry().Fatalf("Type mismatch in configuration for option '%s'. Expected type to be a list (or slice, or array) but got %s.", paramName, paramValueType.Kind())
|
return fmt.Errorf("expected type to be a list (or slice, or array) but got string")
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
// Sensible to convert strings "true"/"false" to respective boolean values as it is
|
// Sensible to convert strings "true"/"false" to respective boolean values as it is
|
||||||
// common practice to write booleans as string in yaml files.
|
// common practice to write booleans as string in yaml files.
|
||||||
paramValue = strings.ToLower(paramValue)
|
paramValue = strings.ToLower(paramValue)
|
||||||
if paramValue == "true" {
|
if paramValue == "true" {
|
||||||
config[paramName] = true
|
config[paramName] = true
|
||||||
logWarning = false
|
return nil
|
||||||
} else if paramValue == "false" {
|
} else if paramValue == "false" {
|
||||||
config[paramName] = false
|
config[paramName] = false
|
||||||
logWarning = false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if logWarning {
|
return errIncompatibleTypes
|
||||||
log.Entry().Warnf("Config value for '%s' is of unexpected type and is ignored", paramName)
|
}
|
||||||
|
|
||||||
|
func convertValueFromFloat(config map[string]interface{}, optionsField *reflect.StructField, paramName string, paramValue float64) error {
|
||||||
|
switch optionsField.Type.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
config[paramName] = strconv.FormatFloat(paramValue, 'f', -1, 64)
|
||||||
|
return nil
|
||||||
|
case reflect.Float32:
|
||||||
|
config[paramName] = float32(paramValue)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return errIncompatibleTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertValueFromInt(config map[string]interface{}, optionsField *reflect.StructField, paramName string, paramValue int64) error {
|
||||||
|
switch optionsField.Type.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
config[paramName] = strconv.FormatInt(paramValue, 10)
|
||||||
|
return nil
|
||||||
|
case reflect.Float32:
|
||||||
|
config[paramName] = float32(paramValue)
|
||||||
|
return nil
|
||||||
|
case reflect.Float64:
|
||||||
|
config[paramName] = float64(paramValue)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return config
|
|
||||||
|
return errIncompatibleTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
func findStructFieldByJSONTag(tagName string, optionsType reflect.Type) *reflect.StructField {
|
func findStructFieldByJSONTag(tagName string, optionsType reflect.Type) *reflect.StructField {
|
||||||
|
@ -134,6 +134,14 @@ func TestGetProjectConfigFile(t *testing.T) {
|
|||||||
func TestConvertTypes(t *testing.T) {
|
func TestConvertTypes(t *testing.T) {
|
||||||
t.Run("Converts strings to booleans", func(t *testing.T) {
|
t.Run("Converts strings to booleans", func(t *testing.T) {
|
||||||
// Init
|
// Init
|
||||||
|
hasFailed := false
|
||||||
|
|
||||||
|
exitFunc := log.Entry().Logger.ExitFunc
|
||||||
|
log.Entry().Logger.ExitFunc = func(int) {
|
||||||
|
hasFailed = true
|
||||||
|
}
|
||||||
|
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
|
||||||
|
|
||||||
options := struct {
|
options := struct {
|
||||||
Foo bool `json:"foo,omitempty"`
|
Foo bool `json:"foo,omitempty"`
|
||||||
Bar bool `json:"bar,omitempty"`
|
Bar bool `json:"bar,omitempty"`
|
||||||
@ -156,6 +164,71 @@ func TestConvertTypes(t *testing.T) {
|
|||||||
assert.Equal(t, true, stepConfig["bar"])
|
assert.Equal(t, true, stepConfig["bar"])
|
||||||
assert.Equal(t, false, options.Foo)
|
assert.Equal(t, false, options.Foo)
|
||||||
assert.Equal(t, true, options.Bar)
|
assert.Equal(t, true, options.Bar)
|
||||||
|
assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework")
|
||||||
|
})
|
||||||
|
t.Run("Converts numbers to strings", func(t *testing.T) {
|
||||||
|
// Init
|
||||||
|
hasFailed := false
|
||||||
|
|
||||||
|
exitFunc := log.Entry().Logger.ExitFunc
|
||||||
|
log.Entry().Logger.ExitFunc = func(int) {
|
||||||
|
hasFailed = true
|
||||||
|
}
|
||||||
|
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
|
||||||
|
|
||||||
|
options := struct {
|
||||||
|
Foo string `json:"foo,omitempty"`
|
||||||
|
Bar string `json:"bar,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
stepConfig := map[string]interface{}{}
|
||||||
|
stepConfig["foo"] = 1.5
|
||||||
|
stepConfig["bar"] = 42
|
||||||
|
|
||||||
|
// Test
|
||||||
|
stepConfig = checkTypes(stepConfig, options)
|
||||||
|
|
||||||
|
confJSON, _ := json.Marshal(stepConfig)
|
||||||
|
_ = json.Unmarshal(confJSON, &options)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.Equal(t, "1.5", stepConfig["foo"])
|
||||||
|
assert.Equal(t, "42", stepConfig["bar"])
|
||||||
|
assert.Equal(t, "1.5", options.Foo)
|
||||||
|
assert.Equal(t, "42", options.Bar)
|
||||||
|
assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework")
|
||||||
|
})
|
||||||
|
t.Run("Keeps numbers", func(t *testing.T) {
|
||||||
|
// Init
|
||||||
|
hasFailed := false
|
||||||
|
|
||||||
|
exitFunc := log.Entry().Logger.ExitFunc
|
||||||
|
log.Entry().Logger.ExitFunc = func(int) {
|
||||||
|
hasFailed = true
|
||||||
|
}
|
||||||
|
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
|
||||||
|
|
||||||
|
options := struct {
|
||||||
|
Foo int `json:"foo,omitempty"`
|
||||||
|
Bar float32 `json:"bar,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
stepConfig := map[string]interface{}{}
|
||||||
|
stepConfig["foo"] = 1
|
||||||
|
stepConfig["bar"] = 42
|
||||||
|
|
||||||
|
// Test
|
||||||
|
stepConfig = checkTypes(stepConfig, options)
|
||||||
|
|
||||||
|
confJSON, _ := json.Marshal(stepConfig)
|
||||||
|
_ = json.Unmarshal(confJSON, &options)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.Equal(t, 1, stepConfig["foo"])
|
||||||
|
assert.Equal(t, float32(42.0), stepConfig["bar"])
|
||||||
|
assert.Equal(t, 1, options.Foo)
|
||||||
|
assert.Equal(t, float32(42.0), options.Bar)
|
||||||
|
assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework")
|
||||||
})
|
})
|
||||||
t.Run("Exits on unsupported type mismatch", func(t *testing.T) {
|
t.Run("Exits on unsupported type mismatch", func(t *testing.T) {
|
||||||
// Init
|
// Init
|
||||||
|
Loading…
Reference in New Issue
Block a user