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-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
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 .
` ,
//ToDo: respect stageName to also come from parametersJSON -> first env.STAGE_NAME, second: parametersJSON, third: flag
}
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 ( ) )
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 ( ) )
2019-11-04 17:07:30 +02:00
rootCmd . AddCommand ( GithubPublishReleaseCommand ( ) )
2019-12-16 18:34:12 +02:00
rootCmd . AddCommand ( GithubCreatePullRequestCommand ( ) )
2020-02-10 15:53:12 +02:00
rootCmd . AddCommand ( CloudFoundryDeleteServiceCommand ( ) )
2020-02-04 12:43:27 +02:00
rootCmd . AddCommand ( AbapEnvironmentPullGitRepoCommand ( ) )
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-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-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 ( ) )
2019-10-25 14:58:59 +02:00
addRootFlags ( rootCmd )
if err := rootCmd . Execute ( ) ; err != nil {
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" )
2019-11-06 17:22:50 +02:00
rootCmd . PersistentFlags ( ) . StringVar ( & GeneralConfig . StageName , "stageName" , os . Getenv ( "STAGE_NAME" ) , "Name of the stage for which configuration should be included" )
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
}
// 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 {
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-05-06 12:17:56 +02:00
log . SetFormatter ( GeneralConfig . LogFormat )
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-05-05 08:36:24 +02:00
for name , v := range stepConfig . HookConfig {
if name == "sentry" {
hookConfig , _ := v . MarshalJSON ( )
_ = json . Unmarshal ( hookConfig , & GeneralConfig . HookConfig . SentryConfig )
}
}
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-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
}
paramValueType := reflect . ValueOf ( config [ paramName ] )
2020-07-27 11:13:13 +02:00
if paramValueType . Kind ( ) != reflect . String {
// Type check is limited to strings at the moment
2020-04-17 10:29:18 +02:00
continue
}
2020-07-27 11:13:13 +02:00
paramValue := paramValueType . String ( )
logWarning := true
2020-04-17 10:29:18 +02:00
2020-07-27 11:13:13 +02:00
switch optionsField . Type . Kind ( ) {
2020-04-17 10:29:18 +02:00
case reflect . String :
2020-07-27 11:13:13 +02:00
// 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
}
2020-04-17 10:29:18 +02:00
}
2020-07-27 11:13:13 +02:00
if logWarning {
log . Entry ( ) . Warnf ( "Config value for '%s' is of unexpected type and is ignored" , paramName )
2020-04-17 10:29:18 +02:00
}
}
return config
}
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
}