From 6ff4143b08fa12f8c5bcac7ad3f9bd4889bed0a4 Mon Sep 17 00:00:00 2001 From: Daniel Mieg <56156797+DanielMieg@users.noreply.github.com> Date: Thu, 30 Jun 2022 10:43:33 +0200 Subject: [PATCH] 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 --- cmd/abapEnvironmentCheckoutBranch.go | 10 +- cmd/abapEnvironmentCloneGitRepo.go | 117 ++++++-- cmd/abapEnvironmentCloneGitRepo_generated.go | 2 +- cmd/abapEnvironmentCloneGitRepo_test.go | 268 +++++++++++++++++- pkg/abaputils/abaputils.go | 17 +- pkg/abaputils/manageGitRepositoryUtils.go | 14 +- .../metadata/abapEnvironmentCloneGitRepo.yaml | 2 +- 7 files changed, 383 insertions(+), 47 deletions(-) diff --git a/cmd/abapEnvironmentCheckoutBranch.go b/cmd/abapEnvironmentCheckoutBranch.go index 0155ffef1..4d0402e64 100644 --- a/cmd/abapEnvironmentCheckoutBranch.go +++ b/cmd/abapEnvironmentCheckoutBranch.go @@ -74,7 +74,7 @@ func runAbapEnvironmentCheckoutBranch(options *abapEnvironmentCheckoutBranchOpti if err != nil { 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") return nil } @@ -189,15 +189,15 @@ func handleCheckout(repo abaputils.Repository, checkoutConnectionDetails abaputi func startCheckoutLogs(branchName string, repositoryName string) { 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("--------------------------------") + log.Entry().Infof("-------------------------") } 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().Info("--------------------------------") + log.Entry().Infof("-------------------------") } func convertCheckoutConfig(config *abapEnvironmentCheckoutBranchOptions) abaputils.AbapEnvironmentOptions { diff --git a/cmd/abapEnvironmentCloneGitRepo.go b/cmd/abapEnvironmentCloneGitRepo.go index 0b881dffc..571174671 100644 --- a/cmd/abapEnvironmentCloneGitRepo.go +++ b/cmd/abapEnvironmentCloneGitRepo.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/http" "net/http/cookiejar" "reflect" "time" @@ -59,6 +60,11 @@ func runAbapEnvironmentCloneGitRepo(config *abapEnvironmentCloneGitRepoOptions, 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) if errGetRepos != nil { return fmt.Errorf("Something failed during the clone: %w", errGetRepos) @@ -75,28 +81,40 @@ func runAbapEnvironmentCloneGitRepo(config *abapEnvironmentCloneGitRepoOptions, log.Entry().Info("-------------------------") // 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 { return errors.Wrapf(errorTriggerClone, errorString) } - // Polling the status of the repository import on the ABAP Environment system - status, errorPollEntity := abaputils.PollEntity(repo.Name, uriConnectionDetails, client, com.GetPollIntervall()) - if errorPollEntity != nil { - return errors.Wrapf(errorPollEntity, errorString) + if !didCheckoutPullInstead { + // Polling the status of the repository import on the ABAP Environment system + // If the repository had been cloned already, as checkout/pull has been done - polling the status is not necessary anymore + 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("All repositories were cloned successfully") 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 cloneConnectionDetails.XCsrfToken = "fetch" @@ -107,7 +125,7 @@ func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.Co resp, err := abaputils.GetHTTPResponse("HEAD", cloneConnectionDetails, nil, client) if err != nil { err = abaputils.HandleHTTPError(resp, err, "Authentication on the ABAP system failed", cloneConnectionDetails) - return uriConnectionDetails, err + return uriConnectionDetails, err, false } defer resp.Body.Close() @@ -117,14 +135,14 @@ func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.Co // Trigger the Clone of a Repository 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()) resp, err = abaputils.GetHTTPResponse("POST", cloneConnectionDetails, jsonBody, client) if err != nil { - err = abaputils.HandleHTTPError(resp, err, "Could not clone the "+repo.GetCloneLogString(), uriConnectionDetails) - return uriConnectionDetails, err + err, alreadyCloned := handleCloneError(resp, err, cloneConnectionDetails, client, repo) + return uriConnectionDetails, err, alreadyCloned } 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") @@ -134,20 +152,83 @@ func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.Co var abapResp map[string]*json.RawMessage bodyText, errRead := ioutil.ReadAll(resp.Body) if errRead != nil { - return uriConnectionDetails, err + return uriConnectionDetails, err, false } json.Unmarshal(bodyText, &abapResp) json.Unmarshal(*abapResp["d"], &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") 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 // 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 + "')" - 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 { diff --git a/cmd/abapEnvironmentCloneGitRepo_generated.go b/cmd/abapEnvironmentCloneGitRepo_generated.go index a80f578d0..28851089b 100644 --- a/cmd/abapEnvironmentCloneGitRepo_generated.go +++ b/cmd/abapEnvironmentCloneGitRepo_generated.go @@ -43,7 +43,7 @@ func AbapEnvironmentCloneGitRepoCommand() *cobra.Command { var createAbapEnvironmentCloneGitRepoCmd = &cobra.Command{ Use: STEP_NAME, 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: * 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). diff --git a/cmd/abapEnvironmentCloneGitRepo_test.go b/cmd/abapEnvironmentCloneGitRepo_test.go index 3836794ba..cb64bd882 100644 --- a/cmd/abapEnvironmentCloneGitRepo_test.go +++ b/cmd/abapEnvironmentCloneGitRepo_test.go @@ -1,13 +1,16 @@ package cmd import ( + "bytes" "encoding/json" "fmt" "io/ioutil" + "net/http" "os" "testing" "github.com/SAP/jenkins-library/pkg/abaputils" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -31,7 +34,7 @@ func init() { } 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{} defer autils.Cleanup() autils.ReturnedConnectionDetailsHTTP.Password = "password" @@ -73,8 +76,6 @@ repositories: CfServiceKeyName: "testServiceKey", Username: "testUser", Password: "testPassword", - RepositoryName: "testRepo1", - BranchName: "testBranch1", Repositories: "filename.yaml", } @@ -95,13 +96,44 @@ repositories: `{"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 + `}`, logResultSuccess, `{"d" : { "EntitySets" : [ "LogOverviews" ] } }`, `{"d" : { "status" : "S" } }`, `{"d" : { "status" : "R" } }`, `{"d" : { "status" : "R" } }`, - `{"d" : { "status" : "R" } }`, }, Token: "myToken", StatusCode: 200, @@ -299,8 +331,6 @@ repositories: CfServiceKeyName: "testServiceKey", Username: "testUser", Password: "testPassword", - RepositoryName: "testRepo1", - BranchName: "testBranch1", 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") + } + }) } diff --git a/pkg/abaputils/abaputils.go b/pkg/abaputils/abaputils.go index 3dc37487e..057888119 100644 --- a/pkg/abaputils/abaputils.go +++ b/pkg/abaputils/abaputils.go @@ -176,40 +176,39 @@ func HandleHTTPError(resp *http.Response, err error, message string, connectionD log.Entry().WithField("StatusCode", resp.Status).Error(message) - errorDetails, parsingError := getErrorDetailsFromResponse(resp) + errorText, errorCode, parsingError := GetErrorDetailsFromResponse(resp) if parsingError != nil { return err } - abapError := errors.New(errorDetails) + abapError := errors.New(fmt.Sprintf("%s - %s", errorCode, errorText)) err = errors.Wrap(abapError, err.Error()) } 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 var abapErrorResponse AbapError bodyText, readError := ioutil.ReadAll(resp.Body) if readError != nil { - return errorString, readError + return "", "", readError } var abapResp map[string]*json.RawMessage errUnmarshal := json.Unmarshal(bodyText, &abapResp) if errUnmarshal != nil { - return errorString, errUnmarshal + return "", "", errUnmarshal } if _, ok := abapResp["error"]; ok { json.Unmarshal(*abapResp["error"], &abapErrorResponse) if (AbapError{}) != abapErrorResponse { - log.Entry().WithField("ErrorCode", abapErrorResponse.Code).Error(abapErrorResponse.Message.Value) - errorString = fmt.Sprintf("%s - %s", abapErrorResponse.Code, abapErrorResponse.Message.Value) - return errorString, nil + log.Entry().WithField("ErrorCode", abapErrorResponse.Code).Debug(abapErrorResponse.Message.Value) + return abapErrorResponse.Message.Value, abapErrorResponse.Code, nil } } - return errorString, errors.New("Could not parse the JSON error response") + return "", "", errors.New("Could not parse the JSON error response") } diff --git a/pkg/abaputils/manageGitRepositoryUtils.go b/pkg/abaputils/manageGitRepositoryUtils.go index a5fcad5aa..eb2a91675 100644 --- a/pkg/abaputils/manageGitRepositoryUtils.go +++ b/pkg/abaputils/manageGitRepositoryUtils.go @@ -28,7 +28,7 @@ func PollEntity(repositoryName string, connectionDetails ConnectionDetailsHTTP, return status, err } 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" { printTransportLogs := true if serviceContainsNewLogEntities(connectionDetails, client) { @@ -233,13 +233,13 @@ func GetStatus(failureMessage string, connectionDetails ConnectionDetailsHTTP, c var abapResp map[string]*json.RawMessage bodyText, _ := ioutil.ReadAll(resp.Body) - json.Unmarshal(bodyText, &abapResp) - if err != nil { - return body, resp.Status, errors.Wrap(err, "Could not read response") + marshallError := json.Unmarshal(bodyText, &abapResp) + if marshallError != nil { + return body, status, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system") } - json.Unmarshal(*abapResp["d"], &body) - if err != nil { - return body, resp.Status, errors.Wrap(err, "Could not read response") + marshallError = json.Unmarshal(*abapResp["d"], &body) + if marshallError != nil { + return body, status, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system") } if reflect.DeepEqual(PullEntity{}, body) { diff --git a/resources/metadata/abapEnvironmentCloneGitRepo.yaml b/resources/metadata/abapEnvironmentCloneGitRepo.yaml index 533c32412..0bdc4c262 100644 --- a/resources/metadata/abapEnvironmentCloneGitRepo.yaml +++ b/resources/metadata/abapEnvironmentCloneGitRepo.yaml @@ -2,7 +2,7 @@ metadata: name: abapEnvironmentCloneGitRepo description: Clones a git repository to a SAP BTP ABAP Environment system 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: * 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).