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

Execute checkout & pull when already cloned (#3850)

* Execute checkout & pull when already cloned

* Revert

* Disallow config overload

* Add custom error handler for clone

* Implement new pull parameters

* Add tests

* Formatting

* Rename Param

* Add comment

* Add docu

* Adapt testst to merge

* Fix Unit Test
This commit is contained in:
Daniel Mieg 2022-06-30 10:43:33 +02:00 committed by GitHub
parent ce161590ae
commit 6ff4143b08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 383 additions and 47 deletions

View File

@ -74,7 +74,7 @@ func runAbapEnvironmentCheckoutBranch(options *abapEnvironmentCheckoutBranchOpti
if err != nil { if err != nil {
return fmt.Errorf("Something failed during the checkout: %w", err) return fmt.Errorf("Something failed during the checkout: %w", err)
} }
log.Entry().Info("-------------------------") log.Entry().Infof("-------------------------")
log.Entry().Info("All branches were checked out successfully") log.Entry().Info("All branches were checked out successfully")
return nil return nil
} }
@ -189,15 +189,15 @@ func handleCheckout(repo abaputils.Repository, checkoutConnectionDetails abaputi
func startCheckoutLogs(branchName string, repositoryName string) { func startCheckoutLogs(branchName string, repositoryName string) {
log.Entry().Infof("Starting to switch branch to branch '%v' on repository '%v'", branchName, repositoryName) log.Entry().Infof("Starting to switch branch to branch '%v' on repository '%v'", branchName, repositoryName)
log.Entry().Info("--------------------------------") log.Entry().Infof("-------------------------")
log.Entry().Info("Start checkout branch: " + branchName) log.Entry().Info("Start checkout branch: " + branchName)
log.Entry().Info("--------------------------------") log.Entry().Infof("-------------------------")
} }
func finishCheckoutLogs(branchName string, repositoryName string) { func finishCheckoutLogs(branchName string, repositoryName string) {
log.Entry().Info("--------------------------------") log.Entry().Infof("-------------------------")
log.Entry().Infof("Checkout of branch %v on repository %v was successful", branchName, repositoryName) log.Entry().Infof("Checkout of branch %v on repository %v was successful", branchName, repositoryName)
log.Entry().Info("--------------------------------") log.Entry().Infof("-------------------------")
} }
func convertCheckoutConfig(config *abapEnvironmentCheckoutBranchOptions) abaputils.AbapEnvironmentOptions { func convertCheckoutConfig(config *abapEnvironmentCheckoutBranchOptions) abaputils.AbapEnvironmentOptions {

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"net/http/cookiejar" "net/http/cookiejar"
"reflect" "reflect"
"time" "time"
@ -59,6 +60,11 @@ func runAbapEnvironmentCloneGitRepo(config *abapEnvironmentCloneGitRepoOptions,
Password: connectionDetails.Password, Password: connectionDetails.Password,
}) })
errConfig := checkConfiguration(config)
if errConfig != nil {
return errors.Wrap(errConfig, "The provided configuration is not allowed")
}
repositories, errGetRepos := abaputils.GetRepositories(&abaputils.RepositoriesConfig{BranchName: config.BranchName, RepositoryName: config.RepositoryName, Repositories: config.Repositories}, true) repositories, errGetRepos := abaputils.GetRepositories(&abaputils.RepositoriesConfig{BranchName: config.BranchName, RepositoryName: config.RepositoryName, Repositories: config.Repositories}, true)
if errGetRepos != nil { if errGetRepos != nil {
return fmt.Errorf("Something failed during the clone: %w", errGetRepos) return fmt.Errorf("Something failed during the clone: %w", errGetRepos)
@ -75,28 +81,40 @@ func runAbapEnvironmentCloneGitRepo(config *abapEnvironmentCloneGitRepoOptions,
log.Entry().Info("-------------------------") log.Entry().Info("-------------------------")
// Triggering the Clone of the repository into the ABAP Environment system // Triggering the Clone of the repository into the ABAP Environment system
uriConnectionDetails, errorTriggerClone := triggerClone(repo, connectionDetails, client) uriConnectionDetails, errorTriggerClone, didCheckoutPullInstead := triggerClone(repo, connectionDetails, client)
if errorTriggerClone != nil { if errorTriggerClone != nil {
return errors.Wrapf(errorTriggerClone, errorString) return errors.Wrapf(errorTriggerClone, errorString)
} }
// Polling the status of the repository import on the ABAP Environment system if !didCheckoutPullInstead {
status, errorPollEntity := abaputils.PollEntity(repo.Name, uriConnectionDetails, client, com.GetPollIntervall()) // Polling the status of the repository import on the ABAP Environment system
if errorPollEntity != nil { // If the repository had been cloned already, as checkout/pull has been done - polling the status is not necessary anymore
return errors.Wrapf(errorPollEntity, errorString) status, errorPollEntity := abaputils.PollEntity(repo.Name, uriConnectionDetails, client, com.GetPollIntervall())
if errorPollEntity != nil {
return errors.Wrapf(errorPollEntity, errorString)
}
if status == "E" {
return errors.New("Clone of " + logString + " failed on the ABAP System")
}
log.Entry().Info("The " + logString + " was cloned successfully")
} }
if status == "E" {
return errors.New("Clone of " + logString + " failed on the ABAP System")
}
log.Entry().Info("The " + logString + " was cloned successfully")
} }
log.Entry().Info("-------------------------") log.Entry().Info("-------------------------")
log.Entry().Info("All repositories were cloned successfully") log.Entry().Info("All repositories were cloned successfully")
return nil return nil
} }
func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (abaputils.ConnectionDetailsHTTP, error) { func checkConfiguration(config *abapEnvironmentCloneGitRepoOptions) error {
if config.Repositories != "" && config.RepositoryName != "" {
return errors.New("It is not allowed to configure the parameters `repositories`and `repositoryName` at the same time")
}
if config.Repositories == "" && config.RepositoryName == "" {
return errors.New("Please provide one of the following parameters: `repositories` or `repositoryName`")
}
return nil
}
func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (abaputils.ConnectionDetailsHTTP, error, bool) {
uriConnectionDetails := cloneConnectionDetails uriConnectionDetails := cloneConnectionDetails
cloneConnectionDetails.XCsrfToken = "fetch" cloneConnectionDetails.XCsrfToken = "fetch"
@ -107,7 +125,7 @@ func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.Co
resp, err := abaputils.GetHTTPResponse("HEAD", cloneConnectionDetails, nil, client) resp, err := abaputils.GetHTTPResponse("HEAD", cloneConnectionDetails, nil, client)
if err != nil { if err != nil {
err = abaputils.HandleHTTPError(resp, err, "Authentication on the ABAP system failed", cloneConnectionDetails) err = abaputils.HandleHTTPError(resp, err, "Authentication on the ABAP system failed", cloneConnectionDetails)
return uriConnectionDetails, err return uriConnectionDetails, err, false
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -117,14 +135,14 @@ func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.Co
// Trigger the Clone of a Repository // Trigger the Clone of a Repository
if repo.Name == "" { if repo.Name == "" {
return uriConnectionDetails, errors.New("An empty string was passed for the parameter 'repositoryName'") return uriConnectionDetails, errors.New("An empty string was passed for the parameter 'repositoryName'"), false
} }
jsonBody := []byte(repo.GetCloneRequestBody()) jsonBody := []byte(repo.GetCloneRequestBody())
resp, err = abaputils.GetHTTPResponse("POST", cloneConnectionDetails, jsonBody, client) resp, err = abaputils.GetHTTPResponse("POST", cloneConnectionDetails, jsonBody, client)
if err != nil { if err != nil {
err = abaputils.HandleHTTPError(resp, err, "Could not clone the "+repo.GetCloneLogString(), uriConnectionDetails) err, alreadyCloned := handleCloneError(resp, err, cloneConnectionDetails, client, repo)
return uriConnectionDetails, err return uriConnectionDetails, err, alreadyCloned
} }
defer resp.Body.Close() defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", repo.Name).WithField("branchName", repo.Branch).WithField("commitID", repo.CommitID).WithField("Tag", repo.Tag).Info("Triggered Clone of Repository / Software Component") log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", repo.Name).WithField("branchName", repo.Branch).WithField("commitID", repo.CommitID).WithField("Tag", repo.Tag).Info("Triggered Clone of Repository / Software Component")
@ -134,20 +152,83 @@ func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.Co
var abapResp map[string]*json.RawMessage var abapResp map[string]*json.RawMessage
bodyText, errRead := ioutil.ReadAll(resp.Body) bodyText, errRead := ioutil.ReadAll(resp.Body)
if errRead != nil { if errRead != nil {
return uriConnectionDetails, err return uriConnectionDetails, err, false
} }
json.Unmarshal(bodyText, &abapResp) json.Unmarshal(bodyText, &abapResp)
json.Unmarshal(*abapResp["d"], &body) json.Unmarshal(*abapResp["d"], &body)
if reflect.DeepEqual(abaputils.CloneEntity{}, body) { if reflect.DeepEqual(abaputils.CloneEntity{}, body) {
log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", repo.Name).WithField("branchName", repo.Branch).WithField("commitID", repo.CommitID).WithField("Tag", repo.Tag).Error("Could not Clone the Repository / Software Component") log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", repo.Name).WithField("branchName", repo.Branch).WithField("commitID", repo.CommitID).WithField("Tag", repo.Tag).Error("Could not Clone the Repository / Software Component")
err := errors.New("Request to ABAP System not successful") err := errors.New("Request to ABAP System not successful")
return uriConnectionDetails, err return uriConnectionDetails, err, false
} }
// The entity "Clones" does not allow for polling. To poll the progress, the related entity "Pull" has to be called // The entity "Clones" does not allow for polling. To poll the progress, the related entity "Pull" has to be called
// While "Clones" has the key fields UUID, SC_NAME and BRANCH_NAME, "Pull" only has the key field UUID // While "Clones" has the key fields UUID, SC_NAME and BRANCH_NAME, "Pull" only has the key field UUID
uriConnectionDetails.URL = uriConnectionDetails.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull(uuid=guid'" + body.UUID + "')" uriConnectionDetails.URL = uriConnectionDetails.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull(uuid=guid'" + body.UUID + "')"
return uriConnectionDetails, nil return uriConnectionDetails, nil, false
}
func handleCloneError(resp *http.Response, err error, cloneConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, repo abaputils.Repository) (returnedError error, alreadyCloned bool) {
alreadyCloned = false
returnedError = nil
if resp == nil {
log.Entry().WithError(err).WithField("ABAP Endpoint", cloneConnectionDetails.URL).Error("Request failed")
returnedError = errors.New("Response is nil")
return
}
defer resp.Body.Close()
errorText, errorCode, parsingError := abaputils.GetErrorDetailsFromResponse(resp)
if parsingError != nil {
returnedError = err
return
}
if errorCode == "A4C_A2G/257" {
// With the latest release, a repeated "clone" was prohibited
// As an intermediate workaround, we react to the error message A4C_A2G/257 that gets thrown, if the repository had already been cloned
// In this case, a checkout branch and a pull will be performed
alreadyCloned = true
log.Entry().Infof("-------------------------")
log.Entry().Infof("-------------------------")
log.Entry().Infof("%s", "The repository / software component has already been cloned on the ABAP Environment system ")
log.Entry().Infof("%s", "A `checkout branch` and a `pull` will be performed instead")
log.Entry().Infof("-------------------------")
log.Entry().Infof("-------------------------")
checkoutOptions := abapEnvironmentCheckoutBranchOptions{
Username: cloneConnectionDetails.User,
Password: cloneConnectionDetails.Password,
Host: cloneConnectionDetails.Host,
RepositoryName: repo.Name,
BranchName: repo.Branch,
}
c := command.Command{}
c.Stdout(log.Writer())
c.Stderr(log.Writer())
com := abaputils.AbapUtils{
Exec: &c,
}
returnedError = runAbapEnvironmentCheckoutBranch(&checkoutOptions, &com, client)
if returnedError != nil {
return
}
log.Entry().Infof("-------------------------")
log.Entry().Infof("-------------------------")
pullOptions := *&abapEnvironmentPullGitRepoOptions{
Username: cloneConnectionDetails.User,
Password: cloneConnectionDetails.Password,
Host: cloneConnectionDetails.Host,
RepositoryName: repo.Name,
CommitID: repo.CommitID,
}
returnedError = runAbapEnvironmentPullGitRepo(&pullOptions, &com, client)
if returnedError != nil {
return
}
} else {
log.Entry().WithField("StatusCode", resp.Status).Error("Could not clone the " + repo.GetCloneLogString())
abapError := errors.New(fmt.Sprintf("%s - %s", errorCode, errorText))
returnedError = errors.Wrap(abapError, err.Error())
}
return
} }
func convertCloneConfig(config *abapEnvironmentCloneGitRepoOptions) abaputils.AbapEnvironmentOptions { func convertCloneConfig(config *abapEnvironmentCloneGitRepoOptions) abaputils.AbapEnvironmentOptions {

View File

@ -43,7 +43,7 @@ func AbapEnvironmentCloneGitRepoCommand() *cobra.Command {
var createAbapEnvironmentCloneGitRepoCmd = &cobra.Command{ var createAbapEnvironmentCloneGitRepoCmd = &cobra.Command{
Use: STEP_NAME, Use: STEP_NAME,
Short: "Clones a git repository to a SAP BTP ABAP Environment system", Short: "Clones a git repository to a SAP BTP ABAP Environment system",
Long: `Clones a git repository (Software Component) to a SAP BTP ABAP Environment system. Long: `Clones a git repository (Software Component) to a SAP BTP ABAP Environment system. If the repository is already cloned, the step will checkout the configured branch and pull the specified commit, instead.
Please provide either of the following options: Please provide either of the following options:
* The host and credentials the BTP ABAP Environment system itself. The credentials must be configured for the Communication Scenario [SAP_COM_0510](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/b04a9ae412894725a2fc539bfb1ca055.html). * The host and credentials the BTP ABAP Environment system itself. The credentials must be configured for the Communication Scenario [SAP_COM_0510](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/b04a9ae412894725a2fc539bfb1ca055.html).

View File

@ -1,13 +1,16 @@
package cmd package cmd
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"testing" "testing"
"github.com/SAP/jenkins-library/pkg/abaputils" "github.com/SAP/jenkins-library/pkg/abaputils"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -31,7 +34,7 @@ func init() {
} }
func TestCloneStep(t *testing.T) { func TestCloneStep(t *testing.T) {
t.Run("Run Step - Successful", func(t *testing.T) { t.Run("Run Step - Successful with repositories.yml", func(t *testing.T) {
var autils = abaputils.AUtilsMock{} var autils = abaputils.AUtilsMock{}
defer autils.Cleanup() defer autils.Cleanup()
autils.ReturnedConnectionDetailsHTTP.Password = "password" autils.ReturnedConnectionDetailsHTTP.Password = "password"
@ -73,8 +76,6 @@ repositories:
CfServiceKeyName: "testServiceKey", CfServiceKeyName: "testServiceKey",
Username: "testUser", Username: "testUser",
Password: "testPassword", Password: "testPassword",
RepositoryName: "testRepo1",
BranchName: "testBranch1",
Repositories: "filename.yaml", Repositories: "filename.yaml",
} }
@ -95,13 +96,44 @@ repositories:
`{"d" : { "status" : "R" } }`, `{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`, `{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`, `{"d" : { "status" : "R" } }`,
},
Token: "myToken",
}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, client)
assert.NoError(t, err, "Did not expect error")
assert.Equal(t, 0, len(client.BodyList), "Not all requests were done")
})
t.Run("Run Step - Successful with repositoryName", func(t *testing.T) {
var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
autils.ReturnedConnectionDetailsHTTP.Password = "password"
autils.ReturnedConnectionDetailsHTTP.User = "user"
autils.ReturnedConnectionDetailsHTTP.URL = "https://example.com"
autils.ReturnedConnectionDetailsHTTP.XCsrfToken = "xcsrftoken"
config := abapEnvironmentCloneGitRepoOptions{
CfAPIEndpoint: "https://api.endpoint.com",
CfOrg: "testOrg",
CfSpace: "testSpace",
CfServiceInstance: "testInstance",
CfServiceKeyName: "testServiceKey",
Username: "testUser",
Password: "testPassword",
RepositoryName: "testRepo1",
BranchName: "testBranch1",
}
logResultSuccess := fmt.Sprintf(`{"d": { "sc_name": "/DMO/SWC", "status": "S", "to_Log_Overview": { "results": [ { "log_index": 1, "log_name": "Main Import", "type_of_found_issues": "Success", "timestamp": "/Date(1644332299000+0000)/", "to_Log_Protocol": { "results": [ { "log_index": 1, "index_no": "1", "log_name": "", "type": "Info", "descr": "Main import", "timestamp": null, "criticality": 0 } ] } } ] } } }`)
client := &abaputils.ClientMock{
BodyList: []string{
`{"d" : ` + executionLogStringClone + `}`, `{"d" : ` + executionLogStringClone + `}`,
logResultSuccess, logResultSuccess,
`{"d" : { "EntitySets" : [ "LogOverviews" ] } }`, `{"d" : { "EntitySets" : [ "LogOverviews" ] } }`,
`{"d" : { "status" : "S" } }`, `{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`, `{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`, `{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
}, },
Token: "myToken", Token: "myToken",
StatusCode: 200, StatusCode: 200,
@ -299,8 +331,6 @@ repositories:
CfServiceKeyName: "testServiceKey", CfServiceKeyName: "testServiceKey",
Username: "testUser", Username: "testUser",
Password: "testPassword", Password: "testPassword",
RepositoryName: "testRepo1",
BranchName: "testBranch1",
Repositories: "filename.yaml", Repositories: "filename.yaml",
} }
@ -319,4 +349,230 @@ repositories:
} }
}) })
t.Run("Config overload", func(t *testing.T) {
var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
autils.ReturnedConnectionDetailsHTTP.Password = "password"
autils.ReturnedConnectionDetailsHTTP.User = "user"
autils.ReturnedConnectionDetailsHTTP.URL = "https://example.com"
autils.ReturnedConnectionDetailsHTTP.XCsrfToken = "xcsrftoken"
config := abapEnvironmentCloneGitRepoOptions{
CfAPIEndpoint: "https://api.endpoint.com",
CfOrg: "testOrg",
CfSpace: "testSpace",
CfServiceInstance: "testInstance",
CfServiceKeyName: "testServiceKey",
Username: "testUser",
Password: "testPassword",
Repositories: "filename.yaml",
RepositoryName: "/DMO/REPO",
BranchName: "Branch",
}
logResultError := fmt.Sprintf(`{"d": { "sc_name": "/DMO/SWC", "status": "S", "to_Log_Overview": { "results": [ { "log_index": 1, "log_name": "Main Import", "type_of_found_issues": "Error", "timestamp": "/Date(1644332299000+0000)/", "to_Log_Protocol": { "results": [ { "log_index": 1, "index_no": "1", "log_name": "", "type": "Info", "descr": "Main import", "timestamp": null, "criticality": 0 } ] } } ] } } }`)
client := &abaputils.ClientMock{
BodyList: []string{
logResultError,
`{"d" : { "EntitySets" : [ "LogOverviews" ] } }`,
`{"d" : { "status" : "E" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
},
Token: "myToken",
StatusCode: 200,
}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, client)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "The provided configuration is not allowed: It is not allowed to configure the parameters `repositories`and `repositoryName` at the same time", err.Error(), "Expected different error message")
}
})
}
func TestALreadyCloned(t *testing.T) {
t.Run("Already Cloned", func(t *testing.T) {
var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
autils.ReturnedConnectionDetailsHTTP.Password = "password"
autils.ReturnedConnectionDetailsHTTP.User = "user"
autils.ReturnedConnectionDetailsHTTP.URL = "https://example.com"
autils.ReturnedConnectionDetailsHTTP.Host = "example.com"
autils.ReturnedConnectionDetailsHTTP.XCsrfToken = "xcsrftoken"
logResultSuccess := fmt.Sprintf(`{"d": { "sc_name": "/DMO/SWC", "status": "S", "to_Log_Overview": { "results": [ { "log_index": 1, "log_name": "Main Import", "type_of_found_issues": "Success", "timestamp": "/Date(1644332299000+0000)/", "to_Log_Protocol": { "results": [ { "log_index": 1, "index_no": "1", "log_name": "", "type": "Info", "descr": "Main import", "timestamp": null, "criticality": 0 } ] } } ] } } }`)
client := &abaputils.ClientMock{
BodyList: []string{
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "EntitySets" : [ "LogOverviews" ] } }`,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "EntitySets" : [ "LogOverviews" ] } }`,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
},
Token: "myToken",
StatusCode: 200,
}
bodyString := `{"error" : { "code" : "A4C_A2G/257", "message" : { "lang" : "de", "value" : "Already Cloned"} } }`
body := []byte(bodyString)
resp := http.Response{
Status: "400 Bad Request",
StatusCode: 400,
Body: ioutil.NopCloser(bytes.NewReader(body)),
}
repo := abaputils.Repository{
Name: "Test",
Branch: "Branch",
CommitID: "abcd1234",
}
err := errors.New("Custom Error")
err, _ = handleCloneError(&resp, err, autils.ReturnedConnectionDetailsHTTP, client, repo)
assert.NoError(t, err, "Did not expect error")
})
t.Run("Already Cloned, Pull fails", func(t *testing.T) {
var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
autils.ReturnedConnectionDetailsHTTP.Password = "password"
autils.ReturnedConnectionDetailsHTTP.User = "user"
autils.ReturnedConnectionDetailsHTTP.URL = "https://example.com"
autils.ReturnedConnectionDetailsHTTP.Host = "example.com"
autils.ReturnedConnectionDetailsHTTP.XCsrfToken = "xcsrftoken"
logResultSuccess := fmt.Sprintf(`{"d": { "sc_name": "/DMO/SWC", "status": "S", "to_Log_Overview": { "results": [ { "log_index": 1, "log_name": "Main Import", "type_of_found_issues": "Success", "timestamp": "/Date(1644332299000+0000)/", "to_Log_Protocol": { "results": [ { "log_index": 1, "index_no": "1", "log_name": "", "type": "Info", "descr": "Main import", "timestamp": null, "criticality": 0 } ] } } ] } } }`)
client := &abaputils.ClientMock{
BodyList: []string{
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "EntitySets" : [ "LogOverviews" ] } }`,
`{"d" : { "status" : "E" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "EntitySets" : [ "LogOverviews" ] } }`,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
},
Token: "myToken",
StatusCode: 200,
}
bodyString := `{"error" : { "code" : "A4C_A2G/257", "message" : { "lang" : "de", "value" : "Already Cloned"} } }`
body := []byte(bodyString)
resp := http.Response{
Status: "400 Bad Request",
StatusCode: 400,
Body: ioutil.NopCloser(bytes.NewReader(body)),
}
repo := abaputils.Repository{
Name: "Test",
Branch: "Branch",
CommitID: "abcd1234",
}
err := errors.New("Custom Error")
err, _ = handleCloneError(&resp, err, autils.ReturnedConnectionDetailsHTTP, client, repo)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Pull of the repository / software component 'Test', commit 'abcd1234' failed on the ABAP system", err.Error(), "Expected different error message")
}
})
t.Run("Already Cloned, checkout fails", func(t *testing.T) {
var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
autils.ReturnedConnectionDetailsHTTP.Password = "password"
autils.ReturnedConnectionDetailsHTTP.User = "user"
autils.ReturnedConnectionDetailsHTTP.URL = "https://example.com"
autils.ReturnedConnectionDetailsHTTP.Host = "example.com"
autils.ReturnedConnectionDetailsHTTP.XCsrfToken = "xcsrftoken"
logResultSuccess := fmt.Sprintf(`{"d": { "sc_name": "/DMO/SWC", "status": "S", "to_Log_Overview": { "results": [ { "log_index": 1, "log_name": "Main Import", "type_of_found_issues": "Success", "timestamp": "/Date(1644332299000+0000)/", "to_Log_Protocol": { "results": [ { "log_index": 1, "index_no": "1", "log_name": "", "type": "Info", "descr": "Main import", "timestamp": null, "criticality": 0 } ] } } ] } } }`)
client := &abaputils.ClientMock{
BodyList: []string{
logResultSuccess,
`{"d" : { "EntitySets" : [ "LogOverviews" ] } }`,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
logResultSuccess,
`{"d" : { "EntitySets" : [ "LogOverviews" ] } }`,
`{"d" : { "status" : "E" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
},
Token: "myToken",
StatusCode: 200,
}
bodyString := `{"error" : { "code" : "A4C_A2G/257", "message" : { "lang" : "de", "value" : "Already Cloned"} } }`
body := []byte(bodyString)
resp := http.Response{
Status: "400 Bad Request",
StatusCode: 400,
Body: ioutil.NopCloser(bytes.NewReader(body)),
}
repo := abaputils.Repository{
Name: "Test",
Branch: "Branch",
CommitID: "abcd1234",
}
err := errors.New("Custom Error")
err, _ = handleCloneError(&resp, err, autils.ReturnedConnectionDetailsHTTP, client, repo)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Something failed during the checkout: Checkout failed: Checkout of branch Branch failed on the ABAP System", err.Error(), "Expected different error message")
}
})
t.Run("Already Cloned, checkout fails", func(t *testing.T) {
var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
autils.ReturnedConnectionDetailsHTTP.Password = "password"
autils.ReturnedConnectionDetailsHTTP.User = "user"
autils.ReturnedConnectionDetailsHTTP.URL = "https://example.com"
autils.ReturnedConnectionDetailsHTTP.Host = "example.com"
autils.ReturnedConnectionDetailsHTTP.XCsrfToken = "xcsrftoken"
client := &abaputils.ClientMock{
BodyList: []string{
`{"d" : { "status" : "R" } }`,
},
Token: "myToken",
StatusCode: 200,
}
bodyString := `{"error" : { "code" : "A4C_A2G/258", "message" : { "lang" : "de", "value" : "Some error message"} } }`
body := []byte(bodyString)
resp := http.Response{
Status: "400 Bad Request",
StatusCode: 400,
Body: ioutil.NopCloser(bytes.NewReader(body)),
}
repo := abaputils.Repository{
Name: "Test",
Branch: "Branch",
CommitID: "abcd1234",
}
err := errors.New("Custom Error")
err, _ = handleCloneError(&resp, err, autils.ReturnedConnectionDetailsHTTP, client, repo)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Custom Error: A4C_A2G/258 - Some error message", err.Error(), "Expected different error message")
}
})
} }

