1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-09-16 09:26:22 +02:00

New piper step APIProviderDownload contribution (#3349)

* added store file function in cpi common utils

Change-Id: Ia429a2792266e082d139025a71799d21c30a7df9

* Added api provider steps

Change-Id: Icd2829a91db4c53d0de2330822d2b33933973868

* Update apiProviderDownload.yaml

* fixed yaml JLINT issue

Change-Id: Iac974abc30fa00e68c0177072b93716b0af5e0c5

* Removed trailing spaces

Change-Id: I927e9314fce6e9cab68d6b97577c7c96bb2bddad

* Resolved common steps groovy script conflict

Change-Id: I3ad144b618e1c77953aaeccaa5bf7309aff77ca9

* Change for conflict resolution

Change-Id: Ic955833eca844f090b7983f99f9d3649ebb981c7

* Fixed method name and its corresponding implementation

Change-Id: I465c1f1d5306bb978386de9efca3c521e385b89c

* Moved re-usable function to commonUtils package

Change-Id: Ide06462b01caeb2bf438ad7661e01c15bf8e8e24

* Changed the implementation to use existing writeFile method

* Fixed review comments on documentation and test structuring

Change-Id: Ifebd2f4b50754b2097b2d564fb3cc37c433ef6c9

* Fixed documentation alignment issues

* Fixed spaces issue

Change-Id: I834bd94e01bce72e7f81ab49ba32671c91c66ca9

* Documentation removed extra spaces

Change-Id: I9a639d76ed9b81c870f18349504044bb70753b52

* Fixed doc build issue

Change-Id: I96c3e15e73834b64f8b8e3432ce59f6b037f93fd

* Fixed documentation build issues

Change-Id: I7fca2ba69bc7b7298ee300ccd1ae16a6238dc96b

* Re-generated code for fixing build failure

Change-Id: I22b7ee6162f643d9f3b60f6a33eb7858927182a0

* Adopted file utils & mock

Change-Id: Ic46462003527f41df64395a5a615c19bf374e8ef

* Removed ioutil call in the test & adopted error variable names

* Removed commented lines

Change-Id: I99a12e39bc04323e9c19f1409d97eeca267e6fdb

* Added test for asserting file download and adopted error variables

Change-Id: I49463a3b75987bf68f5261d45602d2d7bd960a05

* Added download path assertion positive & negative case

Change-Id: Ieee461c3973b9dfa8f395dc936e4241ff9694c7b

* Modified tests with DownloadPath variable

Change-Id: Iaf14c9ea1a8242b6c8d8e9e4fac8c23d9c1b3a74

* Added testcase to validate file content

Change-Id: I21aed481b433450c3b536dbb29d45291f61848d8

* Refactored test for file content check to avoid failures

Change-Id: I3b4fe9a0de678f437fd4cc0a8203ae9434d9fa8e

* Removed auto-generated comments

Change-Id: I86c4ac3e7e4476a75d6cbed58826ec1f3278d7d2

* Fixed documentation review comments

Change-Id: I4faf31473b53fc53a5517d418c343bf7320eec55

* Fixed documentation indentation

Change-Id: I386f046cf4e10ee6deb5a81fcfc8c430c97086c8

* Fix build issue

Change-Id: I61a829cabaf03ffd5e77cddc594486a650118fa3
This commit is contained in:
maheshsrikrishnan
2022-03-09 17:37:23 +05:30
committed by GitHub
parent c086f84668
commit 64a00c540a
12 changed files with 464 additions and 1 deletions

View File

@@ -0,0 +1,88 @@
package cmd
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/cpi"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
)
type apiProviderDownloadUtils interface {
command.ExecRunner
FileWrite(path string, content []byte, perm os.FileMode) error
FileExists(filename string) (bool, error)
}
type apiProviderDownloadUtilsBundle struct {
*command.Command
*piperutils.Files
}
func newApiProviderDownloadUtils() apiProviderDownloadUtils {
utils := apiProviderDownloadUtilsBundle{
Command: &command.Command{},
Files: &piperutils.Files{},
}
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}
func apiProviderDownload(config apiProviderDownloadOptions, telemetryData *telemetry.CustomData) {
utils := newApiProviderDownloadUtils()
httpClient := &piperhttp.Client{}
err := runApiProviderDownload(&config, telemetryData, httpClient, utils)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runApiProviderDownload(config *apiProviderDownloadOptions, telemetryData *telemetry.CustomData, httpClient piperhttp.Sender, utils apiProviderDownloadUtils) error {
clientOptions := piperhttp.ClientOptions{}
header := make(http.Header)
header.Add("Accept", "application/json")
serviceKey, err := cpi.ReadCpiServiceKey(config.APIServiceKey)
if err != nil {
return err
}
downloadArtifactURL := fmt.Sprintf("%s/apiportal/api/1.0/Management.svc/APIProviders('%s')", serviceKey.OAuth.Host, config.APIProviderName)
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 := http.MethodGet
downloadResp, httpErr := httpClient.SendRequest(httpMethod, downloadArtifactURL, nil, header, nil)
if httpErr != nil {
return errors.Wrapf(httpErr, "HTTP %v request to %v failed with error", httpMethod, downloadArtifactURL)
}
if downloadResp == nil {
return errors.Errorf("did not retrieve a HTTP response: %v", httpErr)
}
if downloadResp != nil && downloadResp.Body != nil {
defer downloadResp.Body.Close()
}
if downloadResp.StatusCode == 200 {
jsonFilePath := config.DownloadPath
content, err := ioutil.ReadAll(downloadResp.Body)
if err != nil {
return err
}
err = utils.FileWrite(jsonFilePath, content, 0775)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,175 @@
// 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 apiProviderDownloadOptions struct {
APIServiceKey string `json:"apiServiceKey,omitempty"`
APIProviderName string `json:"apiProviderName,omitempty"`
DownloadPath string `json:"downloadPath,omitempty"`
}
// ApiProviderDownloadCommand Download a specific API Provider from the API Portal
func ApiProviderDownloadCommand() *cobra.Command {
const STEP_NAME = "apiProviderDownload"
metadata := apiProviderDownloadMetadata()
var stepConfig apiProviderDownloadOptions
var startTime time.Time
var logCollector *log.CollectorHook
var splunkClient *splunk.Splunk
telemetryClient := &telemetry.Telemetry{}
var createApiProviderDownloadCmd = &cobra.Command{
Use: STEP_NAME,
Short: "Download a specific API Provider from the API Portal",
Long: `With this step you can download a specific API Provider from the API Portal, which returns a JSON file with the api provider contents in to current workspace using the OData API. Learn more about the SAP API Management API for downloading an api provider artifact [here](https://api.sap.com/api/APIPortal_CF/overview).`,
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() {
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)
}
apiProviderDownload(stepConfig, &stepTelemetryData)
stepTelemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addApiProviderDownloadFlags(createApiProviderDownloadCmd, &stepConfig)
return createApiProviderDownloadCmd
}
func addApiProviderDownloadFlags(cmd *cobra.Command, stepConfig *apiProviderDownloadOptions) {
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().StringVar(&stepConfig.APIProviderName, "apiProviderName", os.Getenv("PIPER_apiProviderName"), "Specifies the name of the API Provider.")
cmd.Flags().StringVar(&stepConfig.DownloadPath, "downloadPath", os.Getenv("PIPER_downloadPath"), "Specifies api provider download directory location. The file name must not be included in the path.")
cmd.MarkFlagRequired("apiServiceKey")
cmd.MarkFlagRequired("apiProviderName")
cmd.MarkFlagRequired("downloadPath")
}
// retrieve step metadata
func apiProviderDownloadMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "apiProviderDownload",
Aliases: []config.Alias{},
Description: "Download a specific API Provider 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: "apiProviderName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_apiProviderName"),
},
{
Name: "downloadPath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_downloadPath"),
},
},
},
},
}
return theMetaData
}

