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

IntegrationArtifactUpload Command (#2610)

* IntegrationArtifactUpload Command
This commit is contained in:
Mayur Belur Mohan
2021-02-17 14:29:56 +05:30
committed by GitHub
parent 484d9c2ec1
commit 06afce3005
13 changed files with 972 additions and 12 deletions

View File

@@ -113,5 +113,12 @@ func (c *httpMockCpis) SendRequest(method string, url string, r io.Reader, heade
}
return &res, nil
}
if c.CPIFunction == "" {
c.CPIFunction = cpi.GetCPIFunctionNameByURLCheck(url, method, c.TestType)
resp, error := cpi.GetCPIFunctionMockResponse(c.CPIFunction, c.TestType)
c.CPIFunction = ""
return resp, error
}
return cpi.GetCPIFunctionMockResponse(c.CPIFunction, c.TestType)
}

View File

@@ -0,0 +1,206 @@
package cmd
import (
"bytes"
b64 "encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/Jeffail/gabs/v2"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/cpi"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
)
type integrationArtifactUploadUtils interface {
command.ExecRunner
// Add more methods here, or embed additional interfaces, or remove/replace as required.
// The integrationArtifactUploadUtils 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 integrationArtifactUploadUtilsBundle struct {
*command.Command
// Embed more structs as necessary to implement methods or interfaces you add to integrationArtifactUploadUtils.
// 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
// integrationArtifactUploadUtilsBundle and forward to the implementation of the dependency.
}
func newIntegrationArtifactUploadUtils() integrationArtifactUploadUtils {
utils := integrationArtifactUploadUtilsBundle{
Command: &command.Command{},
}
// Reroute command output to logging framework
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}
func integrationArtifactUpload(config integrationArtifactUploadOptions, telemetryData *telemetry.CustomData) {
// Utils can be used wherever the command.ExecRunner interface is expected.
// It can also be used for example as a mavenExecRunner.
httpClient := &piperhttp.Client{}
fileUtils := &piperutils.Files{}
// 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 := runIntegrationArtifactUpload(&config, telemetryData, fileUtils, httpClient)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runIntegrationArtifactUpload(config *integrationArtifactUploadOptions, telemetryData *telemetry.CustomData, fileUtils piperutils.FileUtils, httpClient piperhttp.Sender) error {
clientOptions := piperhttp.ClientOptions{}
header := make(http.Header)
header.Add("Accept", "application/json")
iFlowStatusServiceURL := fmt.Sprintf("%s/api/v1/IntegrationDesigntimeArtifacts(Id='%s',Version='%s')", config.Host, config.IntegrationFlowID, config.IntegrationFlowVersion)
tokenParameters := cpi.TokenParameters{TokenURL: config.OAuthTokenProviderURL, Username: config.Username, Password: config.Password, Client: httpClient}
token, err := cpi.CommonUtils.GetBearerToken(tokenParameters)
if err != nil {
return errors.Wrap(err, "failed to fetch Bearer Token")
}
clientOptions.Token = fmt.Sprintf("Bearer %s", token)
httpClient.SetOptions(clientOptions)
httpMethod := "GET"
//Check availability of integration artefact in CPI design time
iFlowStatusResp, httpErr := httpClient.SendRequest(httpMethod, iFlowStatusServiceURL, nil, header, nil)
if iFlowStatusResp != nil && iFlowStatusResp.Body != nil {
defer iFlowStatusResp.Body.Close()
}
if iFlowStatusResp.StatusCode == 200 {
return UpdateIntegrationArtifact(config, httpClient, fileUtils)
} else if httpErr != nil && iFlowStatusResp.StatusCode == 404 {
return UploadIntegrationArtifact(config, httpClient, fileUtils)
}
if iFlowStatusResp == nil {
return errors.Errorf("did not retrieve a HTTP response: %v", httpErr)
}
if httpErr != nil {
responseBody, readErr := ioutil.ReadAll(iFlowStatusResp.Body)
if readErr != nil {
return errors.Wrapf(readErr, "HTTP response body could not be read, Response status code: %v", iFlowStatusResp.StatusCode)
}
log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code: %v", responseBody, iFlowStatusResp.StatusCode)
return errors.Wrapf(httpErr, "HTTP %v request to %v failed with error: %v", httpMethod, iFlowStatusServiceURL, responseBody)
}
return errors.Errorf("Failed to check integration flow availability, Response Status code: %v", iFlowStatusResp.StatusCode)
}
//UploadIntegrationArtifact - Upload new integration artifact
func UploadIntegrationArtifact(config *integrationArtifactUploadOptions, httpClient piperhttp.Sender, fileUtils piperutils.FileUtils) error {
httpMethod := "POST"
uploadIflowStatusURL := fmt.Sprintf("%s/api/v1/IntegrationDesigntimeArtifacts", config.Host)
header := make(http.Header)
header.Add("content-type", "application/json")
payload, jsonError := GetJSONPayloadAsByteArray(config, "create", fileUtils)
if jsonError != nil {
return errors.Wrapf(jsonError, "Failed to get json payload for file %v, failed with error", config.FilePath)
}
uploadIflowStatusResp, httpErr := httpClient.SendRequest(httpMethod, uploadIflowStatusURL, payload, header, nil)
if uploadIflowStatusResp != nil && uploadIflowStatusResp.Body != nil {
defer uploadIflowStatusResp.Body.Close()
}
if uploadIflowStatusResp == nil {
return errors.Errorf("did not retrieve a HTTP response: %v", httpErr)
}
if uploadIflowStatusResp.StatusCode == http.StatusCreated {
log.Entry().
WithField("IntegrationFlowID", config.IntegrationFlowID).
Info("Successfully created integration flow artefact in CPI designtime")
return nil
}
if httpErr != nil {
responseBody, readErr := ioutil.ReadAll(uploadIflowStatusResp.Body)
if readErr != nil {
return errors.Wrapf(readErr, "HTTP response body could not be read, Response status code: %v", uploadIflowStatusResp.StatusCode)
}
log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code: %v", responseBody, uploadIflowStatusResp.StatusCode)
return errors.Wrapf(httpErr, "HTTP %v request to %v failed with error: %v", httpMethod, uploadIflowStatusURL, responseBody)
}
return errors.Errorf("Failed to create Integration Flow artefact, Response Status code: %v", uploadIflowStatusResp.StatusCode)
}
//UpdateIntegrationArtifact - Update existing integration artifact
func UpdateIntegrationArtifact(config *integrationArtifactUploadOptions, httpClient piperhttp.Sender, fileUtils piperutils.FileUtils) error {
httpMethod := "POST"
header := make(http.Header)
header.Add("content-type", "application/json")
updateIflowStatusURL := fmt.Sprintf("%s/api/v1/IntegrationDesigntimeArtifactSaveAsVersion?Id='%s'&SaveAsVersion='%s'", config.Host, config.IntegrationFlowID, config.IntegrationFlowVersion)
payload, jsonError := GetJSONPayloadAsByteArray(config, "update", fileUtils)
if jsonError != nil {
return errors.Wrapf(jsonError, "Failed to get json payload for file %v, failed with error", config.FilePath)
}
updateIflowStatusResp, httpErr := httpClient.SendRequest(httpMethod, updateIflowStatusURL, payload, header, nil)
if updateIflowStatusResp != nil && updateIflowStatusResp.Body != nil {
defer updateIflowStatusResp.Body.Close()
}
if updateIflowStatusResp == nil {
return errors.Errorf("did not retrieve a HTTP response: %v", httpErr)
}
if updateIflowStatusResp.StatusCode == http.StatusOK {
log.Entry().
WithField("IntegrationFlowID", config.IntegrationFlowID).
Info("Successfully updated integration flow artefact in CPI designtime")
return nil
}
if httpErr != nil {
responseBody, readErr := ioutil.ReadAll(updateIflowStatusResp.Body)
if readErr != nil {
return errors.Wrapf(readErr, "HTTP response body could not be read, Response status code: %v", updateIflowStatusResp.StatusCode)
}
log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code: %v", responseBody, updateIflowStatusResp.StatusCode)
return errors.Wrapf(httpErr, "HTTP %v request to %v failed with error: %v", httpMethod, updateIflowStatusURL, responseBody)
}
return errors.Errorf("Failed to update Integration Flow artefact, Response Status code: %v", updateIflowStatusResp.StatusCode)
}
//GetJSONPayloadAsByteArray -return http payload as byte array
func GetJSONPayloadAsByteArray(config *integrationArtifactUploadOptions, mode string, fileUtils piperutils.FileUtils) (*bytes.Buffer, error) {
fileContent, readError := fileUtils.FileRead(config.FilePath)
if readError != nil {
return nil, errors.Wrapf(readError, "Error reading file")
}
jsonObj := gabs.New()
if mode == "create" {
jsonObj.Set(config.IntegrationFlowID, "Name")
jsonObj.Set(config.IntegrationFlowID, "Id")
jsonObj.Set(config.PackageID, "PackageId")
jsonObj.Set(b64.StdEncoding.EncodeToString(fileContent), "ArtifactContent")
} else if mode == "update" {
jsonObj.Set(b64.StdEncoding.EncodeToString(fileContent), "ArtifactContent")
} else {
return nil, fmt.Errorf("Unkown node: '%s'", mode)
}
jsonBody, jsonErr := json.Marshal(jsonObj)
if jsonErr != nil {
return nil, errors.Wrapf(jsonErr, "json payload is invalid for integration flow artifact %q", config.IntegrationFlowID)
}
return bytes.NewBuffer(jsonBody), nil
}