View File

@ -176,40 +176,39 @@ func HandleHTTPError(resp *http.Response, err error, message string, connectionD
log.Entry().WithField("StatusCode", resp.Status).Error(message) log.Entry().WithField("StatusCode", resp.Status).Error(message)
errorDetails, parsingError := getErrorDetailsFromResponse(resp) errorText, errorCode, parsingError := GetErrorDetailsFromResponse(resp)
if parsingError != nil { if parsingError != nil {
return err return err
} }
abapError := errors.New(errorDetails) abapError := errors.New(fmt.Sprintf("%s - %s", errorCode, errorText))
err = errors.Wrap(abapError, err.Error()) err = errors.Wrap(abapError, err.Error())
} }
return err return err
} }
func getErrorDetailsFromResponse(resp *http.Response) (errorString string, err error) { func GetErrorDetailsFromResponse(resp *http.Response) (errorString string, errorCode string, err error) {
// Include the error message of the ABAP Environment system, if available // Include the error message of the ABAP Environment system, if available
var abapErrorResponse AbapError var abapErrorResponse AbapError
bodyText, readError := ioutil.ReadAll(resp.Body) bodyText, readError := ioutil.ReadAll(resp.Body)
if readError != nil { if readError != nil {
return errorString, readError return "", "", readError
} }
var abapResp map[string]*json.RawMessage var abapResp map[string]*json.RawMessage
errUnmarshal := json.Unmarshal(bodyText, &abapResp) errUnmarshal := json.Unmarshal(bodyText, &abapResp)
if errUnmarshal != nil { if errUnmarshal != nil {
return errorString, errUnmarshal return "", "", errUnmarshal
} }
if _, ok := abapResp["error"]; ok { if _, ok := abapResp["error"]; ok {
json.Unmarshal(*abapResp["error"], &abapErrorResponse) json.Unmarshal(*abapResp["error"], &abapErrorResponse)
if (AbapError{}) != abapErrorResponse { if (AbapError{}) != abapErrorResponse {
log.Entry().WithField("ErrorCode", abapErrorResponse.Code).Error(abapErrorResponse.Message.Value) log.Entry().WithField("ErrorCode", abapErrorResponse.Code).Debug(abapErrorResponse.Message.Value)
errorString = fmt.Sprintf("%s - %s", abapErrorResponse.Code, abapErrorResponse.Message.Value) return abapErrorResponse.Message.Value, abapErrorResponse.Code, nil
return errorString, nil
} }
} }
return errorString, errors.New("Could not parse the JSON error response") return "", "", errors.New("Could not parse the JSON error response")
} }

View File

@ -28,7 +28,7 @@ func PollEntity(repositoryName string, connectionDetails ConnectionDetailsHTTP,
return status, err return status, err
} }
status = pullEntity.Status status = pullEntity.Status
log.Entry().WithField("StatusCode", responseStatus).Info("Pull Status: " + pullEntity.StatusDescription) log.Entry().WithField("StatusCode", responseStatus).Info("Status: " + pullEntity.StatusDescription)
if pullEntity.Status != "R" { if pullEntity.Status != "R" {
printTransportLogs := true printTransportLogs := true
if serviceContainsNewLogEntities(connectionDetails, client) { if serviceContainsNewLogEntities(connectionDetails, client) {
@ -233,13 +233,13 @@ func GetStatus(failureMessage string, connectionDetails ConnectionDetailsHTTP, c
var abapResp map[string]*json.RawMessage var abapResp map[string]*json.RawMessage
bodyText, _ := ioutil.ReadAll(resp.Body) bodyText, _ := ioutil.ReadAll(resp.Body)
json.Unmarshal(bodyText, &abapResp) marshallError := json.Unmarshal(bodyText, &abapResp)
if err != nil { if marshallError != nil {
return body, resp.Status, errors.Wrap(err, "Could not read response") return body, status, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
} }
json.Unmarshal(*abapResp["d"], &body) marshallError = json.Unmarshal(*abapResp["d"], &body)
if err != nil { if marshallError != nil {
return body, resp.Status, errors.Wrap(err, "Could not read response") return body, status, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
} }
if reflect.DeepEqual(PullEntity{}, body) { if reflect.DeepEqual(PullEntity{}, body) {

View File

@ -2,7 +2,7 @@ metadata:
name: abapEnvironmentCloneGitRepo name: abapEnvironmentCloneGitRepo
description: Clones a git repository to a SAP BTP ABAP Environment system description: Clones a git repository to a SAP BTP ABAP Environment system
longDescription: | longDescription: |
Clones a git repository (Software Component) to a SAP BTP ABAP Environment system. Clones a git repository (Software Component) to a SAP BTP ABAP Environment system. If the repository is already cloned, the step will checkout the configured branch and pull the specified commit, instead.
Please provide either of the following options: Please provide either of the following options:
* The host and credentials the BTP ABAP Environment system itself. The credentials must be configured for the Communication Scenario [SAP_COM_0510](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/b04a9ae412894725a2fc539bfb1ca055.html). * The host and credentials the BTP ABAP Environment system itself. The credentials must be configured for the Communication Scenario [SAP_COM_0510](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/b04a9ae412894725a2fc539bfb1ca055.html).