View File

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

View File

@@ -0,0 +1,93 @@
package cmd
import (
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/stretchr/testify/assert"
)
type apiProviderDownloadTestUtilsBundle struct {
*mock.ExecMockRunner
*mock.FilesMock
}
func apiProviderDownloadMockUtilsBundle() *apiProviderDownloadTestUtilsBundle {
utilsBundle := apiProviderDownloadTestUtilsBundle{
ExecMockRunner: &mock.ExecMockRunner{},
FilesMock: &mock.FilesMock{},
}
return &utilsBundle
}
//Successful API Provider download cases
func TestApiProviderDownloadSuccess(t *testing.T) {
t.Parallel()
t.Run("Successful Download of API Provider", func(t *testing.T) {
apiServiceKey := `{
"oauth": {
"url": "https://demo",
"clientid": "demouser",
"clientsecret": "******",
"tokenurl": "https://demo/oauth/token"
}
}`
config := apiProviderDownloadOptions{
APIServiceKey: apiServiceKey,
APIProviderName: "provider1",
DownloadPath: "APIProvider.json",
}
httpClient := httpMockCpis{CPIFunction: "APIProviderDownload", ResponseBody: ``, TestType: "Positive"}
utilsMock := apiProviderDownloadMockUtilsBundle()
err := runApiProviderDownload(&config, nil, &httpClient, utilsMock)
if assert.NoError(t, err) {
t.Run("Assert file download & content", func(t *testing.T) {
fileExist := assert.True(t, utilsMock.HasWrittenFile(config.DownloadPath))
if fileExist {
providerbyteContent, _ := utilsMock.FileRead(config.DownloadPath)
providerContent := string(providerbyteContent)
assert.Equal(t, providerContent, "{\n\t\t\t\t\"d\": {\n\t\t\t\t\t\"results\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"__metadata\": {\n\t\t\t\t\t\t\t\t\"id\": \"https://roverpoc.it-accd002.cfapps.sap.hana.ondemand.com:443/api/v1/MessageProcessingLogs('AGAS1GcWkfBv-ZtpS6j7TKjReO7t')\",\n\t\t\t\t\t\t\t\t\"uri\": \"https://roverpoc.it-accd002.cfapps.sap.hana.ondemand.com:443/api/v1/MessageProcessingLogs('AGAS1GcWkfBv-ZtpS6j7TKjReO7t')\",\n\t\t\t\t\t\t\t\t\"type\": \"com.sap.hci.api.MessageProcessingLog\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"MessageGuid\": \"AGAS1GcWkfBv-ZtpS6j7TKjReO7t\",\n\t\t\t\t\t\t\t\"CorrelationId\": \"AGAS1GevYrPodxieoYf4YSY4jd-8\",\n\t\t\t\t\t\t\t\"ApplicationMessageId\": null,\n\t\t\t\t\t\t\t\"ApplicationMessageType\": null,\n\t\t\t\t\t\t\t\"LogStart\": \"/Date(1611846759005)/\",\n\t\t\t\t\t\t\t\"LogEnd\": \"/Date(1611846759032)/\",\n\t\t\t\t\t\t\t\"Sender\": null,\n\t\t\t\t\t\t\t\"Receiver\": null,\n\t\t\t\t\t\t\t\"IntegrationFlowName\": \"flow1\",\n\t\t\t\t\t\t\t\"Status\": \"COMPLETED\",\n\t\t\t\t\t\t\t\"LogLevel\": \"INFO\",\n\t\t\t\t\t\t\t\"CustomStatus\": \"COMPLETED\",\n\t\t\t\t\t\t\t\"TransactionId\": \"aa220151116748eeae69db3e88f2bbc8\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}")
}
})
t.Run("Assert API Provider url", func(t *testing.T) {
assert.Equal(t, "https://demo/apiportal/api/1.0/Management.svc/APIProviders('provider1')", httpClient.URL)
})
t.Run("Assert method as GET", func(t *testing.T) {
assert.Equal(t, "GET", httpClient.Method)
})
}
})
}
//API Provider download failure cases
func TestApiProviderDownloadFailure(t *testing.T) {
t.Parallel()
t.Run("Failed case of API Provider Download", func(t *testing.T) {
apiServiceKey := `{
"oauth": {
"url": "https://demo",
"clientid": "demouser",
"clientsecret": "******",
"tokenurl": "https://demo/oauth/token"
}
}`
config := apiProviderDownloadOptions{
APIServiceKey: apiServiceKey,
APIProviderName: "provider1",
DownloadPath: "APIProvider.json",
}
httpClient := httpMockCpis{CPIFunction: "APIProviderDownloadFailure", ResponseBody: ``, TestType: "Negative"}
utilsMock := apiProviderDownloadMockUtilsBundle()
err := runApiProviderDownload(&config, nil, &httpClient, utilsMock)
assert.False(t, utilsMock.HasWrittenFile(config.DownloadPath))
assert.EqualError(t, err, "HTTP GET request to https://demo/apiportal/api/1.0/Management.svc/APIProviders('provider1') failed with error: Service not Found")
})
}