View File

@@ -0,0 +1,208 @@
// 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 integrationArtifactUploadOptions struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
IntegrationFlowID string `json:"integrationFlowId,omitempty"`
IntegrationFlowVersion string `json:"integrationFlowVersion,omitempty"`
IntegrationFlowName string `json:"integrationFlowName,omitempty"`
PackageID string `json:"packageId,omitempty"`
Host string `json:"host,omitempty"`
OAuthTokenProviderURL string `json:"oAuthTokenProviderUrl,omitempty"`
FilePath string `json:"filePath,omitempty"`
}
// IntegrationArtifactUploadCommand Upload or Update an integration flow designtime artefact
func IntegrationArtifactUploadCommand() *cobra.Command {
const STEP_NAME = "integrationArtifactUpload"
metadata := integrationArtifactUploadMetadata()
var stepConfig integrationArtifactUploadOptions
var startTime time.Time
var createIntegrationArtifactUploadCmd = &cobra.Command{
Use: STEP_NAME,
Short: "Upload or Update an integration flow designtime artefact",
Long: `With this step you can either upload or update a integration flow designtime artifact using the OData API. Learn more about the SAP Cloud Integration remote API for updating an integration flow artifact [here](https://help.sap.com/viewer/368c481cd6954bdfa5d0435479fd4eaf/Cloud/en-US/83733a65c0214aa6acba035e8640bb5a.html).`,
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)
integrationArtifactUpload(stepConfig, &telemetryData)
telemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addIntegrationArtifactUploadFlags(createIntegrationArtifactUploadCmd, &stepConfig)
return createIntegrationArtifactUploadCmd
}
func addIntegrationArtifactUploadFlags(cmd *cobra.Command, stepConfig *integrationArtifactUploadOptions) {
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User to authenticate to the SAP Cloud Platform Integration Service")
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password to authenticate to the SAP Cloud Platform Integration Service")
cmd.Flags().StringVar(&stepConfig.IntegrationFlowID, "integrationFlowId", os.Getenv("PIPER_integrationFlowId"), "Specifies the ID of the Integration Flow artifact")
cmd.Flags().StringVar(&stepConfig.IntegrationFlowVersion, "integrationFlowVersion", os.Getenv("PIPER_integrationFlowVersion"), "Specifies the version of the Integration Flow artifact")
cmd.Flags().StringVar(&stepConfig.IntegrationFlowName, "integrationFlowName", os.Getenv("PIPER_integrationFlowName"), "Specifies the Name of the Integration Flow artifact")
cmd.Flags().StringVar(&stepConfig.PackageID, "packageId", os.Getenv("PIPER_packageId"), "Specifies the ID of the Integration Package")
cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "Specifies the protocol and host address, including the port. Please provide in the format `<protocol>://<host>:<port>`. Supported protocols are `http` and `https`.")
cmd.Flags().StringVar(&stepConfig.OAuthTokenProviderURL, "oAuthTokenProviderUrl", os.Getenv("PIPER_oAuthTokenProviderUrl"), "Specifies the oAuth Provider protocol and host address, including the port. Please provide in the format `<protocol>://<host>:<port>`. Supported protocols are `http` and `https`.")
cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "Specifies integration artifact relative file path.")
cmd.MarkFlagRequired("username")
cmd.MarkFlagRequired("password")
cmd.MarkFlagRequired("integrationFlowId")
cmd.MarkFlagRequired("integrationFlowVersion")
cmd.MarkFlagRequired("integrationFlowName")
cmd.MarkFlagRequired("packageId")
cmd.MarkFlagRequired("host")
cmd.MarkFlagRequired("oAuthTokenProviderUrl")
cmd.MarkFlagRequired("filePath")
}
// retrieve step metadata
func integrationArtifactUploadMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "integrationArtifactUpload",
Aliases: []config.Alias{},
Description: "Upload or Update an integration flow designtime artefact",
},
Spec: config.StepSpec{
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "username",
ResourceRef: []config.ResourceReference{
{
Name: "cpiCredentialsId",
Param: "username",
Type: "secret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "password",
ResourceRef: []config.ResourceReference{
{
Name: "cpiCredentialsId",
Param: "password",
Type: "secret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "integrationFlowId",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "integrationFlowVersion",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "integrationFlowName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "packageId",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "host",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "oAuthTokenProviderUrl",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "filePath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
},
},
},
}
return theMetaData
}

