diff --git a/cmd/apiProviderList.go b/cmd/apiProviderList.go new file mode 100644 index 000000000..8c7decc82 --- /dev/null +++ b/cmd/apiProviderList.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 apiProviderList(config apiProviderListOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *apiProviderListCommonPipelineEnvironment) { + httpClient := &piperhttp.Client{} + err := runApiProviderList(&config, telemetryData, httpClient, commonPipelineEnvironment) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } +} + +func runApiProviderList(config *apiProviderListOptions, telemetryData *telemetry.CustomData, httpClient piperhttp.Sender, commonPipelineEnvironment *apiProviderListCommonPipelineEnvironment) error { + apimData := apim.Bundle{APIServiceKey: config.APIServiceKey, Client: httpClient} + err := apim.Utils.InitAPIM(&apimData) + if err != nil { + return err + } + return getApiProviderList(config, apimData, commonPipelineEnvironment) +} + +func getApiProviderList(config *apiProviderListOptions, apistruct apim.Bundle, commonPipelineEnvironment *apiProviderListCommonPipelineEnvironment) 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") + } + getApiProviderListURL := fmt.Sprintf("%s/apiportal/api/1.0/Management.svc/APIProviders%s", apistruct.Host, odataFilters) + header := make(http.Header) + header.Add("Accept", "application/json") + apiProviderListResp, httpErr := httpClient.SendRequest(httpMethod, getApiProviderListURL, nil, header, nil) + failureMessage := "Failed to get List of API Providers" + successMessage := "Successfully retrieved the api provider list from API Portal" + httpGetRequestParameters := cpi.HttpFileUploadRequestParameters{ + ErrMessage: failureMessage, + Response: apiProviderListResp, + HTTPMethod: httpMethod, + HTTPURL: getApiProviderListURL, + HTTPErr: httpErr, + SuccessMessage: successMessage} + resp, err := cpi.HTTPUploadUtils.HandleHTTPGetRequestResponse(httpGetRequestParameters) + commonPipelineEnvironment.custom.APIProviderList = resp + return err +} diff --git a/cmd/apiProviderList_generated.go b/cmd/apiProviderList_generated.go new file mode 100644 index 000000000..028802510 --- /dev/null +++ b/cmd/apiProviderList_generated.go @@ -0,0 +1,286 @@ +// 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 apiProviderListOptions 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 apiProviderListCommonPipelineEnvironment struct { + custom struct { + APIProviderList string + } +} + +func (p *apiProviderListCommonPipelineEnvironment) persist(path, resourceName string) { + content := []struct { + category string + name string + value interface{} + }{ + {category: "custom", name: "apiProviderList", value: p.custom.APIProviderList}, + } + + 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") + } +} + +// ApiProviderListCommand Get a full List of all API providers from the API Portal +func ApiProviderListCommand() *cobra.Command { + const STEP_NAME = "apiProviderList" + + metadata := apiProviderListMetadata() + var stepConfig apiProviderListOptions + var startTime time.Time + var commonPipelineEnvironment apiProviderListCommonPipelineEnvironment + var logCollector *log.CollectorHook + var splunkClient *splunk.Splunk + telemetryClient := &telemetry.Telemetry{} + + var createApiProviderListCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "Get a full List of all API providers from the API Portal", + Long: `With this step, you can get a list of all API providers from the API Portal using the OData API. Learn more about the API Management API for getting list of an API Providers [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) + } + + 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() { + 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) + } + apiProviderList(stepConfig, &stepTelemetryData, &commonPipelineEnvironment) + stepTelemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addApiProviderListFlags(createApiProviderListCmd, &stepConfig) + return createApiProviderListCmd +} + +func addApiProviderListFlags(cmd *cobra.Command, stepConfig *apiProviderListOptions) { + 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 apiProviderListMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "apiProviderList", + Aliases: []config.Alias{}, + Description: "Get a full List of all API providers 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/apiProviderList"}, + }, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/apiProviderList_generated_test.go b/cmd/apiProviderList_generated_test.go new file mode 100644 index 000000000..f6eaf0604 --- /dev/null +++ b/cmd/apiProviderList_generated_test.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestApiProviderListCommand(t *testing.T) { + t.Parallel() + + testCmd := ApiProviderListCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "apiProviderList", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/apiProviderList_test.go b/cmd/apiProviderList_test.go new file mode 100644 index 000000000..2fac1f6bd --- /dev/null +++ b/cmd/apiProviderList_test.go @@ -0,0 +1,53 @@ +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 TestRunApiProviderList(t *testing.T) { + t.Parallel() + + t.Run("Get API providers successfull test", func(t *testing.T) { + config := getDefaultOptionsForApiProviderList() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 200, ResponseBody: `{"some": "test"}`} + seOut := apiProviderListCommonPipelineEnvironment{} + apim := apim.Bundle{APIServiceKey: config.APIServiceKey, Client: httpClientMock} + // test + err := getApiProviderList(&config, apim, &seOut) + + // assert + if assert.NoError(t, err) { + assert.EqualValues(t, seOut.custom.APIProviderList, "{\"some\": \"test\"}") + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "/apiportal/api/1.0/Management.svc/APIProviders?orderby=value&$select=name&$top=2", httpClientMock.URL) + }) + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClientMock.Method) + }) + } + }) + + t.Run("Get API provider failed test", func(t *testing.T) { + config := getDefaultOptionsForApiProviderList() + httpClientMock := &apimhttp.HttpMockAPIM{StatusCode: 400} + seOut := apiProviderListCommonPipelineEnvironment{} + apim := apim.Bundle{APIServiceKey: config.APIServiceKey, Client: httpClientMock} + // test + err := getApiProviderList(&config, apim, &seOut) + // assert + assert.EqualError(t, err, "HTTP GET request to /apiportal/api/1.0/Management.svc/APIProviders?orderby=value&$select=name&$top=2 failed with error: : Bad Request") + }) +} + +func getDefaultOptionsForApiProviderList() apiProviderListOptions { + return apiProviderListOptions{ + APIServiceKey: apimhttp.GetServiceKey(), + Top: 2, + Select: "name", + Orderby: "value", + } +} diff --git a/cmd/metadata_generated.go b/cmd/metadata_generated.go index 8526e2684..68fd4ab35 100644 --- a/cmd/metadata_generated.go +++ b/cmd/metadata_generated.go @@ -29,6 +29,7 @@ func GetAllStepMetadata() map[string]config.StepData { "apiKeyValueMapDownload": apiKeyValueMapDownloadMetadata(), "apiKeyValueMapUpload": apiKeyValueMapUploadMetadata(), "apiProviderDownload": apiProviderDownloadMetadata(), + "apiProviderList": apiProviderListMetadata(), "apiProviderUpload": apiProviderUploadMetadata(), "apiProxyDownload": apiProxyDownloadMetadata(), "apiProxyList": apiProxyListMetadata(), diff --git a/cmd/piper.go b/cmd/piper.go index 87593ef13..5458792e5 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -190,6 +190,7 @@ func Execute() { rootCmd.AddCommand(AwsS3UploadCommand()) rootCmd.AddCommand(ApiProxyListCommand()) rootCmd.AddCommand(AnsSendEventCommand()) + rootCmd.AddCommand(ApiProviderListCommand()) addRootFlags(rootCmd) diff --git a/documentation/docs/steps/apiProviderList.md b/documentation/docs/steps/apiProviderList.md new file mode 100644 index 000000000..7d4e04af0 --- /dev/null +++ b/documentation/docs/steps/apiProviderList.md @@ -0,0 +1,49 @@ +# ${docGenStepName} + +## ${docGenDescription} + +With this step, you can retrieve all the API providers from the API portal. An API provider is a concept in API Management, capability of SAP Integration Suite, which defines the connection details for services running on specific hosts whose details you want to access. + +You use an API provider to define not only the details of the host you want an application to reach, but also to define any further details that are necessary to establish the connection, for example, proxy settings. For more details, see the blog [API Providers](https://blogs.sap.com/2017/07/27/blog-series-api-providers/) + +To consume the ApiProviderList 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 ApiProviderList 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 ApiProviderList step to get the api providers list from the API Portal. +* ApiProviderList only supports GET operation. + +## Prerequisites + +## ${docGenParameters} + +## ${docGenConfiguration} + +## ${docJenkinsPluginDependencies} + +## Example + +Configuration example for a `Jenkinsfile`: + +```groovy +apiProviderList script: this +``` + +Configuration example for a YAML file(for example `.pipeline/config.yaml`): + +```yaml +steps: + <...> + apiProviderList: + apimApiServiceKeyCredentialsId: 'MY_API_SERVICE_KEY' + Top: MY_API_PROVIDER_GET_N_ENTITIES + Skip: MY_API_PROVIDER_SKIP_N_ENTITIES + Filter: MY_API_PROVIDER_FILTER_BY_ENTITY_FIELD + Orderby: MY_API_PROVIDER_ORDER_BY_ENTITY_FIELD + Count: MY_API_PROVIDER_ORDER_ENTITY_COUNT + Search: MY_API_PROVIDER_SEARCH_BY_ENTITY_FIELD + Select: MY_API_PROVIDER_SELECT_BY_ENTITY_FIELD + Expand: MY_API_PROVIDER_EXPAND_BY_ENTITY_FIELD +``` diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 07348b11d..d2555ecfd 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -74,6 +74,7 @@ nav: - apiProxyDownload: steps/apiProxyDownload.md - apiProxyList: steps/apiProxyList.md - apiProviderDownload: steps/apiProviderDownload.md + - apiProviderList: steps/apiProviderList.md - apiProviderUpload: steps/apiProviderUpload.md - apiProxyUpload: steps/apiProxyUpload.md - artifactPrepareVersion: steps/artifactPrepareVersion.md diff --git a/resources/metadata/apiProviderList.yaml b/resources/metadata/apiProviderList.yaml new file mode 100644 index 000000000..df5da4b93 --- /dev/null +++ b/resources/metadata/apiProviderList.yaml @@ -0,0 +1,94 @@ +metadata: + name: apiProviderList + description: Get a full List of all API providers from the API Portal + longDescription: | + With this step, you can get a list of all API providers from the API Portal using the OData API. Learn more about the API Management API for getting list of an API Providers [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/apiProviderList diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index 7efa1d54c..1e0c1a1b6 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -222,7 +222,8 @@ public class CommonStepsTest extends BasePiperTest{ 'apiProxyList', //implementing new golang pattern without fields 'azureBlobUpload', 'awsS3Upload', - 'ansSendEvent' + 'ansSendEvent', + 'apiProviderList', //implementing new golang pattern without fields ] @Test diff --git a/vars/apiProviderList.groovy b/vars/apiProviderList.groovy new file mode 100644 index 000000000..322ce23c3 --- /dev/null +++ b/vars/apiProviderList.groovy @@ -0,0 +1,11 @@ +import groovy.transform.Field + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/apiProviderList.yaml' + +void call(Map parameters = [:]) { + List credentials = [ + [type: 'token', id: 'apimApiServiceKeyCredentialsId', env: ['PIPER_apiServiceKey']] + ] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) +}