mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-02-19 19:44:27 +02:00
* adding LogOutput to clone step * delete comments * adding stepName * change step name * adding multiple log archive outputs files * changing file name * change filename * change time format * adding second file for testing * adding second file * change structure for PersistReportsAndLinks * change to pointer * change pointer * cleanup * changing file name * adding logArchive for pull action * adding logArchive for checkoutBranch * refactor zip archive log * change structure * adding PersistArchiveLogsForPiperStep function * adding persist structure to checkout * adding FileNameStep * adding unit tests * correct name * change whitespace in yaml * fixing unit tests * fixing createTag unit test * fixing unit test * fixing unit test * rename ArchiveOutputLogs to LogOutputManager * refactor pointer structure * adopt tests to pointer structure * fixing / error in repo name * adding log overview also after archive log * change log output structure * adding always execution log * update unit tests --------- Co-authored-by: Daniel Mieg <56156797+DanielMieg@users.noreply.github.com>
416 lines
14 KiB
Go
416 lines
14 KiB
Go
package abaputils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"reflect"
|
|
"strconv"
|
|
"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_0510 struct {
|
|
con ConnectionDetailsHTTP
|
|
client piperhttp.Sender
|
|
repository Repository
|
|
path string
|
|
cloneEntity string
|
|
repositoryEntity string
|
|
tagsEntity string
|
|
checkoutAction string
|
|
actionEntity string
|
|
uuid string
|
|
failureMessage string
|
|
maxRetries int
|
|
retryBaseSleepUnit time.Duration
|
|
retryMaxSleepTime time.Duration
|
|
retryAllowedErrorCodes []string
|
|
}
|
|
|
|
func (api *SAP_COM_0510) init(con ConnectionDetailsHTTP, client piperhttp.Sender, repo Repository) {
|
|
api.con = con
|
|
api.client = client
|
|
api.repository = repo
|
|
api.path = "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY"
|
|
api.cloneEntity = "/Clones"
|
|
api.repositoryEntity = "/Repositories"
|
|
api.tagsEntity = "/Tags"
|
|
api.actionEntity = "/Pull"
|
|
api.checkoutAction = "/checkout_branch"
|
|
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_0510) GetExecutionLog() (execLog ExecutionLog, err error) {
|
|
return execLog, errors.New("Not implemented")
|
|
}
|
|
|
|
func (api *SAP_COM_0510) getUUID() string {
|
|
return api.uuid
|
|
}
|
|
|
|
func (api *SAP_COM_0510) 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_0510) 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"))
|
|
}
|
|
|
|
// the request looks like: POST/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/checkout_branch?branch_name='newBranch'&sc_name=/DMO/GIT_REPOSITORY'
|
|
checkoutConnectionDetails := api.con
|
|
checkoutConnectionDetails.URL = api.con.URL + api.path + api.checkoutAction + `?branch_name='` + api.repository.Branch + `'&sc_name='` + api.repository.Name + `'`
|
|
jsonBody := []byte(``)
|
|
|
|
return api.triggerRequest(checkoutConnectionDetails, jsonBody)
|
|
}
|
|
|
|
func (api *SAP_COM_0510) parseActionResponse(resp *http.Response, err error) (ActionEntity, error) {
|
|
var body ActionEntity
|
|
var abapResp map[string]*json.RawMessage
|
|
bodyText, errRead := io.ReadAll(resp.Body)
|
|
if errRead != nil {
|
|
return ActionEntity{}, err
|
|
}
|
|
if err := json.Unmarshal(bodyText, &abapResp); err != nil {
|
|
return ActionEntity{}, err
|
|
}
|
|
if err := json.Unmarshal(*abapResp["d"], &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_0510) 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.actionEntity
|
|
|
|
jsonBody := []byte(api.repository.GetPullRequestBody())
|
|
return api.triggerRequest(pullConnectionDetails, jsonBody)
|
|
}
|
|
|
|
func (api *SAP_COM_0510) GetLogProtocol(logOverviewEntry LogResultsV2, page int) (result []LogProtocol, count int, err error) {
|
|
|
|
connectionDetails := api.con
|
|
connectionDetails.URL = logOverviewEntry.ToLogProtocol.Deferred.URI + 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 LogProtocolResults
|
|
var abapResp map[string]*json.RawMessage
|
|
bodyText, _ := io.ReadAll(resp.Body)
|
|
|
|
marshallError := json.Unmarshal(bodyText, &abapResp)
|
|
if marshallError != nil {
|
|
return nil, 0, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
|
|
}
|
|
marshallError = json.Unmarshal(*abapResp["d"], &body)
|
|
if marshallError != nil {
|
|
return nil, 0, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
|
|
}
|
|
|
|
count, errConv := strconv.Atoi(body.Count)
|
|
if errConv != nil {
|
|
return nil, 0, errors.Wrap(errConv, "Could not parse response from the ABAP Environment system")
|
|
}
|
|
|
|
return body.Results, count, nil
|
|
}
|
|
|
|
func (api *SAP_COM_0510) GetLogOverview() (result []LogResultsV2, err error) {
|
|
|
|
connectionDetails := api.con
|
|
connectionDetails.URL = api.con.URL + api.path + api.actionEntity + "(uuid=guid'" + api.getUUID() + "')" + "?$expand=to_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 body ActionEntity
|
|
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["d"], &body)
|
|
if marshallError != nil {
|
|
return nil, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
|
|
}
|
|
|
|
if reflect.DeepEqual(ActionEntity{}, body) {
|
|
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
|
|
}
|
|
|
|
abapStatusCode := body.Status
|
|
log.Entry().Info("Status: " + abapStatusCode + " - " + body.StatusDescription)
|
|
return body.ToLogOverview.Results, nil
|
|
|
|
}
|
|
|
|
func (api *SAP_COM_0510) GetAction() (string, error) {
|
|
|
|
connectionDetails := api.con
|
|
connectionDetails.URL = api.con.URL + api.path + api.actionEntity + "(uuid=guid'" + 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_0510) getRepositoryName() string {
|
|
return api.repository.Name
|
|
}
|
|
|
|
func (api *SAP_COM_0510) GetRepository() (bool, string, error, bool) {
|
|
|
|
if api.repository.Name == "" {
|
|
return false, "", errors.New("An empty string was passed for the parameter 'repositoryName'"), false
|
|
}
|
|
|
|
swcConnectionDetails := api.con
|
|
swcConnectionDetails.URL = api.con.URL + api.path + api.repositoryEntity + "('" + strings.Replace(api.repository.Name, "/", "%2F", -1) + "')"
|
|
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, false
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var body RepositoryEntity
|
|
var abapResp map[string]*json.RawMessage
|
|
bodyText, errRead := io.ReadAll(resp.Body)
|
|
if errRead != nil {
|
|
return false, "", err, false
|
|
}
|
|
|
|
if err := json.Unmarshal(bodyText, &abapResp); err != nil {
|
|
return false, "", err, false
|
|
}
|
|
if err := json.Unmarshal(*abapResp["d"], &body); err != nil {
|
|
return false, "", err, false
|
|
}
|
|
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, false
|
|
}
|
|
|
|
if body.AvailOnInst {
|
|
return true, body.ActiveBranch, nil, false
|
|
}
|
|
return false, "", err, false
|
|
|
|
}
|
|
|
|
func (api *SAP_COM_0510) 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.cloneEntity
|
|
body := []byte(api.repository.GetCloneRequestBodyWithSWC())
|
|
|
|
return api.triggerRequest(cloneConnectionDetails, body)
|
|
|
|
}
|
|
|
|
func (api *SAP_COM_0510) 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_0510) 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,
|
|
TrustedCerts: api.con.CertificateNames,
|
|
})
|
|
|
|
headConnection := api.con
|
|
headConnection.XCsrfToken = "fetch"
|
|
headConnection.URL = api.con.URL + api.path
|
|
|
|
// Loging into the ABAP System - getting the x-csrf-token and cookies
|
|
resp, err := GetHTTPResponse("HEAD", 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_0510) 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_0510) setSleepTimeConfig(timeUnit time.Duration, maxSleepTime time.Duration) {
|
|
api.retryBaseSleepUnit = timeUnit
|
|
api.retryMaxSleepTime = maxSleepTime
|
|
}
|
|
|
|
func (api *SAP_COM_0510) getLogProtocolQuery(page int) string {
|
|
skip := page * numberOfEntriesPerPage
|
|
top := numberOfEntriesPerPage
|
|
|
|
return fmt.Sprintf("?$skip=%s&$top=%s&$inlinecount=allpages", fmt.Sprint(skip), fmt.Sprint(top))
|
|
}
|
|
|
|
// ConvertTime formats an ABAP timestamp string from format /Date(1585576807000+0000)/ into a UNIX timestamp and returns it
|
|
func (api *SAP_COM_0510) ConvertTime(logTimeStamp string) time.Time {
|
|
seconds := strings.TrimPrefix(strings.TrimSuffix(logTimeStamp, "000+0000)/"), "/Date(")
|
|
n, error := strconv.ParseInt(seconds, 10, 64)
|
|
if error != nil {
|
|
return time.Unix(0, 0).UTC()
|
|
}
|
|
t := time.Unix(n, 0).UTC()
|
|
return t
|
|
}
|
|
|
|
// Dummy implementation of the "optional" method UpdateRepoWithBYOGCredentials (only used in SAP_COM_0948)
|
|
func (api *SAP_COM_0510) UpdateRepoWithBYOGCredentials(byogAuthMethod string, byogUsername string, byogPassword string) {
|
|
panic("UpdateRepoWithBYOGCredentials cannot be used in SAP_COM_0510")
|
|
}
|
|
|
|
// Dummy implementation of the "optional" method LogArchive (only used in SAP_COM_0948)
|
|
func (api *SAP_COM_0510) GetLogArchive() (result []byte, err error) {
|
|
panic("GetLogArchive cannot be used in SAP_COM_0510")
|
|
}
|