From cdea4b7713dd6db1ec089d6e7480b2c188eb4654 Mon Sep 17 00:00:00 2001 From: Mayur Belur Mohan <68264167+mayurmohan@users.noreply.github.com> Date: Tue, 21 Jun 2022 16:38:00 +0530 Subject: [PATCH] ApiProxyList Command (#3794) * ApiProxyList Command * CodeReview Fixes * CodeReview Fixes * CodeReview FIxes * CodeReview Fixes * CodeReview FIxes * CodeReview Fixes * fixing unit test * doc fixes * Update documentation/docs/steps/apiProxyList.md Co-authored-by: Oliver Feldmann * CodeReview Fixes * CodeReview Fixes * CodeReview Fixes * codereview fix Co-authored-by: Oliver Feldmann --- cmd/apiProxyList.go | 58 +++++ cmd/apiProxyList_generated.go | 282 +++++++++++++++++++++++ cmd/apiProxyList_generated_test.go | 17 ++ cmd/apiProxyList_test.go | 54 +++++ cmd/metadata_generated.go | 1 + cmd/piper.go | 1 + documentation/docs/steps/apiProxyList.md | 49 ++++ documentation/mkdocs.yml | 1 + go.mod | 1 + go.sum | 2 + pkg/apim/APIMUtility.go | 26 +++ pkg/apim/APIMUtility_test.go | 27 +++ pkg/cpi/commonUtils.go | 30 +++ resources/metadata/apiProxyList.yaml | 94 ++++++++ test/groovy/CommonStepsTest.groovy | 2 + vars/apiProxyList.groovy | 11 + 16 files changed, 656 insertions(+) create mode 100644 cmd/apiProxyList.go create mode 100644 cmd/apiProxyList_generated.go create mode 100644 cmd/apiProxyList_generated_test.go create mode 100644 cmd/apiProxyList_test.go create mode 100644 documentation/docs/steps/apiProxyList.md create mode 100644 pkg/apim/APIMUtility_test.go create mode 100644 resources/metadata/apiProxyList.yaml create mode 100644 vars/apiProxyList.groovy diff --git a/cmd/apiProxyList.go b/cmd/apiProxyList.go new file mode 100644 index 000000000..704ca41c6 --- /dev/null +++ b/cmd/apiProxyList.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "fmt" + "net/http" + + "github.com/SAP/jenkins-library/pkg/apim" + "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 apiProxyList(config apiProxyListOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *apiProxyListCommonPipelineEnvironment) { + httpClient := &piperhttp.Client{} + err := runApiProxyList(&config, telemetryData, httpClient, commonPipelineEnvironment) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } +} + +func runApiProxyList(config *apiProxyListOptions, telemetryData *telemetry.CustomData, httpClient piperhttp.Sender, commonPipelineEnvironment *apiProxyListCommonPipelineEnvironment) error { + apimData := apim.Bundle{APIServiceKey: config.APIServiceKey, Client: httpClient} + err := apim.Utils.InitAPIM(&apimData) + if err != nil { + return err + } + return getApiProxyList(config, apimData, commonPipelineEnvironment) +} + +func getApiProxyList(config *apiProxyListOptions, apistruct apim.Bundle, commonPipelineEnvironment *apiProxyListCommonPipelineEnvironment) error { + httpClient := apistruct.Client + httpMethod := http.MethodGet + odataFilterInputs := apim.OdataParameters{Filter: config.Filter, Search: config.Search, + Top: config.Top, Skip: config.Skip, Orderby: config.Orderby, + Select: config.Select, Expand: config.Expand} + odataFilters, urlErr := apim.OdataUtils.MakeOdataQuery(&odataFilterInputs) + if urlErr != nil { + return errors.Wrap(urlErr, "failed to create odata filter") + } + getApiProxyListURL := fmt.Sprintf("%s/apiportal/api/1.0/Management.svc/APIProxies%s", apistruct.Host, odataFilters) + header := make(http.Header) + header.Add("Accept", "application/json") + apiProxyListResp, httpErr := httpClient.SendRequest(httpMethod, getApiProxyListURL, nil, header, nil) + failureMessage := "Failed to get List of API Proxy" + successMessage := "Successfully retrieved the api proxy list from API Portal" + httpGetRequestParameters := cpi.HttpFileUploadRequestParameters{ + ErrMessage: failureMessage, + Response: apiProxyListResp, + HTTPMethod: httpMethod, + HTTPURL: getApiProxyListURL, + HTTPErr: httpErr, + SuccessMessage: successMessage} + resp, err := cpi.HTTPUploadUtils.HandleHTTPGetRequestResponse(httpGetRequestParameters) + commonPipelineEnvironment.custom.APIProxyList = resp + return err +} diff --git a/cmd/apiProxyList_generated.go b/cmd/apiProxyList_generated.go new file mode 100644 index 000000000..ce36dc16d --- /dev/null +++ b/cmd/apiProxyList_generated.go @@ -0,0 +1,282 @@ +// Code generated by piper's step-generator. DO NOT EDIT. + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/piperenv" + "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 apiProxyListOptions struct { + APIServiceKey string `json:"apiServiceKey,omitempty"` + Top int `json:"top,omitempty"` + Skip int `json:"skip,omitempty"` + Filter string `json:"filter,omitempty"` + Count bool `json:"count,omitempty"` + Search string `json:"search,omitempty"` + Orderby string `json:"orderby,omitempty"` + Select string `json:"select,omitempty"` + Expand string `json:"expand,omitempty"` +} + +type apiProxyListCommonPipelineEnvironment struct { + custom struct { + APIProxyList string + } +} + +func (p *apiProxyListCommonPipelineEnvironment) persist(path, resourceName string) { + content := []struct { + category string + name string + value interface{} + }{ + {category: "custom", name: "apiProxyList", value: p.custom.APIProxyList}, + } + + errCount := 0 + for _, param := range content { + err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(param.category, param.name), param.value) + if err != nil { + log.Entry().WithError(err).Error("Error persisting piper environment.") + errCount++ + } + } + if errCount > 0 { + log.Entry().Error("failed to persist Piper environment") + } +} + +// ApiProxyListCommand Get the List of an API Proxy from the API Portal +func ApiProxyListCommand() *cobra.Command { + const STEP_NAME = "apiProxyList" + + metadata := apiProxyListMetadata() + var stepConfig apiProxyListOptions + var startTime time.Time + var commonPipelineEnvironment apiProxyListCommonPipelineEnvironment + var logCollector *log.CollectorHook + var splunkClient *splunk.Splunk + telemetryClient := &telemetry.Telemetry{} + + var createApiProxyListCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "Get the List of an API Proxy from the API Portal", + Long: `With this step you can get list of all API Proxy from the API Portal using the OData API. Learn more about the API Management API for getting list of an API proxy artifact [here](https://help.sap.com/viewer/66d066d903c2473f81ec33acfe2ccdb4/Cloud/en-US/e26b3320cd534ae4bc743af8013a8abb.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 { + splunkClient = &splunk.Splunk{} + logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID} + log.RegisterHook(logCollector) + } + + 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() { + commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment") + 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) + } + apiProxyList(stepConfig, &stepTelemetryData, &commonPipelineEnvironment) + stepTelemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addApiProxyListFlags(createApiProxyListCmd, &stepConfig) + return createApiProxyListCmd +} + +func addApiProxyListFlags(cmd *cobra.Command, stepConfig *apiProxyListOptions) { + cmd.Flags().StringVar(&stepConfig.APIServiceKey, "apiServiceKey", os.Getenv("PIPER_apiServiceKey"), "Service key JSON string to access the API Management Runtime service instance of plan 'api'") + cmd.Flags().IntVar(&stepConfig.Top, "top", 0, "Show only the first n items.") + cmd.Flags().IntVar(&stepConfig.Skip, "skip", 0, "Skip the first n items.") + cmd.Flags().StringVar(&stepConfig.Filter, "filter", os.Getenv("PIPER_filter"), "Filter items by property values.") + cmd.Flags().BoolVar(&stepConfig.Count, "count", false, "Include count of items.") + cmd.Flags().StringVar(&stepConfig.Search, "search", os.Getenv("PIPER_search"), "Search items by search phrases.") + cmd.Flags().StringVar(&stepConfig.Orderby, "orderby", os.Getenv("PIPER_orderby"), "Order by property values.") + cmd.Flags().StringVar(&stepConfig.Select, "select", os.Getenv("PIPER_select"), "Select properties to be returned.") + cmd.Flags().StringVar(&stepConfig.Expand, "expand", os.Getenv("PIPER_expand"), "Expand related entities.") + + cmd.MarkFlagRequired("apiServiceKey") +} + +// retrieve step metadata +func apiProxyListMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "apiProxyList", + Aliases: []config.Alias{}, + Description: "Get the List of an API Proxy from the API Portal", + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Secrets: []config.StepSecrets{ + {Name: "apimApiServiceKeyCredentialsId", Description: "Jenkins secret text credential ID containing the service key to the API Management Runtime service instance of plan 'api'", Type: "jenkins"}, + }, + Parameters: []config.StepParameters{ + { + Name: "apiServiceKey", + ResourceRef: []config.ResourceReference{ + { + Name: "apimApiServiceKeyCredentialsId", + Param: "apiServiceKey", + Type: "secret", + }, + }, + Scope: []string{"PARAMETERS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_apiServiceKey"), + }, + { + Name: "top", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + Default: 0, + }, + { + Name: "skip", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + Default: 0, + }, + { + Name: "filter", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_filter"), + }, + { + Name: "count", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + Default: false, + }, + { + Name: "search", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_search"), + }, + { + Name: "orderby", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_orderby"), + }, + { + Name: "select", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_select"), + }, + { + Name: "expand", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_expand"), + }, + }, + }, + Outputs: config.StepOutputs{ + Resources: []config.StepResources{ + { + Name: "commonPipelineEnvironment", + Type: "piperEnvironment", + Parameters: []map[string]interface{}{ + {"name": "custom/apiProxyList"}, + }, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/apiProxyList_generated_test.go b/cmd/apiProxyList_generated_test.go new file mode 100644 index 000000000..78ef2b26b --- /dev/null +++ b/cmd/apiProxyList_generated_test.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestApiProxyListCommand(t *testing.T) { + t.Parallel() + + testCmd := ApiProxyListCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "apiProxyList", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/apiProxyList_test.go b/cmd/apiProxyList_test.go new file mode 100644 index 000000000..6cf9430e0 --- /dev/null +++ b/cmd/apiProxyList_test.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "testing" + + "github.com/SAP/jenkins-library/pkg/apim" + apimhttp "github.com/SAP/jenkins-library/pkg/apim" + "github.com/stretchr/testify/assert" +) + +func TestRunApiProxyList(t *testing.T) { + t.Parallel() + + t.Run("Get API Proxy List successfull test", func(t *testing.T) { + config := getDefaultOptionsForApiProxyList() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 200, ResponseBody: `{"some": "test"}`} + seOut := apiProxyListCommonPipelineEnvironment{} + apim := apim.Bundle{APIServiceKey: config.APIServiceKey, Client: httpClientMock} + // test + err := getApiProxyList(&config, apim, &seOut) + + // assert + if assert.NoError(t, err) { + assert.EqualValues(t, seOut.custom.APIProxyList, "{\"some\": \"test\"}") + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "/apiportal/api/1.0/Management.svc/APIProxies?filter=isCopy+eq+false&$orderby=name&$skip=1&$top=4", httpClientMock.URL) + }) + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClientMock.Method) + }) + } + }) + + t.Run("Get API Proxy List failed test", func(t *testing.T) { + config := getDefaultOptionsForApiProxyList() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 400} + seOut := apiProxyListCommonPipelineEnvironment{} + apim := apim.Bundle{APIServiceKey: config.APIServiceKey, Client: httpClientMock} + // test + err := getApiProxyList(&config, apim, &seOut) + // assert + assert.EqualError(t, err, "HTTP GET request to /apiportal/api/1.0/Management.svc/APIProxies?filter=isCopy+eq+false&$orderby=name&$skip=1&$top=4 failed with error: : Bad Request") + }) +} + +func getDefaultOptionsForApiProxyList() apiProxyListOptions { + return apiProxyListOptions{ + APIServiceKey: apimhttp.GetServiceKey(), + Top: 4, + Skip: 1, + Filter: "isCopy eq false", + Orderby: "name", + } +} diff --git a/cmd/metadata_generated.go b/cmd/metadata_generated.go index b3dc76ada..eef833ef9 100644 --- a/cmd/metadata_generated.go +++ b/cmd/metadata_generated.go @@ -30,6 +30,7 @@ func GetAllStepMetadata() map[string]config.StepData { "apiProviderDownload": apiProviderDownloadMetadata(), "apiProviderUpload": apiProviderUploadMetadata(), "apiProxyDownload": apiProxyDownloadMetadata(), + "apiProxyList": apiProxyListMetadata(), "apiProxyUpload": apiProxyUploadMetadata(), "artifactPrepareVersion": artifactPrepareVersionMetadata(), "awsS3Upload": awsS3UploadMetadata(), diff --git a/cmd/piper.go b/cmd/piper.go index 1e25fa8ec..2eb75f1da 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -187,6 +187,7 @@ func Execute() { rootCmd.AddCommand(PythonBuildCommand()) rootCmd.AddCommand(AzureBlobUploadCommand()) rootCmd.AddCommand(AwsS3UploadCommand()) + rootCmd.AddCommand(ApiProxyListCommand()) addRootFlags(rootCmd) diff --git a/documentation/docs/steps/apiProxyList.md b/documentation/docs/steps/apiProxyList.md new file mode 100644 index 000000000..ee90c7241 --- /dev/null +++ b/documentation/docs/steps/apiProxyList.md @@ -0,0 +1,49 @@ +# ${docGenStepName} + +## ${docGenDescription} + +With this step, you can retrieve all the API proxies from the API portal. An API Proxy is a concept in API Management, capability of SAP Integration Suite, which anonymizes any HTTP endpoints like REST, OData, or SOAP and enhance it with policies and routes. + +An API proxy is a discrete representation of an API. It is implemented as a set of configuration files, policies, and code snippets that rely on the resource information provided by API Management. For more information, see the document [API Proxy](https://help.sap.com/doc/654e5912ee554d46bcc6347599fb2096/CLOUD/en-US/Unit%2004.3%20-%20API%20Proxy%20-%20API%20Resource.pdf/) + +To consume the ApiProxyList step, proceed as follows: + +* Copy the SAP API management service key from the SAP BTP cockpit. Go to instance and subscriptions → service API Management, API portal, which was created under apiportal-apiaccess plan. +* Store your service key created for API Management in the Jenkins server as a secret text. +* Create a new Jenkins pipeline designated for the ApiProxyList step. +* Execute the pipeline and validate the step exection results as explained in the blog [Integration Suite Piper commands](https://blogs.sap.com/2022/01/05/orking-with-integration-suite-piper-commands/) +* Use the ApiProxyList step to get the api proxy list from the API portal. +* ApiProxyList only supports GET operation. + +## Prerequisites + +## ${docGenParameters} + +## ${docGenConfiguration} + +## ${docJenkinsPluginDependencies} + +## Example + +Configuration example for a `Jenkinsfile`: + +```groovy +apiProxyList script: this +``` + +Configuration example for a YAML file(for example `.pipeline/config.yaml`): + +```yaml +steps: + <...> + apiProxyList: + apimApiServiceKeyCredentialsId: 'MY_API_SERVICE_KEY' + Top: MY_API_PROXY_GET_N_ENTITIES + Skip: MY_API_PROXY_SKIP_N_ENTITIES + Filter: MY_API_PROXY_FILTER_BY_ENTITY_FIELD + Orderby: MY_API_PROXY_ORDER_BY_ENTITY_FIELD + Count: MY_API_PROXY_ORDER_ENTITY_COUNT + Search: MY_API_PROXY_SEARCH_BY_ENTITY_FIELD + Select: MY_API_PROXY_SELECT_BY_ENTITY_FIELD + Expand: MY_API_PROXY_EXPAND_BY_ENTITY_FIELD +``` diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 308e776b0..7d16f7130 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -72,6 +72,7 @@ nav: - apiKeyValueMapDownload: steps/apiKeyValueMapDownload.md - apiKeyValueMapUpload: steps/apiKeyValueMapUpload.md - apiProxyDownload: steps/apiProxyDownload.md + - apiProxyList: steps/apiProxyList.md - apiProviderDownload: steps/apiProviderDownload.md - apiProviderUpload: steps/apiProviderUpload.md - apiProxyUpload: steps/apiProxyUpload.md diff --git a/go.mod b/go.mod index f4cbb4b6d..ee7963e0e 100644 --- a/go.mod +++ b/go.mod @@ -250,6 +250,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/oracle/oci-go-sdk v13.1.0+incompatible // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect + github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect diff --git a/go.sum b/go.sum index e76362231..fe046e418 100644 --- a/go.sum +++ b/go.sum @@ -1557,6 +1557,8 @@ github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otz github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c h1:Gcce/r5tSQeprxswXXOwQ/RBU1bjQWVd9dB7QKoPXBE= +github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c/go.mod h1:1iCZ0433JJMecYqCa+TdWA9Pax8MGl4ByuNDZ7eSnQY= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= diff --git a/pkg/apim/APIMUtility.go b/pkg/apim/APIMUtility.go index 4275da078..aaff672bf 100644 --- a/pkg/apim/APIMUtility.go +++ b/pkg/apim/APIMUtility.go @@ -3,10 +3,12 @@ package apim import ( "encoding/json" "fmt" + "strings" "github.com/SAP/jenkins-library/pkg/cpi" piperhttp "github.com/SAP/jenkins-library/pkg/http" "github.com/SAP/jenkins-library/pkg/xsuaa" + "github.com/pasztorpisti/qs" "github.com/pkg/errors" ) @@ -16,6 +18,18 @@ type Utils interface { IsPayloadJSON() bool } +//OdataUtils for apim +type OdataUtils interface { + MakeOdataQuery() (string, error) +} + +//OdataParameters struct +type OdataParameters struct { + Filter, Search string + Top, Skip int + Orderby, Select, Expand string +} + //Bundle struct type Bundle struct { APIServiceKey, Host, Payload string @@ -51,3 +65,15 @@ func (apim *Bundle) IsPayloadJSON() bool { var js json.RawMessage return json.Unmarshal([]byte(apim.Payload), &js) == nil } + +func (odataFilters *OdataParameters) MakeOdataQuery() (string, error) { + + customMarshaler := qs.NewMarshaler(&qs.MarshalOptions{ + DefaultMarshalPresence: qs.OmitEmpty, + }) + values, encodeErr := customMarshaler.Marshal(odataFilters) + if encodeErr == nil && len(values) > 0 { + values = "?" + strings.ReplaceAll(values, "&", "&$") + } + return values, encodeErr +} diff --git a/pkg/apim/APIMUtility_test.go b/pkg/apim/APIMUtility_test.go new file mode 100644 index 000000000..6bda1ef20 --- /dev/null +++ b/pkg/apim/APIMUtility_test.go @@ -0,0 +1,27 @@ +package apim + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOdataQueryInitExisting(t *testing.T) { + t.Run("MakeOdataQuery- Success Test", func(t *testing.T) { + odataFilterInputs := OdataParameters{Filter: "isCopy eq false", Search: "", + Top: 4, Skip: 1, Orderby: "name", + Select: "", Expand: ""} + odataFilters, err := OdataUtils.MakeOdataQuery(&odataFilterInputs) + assert.NoError(t, err) + assert.Equal(t, "?filter=isCopy+eq+false&$orderby=name&$skip=1&$top=4", odataFilters) + }) + + t.Run("MakeOdataQuery- empty odata filters Test", func(t *testing.T) { + odataFilterInputs := OdataParameters{Filter: "", Search: "", + Top: 0, Skip: 0, Orderby: "", + Select: "", Expand: ""} + odataFilters, err := OdataUtils.MakeOdataQuery(&odataFilterInputs) + assert.NoError(t, err) + assert.Equal(t, "", odataFilters) + }) +} diff --git a/pkg/cpi/commonUtils.go b/pkg/cpi/commonUtils.go index 64c63ebf2..27e9b3c20 100644 --- a/pkg/cpi/commonUtils.go +++ b/pkg/cpi/commonUtils.go @@ -30,6 +30,7 @@ type HttpCPIUtils interface { //HTTPUploadUtils for CPI type HTTPUploadUtils interface { HandleHTTPFileUploadResponse() error + HandleHTTPGetRequestResponse() (string, error) } //TokenParameters struct @@ -190,3 +191,32 @@ func (httpFileUploadRequestParameters HttpFileUploadRequestParameters) HandleHTT } return errors.Errorf("%s, Response Status code: %v", httpFileUploadRequestParameters.ErrMessage, response.StatusCode) } + +// HandleHTTPGetRequestResponse - Handle the GET Request response data +func (httpGetRequestParameters HttpFileUploadRequestParameters) HandleHTTPGetRequestResponse() (string, error) { + response := httpGetRequestParameters.Response + httpErr := httpGetRequestParameters.HTTPErr + if response != nil && response.Body != nil { + defer response.Body.Close() + } + + if response == nil { + return "", errors.Errorf("did not retrieve a HTTP response: %v", httpErr) + } + if response.StatusCode == http.StatusOK { + responseBody, readErr := ioutil.ReadAll(response.Body) + if readErr != nil { + return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", response.StatusCode) + } + return string(responseBody), nil + } + if httpErr != nil { + responseBody, readErr := ioutil.ReadAll(response.Body) + if readErr != nil { + return "", errors.Wrapf(readErr, "HTTP response body could not be read, Response status code: %v", response.StatusCode) + } + log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code: %v", string(responseBody), response.StatusCode) + return "", errors.Wrapf(httpErr, "HTTP %v request to %v failed with error: %v", httpGetRequestParameters.HTTPMethod, httpGetRequestParameters.HTTPURL, string(responseBody)) + } + return "", errors.Errorf("%s, Response Status code: %v", httpGetRequestParameters.ErrMessage, response.StatusCode) +} diff --git a/resources/metadata/apiProxyList.yaml b/resources/metadata/apiProxyList.yaml new file mode 100644 index 000000000..a9ea9f191 --- /dev/null +++ b/resources/metadata/apiProxyList.yaml @@ -0,0 +1,94 @@ +metadata: + name: apiProxyList + description: Get the List of an API Proxy from the API Portal + longDescription: | + With this step you can get list of all API Proxy from the API Portal using the OData API. Learn more about the API Management API for getting list of an API proxy artifact [here](https://help.sap.com/viewer/66d066d903c2473f81ec33acfe2ccdb4/Cloud/en-US/e26b3320cd534ae4bc743af8013a8abb.html). + +spec: + inputs: + secrets: + - name: apimApiServiceKeyCredentialsId + description: Jenkins secret text credential ID containing the service key to the API Management Runtime service instance of plan 'api' + type: jenkins + params: + - name: apiServiceKey + type: string + description: Service key JSON string to access the API Management Runtime service instance of plan 'api' + scope: + - PARAMETERS + mandatory: true + secret: true + resourceRef: + - name: apimApiServiceKeyCredentialsId + type: secret + param: apiServiceKey + - name: top + type: int + description: Show only the first n items. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + - name: skip + type: int + description: Skip the first n items. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + - name: filter + type: string + description: Filter items by property values. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + - name: count + type: bool + description: Include count of items. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + - name: search + type: string + description: Search items by search phrases. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + - name: orderby + type: string + description: Order by property values. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + - name: select + type: string + description: Select properties to be returned. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + - name: expand + type: string + description: Expand related entities. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + outputs: + resources: + - name: commonPipelineEnvironment + type: piperEnvironment + params: + - name: custom/apiProxyList diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index e327894ea..919b6375c 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -217,6 +217,8 @@ public class CommonStepsTest extends BasePiperTest{ 'apiKeyValueMapUpload', //implementing new golang pattern without fields 'apiProviderUpload', //implementing new golang pattern without fields 'pythonBuild', //implementing new golang pattern without fields + 'awsS3Upload', + 'apiProxyList', //implementing new golang pattern without fields 'azureBlobUpload', 'awsS3Upload' ] diff --git a/vars/apiProxyList.groovy b/vars/apiProxyList.groovy new file mode 100644 index 000000000..9d96d25d8 --- /dev/null +++ b/vars/apiProxyList.groovy @@ -0,0 +1,11 @@ +import groovy.transform.Field + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/apiProxyList.yaml' + +void call(Map parameters = [:]) { + List credentials = [ + [type: 'token', id: 'apimApiServiceKeyCredentialsId', env: ['PIPER_apiServiceKey']] + ] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) +}