1
0
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:
Stephan Aßmus 2020-07-27 08:30:02 +02:00 committed by GitHub
parent 97737a2d8d
commit 7f4fab762d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 143 additions and 25 deletions

View File

@ -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)

View File

@ -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