View File

@@ -26,6 +26,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"abapEnvironmentRunAUnitTest": abapEnvironmentRunAUnitTestMetadata(),
"apiKeyValueMapDownload": apiKeyValueMapDownloadMetadata(),
"apiKeyValueMapUpload": apiKeyValueMapUploadMetadata(),
"apiProviderDownload": apiProviderDownloadMetadata(),
"apiProxyDownload": apiProxyDownloadMetadata(),
"apiProxyUpload": apiProxyUploadMetadata(),
"artifactPrepareVersion": artifactPrepareVersionMetadata(),

View File

@@ -179,6 +179,7 @@ func Execute() {
rootCmd.AddCommand(ShellExecuteCommand())
rootCmd.AddCommand(ApiProxyDownloadCommand())
rootCmd.AddCommand(ApiKeyValueMapDownloadCommand())
rootCmd.AddCommand(ApiProviderDownloadCommand())
rootCmd.AddCommand(ApiProxyUploadCommand())
rootCmd.AddCommand(GradleExecuteBuildCommand())
rootCmd.AddCommand(ApiKeyValueMapUploadCommand())

View File

@@ -0,0 +1,35 @@
# ${docGenStepName}
## ${docGenDescription}
* API Provider defines the connection details for services running on specific hosts.
* This function returns the APIProvider entity and stores it in the file system.
## Prerequisites
API Provider artifact to be downloaded should exist in the API Portal.
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}
## Example
Example configuration for the use in a `Jenkinsfile`.
```groovy
apiProviderDownload script: this
```
Example for the use in a YAML configuration file (such as `.pipeline/config.yaml`).
```yaml
steps:
<...>
apiProviderDownload:
apimApiServiceKeyCredentialsId: 'MY_API_SERVICE_KEY'
apiProviderName: 'MY_API_PROVIDER_NAME'
downloadPath: MY_API_PROVIDER_JSON_FILE_DOWNLOAD_PATH
```

