diff --git a/cmd/exportStepConfig.go b/cmd/exportStepConfig.go new file mode 100644 index 0000000000..19d90e673d --- /dev/null +++ b/cmd/exportStepConfig.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "os" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/ghodss/yaml" + "github.com/spf13/cobra" +) + +var exportStepConfigOptions struct { + stepName string + stepMetadata string + outputFile string +} + +// ExportStepConfigCommand is the entry command for exporting the step configuration +func ExportStepConfigCommand() *cobra.Command { + var exportStepConfigCmd = &cobra.Command{ + Use: "exportStepConfig", + Short: "For internal use by Piper team only. Do NOT use this command in production pipelines.", + PreRun: func(cmd *cobra.Command, _ []string) { + path, _ := os.Getwd() + fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} + log.RegisterHook(fatalHook) + initStageName(false) + log.SetVerbose(GeneralConfig.Verbose) + GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens) + }, + Run: func(cmd *cobra.Command, _ []string) { + metadata, err := config.ResolveMetadata(GeneralConfig.GitHubAccessTokens, GeneralConfig.MetaDataResolver, exportStepConfigOptions.stepMetadata, "") + if err != nil { + log.Entry().WithError(err).Fatal("Failed to resolve metadata") + return + } + + var result map[string]interface{} + err = PrepareConfig(cmd, &metadata, exportStepConfigOptions.stepName, nil, config.OpenPiperFile, &result) + if err != nil { + log.Entry().WithError(err).Fatal("Failed to prepare configuration") + return + } + + yamlData, err := yaml.Marshal(result) + if err != nil { + log.Entry().WithError(err).Fatal("Failed to marshal result to YAML") + return + } + err = os.WriteFile(exportStepConfigOptions.outputFile, yamlData, 0644) + if err != nil { + log.Entry().WithError(err).Fatal("Failed to write YAML to file") + return + } + log.Entry().Infof("Configuration exported to %s\n", exportStepConfigOptions.outputFile) + }, + } + addExportStepConfigFlags(exportStepConfigCmd) + return exportStepConfigCmd +} + +func addExportStepConfigFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&exportStepConfigOptions.stepName, "stepName", "", "Name of the step being checked") + cmd.Flags().StringVar(&exportStepConfigOptions.stepMetadata, "metadataFile", "", "Step metadata, passed as path to yaml") + cmd.Flags().StringVar(&exportStepConfigOptions.outputFile, "outputFilePath", "", "Defines a file path. If set, the output will be written to the defined file") + _ = cmd.MarkFlagRequired("stepName") + _ = cmd.MarkFlagRequired("metadataFile") + _ = cmd.MarkFlagRequired("outputFilePath") +} diff --git a/cmd/piper.go b/cmd/piper.go index 885e916737..fe1bbe60d9 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -231,6 +231,10 @@ func Execute() { rootCmd.AddCommand(AbapLandscapePortalUpdateAddOnProductCommand()) rootCmd.AddCommand(ImagePushToRegistryCommand()) + // this command is only for coexistence and compatibility while migrating steps to the new architecture + // Should never be used in productive pipelines! + rootCmd.AddCommand(ExportStepConfigCommand()) + addRootFlags(rootCmd) if err := rootCmd.Execute(); err != nil { @@ -347,7 +351,7 @@ func initStageName(outputToLog bool) { } // 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, t map[string]string) (io.ReadCloser, error)) error { +func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName string, options interface{}, openFile func(s string, t map[string]string) (io.ReadCloser, error), outputResult ...interface{}) error { log.SetFormatter(GeneralConfig.LogFormat) initStageName(true) @@ -434,10 +438,24 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin } } - stepConfig.Config = checkTypes(stepConfig.Config, options) - confJSON, _ := json.Marshal(stepConfig.Config) - _ = json.Unmarshal(confJSON, &options) + if len(outputResult) == 1 { + // This branch is only used by the exportStepConfig command. + // No other step should use this! + // This is required temporarily for compatibility during migration to the new architecture. + + // The main reason for this separate branch is that checkTypes() below expects + // a struct in &options and will panic otherwise. We cannot pass a struct here + // because the required fields are not known at compile time, since exportStepConfig + // can be called for any step. + // And yeah, this workaround is ugly, I know. + reflect.ValueOf(outputResult[0]).Elem().Set(reflect.ValueOf(stepConfig.Config)) // copy stepConfig.Config into outputResult[0] + } else { + // For all other steps, check and convert types, then unmarshal config into options (as before) + stepConfig.Config = checkTypes(stepConfig.Config, options) + confJSON, _ := json.Marshal(stepConfig.Config) + _ = json.Unmarshal(confJSON, &options) + } config.MarkFlagsWithValue(cmd, stepConfig) retrieveHookConfig(stepConfig.HookConfig, &GeneralConfig.HookConfig)