2019-10-24 10:59:58 +02:00
package cmd
import (
2019-10-25 14:58:59 +02:00
"encoding/json"
2019-10-24 10:59:58 +02:00
"fmt"
"io"
"os"
2020-02-06 10:08:15 +02:00
"path/filepath"
2020-04-17 10:29:18 +02:00
"reflect"
2020-07-29 09:22:10 +02:00
"strconv"
2020-02-06 10:08:15 +02:00
"strings"
2019-10-24 10:59:58 +02:00
2019-10-25 14:58:59 +02:00
"github.com/SAP/jenkins-library/pkg/config"
2019-11-11 10:52:44 +02:00
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
2019-10-25 14:58:59 +02:00
"github.com/pkg/errors"
2019-10-24 10:59:58 +02:00
"github.com/spf13/cobra"
)
2019-11-06 17:22:50 +02:00
// GeneralConfigOptions contains all global configuration options for piper binary
type GeneralConfigOptions struct {
2020-05-14 10:50:58 +02:00
CorrelationID string
CustomConfig string
DefaultConfig [ ] string //ordered list of Piper default configurations. Can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
IgnoreCustomDefaults bool
ParametersJSON string
EnvRootPath string
NoTelemetry bool
StageName string
StepConfigJSON string
StepMetadata string //metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
StepName string
Verbose bool
LogFormat string
2020-09-16 14:50:09 +02:00
VaultRoleID string
VaultRoleSecretID string
2020-05-14 10:50:58 +02:00
HookConfig HookConfiguration
2020-05-05 08:36:24 +02:00
}
// HookConfiguration contains the configuration for supported hooks, so far only Sentry is supported.
type HookConfiguration struct {
SentryConfig SentryConfiguration ` json:"sentry,omitempty" `
}
// SentryConfiguration defines the configuration options for the Sentry logging system
type SentryConfiguration struct {
Dsn string ` json:"dsn,omitempty" `
2019-10-24 10:59:58 +02:00
}
var rootCmd = & cobra . Command {
Use : "piper" ,
Short : "Executes CI/CD steps from project 'Piper' " ,
Long : `
2020-04-24 18:29:30 +02:00
This project ' Piper ' binary provides a CI / CD step library .
2019-10-24 10:59:58 +02:00
It contains many steps which can be used within CI / CD systems as well as directly on e . g . a developer ' s machine .
` ,
}
2019-11-06 15:07:41 +02:00
// GeneralConfig contains global configuration flags for piper binary
2019-11-06 17:22:50 +02:00
var GeneralConfig GeneralConfigOptions
2019-10-24 10:59:58 +02:00
// Execute is the starting point of the piper command line tool
func Execute ( ) {
2020-04-03 16:34:40 +02:00
rootCmd . AddCommand ( ArtifactPrepareVersionCommand ( ) )
2019-10-24 10:59:58 +02:00
rootCmd . AddCommand ( ConfigCommand ( ) )
2020-05-18 08:55:25 +02:00
rootCmd . AddCommand ( ContainerSaveImageCommand ( ) )
2020-09-07 15:22:01 +02:00
rootCmd . AddCommand ( CommandLineCompletionCommand ( ) )
2019-10-31 14:57:29 +02:00
rootCmd . AddCommand ( VersionCommand ( ) )
2019-12-13 11:55:45 +02:00
rootCmd . AddCommand ( DetectExecuteScanCommand ( ) )
2019-10-30 10:20:25 +02:00
rootCmd . AddCommand ( KarmaExecuteTestsCommand ( ) )
2020-03-23 11:38:31 +02:00
rootCmd . AddCommand ( SonarExecuteScanCommand ( ) )
2020-01-24 15:30:27 +02:00
rootCmd . AddCommand ( KubernetesDeployCommand ( ) )
2019-12-05 15:22:38 +02:00
rootCmd . AddCommand ( XsDeployCommand ( ) )
2020-09-14 12:05:12 +02:00
rootCmd . AddCommand ( GithubCheckBranchProtectionCommand ( ) )
2020-09-11 18:56:51 +02:00
rootCmd . AddCommand ( GithubCreatePullRequestCommand ( ) )
2020-09-14 12:05:12 +02:00
rootCmd . AddCommand ( GithubPublishReleaseCommand ( ) )
2020-09-14 18:08:24 +02:00
rootCmd . AddCommand ( GithubSetCommitStatusCommand ( ) )
2020-10-20 09:05:17 +02:00
rootCmd . AddCommand ( GitopsUpdateDeploymentCommand ( ) )
2020-02-10 15:53:12 +02:00
rootCmd . AddCommand ( CloudFoundryDeleteServiceCommand ( ) )
2020-02-04 12:43:27 +02:00
rootCmd . AddCommand ( AbapEnvironmentPullGitRepoCommand ( ) )
2020-08-21 14:49:48 +02:00
rootCmd . AddCommand ( AbapEnvironmentCloneGitRepoCommand ( ) )
2020-08-04 17:52:28 +02:00
rootCmd . AddCommand ( AbapEnvironmentCheckoutBranchCommand ( ) )
2020-01-28 00:40:53 +02:00
rootCmd . AddCommand ( CheckmarxExecuteScanCommand ( ) )
2020-05-25 19:48:59 +02:00
rootCmd . AddCommand ( FortifyExecuteScanCommand ( ) )
2020-02-25 15:33:34 +02:00
rootCmd . AddCommand ( MtaBuildCommand ( ) )
2020-02-06 17:16:34 +02:00
rootCmd . AddCommand ( ProtecodeExecuteScanCommand ( ) )
2020-02-28 14:09:46 +02:00
rootCmd . AddCommand ( MavenExecuteCommand ( ) )
2020-04-01 11:45:31 +02:00
rootCmd . AddCommand ( CloudFoundryCreateServiceKeyCommand ( ) )
2020-03-13 14:32:37 +02:00
rootCmd . AddCommand ( MavenBuildCommand ( ) )
2020-07-29 19:51:27 +02:00
rootCmd . AddCommand ( MavenExecuteIntegrationCommand ( ) )
2020-03-12 16:45:57 +02:00
rootCmd . AddCommand ( MavenExecuteStaticCodeChecksCommand ( ) )
2020-03-20 19:20:52 +02:00
rootCmd . AddCommand ( NexusUploadCommand ( ) )
2020-05-13 14:51:48 +02:00
rootCmd . AddCommand ( AbapEnvironmentRunATCCheckCommand ( ) )
2020-04-24 18:29:30 +02:00
rootCmd . AddCommand ( NpmExecuteScriptsCommand ( ) )
2020-06-22 10:12:28 +02:00
rootCmd . AddCommand ( NpmExecuteLintCommand ( ) )
2020-04-24 15:31:41 +02:00
rootCmd . AddCommand ( GctsCreateRepositoryCommand ( ) )
2020-07-14 10:58:57 +02:00
rootCmd . AddCommand ( GctsExecuteABAPUnitTestsCommand ( ) )
2020-05-18 21:39:35 +02:00
rootCmd . AddCommand ( GctsDeployCommand ( ) )
2020-07-27 08:30:02 +02:00
rootCmd . AddCommand ( MalwareExecuteScanCommand ( ) )
2020-08-19 10:17:27 +02:00
rootCmd . AddCommand ( CloudFoundryCreateServiceCommand ( ) )
2020-07-31 12:38:00 +02:00
rootCmd . AddCommand ( CloudFoundryDeployCommand ( ) )
2020-07-27 11:13:13 +02:00
rootCmd . AddCommand ( GctsRollbackCommand ( ) )
Whitesource scan (MVP) (#1658)
* Whitesource MVP for Gradle, Golang, and NPM/Yarn
* Refactoring
* Refactor and cleanup, better error checking
* publish stepResults, use pkg/versioning, bubble up errors, add gomod versioning support
* Run gofmt and cleanup comments
* Resolve PR comments
* Update resources/metadata/whitesource.yaml
Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
* Only determine project coordinates if they are missing
Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
* Gradle versioning artifact
* fix gradle artifact version regexp and refactor
* Fix token extraction from output buffer
* Fix some issues with pip and jsonfile versioning logic
* Remove useless spacing
* Remove unnecessary test file and fix naming style for JSONDescriptor
* Automatically download wss-unified-agent if file does not exist
* adds downloadVulnerabilityReport, checkSecurityViolations, minor refactoring
* adds config.ReportDirectoryName, improves readability
* Version-wide reporting for vulnerabilities and list of libraries.
* Refactor and improve build accuracy
* fix sed command
* Add includes file pattern config option
* Adds --exclude command line flag
* run go mod tidy and regenerate step framework
* Fix unit tests
* revert changes
* poll project status before downloading reports
* merge with master
* go mod tidy, go fmt, and fix whitesource unit test
* sync go.mod
* sync go.mod again
Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
2020-07-01 07:54:13 +02:00
rootCmd . AddCommand ( WhitesourceExecuteScanCommand ( ) )
2020-06-18 07:45:22 +02:00
rootCmd . AddCommand ( GctsCloneRepositoryCommand ( ) )
2020-06-18 14:50:46 +02:00
rootCmd . AddCommand ( JsonApplyPatchCommand ( ) )
2020-07-10 08:07:59 +02:00
rootCmd . AddCommand ( KanikoExecuteCommand ( ) )
2020-08-27 07:54:03 +02:00
rootCmd . AddCommand ( AbapEnvironmentAssemblePackagesCommand ( ) )
2020-09-17 11:01:19 +02:00
rootCmd . AddCommand ( AbapAddonAssemblyKitCheckCVsCommand ( ) )
2020-09-17 15:56:14 +02:00
rootCmd . AddCommand ( AbapAddonAssemblyKitCheckPVCommand ( ) )
2020-09-18 11:18:51 +02:00
rootCmd . AddCommand ( AbapAddonAssemblyKitCreateTargetVectorCommand ( ) )
2020-09-18 10:24:46 +02:00
rootCmd . AddCommand ( AbapAddonAssemblyKitPublishTargetVectorCommand ( ) )
2020-09-18 14:07:42 +02:00
rootCmd . AddCommand ( AbapAddonAssemblyKitRegisterPackagesCommand ( ) )
2020-09-18 16:40:49 +02:00
rootCmd . AddCommand ( AbapAddonAssemblyKitReleasePackagesCommand ( ) )
2020-09-18 17:27:04 +02:00
rootCmd . AddCommand ( AbapAddonAssemblyKitReserveNextPackagesCommand ( ) )
2020-09-24 11:30:25 +02:00
rootCmd . AddCommand ( CloudFoundryCreateSpaceCommand ( ) )
rootCmd . AddCommand ( CloudFoundryDeleteSpaceCommand ( ) )
2019-10-25 14:58:59 +02:00
addRootFlags ( rootCmd )
if err := rootCmd . Execute ( ) ; err != nil {
2020-09-22 07:49:34 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
2020-06-24 10:04:05 +02:00
log . Entry ( ) . WithError ( err ) . Fatal ( "configuration error" )
2019-10-25 14:58:59 +02:00
}
}
func addRootFlags ( rootCmd * cobra . Command ) {
2020-04-28 07:42:02 +02:00
rootCmd . PersistentFlags ( ) . StringVar ( & GeneralConfig . CorrelationID , "correlationID" , os . Getenv ( "PIPER_correlationID" ) , "ID for unique identification of a pipeline run" )
2019-11-06 17:22:50 +02:00
rootCmd . PersistentFlags ( ) . StringVar ( & GeneralConfig . CustomConfig , "customConfig" , ".pipeline/config.yml" , "Path to the pipeline configuration file" )
2019-11-21 17:09:57 +02:00
rootCmd . PersistentFlags ( ) . StringSliceVar ( & GeneralConfig . DefaultConfig , "defaultConfig" , [ ] string { ".pipeline/defaults.yaml" } , "Default configurations, passed as path to yaml file" )
2020-05-14 10:50:58 +02:00
rootCmd . PersistentFlags ( ) . BoolVar ( & GeneralConfig . IgnoreCustomDefaults , "ignoreCustomDefaults" , false , "Disables evaluation of the parameter 'customDefaults' in the pipeline configuration file" )
2019-11-06 17:22:50 +02:00
rootCmd . PersistentFlags ( ) . StringVar ( & GeneralConfig . ParametersJSON , "parametersJSON" , os . Getenv ( "PIPER_parametersJSON" ) , "Parameters to be considered in JSON format" )
2020-01-15 13:16:25 +02:00
rootCmd . PersistentFlags ( ) . StringVar ( & GeneralConfig . EnvRootPath , "envRootPath" , ".pipeline" , "Root path to Piper pipeline shared environments" )
2020-08-13 17:55:12 +02:00
rootCmd . PersistentFlags ( ) . StringVar ( & GeneralConfig . StageName , "stageName" , "" , "Name of the stage for which configuration should be included" )
2019-11-06 17:22:50 +02:00
rootCmd . PersistentFlags ( ) . StringVar ( & GeneralConfig . StepConfigJSON , "stepConfigJSON" , os . Getenv ( "PIPER_stepConfigJSON" ) , "Step configuration in JSON format" )
2020-01-29 14:17:54 +02:00
rootCmd . PersistentFlags ( ) . BoolVar ( & GeneralConfig . NoTelemetry , "noTelemetry" , false , "Disables telemetry reporting" )
2019-11-06 17:22:50 +02:00
rootCmd . PersistentFlags ( ) . BoolVarP ( & GeneralConfig . Verbose , "verbose" , "v" , false , "verbose output" )
2020-05-06 12:17:56 +02:00
rootCmd . PersistentFlags ( ) . StringVar ( & GeneralConfig . LogFormat , "logFormat" , "default" , "Log format to use. Options: default, timestamp, plain, full." )
2019-10-24 10:59:58 +02:00
2019-10-25 14:58:59 +02:00
}
2020-08-13 17:55:12 +02:00
const stageNameEnvKey = "STAGE_NAME"
// initStageName initializes GeneralConfig.StageName from either GeneralConfig.ParametersJSON
// or the environment variable 'STAGE_NAME', unless it has been provided as command line option.
// Log output needs to be suppressed via outputToLog by the getConfig step.
func initStageName ( outputToLog bool ) {
var stageNameSource string
if outputToLog {
defer func ( ) {
log . Entry ( ) . Infof ( "Using stageName '%s' from %s" , GeneralConfig . StageName , stageNameSource )
} ( )
}
if GeneralConfig . StageName != "" {
// Means it was given as command line argument and has the highest precedence
stageNameSource = "command line arguments"
return
}
// Use stageName from ENV as fall-back, for when extracting it from parametersJSON fails below
GeneralConfig . StageName = os . Getenv ( stageNameEnvKey )
stageNameSource = fmt . Sprintf ( "env variable '%s'" , stageNameEnvKey )
if len ( GeneralConfig . ParametersJSON ) == 0 {
return
}
var params map [ string ] interface { }
err := json . Unmarshal ( [ ] byte ( GeneralConfig . ParametersJSON ) , & params )
if err != nil {
if outputToLog {
log . Entry ( ) . Infof ( "Failed to extract 'stageName' from parametersJSON: %v" , err )
}
return
}
stageName , hasKey := params [ "stageName" ]
if ! hasKey {
return
}
if stageNameString , ok := stageName . ( string ) ; ok && stageNameString != "" {
stageNameSource = "parametersJSON"
GeneralConfig . StageName = stageNameString
}
}
2019-10-25 14:58:59 +02:00
// PrepareConfig reads step configuration from various sources and merges it (defaults, config file, flags, ...)
func PrepareConfig ( cmd * cobra . Command , metadata * config . StepData , stepName string , options interface { } , openFile func ( s string ) ( io . ReadCloser , error ) ) error {
2020-08-13 17:55:12 +02:00
log . SetFormatter ( GeneralConfig . LogFormat )
initStageName ( true )
2019-10-25 14:58:59 +02:00
filters := metadata . GetParameterFilters ( )
2020-01-29 14:17:54 +02:00
// add telemetry parameter "collectTelemetryData" to ALL, GENERAL and PARAMETER filters
filters . All = append ( filters . All , "collectTelemetryData" )
filters . General = append ( filters . General , "collectTelemetryData" )
filters . Parameters = append ( filters . Parameters , "collectTelemetryData" )
2020-01-15 13:16:25 +02:00
resourceParams := metadata . GetResourceParameters ( GeneralConfig . EnvRootPath , "commonPipelineEnvironment" )
2019-10-25 14:58:59 +02:00
flagValues := config . AvailableFlagValues ( cmd , & filters )
var myConfig config . Config
var stepConfig config . StepConfig
2020-09-16 14:50:09 +02:00
// add vault credentials so that configuration can be fetched from vault
if GeneralConfig . VaultRoleID == "" {
2020-10-13 14:14:47 +02:00
GeneralConfig . VaultRoleID = os . Getenv ( "PIPER_vaultAppRoleID" )
2020-09-16 14:50:09 +02:00
}
if GeneralConfig . VaultRoleSecretID == "" {
2020-10-13 14:14:47 +02:00
GeneralConfig . VaultRoleSecretID = os . Getenv ( "PIPER_vaultAppRoleSecretID" )
2020-09-16 14:50:09 +02:00
}
myConfig . SetVaultCredentials ( GeneralConfig . VaultRoleID , GeneralConfig . VaultRoleSecretID )
2019-11-06 17:22:50 +02:00
if len ( GeneralConfig . StepConfigJSON ) != 0 {
2019-10-25 14:58:59 +02:00
// ignore config & defaults in favor of passed stepConfigJSON
2019-11-06 17:22:50 +02:00
stepConfig = config . GetStepConfigWithJSON ( flagValues , GeneralConfig . StepConfigJSON , filters )
2020-06-10 09:39:29 +02:00
log . Entry ( ) . Infof ( "Project config: passed via JSON" )
log . Entry ( ) . Infof ( "Project defaults: passed via JSON" )
2019-10-25 14:58:59 +02:00
} else {
// use config & defaults
2019-11-11 10:52:44 +02:00
var customConfig io . ReadCloser
var err error
2019-10-25 14:58:59 +02:00
//accept that config file and defaults cannot be loaded since both are not mandatory here
2019-12-11 11:13:23 +02:00
{
2020-02-06 10:08:15 +02:00
projectConfigFile := getProjectConfigFile ( GeneralConfig . CustomConfig )
2020-06-10 09:39:29 +02:00
if exists , err := piperutils . FileExists ( projectConfigFile ) ; exists {
log . Entry ( ) . Infof ( "Project config: '%s'" , projectConfigFile )
2020-02-06 10:08:15 +02:00
if customConfig , err = openFile ( projectConfigFile ) ; err != nil {
2020-03-31 08:47:09 +02:00
return errors . Wrapf ( err , "Cannot read '%s'" , projectConfigFile )
2019-12-11 11:13:23 +02:00
}
} else {
2020-06-10 09:39:29 +02:00
log . Entry ( ) . Infof ( "Project config: NONE ('%s' does not exist)" , projectConfigFile )
2019-12-11 11:13:23 +02:00
customConfig = nil
}
}
2019-10-25 14:58:59 +02:00
var defaultConfig [ ] io . ReadCloser
2020-06-10 09:39:29 +02:00
if len ( GeneralConfig . DefaultConfig ) == 0 {
log . Entry ( ) . Info ( "Project defaults: NONE" )
}
for _ , projectDefaultFile := range GeneralConfig . DefaultConfig {
fc , err := openFile ( projectDefaultFile )
2020-03-31 08:47:09 +02:00
// only create error for non-default values
2020-06-10 09:39:29 +02:00
if err != nil {
if projectDefaultFile != ".pipeline/defaults.yaml" {
log . Entry ( ) . Infof ( "Project defaults: '%s'" , projectDefaultFile )
return errors . Wrapf ( err , "Cannot read '%s'" , projectDefaultFile )
}
} else {
log . Entry ( ) . Infof ( "Project defaults: '%s'" , projectDefaultFile )
2020-03-31 08:47:09 +02:00
defaultConfig = append ( defaultConfig , fc )
2020-03-30 14:31:24 +02:00
}
2019-10-25 14:58:59 +02:00
}
2020-05-14 10:50:58 +02:00
stepConfig , err = myConfig . GetStepConfig ( flagValues , GeneralConfig . ParametersJSON , customConfig , defaultConfig , GeneralConfig . IgnoreCustomDefaults , filters , metadata . Spec . Inputs . Parameters , metadata . Spec . Inputs . Secrets , resourceParams , GeneralConfig . StageName , stepName , metadata . Metadata . Aliases )
2019-10-25 14:58:59 +02:00
if err != nil {
return errors . Wrap ( err , "retrieving step configuration failed" )
}
2019-10-24 10:59:58 +02:00
}
2019-10-25 14:58:59 +02:00
2020-01-29 14:17:54 +02:00
if fmt . Sprintf ( "%v" , stepConfig . Config [ "collectTelemetryData" ] ) == "false" {
GeneralConfig . NoTelemetry = true
}
2020-04-29 15:05:00 +02:00
if ! GeneralConfig . Verbose && stepConfig . Config [ "verbose" ] != nil {
if verboseValue , ok := stepConfig . Config [ "verbose" ] . ( bool ) ; ok {
log . SetVerbose ( verboseValue )
} else {
return fmt . Errorf ( "invalid value for parameter verbose: '%v'" , stepConfig . Config [ "verbose" ] )
2020-01-28 00:40:53 +02:00
}
}
2020-05-06 17:43:32 +02:00
stepConfig . Config = checkTypes ( stepConfig . Config , options )
2019-10-25 14:58:59 +02:00
confJSON , _ := json . Marshal ( stepConfig . Config )
2020-04-01 20:46:33 +02:00
_ = json . Unmarshal ( confJSON , & options )
2019-10-25 14:58:59 +02:00
config . MarkFlagsWithValue ( cmd , stepConfig )
2020-07-28 17:19:33 +02:00
retrieveHookConfig ( stepConfig . HookConfig , & GeneralConfig . HookConfig )
2020-05-05 08:36:24 +02:00
2019-10-25 14:58:59 +02:00
return nil
2019-10-24 10:59:58 +02:00
}
2020-02-06 10:08:15 +02:00
2020-07-28 17:19:33 +02:00
func retrieveHookConfig ( source * json . RawMessage , target * HookConfiguration ) {
if source != nil {
log . Entry ( ) . Info ( "Retrieving hook configuration" )
err := json . Unmarshal ( * source , target )
if err != nil {
log . Entry ( ) . Warningf ( "Failed to retrieve hook configuration: %v" , err )
}
}
}
2020-07-29 09:22:10 +02:00
var errIncompatibleTypes = fmt . Errorf ( "incompatible types" )
2020-05-06 17:43:32 +02:00
func checkTypes ( config map [ string ] interface { } , options interface { } ) map [ string ] interface { } {
2020-04-17 10:29:18 +02:00
optionsType := getStepOptionsStructType ( options )
for paramName := range config {
optionsField := findStructFieldByJSONTag ( paramName , optionsType )
if optionsField == nil {
continue
}
2020-07-29 09:22:10 +02:00
if config [ paramName ] == nil {
// There is a key, but no value. This can result from merging values from the CPE.
continue
}
2020-04-17 10:29:18 +02:00
paramValueType := reflect . ValueOf ( config [ paramName ] )
2020-07-29 09:22:10 +02:00
if optionsField . Type . Kind ( ) == paramValueType . Kind ( ) {
// Types already match, nothing to do
2020-04-17 10:29:18 +02:00
continue
}
2020-07-29 09:22:10 +02:00
var typeError error = nil
2020-04-17 10:29:18 +02:00
2020-07-29 09:22:10 +02:00
switch paramValueType . Kind ( ) {
2020-04-17 10:29:18 +02:00
case reflect . String :
2020-07-29 09:22:10 +02:00
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 :
log . Entry ( ) . Warnf ( "Config value for '%s' is of unexpected type %s, expected %s. " +
"The value may be ignored as a result. To avoid any risk, specify this value with explicit type." ,
paramName , paramValueType . Kind ( ) , optionsField . Type . Kind ( ) )
2020-04-17 10:29:18 +02:00
}
2020-07-29 09:22:10 +02:00
if typeError != nil {
typeError = fmt . Errorf ( "config value for '%s' is of unexpected type %s, expected %s: %w" ,
paramName , paramValueType . Kind ( ) , optionsField . Type . Kind ( ) , typeError )
log . SetErrorCategory ( log . ErrorConfiguration )
2020-08-12 15:41:29 +02:00
log . Entry ( ) . WithError ( typeError ) . Fatal ( "type error in configuration" )
2020-04-17 10:29:18 +02:00
}
}
return config
}
2020-07-29 09:22:10 +02:00
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 :
2020-10-27 16:14:00 +02:00
val := strconv . FormatFloat ( paramValue , 'f' , - 1 , 64 )
// allow float numbers containing a decimal separator
if strings . Contains ( val , "." ) {
config [ paramName ] = val
return nil
}
// if no decimal separator is available we cannot be sure that the result is correct:
// long numbers like e.g. 73554900100200011600 will not be represented correctly after reading the yaml
// thus we cannot assume that the string is correct.
// short numbers will be handled as int anyway
return errIncompatibleTypes
2020-07-29 09:22:10 +02:00
case reflect . Float32 :
config [ paramName ] = float32 ( paramValue )
return nil
case reflect . Float64 :
config [ paramName ] = paramValue
return nil
case reflect . Int :
// Treat as type-mismatch only in case the conversion would be lossy.
// In that case, the json.Unmarshall() would indeed just drop it, so we want to fail.
if float64 ( int ( paramValue ) ) == paramValue {
config [ paramName ] = int ( 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
}
2020-04-17 10:29:18 +02:00
func findStructFieldByJSONTag ( tagName string , optionsType reflect . Type ) * reflect . StructField {
for i := 0 ; i < optionsType . NumField ( ) ; i ++ {
field := optionsType . Field ( i )
tag := field . Tag . Get ( "json" )
if tagName == tag || tagName + ",omitempty" == tag {
return & field
}
}
return nil
}
func getStepOptionsStructType ( stepOptions interface { } ) reflect . Type {
typedOptions := reflect . ValueOf ( stepOptions )
if typedOptions . Kind ( ) == reflect . Ptr {
typedOptions = typedOptions . Elem ( )
}
return typedOptions . Type ( )
}
2020-02-06 10:08:15 +02:00
func getProjectConfigFile ( name string ) string {
var altName string
if ext := filepath . Ext ( name ) ; ext == ".yml" {
altName = fmt . Sprintf ( "%v.yaml" , strings . TrimSuffix ( name , ext ) )
} else if ext == "yaml" {
altName = fmt . Sprintf ( "%v.yml" , strings . TrimSuffix ( name , ext ) )
}
fileExists , _ := piperutils . FileExists ( name )
altExists , _ := piperutils . FileExists ( altName )
// configured filename will always take precedence, even if not existing
if ! fileExists && altExists {
return altName
}
return name
}