2019-10-24 10:59:58 +02:00
package cmd
import (
"fmt"
2020-05-20 10:50:35 +02:00
"io"
"os"
2020-10-14 11:13:08 +02:00
"path"
"path/filepath"
2020-05-20 10:50:35 +02:00
2019-10-24 10:59:58 +02:00
"github.com/SAP/jenkins-library/pkg/config"
2020-05-26 07:58:03 +02:00
"github.com/SAP/jenkins-library/pkg/log"
2021-02-10 17:18:00 +02:00
"github.com/SAP/jenkins-library/pkg/reporting"
ws "github.com/SAP/jenkins-library/pkg/whitesource"
2019-10-24 10:59:58 +02:00
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type configCommandOptions struct {
2021-08-04 10:09:02 +02:00
output string //output format, so far only JSON
parametersJSON string //parameters to be considered in JSON format
stageConfig bool
stageConfigAcceptedParameters [ ] string
stepMetadata string //metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
stepName string
contextConfig bool
openFile func ( s string , t map [ string ] string ) ( io . ReadCloser , error )
2019-10-24 10:59:58 +02:00
}
var configOptions configCommandOptions
// ConfigCommand is the entry command for loading the configuration of a pipeline step
func ConfigCommand ( ) * cobra . Command {
2019-11-21 17:09:57 +02:00
configOptions . openFile = config . OpenPiperFile
2019-10-24 10:59:58 +02:00
var createConfigCmd = & cobra . Command {
Use : "getConfig" ,
Short : "Loads the project 'Piper' configuration respecting defaults and parameters." ,
2020-05-26 07:58:03 +02:00
PreRun : func ( cmd * cobra . Command , args [ ] string ) {
path , _ := os . Getwd ( )
fatalHook := & log . FatalHook { CorrelationID : GeneralConfig . CorrelationID , Path : path }
log . RegisterHook ( fatalHook )
2020-08-13 17:55:12 +02:00
initStageName ( false )
2021-07-08 15:26:07 +02:00
GeneralConfig . GitHubAccessTokens = ResolveAccessTokens ( GeneralConfig . GitHubTokens )
2020-05-26 07:58:03 +02:00
} ,
Run : func ( cmd * cobra . Command , _ [ ] string ) {
err := generateConfig ( )
if err != nil {
2020-08-19 20:20:00 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
log . Entry ( ) . WithError ( err ) . Fatal ( "failed to retrieve configuration" )
2020-05-26 07:58:03 +02:00
}
2019-10-24 10:59:58 +02:00
} ,
}
addConfigFlags ( createConfigCmd )
return createConfigCmd
}
func generateConfig ( ) error {
var myConfig config . Config
var stepConfig config . StepConfig
2021-08-04 10:09:02 +02:00
if configOptions . stageConfig {
projectConfigFile := getProjectConfigFile ( GeneralConfig . CustomConfig )
2019-10-24 10:59:58 +02:00
2021-08-04 10:09:02 +02:00
customConfig , err := configOptions . openFile ( projectConfigFile , GeneralConfig . GitHubAccessTokens )
if err != nil {
if ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "config: open configuration file '%v' failed" , projectConfigFile )
}
customConfig = nil
}
2020-10-14 11:13:08 +02:00
2021-08-04 10:09:02 +02:00
defaultConfig := [ ] io . ReadCloser { }
for _ , f := range GeneralConfig . DefaultConfig {
fc , err := configOptions . openFile ( f , GeneralConfig . GitHubAccessTokens )
// only create error for non-default values
if err != nil && f != ".pipeline/defaults.yaml" {
return errors . Wrapf ( err , "config: getting defaults failed: '%v'" , f )
}
if err == nil {
defaultConfig = append ( defaultConfig , fc )
}
}
2020-01-15 13:16:25 +02:00
2021-08-04 10:09:02 +02:00
stepConfig , err = myConfig . GetStageConfig ( GeneralConfig . ParametersJSON , customConfig , defaultConfig , GeneralConfig . IgnoreCustomDefaults , configOptions . stageConfigAcceptedParameters , GeneralConfig . StageName )
if err != nil {
return errors . Wrap ( err , "getting stage config failed" )
}
2020-04-14 18:10:56 +02:00
2021-08-04 10:09:02 +02:00
} else {
metadata , err := resolveMetadata ( )
if err != nil {
return errors . Wrapf ( err , "failed to resolve metadata" )
2020-03-31 16:06:27 +02:00
}
2019-10-24 10:59:58 +02:00
2021-08-04 10:09:02 +02:00
// prepare output resource directories:
// this is needed in order to have proper directory permissions in case
// resources written inside a container image with a different user
// Remark: This is so far only relevant for Jenkins environments where getConfig is executed
prepareOutputEnvironment ( metadata . Spec . Outputs . Resources , GeneralConfig . EnvRootPath )
2019-10-24 10:59:58 +02:00
2021-08-04 10:09:02 +02:00
resourceParams := metadata . GetResourceParameters ( GeneralConfig . EnvRootPath , "commonPipelineEnvironment" )
projectConfigFile := getProjectConfigFile ( GeneralConfig . CustomConfig )
customConfig , err := configOptions . openFile ( projectConfigFile , GeneralConfig . GitHubAccessTokens )
if err != nil {
if ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "config: open configuration file '%v' failed" , projectConfigFile )
}
customConfig = nil
2019-10-24 10:59:58 +02:00
}
2021-08-04 10:09:02 +02:00
defaultConfig , paramFilter , err := defaultsAndFilters ( & metadata , metadata . Metadata . Name )
if err != nil {
return errors . Wrap ( err , "defaults: retrieving step defaults failed" )
2020-03-31 08:47:09 +02:00
}
2019-10-24 10:59:58 +02:00
2021-08-04 10:09:02 +02:00
for _ , f := range GeneralConfig . DefaultConfig {
fc , err := configOptions . openFile ( f , GeneralConfig . GitHubAccessTokens )
// only create error for non-default values
if err != nil && f != ".pipeline/defaults.yaml" {
return errors . Wrapf ( err , "config: getting defaults failed: '%v'" , f )
}
if err == nil {
defaultConfig = append ( defaultConfig , fc )
}
}
2019-10-24 10:59:58 +02:00
2021-08-04 10:09:02 +02:00
var flags map [ string ] interface { }
2019-10-29 11:58:24 +02:00
2021-08-04 10:09:02 +02:00
params := [ ] config . StepParameters { }
if ! configOptions . contextConfig {
params = metadata . Spec . Inputs . Parameters
}
2019-10-24 10:59:58 +02:00
2021-08-04 10:09:02 +02:00
stepConfig , err = myConfig . GetStepConfig ( flags , GeneralConfig . ParametersJSON , customConfig , defaultConfig , GeneralConfig . IgnoreCustomDefaults , paramFilter , params , metadata . Spec . Inputs . Secrets , resourceParams , GeneralConfig . StageName , metadata . Metadata . Name , metadata . Metadata . Aliases )
if err != nil {
return errors . Wrap ( err , "getting step config failed" )
}
// apply context conditions if context configuration is requested
if configOptions . contextConfig {
applyContextConditions ( metadata , & stepConfig )
}
2020-01-24 15:30:27 +02:00
}
2019-10-24 10:59:58 +02:00
myConfigJSON , _ := config . GetJSON ( stepConfig . Config )
fmt . Println ( myConfigJSON )
return nil
}
func addConfigFlags ( cmd * cobra . Command ) {
//ToDo: support more output options, like https://kubernetes.io/docs/reference/kubectl/overview/#formatting-output
cmd . Flags ( ) . StringVar ( & configOptions . output , "output" , "json" , "Defines the output format" )
cmd . Flags ( ) . StringVar ( & configOptions . parametersJSON , "parametersJSON" , os . Getenv ( "PIPER_parametersJSON" ) , "Parameters to be considered in JSON format" )
2021-08-04 10:09:02 +02:00
cmd . Flags ( ) . BoolVar ( & configOptions . stageConfig , "stageConfig" , false , "Defines if step stage configuration should be loaded and no step-specific config" )
cmd . Flags ( ) . StringArrayVar ( & configOptions . stageConfigAcceptedParameters , "stageConfigAcceptedParams" , [ ] string { } , "Defines the parameters used for filtering stage/general configuration when accessing stage config" )
2019-10-24 10:59:58 +02:00
cmd . Flags ( ) . StringVar ( & configOptions . stepMetadata , "stepMetadata" , "" , "Step metadata, passed as path to yaml" )
2021-06-16 08:43:30 +02:00
cmd . Flags ( ) . StringVar ( & configOptions . stepName , "stepName" , "" , "Step name, used to get step metadata if yaml path is not set" )
2019-10-24 10:59:58 +02:00
cmd . Flags ( ) . BoolVar ( & configOptions . contextConfig , "contextConfig" , false , "Defines if step context configuration should be loaded instead of step config" )
}
2019-11-22 11:14:21 +02:00
func defaultsAndFilters ( metadata * config . StepData , stepName string ) ( [ ] io . ReadCloser , config . StepFilters , error ) {
2019-10-24 10:59:58 +02:00
if configOptions . contextConfig {
2019-11-22 11:14:21 +02:00
defaults , err := metadata . GetContextDefaults ( stepName )
2019-10-24 10:59:58 +02:00
if err != nil {
return nil , config . StepFilters { } , errors . Wrap ( err , "metadata: getting context defaults failed" )
}
return [ ] io . ReadCloser { defaults } , metadata . GetContextParameterFilters ( ) , nil
}
//ToDo: retrieve default values from metadata
2020-03-31 08:47:09 +02:00
return [ ] io . ReadCloser { } , metadata . GetParameterFilters ( ) , nil
2019-10-24 10:59:58 +02:00
}
2020-01-24 15:30:27 +02:00
func applyContextConditions ( metadata config . StepData , stepConfig * config . StepConfig ) {
//consider conditions for context configuration
//containers
2020-07-10 10:32:26 +02:00
config . ApplyContainerConditions ( metadata . Spec . Containers , stepConfig )
2020-01-24 15:30:27 +02:00
//sidecars
2020-07-10 10:32:26 +02:00
config . ApplyContainerConditions ( metadata . Spec . Sidecars , stepConfig )
2020-01-24 15:30:27 +02:00
//ToDo: remove all unnecessary sub maps?
// e.g. extract delete() from applyContainerConditions - loop over all stepConfig.Config[param.Value] and remove ...
}
2020-10-14 11:13:08 +02:00
func prepareOutputEnvironment ( outputResources [ ] config . StepResources , envRootPath string ) {
for _ , oResource := range outputResources {
for _ , oParam := range oResource . Parameters {
paramPath := path . Join ( envRootPath , oResource . Name , fmt . Sprint ( oParam [ "name" ] ) )
if oParam [ "fields" ] != nil {
paramFields , ok := oParam [ "fields" ] . ( [ ] map [ string ] string )
if ok && len ( paramFields ) > 0 {
paramPath = path . Join ( paramPath , paramFields [ 0 ] [ "name" ] )
}
}
if _ , err := os . Stat ( filepath . Dir ( paramPath ) ) ; os . IsNotExist ( err ) {
log . Entry ( ) . Debugf ( "Creating directory: %v" , filepath . Dir ( paramPath ) )
os . MkdirAll ( filepath . Dir ( paramPath ) , 0777 )
}
}
}
2021-02-10 17:18:00 +02:00
// prepare additional output directories known to possibly create permission issues when created from within a container
// ToDo: evaluate if we can rather call this only in the correct step context (we know the step when calling getConfig!)
// Could this be part of the container definition in the step.yaml?
stepOutputDirectories := [ ] string {
2021-03-19 12:10:08 +02:00
reporting . StepReportDirectory , // standard directory to collect md reports for pipelineCreateScanSummary
ws . ReportsDirectory , // standard directory for reports created by whitesourceExecuteScan
2021-02-10 17:18:00 +02:00
}
for _ , dir := range stepOutputDirectories {
if _ , err := os . Stat ( dir ) ; os . IsNotExist ( err ) {
log . Entry ( ) . Debugf ( "Creating directory: %v" , dir )
os . MkdirAll ( dir , 0777 )
}
}
2020-10-14 11:13:08 +02:00
}
2021-06-16 08:43:30 +02:00
func resolveMetadata ( ) ( config . StepData , error ) {
var metadata config . StepData
if configOptions . stepMetadata != "" {
2021-07-08 15:26:07 +02:00
metadataFile , err := configOptions . openFile ( configOptions . stepMetadata , GeneralConfig . GitHubAccessTokens )
2021-06-16 08:43:30 +02:00
if err != nil {
return metadata , errors . Wrap ( err , "open failed" )
}
err = metadata . ReadPipelineStepData ( metadataFile )
if err != nil {
return metadata , errors . Wrap ( err , "read failed" )
}
} else {
if configOptions . stepName != "" {
2021-08-12 14:54:46 +02:00
if GeneralConfig . MetaDataResolver == nil {
GeneralConfig . MetaDataResolver = GetAllStepMetadata
}
metadataMap := GeneralConfig . MetaDataResolver ( )
2021-06-16 08:43:30 +02:00
var ok bool
metadata , ok = metadataMap [ configOptions . stepName ]
if ! ok {
return metadata , errors . Errorf ( "could not retrieve by stepName %v" , configOptions . stepName )
}
} else {
return metadata , errors . Errorf ( "either one of stepMetadata or stepName parameter has to be passed" )
}
}
return metadata , nil
}