1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-11-28 08:49:44 +02:00

ApiKeyValueMapUpload Command (#3407)

* ApiKeyValueMapUpload Command

* include command in metadata

* TestCase Fixes

* CodeReview Fixes

* CodeReview Fixes

* Code Review Fixes

* CodeReview Fixes

* CodeReview Fixes

* CodeReview FIxes

* CodeReview Fixes

* Documenation change

* documentation fix

* Documentation Fix

* Documentation Fix

* documentation Fix

* CodeReview Fixes

* CodeReview Fixes

* Revert changes

* Documentation Fix

* CodeReview FIxes

* Doc Fixes

* Code Review Fixes

* Code Review Fixes

* CodeReview FIxes

* Documentation Fix

* Documentation Changes

* Documentation Fix

* codereview fix

* Documentation Fix

* CodeReview Fixes

* CodeReview Fix

* Documentation FIx

* doc fix

* Doc Fix

* Documentation Fix

* codereview fix

* revert fix

* Code Review Fix

Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>
This commit is contained in:
Mayur Belur Mohan 2022-03-07 15:33:44 +05:30 committed by GitHub
parent 6398e61995
commit 9a3b800b9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 524 additions and 1 deletions

106
cmd/apiKeyValueMapUpload.go Normal file
View File

@ -0,0 +1,106 @@
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/Jeffail/gabs/v2"
"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 apiKeyValueMapUpload(config apiKeyValueMapUploadOptions, 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 := runApiKeyValueMapUpload(&config, telemetryData, httpClient)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runApiKeyValueMapUpload(config *apiKeyValueMapUploadOptions, telemetryData *telemetry.CustomData, 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
uploadApiKeyValueMapStatusURL := fmt.Sprintf("%s/apiportal/api/1.0/Management.svc/KeyMapEntries", serviceKey.OAuth.Host)
header := make(http.Header)
header.Add("Content-Type", "application/json")
header.Add("Accept", "application/json")
payload, jsonErr := createJSONPayload(config)
if jsonErr != nil {
return jsonErr
}
apiProxyUploadStatusResp, httpErr := httpClient.SendRequest(httpMethod, uploadApiKeyValueMapStatusURL, payload, header, nil)
if httpErr != nil {
return errors.Wrapf(httpErr, "HTTP %q request to %q failed with error", httpMethod, uploadApiKeyValueMapStatusURL)
}
if apiProxyUploadStatusResp != nil && apiProxyUploadStatusResp.Body != nil {
defer apiProxyUploadStatusResp.Body.Close()
}
if apiProxyUploadStatusResp == nil {
return errors.Errorf("did not retrieve a HTTP response")
}
if apiProxyUploadStatusResp.StatusCode == http.StatusCreated {
log.Entry().
WithField("KeyValueMap", config.KeyValueMapName).
Info("Successfully created api key value map artefact in API Portal")
return nil
}
response, readErr := ioutil.ReadAll(apiProxyUploadStatusResp.Body)
if readErr != nil {
return errors.Wrapf(readErr, "HTTP response body could not be read, Response status code: %v", apiProxyUploadStatusResp.StatusCode)
}
log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code: %v", string(response), apiProxyUploadStatusResp.StatusCode)
return errors.Errorf("Failed to upload API key value map artefact, Response Status code: %v", apiProxyUploadStatusResp.StatusCode)
}
//createJSONPayload -return http payload as byte array
func createJSONPayload(config *apiKeyValueMapUploadOptions) (*bytes.Buffer, error) {
jsonObj := gabs.New()
jsonObj.Set(config.Key, "name")
jsonObj.Set(config.KeyValueMapName, "map_name")
jsonObj.Set(config.Value, "value")
jsonRootObj := gabs.New()
jsonRootObj.Set(config.KeyValueMapName, "name")
jsonRootObj.Set(true, "encrypted")
jsonRootObj.Set("ENV", "scope")
jsonRootObj.ArrayAppend(jsonObj, "keyMapEntryValues")
jsonBody, jsonErr := json.Marshal(jsonRootObj)
if jsonErr != nil {
return nil, errors.Wrapf(jsonErr, "json payload is invalid for key value map %q", config.KeyValueMapName)
}
payload := bytes.NewBuffer([]byte(jsonBody))
return payload, nil
}

View File

