1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

transport request upload cts / the step (#2416)

fiori cts upload piper-go step
This commit is contained in:
Marcus Holl
2021-01-13 16:14:56 +01:00
committed by GitHub
parent f9af1841e4
commit c8c1315d0c
7 changed files with 611 additions and 0 deletions

View File

@@ -59,6 +59,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"protecodeExecuteScan": protecodeExecuteScanMetadata(),
"containerSaveImage": containerSaveImageMetadata(),
"sonarExecuteScan": sonarExecuteScanMetadata(),
"transportRequestUploadCTS": transportRequestUploadCTSMetadata(),
"vaultRotateSecretId": vaultRotateSecretIdMetadata(),
"artifactPrepareVersion": artifactPrepareVersionMetadata(),
"whitesourceExecuteScan": whitesourceExecuteScanMetadata(),

View File

@@ -120,6 +120,7 @@ func Execute() {
rootCmd.AddCommand(CloudFoundryCreateSpaceCommand())
rootCmd.AddCommand(CloudFoundryDeleteSpaceCommand())
rootCmd.AddCommand(VaultRotateSecretIdCommand())
rootCmd.AddCommand(TransportRequestUploadCTSCommand())
rootCmd.AddCommand(DeployIntegrationArtifactCommand())
addRootFlags(rootCmd)

View File

@@ -0,0 +1,95 @@
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"
)
type transportRequestUploadUtils interface {
command.ShellRunner
// Add more methods here, or embed additional interfaces, or remove/replace as required.
// The transportRequestUploadUtils 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.
}
// CTSUploadAction ...
type CTSUploadAction interface {
Perform(command.ShellRunner) error
WithConnection(transportrequest.CTSConnection)
WithApplication(transportrequest.CTSApplication)
WithNodeProperties(transportrequest.CTSNode)
WithTransportRequestID(string)
WithConfigFile(string)
WithDeployUser(string)
}
type transportRequestUploadCTSUtilsBundle struct {
*command.Command
// Embed more structs as necessary to implement methods or interfaces you add to transportRequestUploadUtils.
// 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
// transportRequestUploadUtilsBundle and forward to the implementation of the dependency.
}
func newTransportRequestUploadCTSUtils() transportRequestUploadUtils {
utils := transportRequestUploadCTSUtilsBundle{
Command: &command.Command{},
}
// Reroute command output to logging framework
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}
func transportRequestUploadCTS(config transportRequestUploadCTSOptions, 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 := newTransportRequestUploadCTSUtils()
// 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 := runTransportRequestUploadCTS(&config, &transportrequest.CTSUploadAction{}, telemetryData, utils)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runTransportRequestUploadCTS(
config *transportRequestUploadCTSOptions,
action CTSUploadAction,
telemetryData *telemetry.CustomData,
cmd command.ShellRunner) error {
log.Entry().Debugf("Entering 'runTransportRequestUpload' with config: %v", config)
action.WithConnection(transportrequest.CTSConnection{
Endpoint: config.Endpoint,
Client: config.Client,
User: config.Username,
Password: config.Password,
})
action.WithApplication(transportrequest.CTSApplication{
Name: config.ApplicationName,
Pack: config.AbapPackage,
Desc: config.Description,
})
action.WithNodeProperties(transportrequest.CTSNode{
DeployDependencies: config.DeployToolDependencies,
InstallOpts: config.NpmInstallOpts,
})
action.WithTransportRequestID(config.TransportRequestID)
action.WithConfigFile(config.DeployConfigFile)
action.WithDeployUser(config.OsDeployUser)
return action.Perform(cmd)
}

View File

@@ -0,0 +1,222 @@
// 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/telemetry"
"github.com/spf13/cobra"
)
type transportRequestUploadCTSOptions struct {
Description string `json:"description,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Client string `json:"client,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
ApplicationName string `json:"applicationName,omitempty"`
AbapPackage string `json:"abapPackage,omitempty"`
OsDeployUser string `json:"osDeployUser,omitempty"`
DeployConfigFile string `json:"deployConfigFile,omitempty"`
TransportRequestID string `json:"transportRequestId,omitempty"`
DeployToolDependencies []string `json:"deployToolDependencies,omitempty"`
NpmInstallOpts []string `json:"npmInstallOpts,omitempty"`
}
// TransportRequestUploadCTSCommand Uploads content to a transport request
func TransportRequestUploadCTSCommand() *cobra.Command {
const STEP_NAME = "transportRequestUploadCTS"
metadata := transportRequestUploadCTSMetadata()
var stepConfig transportRequestUploadCTSOptions
var startTime time.Time
var createTransportRequestUploadCTSCmd = &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)
}
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)
}
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
transportRequestUploadCTS(stepConfig, &telemetryData)
telemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addTransportRequestUploadCTSFlags(createTransportRequestUploadCTSCmd, &stepConfig)
return createTransportRequestUploadCTSCmd
}
func addTransportRequestUploadCTSFlags(cmd *cobra.Command, stepConfig *transportRequestUploadCTSOptions) {
cmd.Flags().StringVar(&stepConfig.Description, "description", `Deployed with Piper based on SAP Fiori tools`, "The description of the application. the desription is only taken into account for a new upload. In case of an update the description will not be updated.")
cmd.Flags().StringVar(&stepConfig.Endpoint, "endpoint", os.Getenv("PIPER_endpoint"), "The service endpoint")
cmd.Flags().StringVar(&stepConfig.Client, "client", os.Getenv("PIPER_client"), "The ABAP client")
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "The deploy user")
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "The password for the deploy user")
cmd.Flags().StringVar(&stepConfig.ApplicationName, "applicationName", os.Getenv("PIPER_applicationName"), "The name of the application.")
cmd.Flags().StringVar(&stepConfig.AbapPackage, "abapPackage", os.Getenv("PIPER_abapPackage"), "The ABAP package name of your application")
cmd.Flags().StringVar(&stepConfig.OsDeployUser, "osDeployUser", `node`, "By default we use a standard node docker image and prepare some fiori related packages before performing the deployment. For that we need to launch the image with root privileges. After that, before actually performing the deployment we swith to a non root user. This user can be specified here.")
cmd.Flags().StringVar(&stepConfig.DeployConfigFile, "deployConfigFile", `ui5-deploy.yaml`, "The ABAP package name of your application")
cmd.Flags().StringVar(&stepConfig.TransportRequestID, "transportRequestId", os.Getenv("PIPER_transportRequestId"), "The id of the transport request to upload the file. This parameter is only taken into account when provided via signature to the step.")
cmd.Flags().StringSliceVar(&stepConfig.DeployToolDependencies, "deployToolDependencies", []string{}, "By default we use a standard node docker iamge and prepare some fiori related packages performing the deployment. The additional dependencies can be provided here. In case you use an already prepared docker image which contains the required dependencies, the empty list can be provide here. Caused hereby installing additional dependencies will be skipped.")
cmd.Flags().StringSliceVar(&stepConfig.NpmInstallOpts, "npmInstallOpts", []string{}, "A list containing additional options for the npm install call. `-g`, `--global` is always assumed. Can be used for e.g. providing custom registries (`--registry https://your.registry.com`) or for providing the verbose flag (`--verbose`) for troubleshooting.")
cmd.MarkFlagRequired("username")
cmd.MarkFlagRequired("password")
cmd.MarkFlagRequired("applicationName")
cmd.MarkFlagRequired("abapPackage")
cmd.MarkFlagRequired("transportRequestId")
}
// retrieve step metadata
func transportRequestUploadCTSMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "transportRequestUploadCTS",
Aliases: []config.Alias{},
Description: "Uploads content to a transport request",
},
Spec: config.StepSpec{
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "description",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "endpoint",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
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/client"}},
},
{
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: "abapPackage",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "osDeployUser",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "changeManagement/cts/osDeployUser"}},
},
{
Name: "deployConfigFile",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "cts/deployConfigFile"}},
},
{
Name: "transportRequestId",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "deployToolDependencies",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{{Name: "changeManagement/cts/deployToolDependencies"}},
},
{
Name: "npmInstallOpts",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{{Name: "changeManagement/cts/deployToolDependencies"}},
},
},
},
},
}
return theMetaData
}

