You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	Abap environment update addon product (#4774)
* new Piper step abapEnvironmentUpdateAddOnProduct * modified entity json format and some minor function changes * modified groovy file for pipelineStageIntTests and addonDescriptor to be mandatory in yaml file * sync with fork branch ranliii/abap-environment-update-addon-product * added generated file * fail the step as long as addon update not successful and unit tests * added docu for the new step * tried to fix groovy unit test * tried to fix groovy unit test 2 * for test * fixed error * fixed error 2 * tried to fix groovy unit test error * added groovy unit test for new Piper step * tried to fix groovy unit test error * tried to fix groovy unit test error 2 * changes after first review * remove .DS_Store * for test * revert test relevant changes * try to fix groovy test error * try to fix groovy error * 3rd try to fix groovy test error * rewrite the failed groovy test * small changes and try with timeout as well as poll interval * changes for test * revert test-related changes * try to fix errors * Revert "Merge branch 'master' into abap-environment-update-addon-product" This reverts commit1ee0bcd80d, reversing changes made to3c4a99dfb0. * try to fix error * try to fix error 2 * try to fix error 3 * align go.mod with master branch * revert go.mod to commit3c4a99d* for test * revert test changes * new unit test * Revert "Revert "Merge branch 'master' into abap-environment-update-addon-product"" This reverts commit363c038001. * go generate after merging master --------- Co-authored-by: Jk1484 <35270240+Jk1484@users.noreply.github.com> Co-authored-by: Ran Li <ran.li01@sap.com> Co-authored-by: tiloKo <70266685+tiloKo@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										474
									
								
								cmd/abapLandscapePortalUpdateAddOnProduct.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										474
									
								
								cmd/abapLandscapePortalUpdateAddOnProduct.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,474 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/tls" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/abaputils" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"golang.org/x/exp/slices" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	StatusComplete       = "C" | ||||
| 	StatusError          = "E" | ||||
| 	StatusInProgress     = "I" | ||||
| 	StatusScheduled      = "S" | ||||
| 	StatusAborted        = "X" | ||||
| 	maxRuntimeInMinute   = time.Duration(120) * time.Minute | ||||
| 	pollIntervalInSecond = time.Duration(30) * time.Second | ||||
| ) | ||||
|  | ||||
| type httpClient interface { | ||||
| 	Do(*http.Request) (*http.Response, error) | ||||
| } | ||||
|  | ||||
| type uaa struct { | ||||
| 	CertUrl     string `json:"certurl"` | ||||
| 	ClientId    string `json:"clientid"` | ||||
| 	Certificate string `json:"certificate"` | ||||
| 	Key         string `json:"key"` | ||||
| } | ||||
|  | ||||
| type serviceKey struct { | ||||
| 	Url string `json:"url"` | ||||
| 	Uaa uaa    `json:"uaa"` | ||||
| } | ||||
|  | ||||
| type accessTokenResp struct { | ||||
| 	AccessToken string `json:"access_token"` | ||||
| 	TokenType   string `json:"token_type"` | ||||
| 	ExpiresIn   int    `json:"expires_in"` | ||||
| 	Scope       string `json:"scope"` | ||||
| } | ||||
|  | ||||
| type systemEntity struct { | ||||
| 	SystemId     string `json:"SystemId"` | ||||
| 	SystemNumber string `json:"SystemNumber"` | ||||
| 	ZoneId       string `json:"zone_id"` | ||||
| } | ||||
|  | ||||
| type reqEntity struct { | ||||
| 	RequestId string `json:"RequestId"` | ||||
| 	ZoneId    string `json:"zone_id"` | ||||
| 	Status    string `json:"Status"` | ||||
| 	SystemId  string `json:"SystemId"` | ||||
| } | ||||
|  | ||||
| type updateAddOnReq struct { | ||||
| 	ProductName    string `json:"productName"` | ||||
| 	ProductVersion string `json:"productVersion"` | ||||
| } | ||||
|  | ||||
| type updateAddOnResp struct { | ||||
| 	RequestId string `json:"requestId"` | ||||
| 	ZoneId    string `json:"zoneId"` | ||||
| 	Status    string `json:"status"` | ||||
| 	SystemId  string `json:"systemId"` | ||||
| } | ||||
|  | ||||
| var client, clientToken httpClient | ||||
| var servKey serviceKey | ||||
|  | ||||
| func abapLandscapePortalUpdateAddOnProduct(config abapLandscapePortalUpdateAddOnProductOptions, telemetryData *telemetry.CustomData) { | ||||
| 	client = &http.Client{} | ||||
|  | ||||
| 	if prepareErr := parseServiceKeyAndPrepareAccessTokenHttpClient(config.LandscapePortalAPIServiceKey, &clientToken, &servKey); prepareErr != nil { | ||||
| 		err := fmt.Errorf("Failed to prepare credentials to get access token of LP API. Error: %v\n", prepareErr) | ||||
| 		log.Entry().WithError(err).Fatal("step execution failed") | ||||
| 	} | ||||
| 	// 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 := runAbapLandscapePortalUpdateAddOnProduct(&config, client, clientToken, servKey, maxRuntimeInMinute, pollIntervalInSecond) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("step execution failed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func runAbapLandscapePortalUpdateAddOnProduct(config *abapLandscapePortalUpdateAddOnProductOptions, client httpClient, clientToken httpClient, servKey serviceKey, maxRuntimeInMinute time.Duration, pollIntervalInSecond time.Duration) error { | ||||
| 	var systemId, reqId, reqStatus string | ||||
| 	var getStatusReq http.Request | ||||
| 	var err error | ||||
|  | ||||
| 	// get system | ||||
| 	if getSystemErr := getSystemBySystemNumber(config, client, clientToken, servKey, &systemId); getSystemErr != nil { | ||||
| 		err = fmt.Errorf("Failed to get system with systemNumber %v. Error: %v\n", config.AbapSystemNumber, getSystemErr) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// update addon in the system | ||||
| 	if updateAddOnErr := updateAddOn(config.AddonDescriptorFileName, client, clientToken, servKey, systemId, &reqId); updateAddOnErr != nil { | ||||
| 		err = fmt.Errorf("Failed to update addon in the system with systemId %v. Error: %v\n", systemId, updateAddOnErr) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// prepare http request to poll status of addon update | ||||
| 	if prepareGetStatusHttpRequestErr := prepareGetStatusHttpRequest(clientToken, servKey, reqId, &getStatusReq); prepareGetStatusHttpRequestErr != nil { | ||||
| 		err = fmt.Errorf("Failed to prepare http request to poll status of addon update request %v. Error: %v\n", reqId, prepareGetStatusHttpRequestErr) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// keep polling request status until it reaches a final status or timeout | ||||
| 	if waitToBeFinishedErr := waitToBeFinished(maxRuntimeInMinute, pollIntervalInSecond, client, &getStatusReq, reqId, &reqStatus); waitToBeFinishedErr != nil { | ||||
| 		err = fmt.Errorf("Error occurred before a final status can be reached. Error: %v\n", waitToBeFinishedErr) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// respond to the final status of addon update | ||||
| 	if respondToUpdateAddOnFinalStatusErr := respondToUpdateAddOnFinalStatus(client, clientToken, servKey, reqId, reqStatus); respondToUpdateAddOnFinalStatusErr != nil { | ||||
| 		err = fmt.Errorf("The final status of addon update is %v. Error: %v\n", reqStatus, respondToUpdateAddOnFinalStatusErr) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to parse service key JSON and prepare http client for access token | ||||
| func parseServiceKeyAndPrepareAccessTokenHttpClient(servKeyJSON string, clientToken *httpClient, servKey *serviceKey) error { | ||||
| 	// parse the service key from JSON string to struct | ||||
| 	if parseServiceKeyErr := json.Unmarshal([]byte(servKeyJSON), servKey); parseServiceKeyErr != nil { | ||||
| 		return parseServiceKeyErr | ||||
| 	} | ||||
|  | ||||
| 	// configure http client with certificate authorization for getLPAPIAccessToken | ||||
| 	certSource := servKey.Uaa.Certificate | ||||
| 	keySource := servKey.Uaa.Key | ||||
|  | ||||
| 	certPem := strings.Replace(certSource, `\n`, "\n", -1) | ||||
| 	keyPem := strings.Replace(keySource, `\n`, "\n", -1) | ||||
|  | ||||
| 	certificate, certErr := tls.X509KeyPair([]byte(certPem), []byte(keyPem)) | ||||
| 	if certErr != nil { | ||||
| 		return certErr | ||||
| 	} | ||||
|  | ||||
| 	*clientToken = &http.Client{ | ||||
| 		Transport: &http.Transport{ | ||||
| 			TLSClientConfig: &tls.Config{ | ||||
| 				Certificates: []tls.Certificate{certificate}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to get access token of Landscape Portal API | ||||
| func getLPAPIAccessToken(clientToken httpClient, servKey serviceKey) (string, error) { | ||||
| 	authRawURL := servKey.Uaa.CertUrl + "/oauth/token" | ||||
|  | ||||
| 	// configure request body | ||||
| 	reqBody := url.Values{} | ||||
| 	reqBody.Set("grant_type", "client_credentials") | ||||
| 	reqBody.Set("client_id", servKey.Uaa.ClientId) | ||||
|  | ||||
| 	encodedReqBody := reqBody.Encode() | ||||
|  | ||||
| 	// generate http request and configure header | ||||
| 	req, reqErr := http.NewRequest(http.MethodPost, authRawURL, strings.NewReader(encodedReqBody)) | ||||
| 	if reqErr != nil { | ||||
| 		return "", reqErr | ||||
| 	} | ||||
|  | ||||
| 	req.Header.Add("Content-Type", "application/x-www-form-urlencoded") | ||||
|  | ||||
| 	resp, getAccessTokenErr := clientToken.Do(req) | ||||
| 	if getAccessTokenErr != nil { | ||||
| 		return "", getAccessTokenErr | ||||
| 	} | ||||
|  | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// error case of response status code being non 200 | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		err := fmt.Errorf("Unexpected response status %v received when getting access token of LP API.\n", resp.Status) | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// read and parse response body | ||||
| 	respBody := accessTokenResp{} | ||||
| 	if parseRespBodyErr := parseRespBody[accessTokenResp](resp, &respBody); parseRespBodyErr != nil { | ||||
| 		return "", parseRespBodyErr | ||||
| 	} | ||||
|  | ||||
| 	return respBody.AccessToken, nil | ||||
| } | ||||
|  | ||||
| // this function is used to check the existence of integration test system | ||||
| func getSystemBySystemNumber(config *abapLandscapePortalUpdateAddOnProductOptions, client httpClient, clientToken httpClient, servKey serviceKey, systemId *string) error { | ||||
| 	accessToken, getAccessTokenErr := getLPAPIAccessToken(clientToken, servKey) | ||||
| 	if getAccessTokenErr != nil { | ||||
| 		return getAccessTokenErr | ||||
| 	} | ||||
|  | ||||
| 	// define the raw url of the request and parse it into required form used in http.Request | ||||
| 	getSystemRawURL := servKey.Url + "/api/systems/" + config.AbapSystemNumber | ||||
| 	getSystemURL, urlParseErr := url.Parse(getSystemRawURL) | ||||
| 	if urlParseErr != nil { | ||||
| 		return urlParseErr | ||||
| 	} | ||||
|  | ||||
| 	req := http.Request{ | ||||
| 		Method: http.MethodGet, | ||||
| 		URL:    getSystemURL, | ||||
| 		Header: map[string][]string{ | ||||
| 			"Authorization": {"Bearer " + accessToken}, | ||||
| 			"Content-Type":  {"application/json"}, | ||||
| 			"Accept":        {"application/json"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	resp, getSystemErr := client.Do(&req) | ||||
| 	if getSystemErr != nil { | ||||
| 		return getSystemErr | ||||
| 	} | ||||
|  | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// error case of response status code being non 200 | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		err := fmt.Errorf("Unexpected response status %v received when getting system with systemNumber %v.\n", resp.Status, config.AbapSystemNumber) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// read and parse response body | ||||
| 	respBody := systemEntity{} | ||||
| 	if parseRespBodyErr := parseRespBody[systemEntity](resp, &respBody); parseRespBodyErr != nil { | ||||
| 		return parseRespBodyErr | ||||
| 	} | ||||
|  | ||||
| 	*systemId = respBody.SystemId | ||||
|  | ||||
| 	fmt.Printf("Successfully got ABAP system with systemNumber %v and systemId %v.\n", respBody.SystemNumber, respBody.SystemId) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to define and maintain the request body of querying status of addon update request | ||||
| func prepareGetStatusHttpRequest(clientToken httpClient, servKey serviceKey, reqId string, getStatusReq *http.Request) error { | ||||
| 	accessToken, getAccessTokenErr := getLPAPIAccessToken(clientToken, servKey) | ||||
| 	if getAccessTokenErr != nil { | ||||
| 		return getAccessTokenErr | ||||
| 	} | ||||
|  | ||||
| 	// define the raw url of the request and parse it into required form used in http.Request | ||||
| 	getStatusRawURL := servKey.Url + "/api/requests/" + reqId | ||||
| 	getStatusURL, urlParseErr := url.Parse(getStatusRawURL) | ||||
| 	if urlParseErr != nil { | ||||
| 		return urlParseErr | ||||
| 	} | ||||
|  | ||||
| 	req := http.Request{ | ||||
| 		Method: http.MethodGet, | ||||
| 		URL:    getStatusURL, | ||||
| 		Header: map[string][]string{ | ||||
| 			"Authorization": {"Bearer " + accessToken}, | ||||
| 			"Content-Type":  {"application/json"}, | ||||
| 			"Accept":        {"application/json"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	// store the req in the global variable for later usage | ||||
| 	*getStatusReq = req | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to poll status of addon update request and maintain the status | ||||
| func pollStatusOfUpdateAddOn(client httpClient, req *http.Request, reqId string, status *string) error { | ||||
| 	resp, getStatusErr := client.Do(req) | ||||
| 	if getStatusErr != nil { | ||||
| 		return getStatusErr | ||||
| 	} | ||||
|  | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// error case of response status code being non 200 | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		err := fmt.Errorf("Unexpected response status %v received when polling status of request %v.\n", resp.Status, reqId) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// read and parse response body | ||||
| 	respBody := reqEntity{} | ||||
| 	if parseRespBodyErr := parseRespBody[reqEntity](resp, &respBody); parseRespBodyErr != nil { | ||||
| 		return parseRespBodyErr | ||||
| 	} | ||||
|  | ||||
| 	*status = respBody.Status | ||||
|  | ||||
| 	fmt.Printf("Successfully polled status %v of request %v.\n", respBody.Status, respBody.RequestId) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to update addon | ||||
| func updateAddOn(addOnFileName string, client httpClient, clientToken httpClient, servKey serviceKey, systemId string, reqId *string) error { | ||||
| 	accessToken, getAccessTokenErr := getLPAPIAccessToken(clientToken, servKey) | ||||
| 	if getAccessTokenErr != nil { | ||||
| 		return getAccessTokenErr | ||||
| 	} | ||||
|  | ||||
| 	// read productName and productVersion from addon.yml | ||||
| 	addOnDescriptor, readAddOnErr := abaputils.ReadAddonDescriptor(addOnFileName) | ||||
| 	if readAddOnErr != nil { | ||||
| 		return readAddOnErr | ||||
| 	} | ||||
|  | ||||
| 	// define the raw url of the request and parse it into required form used in http.Request | ||||
| 	updateAddOnRawURL := servKey.Url + "/api/systems/" + systemId + "/deployProduct" | ||||
|  | ||||
| 	// define the request body as a struct | ||||
| 	reqBody := updateAddOnReq{ | ||||
| 		ProductName:    addOnDescriptor.AddonProduct, | ||||
| 		ProductVersion: addOnDescriptor.AddonVersionYAML, | ||||
| 	} | ||||
|  | ||||
| 	// encode the request body to JSON | ||||
| 	var reqBuff bytes.Buffer | ||||
| 	json.NewEncoder(&reqBuff).Encode(reqBody) | ||||
|  | ||||
| 	req, reqErr := http.NewRequest(http.MethodPost, updateAddOnRawURL, &reqBuff) | ||||
| 	if reqErr != nil { | ||||
| 		return reqErr | ||||
| 	} | ||||
|  | ||||
| 	req.Header = map[string][]string{ | ||||
| 		"Authorization": {"Bearer " + accessToken}, | ||||
| 		"Content-Type":  {"application/json"}, | ||||
| 		"Accept":        {"application/json"}, | ||||
| 	} | ||||
|  | ||||
| 	resp, updateAddOnErr := client.Do(req) | ||||
| 	if updateAddOnErr != nil { | ||||
| 		return updateAddOnErr | ||||
| 	} | ||||
|  | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// error case of response status code being non 200 | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		err := fmt.Errorf("Unexpected response status %v received when updating addon in system with systemId %v.\n", resp.Status, systemId) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// read and parse response body | ||||
| 	respBody := updateAddOnResp{} | ||||
| 	if parseRespBodyErr := parseRespBody[updateAddOnResp](resp, &respBody); parseRespBodyErr != nil { | ||||
| 		return parseRespBodyErr | ||||
| 	} | ||||
|  | ||||
| 	*reqId = respBody.RequestId | ||||
|  | ||||
| 	fmt.Printf("Successfully triggered addon update in system with systemId %v, the returned request id is %v.\n", systemId, respBody.RequestId) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to cancel addon update | ||||
| func cancelUpdateAddOn(client httpClient, clientToken httpClient, servKey serviceKey, reqId string) error { | ||||
| 	accessToken, getAccessTokenErr := getLPAPIAccessToken(clientToken, servKey) | ||||
| 	if getAccessTokenErr != nil { | ||||
| 		return getAccessTokenErr | ||||
| 	} | ||||
|  | ||||
| 	// define the raw url of the request and parse it into required form used in http.Request | ||||
| 	cancelUpdateAddOnRawURL := servKey.Url + "/api/requests/" + reqId | ||||
| 	cancelUpdateAddOnURL, urlParseErr := url.Parse(cancelUpdateAddOnRawURL) | ||||
| 	if urlParseErr != nil { | ||||
| 		return urlParseErr | ||||
| 	} | ||||
|  | ||||
| 	req := http.Request{ | ||||
| 		Method: http.MethodDelete, | ||||
| 		URL:    cancelUpdateAddOnURL, | ||||
| 		Header: map[string][]string{ | ||||
| 			"Authorization": {"Bearer " + accessToken}, | ||||
| 			"Content-Type":  {"application/json"}, | ||||
| 			"Accept":        {"application/json"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	resp, cancelUpdateAddOnErr := client.Do(&req) | ||||
| 	if cancelUpdateAddOnErr != nil { | ||||
| 		return cancelUpdateAddOnErr | ||||
| 	} | ||||
|  | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// error case of response status code being non 204 | ||||
| 	if resp.StatusCode != http.StatusNoContent { | ||||
| 		err := fmt.Errorf("Unexpected response status %v received when canceling addon update request %v.\n", resp.Status, reqId) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	fmt.Printf("Successfully canceled addon update request %v.\n", reqId) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to respond to a final status of addon update | ||||
| func respondToUpdateAddOnFinalStatus(client httpClient, clientToken httpClient, servKey serviceKey, reqId string, status string) error { | ||||
| 	switch status { | ||||
| 	case StatusComplete: | ||||
| 		fmt.Println("Addon update succeeded.") | ||||
| 	case StatusError: | ||||
| 		fmt.Println("Addon update failed and will be canceled.") | ||||
|  | ||||
| 		if cancelUpdateAddOnErr := cancelUpdateAddOn(client, clientToken, servKey, reqId); cancelUpdateAddOnErr != nil { | ||||
| 			err := fmt.Errorf("Failed to cancel addon update. Error: %v\n", cancelUpdateAddOnErr) | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		err := fmt.Errorf("Addon update failed.\n") | ||||
| 		return err | ||||
|  | ||||
| 	case StatusAborted: | ||||
| 		fmt.Println("Addon update was aborted.") | ||||
| 		err := fmt.Errorf("Addon update was aborted.\n") | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to parse response body of http request | ||||
| func parseRespBody[T comparable](resp *http.Response, respBody *T) error { | ||||
| 	respBodyRaw, readRespErr := io.ReadAll(resp.Body) | ||||
| 	if readRespErr != nil { | ||||
| 		return readRespErr | ||||
| 	} | ||||
|  | ||||
| 	if decodeRespBodyErr := json.Unmarshal(respBodyRaw, &respBody); decodeRespBodyErr != nil { | ||||
| 		return decodeRespBodyErr | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // this function is used to wait for a final status/timeout | ||||
| func waitToBeFinished(maxRuntimeInMinute time.Duration, pollIntervalInSecond time.Duration, client httpClient, getStatusReq *http.Request, reqId string, reqStatus *string) error { | ||||
| 	timeout := time.After(maxRuntimeInMinute) | ||||
| 	ticker := time.Tick(pollIntervalInSecond) | ||||
| 	reqFinalStatus := []string{StatusComplete, StatusError, StatusAborted} | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-timeout: | ||||
| 			return fmt.Errorf("Timed out: max runtime %v reached.", maxRuntimeInMinute) | ||||
| 		case <-ticker: | ||||
| 			if pollStatusOfUpdateAddOnErr := pollStatusOfUpdateAddOn(client, getStatusReq, reqId, reqStatus); pollStatusOfUpdateAddOnErr != nil { | ||||
| 				err := fmt.Errorf("Error happened when waiting for the addon update request %v to reach a final status. Error: %v\n", reqId, pollStatusOfUpdateAddOnErr) | ||||
| 				return err | ||||
| 			} | ||||
| 			if !slices.Contains(reqFinalStatus, *reqStatus) { | ||||
| 				fmt.Printf("Addon update request %v is still in progress, will poll the status in %v.\n", reqId, pollIntervalInSecond) | ||||
| 			} else { | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										185
									
								
								cmd/abapLandscapePortalUpdateAddOnProduct_generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								cmd/abapLandscapePortalUpdateAddOnProduct_generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| // 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 abapLandscapePortalUpdateAddOnProductOptions struct { | ||||
| 	LandscapePortalAPIServiceKey string `json:"landscapePortalAPIServiceKey,omitempty"` | ||||
| 	AbapSystemNumber             string `json:"abapSystemNumber,omitempty"` | ||||
| 	AddonDescriptorFileName      string `json:"addonDescriptorFileName,omitempty"` | ||||
| } | ||||
|  | ||||
| // AbapLandscapePortalUpdateAddOnProductCommand Update the AddOn product in SAP BTP ABAP Environment system of Landscape Portal | ||||
| func AbapLandscapePortalUpdateAddOnProductCommand() *cobra.Command { | ||||
| 	const STEP_NAME = "abapLandscapePortalUpdateAddOnProduct" | ||||
|  | ||||
| 	metadata := abapLandscapePortalUpdateAddOnProductMetadata() | ||||
| 	var stepConfig abapLandscapePortalUpdateAddOnProductOptions | ||||
| 	var startTime time.Time | ||||
| 	var logCollector *log.CollectorHook | ||||
| 	var splunkClient *splunk.Splunk | ||||
| 	telemetryClient := &telemetry.Telemetry{} | ||||
|  | ||||
| 	var createAbapLandscapePortalUpdateAddOnProductCmd = &cobra.Command{ | ||||
| 		Use:   STEP_NAME, | ||||
| 		Short: "Update the AddOn product in SAP BTP ABAP Environment system of Landscape Portal", | ||||
| 		Long:  `This step describes the AddOn product update in SAP BTP ABAP Environment system of Landscape Portal`, | ||||
| 		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.LandscapePortalAPIServiceKey) | ||||
|  | ||||
| 			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 || len(GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint) > 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() { | ||||
| 				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.Initialize(GeneralConfig.CorrelationID, | ||||
| 						GeneralConfig.HookConfig.SplunkConfig.Dsn, | ||||
| 						GeneralConfig.HookConfig.SplunkConfig.Token, | ||||
| 						GeneralConfig.HookConfig.SplunkConfig.Index, | ||||
| 						GeneralConfig.HookConfig.SplunkConfig.SendLogs) | ||||
| 					splunkClient.Send(telemetryClient.GetData(), logCollector) | ||||
| 				} | ||||
| 				if len(GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint) > 0 { | ||||
| 					splunkClient.Initialize(GeneralConfig.CorrelationID, | ||||
| 						GeneralConfig.HookConfig.SplunkConfig.ProdCriblEndpoint, | ||||
| 						GeneralConfig.HookConfig.SplunkConfig.ProdCriblToken, | ||||
| 						GeneralConfig.HookConfig.SplunkConfig.ProdCriblIndex, | ||||
| 						GeneralConfig.HookConfig.SplunkConfig.SendLogs) | ||||
| 					splunkClient.Send(telemetryClient.GetData(), logCollector) | ||||
| 				} | ||||
| 			} | ||||
| 			log.DeferExitHandler(handler) | ||||
| 			defer handler() | ||||
| 			telemetryClient.Initialize(GeneralConfig.NoTelemetry, STEP_NAME, GeneralConfig.HookConfig.PendoConfig.Token) | ||||
| 			abapLandscapePortalUpdateAddOnProduct(stepConfig, &stepTelemetryData) | ||||
| 			stepTelemetryData.ErrorCode = "0" | ||||
| 			log.Entry().Info("SUCCESS") | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	addAbapLandscapePortalUpdateAddOnProductFlags(createAbapLandscapePortalUpdateAddOnProductCmd, &stepConfig) | ||||
| 	return createAbapLandscapePortalUpdateAddOnProductCmd | ||||
| } | ||||
|  | ||||
| func addAbapLandscapePortalUpdateAddOnProductFlags(cmd *cobra.Command, stepConfig *abapLandscapePortalUpdateAddOnProductOptions) { | ||||
| 	cmd.Flags().StringVar(&stepConfig.LandscapePortalAPIServiceKey, "landscapePortalAPIServiceKey", os.Getenv("PIPER_landscapePortalAPIServiceKey"), "Service key JSON string to access the Landscape Portal Access API") | ||||
| 	cmd.Flags().StringVar(&stepConfig.AbapSystemNumber, "abapSystemNumber", os.Getenv("PIPER_abapSystemNumber"), "System Number of the abap integration test system") | ||||
| 	cmd.Flags().StringVar(&stepConfig.AddonDescriptorFileName, "addonDescriptorFileName", `addon.yml`, "File name of the YAML file which describes the Product Version and corresponding Software Component Versions") | ||||
|  | ||||
| 	cmd.MarkFlagRequired("landscapePortalAPIServiceKey") | ||||
| 	cmd.MarkFlagRequired("abapSystemNumber") | ||||
| 	cmd.MarkFlagRequired("addonDescriptorFileName") | ||||
| } | ||||
|  | ||||
| // retrieve step metadata | ||||
| func abapLandscapePortalUpdateAddOnProductMetadata() config.StepData { | ||||
| 	var theMetaData = config.StepData{ | ||||
| 		Metadata: config.StepMetadata{ | ||||
| 			Name:        "abapLandscapePortalUpdateAddOnProduct", | ||||
| 			Aliases:     []config.Alias{}, | ||||
| 			Description: "Update the AddOn product in SAP BTP ABAP Environment system of Landscape Portal", | ||||
| 		}, | ||||
| 		Spec: config.StepSpec{ | ||||
| 			Inputs: config.StepInputs{ | ||||
| 				Secrets: []config.StepSecrets{ | ||||
| 					{Name: "landscapePortalAPICredentialsId", Description: "Jenkins secret text credential ID containing the service key to access the Landscape Portal Access API", Type: "jenkins"}, | ||||
| 				}, | ||||
| 				Parameters: []config.StepParameters{ | ||||
| 					{ | ||||
| 						Name: "landscapePortalAPIServiceKey", | ||||
| 						ResourceRef: []config.ResourceReference{ | ||||
| 							{ | ||||
| 								Name:  "landscapePortalAPICredentialsId", | ||||
| 								Param: "landscapePortalAPIServiceKey", | ||||
| 								Type:  "secret", | ||||
| 							}, | ||||
| 						}, | ||||
| 						Scope:     []string{"PARAMETERS"}, | ||||
| 						Type:      "string", | ||||
| 						Mandatory: true, | ||||
| 						Aliases:   []config.Alias{}, | ||||
| 						Default:   os.Getenv("PIPER_landscapePortalAPIServiceKey"), | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "abapSystemNumber", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 						Default:     os.Getenv("PIPER_abapSystemNumber"), | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "addonDescriptorFileName", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 						Default:     `addon.yml`, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	return theMetaData | ||||
| } | ||||
							
								
								
									
										20
									
								
								cmd/abapLandscapePortalUpdateAddOnProduct_generated_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								cmd/abapLandscapePortalUpdateAddOnProduct_generated_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| //go:build unit | ||||
| // +build unit | ||||
|  | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestAbapLandscapePortalUpdateAddOnProductCommand(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	testCmd := AbapLandscapePortalUpdateAddOnProductCommand() | ||||
|  | ||||
| 	// only high level testing performed - details are tested in step generation procedure | ||||
| 	assert.Equal(t, "abapLandscapePortalUpdateAddOnProduct", testCmd.Use, "command name incorrect") | ||||
|  | ||||
| } | ||||
							
								
								
									
										655
									
								
								cmd/abapLandscapePortalUpdateAddOnProduct_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										655
									
								
								cmd/abapLandscapePortalUpdateAddOnProduct_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,655 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	resBodyJSON_token = `{"access_token": "some-access-token", "token_type": "bearer", "expires_in": 86400, "scope": "some-scope"}` | ||||
| 	resBodyJSON_sys   = `{"SystemId": "some-system-id", "SystemNumber": "some-system-number", "zone_id": "some-zone-id"}` | ||||
| 	resBodyJSON_req_S = `{"RequestId": "some-request-id","zone_id": "some-zone-id", "Status": "S", "SystemId": "some-system-id"}` | ||||
| 	resBodyJSON_req_I = `{"RequestId": "some-request-id","zone_id": "some-zone-id", "Status": "I", "SystemId": "some-system-id"}` | ||||
| 	resBodyJSON_req_C = `{"RequestId": "some-request-id","zone_id": "some-zone-id", "Status": "C", "SystemId": "some-system-id"}` | ||||
| 	resBodyJSON_req_E = `{"RequestId": "some-request-id","zone_id": "some-zone-id", "Status": "E", "SystemId": "some-system-id"}` | ||||
| 	resBodyJSON_req_X = `{"RequestId": "some-request-id","zone_id": "some-zone-id", "Status": "X", "SystemId": "some-system-id"}` | ||||
| ) | ||||
|  | ||||
| type mockClient struct { | ||||
| 	DoFunc func(*http.Request) (*http.Response, error) | ||||
| } | ||||
|  | ||||
| var GetDoFunc func(req *http.Request) (*http.Response, error) | ||||
|  | ||||
| var testUaa = uaa{ | ||||
| 	CertUrl:     "https://some-cert-url.com", | ||||
| 	ClientId:    "some-client-id", | ||||
| 	Certificate: "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", | ||||
| 	Key:         "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", | ||||
| } | ||||
| var mockServKey = serviceKey{ | ||||
| 	Url: "https://some-url.com", | ||||
| 	Uaa: testUaa, | ||||
| } | ||||
|  | ||||
| var mockServiceKeyJSON = `{ | ||||
| 	"url": "https://some-url.com", | ||||
| 	"uaa": { | ||||
| 	    "clientid": "some-client-id", | ||||
| 	    "url": "https://some-uaa-url.com", | ||||
| 	    "certificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", | ||||
| 	    "certurl": "https://some-cert-url.com", | ||||
| 	    "key": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----" | ||||
| 	}, | ||||
| 	"vendor": "SAP" | ||||
|     }` | ||||
|  | ||||
| var mockUpdateAddOnConfig = abapLandscapePortalUpdateAddOnProductOptions{ | ||||
| 	LandscapePortalAPIServiceKey: mockServiceKeyJSON, | ||||
| 	AbapSystemNumber:             "some-system-number", | ||||
| 	AddonDescriptorFileName:      "addon.yml", | ||||
| } | ||||
|  | ||||
| func (m *mockClient) Do(req *http.Request) (*http.Response, error) { | ||||
| 	return GetDoFunc(req) | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	client = &mockClient{} | ||||
| 	clientToken = &mockClient{} | ||||
| } | ||||
|  | ||||
| func TestParseServiceKeyAndPrepareAccessTokenHttpClient(t *testing.T) { | ||||
| 	t.Run("Successfully parsed service key", func(t *testing.T) { | ||||
| 		var testServKey serviceKey | ||||
| 		clientParseServKey := clientToken | ||||
|  | ||||
| 		err := parseServiceKeyAndPrepareAccessTokenHttpClient(mockUpdateAddOnConfig.LandscapePortalAPIServiceKey, &clientParseServKey, &testServKey) | ||||
|  | ||||
| 		assert.Equal(t, nil, err) | ||||
| 		assert.Equal(t, "https://some-url.com", testServKey.Url) | ||||
| 		assert.Equal(t, "some-client-id", testServKey.Uaa.ClientId) | ||||
| 		assert.Equal(t, "https://some-cert-url.com", testServKey.Uaa.CertUrl) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGetLPAPIAccessToken(t *testing.T) { | ||||
| 	t.Run("Successfully got LP API access token", func(t *testing.T) { | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			resBodyReader := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 			return &http.Response{ | ||||
| 				StatusCode: 200, | ||||
| 				Body:       resBodyReader, | ||||
| 			}, nil | ||||
| 		} | ||||
|  | ||||
| 		res, err := getLPAPIAccessToken(clientToken, mockServKey) | ||||
|  | ||||
| 		assert.Equal(t, "some-access-token", res) | ||||
| 		assert.Equal(t, nil, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Failed to get LP API access token", func(t *testing.T) { | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			return nil, fmt.Errorf("Failed to get access token.") | ||||
| 		} | ||||
| 		res, err := getLPAPIAccessToken(clientToken, mockServKey) | ||||
|  | ||||
| 		assert.Equal(t, "", res) | ||||
| 		assert.Equal(t, fmt.Errorf("Failed to get access token."), err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGetSystemBySystemNumber(t *testing.T) { | ||||
| 	reqUrl_token := mockServKey.Uaa.CertUrl + "/oauth/token" | ||||
| 	reqUrl_sys := mockServKey.Url + "/api/systems/" + mockUpdateAddOnConfig.AbapSystemNumber | ||||
|  | ||||
| 	t.Run("Successfully got ABAP system", func(t *testing.T) { | ||||
| 		var testSysId string | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_sys { | ||||
| 				resBodyReader_sys := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_sys))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_sys, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		err := getSystemBySystemNumber(&mockUpdateAddOnConfig, client, clientToken, mockServKey, &testSysId) | ||||
|  | ||||
| 		assert.Equal(t, "some-system-id", testSysId) | ||||
| 		assert.Equal(t, nil, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Failed to get ABAP system", func(t *testing.T) { | ||||
| 		var testSysId string | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_sys { | ||||
| 				return nil, fmt.Errorf("Failed to get ABAP system.") | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		err := getSystemBySystemNumber(&mockUpdateAddOnConfig, client, clientToken, mockServKey, &testSysId) | ||||
|  | ||||
| 		assert.Equal(t, "", testSysId) | ||||
| 		assert.Equal(t, fmt.Errorf("Failed to get ABAP system."), err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestUpdateAddOn(t *testing.T) { | ||||
| 	testSysId := "some-system-id" | ||||
| 	reqUrl_token := mockServKey.Uaa.CertUrl + "/oauth/token" | ||||
| 	reqUrl_update := mockServKey.Url + "/api/systems/" + testSysId + "/deployProduct" | ||||
|  | ||||
| 	t.Run("Successfully updated addon", func(t *testing.T) { | ||||
| 		// write addon.yml | ||||
| 		dir := t.TempDir() | ||||
| 		oldCWD, _ := os.Getwd() | ||||
| 		_ = os.Chdir(dir) | ||||
| 		// clean up tmp dir | ||||
| 		defer func() { | ||||
| 			_ = os.Chdir(oldCWD) | ||||
| 		}() | ||||
|  | ||||
| 		addonYML := `addonProduct: some-addon-product | ||||
| addonVersion: 1.0.0 | ||||
| ` | ||||
| 		addonYMLBytes := []byte(addonYML) | ||||
| 		os.WriteFile("addon.yml", addonYMLBytes, 0644) | ||||
|  | ||||
| 		// mock Do func | ||||
| 		var testReqId string | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_update { | ||||
| 				resBodyReader_req_S := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_S))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_req_S, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		err := updateAddOn(mockUpdateAddOnConfig.AddonDescriptorFileName, client, clientToken, mockServKey, testSysId, &testReqId) | ||||
|  | ||||
| 		assert.Equal(t, "some-request-id", testReqId) | ||||
| 		assert.Equal(t, nil, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Failed to update addon", func(t *testing.T) { | ||||
| 		// write addon.yml | ||||
| 		dir := t.TempDir() | ||||
| 		oldCWD, _ := os.Getwd() | ||||
| 		_ = os.Chdir(dir) | ||||
| 		// clean up tmp dir | ||||
| 		defer func() { | ||||
| 			_ = os.Chdir(oldCWD) | ||||
| 		}() | ||||
|  | ||||
| 		addonYML := `addonProduct: some-addon-product | ||||
| addonVersion: 1.0.0 | ||||
| ` | ||||
| 		addonYMLBytes := []byte(addonYML) | ||||
| 		os.WriteFile("addon.yml", addonYMLBytes, 0644) | ||||
|  | ||||
| 		// mock Do func | ||||
| 		var testReqId string | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_update { | ||||
| 				return nil, fmt.Errorf("Failed to update addon.") | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		err := updateAddOn(mockUpdateAddOnConfig.AddonDescriptorFileName, client, clientToken, mockServKey, testSysId, &testReqId) | ||||
|  | ||||
| 		assert.Equal(t, "", testReqId) | ||||
| 		assert.Equal(t, fmt.Errorf("Failed to update addon."), err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestPollStatusOfUpdateAddOn(t *testing.T) { | ||||
| 	var testReq http.Request | ||||
|  | ||||
| 	testReqId := "some-request-id" | ||||
| 	reqUrl_token := mockServKey.Uaa.CertUrl + "/oauth/token" | ||||
| 	reqUrl_pollAndCancel := mockServKey.Url + "/api/requests/" + testReqId | ||||
|  | ||||
| 	t.Run("Successfully polled request status", func(t *testing.T) { | ||||
| 		var testStatus string | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel { | ||||
| 				resBodyReader_pollStatus := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_I))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_pollStatus, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		err1 := prepareGetStatusHttpRequest(clientToken, mockServKey, testReqId, &testReq) | ||||
| 		err2 := pollStatusOfUpdateAddOn(client, &testReq, testReqId, &testStatus) | ||||
|  | ||||
| 		assert.Equal(t, "I", testStatus) | ||||
| 		assert.Equal(t, nil, err1) | ||||
| 		assert.Equal(t, nil, err2) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Failed to poll request status", func(t *testing.T) { | ||||
| 		var testStatus string | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel { | ||||
| 				return nil, fmt.Errorf("Failed to poll status.") | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		err1 := prepareGetStatusHttpRequest(clientToken, mockServKey, testReqId, &testReq) | ||||
| 		err2 := pollStatusOfUpdateAddOn(client, &testReq, testReqId, &testStatus) | ||||
|  | ||||
| 		assert.Equal(t, "", testStatus) | ||||
| 		assert.Equal(t, nil, err1) | ||||
| 		assert.Equal(t, fmt.Errorf("Failed to poll status."), err2) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestCancelUpdateAddOn(t *testing.T) { | ||||
| 	testReqId := "some-request-id" | ||||
| 	reqUrl_token := mockServKey.Uaa.CertUrl + "/oauth/token" | ||||
| 	reqUrl_pollAndCancel := mockServKey.Url + "/api/requests/" + testReqId | ||||
|  | ||||
| 	t.Run("Successfully canceled addon update", func(t *testing.T) { | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel { | ||||
| 				resBodyReader_cancelUpdate := io.NopCloser(nil) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 204, | ||||
| 					Body:       resBodyReader_cancelUpdate, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		err := cancelUpdateAddOn(client, clientToken, mockServKey, testReqId) | ||||
|  | ||||
| 		assert.Equal(t, nil, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Failed to cancel addon update", func(t *testing.T) { | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel { | ||||
| 				return nil, fmt.Errorf("Failed to cancel addon update.") | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		err := cancelUpdateAddOn(client, clientToken, mockServKey, testReqId) | ||||
|  | ||||
| 		assert.Equal(t, fmt.Errorf("Failed to cancel addon update."), err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestRunAbapLandscapePortalUpdateAddOnProduct(t *testing.T) { | ||||
| 	reqUrl_token := mockServKey.Uaa.CertUrl + "/oauth/token" | ||||
| 	reqUrl_sys := mockServKey.Url + "/api/systems/" + mockUpdateAddOnConfig.AbapSystemNumber | ||||
| 	reqUrl_update := mockServKey.Url + "/api/systems/" + "some-system-id" + "/deployProduct" | ||||
| 	reqUrl_pollAndCancel := mockServKey.Url + "/api/requests/" + "some-request-id" | ||||
|  | ||||
| 	t.Run("Successfully ran update addon in ABAP system", func(t *testing.T) { | ||||
| 		// write addon.yml | ||||
| 		dir := t.TempDir() | ||||
| 		oldCWD, _ := os.Getwd() | ||||
| 		_ = os.Chdir(dir) | ||||
| 		// clean up tmp dir | ||||
| 		defer func() { | ||||
| 			_ = os.Chdir(oldCWD) | ||||
| 		}() | ||||
|  | ||||
| 		addonYML := `addonProduct: some-addon-product | ||||
| addonVersion: 1.0.0 | ||||
| ` | ||||
| 		addonYMLBytes := []byte(addonYML) | ||||
| 		os.WriteFile("addon.yml", addonYMLBytes, 0644) | ||||
|  | ||||
| 		// mock Do func | ||||
| 		maxRuntimeInMinute := time.Duration(1) * time.Minute | ||||
| 		pollIntervalInSecond := time.Duration(1) * time.Second | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_sys { | ||||
| 				resBodyReader_sys := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_sys))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_sys, | ||||
| 				}, nil | ||||
| 			} | ||||
| 			if req.URL.String() == reqUrl_update { | ||||
| 				resBodyReader_update := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_S))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_update, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel { | ||||
| 				resBodyReader_pollStatus_C := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_C))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_pollStatus_C, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		// execution and assertion | ||||
| 		err := runAbapLandscapePortalUpdateAddOnProduct(&mockUpdateAddOnConfig, client, clientToken, mockServKey, maxRuntimeInMinute, pollIntervalInSecond) | ||||
|  | ||||
| 		assert.Equal(t, nil, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Update addon ended in error", func(t *testing.T) { | ||||
| 		// write addon.yml | ||||
| 		dir := t.TempDir() | ||||
| 		oldCWD, _ := os.Getwd() | ||||
| 		_ = os.Chdir(dir) | ||||
| 		// clean up tmp dir | ||||
| 		defer func() { | ||||
| 			_ = os.Chdir(oldCWD) | ||||
| 		}() | ||||
|  | ||||
| 		addonYML := `addonProduct: some-addon-product | ||||
| addonVersion: 1.0.0 | ||||
| ` | ||||
| 		addonYMLBytes := []byte(addonYML) | ||||
| 		os.WriteFile("addon.yml", addonYMLBytes, 0644) | ||||
|  | ||||
| 		// mock Do func | ||||
| 		maxRuntimeInMinute := time.Duration(1) * time.Minute | ||||
| 		pollIntervalInSecond := time.Duration(1) * time.Second | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_sys { | ||||
| 				resBodyReader_sys := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_sys))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_sys, | ||||
| 				}, nil | ||||
| 			} | ||||
| 			if req.URL.String() == reqUrl_update { | ||||
| 				resBodyReader_update := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_S))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_update, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel && req.Method == "GET" { | ||||
| 				resBodyReader_pollStatus_E := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_E))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_pollStatus_E, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel && req.Method == "DELETE" { | ||||
| 				resBodyReader_cancelUpdate := io.NopCloser(nil) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 204, | ||||
| 					Body:       resBodyReader_cancelUpdate, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		// execution and assertion | ||||
| 		expectedErr1 := fmt.Errorf("Addon update failed.\n") | ||||
| 		expectedErr2 := fmt.Errorf("The final status of addon update is E. Error: %v\n", expectedErr1) | ||||
|  | ||||
| 		err := runAbapLandscapePortalUpdateAddOnProduct(&mockUpdateAddOnConfig, client, clientToken, mockServKey, maxRuntimeInMinute, pollIntervalInSecond) | ||||
|  | ||||
| 		assert.Equal(t, expectedErr2, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Update addon was aborted", func(t *testing.T) { | ||||
| 		// write addon.yml | ||||
| 		dir := t.TempDir() | ||||
| 		oldCWD, _ := os.Getwd() | ||||
| 		_ = os.Chdir(dir) | ||||
| 		// clean up tmp dir | ||||
| 		defer func() { | ||||
| 			_ = os.Chdir(oldCWD) | ||||
| 		}() | ||||
|  | ||||
| 		addonYML := `addonProduct: some-addon-product | ||||
| addonVersion: 1.0.0 | ||||
| ` | ||||
| 		addonYMLBytes := []byte(addonYML) | ||||
| 		os.WriteFile("addon.yml", addonYMLBytes, 0644) | ||||
|  | ||||
| 		// mock Do func | ||||
| 		maxRuntimeInMinute := time.Duration(1) * time.Minute | ||||
| 		pollIntervalInSecond := time.Duration(1) * time.Second | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_sys { | ||||
| 				resBodyReader_sys := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_sys))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_sys, | ||||
| 				}, nil | ||||
| 			} | ||||
| 			if req.URL.String() == reqUrl_update { | ||||
| 				resBodyReader_update := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_S))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_update, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel && req.Method == "GET" { | ||||
| 				resBodyReader_pollStatus_X := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_X))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_pollStatus_X, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel && req.Method == "DELETE" { | ||||
| 				resBodyReader_cancelUpdate := io.NopCloser(nil) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 204, | ||||
| 					Body:       resBodyReader_cancelUpdate, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		// execution and assertion | ||||
| 		expectedErr1 := fmt.Errorf("Addon update was aborted.\n") | ||||
| 		expectedErr2 := fmt.Errorf("The final status of addon update is X. Error: %v\n", expectedErr1) | ||||
|  | ||||
| 		err := runAbapLandscapePortalUpdateAddOnProduct(&mockUpdateAddOnConfig, client, clientToken, mockServKey, maxRuntimeInMinute, pollIntervalInSecond) | ||||
|  | ||||
| 		assert.Equal(t, expectedErr2, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Update addon reached timeout", func(t *testing.T) { | ||||
| 		// write addon.yml | ||||
| 		dir := t.TempDir() | ||||
| 		oldCWD, _ := os.Getwd() | ||||
| 		_ = os.Chdir(dir) | ||||
| 		// clean up tmp dir | ||||
| 		defer func() { | ||||
| 			_ = os.Chdir(oldCWD) | ||||
| 		}() | ||||
|  | ||||
| 		addonYML := `addonProduct: some-addon-product | ||||
| addonVersion: 1.0.0 | ||||
| ` | ||||
| 		addonYMLBytes := []byte(addonYML) | ||||
| 		os.WriteFile("addon.yml", addonYMLBytes, 0644) | ||||
|  | ||||
| 		// mock Do func | ||||
| 		maxRuntimeInMinute := time.Duration(3) * time.Second | ||||
| 		pollIntervalInSecond := time.Duration(1) * time.Second | ||||
|  | ||||
| 		GetDoFunc = func(req *http.Request) (*http.Response, error) { | ||||
| 			if req.URL.String() == reqUrl_token { | ||||
| 				resBodyReader_token := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_token))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_token, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_sys { | ||||
| 				resBodyReader_sys := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_sys))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_sys, | ||||
| 				}, nil | ||||
| 			} | ||||
| 			if req.URL.String() == reqUrl_update { | ||||
| 				resBodyReader_update := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_S))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_update, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			if req.URL.String() == reqUrl_pollAndCancel && req.Method == "GET" { | ||||
| 				resBodyReader_pollStatus_I := io.NopCloser(bytes.NewReader([]byte(resBodyJSON_req_I))) | ||||
| 				return &http.Response{ | ||||
| 					StatusCode: 200, | ||||
| 					Body:       resBodyReader_pollStatus_I, | ||||
| 				}, nil | ||||
| 			} | ||||
|  | ||||
| 			return nil, fmt.Errorf("some-unknown-error") | ||||
| 		} | ||||
|  | ||||
| 		// execution and assertion | ||||
| 		expectedErr1 := fmt.Errorf("Timed out: max runtime %v reached.", maxRuntimeInMinute) | ||||
| 		expectedErr2 := fmt.Errorf("Error occurred before a final status can be reached. Error: %v\n", expectedErr1) | ||||
|  | ||||
| 		err := runAbapLandscapePortalUpdateAddOnProduct(&mockUpdateAddOnConfig, client, clientToken, mockServKey, maxRuntimeInMinute, pollIntervalInSecond) | ||||
|  | ||||
| 		assert.Equal(t, expectedErr2, err) | ||||
| 	}) | ||||
| } | ||||
| @@ -25,6 +25,7 @@ func GetAllStepMetadata() map[string]config.StepData { | ||||
| 		"abapEnvironmentPushATCSystemConfig":        abapEnvironmentPushATCSystemConfigMetadata(), | ||||
| 		"abapEnvironmentRunATCCheck":                abapEnvironmentRunATCCheckMetadata(), | ||||
| 		"abapEnvironmentRunAUnitTest":               abapEnvironmentRunAUnitTestMetadata(), | ||||
| 		"abapLandscapePortalUpdateAddOnProduct":     abapLandscapePortalUpdateAddOnProductMetadata(), | ||||
| 		"ansSendEvent":                              ansSendEventMetadata(), | ||||
| 		"apiKeyValueMapDownload":                    apiKeyValueMapDownloadMetadata(), | ||||
| 		"apiKeyValueMapUpload":                      apiKeyValueMapUploadMetadata(), | ||||
|   | ||||
| @@ -206,6 +206,7 @@ func Execute() { | ||||
| 	rootCmd.AddCommand(TmsExportCommand()) | ||||
| 	rootCmd.AddCommand(IntegrationArtifactTransportCommand()) | ||||
| 	rootCmd.AddCommand(AscAppUploadCommand()) | ||||
| 	rootCmd.AddCommand(AbapLandscapePortalUpdateAddOnProductCommand()) | ||||
| 	rootCmd.AddCommand(ImagePushToRegistryCommand()) | ||||
|  | ||||
| 	addRootFlags(rootCmd) | ||||
|   | ||||
| @@ -0,0 +1,56 @@ | ||||
| # ${docGenStepName} | ||||
|  | ||||
| ## ${docGenDescription} | ||||
|  | ||||
| ## Prerequisites | ||||
|  | ||||
| - Please make sure, that you are under Embedded Steampunk environment. | ||||
| - Please make sure, that the service landscape-portal-api-for-s4hc with plan api was assigned as entitlement to the subaccount, where you are about to deploy addon product. | ||||
| - Please make sure, that before deploying addon product, an instance of landscape-portal-api-for-s4hc (plan api) was created, and a service key with x509 authentication mechanism was created for the instance. The service key needs to be stored in the Jenkins Credentials Store. | ||||
| - Please make sure, that the system to deploy addon product is active, and the descriptor file with deployment information is available. | ||||
|  | ||||
| ## ${docGenParameters} | ||||
|  | ||||
| ## ${docGenConfiguration} | ||||
|  | ||||
| ## ${docJenkinsPluginDependencies} | ||||
|  | ||||
| ## Example: Configuration in the config.yml | ||||
|  | ||||
| The recommended way to configure your pipeline is via the config.yml file. In this case, calling the step in the Jenkinsfile is reduced to one line: | ||||
|  | ||||
| ```groovy | ||||
| abapLandscapePortalUpdateAddOnProduct script: this | ||||
| ``` | ||||
|  | ||||
| The configuration values for the addon update can be passed through the `config.yml` file: | ||||
|  | ||||
| ```yaml | ||||
| steps: | ||||
|   abapLandscapePortalUpdateAddOnProduct: | ||||
|     landscapePortalAPICredentialsId: 'landscapePortalAPICredentialsId' | ||||
|     abapSystemNumber: 'abapSystemNumber' | ||||
|     addonDescriptorFileName: 'addon.yml' | ||||
|     addonDescriptor: 'addonDescriptor' | ||||
| ``` | ||||
|  | ||||
| ## Example: Configuration in the Jenkinsfile | ||||
|  | ||||
| The step, including all parameters, can also be called directly from the Jenkinsfile. In the following example, a configuration file is used. | ||||
|  | ||||
| ```groovy | ||||
| abapLandscapePortalUpdateAddOnProduct ( | ||||
|   script: this, | ||||
|   landscapePortalAPICredentialsId: 'landscapePortalAPICredentialsId' | ||||
|   abapSystemNumber: 'abapSystemNumber' | ||||
|   addonDescriptorFileName: 'addon.yml' | ||||
|   addonDescriptor: 'addonDescriptor' | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| The file `addon.yml` would look like this: | ||||
|  | ||||
| ```yaml | ||||
| addonProduct: some-addon-product | ||||
| addonVersion: some-addon-version | ||||
| ``` | ||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							| @@ -101,7 +101,7 @@ require ( | ||||
| 	go.opentelemetry.io/otel/metric v1.21.0 // indirect | ||||
| 	go.opentelemetry.io/otel/trace v1.21.0 // indirect | ||||
| 	golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect | ||||
| 	golang.org/x/tools v0.14.0 // indirect | ||||
| 	golang.org/x/tools v0.17.0 // indirect | ||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect | ||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect | ||||
| ) | ||||
| @@ -238,6 +238,7 @@ require ( | ||||
| 	go.opencensus.io v0.24.0 // indirect | ||||
| 	go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect | ||||
| 	golang.org/x/crypto v0.18.0 | ||||
| 	golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 | ||||
| 	golang.org/x/net v0.20.0 // indirect | ||||
| 	golang.org/x/sync v0.6.0 | ||||
| 	golang.org/x/sys v0.16.0 // indirect | ||||
|   | ||||
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1212,6 +1212,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 | ||||
| golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||||
| golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | ||||
| golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo= | ||||
| golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= | ||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||
| golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||
| @@ -1490,8 +1492,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f | ||||
| golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||||
| golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= | ||||
| golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= | ||||
| golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= | ||||
| golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
|   | ||||
| @@ -47,6 +47,7 @@ stages: | ||||
|     abapSystemSizeOfPersistence: 2 | ||||
|     abapSystemSizeOfRuntime: 1 | ||||
|     confirmDeletion: 'true' | ||||
|     integrationTestOption: 'systemProvisioning' | ||||
|     includeAddon: 'true' | ||||
|     cfServiceKeyName: 'sap_com_0582' | ||||
|     cfServiceKeyConfig: '{"scenario_id":"SAP_COM_0582","type":"basic"}' | ||||
|   | ||||
| @@ -0,0 +1,41 @@ | ||||
| metadata: | ||||
|   name: abapLandscapePortalUpdateAddOnProduct | ||||
|   description: "Update the AddOn product in SAP BTP ABAP Environment system of Landscape Portal" | ||||
|   longDescription: | | ||||
|     This step describes the AddOn product update in SAP BTP ABAP Environment system of Landscape Portal | ||||
| spec: | ||||
|   inputs: | ||||
|     secrets: | ||||
|       - name: landscapePortalAPICredentialsId | ||||
|         description: Jenkins secret text credential ID containing the service key to access the Landscape Portal Access API | ||||
|         type: jenkins | ||||
|     params: | ||||
|       - name: landscapePortalAPIServiceKey | ||||
|         type: string | ||||
|         description: Service key JSON string to access the Landscape Portal Access API | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|         mandatory: true | ||||
|         secret: true | ||||
|         resourceRef: | ||||
|           - name: landscapePortalAPICredentialsId | ||||
|             type: secret | ||||
|             param: landscapePortalAPIServiceKey | ||||
|       - name: abapSystemNumber | ||||
|         description: System Number of the abap integration test system | ||||
|         type: string | ||||
|         mandatory: true | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: addonDescriptorFileName | ||||
|         type: string | ||||
|         description: File name of the YAML file which describes the Product Version and corresponding Software Component Versions | ||||
|         mandatory: true | ||||
|         default: addon.yml | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
| @@ -125,6 +125,7 @@ public class CommonStepsTest extends BasePiperTest{ | ||||
|         'abapEnvironmentRunAUnitTest', //implementing new golang pattern without fields | ||||
|         'abapEnvironmentCreateSystem', //implementing new golang pattern without fields | ||||
|         'abapEnvironmentPushATCSystemConfig', //implementing new golang pattern without fields | ||||
|         'abapLandscapePortalUpdateAddOnProduct', //implementing new golang pattern without fields | ||||
|         'artifactPrepareVersion', | ||||
|         'cloudFoundryCreateService', //implementing new golang pattern without fields | ||||
|         'cloudFoundryCreateServiceKey', //implementing new golang pattern without fields | ||||
| @@ -195,7 +196,7 @@ public class CommonStepsTest extends BasePiperTest{ | ||||
|         'integrationArtifactGetServiceEndpoint', //implementing new golang pattern without fields | ||||
|         'integrationArtifactDownload', //implementing new golang pattern without fields | ||||
|         'integrationArtifactUpload', //implementing new golang pattern without fields | ||||
|         'integrationArtifactTransport', //implementing new golang pattern without fields           | ||||
|         'integrationArtifactTransport', //implementing new golang pattern without fields | ||||
|         'integrationArtifactTriggerIntegrationTest', //implementing new golang pattern without fields | ||||
|         'integrationArtifactUnDeploy', //implementing new golang pattern without fields | ||||
|         'integrationArtifactResource', //implementing new golang pattern without fields | ||||
| @@ -226,7 +227,7 @@ public class CommonStepsTest extends BasePiperTest{ | ||||
|         'azureBlobUpload', | ||||
|         'awsS3Upload', | ||||
|         'ansSendEvent', | ||||
|         'apiProviderList', //implementing new golang pattern without fields     | ||||
|         'apiProviderList', //implementing new golang pattern without fields | ||||
|         'tmsUpload', | ||||
|         'tmsExport', | ||||
|         'imagePushToRegistry', | ||||
|   | ||||
| @@ -43,6 +43,7 @@ class abapEnvironmentPipelineStageIntegrationTestsTest extends BasePiperTest { | ||||
|         helper.registerAllowedMethod('cloudFoundryDeleteService', [Map.class], {m -> stepsCalled.add('cloudFoundryDeleteService')}) | ||||
|         helper.registerAllowedMethod('abapEnvironmentBuild', [Map.class], {m -> stepsCalled.add('abapEnvironmentBuild')}) | ||||
|         helper.registerAllowedMethod('cloudFoundryCreateServiceKey', [Map.class], {m -> stepsCalled.add('cloudFoundryCreateServiceKey')}) | ||||
|         helper.registerAllowedMethod('abapLandscapePortalUpdateAddOnProduct', [Map.class], {m -> stepsCalled.add('abapLandscapePortalUpdateAddOnProduct')}) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
| @@ -51,7 +52,7 @@ class abapEnvironmentPipelineStageIntegrationTestsTest extends BasePiperTest { | ||||
|         nullScript.commonPipelineEnvironment.configuration.runStage = [ | ||||
|             'Integration Tests': true | ||||
|         ] | ||||
|         jsr.step.abapEnvironmentPipelineStageIntegrationTests(script: nullScript, confirmDeletion: true) | ||||
|         jsr.step.abapEnvironmentPipelineStageIntegrationTests(script: nullScript, integrationTestOption: 'systemProvisioning', confirmDeletion: true) | ||||
|  | ||||
|         assertThat(stepsCalled, hasItems('input')) | ||||
|         assertThat(stepsCalled, hasItems('abapEnvironmentCreateSystem')) | ||||
| @@ -66,7 +67,7 @@ class abapEnvironmentPipelineStageIntegrationTestsTest extends BasePiperTest { | ||||
|         nullScript.commonPipelineEnvironment.configuration.runStage = [ | ||||
|             'Integration Tests': true | ||||
|         ] | ||||
|         jsr.step.abapEnvironmentPipelineStageIntegrationTests(script: nullScript, confirmDeletion: false) | ||||
|         jsr.step.abapEnvironmentPipelineStageIntegrationTests(script: nullScript, integrationTestOption: 'systemProvisioning', confirmDeletion: false) | ||||
|  | ||||
|  | ||||
|         assertThat(stepsCalled, not(hasItem('input'))) | ||||
| @@ -86,7 +87,7 @@ class abapEnvironmentPipelineStageIntegrationTestsTest extends BasePiperTest { | ||||
|         ] | ||||
|  | ||||
|         try { | ||||
|             jsr.step.abapEnvironmentPipelineStageIntegrationTests(script: nullScript, confirmDeletion: false) | ||||
|             jsr.step.abapEnvironmentPipelineStageIntegrationTests(script: nullScript, integrationTestOption: 'systemProvisioning', confirmDeletion: false) | ||||
|             fail("Expected exception") | ||||
|         } catch (Exception e) { | ||||
|             // failure expected | ||||
| @@ -112,4 +113,40 @@ class abapEnvironmentPipelineStageIntegrationTestsTest extends BasePiperTest { | ||||
|                                                 'cloudFoundryCreateServiceKey'))) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void testabapLandscapePortalUpdateAddOnProduct() { | ||||
|  | ||||
|         nullScript.commonPipelineEnvironment.configuration.runStage = [ | ||||
|             'Integration Tests': true | ||||
|         ] | ||||
|         jsr.step.abapEnvironmentPipelineStageIntegrationTests(script: nullScript, integrationTestOption: 'addOnDeployment') | ||||
|  | ||||
|  | ||||
|         assertThat(stepsCalled, not(hasItems('input', | ||||
|                                                 'abapEnvironmentCreateSystem', | ||||
|                                                 'cloudFoundryDeleteService', | ||||
|                                                 'cloudFoundryCreateServiceKey'))) | ||||
|         assertThat(stepsCalled, hasItems('abapLandscapePortalUpdateAddOnProduct')) | ||||
|         assertThat(stepsCalled, hasItems('abapEnvironmentBuild')) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void testabapLandscapePortalUpdateAddOnProductFails() { | ||||
|  | ||||
|         helper.registerAllowedMethod('abapLandscapePortalUpdateAddOnProduct', [Map.class], {m -> stepsCalled.add('abapLandscapePortalUpdateAddOnProduct'); error("Failed")}) | ||||
|  | ||||
|         nullScript.commonPipelineEnvironment.configuration.runStage = [ | ||||
|             'Integration Tests': true | ||||
|         ] | ||||
|  | ||||
|         try { | ||||
|             jsr.step.abapEnvironmentPipelineStageIntegrationTests(script: nullScript, integrationTestOption: 'addOnDeployment') | ||||
|             fail("Expected exception") | ||||
|         } catch (Exception e) { | ||||
|             // failure expected | ||||
|         } | ||||
|  | ||||
|         assertThat(stepsCalled, not(hasItem('input'))) | ||||
|         assertThat(stepsCalled, hasItems('abapLandscapePortalUpdateAddOnProduct')) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,9 +12,9 @@ import static com.sap.piper.Prerequisites.checkScript | ||||
|     'cloudFoundryDeleteService', | ||||
|     /** If set to true, a confirmation is required to delete the system */ | ||||
|     'confirmDeletion', | ||||
|     /** If set to true, the system is never deleted */ | ||||
|     'debug', | ||||
|     'testBuild' // Parameter for test execution mode, if true stage will be skipped | ||||
|     'debug', // If set to true, the system is never deleted | ||||
|     'testBuild', // Parameter for test execution mode, if true stage will be skipped | ||||
|     'integrationTestOption' // Integration test option | ||||
| ] | ||||
| @Field Set STAGE_STEP_KEYS = GENERAL_CONFIG_KEYS | ||||
| @Field Set STEP_CONFIG_KEYS = STAGE_STEP_KEYS | ||||
| @@ -34,12 +34,15 @@ void call(Map parameters = [:]) { | ||||
|         .addIfEmpty('confirmDeletion', true) | ||||
|         .addIfEmpty('debug', false) | ||||
|         .addIfEmpty('testBuild', false) | ||||
|         .addIfEmpty('integrationTestOption', 'systemProvisioning') | ||||
|         .use() | ||||
|  | ||||
|     if (config.testBuild) { | ||||
|         echo "Stage 'Integration Tests' skipped as parameter 'testBuild' is active" | ||||
|     } else { | ||||
|         piperStageWrapper (script: script, stageName: stageName, stashContent: [], stageLocking: false) { | ||||
|         return null; | ||||
|     } | ||||
|     piperStageWrapper (script: script, stageName: stageName, stashContent: [], stageLocking: false) { | ||||
|         if (config.integrationTestOption == 'systemProvisioning') { | ||||
|             try { | ||||
|                 abapEnvironmentCreateSystem(script: parameters.script, includeAddon: true) | ||||
|                 cloudFoundryCreateServiceKey(script: parameters.script) | ||||
| @@ -48,14 +51,25 @@ void call(Map parameters = [:]) { | ||||
|                 echo "Deployment test of add-on product failed." | ||||
|                 throw e | ||||
|             } finally { | ||||
|                 if (config.confirmDeletion) { | ||||
|                     input message: "Deployment test has been executed. Once you proceed, the test system will be deleted." | ||||
|                 } | ||||
|                 if (!config.debug) { | ||||
|                     cloudFoundryDeleteService script: parameters.script | ||||
|                 } | ||||
|             } | ||||
|         } else if (config.integrationTestOption == 'addOnDeployment') { | ||||
|             try { | ||||
|                 abapLandscapePortalUpdateAddOnProduct(script: parameters.script) | ||||
|                 abapEnvironmentBuild(script: parameters.script, phase: 'GENERATION', downloadAllResultFiles: true, useFieldsOfAddonDescriptor: '[{"use":"Name","renameTo":"SWC"}]') | ||||
|             } catch (Exception e) { | ||||
|                 echo "Deployment test of add-on product failed." | ||||
|                 throw e | ||||
|             } | ||||
|         } else { | ||||
|             e = new Error('Unsupoorted integration test option.') | ||||
|             throw e | ||||
|         } | ||||
|  | ||||
|         if (config.confirmDeletion) { | ||||
|             input message: "Deployment test has been executed. Once you proceed, the test system will be deleted." | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										11
									
								
								vars/abapLandscapePortalUpdateAddOnProduct.groovy
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								vars/abapLandscapePortalUpdateAddOnProduct.groovy
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| import groovy.transform.Field | ||||
|  | ||||
| @Field String STEP_NAME = getClass().getName() | ||||
| @Field String METADATA_FILE = 'metadata/abapLandscapePortalUpdateAddOnProduct.yaml' | ||||
|  | ||||
| void call(Map parameters = [:]) { | ||||
|     List credentials = [ | ||||
|         [type: 'token', id: 'landscapePortalAPICredentialsId', env: ['PIPER_landscapePortalAPIServiceKey']] | ||||
|     ] | ||||
|     piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user