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"
2023-10-04 15:44:48 +05:00
"strings"
2020-05-20 10:50:35 +02:00
2026-02-16 13:56:00 +02:00
"errors"
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-10-21 14:00:51 +02:00
"github.com/SAP/jenkins-library/pkg/piperutils"
2021-02-10 16:18:00 +01: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/spf13/cobra"
)
2023-10-04 15:44:48 +05:00
type ConfigCommandOptions struct {
Output string // output format, so far only JSON, YAML
OutputFile string // if set: path to file where the output should be written to
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 )
2026-01-26 10:25:33 +01:00
SetVaultCredentials bool
2019-10-24 10:59:58 +02:00
}
2023-10-04 15:44:48 +05:00
var configOptions ConfigCommandOptions
func SetConfigOptions ( c ConfigCommandOptions ) {
configOptions . ContextConfig = c . ContextConfig
configOptions . OpenFile = c . OpenFile
configOptions . Output = c . Output
configOptions . OutputFile = c . OutputFile
configOptions . ParametersJSON = c . ParametersJSON
configOptions . StageConfig = c . StageConfig
configOptions . StageConfigAcceptedParameters = c . StageConfigAcceptedParameters
configOptions . StepMetadata = c . StepMetadata
configOptions . StepName = c . StepName
2026-01-26 10:25:33 +01:00
configOptions . SetVaultCredentials = c . SetVaultCredentials
2023-10-04 15:44:48 +05:00
}
2019-10-24 10:59:58 +02:00
2021-10-21 14:00:51 +02:00
type getConfigUtils interface {
FileExists ( filename string ) ( bool , error )
DirExists ( path string ) ( bool , error )
FileWrite ( path string , content [] byte , perm os . FileMode ) error
}
type getConfigUtilsBundle struct {
* piperutils . Files
}
func newGetConfigUtilsUtils () getConfigUtils {
2023-10-04 15:44:48 +05:00
return & getConfigUtilsBundle {
2021-10-21 14:00:51 +02:00
Files : & piperutils . Files {},
}
}
2019-10-24 10:59:58 +02:00
// ConfigCommand is the entry command for loading the configuration of a pipeline step
func ConfigCommand () * cobra . Command {
2023-10-04 15:44:48 +05:00
SetConfigOptions ( ConfigCommandOptions {
OpenFile : config . OpenPiperFile ,
})
2019-10-24 10:59:58 +02:00
2024-10-04 09:33:53 +02:00
createConfigCmd := & cobra . Command {
2019-10-24 10:59:58 +02:00
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 ) {
2024-10-04 09:33:53 +02:00
if err := generateConfigWrapper ( cmd ); 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
}
2022-03-14 12:33:52 +01:00
// GetDockerImageValue provides Piper commands additional access to configuration of step execution image if required
func GetDockerImageValue ( stepName string ) ( string , error ) {
2023-10-04 15:44:48 +05:00
configOptions . ContextConfig = true
configOptions . StepName = stepName
2021-12-01 07:46:18 +01:00
stepConfig , err := getConfig ()
if err != nil {
return "" , err
}
var dockerImageValue string
dockerImageValue , ok := stepConfig . Config [ "dockerImage" ].( string )
if ! ok {
log . Entry (). Infof ( "Config value of %v to compare with is not a string" , stepConfig . Config [ "dockerImage" ])
}
return dockerImageValue , nil
}
2022-02-14 15:16:45 +01:00
func getBuildToolFromStageConfig ( stepName string ) ( string , error ) {
2023-10-04 15:44:48 +05:00
configOptions . ContextConfig = true
configOptions . StepName = stepName
2022-03-14 12:33:52 +01:00
stageConfig , err := GetStageConfig ()
2022-02-14 15:16:45 +01:00
if err != nil {
return "" , err
}
buildTool , ok := stageConfig . Config [ "buildTool" ].( string )
if ! ok {
log . Entry (). Infof ( "Config value of %v to compare with is not a string" , stageConfig . Config [ "buildTool" ])
}
return buildTool , nil
}
2022-03-14 12:33:52 +01:00
// GetStageConfig provides Piper commands additional access to stage configuration if required.
// This allows steps to refer to configuration parameters which are not part of the step itself.
func GetStageConfig () ( config . StepConfig , error ) {
2022-02-14 15:16:45 +01:00
myConfig := config . Config {}
stepConfig := config . StepConfig {}
projectConfigFile := getProjectConfigFile ( GeneralConfig . CustomConfig )
2023-10-04 15:44:48 +05:00
customConfig , err := configOptions . OpenFile ( projectConfigFile , GeneralConfig . GitHubAccessTokens )
2022-02-14 15:16:45 +01:00
if err != nil {
2022-02-16 13:28:51 +01:00
if ! errors . Is ( err , os . ErrNotExist ) {
2026-02-16 13:56:00 +02:00
return stepConfig , fmt . Errorf ( "config: open configuration file '%v' failed: %w" , projectConfigFile , err )
2022-02-14 15:16:45 +01:00
}
customConfig = nil
}
defaultConfig := [] io . ReadCloser {}
for _ , f := range GeneralConfig . DefaultConfig {
2025-01-03 12:42:59 +02:00
if configOptions . OpenFile == nil {
return stepConfig , errors . New ( "config: open file function not set" )
}
2023-10-04 15:44:48 +05:00
fc , err := configOptions . OpenFile ( f , GeneralConfig . GitHubAccessTokens )
2022-02-14 15:16:45 +01:00
// only create error for non-default values
if err != nil && f != ".pipeline/defaults.yaml" {
2026-02-16 13:56:00 +02:00
return stepConfig , fmt . Errorf ( "config: getting defaults failed: '%v': %w" , f , err )
2022-02-14 15:16:45 +01:00
}
if err == nil {
defaultConfig = append ( defaultConfig , fc )
}
}
2023-10-04 15:44:48 +05:00
return myConfig . GetStageConfig ( GeneralConfig . ParametersJSON , customConfig , defaultConfig , GeneralConfig . IgnoreCustomDefaults , configOptions . StageConfigAcceptedParameters , GeneralConfig . StageName )
2022-02-14 15:16:45 +01:00
}
2021-12-01 07:46:18 +01:00
func getConfig () ( config . StepConfig , error ) {
2024-10-04 09:33:53 +02:00
return getConfigWithFlagValues ( nil )
}
func getConfigWithFlagValues ( cmd * cobra . Command ) ( config . StepConfig , error ) {
2019-10-24 10:59:58 +02:00
var myConfig config . Config
var stepConfig config . StepConfig
2022-02-14 15:16:45 +01:00
var err error
2019-10-24 10:59:58 +02:00
2023-10-04 15:44:48 +05:00
if configOptions . StageConfig {
2022-03-14 12:33:52 +01:00
stepConfig , err = GetStageConfig ()
2021-08-04 10:09:02 +02:00
if err != nil {
2026-02-16 13:56:00 +02:00
return stepConfig , fmt . Errorf ( "getting stage config failed: %w" , err )
2020-03-31 08:47:09 +02:00
}
2025-04-04 19:04:21 +03:00
// add hooks (defaults + custom defaults) to stage-config.json output
stepConfig . Config [ "hooks" ] = stepConfig . HookConfig
2021-08-04 10:09:02 +02:00
} else {
2023-10-04 15:44:48 +05:00
log . Entry (). Infof ( "Printing stepName %s" , configOptions . StepName )
2021-12-07 15:54:59 +01:00
if GeneralConfig . MetaDataResolver == nil {
GeneralConfig . MetaDataResolver = GetAllStepMetadata
}
2023-10-04 15:44:48 +05:00
metadata , err := config . ResolveMetadata ( GeneralConfig . GitHubAccessTokens , GeneralConfig . MetaDataResolver , configOptions . StepMetadata , configOptions . StepName )
2021-08-04 10:09:02 +02:00
if err != nil {
2026-02-16 13:56:00 +02:00
return stepConfig , fmt . Errorf ( "failed to resolve metadata: %w" , err )
2021-08-04 10:09:02 +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
2019-10-29 10:58:24 +01:00
2021-08-04 10:09:02 +02:00
prepareOutputEnvironment ( metadata . Spec . Outputs . Resources , GeneralConfig . EnvRootPath )
2019-10-24 10:59:58 +02:00
2021-12-15 17:07:47 +03:00
envParams := metadata . GetResourceParameters ( GeneralConfig . EnvRootPath , "commonPipelineEnvironment" )
reportingEnvParams := config . ReportingParameters . GetResourceParameters ( GeneralConfig . EnvRootPath , "commonPipelineEnvironment" )
resourceParams := mergeResourceParameters ( envParams , reportingEnvParams )
2021-08-04 10:09:02 +02:00
projectConfigFile := getProjectConfigFile ( GeneralConfig . CustomConfig )
2023-10-04 15:44:48 +05:00
customConfig , err := configOptions . OpenFile ( projectConfigFile , GeneralConfig . GitHubAccessTokens )
2021-08-04 10:09:02 +02:00
if err != nil {
2022-02-16 13:28:51 +01:00
if ! errors . Is ( err , os . ErrNotExist ) {
2026-02-16 13:56:00 +02:00
return stepConfig , fmt . Errorf ( "config: open configuration file '%v' failed: %w" , projectConfigFile , err )
2021-08-04 10:09:02 +02:00
}
customConfig = nil
}
defaultConfig , paramFilter , err := defaultsAndFilters ( & metadata , metadata . Metadata . Name )
if err != nil {
2026-02-16 13:56:00 +02:00
return stepConfig , fmt . Errorf ( "defaults: retrieving step defaults failed: %w" , err )
2021-08-04 10:09:02 +02:00
}
for _ , f := range GeneralConfig . DefaultConfig {
2023-10-04 15:44:48 +05:00
fc , err := configOptions . OpenFile ( f , GeneralConfig . GitHubAccessTokens )
2021-08-04 10:09:02 +02:00
// only create error for non-default values
if err != nil && f != ".pipeline/defaults.yaml" {
2026-02-16 13:56:00 +02:00
return stepConfig , fmt . Errorf ( "config: getting defaults failed: '%v': %w" , f , err )
2021-08-04 10:09:02 +02:00
}
if err == nil {
defaultConfig = append ( defaultConfig , fc )
}
}
2023-10-04 15:44:48 +05:00
if configOptions . ContextConfig {
2021-12-15 17:07:47 +03:00
metadata . Spec . Inputs . Parameters = [] config . StepParameters {}
2021-08-04 10:09:02 +02:00
}
2024-10-04 09:33:53 +02:00
var flagValues map [ string ] interface {}
if cmd != nil {
flagValues = config . AvailableFlagValues ( cmd , & paramFilter )
}
2026-01-26 10:25:33 +01:00
if configOptions . SetVaultCredentials {
// add vault credentials so that configuration can be fetched from vault
if GeneralConfig . VaultRoleID == "" {
GeneralConfig . VaultRoleID = os . Getenv ( "PIPER_vaultAppRoleID" )
}
if GeneralConfig . VaultRoleSecretID == "" {
GeneralConfig . VaultRoleSecretID = os . Getenv ( "PIPER_vaultAppRoleSecretID" )
}
if GeneralConfig . VaultToken == "" {
GeneralConfig . VaultToken = os . Getenv ( "PIPER_vaultToken" )
}
myConfig . SetVaultCredentials ( GeneralConfig . VaultRoleID , GeneralConfig . VaultRoleSecretID , GeneralConfig . VaultToken )
}
2024-10-04 09:33:53 +02:00
stepConfig , err = myConfig . GetStepConfig ( flagValues , GeneralConfig . ParametersJSON , customConfig , defaultConfig , GeneralConfig . IgnoreCustomDefaults , paramFilter , metadata , resourceParams , GeneralConfig . StageName , metadata . Metadata . Name )
2021-08-04 10:09:02 +02:00
if err != nil {
2026-02-16 13:56:00 +02:00
return stepConfig , fmt . Errorf ( "getting step config failed: %w" , err )
2021-08-04 10:09:02 +02:00
}
// apply context conditions if context configuration is requested
2023-10-04 15:44:48 +05:00
if configOptions . ContextConfig {
2021-08-04 10:09:02 +02:00
applyContextConditions ( metadata , & stepConfig )
}
2020-01-24 14:30:27 +01:00
}
2021-12-01 07:46:18 +01:00
return stepConfig , nil
}
2024-10-04 09:33:53 +02:00
func generateConfigWrapper ( cmd * cobra . Command ) error {
2023-10-04 15:44:48 +05:00
var formatter func ( interface {}) ( string , error )
switch strings . ToLower ( configOptions . Output ) {
case "yaml" , "yml" :
formatter = config . GetYAML
case "json" :
formatter = config . GetJSON
default :
formatter = config . GetJSON
}
2024-10-04 09:33:53 +02:00
return GenerateConfig ( cmd , formatter )
2023-10-04 15:44:48 +05:00
}
2024-10-04 09:33:53 +02:00
func GenerateConfig ( cmd * cobra . Command , formatter func ( interface {}) ( string , error )) error {
2023-10-04 15:44:48 +05:00
utils := newGetConfigUtilsUtils ()
2021-12-01 07:46:18 +01:00
2024-10-04 09:33:53 +02:00
stepConfig , err := getConfigWithFlagValues ( cmd )
2021-12-01 07:46:18 +01:00
if err != nil {
return err
}
2020-01-24 14:30:27 +01:00
2023-10-04 15:44:48 +05:00
myConfig , err := formatter ( stepConfig . Config )
2022-09-01 12:33:28 +03:00
if err != nil {
2023-10-04 15:44:48 +05:00
return fmt . Errorf ( "failed to marshal config: %w" , err )
2022-09-01 12:33:28 +03:00
}
2019-10-24 10:59:58 +02:00
2023-10-04 15:44:48 +05:00
if len ( configOptions . OutputFile ) > 0 {
2024-10-04 09:33:53 +02:00
if err := utils . FileWrite ( configOptions . OutputFile , [] byte ( myConfig ), 0 o666 ); err != nil {
2023-10-04 15:44:48 +05:00
return fmt . Errorf ( "failed to write output file %v: %w" , configOptions . OutputFile , err )
2021-10-21 14:00:51 +02:00
}
return nil
}
2023-10-04 15:44:48 +05:00
fmt . Println ( myConfig )
2019-10-24 10:59:58 +02:00
return nil
}
func addConfigFlags ( cmd * cobra . Command ) {
2022-02-16 13:28:51 +01:00
// ToDo: support more output options, like https://kubernetes.io/docs/reference/kubectl/overview/#formatting-output
2023-10-04 15:44:48 +05:00
cmd . Flags (). StringVar ( & configOptions . Output , "output" , "json" , "Defines the output format" )
cmd . Flags (). StringVar ( & configOptions . OutputFile , "outputFile" , "" , "Defines a file path. f set, the output will be written to the defines file" )
2019-10-24 10:59:58 +02:00
2023-10-04 15:44:48 +05:00
cmd . Flags (). StringVar ( & configOptions . ParametersJSON , "parametersJSON" , os . Getenv ( "PIPER_parametersJSON" ), "Parameters to be considered in JSON format" )
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" )
cmd . Flags (). StringVar ( & configOptions . StepMetadata , "stepMetadata" , "" , "Step metadata, passed as path to yaml" )
cmd . Flags (). StringVar ( & configOptions . StepName , "stepName" , "" , "Step name, used to get step metadata if yaml path is not set" )
cmd . Flags (). BoolVar ( & configOptions . ContextConfig , "contextConfig" , false , "Defines if step context configuration should be loaded instead of step config" )
2026-01-26 10:25:33 +01:00
cmd . Flags (). BoolVar ( & configOptions . SetVaultCredentials , "setVaultCredentials" , false , "Defines whether to set Vault credentials to enable fetching credentials from Vault or not" )
2019-10-24 10:59:58 +02:00
}
2019-11-22 10:14:21 +01:00
func defaultsAndFilters ( metadata * config . StepData , stepName string ) ([] io . ReadCloser , config . StepFilters , error ) {
2023-10-04 15:44:48 +05:00
if configOptions . ContextConfig {
2019-11-22 10:14:21 +01:00
defaults , err := metadata . GetContextDefaults ( stepName )
2019-10-24 10:59:58 +02:00
if err != nil {
2026-02-16 13:56:00 +02:00
return nil , config . StepFilters {}, fmt . Errorf ( "metadata: getting context defaults failed: %w" , err )
2019-10-24 10:59:58 +02:00
}
return [] io . ReadCloser { defaults }, metadata . GetContextParameterFilters (), nil
}
2022-02-16 13:28:51 +01:00
// 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 14:30:27 +01:00
func applyContextConditions ( metadata config . StepData , stepConfig * config . StepConfig ) {
2022-02-16 13:28:51 +01:00
// consider conditions for context configuration
2020-01-24 14:30:27 +01:00
2022-02-16 13:28:51 +01:00
// containers
2023-12-18 16:01:33 +01:00
config . ApplyContainerConditions ( metadata . Spec . Containers , stepConfig )
2020-01-24 14:30:27 +01:00
2022-02-16 13:28:51 +01:00
// sidecars
2023-12-18 16:01:33 +01:00
config . ApplyContainerConditions ( metadata . Spec . Sidecars , stepConfig )
2020-01-24 14:30:27 +01:00
2022-02-16 13:28:51 +01:00
// ToDo: remove all unnecessary sub maps?
2020-01-24 14:30:27 +01:00
// 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" ])
}
}
2022-02-16 13:28:51 +01:00
if _ , err := os . Stat ( filepath . Dir ( paramPath )); errors . Is ( err , os . ErrNotExist ) {
2020-10-14 11:13:08 +02:00
log . Entry (). Debugf ( "Creating directory: %v" , filepath . Dir ( paramPath ))
2024-10-04 09:33:53 +02:00
_ = os . MkdirAll ( filepath . Dir ( paramPath ), 0 o777 )
2020-10-14 11:13:08 +02:00
}
}
}
2021-02-10 16:18:00 +01: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 11:10:08 +01:00
reporting . StepReportDirectory , // standard directory to collect md reports for pipelineCreateScanSummary
ws . ReportsDirectory , // standard directory for reports created by whitesourceExecuteScan
2021-02-10 16:18:00 +01:00
}
for _ , dir := range stepOutputDirectories {
2022-02-16 13:28:51 +01:00
if _ , err := os . Stat ( dir ); errors . Is ( err , os . ErrNotExist ) {
2021-02-10 16:18:00 +01:00
log . Entry (). Debugf ( "Creating directory: %v" , dir )
2024-10-04 09:33:53 +02:00
_ = os . MkdirAll ( dir , 0 o777 )
2021-02-10 16:18:00 +01:00
}
}
2020-10-14 11:13:08 +02:00
}