diff --git a/cmd/integrationArtifactUnDeploy.go b/cmd/integrationArtifactUnDeploy.go new file mode 100644 index 000000000..7a72805ff --- /dev/null +++ b/cmd/integrationArtifactUnDeploy.go @@ -0,0 +1,74 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "net/http" + + "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/telemetry" + "github.com/pkg/errors" +) + +func integrationArtifactUnDeploy(config integrationArtifactUnDeployOptions, 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{} + // 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 := runIntegrationArtifactUnDeploy(&config, telemetryData, httpClient) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } +} + +func runIntegrationArtifactUnDeploy(config *integrationArtifactUnDeployOptions, telemetryData *telemetry.CustomData, httpClient piperhttp.Sender) error { + clientOptions := piperhttp.ClientOptions{} + header := make(http.Header) + header.Add("Accept", "application/json") + serviceKey, err := cpi.ReadCpiServiceKey(config.APIServiceKey) + if err != nil { + return err + } + unDeployURL := fmt.Sprintf("%s/api/v1/IntegrationRuntimeArtifacts('%s')", serviceKey.OAuth.Host, config.IntegrationFlowID) + tokenParameters := cpi.TokenParameters{TokenURL: serviceKey.OAuth.OAuthTokenProviderURL, Username: serviceKey.OAuth.ClientID, Password: serviceKey.OAuth.ClientSecret, 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 := "DELETE" + unDeployResp, httpErr := httpClient.SendRequest(httpMethod, unDeployURL, nil, header, nil) + if httpErr != nil { + return errors.Wrapf(httpErr, "HTTP %v request to %v failed with error", httpMethod, unDeployURL) + } + + if unDeployResp != nil && unDeployResp.Body != nil { + defer unDeployResp.Body.Close() + } + + if unDeployResp == nil { + return errors.Errorf("did not retrieve a HTTP response") + } + + if unDeployResp.StatusCode == http.StatusAccepted { + log.Entry(). + WithField("IntegrationFlowID", config.IntegrationFlowID). + Info("successfully undeployed from integration runtime") + return nil + } + responseBody, readErr := ioutil.ReadAll(unDeployResp.Body) + + if readErr != nil { + return errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", unDeployResp.StatusCode) + } + log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code : %v", responseBody, unDeployResp.StatusCode) + return errors.Errorf("integration flow undeployment failed, response Status code: %v", unDeployResp.StatusCode) +} diff --git a/cmd/integrationArtifactUnDeploy_generated.go b/cmd/integrationArtifactUnDeploy_generated.go new file mode 100644 index 000000000..7d0dec2f5 --- /dev/null +++ b/cmd/integrationArtifactUnDeploy_generated.go @@ -0,0 +1,148 @@ +// 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 integrationArtifactUnDeployOptions struct { + APIServiceKey string `json:"apiServiceKey,omitempty"` + IntegrationFlowID string `json:"integrationFlowId,omitempty"` +} + +// IntegrationArtifactUnDeployCommand Undeploy a integration flow +func IntegrationArtifactUnDeployCommand() *cobra.Command { + const STEP_NAME = "integrationArtifactUnDeploy" + + metadata := integrationArtifactUnDeployMetadata() + var stepConfig integrationArtifactUnDeployOptions + var startTime time.Time + var logCollector *log.CollectorHook + + var createIntegrationArtifactUnDeployCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "Undeploy a integration flow", + Long: `With this step you can undeploy a integration flow artifact in SAP Cloud integration runtime using OData API. Learn more about the SAP Cloud Integration remote API for undeploying an integration artifact [here](https://help.sap.com/viewer/368c481cd6954bdfa5d0435479fd4eaf/Cloud/en-US/d1679a80543f46509a7329243b595bdb.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.APIServiceKey) + + 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) + } + integrationArtifactUnDeploy(stepConfig, &telemetryData) + telemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addIntegrationArtifactUnDeployFlags(createIntegrationArtifactUnDeployCmd, &stepConfig) + return createIntegrationArtifactUnDeployCmd +} + +func addIntegrationArtifactUnDeployFlags(cmd *cobra.Command, stepConfig *integrationArtifactUnDeployOptions) { + cmd.Flags().StringVar(&stepConfig.APIServiceKey, "apiServiceKey", os.Getenv("PIPER_apiServiceKey"), "Service key JSON string to access the Process Integration Runtime service instance of plan 'api'") + cmd.Flags().StringVar(&stepConfig.IntegrationFlowID, "integrationFlowId", os.Getenv("PIPER_integrationFlowId"), "Specifies the ID of the Integration Flow artifact") + + cmd.MarkFlagRequired("apiServiceKey") + cmd.MarkFlagRequired("integrationFlowId") +} + +// retrieve step metadata +func integrationArtifactUnDeployMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "integrationArtifactUnDeploy", + Aliases: []config.Alias{}, + Description: "Undeploy a integration flow", + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Secrets: []config.StepSecrets{ + {Name: "cpiApiServiceKeyCredentialsId", Description: "Jenkins secret text credential ID containing the service key to the Process Integration Runtime service instance of plan 'api'", Type: "jenkins"}, + }, + Parameters: []config.StepParameters{ + { + Name: "apiServiceKey", + ResourceRef: []config.ResourceReference{ + { + Name: "cpiApiServiceKeyCredentialsId", + Param: "apiServiceKey", + Type: "secret", + }, + }, + Scope: []string{"PARAMETERS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_apiServiceKey"), + }, + { + Name: "integrationFlowId", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "GENERAL", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_integrationFlowId"), + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/integrationArtifactUnDeploy_generated_test.go b/cmd/integrationArtifactUnDeploy_generated_test.go new file mode 100644 index 000000000..f66b8848a --- /dev/null +++ b/cmd/integrationArtifactUnDeploy_generated_test.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIntegrationArtifactUnDeployCommand(t *testing.T) { + t.Parallel() + + testCmd := IntegrationArtifactUnDeployCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "integrationArtifactUnDeploy", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/integrationArtifactUnDeploy_test.go b/cmd/integrationArtifactUnDeploy_test.go new file mode 100644 index 000000000..6928085ec --- /dev/null +++ b/cmd/integrationArtifactUnDeploy_test.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "github.com/SAP/jenkins-library/pkg/mock" + "github.com/stretchr/testify/assert" + "testing" +) + +type integrationArtifactUnDeployMockUtils struct { + *mock.ExecMockRunner + *mock.FilesMock +} + +func TestRunIntegrationArtifactUnDeploy(t *testing.T) { + t.Parallel() + + t.Run("Successful undeploy of integration flow test", func(t *testing.T) { + + apiServiceKey := `{ + "oauth": { + "url": "https://demo", + "clientid": "demouser", + "clientsecret": "******", + "tokenurl": "https://demo/oauth/token" + } + }` + config := integrationArtifactUnDeployOptions{ + APIServiceKey: apiServiceKey, + IntegrationFlowID: "flow1", + } + httpClient := httpMockCpis{CPIFunction: "PositiveAndUnDeployIntegrationDesigntimeArtifact", ResponseBody: ``, TestType: "Positive"} + + // test + err := runIntegrationArtifactUnDeploy(&config, nil, &httpClient) + + // assert + assert.NoError(t, err) + }) + + t.Run("Failed undeploy of integration flow test", func(t *testing.T) { + apiServiceKey := `{ + "oauth": { + "url": "https://demo", + "clientid": "demouser", + "clientsecret": "******", + "tokenurl": "https://demo/oauth/token" + } + }` + config := integrationArtifactUnDeployOptions{ + APIServiceKey: apiServiceKey, + IntegrationFlowID: "flow1", + } + + httpClient := httpMockCpis{CPIFunction: "FailedIntegrationRuntimeArtifactUnDeployment", ResponseBody: ``, TestType: "Negative"} + + // test + err := runIntegrationArtifactUnDeploy(&config, nil, &httpClient) + + // assert + assert.EqualError(t, err, "integration flow undeployment failed, response Status code: 400") + }) +} diff --git a/cmd/metadata_generated.go b/cmd/metadata_generated.go index 4cacc7831..c10ba1a94 100644 --- a/cmd/metadata_generated.go +++ b/cmd/metadata_generated.go @@ -53,6 +53,7 @@ func GetAllStepMetadata() map[string]config.StepData { "integrationArtifactGetMplStatus": integrationArtifactGetMplStatusMetadata(), "integrationArtifactGetServiceEndpoint": integrationArtifactGetServiceEndpointMetadata(), "integrationArtifactTriggerIntegrationTest": integrationArtifactTriggerIntegrationTestMetadata(), + "integrationArtifactUnDeploy": integrationArtifactUnDeployMetadata(), "integrationArtifactUpdateConfiguration": integrationArtifactUpdateConfigurationMetadata(), "integrationArtifactUpload": integrationArtifactUploadMetadata(), "jsonApplyPatch": jsonApplyPatchMetadata(), diff --git a/cmd/piper.go b/cmd/piper.go index 62d079b71..5b88ad4b7 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -149,6 +149,7 @@ func Execute() { rootCmd.AddCommand(AbapEnvironmentAssembleConfirmCommand()) rootCmd.AddCommand(IntegrationArtifactUploadCommand()) rootCmd.AddCommand(IntegrationArtifactTriggerIntegrationTestCommand()) + rootCmd.AddCommand(IntegrationArtifactUnDeployCommand()) rootCmd.AddCommand(TerraformExecuteCommand()) rootCmd.AddCommand(ContainerExecuteStructureTestsCommand()) rootCmd.AddCommand(GaugeExecuteTestsCommand()) diff --git a/documentation/docs/steps/integrationArtifactUnDeploy.md b/documentation/docs/steps/integrationArtifactUnDeploy.md new file mode 100644 index 000000000..590b15449 --- /dev/null +++ b/documentation/docs/steps/integrationArtifactUnDeploy.md @@ -0,0 +1,29 @@ +# ${docGenStepName} + +## ${docGenDescription} + +## Prerequisites + +## ${docGenParameters} + +## ${docGenConfiguration} + +## ${docJenkinsPluginDependencies} + +## Example + +Example configuration for the use in a `Jenkinsfile`. + +```groovy +integrationArtifactUnDeploy script: this +``` + +Example of a YAML configuration file (such as `.pipeline/config.yaml`). + +```yaml +steps: + <...> + integrationArtifactUnDeploy: + cpiApiServiceKeyCredentialsId: 'MY_API_SERVICE_KEY' + integrationFlowId: 'MY_INTEGRATION_FLOW_ID' +``` diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index a61c0943d..6fd208f1d 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -107,6 +107,7 @@ nav: - integrationArtifactDownload: steps/integrationArtifactDownload.md - integrationArtifactGetMplStatus: steps/integrationArtifactGetMplStatus.md - integrationArtifactGetServiceEndpoint: steps/integrationArtifactGetServiceEndpoint.md + - integrationArtifactUnDeploy: steps/integrationArtifactUnDeploy.md - integrationArtifactUpdateConfiguration: steps/integrationArtifactUpdateConfiguration.md - integrationArtifactUpload: steps/integrationArtifactUpload.md - jenkinsMaterializeLog: steps/jenkinsMaterializeLog.md diff --git a/pkg/cpi/mockingUtils.go b/pkg/cpi/mockingUtils.go index a13c0aa26..541c532aa 100644 --- a/pkg/cpi/mockingUtils.go +++ b/pkg/cpi/mockingUtils.go @@ -14,9 +14,9 @@ import ( //GetCPIFunctionMockResponse -Generate mock response payload for different CPI functions func GetCPIFunctionMockResponse(functionName, testType string) (*http.Response, error) { switch functionName { - case "IntegrationArtifactDeploy": + case "IntegrationArtifactDeploy", "PositiveAndUnDeployIntegrationDesigntimeArtifact": return GetEmptyHTTPResponseBodyAndErrorNil() - case "FailIntegrationDesigntimeArtifactDeployment": + case "FailIntegrationDesigntimeArtifactDeployment", "FailedIntegrationRuntimeArtifactUnDeployment": return GetNegativeCaseHTTPResponseBodyAndErrorNil() case "IntegrationArtifactUpdateConfiguration": if testType == "Positive" { diff --git a/resources/metadata/integrationArtifactUnDeploy.yaml b/resources/metadata/integrationArtifactUnDeploy.yaml new file mode 100644 index 000000000..f575e09ad --- /dev/null +++ b/resources/metadata/integrationArtifactUnDeploy.yaml @@ -0,0 +1,33 @@ +metadata: + name: integrationArtifactUnDeploy + description: Undeploy a integration flow + longDescription: | + With this step you can undeploy a integration flow artifact in SAP Cloud integration runtime using OData API. Learn more about the SAP Cloud Integration remote API for undeploying an integration artifact [here](https://help.sap.com/viewer/368c481cd6954bdfa5d0435479fd4eaf/Cloud/en-US/d1679a80543f46509a7329243b595bdb.html) + +spec: + inputs: + secrets: + - name: cpiApiServiceKeyCredentialsId + description: Jenkins secret text credential ID containing the service key to the Process Integration Runtime service instance of plan 'api' + type: jenkins + params: + - name: apiServiceKey + type: string + description: Service key JSON string to access the Process Integration Runtime service instance of plan 'api' + scope: + - PARAMETERS + mandatory: true + secret: true + resourceRef: + - name: cpiApiServiceKeyCredentialsId + type: secret + param: apiServiceKey + - name: integrationFlowId + type: string + description: Specifies the ID of the Integration Flow artifact + scope: + - PARAMETERS + - GENERAL + - STAGES + - STEPS + mandatory: true diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index fe4f25791..04dc66fd4 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -185,6 +185,7 @@ public class CommonStepsTest extends BasePiperTest{ 'integrationArtifactDownload', //implementing new golang pattern without fields 'integrationArtifactUpload', //implementing new golang pattern without fields 'integrationArtifactTriggerIntegrationTest', //implementing new golang pattern without fields + 'integrationArtifactUnDeploy', //implementing new golang pattern without fields 'containerExecuteStructureTests', //implementing new golang pattern without fields 'transportRequestUploadSOLMAN', //implementing new golang pattern without fields 'transportRequestReqIDFromGit', //implementing new golang pattern without fields diff --git a/vars/integrationArtifactUnDeploy.groovy b/vars/integrationArtifactUnDeploy.groovy new file mode 100644 index 000000000..c9606c228 --- /dev/null +++ b/vars/integrationArtifactUnDeploy.groovy @@ -0,0 +1,11 @@ +import groovy.transform.Field + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/integrationArtifactUnDeploy.yaml' + +void call(Map parameters = [:]) { + List credentials = [ + [type: 'token', id: 'cpiApiServiceKeyCredentialsId', env: ['PIPER_apiServiceKey']] + ] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) +}