diff --git a/cmd/integrationArtifactTransport.go b/cmd/integrationArtifactTransport.go new file mode 100644 index 0000000000..cb37bc3f75 --- /dev/null +++ b/cmd/integrationArtifactTransport.go @@ -0,0 +1,227 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "time" + + "github.com/Jeffail/gabs/v2" + "github.com/SAP/jenkins-library/pkg/apim" + piperhttp "github.com/SAP/jenkins-library/pkg/http" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/pkg/errors" +) + +func integrationArtifactTransport(config integrationArtifactTransportOptions, telemetryData *telemetry.CustomData) { + httpClient := &piperhttp.Client{} + err := runIntegrationArtifactTransport(&config, telemetryData, httpClient) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } +} + +func runIntegrationArtifactTransport(config *integrationArtifactTransportOptions, telemetryData *telemetry.CustomData, httpClient piperhttp.Sender) error { + apimData := apim.Bundle{APIServiceKey: config.CasServiceKey, Client: httpClient} + err := apim.Utils.InitAPIM(&apimData) + if err != nil { + return err + } + return CreateIntegrationArtifactTransportRequest(config, apimData) +} + +//CreateIntegrationArtifactTransportRequest - Create a transport request for Integration Package +func CreateIntegrationArtifactTransportRequest(config *integrationArtifactTransportOptions, apistruct apim.Bundle) error { + httpMethod := http.MethodPost + httpClient := apistruct.Client + createTransportRequestURL := fmt.Sprintf("%s/v1/contentResources/export", apistruct.Host) + header := make(http.Header) + header.Add("content-type", "application/json") + payload, jsonError := GetCPITransportReqPayload(config) + if jsonError != nil { + return errors.Wrapf(jsonError, "Failed to get json payload for file %v, failed with error", config.IntegrationPackageID) + } + + createTransportRequestResp, httpErr := httpClient.SendRequest(httpMethod, createTransportRequestURL, payload, header, nil) + + if httpErr != nil { + return errors.Wrapf(httpErr, "HTTP %v request to %v failed with error", httpMethod, createTransportRequestURL) + } + + if createTransportRequestResp != nil && createTransportRequestResp.Body != nil { + defer createTransportRequestResp.Body.Close() + } + + if createTransportRequestResp == nil { + return errors.Errorf("did not retrieve a HTTP response") + } + + if createTransportRequestResp.StatusCode == http.StatusAccepted { + log.Entry(). + WithField("IntegrationPackageID", config.IntegrationPackageID). + Info("successfully created the integration package transport request") + + bodyText, readErr := ioutil.ReadAll(createTransportRequestResp.Body) + if readErr != nil { + return errors.Wrap(readErr, "HTTP response body could not be read") + } + jsonResponse, parsingErr := gabs.ParseJSON([]byte(bodyText)) + if parsingErr != nil { + return errors.Wrapf(parsingErr, "HTTP response body could not be parsed as JSON: %v", string(bodyText)) + } + processId := jsonResponse.Path("processId").Data().(string) + + if processId != "" { + error := pollTransportStatus(processId, retryCount, config, httpClient, apistruct.Host) + return error + } + return errors.New("Invalid process id") + } + responseBody, readErr := ioutil.ReadAll(createTransportRequestResp.Body) + + if readErr != nil { + return errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", createTransportRequestResp.StatusCode) + } + log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code : %v", string(responseBody), createTransportRequestResp.StatusCode) + return errors.Errorf("integration flow deployment failed, response Status code: %v", createTransportRequestResp.StatusCode) +} + +//pollTransportStatus - Poll the integration package transport processing, return status or error details +func pollTransportStatus(processId string, remainingRetries int, config *integrationArtifactTransportOptions, httpClient piperhttp.Sender, apiHost string) error { + + if remainingRetries <= 0 { + return errors.New("failed to start integration artifact after retrying several times") + } + transportStatus, err := getIntegrationTransportProcessingStatus(config, httpClient, apiHost, processId) + if err != nil { + return err + } + + //with specific delay between each retry + if (transportStatus == "RUNNING") || (transportStatus == "INITIAL") { + // Calling Sleep method + sleepTime := int(retryCount * 3) + time.Sleep(time.Duration(sleepTime) * time.Second) + remainingRetries-- + return pollTransportStatus(processId, retryCount, config, httpClient, apiHost) + } + + //if artifact transport completed, then just return + if transportStatus == "FINISHED" { + return nil + } + + //if error return immediately with error details + if transportStatus == "ERROR" || transportStatus == "ABORTED" { + resp, err := getIntegrationTransportError(config, httpClient, apiHost, processId) + if err != nil { + return err + } + return errors.New(resp) + } + return nil +} + +//GetJSONPayload -return http payload as byte array +func GetCPITransportReqPayload(config *integrationArtifactTransportOptions) (*bytes.Buffer, error) { + jsonObj := gabs.New() + jsonObj.Set(rand.Intn(5000), "id") + jsonObj.Set("MonitoringTeam", "requestor") + jsonObj.Set("1.0.0", "version") + jsonObj.Set("TransportManagementService", "exportMode") + jsonObj.Set("MTAR", "exportMediaType") + jsonObj.Set("Integration Artifact transport request for TransportManagementService", "description") + jsonResourceObj := gabs.New() + jsonResourceObj.Set(config.IntegrationPackageID, "id") + jsonResourceObj.Set(config.ResourceID, "resourceID") + jsonResourceObj.Set("d9c3fe08ceeb47a2991e53049f2ed766", "contentType") + jsonResourceObj.Set("package", "subType") + jsonResourceObj.Set(config.Name, "name") + jsonResourceObj.Set("CloudIntegration", "type") + jsonResourceObj.Set(config.Version, "version") + jsonObj.ArrayAppend(jsonResourceObj, "contentResources") + + jsonBody, jsonErr := json.Marshal(jsonObj) + + if jsonErr != nil { + return nil, errors.Wrapf(jsonErr, "Transport request payload is invalid for integration package artifact %q", config.IntegrationPackageID) + } + return bytes.NewBuffer(jsonBody), nil +} + +//getIntegrationTransportProcessingStatus - Get integration package transport request processing Status +func getIntegrationTransportProcessingStatus(config *integrationArtifactTransportOptions, httpClient piperhttp.Sender, apiHost string, processId string) (string, error) { + httpMethod := "GET" + header := make(http.Header) + header.Add("content-type", "application/json") + header.Add("Accept", "application/json") + transportProcStatusURL := fmt.Sprintf("%s/v1/operations/%s", apiHost, processId) + transportProcStatusResp, httpErr := httpClient.SendRequest(httpMethod, transportProcStatusURL, nil, header, nil) + + if transportProcStatusResp != nil && transportProcStatusResp.Body != nil { + defer transportProcStatusResp.Body.Close() + } + + if transportProcStatusResp == nil { + return "", errors.Errorf("did not retrieve a HTTP response: %v", httpErr) + } + + if (transportProcStatusResp.StatusCode == http.StatusOK) || (transportProcStatusResp.StatusCode == http.StatusAccepted) { + log.Entry(). + WithField("IntegrationPackageID", config.IntegrationPackageID). + Info("successfully processed the integration package transport response status") + + bodyText, readErr := ioutil.ReadAll(transportProcStatusResp.Body) + if readErr != nil { + return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", transportProcStatusResp.StatusCode) + } + jsonResponse, parsingErr := gabs.ParseJSON([]byte(bodyText)) + if parsingErr != nil { + return "", errors.Wrapf(parsingErr, "HTTP response body could not be parsed as JSON: %v", string(bodyText)) + } + contentTransporStatus := jsonResponse.Path("state").Data().(string) + return contentTransporStatus, nil + } + if httpErr != nil { + return getHTTPErrorMessage(httpErr, transportProcStatusResp, httpMethod, transportProcStatusURL) + } + return "", errors.Errorf("failed to get transport request processing status, response Status code: %v", transportProcStatusResp.StatusCode) +} + +//getTransportError - Get integration package transport failures error details +func getIntegrationTransportError(config *integrationArtifactTransportOptions, httpClient piperhttp.Sender, apiHost string, processId string) (string, error) { + httpMethod := "GET" + header := make(http.Header) + header.Add("content-type", "application/json") + errorStatusURL := fmt.Sprintf("%s/v1/operations/%s/logs", apiHost, processId) + errorStatusResp, httpErr := httpClient.SendRequest(httpMethod, errorStatusURL, nil, header, nil) + + if errorStatusResp != nil && errorStatusResp.Body != nil { + defer errorStatusResp.Body.Close() + } + + if errorStatusResp == nil { + return "", errors.Errorf("did not retrieve a HTTP response: %v", httpErr) + } + + if errorStatusResp.StatusCode == http.StatusOK { + log.Entry(). + WithField("IntegrationPackageId", config.IntegrationPackageID). + Info("Successfully retrieved deployment failures error details") + responseBody, readErr := ioutil.ReadAll(errorStatusResp.Body) + if readErr != nil { + return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", errorStatusResp.StatusCode) + } + log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code: %v", string(responseBody), errorStatusResp.StatusCode) + errorDetails := string(responseBody) + return errorDetails, nil + } + if httpErr != nil { + return getHTTPErrorMessage(httpErr, errorStatusResp, httpMethod, errorStatusURL) + } + return "", errors.Errorf("failed to get Integration Package transport error details, response Status code: %v", errorStatusResp.StatusCode) +} diff --git a/cmd/integrationArtifactTransport_generated.go b/cmd/integrationArtifactTransport_generated.go new file mode 100644 index 0000000000..5fd4ae2d04 --- /dev/null +++ b/cmd/integrationArtifactTransport_generated.go @@ -0,0 +1,203 @@ +// 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/SAP/jenkins-library/pkg/validation" + "github.com/spf13/cobra" +) + +type integrationArtifactTransportOptions struct { + CasServiceKey string `json:"casServiceKey,omitempty"` + IntegrationPackageID string `json:"integrationPackageId,omitempty"` + ResourceID string `json:"resourceID,omitempty"` + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` +} + +// IntegrationArtifactTransportCommand Integration Package transport using the SAP Content Agent Service +func IntegrationArtifactTransportCommand() *cobra.Command { + const STEP_NAME = "integrationArtifactTransport" + + metadata := integrationArtifactTransportMetadata() + var stepConfig integrationArtifactTransportOptions + var startTime time.Time + var logCollector *log.CollectorHook + var splunkClient *splunk.Splunk + telemetryClient := &telemetry.Telemetry{} + + var createIntegrationArtifactTransportCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "Integration Package transport using the SAP Content Agent Service", + Long: `With this step you can trigger an Integration Package transport from SAP Integration Suite using SAP Content Agent Service and SAP Cloud Transport Management Service. For more information about doing an Integration Package transport using SAP Content Agent Service see the documentation [here](https://help.sap.com/docs/CONTENT_AGENT_SERVICE/ae1a4f2d150d468d9ff56e13f9898e07/8e274fdd41da45a69ff919c0af8c6127.html).`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + startTime = time.Now() + log.SetStepName(STEP_NAME) + log.SetVerbose(GeneralConfig.Verbose) + + GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens) + + 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.CasServiceKey) + + 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 { + splunkClient = &splunk.Splunk{} + logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID} + log.RegisterHook(logCollector) + } + + if err = log.RegisterANSHookIfConfigured(GeneralConfig.CorrelationID); err != nil { + log.Entry().WithError(err).Warn("failed to set up SAP Alert Notification Service log hook") + } + + validation, err := validation.New(validation.WithJSONNamesForStructFields(), validation.WithPredefinedErrorMessages()) + if err != nil { + return err + } + if err = validation.ValidateStruct(stepConfig); err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return err + } + + return nil + }, + Run: func(_ *cobra.Command, _ []string) { + stepTelemetryData := telemetry.CustomData{} + stepTelemetryData.ErrorCode = "1" + handler := func() { + config.RemoveVaultSecretFiles() + stepTelemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) + stepTelemetryData.ErrorCategory = log.GetErrorCategory().String() + stepTelemetryData.PiperCommitHash = GitCommit + telemetryClient.SetData(&stepTelemetryData) + telemetryClient.Send() + if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { + splunkClient.Send(telemetryClient.GetData(), logCollector) + } + } + log.DeferExitHandler(handler) + defer handler() + telemetryClient.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) + if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { + splunkClient.Initialize(GeneralConfig.CorrelationID, + GeneralConfig.HookConfig.SplunkConfig.Dsn, + GeneralConfig.HookConfig.SplunkConfig.Token, + GeneralConfig.HookConfig.SplunkConfig.Index, + GeneralConfig.HookConfig.SplunkConfig.SendLogs) + } + integrationArtifactTransport(stepConfig, &stepTelemetryData) + stepTelemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addIntegrationArtifactTransportFlags(createIntegrationArtifactTransportCmd, &stepConfig) + return createIntegrationArtifactTransportCmd +} + +func addIntegrationArtifactTransportFlags(cmd *cobra.Command, stepConfig *integrationArtifactTransportOptions) { + cmd.Flags().StringVar(&stepConfig.CasServiceKey, "casServiceKey", os.Getenv("PIPER_casServiceKey"), "Service key JSON string to access the CAS service instance") + cmd.Flags().StringVar(&stepConfig.IntegrationPackageID, "integrationPackageId", os.Getenv("PIPER_integrationPackageId"), "Specifies the ID of the integration package artifact.") + cmd.Flags().StringVar(&stepConfig.ResourceID, "resourceID", os.Getenv("PIPER_resourceID"), "Specifies the technical ID of the integration package artifact.") + cmd.Flags().StringVar(&stepConfig.Name, "name", os.Getenv("PIPER_name"), "Specifies the name of the integration package artifact.") + cmd.Flags().StringVar(&stepConfig.Version, "version", os.Getenv("PIPER_version"), "Specifies the version of the Integration Package artifact.") + + cmd.MarkFlagRequired("casServiceKey") + cmd.MarkFlagRequired("integrationPackageId") + cmd.MarkFlagRequired("resourceID") + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("version") +} + +// retrieve step metadata +func integrationArtifactTransportMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "integrationArtifactTransport", + Aliases: []config.Alias{}, + Description: "Integration Package transport using the SAP Content Agent Service", + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Secrets: []config.StepSecrets{ + {Name: "casApiServiceKeyCredentialsId", Description: "Jenkins secret text credential ID containing the service key to the CAS service instance", Type: "jenkins"}, + }, + Parameters: []config.StepParameters{ + { + Name: "casServiceKey", + ResourceRef: []config.ResourceReference{ + { + Name: "casApiServiceKeyCredentialsId", + Param: "casServiceKey", + Type: "secret", + }, + }, + Scope: []string{"PARAMETERS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_casServiceKey"), + }, + { + Name: "integrationPackageId", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "GENERAL", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_integrationPackageId"), + }, + { + Name: "resourceID", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "GENERAL", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_resourceID"), + }, + { + Name: "name", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "GENERAL", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_name"), + }, + { + Name: "version", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "GENERAL", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_version"), + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/integrationArtifactTransport_generated_test.go b/cmd/integrationArtifactTransport_generated_test.go new file mode 100644 index 0000000000..536293f259 --- /dev/null +++ b/cmd/integrationArtifactTransport_generated_test.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIntegrationArtifactTransportCommand(t *testing.T) { + t.Parallel() + + testCmd := IntegrationArtifactTransportCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "integrationArtifactTransport", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/integrationArtifactTransport_test.go b/cmd/integrationArtifactTransport_test.go new file mode 100644 index 0000000000..40088a01c8 --- /dev/null +++ b/cmd/integrationArtifactTransport_test.go @@ -0,0 +1,128 @@ +package cmd + +import ( + "fmt" + "testing" + + "github.com/SAP/jenkins-library/pkg/apim" + apimhttp "github.com/SAP/jenkins-library/pkg/apim" + "github.com/SAP/jenkins-library/pkg/mock" + "github.com/stretchr/testify/assert" +) + +type integrationArtifactTransportMockUtils struct { + *mock.ExecMockRunner + *mock.FilesMock +} + +func newIntegrationArtifactTransportTestsUtils() integrationArtifactTransportMockUtils { + utils := integrationArtifactTransportMockUtils{ + ExecMockRunner: &mock.ExecMockRunner{}, + FilesMock: &mock.FilesMock{}, + } + return utils +} + +func TestRunIntegrationArtifactTransport(t *testing.T) { + t.Parallel() + + t.Run("Create Transport Request Successful test", func(t *testing.T) { + config := getDefaultOptionsForIntegrationArtifactTransport() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 202, ResponseBody: `{"processId": "100", "state": "FINISHED"}`} + apim := apim.Bundle{APIServiceKey: config.CasServiceKey, Client: httpClientMock} + // test + err := CreateIntegrationArtifactTransportRequest(&config, apim) + // assert + if assert.NoError(t, err) { + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "/v1/operations/100", httpClientMock.URL) + }) + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClientMock.Method) + }) + } + }) + + t.Run("getIntegrationTransportProcessingStatus successful test", func(t *testing.T) { + config := getDefaultOptionsForIntegrationArtifactTransport() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 200, ResponseBody: `{"state": "FINISHED"}`} + // test + resp, err := getIntegrationTransportProcessingStatus(&config, httpClientMock, "demo", "100") + + // assert + assert.Equal(t, "FINISHED", resp) + assert.NoError(t, err) + }) + + t.Run("getIntegrationTransportError successful test", func(t *testing.T) { + config := getDefaultOptionsForIntegrationArtifactTransport() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 200, ResponseBody: `{ "logs": [] }`} + // test + resp, err := getIntegrationTransportError(&config, httpClientMock, "demo", "100") + + // assert + assert.Equal(t, "{ \"logs\": [] }", resp) + // assert + if assert.NoError(t, err) { + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "demo/v1/operations/100/logs", httpClientMock.URL) + }) + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClientMock.Method) + }) + } + }) + + t.Run("GetCPITransportReqPayload successful test", func(t *testing.T) { + config := getDefaultOptionsForIntegrationArtifactTransport() + // test + resp, err := GetCPITransportReqPayload(&config) + fmt.Println(resp.String()) + // assert + expJson := `{"contentType":"d9c3fe08ceeb47a2991e53049f2ed766","id":"TestTransport","name":"TestTransport","resourceID":"d9c3fe08ceeb47a2991e53049f2ed766","subType":"package","type":"CloudIntegration","version":"1.0"}` + actJson := resp.String() + assert.Contains(t, actJson, expJson) + assert.NoError(t, err) + }) + + t.Run("Create Transport Request negative test1", func(t *testing.T) { + config := getDefaultOptionsForIntegrationArtifactTransport() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 202, ResponseBody: `{"processId": ""}`} + apim := apim.Bundle{APIServiceKey: config.CasServiceKey, Client: httpClientMock} + // test + err := CreateIntegrationArtifactTransportRequest(&config, apim) + assert.Equal(t, "/v1/contentResources/export", httpClientMock.URL) + assert.Equal(t, "POST", httpClientMock.Method) + assert.Error(t, err) + }) + + t.Run("Create Transport Request negative test2", func(t *testing.T) { + config := getDefaultOptionsForIntegrationArtifactTransport() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 400, ResponseBody: ``} + apim := apim.Bundle{APIServiceKey: config.CasServiceKey, Client: httpClientMock} + // test + err := CreateIntegrationArtifactTransportRequest(&config, apim) + assert.EqualError(t, err, "HTTP POST request to /v1/contentResources/export failed with error: Bad Request") + }) + +} + +func getDefaultOptionsForIntegrationArtifactTransport() integrationArtifactTransportOptions { + + apiServiceKey := `{ + "oauth": { + "url": "https://demo", + "clientid": "sb-2d0622c9", + "clientsecret": "edb5c506=", + "tokenurl": "https://demo/oauth/token" + } + }` + + return integrationArtifactTransportOptions{ + CasServiceKey: apiServiceKey, + IntegrationPackageID: "TestTransport", + ResourceID: "d9c3fe08ceeb47a2991e53049f2ed766", + Name: "TestTransport", + Version: "1.0", + } +} diff --git a/cmd/metadata_generated.go b/cmd/metadata_generated.go index fef58b1539..ef031729da 100644 --- a/cmd/metadata_generated.go +++ b/cmd/metadata_generated.go @@ -75,6 +75,7 @@ func GetAllStepMetadata() map[string]config.StepData { "integrationArtifactGetMplStatus": integrationArtifactGetMplStatusMetadata(), "integrationArtifactGetServiceEndpoint": integrationArtifactGetServiceEndpointMetadata(), "integrationArtifactResource": integrationArtifactResourceMetadata(), + "integrationArtifactTransport": integrationArtifactTransportMetadata(), "integrationArtifactTriggerIntegrationTest": integrationArtifactTriggerIntegrationTestMetadata(), "integrationArtifactUnDeploy": integrationArtifactUnDeployMetadata(), "integrationArtifactUpdateConfiguration": integrationArtifactUpdateConfigurationMetadata(), diff --git a/cmd/piper.go b/cmd/piper.go index 7c80f5ff76..2dafcc2775 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -192,6 +192,7 @@ func Execute() { rootCmd.AddCommand(AnsSendEventCommand()) rootCmd.AddCommand(ApiProviderListCommand()) rootCmd.AddCommand(TmsUploadCommand()) + rootCmd.AddCommand(IntegrationArtifactTransportCommand()) addRootFlags(rootCmd) diff --git a/documentation/docs/steps/integrationArtifactTransport.md b/documentation/docs/steps/integrationArtifactTransport.md new file mode 100644 index 0000000000..3abf43e65b --- /dev/null +++ b/documentation/docs/steps/integrationArtifactTransport.md @@ -0,0 +1,45 @@ +# ${docGenStepName} + +## ${docGenDescription} + +With this step, you can transport Integration Packages from SAP Integration Suite across various landscapes using SAP Content Agent Service. + +SAP Integration Suite provides the ability to transport its content to other services. SAP Content Agent service enables you to assemble the content from various content providers (including SAP Integration Suite) in MTAR format. Later, this content is either available for download or can be exported to a configured transport queue, such as SAP Cloud Transport Management. This step, integrationArtifactTransport, only supports transporting Integration Packages from SAP Integration Suite. For more information on +configurations required for SAP Integration Suite, see [Content Assembly for SAP Integration Suite](https://help.sap.com/docs/CONTENT_AGENT_SERVICE/ae1a4f2d150d468d9ff56e13f9898e07/8e274fdd41da45a69ff919c0af8c6127.html) + +To use the integrationArtifactTransport step, proceed as follows: + +* [Create SAP Content Agent Service Destination](https://help.sap.com/docs/CONTENT_AGENT_SERVICE/ae1a4f2d150d468d9ff56e13f9898e07/a4da0c26ced74bbfbc60e7f607dc05ab.html). +* [Create Cloud Integration Destination](https://help.sap.com/docs/CONTENT_AGENT_SERVICE/ae1a4f2d150d468d9ff56e13f9898e07/c17c4004049d4d9dba373d72ce5610cd.html). +* [Create SAP Cloud Transport Management Destination](https://help.sap.com/docs/CONTENT_AGENT_SERVICE/ae1a4f2d150d468d9ff56e13f9898e07/b44463a657fa4be48ea2525b7eb6e7de.html). +* Transport Cloud Integration Content with SAP Content Agent Service as explained in the blog [TMS – Transport SAP Cloud Integration (CI/CPI) Content with Transport Management Service (TMS) and Content Agent Service (CAS)](https://blogs.sap.com/2022/03/25/transport-sap-cloud-integration-ci-cpi-content-with-transport-management-service-tms-and-content-agent-service-cas/) +* integrationArtifactTransport step only supports Integration Package transport + +## Prerequisites + +## ${docGenParameters} + +## ${docGenConfiguration} + +## ${docJenkinsPluginDependencies} + +## Example + +Configuration example for a `Jenkinsfile`: + +```groovy +integrationArtifactTransport script: this +``` + +Configuration example for a YAML file (for example `.pipeline/config.yaml`): + +```yaml +steps: + <...> + integrationArtifactTransport: + casApiServiceKeyCredentialsId: 'MY_API_SERVICE_KEY' + integrationPackageId: MY_INTEGRATION_PACKAGE_ID + resourceID: MY_INTEGRATION_RESOURCE_ID + name: MY_INTEGRATION_PACKAGE_NAME + version: MY_INTEGRATION_PACKAGE_VERSION +``` diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 47e8478a95..aed9c15632 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -126,6 +126,7 @@ nav: - integrationArtifactGetMplStatus: steps/integrationArtifactGetMplStatus.md - integrationArtifactGetServiceEndpoint: steps/integrationArtifactGetServiceEndpoint.md - integrationArtifactResource: steps/integrationArtifactResource.md + - integrationArtifactTransport: steps/integrationArtifactTransport.md - integrationArtifactUnDeploy: steps/integrationArtifactUnDeploy.md - integrationArtifactUpdateConfiguration: steps/integrationArtifactUpdateConfiguration.md - integrationArtifactUpload: steps/integrationArtifactUpload.md diff --git a/resources/metadata/integrationArtifactTransport.yaml b/resources/metadata/integrationArtifactTransport.yaml new file mode 100644 index 0000000000..cf6eeeda9b --- /dev/null +++ b/resources/metadata/integrationArtifactTransport.yaml @@ -0,0 +1,60 @@ +metadata: + name: integrationArtifactTransport + description: Integration Package transport using the SAP Content Agent Service + longDescription: | + With this step you can trigger an Integration Package transport from SAP Integration Suite using SAP Content Agent Service and SAP Cloud Transport Management Service. For more information about doing an Integration Package transport using SAP Content Agent Service see the documentation [here](https://help.sap.com/docs/CONTENT_AGENT_SERVICE/ae1a4f2d150d468d9ff56e13f9898e07/8e274fdd41da45a69ff919c0af8c6127.html). + +spec: + inputs: + secrets: + - name: casApiServiceKeyCredentialsId + description: Jenkins secret text credential ID containing the service key to the CAS service instance + type: jenkins + params: + - name: casServiceKey + type: string + description: Service key JSON string to access the CAS service instance + scope: + - PARAMETERS + mandatory: true + secret: true + resourceRef: + - name: casApiServiceKeyCredentialsId + type: secret + param: casServiceKey + - name: integrationPackageId + type: string + description: Specifies the ID of the integration package artifact. + scope: + - PARAMETERS + - GENERAL + - STAGES + - STEPS + mandatory: true + - name: resourceID + type: string + description: Specifies the technical ID of the integration package artifact. + scope: + - PARAMETERS + - GENERAL + - STAGES + - STEPS + mandatory: true + - name: name + type: string + description: Specifies the name of the integration package artifact. + scope: + - PARAMETERS + - GENERAL + - STAGES + - STEPS + mandatory: true + - name: version + type: string + description: Specifies the version of the Integration Package artifact. + scope: + - PARAMETERS + - GENERAL + - STAGES + - STEPS + mandatory: true diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index 1e0c1a1b67..57417d810a 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -193,6 +193,7 @@ public class CommonStepsTest extends BasePiperTest{ 'integrationArtifactGetServiceEndpoint', //implementing new golang pattern without fields 'integrationArtifactDownload', //implementing new golang pattern without fields 'integrationArtifactUpload', //implementing new golang pattern without fields + 'integrationArtifactTransport', //implementing new golang pattern without fields 'integrationArtifactTriggerIntegrationTest', //implementing new golang pattern without fields 'integrationArtifactUnDeploy', //implementing new golang pattern without fields 'integrationArtifactResource', //implementing new golang pattern without fields @@ -223,7 +224,7 @@ public class CommonStepsTest extends BasePiperTest{ 'azureBlobUpload', 'awsS3Upload', 'ansSendEvent', - 'apiProviderList', //implementing new golang pattern without fields + 'apiProviderList', //implementing new golang pattern without fields ] @Test diff --git a/vars/integrationArtifactTransport.groovy b/vars/integrationArtifactTransport.groovy new file mode 100644 index 0000000000..975cac72f3 --- /dev/null +++ b/vars/integrationArtifactTransport.groovy @@ -0,0 +1,11 @@ +import groovy.transform.Field + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/integrationArtifactTransport.yaml' + +void call(Map parameters = [:]) { + List credentials = [ + [type: 'token', id: 'casApiServiceKeyCredentialsId', env: ['PIPER_casServiceKey']] + ] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) +}