View File

@@ -0,0 +1,17 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTransportRequestUploadCTSCommand(t *testing.T) {
t.Parallel()
testCmd := TransportRequestUploadCTSCommand()
// only high level testing performed - details are tested in step generation procedure
assert.Equal(t, "transportRequestUploadCTS", testCmd.Use, "command name incorrect")
}

View File

@@ -0,0 +1,147 @@
package cmd
import (
"fmt"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/transportrequest"
"github.com/stretchr/testify/assert"
"testing"
)
type CTSUploadActionMock struct {
Connection transportrequest.CTSConnection
Application transportrequest.CTSApplication
Node transportrequest.CTSNode
TransportRequestID string
ConfigFile string
DeployUser string
thrown error
}
// WithConnection ...
func (action *CTSUploadActionMock) WithConnection(connection transportrequest.CTSConnection) {
action.Connection = connection
}
// WithApplication ...
func (action *CTSUploadActionMock) WithApplication(app transportrequest.CTSApplication) {
action.Application = app
}
// WithNodeProperties ...
func (action *CTSUploadActionMock) WithNodeProperties(node transportrequest.CTSNode) {
action.Node = node
}
// WithTransportRequestID ...
func (action *CTSUploadActionMock) WithTransportRequestID(id string) {
action.TransportRequestID = id
}
// WithConfigFile ...
func (action *CTSUploadActionMock) WithConfigFile(configFile string) {
action.ConfigFile = configFile
}
// WithDeployUser ...
func (action *CTSUploadActionMock) WithDeployUser(deployUser string) {
action.DeployUser = deployUser
}
func (action *CTSUploadActionMock) Perform(cmd command.ShellRunner) error {
return action.thrown
}
type transportRequestUploadMockUtils struct {
*mock.ShellMockRunner
}
func newTransportRequestUploadCTSTestsUtils() transportRequestUploadMockUtils {
utils := transportRequestUploadMockUtils{
ShellMockRunner: &mock.ShellMockRunner{},
}
return utils
}
func TestRunTransportRequestUploadCTS(t *testing.T) {
t.Parallel()
t.Run("happy path", func(t *testing.T) {
// init
config := transportRequestUploadCTSOptions{
Endpoint: "https://example.org:8000",
Client: "001",
Username: "me",
Password: "********",
ApplicationName: "myApp",
AbapPackage: "myPackage",
Description: "lorem ipsum",
TransportRequestID: "XXXK123456",
OsDeployUser: "node", // default provided in config
DeployConfigFile: "ui5-deploy.yaml", // default provided in config
DeployToolDependencies: []string{"@ui5/cli", "@sap/ux-ui5-tooling"},
NpmInstallOpts: []string{"--verbose", "--registry", "https://registry.example.org/"},
}
actionMock := &CTSUploadActionMock{thrown: nil}
// test
err := runTransportRequestUploadCTS(&config, actionMock, nil, newTransportRequestUploadCTSTestsUtils())
// assert
if assert.NoError(t, err) {
assert.Equal(t, &CTSUploadActionMock{
Connection: transportrequest.CTSConnection{
Endpoint: "https://example.org:8000",
Client: "001",
User: "me",
Password: "********",
},
Application: transportrequest.CTSApplication{
Name: "myApp",
Pack: "myPackage",
Desc: "lorem ipsum",
},
Node: transportrequest.CTSNode{
DeployDependencies: []string{
"@ui5/cli",
"@sap/ux-ui5-tooling",
},
InstallOpts: []string{
"--verbose",
"--registry",
"https://registry.example.org/",
},
},
TransportRequestID: "XXXK123456",
ConfigFile: "ui5-deploy.yaml",
DeployUser: "node",
}, actionMock)
}
})
t.Run("error case", func(t *testing.T) {
config := transportRequestUploadCTSOptions{
Endpoint: "https://example.org:8000",
Client: "001",
Username: "me",
Password: "********",
ApplicationName: "myApp",
AbapPackage: "myPackage",
Description: "lorem ipsum",
TransportRequestID: "XXXK123456",
OsDeployUser: "node", // default provided in config
DeployConfigFile: "ui5-deploy.yaml", // default provided in config
DeployToolDependencies: []string{"@ui5/cli", "@sap/ux-ui5-tooling"},
NpmInstallOpts: []string{"--verbose", "--registry", "https://registry.example.org/"},
}
err := runTransportRequestUploadCTS(
&config,
&CTSUploadActionMock{thrown: fmt.Errorf("something went wrong")},
nil,
newTransportRequestUploadCTSTestsUtils())
assert.EqualError(t, err, "something went wrong")
})
}

