1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

ApiProxyUpload Command (#3295)

* ApiProxyUpload Command

* Code Review Fixes

* CodeReview Changes

* CodeReview Fixes

* YAML fixes

* CodeReview Fix

* Code Review Fixes

* CodeReview Fixes

* Code Climate Fixes

* Code Review Fixes

* Code Review Fixes

Co-authored-by: Roland Stengel <r.stengel@sap.com>
Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>
This commit is contained in:
Mayur Belur Mohan
2021-12-28 14:01:50 +05:30
committed by GitHub
parent b5da011200
commit f27cb4e482
13 changed files with 500 additions and 1 deletions

75
cmd/apiProxyUpload.go Normal file
View File

@@ -0,0 +1,75 @@
package cmd
import (
"bytes"
b64 "encoding/base64"
"fmt"
"net/http"
"strings"
"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"
)
func apiProxyUpload(config apiProxyUploadOptions, 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{}
fileUtils := &piperutils.Files{}
// 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 := runApiProxyUpload(&config, telemetryData, fileUtils, httpClient)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runApiProxyUpload(config *apiProxyUploadOptions, telemetryData *telemetry.CustomData, fileUtils piperutils.FileUtils, httpClient piperhttp.Sender) error {
serviceKey, err := cpi.ReadCpiServiceKey(config.APIServiceKey)
if err != nil {
return err
}
clientOptions := piperhttp.ClientOptions{}
tokenParameters := cpi.TokenParameters{TokenURL: serviceKey.OAuth.OAuthTokenProviderURL, Username: serviceKey.OAuth.ClientID, Password: serviceKey.OAuth.ClientSecret, Client: httpClient}
token, tokenErr := cpi.CommonUtils.GetBearerToken(tokenParameters)
if tokenErr != nil {
return errors.Wrap(tokenErr, "failed to fetch Bearer Token")
}
clientOptions.Token = fmt.Sprintf("Bearer %s", token)
httpClient.SetOptions(clientOptions)
httpMethod := http.MethodPost
uploadApiProxyStatusURL := fmt.Sprintf("%s/apiportal/api/1.0/Transport.svc/APIProxies", serviceKey.OAuth.Host)
header := make(http.Header)
header.Add("Accept", "application/zip")
fileContent, readError := fileUtils.FileRead(config.FilePath)
if readError != nil {
return errors.Wrapf(readError, "Error reading file")
}
if !strings.Contains(config.FilePath, "zip") {
return errors.New("not valid zip archive")
}
payload := []byte(b64.StdEncoding.EncodeToString(fileContent))
apiProxyUploadStatusResp, httpErr := httpClient.SendRequest(httpMethod, uploadApiProxyStatusURL, bytes.NewBuffer(payload), header, nil)
failureMessage := "Failed to upload API Proxy artefact"
successMessage := "Successfully created api proxy artefact in API Portal"
httpFileUploadRequestParameters := cpi.HttpFileUploadRequestParameters{
ErrMessage: failureMessage,
FilePath: config.FilePath,
Response: apiProxyUploadStatusResp,
HTTPMethod: httpMethod,
HTTPURL: uploadApiProxyStatusURL,
HTTPErr: httpErr,
SuccessMessage: successMessage}
return cpi.HTTPUploadUtils.HandleHTTPFileUploadResponse(httpFileUploadRequestParameters)
}

View File

@@ -0,0 +1,164 @@
// 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 apiProxyUploadOptions struct {
APIServiceKey string `json:"apiServiceKey,omitempty"`
FilePath string `json:"filePath,omitempty"`
}
// ApiProxyUploadCommand Upload an api proxy artifact in to the API Portal
func ApiProxyUploadCommand() *cobra.Command {
const STEP_NAME = "apiProxyUpload"
metadata := apiProxyUploadMetadata()
var stepConfig apiProxyUploadOptions
var startTime time.Time
var logCollector *log.CollectorHook
var splunkClient *splunk.Splunk
telemetryClient := &telemetry.Telemetry{}
var createApiProxyUploadCmd = &cobra.Command{
Use: STEP_NAME,
Short: "Upload an api proxy artifact in to the API Portal",
Long: `With this step you can upload an api proxy artifact in to the API Portal using the OData API.
Learn more about the SAP API Management API for uploading 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() {
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)
}
apiProxyUpload(stepConfig, &stepTelemetryData)
stepTelemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addApiProxyUploadFlags(createApiProxyUploadCmd, &stepConfig)
return createApiProxyUploadCmd
}
func addApiProxyUploadFlags(cmd *cobra.Command, stepConfig *apiProxyUploadOptions) {
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.FilePath, "filePath", os.Getenv("PIPER_filePath"), "Specifies api proxy zip artifact relative file path")
cmd.MarkFlagRequired("apiServiceKey")
cmd.MarkFlagRequired("filePath")
}
// retrieve step metadata
func apiProxyUploadMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "apiProxyUpload",
Aliases: []config.Alias{},
Description: "Upload an api proxy artifact in to 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: "filePath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_filePath"),
},
},
},
},
}
return theMetaData
}

View File

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

125
cmd/apiProxyUpload_test.go Normal file
View File

@@ -0,0 +1,125 @@
package cmd
import (
"path/filepath"
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/stretchr/testify/assert"
)
func TestRunApiProxyUpload(t *testing.T) {
t.Parallel()
t.Run("Successfull Api Proxy Create Test", func(t *testing.T) {
filesMock := mock.FilesMock{}
path := filepath.Join("tempDir", "apiproxy.zip")
filesMock.AddFile(path, []byte("dummy content"))
exists, err := filesMock.FileExists(path)
if err != nil {
t.Fatal("Failed to create temporary file")
}
assert.True(t, exists)
apiServiceKey := `{
"oauth": {
"url": "https://demo",
"clientid": "demouser",
"clientsecret": "******",
"tokenurl": "https://demo/oauth/token"
}
}`
config := apiProxyUploadOptions{
APIServiceKey: apiServiceKey,
FilePath: path,
}
httpClient := httpMockCpis{CPIFunction: "ApiProxyUpload", ResponseBody: ``, TestType: "ApiProxyUploadPositiveCase"}
err = runApiProxyUpload(&config, nil, &filesMock, &httpClient)
if assert.NoError(t, err) {
t.Run("check url", func(t *testing.T) {
assert.Equal(t, "https://demo/apiportal/api/1.0/Transport.svc/APIProxies", httpClient.URL)
})
t.Run("check method", func(t *testing.T) {
assert.Equal(t, "POST", httpClient.Method)
})
}
})
t.Run("Failed case of API Proxy Create Test", func(t *testing.T) {
filesMock := mock.FilesMock{}
path := filepath.Join("tempDir", "apiproxy.zip")
filesMock.AddFile(path, []byte("dummy content"))
exists, err := filesMock.FileExists(path)
assert.NoError(t, err)
assert.True(t, exists)
apiServiceKey := `{
"oauth": {
"url": "https://demo",
"clientid": "demouser",
"clientsecret": "******",
"tokenurl": "https://demo/oauth/token"
}
}`
config := apiProxyUploadOptions{
APIServiceKey: apiServiceKey,
FilePath: path,
}
httpClient := httpMockCpis{CPIFunction: "ApiProxyArtifactFail", ResponseBody: ``, TestType: "NegativeApiProxyArtifactUploadResBody"}
uploadErr := runApiProxyUpload(&config, nil, &filesMock, &httpClient)
assert.EqualError(t, uploadErr, "HTTP POST request to https://demo/apiportal/api/1.0/Transport.svc/APIProxies failed with error: : Service not Found")
})
t.Run("file not exist", func(t *testing.T) {
filesMock := mock.FilesMock{}
apiServiceKey := `{
"oauth": {
"url": "https://demo",
"clientid": "demouser",
"clientsecret": "******",
"tokenurl": "https://demo/oauth/token"
}
}`
config := apiProxyUploadOptions{
APIServiceKey: apiServiceKey,
FilePath: "",
}
httpClient := httpMockCpis{CPIFunction: "ApiProxyArtifactFail", ResponseBody: ``, TestType: "NegativeApiProxyArtifactUploadResBody"}
uploadErr := runApiProxyUpload(&config, nil, &filesMock, &httpClient)
assert.EqualError(t, uploadErr, "Error reading file: could not read ''")
})
t.Run("file not zip", func(t *testing.T) {
filesMock := mock.FilesMock{}
path := filepath.Join("tempDir", "apiproxy.pptx")
filesMock.AddFile(path, []byte("dummy content"))
apiServiceKey := `{
"oauth": {
"url": "https://demo",
"clientid": "demouser",
"clientsecret": "******",
"tokenurl": "https://demo/oauth/token"
}
}`
config := apiProxyUploadOptions{
APIServiceKey: apiServiceKey,
FilePath: path,
}
httpClient := httpMockCpis{CPIFunction: "ApiProxyArtifactFail", ResponseBody: ``, TestType: "NegativeApiProxyArtifactUploadResBody"}
uploadErr := runApiProxyUpload(&config, nil, &filesMock, &httpClient)
assert.EqualError(t, uploadErr, "not valid zip archive")
})
}

View File

@@ -25,6 +25,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"abapEnvironmentRunAUnitTest": abapEnvironmentRunAUnitTestMetadata(),
"apiKeyValueMapDownload": apiKeyValueMapDownloadMetadata(),
"apiProxyDownload": apiProxyDownloadMetadata(),
"apiProxyUpload": apiProxyUploadMetadata(),
"artifactPrepareVersion": artifactPrepareVersionMetadata(),
"batsExecuteTests": batsExecuteTestsMetadata(),
"checkmarxExecuteScan": checkmarxExecuteScanMetadata(),

View File

@@ -175,6 +175,7 @@ func Execute() {
rootCmd.AddCommand(ShellExecuteCommand())
rootCmd.AddCommand(ApiProxyDownloadCommand())
rootCmd.AddCommand(ApiKeyValueMapDownloadCommand())
rootCmd.AddCommand(ApiProxyUploadCommand())
rootCmd.AddCommand(GradleExecuteBuildCommand())
addRootFlags(rootCmd)

View File

@@ -0,0 +1,29 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequisites
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}
## Example
Example configuration for the use in a `Jenkinsfile`.
```groovy
apiProxyUpload script: this
```
Example for the use in a YAML configuration file (such as `.pipeline/config.yaml`).
```yaml
steps:
<...>
apiProxyUpload:
apimApiServiceKeyCredentialsId: 'MY_API_SERVICE_KEY'
filePath: MY_API_PROXY_ZIP_FILE_PATH
```

View File

@@ -68,6 +68,7 @@ nav:
- abapEnvironmentRunAUnitTest: steps/abapEnvironmentRunAUnitTest.md
- apiKeyValueMapDownload: steps/apiKeyValueMapDownload.md
- apiProxyDownload: steps/apiProxyDownload.md
- apiProxyUpload: steps/apiProxyUpload.md
- artifactPrepareVersion: steps/artifactPrepareVersion.md
- batsExecuteTests: steps/batsExecuteTests.md
- buildExecute: steps/buildExecute.md

View File

@@ -27,6 +27,11 @@ type HttpCPIUtils interface {
HandleHTTPFileDownloadResponse() error
}
//HTTPUploadUtils for CPI
type HTTPUploadUtils interface {
HandleHTTPFileUploadResponse() error
}
//TokenParameters struct
type TokenParameters struct {
TokenURL, Username, Password string
@@ -39,6 +44,13 @@ type HttpFileDownloadRequestParameters struct {
Response *http.Response
}
//HTTPFileUploadRequestParameters struct
type HttpFileUploadRequestParameters struct {
ErrMessage, FilePath, HTTPMethod, HTTPURL, SuccessMessage string
Response *http.Response
HTTPErr error
}
// ServiceKey contains information about a CPI service key
type ServiceKey struct {
OAuth OAuth `json:"oauth"`
@@ -148,3 +160,32 @@ func (httpFileDownloadRequestParameters HttpFileDownloadRequestParameters) Handl
log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code : %v", responseBody, response.StatusCode)
return errors.Errorf("%s, Response Status code: %v", httpFileDownloadRequestParameters.ErrMessage, response.StatusCode)
}
// HandleHTTPFileUploadResponse - Handle the file upload response
func (httpFileUploadRequestParameters HttpFileUploadRequestParameters) HandleHTTPFileUploadResponse() error {
response := httpFileUploadRequestParameters.Response
httpErr := httpFileUploadRequestParameters.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 {
log.Entry().
WithField("Created Artifact", httpFileUploadRequestParameters.FilePath).
Info(httpFileUploadRequestParameters.SuccessMessage)
return 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", httpFileUploadRequestParameters.HTTPMethod, httpFileUploadRequestParameters.HTTPURL, string(responseBody))
}
return errors.Errorf("%s, Response Status code: %v", httpFileUploadRequestParameters.ErrMessage, response.StatusCode)
}

View File

@@ -57,7 +57,7 @@ func GetCPIFunctionMockResponse(functionName, testType string) (*http.Response,
return GetRespBodyHTTPStatusServiceErrorResponse()
}
return GetRespBodyHTTPStatusCreated()
case "IntegrationArtifactResourceUpdate", "IntegrationArtifactResourceDelete":
case "IntegrationArtifactResourceUpdate", "IntegrationArtifactResourceDelete", "ApiProxyUpload":
return GetRespBodyHTTPStatusOK()
default:
res := http.Response{

View File

@@ -0,0 +1,33 @@
metadata:
name: apiProxyUpload
description: Upload an api proxy artifact in to the API Portal
longDescription: |
With this step you can upload an api proxy artifact in to the API Portal using the OData API.
Learn more about the SAP API Management API for uploading 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: filePath
type: string
description: Specifies api proxy zip artifact relative file path
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true

View File

@@ -205,6 +205,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
'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/apiProxyUpload.yaml'
void call(Map parameters = [:]) {
List credentials = [
[type: 'token', id: 'apimApiServiceKeyCredentialsId', env: ['PIPER_apiServiceKey']]
]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}