github.com/jaylevin/jenkins-library@v1.230.4/cmd/getConfig.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path" 8 "path/filepath" 9 10 "github.com/SAP/jenkins-library/pkg/config" 11 "github.com/SAP/jenkins-library/pkg/log" 12 "github.com/SAP/jenkins-library/pkg/piperutils" 13 "github.com/SAP/jenkins-library/pkg/reporting" 14 ws "github.com/SAP/jenkins-library/pkg/whitesource" 15 "github.com/pkg/errors" 16 "github.com/spf13/cobra" 17 ) 18 19 type configCommandOptions struct { 20 output string // output format, so far only JSON 21 outputFile string // if set: path to file where the output should be written to 22 parametersJSON string // parameters to be considered in JSON format 23 stageConfig bool 24 stageConfigAcceptedParameters []string 25 stepMetadata string // metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR' 26 stepName string 27 contextConfig bool 28 openFile func(s string, t map[string]string) (io.ReadCloser, error) 29 } 30 31 var configOptions configCommandOptions 32 33 type getConfigUtils interface { 34 FileExists(filename string) (bool, error) 35 DirExists(path string) (bool, error) 36 FileWrite(path string, content []byte, perm os.FileMode) error 37 } 38 39 type getConfigUtilsBundle struct { 40 *piperutils.Files 41 } 42 43 func newGetConfigUtilsUtils() getConfigUtils { 44 utils := getConfigUtilsBundle{ 45 Files: &piperutils.Files{}, 46 } 47 return &utils 48 } 49 50 // ConfigCommand is the entry command for loading the configuration of a pipeline step 51 func ConfigCommand() *cobra.Command { 52 53 configOptions.openFile = config.OpenPiperFile 54 var createConfigCmd = &cobra.Command{ 55 Use: "getConfig", 56 Short: "Loads the project 'Piper' configuration respecting defaults and parameters.", 57 PreRun: func(cmd *cobra.Command, args []string) { 58 path, _ := os.Getwd() 59 fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} 60 log.RegisterHook(fatalHook) 61 initStageName(false) 62 GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens) 63 }, 64 Run: func(cmd *cobra.Command, _ []string) { 65 utils := newGetConfigUtilsUtils() 66 err := generateConfig(utils) 67 if err != nil { 68 log.SetErrorCategory(log.ErrorConfiguration) 69 log.Entry().WithError(err).Fatal("failed to retrieve configuration") 70 } 71 }, 72 } 73 74 addConfigFlags(createConfigCmd) 75 return createConfigCmd 76 } 77 78 // GetDockerImageValue provides Piper commands additional access to configuration of step execution image if required 79 func GetDockerImageValue(stepName string) (string, error) { 80 configOptions.contextConfig = true 81 configOptions.stepName = stepName 82 stepConfig, err := getConfig() 83 if err != nil { 84 return "", err 85 } 86 87 var dockerImageValue string 88 dockerImageValue, ok := stepConfig.Config["dockerImage"].(string) 89 if !ok { 90 log.Entry().Infof("Config value of %v to compare with is not a string", stepConfig.Config["dockerImage"]) 91 } 92 93 return dockerImageValue, nil 94 } 95 96 func getBuildToolFromStageConfig(stepName string) (string, error) { 97 configOptions.contextConfig = true 98 configOptions.stepName = stepName 99 stageConfig, err := GetStageConfig() 100 if err != nil { 101 return "", err 102 } 103 104 buildTool, ok := stageConfig.Config["buildTool"].(string) 105 if !ok { 106 log.Entry().Infof("Config value of %v to compare with is not a string", stageConfig.Config["buildTool"]) 107 } 108 109 return buildTool, nil 110 } 111 112 // GetStageConfig provides Piper commands additional access to stage configuration if required. 113 // This allows steps to refer to configuration parameters which are not part of the step itself. 114 func GetStageConfig() (config.StepConfig, error) { 115 myConfig := config.Config{} 116 stepConfig := config.StepConfig{} 117 projectConfigFile := getProjectConfigFile(GeneralConfig.CustomConfig) 118 119 customConfig, err := configOptions.openFile(projectConfigFile, GeneralConfig.GitHubAccessTokens) 120 if err != nil { 121 if !errors.Is(err, os.ErrNotExist) { 122 return stepConfig, errors.Wrapf(err, "config: open configuration file '%v' failed", projectConfigFile) 123 } 124 customConfig = nil 125 } 126 127 defaultConfig := []io.ReadCloser{} 128 for _, f := range GeneralConfig.DefaultConfig { 129 fc, err := configOptions.openFile(f, GeneralConfig.GitHubAccessTokens) 130 // only create error for non-default values 131 if err != nil && f != ".pipeline/defaults.yaml" { 132 return stepConfig, errors.Wrapf(err, "config: getting defaults failed: '%v'", f) 133 } 134 if err == nil { 135 defaultConfig = append(defaultConfig, fc) 136 } 137 } 138 139 return myConfig.GetStageConfig(GeneralConfig.ParametersJSON, customConfig, defaultConfig, GeneralConfig.IgnoreCustomDefaults, configOptions.stageConfigAcceptedParameters, GeneralConfig.StageName) 140 } 141 142 func getConfig() (config.StepConfig, error) { 143 var myConfig config.Config 144 var stepConfig config.StepConfig 145 var err error 146 147 if configOptions.stageConfig { 148 stepConfig, err = GetStageConfig() 149 if err != nil { 150 return stepConfig, errors.Wrap(err, "getting stage config failed") 151 } 152 } else { 153 log.Entry().Infof("Printing stepName %s", configOptions.stepName) 154 if GeneralConfig.MetaDataResolver == nil { 155 GeneralConfig.MetaDataResolver = GetAllStepMetadata 156 } 157 metadata, err := config.ResolveMetadata(GeneralConfig.GitHubAccessTokens, GeneralConfig.MetaDataResolver, configOptions.stepMetadata, configOptions.stepName) 158 if err != nil { 159 return stepConfig, errors.Wrapf(err, "failed to resolve metadata") 160 } 161 162 // prepare output resource directories: 163 // this is needed in order to have proper directory permissions in case 164 // resources written inside a container image with a different user 165 // Remark: This is so far only relevant for Jenkins environments where getConfig is executed 166 167 prepareOutputEnvironment(metadata.Spec.Outputs.Resources, GeneralConfig.EnvRootPath) 168 169 envParams := metadata.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment") 170 reportingEnvParams := config.ReportingParameters.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment") 171 resourceParams := mergeResourceParameters(envParams, reportingEnvParams) 172 173 projectConfigFile := getProjectConfigFile(GeneralConfig.CustomConfig) 174 175 customConfig, err := configOptions.openFile(projectConfigFile, GeneralConfig.GitHubAccessTokens) 176 if err != nil { 177 if !errors.Is(err, os.ErrNotExist) { 178 return stepConfig, errors.Wrapf(err, "config: open configuration file '%v' failed", projectConfigFile) 179 } 180 customConfig = nil 181 } 182 183 defaultConfig, paramFilter, err := defaultsAndFilters(&metadata, metadata.Metadata.Name) 184 if err != nil { 185 return stepConfig, errors.Wrap(err, "defaults: retrieving step defaults failed") 186 } 187 188 for _, f := range GeneralConfig.DefaultConfig { 189 fc, err := configOptions.openFile(f, GeneralConfig.GitHubAccessTokens) 190 // only create error for non-default values 191 if err != nil && f != ".pipeline/defaults.yaml" { 192 return stepConfig, errors.Wrapf(err, "config: getting defaults failed: '%v'", f) 193 } 194 if err == nil { 195 defaultConfig = append(defaultConfig, fc) 196 } 197 } 198 199 var flags map[string]interface{} 200 201 if configOptions.contextConfig { 202 metadata.Spec.Inputs.Parameters = []config.StepParameters{} 203 } 204 205 stepConfig, err = myConfig.GetStepConfig(flags, GeneralConfig.ParametersJSON, customConfig, defaultConfig, GeneralConfig.IgnoreCustomDefaults, paramFilter, metadata, resourceParams, GeneralConfig.StageName, metadata.Metadata.Name) 206 if err != nil { 207 return stepConfig, errors.Wrap(err, "getting step config failed") 208 } 209 210 // apply context conditions if context configuration is requested 211 if configOptions.contextConfig { 212 applyContextConditions(metadata, &stepConfig) 213 } 214 } 215 return stepConfig, nil 216 } 217 218 func generateConfig(utils getConfigUtils) error { 219 220 stepConfig, err := getConfig() 221 if err != nil { 222 return err 223 } 224 225 myConfigJSON, _ := config.GetJSON(stepConfig.Config) 226 227 if len(configOptions.outputFile) > 0 { 228 err := utils.FileWrite(configOptions.outputFile, []byte(myConfigJSON), 0666) 229 if err != nil { 230 return fmt.Errorf("failed to write output file %v: %w", configOptions.outputFile, err) 231 } 232 return nil 233 } 234 fmt.Println(myConfigJSON) 235 236 return nil 237 } 238 239 func addConfigFlags(cmd *cobra.Command) { 240 241 // ToDo: support more output options, like https://kubernetes.io/docs/reference/kubectl/overview/#formatting-output 242 cmd.Flags().StringVar(&configOptions.output, "output", "json", "Defines the output format") 243 cmd.Flags().StringVar(&configOptions.outputFile, "outputFile", "", "Defines a file path. f set, the output will be written to the defines file") 244 245 cmd.Flags().StringVar(&configOptions.parametersJSON, "parametersJSON", os.Getenv("PIPER_parametersJSON"), "Parameters to be considered in JSON format") 246 cmd.Flags().BoolVar(&configOptions.stageConfig, "stageConfig", false, "Defines if step stage configuration should be loaded and no step-specific config") 247 cmd.Flags().StringArrayVar(&configOptions.stageConfigAcceptedParameters, "stageConfigAcceptedParams", []string{}, "Defines the parameters used for filtering stage/general configuration when accessing stage config") 248 cmd.Flags().StringVar(&configOptions.stepMetadata, "stepMetadata", "", "Step metadata, passed as path to yaml") 249 cmd.Flags().StringVar(&configOptions.stepName, "stepName", "", "Step name, used to get step metadata if yaml path is not set") 250 cmd.Flags().BoolVar(&configOptions.contextConfig, "contextConfig", false, "Defines if step context configuration should be loaded instead of step config") 251 252 } 253 254 func defaultsAndFilters(metadata *config.StepData, stepName string) ([]io.ReadCloser, config.StepFilters, error) { 255 if configOptions.contextConfig { 256 defaults, err := metadata.GetContextDefaults(stepName) 257 if err != nil { 258 return nil, config.StepFilters{}, errors.Wrap(err, "metadata: getting context defaults failed") 259 } 260 return []io.ReadCloser{defaults}, metadata.GetContextParameterFilters(), nil 261 } 262 // ToDo: retrieve default values from metadata 263 return []io.ReadCloser{}, metadata.GetParameterFilters(), nil 264 } 265 266 func applyContextConditions(metadata config.StepData, stepConfig *config.StepConfig) { 267 // consider conditions for context configuration 268 269 // containers 270 config.ApplyContainerConditions(metadata.Spec.Containers, stepConfig) 271 272 // sidecars 273 config.ApplyContainerConditions(metadata.Spec.Sidecars, stepConfig) 274 275 // ToDo: remove all unnecessary sub maps? 276 // e.g. extract delete() from applyContainerConditions - loop over all stepConfig.Config[param.Value] and remove ... 277 } 278 279 func prepareOutputEnvironment(outputResources []config.StepResources, envRootPath string) { 280 for _, oResource := range outputResources { 281 for _, oParam := range oResource.Parameters { 282 paramPath := path.Join(envRootPath, oResource.Name, fmt.Sprint(oParam["name"])) 283 if oParam["fields"] != nil { 284 paramFields, ok := oParam["fields"].([]map[string]string) 285 if ok && len(paramFields) > 0 { 286 paramPath = path.Join(paramPath, paramFields[0]["name"]) 287 } 288 } 289 if _, err := os.Stat(filepath.Dir(paramPath)); errors.Is(err, os.ErrNotExist) { 290 log.Entry().Debugf("Creating directory: %v", filepath.Dir(paramPath)) 291 os.MkdirAll(filepath.Dir(paramPath), 0777) 292 } 293 } 294 } 295 296 // prepare additional output directories known to possibly create permission issues when created from within a container 297 // ToDo: evaluate if we can rather call this only in the correct step context (we know the step when calling getConfig!) 298 // Could this be part of the container definition in the step.yaml? 299 stepOutputDirectories := []string{ 300 reporting.StepReportDirectory, // standard directory to collect md reports for pipelineCreateScanSummary 301 ws.ReportsDirectory, // standard directory for reports created by whitesourceExecuteScan 302 } 303 304 for _, dir := range stepOutputDirectories { 305 if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { 306 log.Entry().Debugf("Creating directory: %v", dir) 307 os.MkdirAll(dir, 0777) 308 } 309 } 310 }