mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
379 lines
12 KiB
Go
379 lines
12 KiB
Go
package abaputils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
|
"github.com/pkg/errors"
|
|
"k8s.io/utils/strings/slices"
|
|
)
|
|
|
|
type SAP_COM_0948 struct {
|
|
con ConnectionDetailsHTTP
|
|
client piperhttp.Sender
|
|
repository Repository
|
|
path string
|
|
cloneAction string
|
|
pullAction string
|
|
softwareComponentEntity string
|
|
branchEntity string
|
|
tagsEntity string
|
|
checkoutAction string
|
|
actionsEntity string
|
|
uuid string
|
|
failureMessage string
|
|
maxRetries int
|
|
retryBaseSleepUnit time.Duration
|
|
retryMaxSleepTime time.Duration
|
|
retryAllowedErrorCodes []string
|
|
}
|
|
|
|
func (api *SAP_COM_0948) init(con ConnectionDetailsHTTP, client piperhttp.Sender, repo Repository) {
|
|
api.con = con
|
|
api.client = client
|
|
api.repository = repo
|
|
api.path = "/sap/opu/odata4/sap/a4c_mswc_api/srvd_a2x/sap/manage_software_components/0001"
|
|
api.checkoutAction = "/SAP__self.checkout_branch"
|
|
api.softwareComponentEntity = "/SoftwareComponents"
|
|
api.actionsEntity = "/Actions"
|
|
api.branchEntity = "/Branches"
|
|
api.cloneAction = "/SAP__self.clone"
|
|
api.pullAction = "/SAP__self.pull"
|
|
api.tagsEntity = "/Tags"
|
|
api.failureMessage = "The action of the Repository / Software Component " + api.repository.Name + " failed"
|
|
api.maxRetries = 3
|
|
api.setSleepTimeConfig(1*time.Second, 120*time.Second)
|
|
api.retryAllowedErrorCodes = append(api.retryAllowedErrorCodes, "A4C_A2G/228")
|
|
api.retryAllowedErrorCodes = append(api.retryAllowedErrorCodes, "A4C_A2G/501")
|
|
}
|
|
|
|
func (api *SAP_COM_0948) getUUID() string {
|
|
return api.uuid
|
|
}
|
|
|
|
func (api *SAP_COM_0948) CreateTag(tag Tag) error {
|
|
|
|
if reflect.DeepEqual(Tag{}, tag) {
|
|
return errors.New("No Tag provided")
|
|
}
|
|
|
|
con := api.con
|
|
con.URL = api.con.URL + api.path + api.tagsEntity
|
|
|
|
requestBodyStruct := CreateTagBody{RepositoryName: api.repository.Name, CommitID: api.repository.CommitID, Tag: tag.TagName, Description: tag.TagDescription}
|
|
jsonBody, err := json.Marshal(&requestBodyStruct)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return api.triggerRequest(con, jsonBody)
|
|
}
|
|
|
|
func (api *SAP_COM_0948) CheckoutBranch() error {
|
|
|
|
if api.repository.Name == "" || api.repository.Branch == "" {
|
|
return fmt.Errorf("Failed to trigger checkout: %w", errors.New("Repository and/or Branch Configuration is empty. Please make sure that you have specified the correct values"))
|
|
}
|
|
|
|
checkoutConnectionDetails := api.con
|
|
checkoutConnectionDetails.URL = api.con.URL + api.path + api.branchEntity + api.getRepoNameForPath() + api.getBranchNameForPath() + api.checkoutAction
|
|
jsonBody := []byte(`{
|
|
"import_mode" : "",
|
|
"execution_mode": ""
|
|
}`)
|
|
|
|
return api.triggerRequest(checkoutConnectionDetails, jsonBody)
|
|
}
|
|
|
|
func (api *SAP_COM_0948) parseActionResponse(resp *http.Response, err error) (ActionEntity, error) {
|
|
|
|
var body ActionEntity
|
|
bodyText, errRead := io.ReadAll(resp.Body)
|
|
if errRead != nil {
|
|
return ActionEntity{}, err
|
|
}
|
|
if err := json.Unmarshal(bodyText, &body); err != nil {
|
|
return ActionEntity{}, err
|
|
}
|
|
if reflect.DeepEqual(ActionEntity{}, body) {
|
|
log.Entry().WithField("StatusCode", resp.Status).WithField("branchName", api.repository.Branch).Error("Could not switch to specified branch")
|
|
err := errors.New("Request to ABAP System not successful")
|
|
return ActionEntity{}, err
|
|
}
|
|
return body, nil
|
|
}
|
|
|
|
func (api *SAP_COM_0948) Pull() error {
|
|
|
|
// Trigger the Pull of a Repository
|
|
if api.repository.Name == "" {
|
|
return errors.New("An empty string was passed for the parameter 'repositoryName'")
|
|
}
|
|
|
|
pullConnectionDetails := api.con
|
|
pullConnectionDetails.URL = api.con.URL + api.path + api.softwareComponentEntity + api.getRepoNameForPath() + api.pullAction
|
|
|
|
jsonBody := []byte(api.repository.GetPullActionRequestBody())
|
|
return api.triggerRequest(pullConnectionDetails, jsonBody)
|
|
}
|
|
|
|
func (api *SAP_COM_0948) GetLogProtocol(logOverviewEntry LogResultsV2, page int) (result []LogProtocol, count int, err error) {
|
|
|
|
connectionDetails := api.con
|
|
connectionDetails.URL = api.con.URL + api.path + api.actionsEntity + "/" + api.getUUID() + "/_Log_Overview" + "/" + fmt.Sprint(logOverviewEntry.Index) + "/_Log_Protocol" + api.getLogProtocolQuery(page)
|
|
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
|
|
if err != nil {
|
|
log.SetErrorCategory(log.ErrorInfrastructure)
|
|
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
|
|
return nil, 0, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Parse response
|
|
var body LogProtocolResultsV4
|
|
bodyText, _ := io.ReadAll(resp.Body)
|
|
|
|
marshallError := json.Unmarshal(bodyText, &body)
|
|
if marshallError != nil {
|
|
return nil, 0, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
|
|
}
|
|
|
|
return body.Results, body.Count, nil
|
|
}
|
|
|
|
func (api *SAP_COM_0948) GetLogOverview() (result []LogResultsV2, err error) {
|
|
|
|
connectionDetails := api.con
|
|
connectionDetails.URL = api.con.URL + api.path + api.actionsEntity + "/" + api.getUUID() + "/_Log_Overview"
|
|
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
|
|
if err != nil {
|
|
log.SetErrorCategory(log.ErrorInfrastructure)
|
|
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Parse response
|
|
var abapResp map[string]*json.RawMessage
|
|
bodyText, _ := io.ReadAll(resp.Body)
|
|
|
|
marshallError := json.Unmarshal(bodyText, &abapResp)
|
|
if marshallError != nil {
|
|
return nil, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
|
|
}
|
|
marshallError = json.Unmarshal(*abapResp["value"], &result)
|
|
if marshallError != nil {
|
|
return nil, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
|
|
}
|
|
|
|
if reflect.DeepEqual(LogResultsV2{}, result) {
|
|
log.Entry().WithField("StatusCode", resp.Status).Error(api.failureMessage)
|
|
log.SetErrorCategory(log.ErrorInfrastructure)
|
|
var err = errors.New("Request to ABAP System not successful")
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
|
|
}
|
|
|
|
func (api *SAP_COM_0948) GetAction() (string, error) {
|
|
|
|
connectionDetails := api.con
|
|
connectionDetails.URL = api.con.URL + api.path + api.actionsEntity + "/" + api.getUUID()
|
|
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
|
|
if err != nil {
|
|
log.SetErrorCategory(log.ErrorInfrastructure)
|
|
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
|
|
return "E", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Parse Response
|
|
body, parseError := api.parseActionResponse(resp, err)
|
|
if parseError != nil {
|
|
return "E", parseError
|
|
}
|
|
|
|
api.uuid = body.UUID
|
|
|
|
abapStatusCode := body.Status
|
|
log.Entry().Info("Status: " + abapStatusCode + " - " + body.StatusDescription)
|
|
return abapStatusCode, nil
|
|
}
|
|
|
|
func (api *SAP_COM_0948) GetRepository() (bool, string, error) {
|
|
|
|
if api.repository.Name == "" {
|
|
return false, "", errors.New("An empty string was passed for the parameter 'repositoryName'")
|
|
}
|
|
|
|
swcConnectionDetails := api.con
|
|
swcConnectionDetails.URL = api.con.URL + api.path + api.softwareComponentEntity + api.getRepoNameForPath()
|
|
resp, err := GetHTTPResponse("GET", swcConnectionDetails, nil, api.client)
|
|
if err != nil {
|
|
_, errRepo := HandleHTTPError(resp, err, "Reading the Repository / Software Component failed", api.con)
|
|
return false, "", errRepo
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var body RepositoryEntity
|
|
bodyText, errRead := io.ReadAll(resp.Body)
|
|
if errRead != nil {
|
|
return false, "", err
|
|
}
|
|
|
|
if err := json.Unmarshal(bodyText, &body); err != nil {
|
|
return false, "", err
|
|
}
|
|
if reflect.DeepEqual(RepositoryEntity{}, body) {
|
|
log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", api.repository.Name).WithField("branchName", api.repository.Branch).WithField("commitID", api.repository.CommitID).WithField("Tag", api.repository.Tag).Error("Could not Clone the Repository / Software Component")
|
|
err := errors.New("Request to ABAP System not successful")
|
|
return false, "", err
|
|
}
|
|
|
|
if body.AvailOnInst {
|
|
return true, body.ActiveBranch, nil
|
|
}
|
|
return false, "", err
|
|
|
|
}
|
|
|
|
func (api *SAP_COM_0948) Clone() error {
|
|
|
|
// Trigger the Clone of a Repository
|
|
if api.repository.Name == "" {
|
|
return errors.New("An empty string was passed for the parameter 'repositoryName'")
|
|
}
|
|
|
|
cloneConnectionDetails := api.con
|
|
cloneConnectionDetails.URL = api.con.URL + api.path + api.softwareComponentEntity + api.getRepoNameForPath() + api.cloneAction
|
|
body := []byte(api.repository.GetCloneRequestBody())
|
|
|
|
return api.triggerRequest(cloneConnectionDetails, body)
|
|
|
|
}
|
|
|
|
func (api *SAP_COM_0948) triggerRequest(cloneConnectionDetails ConnectionDetailsHTTP, jsonBody []byte) error {
|
|
var err error
|
|
var body ActionEntity
|
|
var resp *http.Response
|
|
var errorCode string
|
|
|
|
for i := 0; i <= api.maxRetries; i++ {
|
|
if i > 0 {
|
|
sleepTime, err := api.getSleepTime(i + 5)
|
|
if err != nil {
|
|
// reached max retry duration
|
|
break
|
|
}
|
|
log.Entry().Infof("Retrying in %s", sleepTime.String())
|
|
time.Sleep(sleepTime)
|
|
}
|
|
resp, err = GetHTTPResponse("POST", cloneConnectionDetails, jsonBody, api.client)
|
|
if err != nil {
|
|
errorCode, err = HandleHTTPError(resp, err, "Triggering the action failed", api.con)
|
|
if slices.Contains(api.retryAllowedErrorCodes, errorCode) {
|
|
// Error Code allows for retry
|
|
continue
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
defer resp.Body.Close()
|
|
log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", api.repository.Name).WithField("branchName", api.repository.Branch).WithField("commitID", api.repository.CommitID).WithField("Tag", api.repository.Tag).Info("Triggered action of Repository / Software Component")
|
|
|
|
body, err = api.parseActionResponse(resp, err)
|
|
break
|
|
}
|
|
api.uuid = body.UUID
|
|
return err
|
|
}
|
|
|
|
// initialRequest implements SoftwareComponentApiInterface.
|
|
func (api *SAP_COM_0948) initialRequest() error {
|
|
// Configuring the HTTP Client and CookieJar
|
|
cookieJar, errorCookieJar := cookiejar.New(nil)
|
|
if errorCookieJar != nil {
|
|
return errors.Wrap(errorCookieJar, "Could not create a Cookie Jar")
|
|
}
|
|
|
|
api.client.SetOptions(piperhttp.ClientOptions{
|
|
MaxRequestDuration: 180 * time.Second,
|
|
CookieJar: cookieJar,
|
|
Username: api.con.User,
|
|
Password: api.con.Password,
|
|
})
|
|
|
|
// HEAD request to the root is not sufficient, as an unauthorized called is allowed to do so
|
|
// Therefore, the request goes to the "Actions" entity without actually fetching data
|
|
headConnection := api.con
|
|
headConnection.XCsrfToken = "fetch"
|
|
headConnection.URL = api.con.URL + api.path + api.actionsEntity + "?$top=0"
|
|
|
|
// Loging into the ABAP System - getting the x-csrf-token and cookies
|
|
resp, err := GetHTTPResponse("GET", headConnection, nil, api.client)
|
|
if err != nil {
|
|
_, err = HandleHTTPError(resp, err, "Authentication on the ABAP system failed", api.con)
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", api.con).Debug("Authentication on the ABAP system successful")
|
|
api.con.XCsrfToken = resp.Header.Get("X-Csrf-Token")
|
|
return nil
|
|
}
|
|
|
|
// getSleepTime Should return the Fibonacci numbers in the define time unit up to the defined maximum duration
|
|
func (api *SAP_COM_0948) getSleepTime(n int) (time.Duration, error) {
|
|
|
|
if n == 0 {
|
|
return 0, nil
|
|
} else if n == 1 {
|
|
return 1 * api.retryBaseSleepUnit, nil
|
|
} else if n < 0 {
|
|
return 0, errors.New("Negative numbers are not allowed")
|
|
}
|
|
var result, i int
|
|
prev := 0
|
|
next := 1
|
|
for i = 2; i <= n; i++ {
|
|
result = prev + next
|
|
prev = next
|
|
next = result
|
|
}
|
|
sleepTime := time.Duration(result) * api.retryBaseSleepUnit
|
|
|
|
if sleepTime > api.retryMaxSleepTime {
|
|
return 0, errors.New("Exceeded max sleep time")
|
|
}
|
|
return sleepTime, nil
|
|
}
|
|
|
|
// setSleepTimeConfig sets the time unit (seconds, nanoseconds) and the maximum sleep duration
|
|
func (api *SAP_COM_0948) setSleepTimeConfig(timeUnit time.Duration, maxSleepTime time.Duration) {
|
|
api.retryBaseSleepUnit = timeUnit
|
|
api.retryMaxSleepTime = maxSleepTime
|
|
}
|
|
|
|
func (api *SAP_COM_0948) getRepoNameForPath() string {
|
|
return "/" + strings.ReplaceAll(api.repository.Name, "/", "%2F")
|
|
}
|
|
|
|
func (api *SAP_COM_0948) getBranchNameForPath() string {
|
|
return "/" + api.repository.Branch
|
|
}
|
|
|
|
func (api *SAP_COM_0948) getLogProtocolQuery(page int) string {
|
|
skip := page * numberOfEntriesPerPage
|
|
top := numberOfEntriesPerPage
|
|
|
|
return fmt.Sprintf("?$skip=%s&$top=%s&$count=true", fmt.Sprint(skip), fmt.Sprint(top))
|
|
}
|