View File

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

View File

@@ -0,0 +1,170 @@
package cmd
import (
"path/filepath"
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/stretchr/testify/assert"
)
type integrationArtifactUploadMockUtils struct {
*mock.ExecMockRunner
*mock.FilesMock
}
func newIntegrationArtifactUploadTestsUtils() integrationArtifactUploadMockUtils {
utils := integrationArtifactUploadMockUtils{
ExecMockRunner: &mock.ExecMockRunner{},
FilesMock: &mock.FilesMock{},
}
return utils
}
func TestRunIntegrationArtifactUpload(t *testing.T) {
t.Parallel()
t.Run("Successfull Integration Flow Create Test", func(t *testing.T) {
filesMock := mock.FilesMock{}
path := filepath.Join("tempDir", "iflow4.zip")
filesMock.AddFile(path, []byte("dummy content"))
exists, err := filesMock.FileExists(path)
assert.NoError(t, err)
assert.True(t, exists)
config := integrationArtifactUploadOptions{
Host: "https://demo",
OAuthTokenProviderURL: "https://demo/oauth/token",
Username: "demouser",
Password: "******",
IntegrationFlowName: "flow4",
IntegrationFlowID: "flow4",
IntegrationFlowVersion: "1.0.4",
PackageID: "CICD",
FilePath: path,
}
httpClient := httpMockCpis{CPIFunction: "", ResponseBody: ``, TestType: "PositiveAndCreateIntegrationDesigntimeArtifactResBody"}
err = runIntegrationArtifactUpload(&config, nil, &filesMock, &httpClient)
if assert.NoError(t, err) {
t.Run("check url", func(t *testing.T) {
assert.Equal(t, "https://demo/api/v1/IntegrationDesigntimeArtifactSaveAsVersion?Id='flow4'&SaveAsVersion='1.0.4'", httpClient.URL)
})
t.Run("check method", func(t *testing.T) {
assert.Equal(t, "POST", httpClient.Method)
})
}
})
t.Run("Successfull Integration Flow Update Test", func(t *testing.T) {
files := mock.FilesMock{}
path := filepath.Join("tempDir", "iflow4.zip")
files.AddFile(path, []byte("dummy content"))
exists, err := files.FileExists(path)
assert.NoError(t, err)
assert.True(t, exists)
config := integrationArtifactUploadOptions{
Host: "https://demo",
OAuthTokenProviderURL: "https://demo/oauth/token",
Username: "demouser",
Password: "******",
IntegrationFlowName: "flow4",
IntegrationFlowID: "flow4",
IntegrationFlowVersion: "1.0.4",
PackageID: "CICD",
FilePath: path,
}
httpClient := httpMockCpis{CPIFunction: "", ResponseBody: ``, TestType: "PositiveAndUpdateIntegrationDesigntimeArtifactResBody"}
err = runIntegrationArtifactUpload(&config, nil, &files, &httpClient)
if assert.NoError(t, err) {
t.Run("check url", func(t *testing.T) {
assert.Equal(t, "https://demo/api/v1/IntegrationDesigntimeArtifacts", httpClient.URL)
})
t.Run("check method", func(t *testing.T) {
assert.Equal(t, "POST", httpClient.Method)
})
}
})
t.Run("Failed case of Integration Flow Get Test", func(t *testing.T) {
config := integrationArtifactUploadOptions{
Host: "https://demo",
OAuthTokenProviderURL: "https://demo/oauth/token",
Username: "demouser",
Password: "******",
IntegrationFlowName: "flow4",
IntegrationFlowID: "flow4",
IntegrationFlowVersion: "1.0.4",
PackageID: "CICD",
FilePath: "path",
}
httpClient := httpMockCpis{CPIFunction: "", ResponseBody: ``, TestType: "NegativeAndGetIntegrationDesigntimeArtifactResBody"}
err := runIntegrationArtifactUpload(&config, nil, nil, &httpClient)
assert.Error(t, err)
})
t.Run("Failed case of Integration Flow Update Test", func(t *testing.T) {
files := mock.FilesMock{}
path := filepath.Join("tempDir", "iflow4.zip")
files.AddFile(path, []byte("dummy content"))
exists, err := files.FileExists(path)
assert.NoError(t, err)
assert.True(t, exists)
config := integrationArtifactUploadOptions{
Host: "https://demo",
OAuthTokenProviderURL: "https://demo/oauth/token",
Username: "demouser",
Password: "******",
IntegrationFlowName: "flow4",
IntegrationFlowID: "flow4",
IntegrationFlowVersion: "1.0.4",
PackageID: "CICD",
FilePath: path,
}
httpClient := httpMockCpis{CPIFunction: "", ResponseBody: ``, TestType: "NegativeAndCreateIntegrationDesigntimeArtifactResBody"}
err = runIntegrationArtifactUpload(&config, nil, &files, &httpClient)
assert.EqualError(t, err, "HTTP POST request to https://demo/api/v1/IntegrationDesigntimeArtifactSaveAsVersion?Id='flow4'&SaveAsVersion='1.0.4' failed with error: []: Internal error")
})
t.Run("Failed case of Integration Flow Create Test", func(t *testing.T) {
filesMock := mock.FilesMock{}
path := filepath.Join("tempDir", "iflow4.zip")
filesMock.AddFile(path, []byte("dummy content"))
exists, err := filesMock.FileExists(path)
assert.NoError(t, err)
assert.True(t, exists)
config := integrationArtifactUploadOptions{
Host: "https://demo",
OAuthTokenProviderURL: "https://demo/oauth/token",
Username: "demouser",
Password: "******",
IntegrationFlowName: "flow4",
IntegrationFlowID: "flow4",
IntegrationFlowVersion: "1.0.4",
PackageID: "CICD",
FilePath: path,
}
httpClient := httpMockCpis{CPIFunction: "", ResponseBody: ``, TestType: "NegativeAndUpdateIntegrationDesigntimeArtifactResBody"}
err = runIntegrationArtifactUpload(&config, nil, &filesMock, &httpClient)
assert.EqualError(t, err, "HTTP POST request to https://demo/api/v1/IntegrationDesigntimeArtifacts failed with error: []: Internal error")
})
}

