Skip to content

Commit

Permalink
Merge branch 'master' into anil/ImagePushToRegistry
Browse files Browse the repository at this point in the history
  • Loading branch information
vstarostin authored Nov 30, 2023
2 parents 2b8a9cd + cce7c0d commit 63b357b
Show file tree
Hide file tree
Showing 41 changed files with 1,820 additions and 1,194 deletions.
117 changes: 26 additions & 91 deletions cmd/abapEnvironmentCheckoutBranch.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package cmd

import (
"encoding/json"
"fmt"
"io"
"net/http/cookiejar"
"reflect"
"time"

Expand All @@ -28,49 +25,39 @@ func abapEnvironmentCheckoutBranch(options abapEnvironmentCheckoutBranchOptions,
Exec: &c,
}

client := piperhttp.Client{}
apiManager := abaputils.SoftwareComponentApiManager{
Client: &piperhttp.Client{},
PollIntervall: 5 * time.Second,
}

// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
err := runAbapEnvironmentCheckoutBranch(&options, &autils, &client)
err := runAbapEnvironmentCheckoutBranch(&options, &autils, &apiManager)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}

func runAbapEnvironmentCheckoutBranch(options *abapEnvironmentCheckoutBranchOptions, com abaputils.Communication, client piperhttp.Sender) (err error) {
func runAbapEnvironmentCheckoutBranch(options *abapEnvironmentCheckoutBranchOptions, com abaputils.Communication, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {

// Mapping for options
subOptions := convertCheckoutConfig(options)

// Determine the host, user and password, either via the input parameters or via a cloud foundry service key
connectionDetails, errorGetInfo := com.GetAbapCommunicationArrangementInfo(subOptions, "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/")
connectionDetails, errorGetInfo := com.GetAbapCommunicationArrangementInfo(subOptions, "")
if errorGetInfo != nil {
log.Entry().WithError(errorGetInfo).Fatal("Parameters for the ABAP Connection not available")
}

// Configuring the HTTP Client and CookieJar
cookieJar, errorCookieJar := cookiejar.New(nil)
if errorCookieJar != nil {
return errors.Wrap(errorCookieJar, "Could not create a Cookie Jar")
}
clientOptions := piperhttp.ClientOptions{
MaxRequestDuration: 180 * time.Second,
CookieJar: cookieJar,
Username: connectionDetails.User,
Password: connectionDetails.Password,
}
client.SetOptions(clientOptions)
pollIntervall := com.GetPollIntervall()

repositories := []abaputils.Repository{}
err = checkCheckoutBranchRepositoryConfiguration(*options)

if err == nil {
repositories, err = abaputils.GetRepositories(&abaputils.RepositoriesConfig{BranchName: options.BranchName, RepositoryName: options.RepositoryName, Repositories: options.Repositories}, true)
if err != nil {
return errors.Wrap(err, "Configuration is not consistent")
}
if err == nil {
err = checkoutBranches(repositories, connectionDetails, client, pollIntervall)
repositories, err = abaputils.GetRepositories(&abaputils.RepositoriesConfig{BranchName: options.BranchName, RepositoryName: options.RepositoryName, Repositories: options.Repositories}, true)
if err != nil {
return errors.Wrap(err, "Could not read repositories")
}
err = checkoutBranches(repositories, connectionDetails, apiManager)
if err != nil {
return fmt.Errorf("Something failed during the checkout: %w", err)
}
Expand All @@ -79,78 +66,20 @@ func runAbapEnvironmentCheckoutBranch(options *abapEnvironmentCheckoutBranchOpti
return nil
}

func checkoutBranches(repositories []abaputils.Repository, checkoutConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, pollIntervall time.Duration) (err error) {
func checkoutBranches(repositories []abaputils.Repository, checkoutConnectionDetails abaputils.ConnectionDetailsHTTP, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {
log.Entry().Infof("Start switching %v branches", len(repositories))
for _, repo := range repositories {
err = handleCheckout(repo, checkoutConnectionDetails, client, pollIntervall)
err = handleCheckout(repo, checkoutConnectionDetails, apiManager)
if err != nil {
break
}
}
return err
}

func triggerCheckout(repositoryName string, branchName string, checkoutConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (abaputils.ConnectionDetailsHTTP, error) {
uriConnectionDetails := checkoutConnectionDetails
uriConnectionDetails.URL = ""
checkoutConnectionDetails.XCsrfToken = "fetch"

if repositoryName == "" || branchName == "" {
return uriConnectionDetails, 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"))
}

// Loging into the ABAP System - getting the x-csrf-token and cookies
resp, err := abaputils.GetHTTPResponse("HEAD", checkoutConnectionDetails, nil, client)
if err != nil {
err = abaputils.HandleHTTPError(resp, err, "Authentication on the ABAP system failed", checkoutConnectionDetails)
return uriConnectionDetails, err
}
defer resp.Body.Close()

log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", checkoutConnectionDetails.URL).Debug("Authentication on the ABAP system was successful")
uriConnectionDetails.XCsrfToken = resp.Header.Get("X-Csrf-Token")
checkoutConnectionDetails.XCsrfToken = uriConnectionDetails.XCsrfToken

// the request looks like: POST/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/checkout_branch?branch_name='newBranch'&sc_name=/DMO/GIT_REPOSITORY'
checkoutConnectionDetails.URL = checkoutConnectionDetails.URL + `/checkout_branch?branch_name='` + branchName + `'&sc_name='` + repositoryName + `'`
jsonBody := []byte(``)

// no JSON body needed
resp, err = abaputils.GetHTTPResponse("POST", checkoutConnectionDetails, jsonBody, client)
if err != nil {
err = abaputils.HandleHTTPError(resp, err, "Could not trigger checkout of branch "+branchName, uriConnectionDetails)
return uriConnectionDetails, err
}
defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.StatusCode).WithField("repositoryName", repositoryName).WithField("branchName", branchName).Debug("Triggered checkout of branch")

// Parse Response
var body abaputils.PullEntity
var abapResp map[string]*json.RawMessage
bodyText, errRead := io.ReadAll(resp.Body)
if errRead != nil {
return uriConnectionDetails, err
}
if err := json.Unmarshal(bodyText, &abapResp); err != nil {
return uriConnectionDetails, err
}
if err := json.Unmarshal(*abapResp["d"], &body); err != nil {
return uriConnectionDetails, err
}

if reflect.DeepEqual(abaputils.PullEntity{}, body) {
log.Entry().WithField("StatusCode", resp.Status).WithField("branchName", branchName).Error("Could not switch to specified branch")
err := errors.New("Request to ABAP System failed")
return uriConnectionDetails, err
}

uriConnectionDetails.URL = body.Metadata.URI
return uriConnectionDetails, nil
}

func checkCheckoutBranchRepositoryConfiguration(options abapEnvironmentCheckoutBranchOptions) error {
if options.Repositories == "" && options.RepositoryName == "" && options.BranchName == "" {
return fmt.Errorf("Checking configuration failed: %w", errors.New("You have not specified any repository or branch configuration to be checked out in the ABAP Environment System. Please make sure that you specified the repositories with their branches that should be checked out either in a dedicated file or via the parameters 'repositoryName' and 'branchName'. For more information please read the User documentation"))
return errors.New("You have not specified any repository or branch configuration to be checked out in the ABAP Environment System. Please make sure that you specified the repositories with their branches that should be checked out either in a dedicated file or via the parameters 'repositoryName' and 'branchName'. For more information please read the user documentation")
}
if options.Repositories != "" && options.RepositoryName != "" && options.BranchName != "" {
log.Entry().Info("It seems like you have specified repositories directly via the configuration parameters 'repositoryName' and 'branchName' as well as in the dedicated repositories configuration file. Please note that in this case both configurations will be handled and checked out.")
Expand All @@ -166,20 +95,26 @@ func checkCheckoutBranchRepositoryConfiguration(options abapEnvironmentCheckoutB
return nil
}

func handleCheckout(repo abaputils.Repository, checkoutConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, pollIntervall time.Duration) (err error) {
func handleCheckout(repo abaputils.Repository, checkoutConnectionDetails abaputils.ConnectionDetailsHTTP, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {

if reflect.DeepEqual(abaputils.Repository{}, repo) {
return fmt.Errorf("Failed to read repository configuration: %w", errors.New("Error in configuration, most likely you have entered empty or wrong configuration values. Please make sure that you have correctly specified the branches in the repositories to be checked out"))
}
startCheckoutLogs(repo.Branch, repo.Name)

uriConnectionDetails, err := triggerCheckout(repo.Name, repo.Branch, checkoutConnectionDetails, client)
api, errGetAPI := apiManager.GetAPI(checkoutConnectionDetails, repo)
if errGetAPI != nil {
return errors.Wrap(errGetAPI, "Could not initialize the connection to the system")
}

err = api.CheckoutBranch()
if err != nil {
return fmt.Errorf("Failed to trigger Checkout: %w", errors.New("Checkout of "+repo.Branch+" for software component "+repo.Name+" failed on the ABAP System"))
}

// Polling the status of the repository import on the ABAP Environment system
status, err := abaputils.PollEntity(repo.Name, uriConnectionDetails, client, pollIntervall)
if err != nil {
status, errorPollEntity := abaputils.PollEntity(api, apiManager.GetPollIntervall())
if errorPollEntity != nil {
return fmt.Errorf("Failed to poll Checkout: %w", errors.New("Status of checkout action on repository"+repo.Name+" failed on the ABAP System"))
}
const abapStatusCheckoutFail = "E"
Expand Down
107 changes: 17 additions & 90 deletions cmd/abapEnvironmentCheckoutBranch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"encoding/json"
"os"
"testing"
"time"

"github.com/SAP/jenkins-library/pkg/abaputils"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -67,11 +67,12 @@ func TestCheckoutBranchStep(t *testing.T) {
StatusCode: 200,
}

err := runAbapEnvironmentCheckoutBranch(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err := runAbapEnvironmentCheckoutBranch(&config, &autils, apiManager)
assert.NoError(t, err, "Did not expect error")
})
t.Run("Run Step Failure - empty config", func(t *testing.T) {
expectedErrorMessage := "Something failed during the checkout: Checking configuration failed: You have not specified any repository or branch configuration to be checked out in the ABAP Environment System. Please make sure that you specified the repositories with their branches that should be checked out either in a dedicated file or via the parameters 'repositoryName' and 'branchName'. For more information please read the User documentation"
expectedErrorMessage := "Configuration is not consistent: You have not specified any repository or branch configuration to be checked out in the ABAP Environment System. Please make sure that you specified the repositories with their branches that should be checked out either in a dedicated file or via the parameters 'repositoryName' and 'branchName'. For more information please read the user documentation"

var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
Expand All @@ -85,7 +86,6 @@ func TestCheckoutBranchStep(t *testing.T) {
logResultError := `{"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{
`{"d" : [] }`,
`{"d" : ` + executionLogStringCheckout + `}`,
logResultError,
`{"d" : { "status" : "E" } }`,
Expand All @@ -96,7 +96,8 @@ func TestCheckoutBranchStep(t *testing.T) {
StatusCode: 200,
}

err := runAbapEnvironmentCheckoutBranch(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err := runAbapEnvironmentCheckoutBranch(&config, &autils, apiManager)
assert.EqualError(t, err, expectedErrorMessage)
})
t.Run("Run Step Failure - wrong status", func(t *testing.T) {
Expand Down Expand Up @@ -124,7 +125,6 @@ func TestCheckoutBranchStep(t *testing.T) {
logResultError := `{"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{
`{"d" : [] }`,
`{"d" : ` + executionLogStringCheckout + `}`,
logResultError,
`{"d" : { "status" : "E" } }`,
Expand All @@ -135,7 +135,8 @@ func TestCheckoutBranchStep(t *testing.T) {
StatusCode: 200,
}

err := runAbapEnvironmentCheckoutBranch(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err := runAbapEnvironmentCheckoutBranch(&config, &autils, apiManager)
assert.EqualError(t, err, expectedErrorMessage)
})
t.Run("Success case: checkout Branches from file config", func(t *testing.T) {
Expand Down Expand Up @@ -183,11 +184,12 @@ repositories:
Password: "testPassword",
Repositories: "repositoriesTest.yml",
}
err = runAbapEnvironmentCheckoutBranch(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentCheckoutBranch(&config, &autils, apiManager)
assert.NoError(t, err)
})
t.Run("Failure case: checkout Branches from empty file config", func(t *testing.T) {
expectedErrorMessage := "Something failed during the checkout: Error in config file repositoriesTest.yml, AddonDescriptor doesn't contain any repositories"
expectedErrorMessage := "Could not read repositories: Error in config file repositoriesTest.yml, AddonDescriptor doesn't contain any repositories"

var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
Expand Down Expand Up @@ -226,11 +228,12 @@ repositories:
Password: "testPassword",
Repositories: "repositoriesTest.yml",
}
err = runAbapEnvironmentCheckoutBranch(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentCheckoutBranch(&config, &autils, apiManager)
assert.EqualError(t, err, expectedErrorMessage)
})
t.Run("Failure case: checkout Branches from wrong file config", func(t *testing.T) {
expectedErrorMessage := "Something failed during the checkout: Could not unmarshal repositoriesTest.yml"
expectedErrorMessage := "Could not read repositories: Could not unmarshal repositoriesTest.yml"

var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
Expand Down Expand Up @@ -274,88 +277,12 @@ repositories:
Password: "testPassword",
Repositories: "repositoriesTest.yml",
}
err = runAbapEnvironmentCheckoutBranch(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentCheckoutBranch(&config, &autils, apiManager)
assert.EqualError(t, err, expectedErrorMessage)
})
}

func TestTriggerCheckout(t *testing.T) {
t.Run("Test trigger checkout: success case", func(t *testing.T) {

// given
receivedURI := "example.com/Branches"
uriExpected := receivedURI
tokenExpected := "myToken"

client := &abaputils.ClientMock{
Body: `{"d" : { "__metadata" : { "uri" : "` + receivedURI + `" } } }`,
Token: tokenExpected,
StatusCode: 200,
}
config := abapEnvironmentCheckoutBranchOptions{
CfAPIEndpoint: "https://api.endpoint.com",
CfOrg: "testOrg",
CfSpace: "testSpace",
CfServiceInstance: "testInstance",
CfServiceKeyName: "testServiceKey",
Username: "testUser",
Password: "testPassword",
RepositoryName: "testRepo1",
BranchName: "feature-unit-test",
}
con := abaputils.ConnectionDetailsHTTP{
User: "MY_USER",
Password: "MY_PW",
URL: "https://api.endpoint.com/Branches",
}
// when
entityConnection, err := triggerCheckout(config.RepositoryName, config.BranchName, con, client)

// then
assert.NoError(t, err)
assert.Equal(t, uriExpected, entityConnection.URL)
assert.Equal(t, tokenExpected, entityConnection.XCsrfToken)
})

t.Run("Test trigger checkout: ABAP Error case", func(t *testing.T) {

// given
errorMessage := "ABAP Error Message"
errorCode := "ERROR/001"
HTTPErrorMessage := "HTTP Error Message"
combinedErrorMessage := "HTTP Error Message: ERROR/001 - ABAP Error Message"

client := &abaputils.ClientMock{
Body: `{"error" : { "code" : "` + errorCode + `", "message" : { "lang" : "en", "value" : "` + errorMessage + `" } } }`,
Token: "myToken",
StatusCode: 400,
Error: errors.New(HTTPErrorMessage),
}
config := abapEnvironmentCheckoutBranchOptions{
CfAPIEndpoint: "https://api.endpoint.com",
CfOrg: "testOrg",
CfSpace: "testSpace",
CfServiceInstance: "testInstance",
CfServiceKeyName: "testServiceKey",
Username: "testUser",
Password: "testPassword",
RepositoryName: "testRepo1",
BranchName: "feature-unit-test",
}
con := abaputils.ConnectionDetailsHTTP{
User: "MY_USER",
Password: "MY_PW",
URL: "https://api.endpoint.com/Branches",
}

// when
_, err := triggerCheckout(config.RepositoryName, config.BranchName, con, client)

// then
assert.Equal(t, combinedErrorMessage, err.Error(), "Different error message expected")
})
}

func TestCheckoutConfigChecker(t *testing.T) {
t.Run("Success case: check config", func(t *testing.T) {
config := abapEnvironmentCheckoutBranchOptions{
Expand All @@ -374,7 +301,7 @@ func TestCheckoutConfigChecker(t *testing.T) {
assert.NoError(t, err)
})
t.Run("Failure case: check empty config", func(t *testing.T) {
expectedErrorMessage := "Checking configuration failed: You have not specified any repository or branch configuration to be checked out in the ABAP Environment System. Please make sure that you specified the repositories with their branches that should be checked out either in a dedicated file or via the parameters 'repositoryName' and 'branchName'. For more information please read the User documentation"
expectedErrorMessage := "You have not specified any repository or branch configuration to be checked out in the ABAP Environment System. Please make sure that you specified the repositories with their branches that should be checked out either in a dedicated file or via the parameters 'repositoryName' and 'branchName'. For more information please read the user documentation"

config := abapEnvironmentCheckoutBranchOptions{}
err := checkCheckoutBranchRepositoryConfiguration(config)
Expand Down
Loading

0 comments on commit 63b357b

Please sign in to comment.