mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
4b257377ec
* Initially generated tmsUpload<...> files * First provisioning of parameters supported by tmsUpload step * Refer to Go step from tmsUpload.groovy * Initial client implementation * Reverting line delimiters in tmsUpoad.groovy back to Unix ones * Temporarily remove when-condition for Release stage * Define useGoStep parameter in tmsUpload.groovy * Unstash buildResult if useGoStep is true * No unstashing and empty credentials, when using go step * Register TmsUploadCommand in piper.go * Cleanup groovy-related changes - they will be temporarily implemented in a different repo * Make getting OAuth token success * Look through the code and cleanup it a bit * Read service key from Jenkins credentials store * Provide initial set of unit tests for methods in /pkg/tms/tms.go file * Minor improvements on logging response on http call error * Check, if positive HTTP status code is as expected * Cleanup tms.yaml file, provide additional unit test for tms.go * Provide unit test for the case, when request body contains spaces * Specify nodeExtDescriptorMapping parameter as of type map in tms.yaml * Implement client method for getting nodes * Write tests for GetNodes method * Add GetMtaExtDescriptor client method and cover it with unit tests * Provide first implementation for Update- and UploadMtaExtDescriptor client methods * Provide first implementation for Update- and UploadMtaExtDescriptor client methods * Provide UploadFile and UploadFileToNode client methods * Provide tests for Update- and UploadMtaExtDescriptor client methods * Write tests for FileUpload and FileUploadToNode client methods * Minor corrections * Remove some TODO comments * Rename some of response structures * Revert change for line delimiters in cmd/piper.go * Add uploadType string parameter to UploadFile and UploadRequest methods of uploader mock to reflect the changed Uploader implementation * Start to implement execution logic in tmsUpload.go * Changes in tms.yaml file - remove resources from inputs in tms.yaml and implement mtaPath parameter settings in the yaml file the same way, as it is done in cloudFoundryDeploy.yaml - rename tms.yaml to tmsUpload.yaml, since some generation policy changed meanwhile * Rename tms.yaml to tmsUpload.yaml and do go generate * Use provided proxy on communication with UAA and TMS * Set proxy even before getting OAuth token * Further implementation of tmsUpload.go * Continuation on implementing the tmsUpload.go executor * Get mtarFilePath and git commitId from commonPipelineEnvironment, if they are missing in configuration file + minor changes * Implement a happy path test for tmsUpload logic * Cover with unit tests main happy and error paths of tmsUpload.go logic * Extend set of unit tests for tmsUpload.go * Eliminate some TODOs, extend unit tests for tmsUpload.go * Delete some TODOs * Remove a couple of more TODOs from tms_test.go file * Provide additional unit test for error due unexpected positive http status code on upload * Revert back line delimiters in cmd/piper.go * Comment out file uploading calls in tmsUpload.go * Run go generate to update generated files * Convert line delimiters in tmsUpload.yaml to Unix ones, as well as provide new line character in the end of the file, plus minor fix for logging in tmsUpload.go file (pipeline complained) * Correct description of a parameter in tmsUpload.yaml, extend unit tests to check for trimming a slash in the end of TMS url for client methods that do upload * [minor] Add a comment in the test code * Add stashContent parameter to do unstashing in tmsUpload.groovy, remove some of the clarified TODOs * Uncomment uploading file calls in tmsUpload.go, declare buildResult stash in tmsUpload.yaml * Remove clarified TODOs from the tmsUpload.go file * Run go fmt for jenkins-library/pkg/tms * Do not get explicitly values from common pipeline environment - all configurations are provided in yaml file * Remove unused struct from tmsUpload_test.go * Run go fmt jenkins-library\pkg\tms * Revise descriptions of parameters provided in tmsUpload.yaml file * Specify STAGES scope for tmsUpload parameters * Provide STAGES scope for the tmsUpload parameters, provide default value for stashContent parameter * Remove trailing space from tmsUpload.yaml * Provide unit tests for proxy-related changes in http.go file * Improve proxy implementation in tmsUpload.go file * Make tmsServiceKey again a mandatory parameter * Run go generate command to make the generated files correspond the yaml state * Change line delimiters back to Unix ones (were switched while resolving the conflicts) * Remove trailing spaces from tmsUpload.yaml * Minor change in a comment to trigger pipelines with commit * Improve checks for zero-structs and for empty maps, as well as use different package to read files in the tests * Revert line endings in http.go * Revert comments formatting changes in files that do not belong to the tmsUpload step
612 lines
25 KiB
Go
612 lines
25 KiB
Go
package cmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/SAP/jenkins-library/pkg/config"
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
|
"github.com/SAP/jenkins-library/pkg/orchestrator"
|
|
"github.com/SAP/jenkins-library/pkg/piperutils"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// GeneralConfigOptions contains all global configuration options for piper binary
|
|
type GeneralConfigOptions struct {
|
|
GitHubAccessTokens map[string]string // map of tokens with url as key in order to maintain url-specific tokens
|
|
CorrelationID string
|
|
CustomConfig string
|
|
GitHubTokens []string // list of entries in form of <server>:<token> to allow token authentication for downloading config / defaults
|
|
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
|
|
VaultRoleID string
|
|
VaultRoleSecretID string
|
|
VaultToken string
|
|
VaultServerURL string
|
|
VaultNamespace string
|
|
VaultPath string
|
|
HookConfig HookConfiguration
|
|
MetaDataResolver func() map[string]config.StepData
|
|
GCPJsonKeyFilePath string
|
|
GCSFolderPath string
|
|
GCSBucketId string
|
|
GCSSubFolder string
|
|
}
|
|
|
|
// HookConfiguration contains the configuration for supported hooks, so far Sentry and Splunk are supported.
|
|
type HookConfiguration struct {
|
|
SentryConfig SentryConfiguration `json:"sentry,omitempty"`
|
|
SplunkConfig SplunkConfiguration `json:"splunk,omitempty"`
|
|
}
|
|
|
|
// SentryConfiguration defines the configuration options for the Sentry logging system
|
|
type SentryConfiguration struct {
|
|
Dsn string `json:"dsn,omitempty"`
|
|
}
|
|
|
|
// SplunkConfiguration defines the configuration options for the Splunk logging system
|
|
type SplunkConfiguration struct {
|
|
Dsn string `json:"dsn,omitempty"`
|
|
Token string `json:"token,omitempty"`
|
|
Index string `json:"index,omitempty"`
|
|
SendLogs bool `json:"sendLogs"`
|
|
}
|
|
|
|
var rootCmd = &cobra.Command{
|
|
Use: "piper",
|
|
Short: "Executes CI/CD steps from project 'Piper' ",
|
|
Long: `
|
|
This project 'Piper' binary provides a CI/CD step library.
|
|
It contains many steps which can be used within CI/CD systems as well as directly on e.g. a developer's machine.
|
|
`,
|
|
}
|
|
|
|
// GeneralConfig contains global configuration flags for piper binary
|
|
var GeneralConfig GeneralConfigOptions
|
|
|
|
// Execute is the starting point of the piper command line tool
|
|
func Execute() {
|
|
|
|
rootCmd.AddCommand(ArtifactPrepareVersionCommand())
|
|
rootCmd.AddCommand(ConfigCommand())
|
|
rootCmd.AddCommand(DefaultsCommand())
|
|
rootCmd.AddCommand(ContainerSaveImageCommand())
|
|
rootCmd.AddCommand(CommandLineCompletionCommand())
|
|
rootCmd.AddCommand(VersionCommand())
|
|
rootCmd.AddCommand(DetectExecuteScanCommand())
|
|
rootCmd.AddCommand(HadolintExecuteCommand())
|
|
rootCmd.AddCommand(KarmaExecuteTestsCommand())
|
|
rootCmd.AddCommand(UiVeri5ExecuteTestsCommand())
|
|
rootCmd.AddCommand(SonarExecuteScanCommand())
|
|
rootCmd.AddCommand(KubernetesDeployCommand())
|
|
rootCmd.AddCommand(HelmExecuteCommand())
|
|
rootCmd.AddCommand(XsDeployCommand())
|
|
rootCmd.AddCommand(GithubCheckBranchProtectionCommand())
|
|
rootCmd.AddCommand(GithubCommentIssueCommand())
|
|
rootCmd.AddCommand(GithubCreateIssueCommand())
|
|
rootCmd.AddCommand(GithubCreatePullRequestCommand())
|
|
rootCmd.AddCommand(GithubPublishReleaseCommand())
|
|
rootCmd.AddCommand(GithubSetCommitStatusCommand())
|
|
rootCmd.AddCommand(GitopsUpdateDeploymentCommand())
|
|
rootCmd.AddCommand(CloudFoundryDeleteServiceCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentPullGitRepoCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentCloneGitRepoCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentCheckoutBranchCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentCreateTagCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentCreateSystemCommand())
|
|
rootCmd.AddCommand(CheckmarxExecuteScanCommand())
|
|
rootCmd.AddCommand(FortifyExecuteScanCommand())
|
|
rootCmd.AddCommand(CodeqlExecuteScanCommand())
|
|
rootCmd.AddCommand(MtaBuildCommand())
|
|
rootCmd.AddCommand(ProtecodeExecuteScanCommand())
|
|
rootCmd.AddCommand(MavenExecuteCommand())
|
|
rootCmd.AddCommand(CloudFoundryCreateServiceKeyCommand())
|
|
rootCmd.AddCommand(MavenBuildCommand())
|
|
rootCmd.AddCommand(MavenExecuteIntegrationCommand())
|
|
rootCmd.AddCommand(MavenExecuteStaticCodeChecksCommand())
|
|
rootCmd.AddCommand(NexusUploadCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentPushATCSystemConfigCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentRunATCCheckCommand())
|
|
rootCmd.AddCommand(NpmExecuteScriptsCommand())
|
|
rootCmd.AddCommand(NpmExecuteLintCommand())
|
|
rootCmd.AddCommand(GctsCreateRepositoryCommand())
|
|
rootCmd.AddCommand(GctsExecuteABAPQualityChecksCommand())
|
|
rootCmd.AddCommand(GctsExecuteABAPUnitTestsCommand())
|
|
rootCmd.AddCommand(GctsDeployCommand())
|
|
rootCmd.AddCommand(MalwareExecuteScanCommand())
|
|
rootCmd.AddCommand(CloudFoundryCreateServiceCommand())
|
|
rootCmd.AddCommand(CloudFoundryDeployCommand())
|
|
rootCmd.AddCommand(GctsRollbackCommand())
|
|
rootCmd.AddCommand(WhitesourceExecuteScanCommand())
|
|
rootCmd.AddCommand(GctsCloneRepositoryCommand())
|
|
rootCmd.AddCommand(JsonApplyPatchCommand())
|
|
rootCmd.AddCommand(KanikoExecuteCommand())
|
|
rootCmd.AddCommand(CnbBuildCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentBuildCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentAssemblePackagesCommand())
|
|
rootCmd.AddCommand(AbapAddonAssemblyKitCheckCVsCommand())
|
|
rootCmd.AddCommand(AbapAddonAssemblyKitCheckPVCommand())
|
|
rootCmd.AddCommand(AbapAddonAssemblyKitCreateTargetVectorCommand())
|
|
rootCmd.AddCommand(AbapAddonAssemblyKitPublishTargetVectorCommand())
|
|
rootCmd.AddCommand(AbapAddonAssemblyKitRegisterPackagesCommand())
|
|
rootCmd.AddCommand(AbapAddonAssemblyKitReleasePackagesCommand())
|
|
rootCmd.AddCommand(AbapAddonAssemblyKitReserveNextPackagesCommand())
|
|
rootCmd.AddCommand(CloudFoundryCreateSpaceCommand())
|
|
rootCmd.AddCommand(CloudFoundryDeleteSpaceCommand())
|
|
rootCmd.AddCommand(VaultRotateSecretIdCommand())
|
|
rootCmd.AddCommand(IsChangeInDevelopmentCommand())
|
|
rootCmd.AddCommand(TransportRequestUploadCTSCommand())
|
|
rootCmd.AddCommand(TransportRequestUploadRFCCommand())
|
|
rootCmd.AddCommand(NewmanExecuteCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactDeployCommand())
|
|
rootCmd.AddCommand(TransportRequestUploadSOLMANCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactUpdateConfigurationCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactGetMplStatusCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactGetServiceEndpointCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactDownloadCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentAssembleConfirmCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactUploadCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactTriggerIntegrationTestCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactUnDeployCommand())
|
|
rootCmd.AddCommand(IntegrationArtifactResourceCommand())
|
|
rootCmd.AddCommand(TerraformExecuteCommand())
|
|
rootCmd.AddCommand(ContainerExecuteStructureTestsCommand())
|
|
rootCmd.AddCommand(GaugeExecuteTestsCommand())
|
|
rootCmd.AddCommand(BatsExecuteTestsCommand())
|
|
rootCmd.AddCommand(PipelineCreateScanSummaryCommand())
|
|
rootCmd.AddCommand(TransportRequestDocIDFromGitCommand())
|
|
rootCmd.AddCommand(TransportRequestReqIDFromGitCommand())
|
|
rootCmd.AddCommand(WritePipelineEnv())
|
|
rootCmd.AddCommand(ReadPipelineEnv())
|
|
rootCmd.AddCommand(InfluxWriteDataCommand())
|
|
rootCmd.AddCommand(AbapEnvironmentRunAUnitTestCommand())
|
|
rootCmd.AddCommand(CheckStepActiveCommand())
|
|
rootCmd.AddCommand(GolangBuildCommand())
|
|
rootCmd.AddCommand(ShellExecuteCommand())
|
|
rootCmd.AddCommand(ApiProxyDownloadCommand())
|
|
rootCmd.AddCommand(ApiKeyValueMapDownloadCommand())
|
|
rootCmd.AddCommand(ApiProviderDownloadCommand())
|
|
rootCmd.AddCommand(ApiProxyUploadCommand())
|
|
rootCmd.AddCommand(GradleExecuteBuildCommand())
|
|
rootCmd.AddCommand(ApiKeyValueMapUploadCommand())
|
|
rootCmd.AddCommand(PythonBuildCommand())
|
|
rootCmd.AddCommand(AzureBlobUploadCommand())
|
|
rootCmd.AddCommand(AwsS3UploadCommand())
|
|
rootCmd.AddCommand(ApiProxyListCommand())
|
|
rootCmd.AddCommand(AnsSendEventCommand())
|
|
rootCmd.AddCommand(ApiProviderListCommand())
|
|
rootCmd.AddCommand(TmsUploadCommand())
|
|
|
|
addRootFlags(rootCmd)
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
log.SetErrorCategory(log.ErrorConfiguration)
|
|
log.Entry().WithError(err).Fatal("configuration error")
|
|
}
|
|
}
|
|
|
|
func addRootFlags(rootCmd *cobra.Command) {
|
|
var provider orchestrator.OrchestratorSpecificConfigProviding
|
|
var err error
|
|
|
|
provider, err = orchestrator.NewOrchestratorSpecificConfigProvider()
|
|
if err != nil {
|
|
log.Entry().Error(err)
|
|
provider = &orchestrator.UnknownOrchestratorConfigProvider{}
|
|
}
|
|
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.CorrelationID, "correlationID", provider.GetBuildURL(), "ID for unique identification of a pipeline run")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.CustomConfig, "customConfig", ".pipeline/config.yml", "Path to the pipeline configuration file")
|
|
rootCmd.PersistentFlags().StringSliceVar(&GeneralConfig.GitHubTokens, "gitHubTokens", AccessTokensFromEnvJSON(os.Getenv("PIPER_gitHubTokens")), "List of entries in form of <hostname>:<token> to allow GitHub token authentication for downloading config / defaults")
|
|
rootCmd.PersistentFlags().StringSliceVar(&GeneralConfig.DefaultConfig, "defaultConfig", []string{".pipeline/defaults.yaml"}, "Default configurations, passed as path to yaml file")
|
|
rootCmd.PersistentFlags().BoolVar(&GeneralConfig.IgnoreCustomDefaults, "ignoreCustomDefaults", false, "Disables evaluation of the parameter 'customDefaults' in the pipeline configuration file")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.ParametersJSON, "parametersJSON", os.Getenv("PIPER_parametersJSON"), "Parameters to be considered in JSON format")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.EnvRootPath, "envRootPath", ".pipeline", "Root path to Piper pipeline shared environments")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.StageName, "stageName", "", "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")
|
|
rootCmd.PersistentFlags().BoolVar(&GeneralConfig.NoTelemetry, "noTelemetry", false, "Disables telemetry reporting")
|
|
rootCmd.PersistentFlags().BoolVarP(&GeneralConfig.Verbose, "verbose", "v", false, "verbose output")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.LogFormat, "logFormat", "default", "Log format to use. Options: default, timestamp, plain, full.")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.VaultServerURL, "vaultServerUrl", "", "The Vault server which should be used to fetch credentials")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.VaultNamespace, "vaultNamespace", "", "The Vault namespace which should be used to fetch credentials")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.VaultPath, "vaultPath", "", "The path which should be used to fetch credentials")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.GCPJsonKeyFilePath, "gcpJsonKeyFilePath", "", "File path to Google Cloud Platform JSON key file")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.GCSFolderPath, "gcsFolderPath", "", "GCS folder path. One of the components of GCS target folder")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.GCSBucketId, "gcsBucketId", "", "Bucket name for Google Cloud Storage")
|
|
rootCmd.PersistentFlags().StringVar(&GeneralConfig.GCSSubFolder, "gcsSubFolder", "", "Used to logically separate results of the same step result type")
|
|
|
|
}
|
|
|
|
// ResolveAccessTokens reads a list of tokens in format host:token passed via command line
|
|
// and transfers this into a map as a more consumable format.
|
|
func ResolveAccessTokens(tokenList []string) map[string]string {
|
|
tokenMap := map[string]string{}
|
|
for _, tokenEntry := range tokenList {
|
|
log.Entry().Debugf("processing token %v", tokenEntry)
|
|
parts := strings.Split(tokenEntry, ":")
|
|
if len(parts) != 2 {
|
|
log.Entry().Warningf("wrong format for access token %v", tokenEntry)
|
|
} else {
|
|
tokenMap[parts[0]] = parts[1]
|
|
}
|
|
}
|
|
return tokenMap
|
|
}
|
|
|
|
// AccessTokensFromEnvJSON resolves access tokens when passed as JSON in an environment variable
|
|
func AccessTokensFromEnvJSON(env string) []string {
|
|
accessTokens := []string{}
|
|
if len(env) == 0 {
|
|
return accessTokens
|
|
}
|
|
err := json.Unmarshal([]byte(env), &accessTokens)
|
|
if err != nil {
|
|
log.Entry().Infof("Token json '%v' has wrong format.", env)
|
|
}
|
|
return accessTokens
|
|
}
|
|
|
|
// initStageName initializes GeneralConfig.StageName from either GeneralConfig.ParametersJSON
|
|
// or the environment variable (orchestrator specific), unless it has been provided as command line option.
|
|
// Log output needs to be suppressed via outputToLog by the getConfig step.
|
|
func initStageName(outputToLog bool) {
|
|
var stageNameSource string
|
|
if outputToLog {
|
|
defer func() {
|
|
log.Entry().Infof("Using stageName '%s' from %s", GeneralConfig.StageName, stageNameSource)
|
|
}()
|
|
}
|
|
|
|
if GeneralConfig.StageName != "" {
|
|
// Means it was given as command line argument and has the highest precedence
|
|
stageNameSource = "command line arguments"
|
|
return
|
|
}
|
|
|
|
// Use stageName from ENV as fall-back, for when extracting it from parametersJSON fails below
|
|
provider, err := orchestrator.NewOrchestratorSpecificConfigProvider()
|
|
if err != nil {
|
|
log.Entry().WithError(err).Warning("Cannot infer stage name from CI environment")
|
|
} else {
|
|
stageNameSource = "env variable"
|
|
GeneralConfig.StageName = provider.GetStageName()
|
|
}
|
|
|
|
if len(GeneralConfig.ParametersJSON) == 0 {
|
|
return
|
|
}
|
|
|
|
var params map[string]interface{}
|
|
err = json.Unmarshal([]byte(GeneralConfig.ParametersJSON), ¶ms)
|
|
if err != nil {
|
|
if outputToLog {
|
|
log.Entry().Infof("Failed to extract 'stageName' from parametersJSON: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
stageName, hasKey := params["stageName"]
|
|
if !hasKey {
|
|
return
|
|
}
|
|
|
|
if stageNameString, ok := stageName.(string); ok && stageNameString != "" {
|
|
stageNameSource = "parametersJSON"
|
|
GeneralConfig.StageName = stageNameString
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
|
|
log.SetFormatter(GeneralConfig.LogFormat)
|
|
|
|
initStageName(true)
|
|
|
|
filters := metadata.GetParameterFilters()
|
|
|
|
// 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")
|
|
|
|
envParams := metadata.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
|
|
reportingEnvParams := config.ReportingParameters.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
|
|
resourceParams := mergeResourceParameters(envParams, reportingEnvParams)
|
|
|
|
flagValues := config.AvailableFlagValues(cmd, &filters)
|
|
|
|
var myConfig config.Config
|
|
var stepConfig config.StepConfig
|
|
|
|
// 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)
|
|
|
|
if len(GeneralConfig.StepConfigJSON) != 0 {
|
|
// ignore config & defaults in favor of passed stepConfigJSON
|
|
stepConfig = config.GetStepConfigWithJSON(flagValues, GeneralConfig.StepConfigJSON, filters)
|
|
log.Entry().Infof("Project config: passed via JSON")
|
|
log.Entry().Infof("Project defaults: passed via JSON")
|
|
} else {
|
|
// use config & defaults
|
|
var customConfig io.ReadCloser
|
|
var err error
|
|
//accept that config file and defaults cannot be loaded since both are not mandatory here
|
|
{
|
|
projectConfigFile := getProjectConfigFile(GeneralConfig.CustomConfig)
|
|
if exists, err := piperutils.FileExists(projectConfigFile); exists {
|
|
log.Entry().Infof("Project config: '%s'", projectConfigFile)
|
|
if customConfig, err = openFile(projectConfigFile, GeneralConfig.GitHubAccessTokens); err != nil {
|
|
return errors.Wrapf(err, "Cannot read '%s'", projectConfigFile)
|
|
}
|
|
} else {
|
|
log.Entry().Infof("Project config: NONE ('%s' does not exist)", projectConfigFile)
|
|
customConfig = nil
|
|
}
|
|
}
|
|
var defaultConfig []io.ReadCloser
|
|
if len(GeneralConfig.DefaultConfig) == 0 {
|
|
log.Entry().Info("Project defaults: NONE")
|
|
}
|
|
for _, projectDefaultFile := range GeneralConfig.DefaultConfig {
|
|
fc, err := openFile(projectDefaultFile, GeneralConfig.GitHubAccessTokens)
|
|
// only create error for non-default values
|
|
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)
|
|
defaultConfig = append(defaultConfig, fc)
|
|
}
|
|
}
|
|
stepConfig, err = myConfig.GetStepConfig(flagValues, GeneralConfig.ParametersJSON, customConfig, defaultConfig, GeneralConfig.IgnoreCustomDefaults, filters, *metadata, resourceParams, GeneralConfig.StageName, stepName)
|
|
if verbose, ok := stepConfig.Config["verbose"].(bool); ok && verbose {
|
|
log.SetVerbose(verbose)
|
|
GeneralConfig.Verbose = verbose
|
|
} else if !ok && stepConfig.Config["verbose"] != nil {
|
|
log.Entry().Warnf("invalid value for parameter verbose: '%v'", stepConfig.Config["verbose"])
|
|
}
|
|
if err != nil {
|
|
return errors.Wrap(err, "retrieving step configuration failed")
|
|
}
|
|
}
|
|
|
|
if fmt.Sprintf("%v", stepConfig.Config["collectTelemetryData"]) == "false" {
|
|
GeneralConfig.NoTelemetry = true
|
|
}
|
|
|
|
stepConfig.Config = checkTypes(stepConfig.Config, options)
|
|
confJSON, _ := json.Marshal(stepConfig.Config)
|
|
_ = json.Unmarshal(confJSON, &options)
|
|
|
|
config.MarkFlagsWithValue(cmd, stepConfig)
|
|
|
|
retrieveHookConfig(stepConfig.HookConfig, &GeneralConfig.HookConfig)
|
|
|
|
if GeneralConfig.GCPJsonKeyFilePath == "" {
|
|
GeneralConfig.GCPJsonKeyFilePath, _ = stepConfig.Config["gcpJsonKeyFilePath"].(string)
|
|
}
|
|
if GeneralConfig.GCSFolderPath == "" {
|
|
GeneralConfig.GCSFolderPath, _ = stepConfig.Config["gcsFolderPath"].(string)
|
|
}
|
|
if GeneralConfig.GCSBucketId == "" {
|
|
GeneralConfig.GCSBucketId, _ = stepConfig.Config["gcsBucketId"].(string)
|
|
}
|
|
if GeneralConfig.GCSSubFolder == "" {
|
|
GeneralConfig.GCSSubFolder, _ = stepConfig.Config["gcsSubFolder"].(string)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func retrieveHookConfig(source map[string]interface{}, target *HookConfiguration) {
|
|
if source != nil {
|
|
log.Entry().Info("Retrieving hook configuration")
|
|
b, err := json.Marshal(source)
|
|
if err != nil {
|
|
log.Entry().Warningf("Failed to marshal source hook configuration: %v", err)
|
|
}
|
|
err = json.Unmarshal(b, target)
|
|
if err != nil {
|
|
log.Entry().Warningf("Failed to retrieve hook configuration: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var errIncompatibleTypes = fmt.Errorf("incompatible types")
|
|
|
|
func checkTypes(config map[string]interface{}, options interface{}) map[string]interface{} {
|
|
optionsType := getStepOptionsStructType(options)
|
|
|
|
for paramName := range config {
|
|
optionsField := findStructFieldByJSONTag(paramName, optionsType)
|
|
if optionsField == nil {
|
|
continue
|
|
}
|
|
|
|
if config[paramName] == nil {
|
|
// There is a key, but no value. This can result from merging values from the CPE.
|
|
continue
|
|
}
|
|
|
|
paramValueType := reflect.ValueOf(config[paramName])
|
|
if optionsField.Type.Kind() == paramValueType.Kind() {
|
|
// Types already match, nothing to do
|
|
continue
|
|
}
|
|
|
|
var typeError error = nil
|
|
|
|
switch paramValueType.Kind() {
|
|
case reflect.String:
|
|
typeError = convertValueFromString(config, optionsField, paramName, paramValueType.String())
|
|
case reflect.Float32, reflect.Float64:
|
|
typeError = convertValueFromFloat(config, optionsField, paramName, paramValueType.Float())
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
typeError = convertValueFromInt(config, optionsField, paramName, paramValueType.Int())
|
|
default:
|
|
log.Entry().Warnf("Config value for '%s' is of unexpected type %s, expected %s. "+
|
|
"The value may be ignored as a result. To avoid any risk, specify this value with explicit type.",
|
|
paramName, paramValueType.Kind(), optionsField.Type.Kind())
|
|
}
|
|
|
|
if typeError != nil {
|
|
typeError = fmt.Errorf("config value for '%s' is of unexpected type %s, expected %s: %w",
|
|
paramName, paramValueType.Kind(), optionsField.Type.Kind(), typeError)
|
|
log.SetErrorCategory(log.ErrorConfiguration)
|
|
log.Entry().WithError(typeError).Fatal("type error in configuration")
|
|
}
|
|
}
|
|
return config
|
|
}
|
|
|
|
func convertValueFromString(config map[string]interface{}, optionsField *reflect.StructField, paramName, paramValue string) error {
|
|
switch optionsField.Type.Kind() {
|
|
case reflect.Slice, reflect.Array:
|
|
// Could do automatic conversion for those types in theory,
|
|
// but that might obscure what really happens in error cases.
|
|
return fmt.Errorf("expected type to be a list (or slice, or array) but got string")
|
|
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
|
|
return nil
|
|
} else if paramValue == "false" {
|
|
config[paramName] = false
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return errIncompatibleTypes
|
|
}
|
|
|
|
func convertValueFromFloat(config map[string]interface{}, optionsField *reflect.StructField, paramName string, paramValue float64) error {
|
|
switch optionsField.Type.Kind() {
|
|
case reflect.String:
|
|
val := strconv.FormatFloat(paramValue, 'f', -1, 64)
|
|
// if Sprinted value and val are equal, we can be pretty sure that the result fits
|
|
// for very large numbers for example an exponential format is printed
|
|
if val == fmt.Sprint(paramValue) {
|
|
config[paramName] = val
|
|
return nil
|
|
}
|
|
// allow float numbers containing a decimal separator
|
|
if strings.Contains(val, ".") {
|
|
config[paramName] = val
|
|
return nil
|
|
}
|
|
// if now no decimal separator is available we cannot be sure that the result is correct:
|
|
// long numbers like e.g. 73554900100200011600 will not be represented correctly after reading the yaml
|
|
// thus we cannot assume that the string is correct.
|
|
// short numbers will be handled as int anyway
|
|
return errIncompatibleTypes
|
|
case reflect.Float32:
|
|
config[paramName] = float32(paramValue)
|
|
return nil
|
|
case reflect.Float64:
|
|
config[paramName] = paramValue
|
|
return nil
|
|
case reflect.Int:
|
|
// Treat as type-mismatch only in case the conversion would be lossy.
|
|
// In that case, the json.Unmarshall() would indeed just drop it, so we want to fail.
|
|
if float64(int(paramValue)) == paramValue {
|
|
config[paramName] = int(paramValue)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return errIncompatibleTypes
|
|
}
|
|
|
|
func convertValueFromInt(config map[string]interface{}, optionsField *reflect.StructField, paramName string, paramValue int64) error {
|
|
switch optionsField.Type.Kind() {
|
|
case reflect.String:
|
|
config[paramName] = strconv.FormatInt(paramValue, 10)
|
|
return nil
|
|
case reflect.Float32:
|
|
config[paramName] = float32(paramValue)
|
|
return nil
|
|
case reflect.Float64:
|
|
config[paramName] = float64(paramValue)
|
|
return nil
|
|
}
|
|
|
|
return errIncompatibleTypes
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func mergeResourceParameters(resParams ...map[string]interface{}) map[string]interface{} {
|
|
result := make(map[string]interface{})
|
|
for _, m := range resParams {
|
|
for k, v := range m {
|
|
result[k] = v
|
|
}
|
|
}
|
|
return result
|
|
}
|