View File

@@ -50,6 +50,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"integrationArtifactGetMplStatus": integrationArtifactGetMplStatusMetadata(),
"integrationArtifactGetServiceEndpoint": integrationArtifactGetServiceEndpointMetadata(),
"integrationArtifactUpdateConfiguration": integrationArtifactUpdateConfigurationMetadata(),
"integrationArtifactUpload": integrationArtifactUploadMetadata(),
"jsonApplyPatch": jsonApplyPatchMetadata(),
"kanikoExecute": kanikoExecuteMetadata(),
"karmaExecuteTests": karmaExecuteTestsMetadata(),

View File

@@ -132,6 +132,7 @@ func Execute() {
rootCmd.AddCommand(IntegrationArtifactGetMplStatusCommand())
rootCmd.AddCommand(IntegrationArtifactDownloadCommand())
rootCmd.AddCommand(AbapEnvironmentAssembleConfirmCommand())
rootCmd.AddCommand(IntegrationArtifactUploadCommand())
addRootFlags(rootCmd)
if err := rootCmd.Execute(); err != nil {

View File

@@ -0,0 +1,36 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequisites
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}
## Example
Example configuration for the use in a `Jenkinsfile`.
```groovy
integrationArtifactUpload script: this
```
Example for the use in a YAML configuration file (such as `.pipeline/config.yaml`).
```yaml
steps:
<...>
integrationArtifactUpload:
cpiCredentialsId: 'MY_CPI_OAUTH_CREDENTIALSID_IN_JENKINS'
integrationFlowId: 'MY_INTEGRATION_FLOW_ID'
integrationFlowVersion: 'MY_INTEGRATION_FLOW_VERSION'
integrationFlowName: 'MY_INTEGRATION_FLOW_Name'
packageId: 'MY_INTEGRATION_Package_ID'
filePath: 'MY_INTEGRATION_FLOW_Artifact_Relative_Path'
host: https://CPI_HOST_ITSPACES_URL
oAuthTokenProviderUrl: https://CPI_HOST_OAUTH_URL
downloadPath: /MY_INTEGRATION_FLOW_DOWNLOAD_PATH
```

View File

@@ -106,6 +106,7 @@ nav:
- integrationArtifactGetMplStatus: steps/integrationArtifactGetMplStatus.md
- integrationArtifactGetServiceEndpoint: steps/integrationArtifactGetServiceEndpoint.md
- integrationArtifactUpdateConfiguration: steps/integrationArtifactUpdateConfiguration.md
- integrationArtifactUpload: steps/integrationArtifactUpload.md
- jenkinsMaterializeLog: steps/jenkinsMaterializeLog.md
- kanikoExecute: steps/kanikoExecute.md
- karmaExecuteTests: steps/karmaExecuteTests.md

View File

@@ -35,23 +35,25 @@ func GetCPIFunctionMockResponse(functionName, testType string) (*http.Response,
if testType == "Negative_With_ResponseBody" {
return GetNegativeCaseHTTPResponseBodyAndErrorNil()
}
res := http.Response{
StatusCode: 404,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
"code": "Not Found",
"message": {
"@lang": "en",
"#text": "Parameter key 'Parameter1' not found."
}
}`))),
}
return &res, errors.New("Not found - either wrong version for the given Id or wrong parameter key")
return GetParameterKeyMissingResponseBody()
case "IntegrationArtifactGetMplStatus":
return GetIntegrationArtifactGetMplStatusCommandMockResponse(testType)
case "IntegrationArtifactGetServiceEndpoint":
return GetIntegrationArtifactGetServiceEndpointCommandMockResponse(testType)
case "IntegrationArtifactDownload":
return IntegrationArtifactDownloadCommandMockResponse(testType)
case "GetIntegrationDesigntimeArtifact":
return GetIntegrationDesigntimeArtifactMockResponse(testType)
case "UploadIntegrationDesigntimeArtifact":
return GetIntegrationDesigntimeArtifactMockResponse(testType)
case "UploadIntegrationDesigntimeArtifactNegative":
return GetRespBodyHTTPStatusServiceErrorResponse()
case "UpdateIntegrationDesigntimeArtifactNegative":
return GetRespBodyHTTPStatusServiceErrorResponse()
case "UpdateIntegrationDesigntimeArtifact":
return UpdateIntegrationDesigntimeArtifactMockResponse(testType)
case "IntegrationDesigntimeArtifactUpdate":
return IntegrationDesigntimeArtifactUpdateMockResponse(testType)
default:
res := http.Response{
StatusCode: 404,
@@ -70,6 +72,21 @@ func GetEmptyHTTPResponseBodyAndErrorNil() (*http.Response, error) {
return &res, nil
}
//GetParameterKeyMissingResponseBody -Parameter key missing http respose body
func GetParameterKeyMissingResponseBody() (*http.Response, error) {
res := http.Response{
StatusCode: 404,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
"code": "Not Found",
"message": {
"@lang": "en",
"#text": "Parameter key 'Parameter1' not found."
}
}`))),
}
return &res, errors.New("Not found - either wrong version for the given Id or wrong parameter key")
}
//GetNegativeCaseHTTPResponseBodyAndErrorNil -Negative case http respose body
func GetNegativeCaseHTTPResponseBodyAndErrorNil() (*http.Response, error) {
res := http.Response{
@@ -189,11 +206,127 @@ func GetIntegrationArtifactGetServiceEndpointPositiveCaseRespBody() (*http.Respo
return &resp, nil
}
//GetRespBodyHTTPStatusOK -Provide http respose body for Http StatusOK
func GetRespBodyHTTPStatusOK() (*http.Response, error) {
resp := http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader([]byte(``))),
}
return &resp, nil
}
//GetRespBodyHTTPStatusCreated -Provide http respose body for Http StatusOK
func GetRespBodyHTTPStatusCreated() (*http.Response, error) {
resp := http.Response{
StatusCode: 201,
Body: ioutil.NopCloser(bytes.NewReader([]byte(``))),
}
return &resp, nil
}
//GetRespBodyHTTPStatusServiceNotFound -Provide http respose body for Http URL not Found
func GetRespBodyHTTPStatusServiceNotFound() (*http.Response, error) {
resp := http.Response{
StatusCode: 404,
Body: ioutil.NopCloser(bytes.NewReader([]byte(``))),
}
return &resp, errors.New("Integration Package not found")
}
//GetRespBodyHTTPStatusServiceErrorResponse -Provide http respose body for server error
func GetRespBodyHTTPStatusServiceErrorResponse() (*http.Response, error) {
resp := http.Response{
StatusCode: 500,
Body: ioutil.NopCloser(bytes.NewReader([]byte(``))),
}
return &resp, errors.New("Internal error")
}
//IntegrationArtifactDownloadCommandMockResponse -Provide http respose body
func IntegrationArtifactDownloadCommandMockResponse(testType string) (*http.Response, error) {
return GetMockResponseByTestTypeAndMockFunctionName("IntegrationArtifactDownloadCommandMockResponse", testType)
}
//GetIntegrationDesigntimeArtifactMockResponse -Provide http respose body
func GetIntegrationDesigntimeArtifactMockResponse(testType string) (*http.Response, error) {
return GetMockResponseByTestTypeAndMockFunctionName("GetIntegrationDesigntimeArtifactMockResponse", testType)
}
//IntegrationDesigntimeArtifactUpdateMockResponse -Provide http respose body
func IntegrationDesigntimeArtifactUpdateMockResponse(testType string) (*http.Response, error) {
return GetMockResponseByTestTypeAndMockFunctionName("IntegrationDesigntimeArtifactUpdateMockResponse", testType)
}
//GetMockResponseByTestTypeAndMockFunctionName - Get mock response by testtype and mock function name
func GetMockResponseByTestTypeAndMockFunctionName(mockFuntionName, testType string) (*http.Response, error) {
response, error := GetPositiveCaseResponseByTestType(testType)
switch mockFuntionName {
case "IntegrationDesigntimeArtifactUpdateMockResponse":
if response == nil && error == nil {
res := http.Response{
StatusCode: 400,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
"code": "Bad Request",
"message": {
"@lang": "en",
"#text": "invalid request"
}
}`))),
}
return &res, errors.New("Unable to get status of integration artifact, Response Status code:400")
}
case "GetIntegrationDesigntimeArtifactMockResponse":
if response == nil && error == nil {
res := http.Response{
StatusCode: 400,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
"code": "Bad Request",
"message": {
"@lang": "en",
"#text": "invalid request"
}
}`))),
}
return &res, errors.New("Unable to get status of integration artifact, Response Status code:400")
}
case "IntegrationArtifactDownloadCommandMockResponse":
if response == nil && error == nil {
res := http.Response{
StatusCode: 400,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
"code": "Bad Request",
"message": {
"@lang": "en",
"#text": "invalid request"
}
}`))),
}
return &res, errors.New("Unable to download integration artifact, Response Status code:400")
}
}
return response, error
}
//UpdateIntegrationDesigntimeArtifactMockResponse -Provide http respose body
func UpdateIntegrationDesigntimeArtifactMockResponse(testType string) (*http.Response, error) {
response, error := GetRespBodyHTTPStatusCreated()
if response == nil && error == nil {
res := http.Response{
@@ -206,7 +339,7 @@ func IntegrationArtifactDownloadCommandMockResponse(testType string) (*http.Resp
}
}`))),
}
return &res, errors.New("Unable to download integration artifact, Response Status code:400")
return &res, errors.New("Unable to get status of integration artifact, Response Status code:400")
}
return response, error
}
@@ -229,7 +362,80 @@ func GetPositiveCaseResponseByTestType(testType string) (*http.Response, error)
switch testType {
case "PositiveAndGetetIntegrationArtifactDownloadResBody":
return IntegrationArtifactDownloadCommandMockResponsePositiveCaseRespBody()
case "PositiveAndCreateIntegrationDesigntimeArtifactResBody":
return GetRespBodyHTTPStatusOK()
case "NegativeAndCreateIntegrationDesigntimeArtifactResBody":
return GetRespBodyHTTPStatusOK()
case "PositiveAndUpdateIntegrationDesigntimeArtifactResBody":
return GetRespBodyHTTPStatusServiceNotFound()
case "NegativeAndUpdateIntegrationDesigntimeArtifactResBody":
return GetRespBodyHTTPStatusServiceNotFound()
default:
return nil, nil
}
}
//GetCPIFunctionNameByURLCheck - get postive response by test case type
func GetCPIFunctionNameByURLCheck(url, method, testType string) string {
switch url {
case "https://demo/api/v1/IntegrationDesigntimeArtifacts(Id='flow4',Version='1.0.4')":
return GetFunctionNameByTestTypeAndMethod(method, testType)
case "https://demo/api/v1/IntegrationDesigntimeArtifactSaveAsVersion?Id='flow4'&SaveAsVersion='1.0.4'":
return GetFunctionNameByTestTypeAndMethod(method, testType)
case "https://demo/api/v1/IntegrationDesigntimeArtifacts":
return GetFunctionNameByTestTypeAndMethod(method, testType)
default:
return ""
}
}
//GetFunctionNameByTestTypeAndMethod -get function name by test tyep
func GetFunctionNameByTestTypeAndMethod(method, testType string) string {
switch testType {
case "PositiveAndCreateIntegrationDesigntimeArtifactResBody":
if method == "GET" {
return "GetIntegrationDesigntimeArtifact"
}
if method == "POST" {
return "UploadIntegrationDesigntimeArtifact"
}
case "PositiveAndUpdateIntegrationDesigntimeArtifactResBody":
if method == "GET" {
return "IntegrationDesigntimeArtifactUpdate"
}
if method == "POST" {
return "UpdateIntegrationDesigntimeArtifact"
}
case "NegativeAndGetIntegrationDesigntimeArtifactResBody":
if method == "GET" {
return "GetIntegrationDesigntimeArtifact"
}
case "NegativeAndCreateIntegrationDesigntimeArtifactResBody":
if method == "GET" {
return "GetIntegrationDesigntimeArtifact"
}
if method == "POST" {
return "UploadIntegrationDesigntimeArtifactNegative"
}
case "NegativeAndUpdateIntegrationDesigntimeArtifactResBody":
if method == "GET" {
return "GetIntegrationDesigntimeArtifact"
}
if method == "POST" {
return "UpdateIntegrationDesigntimeArtifactNegative"
}
default:
return ""
}
return ""
}

