mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
Bugfix: Convert int/float parameters (#1837)
This commit is contained in:
parent
97737a2d8d
commit
7f4fab762d
95
cmd/piper.go
95
cmd/piper.go
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
@ -88,8 +89,8 @@ func Execute() {
|
||||
rootCmd.AddCommand(GctsCreateRepositoryCommand())
|
||||
rootCmd.AddCommand(GctsExecuteABAPUnitTestsCommand())
|
||||
rootCmd.AddCommand(GctsDeployCommand())
|
||||
rootCmd.AddCommand(MalwareExecuteScanCommand())
|
||||
rootCmd.AddCommand(GctsRollbackCommand())
|
||||
rootCmd.AddCommand(MalwareExecuteScanCommand())
|
||||
rootCmd.AddCommand(WhitesourceExecuteScanCommand())
|
||||
rootCmd.AddCommand(GctsCloneRepositoryCommand())
|
||||
rootCmd.AddCommand(JsonApplyPatchCommand())
|
||||
@ -209,6 +210,8 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin
|
||||
return nil
|
||||
}
|
||||
|
||||
var errIncompatibleTypes = fmt.Errorf("incompatible types")
|
||||
|
||||
func checkTypes(config map[string]interface{}, options interface{}) map[string]interface{} {
|
||||
optionsType := getStepOptionsStructType(options)
|
||||
|
||||
@ -219,42 +222,84 @@ func checkTypes(config map[string]interface{}, options interface{}) map[string]i
|
||||
}
|
||||
|
||||
paramValueType := reflect.ValueOf(config[paramName])
|
||||
if paramValueType.Kind() != reflect.String {
|
||||
// Type check is limited to strings at the moment
|
||||
if optionsField.Type.Kind() == paramValueType.Kind() {
|
||||
// Types already match, nothing to do
|
||||
continue
|
||||
}
|
||||
|
||||
paramValue := paramValueType.String()
|
||||
logWarning := true
|
||||
var typeError error = nil
|
||||
|
||||
switch optionsField.Type.Kind() {
|
||||
switch paramValueType.Kind() {
|
||||
case reflect.String:
|
||||
// Types match, ignore
|
||||
logWarning = false
|
||||
case reflect.Slice, reflect.Array:
|
||||
// Could do automatic conversion for those types in theory,
|
||||
// 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())
|
||||
case reflect.Bool:
|
||||
// Sensible to convert strings "true"/"false" to respective boolean values as it is
|
||||
// common practice to write booleans as string in yaml files.
|
||||
paramValue = strings.ToLower(paramValue)
|
||||
if paramValue == "true" {
|
||||
config[paramName] = true
|
||||
logWarning = false
|
||||
} else if paramValue == "false" {
|
||||
config[paramName] = false
|
||||
logWarning = false
|
||||
}
|
||||
typeError = convertValueFromString(config, optionsField, paramName, paramValueType.String())
|
||||
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 logWarning {
|
||||
log.Entry().Warnf("Config value for '%s' is of unexpected type and is ignored", paramName)
|
||||
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:
|
||||
// Could do automatic conversion for those types in theory,
|
||||
// but that might obscure what really happens in error cases.
|
||||
return fmt.Errorf("expected type to be a list (or slice, or array) but got string")
|
||||
case reflect.Bool:
|
||||
// Sensible to convert strings "true"/"false" to respective boolean values as it is
|
||||
// common practice to write booleans as string in yaml files.
|
||||
paramValue = strings.ToLower(paramValue)
|
||||
if paramValue == "true" {
|
||||
config[paramName] = true
|
||||
return nil
|
||||
} else if paramValue == "false" {
|
||||
config[paramName] = false
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errIncompatibleTypes
|
||||
}
|
||||
|
||||
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 errIncompatibleTypes
|
||||
}
|
||||
|
||||
func findStructFieldByJSONTag(tagName string, optionsType reflect.Type) *reflect.StructField {
|
||||
for i := 0; i < optionsType.NumField(); i++ {
|
||||
field := optionsType.Field(i)
|
||||
|
@ -134,6 +134,14 @@ func TestGetProjectConfigFile(t *testing.T) {
|
||||
func TestConvertTypes(t *testing.T) {
|
||||
t.Run("Converts strings to booleans", 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 bool `json:"foo,omitempty"`
|
||||
Bar bool `json:"bar,omitempty"`
|
||||
@ -156,6 +164,71 @@ func TestConvertTypes(t *testing.T) {
|
||||
assert.Equal(t, true, stepConfig["bar"])
|
||||
assert.Equal(t, false, options.Foo)
|
||||
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) {
|
||||
// Init
|
||||
|
Loading…
Reference in New Issue
Block a user