View File

@@ -69,6 +69,7 @@ nav:
- apiKeyValueMapDownload: steps/apiKeyValueMapDownload.md
- apiKeyValueMapUpload: steps/apiKeyValueMapUpload.md
- apiProxyDownload: steps/apiProxyDownload.md
- apiProviderDownload: steps/apiProviderDownload.md
- apiProxyUpload: steps/apiProxyUpload.md
- artifactPrepareVersion: steps/artifactPrepareVersion.md
- batsExecuteTests: steps/batsExecuteTests.md

View File

@@ -27,7 +27,7 @@ func GetCPIFunctionMockResponse(functionName, testType string) (*http.Response,
return GetNegativeCaseHTTPResponseBodyAndErrorNil()
}
return GetParameterKeyMissingResponseBody()
case "IntegrationArtifactGetMplStatus", "APIKeyValueMapDownload":
case "IntegrationArtifactGetMplStatus", "APIKeyValueMapDownload", "APIProviderDownload":
return GetIntegrationArtifactGetMplStatusCommandMockResponse(testType)
case "IntegrationArtifactGetServiceEndpoint":
return GetIntegrationArtifactGetServiceEndpointCommandMockResponse(testType)

View File

@@ -0,0 +1,40 @@
metadata:
name: apiProviderDownload
description: Download a specific API Provider from the API Portal
longDescription: |
With this step you can download a specific API Provider from the API Portal, which returns a JSON file with the api provider contents in to current workspace using the OData API. Learn more about the SAP API Management API for downloading an api provider artifact [here](https://api.sap.com/api/APIPortal_CF/overview).
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: apiProviderName
type: string
description: Specifies the name of the API Provider.
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: downloadPath
type: string
description: Specifies api provider download directory location. The file name must not be included in the path.
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true

View File

@@ -207,6 +207,7 @@ public class CommonStepsTest extends BasePiperTest{
'golangBuild', //implementing new golang pattern without fields
'apiProxyDownload', //implementing new golang pattern without fields
'apiKeyValueMapDownload', //implementing new golang pattern without fields
'apiProviderDownload', //implementing new golang pattern without fields
'apiProxyUpload', //implementing new golang pattern without fields
'gradleExecuteBuild', //implementing new golang pattern without fields
'shellExecute', //implementing new golang pattern without fields

View File

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