View File

@@ -0,0 +1,95 @@
metadata:
name: integrationArtifactUpload
description: Upload or Update an integration flow designtime artefact
longDescription: |
With this step you can either upload or update a integration flow designtime artifact using the OData API. Learn more about the SAP Cloud Integration remote API for updating an integration flow artifact [here](https://help.sap.com/viewer/368c481cd6954bdfa5d0435479fd4eaf/Cloud/en-US/83733a65c0214aa6acba035e8640bb5a.html).
spec:
inputs:
secrets:
- name: cpiCredentialsId
description: Jenkins credentials ID containing username and password for authentication to the SAP Cloud Platform Integration API's
type: jenkins
params:
- name: username
type: string
description: User to authenticate to the SAP Cloud Platform Integration Service
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
secret: true
resourceRef:
- name: cpiCredentialsId
type: secret
param: username
- name: password
type: string
description: Password to authenticate to the SAP Cloud Platform Integration Service
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
secret: true
resourceRef:
- name: cpiCredentialsId
type: secret
param: password
- name: integrationFlowId
type: string
description: Specifies the ID of the Integration Flow artifact
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: integrationFlowVersion
type: string
description: Specifies the version of the Integration Flow artifact
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: integrationFlowName
type: string
description: Specifies the Name of the Integration Flow artifact
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: packageId
type: string
description: Specifies the ID of the Integration Package
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: host
type: string
description: Specifies the protocol and host address, including the port. Please provide in the format `<protocol>://<host>:<port>`. Supported protocols are `http` and `https`.
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: oAuthTokenProviderUrl
type: string
description: Specifies the oAuth Provider protocol and host address, including the port. Please provide in the format `<protocol>://<host>:<port>`. Supported protocols are `http` and `https`.
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: filePath
type: string
description: Specifies integration artifact relative file path.
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true

View File

@@ -178,6 +178,7 @@ public class CommonStepsTest extends BasePiperTest{
'integrationArtifactGetMplStatus', //implementing new golang pattern without fields
'integrationArtifactGetServiceEndpoint', //implementing new golang pattern without fields
'integrationArtifactDownload', //implementing new golang pattern without fields
'integrationArtifactUpload', //implementing new golang pattern without fields
]
@Test

View File

@@ -0,0 +1,11 @@
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field String METADATA_FILE = 'metadata/integrationArtifactUpload.yaml'
void call(Map parameters = [:]) {
List credentials = [
[type: 'usernamePassword', id: 'cpiCredentialsId', env: ['PIPER_username', 'PIPER_password']]
]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}