View File

@@ -0,0 +1,128 @@
metadata:
name: transportRequestUploadCTS
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: description
type: string
description: "The description of the application. the desription is only taken into account for a new upload. In case of an update the description will not be updated."
default: "Deployed with Piper based on SAP Fiori tools"
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: endpoint
type: string
description: "The service endpoint"
aliases:
- name: changeManagement/endpoint
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: client
type: string
aliases:
- name: changeManagement/client
description: "The ABAP client"
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: username
type: string
mandatory: true
description: "The deploy user"
secret: true
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: password
type: string
mandatory: true
description: "The password for the deploy user"
secret: true
scope:
- PARAMETERS
- name: applicationName
type: string
mandatory: true
description: "The name of the application."
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: abapPackage
type: string
mandatory: true
description: "The ABAP package name of your application"
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: osDeployUser
type: string
default: node
description: "By default we use a standard node docker image and prepare some fiori related packages before performing the deployment. For that we need to launch the image with root privileges. After that, before actually performing the deployment we swith to a non root user. This user can be specified here."
aliases:
- name: changeManagement/cts/osDeployUser
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: deployConfigFile
default: ui5-deploy.yaml
type: string
aliases:
- name: changeManagement/cts/deployConfigFile
description: "The ABAP package name of your application"
aliases:
- name: cts/deployConfigFile
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: transportRequestId
type: string
mandatory: true
description: "The id of the transport request to upload the file. This parameter is only taken into account when provided via signature to the step."
scope:
- PARAMETERS
- name: deployToolDependencies
type: "[]string"
description: "By default we use a standard node docker iamge and prepare some fiori related packages performing the deployment. The additional dependencies can be provided here. In case you use an already prepared docker image which contains the required dependencies, the empty list can be provide here. Caused hereby installing additional dependencies will be skipped."
aliases:
- name: changeManagement/cts/deployToolDependencies
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: npmInstallOpts
type: "[]string"
description: "A list containing additional options for the npm install call. `-g`, `--global` is always assumed. Can be used for e.g. providing custom registries (`--registry https://your.registry.com`) or for providing the verbose flag (`--verbose`) for troubleshooting."
aliases:
- name: changeManagement/cts/deployToolDependencies
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL