1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-16 05:16:08 +02:00

OData V4 error message handling (#5013)

* OData V4 error message handling

* Adapt tests
This commit is contained in:
Daniel Mieg 2024-08-16 13:41:23 +02:00 committed by GitHub
parent f902f48ac9
commit 8109f6fbe8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 116 additions and 49 deletions

View File

@ -178,7 +178,7 @@ func GetHTTPResponse(requestType string, connectionDetails ConnectionDetailsHTTP
return httpResponse, err
}
// HandleHTTPError handles ABAP error messages which can occur when using OData services
// HandleHTTPError handles ABAP error messages which can occur when using OData V2 services
//
// The point of this function is to enrich the error received from a HTTP Request (which is passed as a parameter to this function).
// Further error details may be present in the response body of the HTTP response.
@ -218,10 +218,11 @@ func HandleHTTPError(resp *http.Response, err error, message string, connectionD
return errorCode, err
}
// GetErrorDetailsFromResponse parses OData V2 Responses containing ABAP Error messages
func GetErrorDetailsFromResponse(resp *http.Response) (errorString string, errorCode string, err error) {
// Include the error message of the ABAP Environment system, if available
var abapErrorResponse AbapError
var abapErrorResponse AbapErrorODataV2
bodyText, readError := io.ReadAll(resp.Body)
if readError != nil {
return "", "", readError
@ -233,7 +234,7 @@ func GetErrorDetailsFromResponse(resp *http.Response) (errorString string, error
}
if _, ok := abapResp["error"]; ok {
json.Unmarshal(*abapResp["error"], &abapErrorResponse)
if (AbapError{}) != abapErrorResponse {
if (AbapErrorODataV2{}) != abapErrorResponse {
log.Entry().WithField("ErrorCode", abapErrorResponse.Code).Debug(abapErrorResponse.Message.Value)
return abapErrorResponse.Message.Value, abapErrorResponse.Code, nil
}
@ -311,12 +312,18 @@ type ConnectionDetailsHTTP struct {
CertificateNames []string `json:"-"`
}
// AbapError contains the error code and the error message for ABAP errors
type AbapError struct {
// AbapErrorODataV2 contains the error code and the error message for ABAP errors
type AbapErrorODataV2 struct {
Code string `json:"code"`
Message AbapErrorMessage `json:"message"`
}
// AbapErrorODataV4 contains the error code and the error message for ABAP errors
type AbapErrorODataV4 struct {
Code string `json:"code"`
Message string `json:"message"`
}
// AbapErrorMessage contains the lanuage and value fields for ABAP errors
type AbapErrorMessage struct {
Lang string `json:"lang"`

View File

@ -7,6 +7,7 @@ import (
"net/http"
"net/http/cookiejar"
"reflect"
"regexp"
"strings"
"time"
@ -67,7 +68,7 @@ func (api *SAP_COM_0948) GetExecutionLog() (execLog ExecutionLog, err error) {
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
_, err = handleHTTPError(resp, err, api.failureMessage, connectionDetails)
return execLog, err
}
defer resp.Body.Close()
@ -161,7 +162,7 @@ func (api *SAP_COM_0948) GetLogProtocol(logOverviewEntry LogResultsV2, page int)
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
_, err = handleHTTPError(resp, err, api.failureMessage, connectionDetails)
return nil, 0, err
}
defer resp.Body.Close()
@ -185,7 +186,7 @@ func (api *SAP_COM_0948) GetLogOverview() (result []LogResultsV2, err error) {
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
_, err = handleHTTPError(resp, err, api.failureMessage, connectionDetails)
return nil, err
}
defer resp.Body.Close()
@ -220,7 +221,7 @@ func (api *SAP_COM_0948) GetAction() (string, error) {
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
_, err = handleHTTPError(resp, err, api.failureMessage, connectionDetails)
return "E", err
}
defer resp.Body.Close()
@ -248,7 +249,7 @@ func (api *SAP_COM_0948) GetRepository() (bool, string, error, bool) {
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)
_, errRepo := handleHTTPError(resp, err, "Reading the Repository / Software Component failed", api.con)
return false, "", errRepo, false
}
defer resp.Body.Close()
@ -323,7 +324,7 @@ func (api *SAP_COM_0948) triggerRequest(cloneConnectionDetails ConnectionDetails
}
resp, err = GetHTTPResponse("POST", cloneConnectionDetails, jsonBody, api.client)
if err != nil {
errorCode, err = HandleHTTPError(resp, err, "Triggering the action failed", api.con)
errorCode, err = handleHTTPError(resp, err, "Triggering the action failed", api.con)
if slices.Contains(api.retryAllowedErrorCodes, errorCode) {
// Error Code allows for retry
continue
@ -366,7 +367,7 @@ func (api *SAP_COM_0948) initialRequest() error {
// 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)
_, err = handleHTTPError(resp, err, "Authentication on the ABAP system failed", api.con)
return err
}
defer resp.Body.Close()
@ -431,3 +432,62 @@ func (api *SAP_COM_0948) ConvertTime(logTimeStamp string) time.Time {
}
return t
}
func handleHTTPError(resp *http.Response, err error, message string, connectionDetails ConnectionDetailsHTTP) (string, error) {
var errorText string
var errorCode string
var parsingError error
if resp == nil {
// Response is nil in case of a timeout
log.Entry().WithError(err).WithField("ABAP Endpoint", connectionDetails.URL).Error("Request failed")
match, _ := regexp.MatchString(".*EOF$", err.Error())
if match {
AddDefaultDashedLine(1)
log.Entry().Infof("%s", "A connection could not be established to the ABAP system. The typical root cause is the network configuration (firewall, IP allowlist, etc.)")
AddDefaultDashedLine(1)
}
log.Entry().Infof("Error message: %s,", err.Error())
} else {
defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.Status).WithField("User", connectionDetails.User).WithField("URL", connectionDetails.URL).Error(message)
errorText, errorCode, parsingError = getErrorDetailsFromResponse(resp)
if parsingError != nil {
return "", err
}
abapError := errors.New(fmt.Sprintf("%s - %s", errorCode, errorText))
err = errors.Wrap(abapError, err.Error())
}
return errorCode, err
}
func getErrorDetailsFromResponse(resp *http.Response) (errorString string, errorCode string, err error) {
// Include the error message of the ABAP Environment system, if available
var abapErrorResponse AbapErrorODataV4
bodyText, readError := io.ReadAll(resp.Body)
if readError != nil {
return "", "", readError
}
var abapResp map[string]*json.RawMessage
errUnmarshal := json.Unmarshal(bodyText, &abapResp)
if errUnmarshal != nil {
return "", "", errUnmarshal
}
if _, ok := abapResp["error"]; ok {
json.Unmarshal(*abapResp["error"], &abapErrorResponse)
if (AbapErrorODataV4{}) != abapErrorResponse {
log.Entry().WithField("ErrorCode", abapErrorResponse.Code).Debug(abapErrorResponse.Message)
return abapErrorResponse.Message, abapErrorResponse.Code, nil
}
}
return "", "", errors.New("Could not parse the JSON error response")
}

View File

@ -50,7 +50,7 @@ func TestRetry0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
api.setSleepTimeConfig(time.Nanosecond, 120*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -66,7 +66,7 @@ func TestRetry0948(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{ "status" : "R", "UUID" : "GUID" }`,
`{"error" : { "code" : "A4C_A2G/224", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/224", "message" : "Error Text" } }`,
`{ }`,
},
Token: "myToken",
@ -80,8 +80,8 @@ func TestRetry0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api.setSleepTimeConfig(time.Nanosecond, 120*time.Nanosecond)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
api.setSleepTimeConfig(time.Nanosecond, 20*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -95,15 +95,15 @@ func TestRetry0948(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{ }`,
},
Token: "myToken",
@ -124,12 +124,12 @@ func TestRetry0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
api.setSleepTimeConfig(time.Nanosecond, 20*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
api.(*SAP_COM_0948).maxRetries = 20
api.(*SAP_COM_0948).maxRetries = 5
errAction := api.(*SAP_COM_0948).triggerRequest(ConnectionDetailsHTTP{User: "CC_USER", Password: "abc123", URL: "https://example.com/path"}, []byte("{}"))
assert.ErrorContains(t, errAction, "HTTP 400: A4C_A2G/228 - Error Text")
@ -142,15 +142,15 @@ func TestRetry0948(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : "Error Text" } }`,
`{ }`,
},
Token: "myToken",
@ -171,7 +171,7 @@ func TestRetry0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
api.setSleepTimeConfig(time.Nanosecond, 999*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -200,7 +200,7 @@ func TestClone0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -223,7 +223,7 @@ func TestClone0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
api.setSleepTimeConfig(time.Nanosecond, 120*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -252,7 +252,7 @@ func TestClone0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
api.setSleepTimeConfig(time.Nanosecond, 120*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -317,7 +317,7 @@ func TestPull0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -339,7 +339,7 @@ func TestPull0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -363,7 +363,7 @@ func TestCheckout0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -385,7 +385,7 @@ func TestCheckout0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -409,7 +409,7 @@ func TestGetRepo0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -434,7 +434,7 @@ func TestCreateTag0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -456,7 +456,7 @@ func TestCreateTag0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -478,7 +478,7 @@ func TestCreateTag0948(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api, err := apiManager.GetAPI(conTest0948, repoTest0948)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0948{}, api.(*SAP_COM_0948), "API has wrong type")
@ -565,7 +565,7 @@ func TestGetExecutionLog(t *testing.T) {
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, _ := apiManager.GetAPI(con, Repository{Name: "/DMO/REPO"})
api, _ := apiManager.GetAPI(conTest0948, Repository{Name: "/DMO/REPO"})
results, errAction := api.GetExecutionLog()
assert.NoError(t, errAction)