@ -0,0 +1,188 @@
// 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 apiKeyValueMapUploadOptions struct {
APIServiceKey string `json:"apiServiceKey,omitempty"`
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
KeyValueMapName string `json:"keyValueMapName,omitempty"`
}
// ApiKeyValueMapUploadCommand this steps creates an API key value map artifact in the API Portal
func ApiKeyValueMapUploadCommand() *cobra.Command {
const STEP_NAME = "apiKeyValueMapUpload"
metadata := apiKeyValueMapUploadMetadata()
var stepConfig apiKeyValueMapUploadOptions
var startTime time.Time
var logCollector *log.CollectorHook
var splunkClient *splunk.Splunk
telemetryClient := &telemetry.Telemetry{}
var createApiKeyValueMapUploadCmd = &cobra.Command{
Use: STEP_NAME,
Short: "this steps creates an API key value map artifact in the API Portal",
Long: `This steps creates an API key value map artifact in the API Portal using the OData API.
Learn more about the SAP API Management API for creating an API key value map 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)
}
apiKeyValueMapUpload(stepConfig, &stepTelemetryData)
stepTelemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addApiKeyValueMapUploadFlags(createApiKeyValueMapUploadCmd, &stepConfig)
return createApiKeyValueMapUploadCmd
}
func addApiKeyValueMapUploadFlags(cmd *cobra.Command, stepConfig *apiKeyValueMapUploadOptions) {
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.Key, "key", os.Getenv("PIPER_key"), "Specifies API key name of API key value map")
cmd.Flags().StringVar(&stepConfig.Value, "value", os.Getenv("PIPER_value"), "Specifies API key value of API key value map")
cmd.Flags().StringVar(&stepConfig.KeyValueMapName, "keyValueMapName", os.Getenv("PIPER_keyValueMapName"), "Specifies the name of the API key value map")
cmd.MarkFlagRequired("apiServiceKey")
cmd.MarkFlagRequired("key")
cmd.MarkFlagRequired("value")
cmd.MarkFlagRequired("keyValueMapName")
}
// retrieve step metadata
func apiKeyValueMapUploadMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "apiKeyValueMapUpload",
Aliases: []config.Alias{},
Description: "this steps creates an API key value map artifact in 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: "key",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_key"),
},
{
Name: "value",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_value"),
},
{
Name: "keyValueMapName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_keyValueMapName"),
},
},
},
},
}
return theMetaData
}

View File

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

View File

@ -0,0 +1,98 @@
package cmd
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRunApiKeyValueMapUpload(t *testing.T) {
t.Parallel()
t.Run("Successfull Api Key Value Map Create Test", func(t *testing.T) {
// init
config := getDefaultOptions()
httpClient := httpMockCpis{CPIFunction: "ApiKeyValueMapUpload", ResponseBody: ``, TestType: "PositiveCase"}
// test
err := runApiKeyValueMapUpload(&config, nil, &httpClient)
// assert
if assert.NoError(t, err) {
t.Run("check url", func(t *testing.T) {
assert.Equal(t, "https://demo/apiportal/api/1.0/Management.svc/KeyMapEntries", httpClient.URL)
})
t.Run("check method", func(t *testing.T) {
assert.Equal(t, "POST", httpClient.Method)
})
}
})
t.Run("Failed case of API Key Value Map Create Test", func(t *testing.T) {
// init
config := getDefaultOptions()
httpClient := httpMockCpis{CPIFunction: "ApiKeyValueMapUpload", ResponseBody: ``, TestType: "Negative"}
// test
err := runApiKeyValueMapUpload(&config, nil, &httpClient)
// assert
assert.EqualError(t, err, "HTTP \"POST\" request to \"https://demo/apiportal/api/1.0/Management.svc/KeyMapEntries\" failed with error: 401 Unauthorized")
})
t.Run("Test API Key Value Map payload", func(t *testing.T) {
// init
config := getDefaultOptions()
testPayload := bytes.NewBuffer([]byte("{\"encrypted\":true,\"keyMapEntryValues\":[{\"map_name\":\"demoMap\",\"name\":\"demo\",\"value\":\"name\"}],\"name\":\"demoMap\",\"scope\":\"ENV\"}"))
// test
payload, err := createJSONPayload(&config)
// assert
require.NoError(t, err)
assert.Equal(t, testPayload, payload)
})
t.Run("Http Response not accepted Test case", func(t *testing.T) {
// init
config := getDefaultOptions()
httpClient := httpMockCpis{CPIFunction: "ApiKeyValueMapUpload", ResponseBody: ``, TestType: "HttpResponseNotAccepted"}
// test
err := runApiKeyValueMapUpload(&config, nil, &httpClient)
// assert
assert.EqualError(t, err, "Failed to upload API key value map artefact, Response Status code: 202")
})
t.Run("Http Response not accepted Test case", func(t *testing.T) {
// init
config := getDefaultOptions()
httpClient := httpMockCpis{CPIFunction: "ApiKeyValueMapUpload", ResponseBody: ``, TestType: "NilHttpResponse"}
// test
err := runApiKeyValueMapUpload(&config, nil, &httpClient)
// assert
assert.EqualError(t, err, "HTTP \"POST\" request to \"https://demo/apiportal/api/1.0/Management.svc/KeyMapEntries\" failed with error: invalid payalod")
})
}
func getDefaultOptions() apiKeyValueMapUploadOptions {
return apiKeyValueMapUploadOptions{
APIServiceKey: `{
"oauth": {
"url": "https://demo",
"clientid": "demouser",
"clientsecret": "******",
"tokenurl": "https://demo/oauth/token"
}
}`,
Key: "demo",
Value: "name",
KeyValueMapName: "demoMap",
}
}

View File

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

View File

@ -181,6 +181,7 @@ func Execute() {
rootCmd.AddCommand(ApiKeyValueMapDownloadCommand())
rootCmd.AddCommand(ApiProxyUploadCommand())
rootCmd.AddCommand(GradleExecuteBuildCommand())
rootCmd.AddCommand(ApiKeyValueMapUploadCommand())
addRootFlags(rootCmd)

View File

@ -0,0 +1,44 @@
# ${docGenStepName}
## ${docGenDescription}
With this step you can store one or more key value pairs of data stored in a group called a map or key value map.
To consume the ApiKeyValueMapUpload step, proceed as follows:
* Copy the SAP API management service key from the SAP BTP sub account cockpit, under instance and subscriptions &rarr; service API Management, API portal, which was created under apiportal-apiaccess plan.
* Store your service key created for SAP API Management in the Jenkins server as a secret text.
* Create a new Jenkins pipeline designated for the ApiKeyValueMapUpload 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/)
* Using the ApiKeyValueMapUpload step, you can create a new API key value map in the API portal.
* The ApiKeyValueMapUpload step allows you to prevent command execution in case an API key value map already exists.
* If API key value map already exists, then delete it and execute the piper step again, which will create a new API Key value Map.
* ApiKeyValueMapUpload only supports create operation.
## Prerequisites
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}
## Example
Configuration example for a `Jenkinsfile`:
```groovy
apiKeyValueMapUpload script: this
```
Configuration example for a YAML file(for example `.pipeline/config.yaml`):
```yaml
steps:
<...>
apiKeyValueMapUpload:
apimApiServiceKeyCredentialsId: 'MY_API_SERVICE_KEY'
key: API_KEY_NAME
value: API_KEY_VALUE
keyValueMapName: API_KEY_VALUE_MAP_NAME
```

View File

@ -67,6 +67,7 @@ nav:
- abapEnvironmentRunATCCheck: steps/abapEnvironmentRunATCCheck.md
- abapEnvironmentRunAUnitTest: steps/abapEnvironmentRunAUnitTest.md
- apiKeyValueMapDownload: steps/apiKeyValueMapDownload.md
- apiKeyValueMapUpload: steps/apiKeyValueMapUpload.md
- apiProxyDownload: steps/apiProxyDownload.md
- apiProxyUpload: steps/apiProxyUpload.md
- artifactPrepareVersion: steps/artifactPrepareVersion.md

View File

@ -53,10 +53,16 @@ func GetCPIFunctionMockResponse(functionName, testType string) (*http.Response,
return TriggerIntegrationTestMockResponse(testType)
case "IntegrationArtifactGetMplStatusError":
return GetIntegrationArtifactDeployErrorStatusMockResponseBody()
case "IntegrationArtifactResourceCreate":
case "IntegrationArtifactResourceCreate", "ApiKeyValueMapUpload":
if testType == "Negative" {
return GetRespBodyHTTPStatusServiceErrorResponse()
}
if testType == "HttpResponseNotAccepted" {
return GetEmptyHTTPResponseBodyAndErrorNil()
}
if testType == "NilHttpResponse" {
return nil, errors.New("invalid payalod")
}
return GetRespBodyHTTPStatusCreated()
case "IntegrationArtifactResourceUpdate", "IntegrationArtifactResourceDelete", "ApiProxyUpload":
return GetRespBodyHTTPStatusOK()

View File

@ -0,0 +1,49 @@
metadata:
name: apiKeyValueMapUpload
description: this steps creates an API key value map artifact in the API Portal
longDescription: |
This steps creates an API key value map artifact in the API Portal using the OData API.
Learn more about the SAP API Management API for creating an API key value map 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: key
type: string
description: Specifies API key name of API key value map
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: value
type: string
description: Specifies API key value of API key value map
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
- name: keyValueMapName
type: string
description: Specifies the name of the API key value map
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true

View File

@ -210,6 +210,7 @@ public class CommonStepsTest extends BasePiperTest{
'apiProxyUpload', //implementing new golang pattern without fields
'gradleExecuteBuild', //implementing new golang pattern without fields
'shellExecute', //implementing new golang pattern without fields
'apiKeyValueMapUpload', //implementing new golang pattern without fields
]
@Test

View File

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