1
0
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:
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" "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 {

View File

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