You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	| @@ -75,6 +75,7 @@ func GetAllStepMetadata() map[string]config.StepData { | ||||
| 		"transportRequestDocIDFromGit":            transportRequestDocIDFromGitMetadata(), | ||||
| 		"transportRequestReqIDFromGit":            transportRequestReqIDFromGitMetadata(), | ||||
| 		"transportRequestUploadCTS":               transportRequestUploadCTSMetadata(), | ||||
| 		"transportRequestUploadRFC":               transportRequestUploadRFCMetadata(), | ||||
| 		"transportRequestUploadSOLMAN":            transportRequestUploadSOLMANMetadata(), | ||||
| 		"uiVeri5ExecuteTests":                     uiVeri5ExecuteTestsMetadata(), | ||||
| 		"vaultRotateSecretId":                     vaultRotateSecretIdMetadata(), | ||||
|   | ||||
| @@ -136,6 +136,7 @@ func Execute() { | ||||
| 	rootCmd.AddCommand(VaultRotateSecretIdCommand()) | ||||
| 	rootCmd.AddCommand(CheckChangeInDevelopmentCommand()) | ||||
| 	rootCmd.AddCommand(TransportRequestUploadCTSCommand()) | ||||
| 	rootCmd.AddCommand(TransportRequestUploadRFCCommand()) | ||||
| 	rootCmd.AddCommand(NewmanExecuteCommand()) | ||||
| 	rootCmd.AddCommand(IntegrationArtifactDeployCommand()) | ||||
| 	rootCmd.AddCommand(TransportRequestUploadSOLMANCommand()) | ||||
|   | ||||
							
								
								
									
										85
									
								
								cmd/transportRequestUploadRFC.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								cmd/transportRequestUploadRFC.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"github.com/SAP/jenkins-library/pkg/command" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/SAP/jenkins-library/pkg/transportrequest/rfc" | ||||
| ) | ||||
|  | ||||
| type transportRequestUploadRFCUtils interface { | ||||
| 	rfc.Exec | ||||
| 	// Add more methods here, or embed additional interfaces, or remove/replace as required. | ||||
| 	// The transportRequestUploadRFCUtils interface should be descriptive of your runtime dependencies, | ||||
| 	// i.e. include everything you need to be able to mock in tests. | ||||
| 	// Unit tests shall be executable in parallel (not depend on global state), and don't (re-)test dependencies. | ||||
| } | ||||
|  | ||||
| type transportRequestUploadRFCUtilsBundle struct { | ||||
| 	*command.Command | ||||
|  | ||||
| 	// Embed more structs as necessary to implement methods or interfaces you add to transportRequestUploadRFCUtils. | ||||
| 	// Structs embedded in this way must each have a unique set of methods attached. | ||||
| 	// If there is no struct which implements the method you need, attach the method to | ||||
| 	// transportRequestUploadRFCUtilsBundle and forward to the implementation of the dependency. | ||||
| } | ||||
|  | ||||
| func newTransportRequestUploadRFCUtils() transportRequestUploadRFCUtils { | ||||
| 	utils := transportRequestUploadRFCUtilsBundle{ | ||||
| 		Command: &command.Command{}, | ||||
| 	} | ||||
| 	// Reroute command output to logging framework | ||||
| 	utils.Stdout(log.Writer()) | ||||
| 	utils.Stderr(log.Writer()) | ||||
| 	return &utils | ||||
| } | ||||
|  | ||||
| func transportRequestUploadRFC(config transportRequestUploadRFCOptions, telemetryData *telemetry.CustomData) { | ||||
| 	// Utils can be used wherever the command.ExecRunner interface is expected. | ||||
| 	// It can also be used for example as a mavenExecRunner. | ||||
| 	utils := newTransportRequestUploadRFCUtils() | ||||
|  | ||||
| 	action := rfc.UploadAction{} | ||||
| 	// For HTTP calls import  piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||||
| 	// and use a  &piperhttp.Client{} in a custom system | ||||
| 	// Example: step checkmarxExecuteScan.go | ||||
|  | ||||
| 	// Error situations should be bubbled up until they reach the line below which will then stop execution | ||||
| 	// through the log.Entry().Fatal() call leading to an os.Exit(1) in the end. | ||||
| 	err := runTransportRequestUploadRFC(&config, &action, telemetryData, utils) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("step execution failed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func runTransportRequestUploadRFC(config *transportRequestUploadRFCOptions, action rfc.Upload, telemetryData *telemetry.CustomData, cmd rfc.Exec) error { | ||||
|  | ||||
| 	action.WithConnection( | ||||
| 		rfc.Connection{ | ||||
| 			Endpoint: config.Endpoint, | ||||
| 			Client:   config.Client, | ||||
| 			Instance: config.Instance, | ||||
| 			User:     config.Username, | ||||
| 			Password: config.Password, | ||||
| 		}, | ||||
| 	) | ||||
| 	action.WithApplication( | ||||
| 		rfc.Application{ | ||||
| 			Name:        config.ApplicationName, | ||||
| 			Description: config.ApplicationDescription, | ||||
| 			AbapPackage: config.AbapPackage, | ||||
| 		}, | ||||
| 	) | ||||
| 	action.WithConfiguration( | ||||
| 		rfc.UploadConfig{ | ||||
| 			AcceptUnixStyleEndOfLine: config.AcceptUnixStyleLineEndings, | ||||
| 			CodePage:                 config.CodePage, | ||||
| 			FailUploadOnWarning:      config.FailUploadOnWarning, | ||||
| 			Verbose:                  GeneralConfig.Verbose, | ||||
| 		}, | ||||
| 	) | ||||
| 	action.WithTransportRequestID(config.TransportRequestID) | ||||
| 	action.WithApplicationURL(config.ApplicationURL) | ||||
|  | ||||
| 	return action.Perform(cmd) | ||||
| } | ||||
							
								
								
									
										260
									
								
								cmd/transportRequestUploadRFC_generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								cmd/transportRequestUploadRFC_generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,260 @@ | ||||
| // Code generated by piper's step-generator. DO NOT EDIT. | ||||
|  | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/config" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/splunk" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| type transportRequestUploadRFCOptions struct { | ||||
| 	Endpoint                   string `json:"endpoint,omitempty"` | ||||
| 	Client                     string `json:"client,omitempty"` | ||||
| 	Instance                   string `json:"instance,omitempty"` | ||||
| 	Username                   string `json:"username,omitempty"` | ||||
| 	Password                   string `json:"password,omitempty"` | ||||
| 	ApplicationName            string `json:"applicationName,omitempty"` | ||||
| 	ApplicationDescription     string `json:"applicationDescription,omitempty"` | ||||
| 	AbapPackage                string `json:"abapPackage,omitempty"` | ||||
| 	ApplicationURL             string `json:"applicationUrl,omitempty"` | ||||
| 	CodePage                   string `json:"codePage,omitempty"` | ||||
| 	AcceptUnixStyleLineEndings bool   `json:"acceptUnixStyleLineEndings,omitempty"` | ||||
| 	FailUploadOnWarning        bool   `json:"failUploadOnWarning,omitempty"` | ||||
| 	TransportRequestID         string `json:"transportRequestId,omitempty"` | ||||
| } | ||||
|  | ||||
| // TransportRequestUploadRFCCommand Uploads content to a transport request | ||||
| func TransportRequestUploadRFCCommand() *cobra.Command { | ||||
| 	const STEP_NAME = "transportRequestUploadRFC" | ||||
|  | ||||
| 	metadata := transportRequestUploadRFCMetadata() | ||||
| 	var stepConfig transportRequestUploadRFCOptions | ||||
| 	var startTime time.Time | ||||
| 	var logCollector *log.CollectorHook | ||||
|  | ||||
| 	var createTransportRequestUploadRFCCmd = &cobra.Command{ | ||||
| 		Use:   STEP_NAME, | ||||
| 		Short: "Uploads content to a transport request", | ||||
| 		Long:  `Uploads content to a transport request.`, | ||||
| 		PreRunE: func(cmd *cobra.Command, _ []string) error { | ||||
| 			startTime = time.Now() | ||||
| 			log.SetStepName(STEP_NAME) | ||||
| 			log.SetVerbose(GeneralConfig.Verbose) | ||||
|  | ||||
| 			path, _ := os.Getwd() | ||||
| 			fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} | ||||
| 			log.RegisterHook(fatalHook) | ||||
|  | ||||
| 			err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile) | ||||
| 			if err != nil { | ||||
| 				log.SetErrorCategory(log.ErrorConfiguration) | ||||
| 				return err | ||||
| 			} | ||||
| 			log.RegisterSecret(stepConfig.Username) | ||||
| 			log.RegisterSecret(stepConfig.Password) | ||||
|  | ||||
| 			if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { | ||||
| 				sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) | ||||
| 				log.RegisterHook(&sentryHook) | ||||
| 			} | ||||
|  | ||||
| 			if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { | ||||
| 				logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID} | ||||
| 				log.RegisterHook(logCollector) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 		Run: func(_ *cobra.Command, _ []string) { | ||||
| 			telemetryData := telemetry.CustomData{} | ||||
| 			telemetryData.ErrorCode = "1" | ||||
| 			handler := func() { | ||||
| 				config.RemoveVaultSecretFiles() | ||||
| 				telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) | ||||
| 				telemetryData.ErrorCategory = log.GetErrorCategory().String() | ||||
| 				telemetry.Send(&telemetryData) | ||||
| 				if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { | ||||
| 					splunk.Send(&telemetryData, logCollector) | ||||
| 				} | ||||
| 			} | ||||
| 			log.DeferExitHandler(handler) | ||||
| 			defer handler() | ||||
| 			telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) | ||||
| 			if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { | ||||
| 				splunk.Initialize(GeneralConfig.CorrelationID, | ||||
| 					GeneralConfig.HookConfig.SplunkConfig.Dsn, | ||||
| 					GeneralConfig.HookConfig.SplunkConfig.Token, | ||||
| 					GeneralConfig.HookConfig.SplunkConfig.Index, | ||||
| 					GeneralConfig.HookConfig.SplunkConfig.SendLogs) | ||||
| 			} | ||||
| 			transportRequestUploadRFC(stepConfig, &telemetryData) | ||||
| 			telemetryData.ErrorCode = "0" | ||||
| 			log.Entry().Info("SUCCESS") | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	addTransportRequestUploadRFCFlags(createTransportRequestUploadRFCCmd, &stepConfig) | ||||
| 	return createTransportRequestUploadRFCCmd | ||||
| } | ||||
|  | ||||
| func addTransportRequestUploadRFCFlags(cmd *cobra.Command, stepConfig *transportRequestUploadRFCOptions) { | ||||
| 	cmd.Flags().StringVar(&stepConfig.Endpoint, "endpoint", os.Getenv("PIPER_endpoint"), "Service endpoint") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Client, "client", os.Getenv("PIPER_client"), "AS ABAP client number") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Instance, "instance", os.Getenv("PIPER_instance"), "AS ABAP instance number") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "Deploy user") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password for the deploy user") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ApplicationName, "applicationName", os.Getenv("PIPER_applicationName"), "Name of the application.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ApplicationDescription, "applicationDescription", os.Getenv("PIPER_applicationDescription"), "Description of the application.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.AbapPackage, "abapPackage", os.Getenv("PIPER_abapPackage"), "ABAP package name of your application") | ||||
| 	cmd.Flags().StringVar(&stepConfig.ApplicationURL, "applicationUrl", os.Getenv("PIPER_applicationUrl"), "URL where to find the UI5 package to upload to the transport request") | ||||
| 	cmd.Flags().StringVar(&stepConfig.CodePage, "codePage", `UTF-8`, "Code page") | ||||
| 	cmd.Flags().BoolVar(&stepConfig.AcceptUnixStyleLineEndings, "acceptUnixStyleLineEndings", true, "If unix style line endings should be accepted") | ||||
| 	cmd.Flags().BoolVar(&stepConfig.FailUploadOnWarning, "failUploadOnWarning", true, "If the upload should fail in case the log contains warnings") | ||||
| 	cmd.Flags().StringVar(&stepConfig.TransportRequestID, "transportRequestId", os.Getenv("PIPER_transportRequestId"), "Transport request id") | ||||
|  | ||||
| 	cmd.MarkFlagRequired("endpoint") | ||||
| 	cmd.MarkFlagRequired("username") | ||||
| 	cmd.MarkFlagRequired("password") | ||||
| 	cmd.MarkFlagRequired("applicationName") | ||||
| 	cmd.MarkFlagRequired("abapPackage") | ||||
| 	cmd.MarkFlagRequired("applicationUrl") | ||||
| 	cmd.MarkFlagRequired("abapPackage") | ||||
| 	cmd.MarkFlagRequired("transportRequestId") | ||||
| } | ||||
|  | ||||
| // retrieve step metadata | ||||
| func transportRequestUploadRFCMetadata() config.StepData { | ||||
| 	var theMetaData = config.StepData{ | ||||
| 		Metadata: config.StepMetadata{ | ||||
| 			Name:        "transportRequestUploadRFC", | ||||
| 			Aliases:     []config.Alias{{Name: "transportRequestUploadFile", Deprecated: false}}, | ||||
| 			Description: "Uploads content to a transport request", | ||||
| 		}, | ||||
| 		Spec: config.StepSpec{ | ||||
| 			Inputs: config.StepInputs{ | ||||
| 				Parameters: []config.StepParameters{ | ||||
| 					{ | ||||
| 						Name:        "endpoint", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{{Name: "changeManagement/endpoint"}}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "client", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{{Name: "changeManagement/rfc/developmentClient"}}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "instance", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{{Name: "changeManagement/rfc/developmentInstance"}}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "username", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "password", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "applicationName", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "applicationDescription", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "abapPackage", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "applicationUrl", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "abapPackage", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "codePage", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "acceptUnixStyleLineEndings", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "bool", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "failUploadOnWarning", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "bool", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{{Name: "failOnWarning"}}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "transportRequestId", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	return theMetaData | ||||
| } | ||||
							
								
								
									
										17
									
								
								cmd/transportRequestUploadRFC_generated_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								cmd/transportRequestUploadRFC_generated_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestTransportRequestUploadRFCCommand(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	testCmd := TransportRequestUploadRFCCommand() | ||||
|  | ||||
| 	// only high level testing performed - details are tested in step generation procedure | ||||
| 	assert.Equal(t, "transportRequestUploadRFC", testCmd.Use, "command name incorrect") | ||||
|  | ||||
| } | ||||
							
								
								
									
										110
									
								
								cmd/transportRequestUploadRFC_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								cmd/transportRequestUploadRFC_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"github.com/SAP/jenkins-library/pkg/mock" | ||||
| 	"github.com/SAP/jenkins-library/pkg/transportrequest/rfc" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| type transportRequestUploadRFCMockUtils struct { | ||||
| 	*mock.ExecMockRunner | ||||
| } | ||||
|  | ||||
| func newTransportRequestUploadRFCTestsUtils() transportRequestUploadRFCMockUtils { | ||||
| 	utils := transportRequestUploadRFCMockUtils{ | ||||
| 		ExecMockRunner: &mock.ExecMockRunner{}, | ||||
| 	} | ||||
| 	return utils | ||||
| } | ||||
|  | ||||
| type uploadMock struct { | ||||
| 	received     rfc.UploadAction | ||||
| 	uploadCalled bool | ||||
| } | ||||
|  | ||||
| // WithApplicationURL The location of the deployable | ||||
| func (m *uploadMock) WithApplicationURL(z string) { | ||||
| 	m.received.ApplicationURL = z | ||||
| } | ||||
|  | ||||
| // WithTransportRequestID The transport request ID for the upload | ||||
| func (m *uploadMock) WithTransportRequestID(t string) { | ||||
| 	m.received.TransportRequestID = t | ||||
| } | ||||
|  | ||||
| // WithApplication Everything we need to know about the application | ||||
| func (m *uploadMock) WithApplication(a rfc.Application) { | ||||
| 	m.received.Application = a | ||||
| } | ||||
|  | ||||
| // WithConfiguration Everything we need to know in order to perform the upload | ||||
| func (m *uploadMock) WithConfiguration(c rfc.UploadConfig) { | ||||
| 	m.received.Configuration = c | ||||
| } | ||||
|  | ||||
| // WithConnection Everything we need to know about the connection | ||||
| func (m *uploadMock) WithConnection(c rfc.Connection) { | ||||
| 	m.received.Connection = c | ||||
| } | ||||
|  | ||||
| func (m *uploadMock) Perform(exec rfc.Exec) error { | ||||
| 	m.uploadCalled = true | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func TestRunTransportRequestUploadRFC(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	config := transportRequestUploadRFCOptions{ | ||||
| 		Endpoint:                   "https://my.abap.server", | ||||
| 		Client:                     "001", | ||||
| 		Instance:                   "00", | ||||
| 		Username:                   "me", | ||||
| 		Password:                   "******", | ||||
| 		ApplicationName:            "MyApp", | ||||
| 		ApplicationDescription:     "Lorem impsum", | ||||
| 		AbapPackage:                "XX", | ||||
| 		ApplicationURL:             "http://example.org/myDeployable.zip", | ||||
| 		CodePage:                   "UTF-8", | ||||
| 		AcceptUnixStyleLineEndings: true, | ||||
| 		FailUploadOnWarning:        true, | ||||
| 		TransportRequestID:         "XXXK12345678", | ||||
| 	} | ||||
|  | ||||
| 	utils := newTransportRequestUploadRFCTestsUtils() | ||||
|  | ||||
| 	mock := uploadMock{} | ||||
|  | ||||
| 	err := runTransportRequestUploadRFC(&config, &mock, nil, utils) | ||||
|  | ||||
| 	if assert.NoError(t, err) { | ||||
| 		t.Run("upload triggered", func(t *testing.T) { | ||||
| 			assert.True(t, mock.uploadCalled) | ||||
| 		}) | ||||
| 		t.Run("parameters has been marshalled", func(t *testing.T) { | ||||
| 			assert.Equal(t, rfc.UploadAction{ | ||||
| 				Connection: rfc.Connection{ | ||||
| 					Endpoint: "https://my.abap.server", | ||||
| 					Client:   "001", | ||||
| 					Instance: "00", | ||||
| 					User:     "me", | ||||
| 					Password: "******", | ||||
| 				}, | ||||
| 				Application: rfc.Application{ | ||||
| 					Name:        "MyApp", | ||||
| 					Description: "Lorem impsum", | ||||
| 					AbapPackage: "XX", | ||||
| 				}, | ||||
| 				Configuration: rfc.UploadConfig{ | ||||
| 					AcceptUnixStyleEndOfLine: true, | ||||
| 					CodePage:                 "UTF-8", | ||||
| 					FailUploadOnWarning:      true, | ||||
| 					Verbose:                  false, // comes from general config | ||||
| 				}, | ||||
| 				TransportRequestID: "XXXK12345678", | ||||
| 				ApplicationURL:     "http://example.org/myDeployable.zip", | ||||
| 			}, mock.received) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										158
									
								
								pkg/transportrequest/rfc/upload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								pkg/transportrequest/rfc/upload.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | ||||
| package rfc | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/SAP/jenkins-library/pkg/command" | ||||
| 	"github.com/SAP/jenkins-library/pkg/config/validation" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	eq = "=" | ||||
| ) | ||||
|  | ||||
| // Exec Everything we need for calling an executable | ||||
| type Exec interface { | ||||
| 	command.ExecRunner | ||||
| 	GetExitCode() int | ||||
| } | ||||
|  | ||||
| // Upload ... | ||||
| type Upload interface { | ||||
| 	Perform(Exec) error | ||||
| 	WithConnection(Connection) | ||||
| 	WithConfiguration(UploadConfig) | ||||
| 	WithApplication(Application) | ||||
| 	WithTransportRequestID(string) | ||||
| 	WithApplicationURL(string) | ||||
| } | ||||
|  | ||||
| // Connection Everything we need for connecting to the ABAP system | ||||
| type Connection struct { | ||||
| 	// The endpoint in for form <protocol>://<host>:<port>, no path | ||||
| 	Endpoint string | ||||
| 	// The ABAP client, like e.g. "001" | ||||
| 	Client string | ||||
| 	// The ABAP instance, like e.g. "DEV",  "QA" | ||||
| 	Instance string | ||||
| 	User     string | ||||
| 	Password string | ||||
| } | ||||
|  | ||||
| // Application Application specific properties | ||||
| type Application struct { | ||||
| 	Name        string | ||||
| 	Description string | ||||
| 	AbapPackage string | ||||
| } | ||||
|  | ||||
| // UploadConfig additional configuration properties | ||||
| type UploadConfig struct { | ||||
| 	AcceptUnixStyleEndOfLine bool | ||||
| 	CodePage                 string | ||||
| 	FailUploadOnWarning      bool | ||||
| 	Verbose                  bool | ||||
| } | ||||
|  | ||||
| // UploadAction Collects all the properties we need for the deployment | ||||
| type UploadAction struct { | ||||
| 	Connection         Connection | ||||
| 	Application        Application | ||||
| 	Configuration      UploadConfig | ||||
| 	TransportRequestID string | ||||
| 	ApplicationURL     string | ||||
| } | ||||
|  | ||||
| // WithApplicationURL The location of the deployable | ||||
| func (action *UploadAction) WithApplicationURL(z string) { | ||||
| 	action.ApplicationURL = z | ||||
| } | ||||
|  | ||||
| // WithTransportRequestID The transport request ID for the upload | ||||
| func (action *UploadAction) WithTransportRequestID(t string) { | ||||
| 	action.TransportRequestID = t | ||||
| } | ||||
|  | ||||
| // WithApplication Everything we need to know about the application | ||||
| func (action *UploadAction) WithApplication(a Application) { | ||||
| 	action.Application = a | ||||
| } | ||||
|  | ||||
| // WithConfiguration Everything we need to know in order to perform the upload | ||||
| func (action *UploadAction) WithConfiguration(c UploadConfig) { | ||||
| 	action.Configuration = c | ||||
| } | ||||
|  | ||||
| // WithConnection Everything we need to know about the connection | ||||
| func (action *UploadAction) WithConnection(c Connection) { | ||||
| 	action.Connection = c | ||||
| } | ||||
|  | ||||
| // Perform Performs the upload | ||||
| func (action *UploadAction) Perform(command Exec) error { | ||||
|  | ||||
| 	log.Entry().Infof("Deploying artifact '%s' to '%s', client: '%s', instance: '%s'.", | ||||
| 		action.ApplicationURL, action.Connection.Endpoint, action.Connection.Client, action.Connection.Instance, | ||||
| 	) | ||||
|  | ||||
| 	parametersWithMissingValues, err := validation.FindEmptyStringsInConfigStruct(*action) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("invalid configuration parameters detected. SOLMAN upload parameter may be missing : %w", err) | ||||
| 	} | ||||
| 	if len(parametersWithMissingValues) != 0 { | ||||
| 		return fmt.Errorf("Cannot perform artifact upload. The following parameters are not available %s", parametersWithMissingValues) | ||||
| 	} | ||||
|  | ||||
| 	command.SetEnv([]string{ | ||||
| 		"ABAP_DEVELOPMENT_SERVER" + eq + action.Connection.Endpoint, | ||||
| 		"ABAP_DEVELOPMENT_USER" + eq + action.Connection.User, | ||||
| 		"ABAP_DEVELOPMENT_PASSWORD" + eq + action.Connection.Password, | ||||
| 		"ABAP_DEVELOPMENT_INSTANCE" + eq + action.Connection.Instance, | ||||
| 		"ABAP_DEVELOPMENT_CLIENT" + eq + action.Connection.Client, | ||||
| 		"ABAP_APPLICATION_NAME" + eq + action.Application.Name, | ||||
| 		"ABAP_APPLICATION_DESC" + eq + action.Application.Description, | ||||
| 		"ABAP_PACKAGE" + eq + action.Application.AbapPackage, | ||||
| 		"ZIP_FILE_URL" + eq + action.ApplicationURL, | ||||
| 		"CODE_PAGE" + eq + action.Configuration.CodePage, | ||||
| 		"ABAP_ACCEPT_UNIX_STYLE_EOL" + eq + toAbapBool(action.Configuration.AcceptUnixStyleEndOfLine), | ||||
| 		"FAIL_UPLOAD_ON_WARNING" + eq + strconv.FormatBool(action.Configuration.FailUploadOnWarning), | ||||
| 		"VERBOSE" + eq + strconv.FormatBool(action.Configuration.Verbose), | ||||
| 	}) | ||||
|  | ||||
| 	err = command.RunExecutable("cts", fmt.Sprintf("uploadToABAP:%s", action.TransportRequestID)) | ||||
|  | ||||
| 	exitCode := command.GetExitCode() | ||||
| 	if exitCode != 0 { | ||||
| 		message := fmt.Sprintf("upload command returned with exit code '%d'", exitCode) | ||||
| 		if err != nil { | ||||
| 			// Using the wrapping here is to some extend an abuse, since it is not really | ||||
| 			// error chaining (the other error is not necessaryly a "predecessor" of this one). | ||||
| 			// But it is a pragmatic approach for not loosing information for trouble shooting. There | ||||
| 			// is no possibility to have something like suppressed errors. | ||||
| 			err = errors.Wrap(err, message) | ||||
| 		} else { | ||||
| 			err = errors.New(message) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err == nil { | ||||
| 		log.Entry().Infof("Deploying artifact '%s' to '%s', client: '%s', instance: '%s' succeeded.", | ||||
| 			action.ApplicationURL, action.Connection.Endpoint, action.Connection.Client, action.Connection.Instance, | ||||
| 		) | ||||
| 	} else { | ||||
| 		log.Entry().Warnf("Deploying artifact '%s' to '%s', client: '%s', instance: '%s' failed.", | ||||
| 			action.ApplicationURL, action.Connection.Endpoint, action.Connection.Client, action.Connection.Instance, | ||||
| 		) | ||||
| 	} | ||||
| 	return errors.Wrap(err, "cannot upload artifact") | ||||
| } | ||||
|  | ||||
| func toAbapBool(b bool) string { | ||||
| 	abapBool := "-" | ||||
| 	if b { | ||||
| 		abapBool = "X" | ||||
| 	} | ||||
| 	return abapBool | ||||
| } | ||||
							
								
								
									
										115
									
								
								pkg/transportrequest/rfc/upload_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								pkg/transportrequest/rfc/upload_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| package rfc | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/SAP/jenkins-library/pkg/mock" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestUploadRFC(t *testing.T) { | ||||
|  | ||||
| 	defaultUploadAction := UploadAction{ | ||||
| 		Connection: Connection{ | ||||
| 			Endpoint: "https://example.org/rfc", | ||||
| 			Client:   "001", | ||||
| 			Instance: "DEV", | ||||
| 			User:     "me", | ||||
| 			Password: "******", | ||||
| 		}, | ||||
| 		Application: Application{ | ||||
| 			Name:        "myApp", | ||||
| 			Description: "The description", | ||||
| 			AbapPackage: "YYY", | ||||
| 		}, | ||||
| 		Configuration: UploadConfig{ | ||||
| 			AcceptUnixStyleEndOfLine: true, | ||||
| 			CodePage:                 "UTF-8", | ||||
| 			FailUploadOnWarning:      true, | ||||
| 			Verbose:                  true, | ||||
| 		}, | ||||
| 		TransportRequestID: "123456", | ||||
| 		ApplicationURL:     "https://localhost:8081/myDeployable.zip", | ||||
| 	} | ||||
|  | ||||
| 	t.Run("straight forward", func(t *testing.T) { | ||||
|  | ||||
| 		exec := mock.ExecMockRunner{} | ||||
|  | ||||
| 		upload := defaultUploadAction | ||||
|  | ||||
| 		err := upload.Perform(&exec) | ||||
|  | ||||
| 		if assert.NoError(t, err) { | ||||
| 			assert.Equal(t, exec.Calls, []mock.ExecCall{mock.ExecCall{Exec: "cts", Params: []string{"uploadToABAP:123456"}}}) | ||||
| 			assert.Subset(t, []string{ | ||||
| 				"ABAP_DEVELOPMENT_SERVER=https://example.org/rfc", | ||||
| 				"ABAP_DEVELOPMENT_USER=me", | ||||
| 				"ABAP_DEVELOPMENT_PASSWORD=******", | ||||
| 				"ABAP_DEVELOPMENT_INSTANCE=DEV", | ||||
| 				"ABAP_DEVELOPMENT_CLIENT=001", | ||||
| 				"ABAP_APPLICATION_NAME=myApp", | ||||
| 				"ABAP_APPLICATION_DESC=The description", | ||||
| 				"ABAP_PACKAGE=YYY", | ||||
| 				"ZIP_FILE_URL=https://localhost:8081/myDeployable.zip", | ||||
| 				"CODE_PAGE=UTF-8", | ||||
| 				"ABAP_ACCEPT_UNIX_STYLE_EOL=X", | ||||
| 				"FAIL_UPLOAD_ON_WARNING=true", | ||||
| 				"VERBOSE=true", | ||||
| 			}, exec.Env) | ||||
| 			assert.Len(t, exec.Env, 13) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("incomplete config", func(t *testing.T) { | ||||
|  | ||||
| 		exec := mock.ExecMockRunner{} | ||||
|  | ||||
| 		upload := defaultUploadAction | ||||
| 		upload.Connection.Endpoint = "" | ||||
| 		upload.Application.AbapPackage = "" | ||||
|  | ||||
| 		err := upload.Perform(&exec) | ||||
|  | ||||
| 		if assert.Error(t, err) { | ||||
| 			// Don't want to rely on the order, hence not checking for the full string ... | ||||
| 			assert.Contains(t, err.Error(), "Cannot perform artifact upload. The following parameters are not available") | ||||
| 			assert.Contains(t, err.Error(), "Connection.Endpoint") | ||||
| 			assert.Contains(t, err.Error(), "Application.AbapPackage") | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("invocation of cts tooling fails", func(t *testing.T) { | ||||
|  | ||||
| 		t.Run("error raised", func(t *testing.T) { | ||||
|  | ||||
| 			exec := mock.ExecMockRunner{ShouldFailOnCommand: map[string]error{"cts.*": fmt.Errorf("generic failure")}} | ||||
|  | ||||
| 			upload := defaultUploadAction | ||||
|  | ||||
| 			err := upload.Perform(&exec) | ||||
|  | ||||
| 			assert.EqualError(t, err, "cannot upload artifact: generic failure") | ||||
| 		}) | ||||
|  | ||||
| 		t.Run("return code not zero", func(t *testing.T) { | ||||
|  | ||||
| 			exec := mock.ExecMockRunner{ExitCode: 42} | ||||
|  | ||||
| 			upload := defaultUploadAction | ||||
|  | ||||
| 			err := upload.Perform(&exec) | ||||
|  | ||||
| 			assert.EqualError(t, err, "cannot upload artifact: upload command returned with exit code '42'") | ||||
| 		}) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestTheAbapBool(t *testing.T) { | ||||
| 	t.Run("true", func(t *testing.T) { | ||||
| 		assert.Equal(t, "X", toAbapBool(true)) | ||||
| 	}) | ||||
| 	t.Run("false", func(t *testing.T) { | ||||
| 		assert.Equal(t, "-", toAbapBool(false)) | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										144
									
								
								resources/metadata/transportRequestUploadRFC.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								resources/metadata/transportRequestUploadRFC.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| metadata: | ||||
|   name: transportRequestUploadRFC | ||||
|   aliases: | ||||
|     - name: transportRequestUploadFile | ||||
|   description: "Uploads content to a transport request" | ||||
|   longDescription: | | ||||
|     Uploads content to a transport request. | ||||
| spec: | ||||
|   inputs: | ||||
|     secrets: | ||||
|       - name: uploadCredentialsId | ||||
|         description: Jenkins 'Username with password' credentials ID containing user and password to authenticate against the ABAP backend. | ||||
|         type: jenkins | ||||
|         aliases: | ||||
|           - name: changeManagement/credentialsId | ||||
|     params: | ||||
|       - name: endpoint | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         description: "Service endpoint" | ||||
|         aliases: | ||||
|           - name: changeManagement/endpoint | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: client | ||||
|         type: string | ||||
|         aliases: | ||||
|           - name: changeManagement/rfc/developmentClient | ||||
|         description: "AS ABAP client number" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: instance | ||||
|         type: string | ||||
|         aliases: | ||||
|           - name: changeManagement/rfc/developmentInstance | ||||
|         description: "AS ABAP instance number" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: username | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         description: "Deploy user" | ||||
|         secret: true | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: password | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         description: "Password for the deploy user" | ||||
|         secret: true | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|       - name: applicationName | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         description: "Name of the application." | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: applicationDescription | ||||
|         type: string | ||||
|         mandatory: false | ||||
|         description: "Description of the application." | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: abapPackage | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         description: "ABAP package name of your application" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: applicationUrl | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         description: "URL where to find the UI5 package to upload to the transport request" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: abapPackage | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         description: "ABAP package name of your application" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: codePage | ||||
|         type: string | ||||
|         default: "UTF-8" | ||||
|         description: "Code page" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: acceptUnixStyleLineEndings | ||||
|         type: bool | ||||
|         default: true | ||||
|         description: "If unix style line endings should be accepted" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: failUploadOnWarning | ||||
|         type: bool | ||||
|         default: true | ||||
|         aliases: | ||||
|           - name: failOnWarning | ||||
|         description: "If the upload should fail in case the log contains warnings" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|       - name: transportRequestId | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         description: "Transport request id" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
		Reference in New Issue
	
	Block a user