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

[ABAP] Refactor steps to allow API migration (#4687)

* Initial API Manager

* Intermediate part

* Intermediate step

* Fix utils tests

* Adapt pull

* Migrate Checkout

* Refactor createTags

* Refactoring

* Setup tests for SAP_COM_0510

* Add tests

* Refactor parsing

* Add retry to clone

* refactor

* Refactor and tests

* Fix function call

* Adapt create tag tests

* Adapt tests

* Add tests

* Fix tests

* Fix test

* Fix client mock

* Add unit test comments

* Add missing parameters

* Branch not mandatory for clone

* Improve switch branch trigger

---------

Co-authored-by: tiloKo <70266685+tiloKo@users.noreply.github.com>
This commit is contained in:
Daniel Mieg 2023-11-28 13:26:31 +01:00 committed by GitHub
parent 17de9ed34c
commit 0a738e882c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1485 additions and 1070 deletions

View File

@ -1,10 +1,7 @@
package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http/cookiejar"
"reflect"
"time"
@ -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)
}
@ -79,10 +66,10 @@ 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
}
@ -90,67 +77,9 @@ func checkoutBranches(repositories []abaputils.Repository, checkoutConnectionDet
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.")
@ -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"

View File

@ -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"
)
@ -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()
@ -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" } }`,
@ -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) {
@ -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" } }`,
@ -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) {
@ -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()
@ -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()
@ -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{
@ -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)

View File

@ -1,12 +1,6 @@
package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"reflect"
"time"
"github.com/SAP/jenkins-library/pkg/abaputils"
@ -28,81 +22,138 @@ func abapEnvironmentCloneGitRepo(config abapEnvironmentCloneGitRepoOptions, _ *t
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 := runAbapEnvironmentCloneGitRepo(&config, &autils, &client)
err := runAbapEnvironmentCloneGitRepo(&config, &autils, &apiManager)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runAbapEnvironmentCloneGitRepo(config *abapEnvironmentCloneGitRepoOptions, com abaputils.Communication, client piperhttp.Sender) error {
func runAbapEnvironmentCloneGitRepo(config *abapEnvironmentCloneGitRepoOptions, com abaputils.Communication, apiManager abaputils.SoftwareComponentApiManagerInterface) error {
// Mapping for options
subOptions := convertCloneConfig(config)
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}, false)
if errGetRepos != nil {
return errors.Wrap(errGetRepos, "Could not read repositories")
}
// Determine the host, user and password, either via the input parameters or via a cloud foundry service key
connectionDetails, errorGetInfo := com.GetAbapCommunicationArrangementInfo(subOptions, "")
if errorGetInfo != nil {
return errors.Wrap(errorGetInfo, "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")
}
client.SetOptions(piperhttp.ClientOptions{
MaxRequestDuration: 180 * time.Second,
CookieJar: cookieJar,
Username: connectionDetails.User,
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)
}
log.Entry().Infof("Start cloning %v repositories", len(repositories))
for _, repo := range repositories {
logString := repo.GetCloneLogString()
errorString := "Clone of " + logString + " failed on the ABAP system"
abaputils.AddDefaultDashedLine()
log.Entry().Info("Start cloning " + logString)
abaputils.AddDefaultDashedLine()
// Triggering the Clone of the repository into the ABAP Environment system
uriConnectionDetails, errorTriggerClone, didCheckoutPullInstead := triggerClone(repo, connectionDetails, client)
if errorTriggerClone != nil {
return errors.Wrapf(errorTriggerClone, 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")
cloneError := cloneSingleRepo(apiManager, connectionDetails, repo, config, com)
if cloneError != nil {
return cloneError
}
}
abaputils.AddDefaultDashedLine()
abaputils.AddDefaultDashedLine(1)
log.Entry().Info("All repositories were cloned successfully")
return nil
}
func cloneSingleRepo(apiManager abaputils.SoftwareComponentApiManagerInterface, connectionDetails abaputils.ConnectionDetailsHTTP, repo abaputils.Repository, config *abapEnvironmentCloneGitRepoOptions, com abaputils.Communication) error {
// New API instance for each request
// Triggering the Clone of the repository into the ABAP Environment system
// 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
api, errGetAPI := apiManager.GetAPI(connectionDetails, repo)
if errGetAPI != nil {
return errors.Wrap(errGetAPI, "Could not initialize the connection to the system")
}
logString := repo.GetCloneLogString()
errorString := "Clone of " + logString + " failed on the ABAP system"
abaputils.AddDefaultDashedLine(1)
log.Entry().Info("Start cloning " + logString)
abaputils.AddDefaultDashedLine(1)
alreadyCloned, activeBranch, errCheckCloned := api.GetRepository()
if errCheckCloned != nil {
return errors.Wrapf(errCheckCloned, errorString)
}
if !alreadyCloned {
errClone := api.Clone()
if errClone != nil {
return errors.Wrapf(errClone, errorString)
}
status, errorPollEntity := abaputils.PollEntity(api, apiManager.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")
} else {
abaputils.AddDefaultDashedLine(2)
log.Entry().Infof("%s", "The repository / software component has already been cloned on the ABAP Environment system ")
log.Entry().Infof("%s", "If required, a `checkout branch`, and a `pull` will be performed instead")
abaputils.AddDefaultDashedLine(2)
var returnedError error
if repo.Branch != "" && !(activeBranch == repo.Branch) {
returnedError = runAbapEnvironmentCheckoutBranch(getCheckoutOptions(config, repo), com, apiManager)
abaputils.AddDefaultDashedLine(2)
if returnedError != nil {
return returnedError
}
}
returnedError = runAbapEnvironmentPullGitRepo(getPullOptions(config, repo), com, apiManager)
return returnedError
}
return nil
}
func getCheckoutOptions(config *abapEnvironmentCloneGitRepoOptions, repo abaputils.Repository) *abapEnvironmentCheckoutBranchOptions {
checkoutOptions := abapEnvironmentCheckoutBranchOptions{
Username: config.Username,
Password: config.Password,
Host: config.Host,
RepositoryName: repo.Name,
BranchName: repo.Branch,
CfAPIEndpoint: config.CfAPIEndpoint,
CfOrg: config.CfOrg,
CfServiceInstance: config.CfServiceInstance,
CfServiceKeyName: config.CfServiceKeyName,
CfSpace: config.CfSpace,
}
return &checkoutOptions
}
func getPullOptions(config *abapEnvironmentCloneGitRepoOptions, repo abaputils.Repository) *abapEnvironmentPullGitRepoOptions {
pullOptions := abapEnvironmentPullGitRepoOptions{
Username: config.Username,
Password: config.Password,
Host: config.Host,
RepositoryName: repo.Name,
CommitID: repo.CommitID,
CfAPIEndpoint: config.CfAPIEndpoint,
CfOrg: config.CfOrg,
CfServiceInstance: config.CfServiceInstance,
CfServiceKeyName: config.CfServiceKeyName,
CfSpace: config.CfSpace,
}
return &pullOptions
}
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")
@ -113,125 +164,14 @@ func checkConfiguration(config *abapEnvironmentCloneGitRepoOptions) error {
return nil
}
func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (abaputils.ConnectionDetailsHTTP, error, bool) {
func triggerClone(repo abaputils.Repository, api abaputils.SoftwareComponentApiInterface) (error, bool) {
uriConnectionDetails := cloneConnectionDetails
cloneConnectionDetails.XCsrfToken = "fetch"
cloneConnectionDetails.URL = cloneConnectionDetails.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Clones"
// Loging into the ABAP System - getting the x-csrf-token and cookies
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, false
}
defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", cloneConnectionDetails.URL).Debug("Authentication on the ABAP system successful")
uriConnectionDetails.XCsrfToken = resp.Header.Get("X-Csrf-Token")
cloneConnectionDetails.XCsrfToken = uriConnectionDetails.XCsrfToken
// Trigger the Clone of a Repository
if repo.Name == "" {
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, 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")
// Parse Response
var body abaputils.CloneEntity
var abapResp map[string]*json.RawMessage
bodyText, errRead := io.ReadAll(resp.Body)
if errRead != nil {
return uriConnectionDetails, err, false
}
if err := json.Unmarshal(bodyText, &abapResp); err != nil {
return uriConnectionDetails, err, false
}
if err := json.Unmarshal(*abapResp["d"], &body); err != nil {
return uriConnectionDetails, err, false
}
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, false
}
//cloneConnectionDetails.URL = cloneConnectionDetails.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Clones"
// 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, 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
abaputils.AddDefaultDashedLine()
abaputils.AddDefaultDashedLine()
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")
abaputils.AddDefaultDashedLine()
abaputils.AddDefaultDashedLine()
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
}
abaputils.AddDefaultDashedLine()
abaputils.AddDefaultDashedLine()
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
//uriConnectionDetails.URL = uriConnectionDetails.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull(uuid=guid'" + body.UUID + "')"
return nil, false
}
func convertCloneConfig(config *abapEnvironmentCloneGitRepoOptions) abaputils.AbapEnvironmentOptions {

View File

@ -4,19 +4,17 @@
package cmd
import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
"testing"
"time"
"github.com/SAP/jenkins-library/pkg/abaputils"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
var executionLogStringClone string
var apiManager abaputils.SoftwareComponentApiManagerInterface
func init() {
executionLog := abaputils.LogProtocolResults{
@ -29,9 +27,11 @@ func init() {
Timestamp: "/Date(1644332299000+0000)/",
},
},
Count: "1",
}
executionLogResponse, _ := json.Marshal(executionLog)
executionLogStringClone = string(executionLogResponse)
}
func TestCloneStep(t *testing.T) {
@ -80,6 +80,13 @@ repositories:
logResultSuccess := `{"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" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "sc_name" : "/DMO/REPO_B", "avail_on_instance" : false, "active_branch": "branchB" } }`,
`{"d" : [] }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
@ -87,18 +94,14 @@ repositories:
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "sc_name" : "/DMO/REPO_A", "avail_on_instance" : true, "active_branch": "branchA" } }`,
`{"d" : [] }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
},
Token: "myToken",
}
err = runAbapEnvironmentCloneGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err = runAbapEnvironmentCloneGitRepo(&config, &autils, apiManager)
assert.NoError(t, err, "Did not expect error")
assert.Equal(t, 0, len(client.BodyList), "Not all requests were done")
})
@ -120,24 +123,25 @@ repositories:
Username: "testUser",
Password: "testPassword",
RepositoryName: "testRepo1",
BranchName: "testBranch1",
}
logResultSuccess := `{"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 } ] } } ] } } }`
logResultSuccess := `{"d": { "sc_name": "testRepo1", "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" : [] }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "sc_name" : "testRepo1", "avail_on_instance" : false, "active_branch": "testBranch1" } }`,
`{"d" : [] }`,
},
Token: "myToken",
StatusCode: 200,
}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, apiManager)
assert.NoError(t, err, "Did not expect error")
assert.Equal(t, 0, len(client.BodyList), "Not all requests were done")
})
@ -166,12 +170,15 @@ repositories:
BodyList: []string{
`{"d" : {} }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "sc_name" : "testRepo1", "avail_on_instance" : true, "active_branch": "testBranch1" } }`,
`{"d" : [] }`,
},
Token: "myToken",
StatusCode: 200,
}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Clone of repository / software component 'testRepo1', branch 'testBranch1' failed on the ABAP system: Request to ABAP System not successful", err.Error(), "Expected different error message")
}
@ -232,10 +239,10 @@ repositories:
Token: "myToken",
StatusCode: 200,
}
err = runAbapEnvironmentCloneGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err = runAbapEnvironmentCloneGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Clone of repository / software component '/DMO/REPO_A', branch 'branchA', commit 'ABCD1234' failed on the ABAP System", err.Error(), "Expected different error message")
assert.Equal(t, "Clone of repository / software component '/DMO/REPO_A', branch 'branchA', commit 'ABCD1234' failed on the ABAP system: Request to ABAP System not successful", err.Error(), "Expected different error message")
}
})
@ -268,8 +275,8 @@ repositories:
Token: "myToken",
StatusCode: 200,
}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Clone of repository / software component 'testRepo1', branch 'testBranch1' failed on the ABAP system: Request to ABAP System not successful", err.Error(), "Expected different error message")
}
@ -303,8 +310,8 @@ repositories:
Token: "myToken",
StatusCode: 200,
}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Clone of repository / software component 'testRepo1', branch 'testBranch1' failed on the ABAP system: Request to ABAP System not successful", err.Error(), "Expected different error message")
}
@ -337,10 +344,10 @@ repositories:
Token: "myToken",
StatusCode: 200,
}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Something failed during the clone: Could not find filename.yaml", err.Error(), "Expected different error message")
assert.Equal(t, "Could not read repositories: Could not find filename.yaml", err.Error(), "Expected different error message")
}
})
@ -378,8 +385,8 @@ repositories:
Token: "myToken",
StatusCode: 200,
}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err := runAbapEnvironmentCloneGitRepo(&config, &autils, apiManager)
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")
}
@ -387,7 +394,7 @@ repositories:
}
func TestALreadyCloned(t *testing.T) {
t.Run("Already Cloned", func(t *testing.T) {
t.Run("Already cloned, switch branch and pull instead", func(t *testing.T) {
var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
@ -396,46 +403,50 @@ func TestALreadyCloned(t *testing.T) {
autils.ReturnedConnectionDetailsHTTP.URL = "https://example.com"
autils.ReturnedConnectionDetailsHTTP.Host = "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",
}
logResultSuccess := `{"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" : }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : }`,
`{"d" : [] }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "sc_name" : "testRepo1", "avail_on_inst" : true, "active_branch": "testBranch1" } }`,
`{"d" : [] }`,
},
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: io.NopCloser(bytes.NewReader(body)),
}
repo := abaputils.Repository{
Name: "Test",
Branch: "Branch",
Name: "testRepo1",
Branch: "inactie_branch",
CommitID: "abcd1234",
}
err := errors.New("Custom Error")
err, _ = handleCloneError(&resp, err, autils.ReturnedConnectionDetailsHTTP, client, repo)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err := cloneSingleRepo(apiManager, autils.ReturnedConnectionDetailsHTTP, repo, &config, &autils)
assert.NoError(t, err, "Did not expect error")
})
t.Run("Already Cloned, Pull fails", func(t *testing.T) {
t.Run("Already cloned, branch is already checked out, pull instead", func(t *testing.T) {
var autils = abaputils.AUtilsMock{}
defer autils.Cleanup()
@ -444,130 +455,41 @@ func TestALreadyCloned(t *testing.T) {
autils.ReturnedConnectionDetailsHTTP.URL = "https://example.com"
autils.ReturnedConnectionDetailsHTTP.Host = "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",
}
logResultSuccess := `{"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" } }`,
`{"d" : { "sc_name" : "testRepo1", "avail_on_inst" : true, "active_branch": "testBranch1" } }`,
`{"d" : [] }`,
},
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: io.NopCloser(bytes.NewReader(body)),
}
repo := abaputils.Repository{
Name: "Test",
Branch: "Branch",
Name: "testRepo1",
Branch: "testBranch1",
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: Request to ABAP System not successful", err.Error(), "Expected different error message")
}
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Nanosecond}
err := cloneSingleRepo(apiManager, autils.ReturnedConnectionDetailsHTTP, repo, &config, &autils)
assert.NoError(t, err, "Did not expect error")
})
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 := `{"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: io.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: io.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

@ -1,10 +1,7 @@
package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http/cookiejar"
"strings"
"time"
@ -16,7 +13,7 @@ import (
"github.com/pkg/errors"
)
func abapEnvironmentCreateTag(config abapEnvironmentCreateTagOptions, telemetryData *telemetry.CustomData) {
func abapEnvironmentCreateTag(config abapEnvironmentCreateTagOptions, _ *telemetry.CustomData) {
c := command.Command{}
@ -27,58 +24,36 @@ func abapEnvironmentCreateTag(config abapEnvironmentCreateTagOptions, telemetryD
Exec: &c,
}
client := piperhttp.Client{}
apiManager := abaputils.SoftwareComponentApiManager{
Client: &piperhttp.Client{},
PollIntervall: 5 * time.Second,
}
if err := runAbapEnvironmentCreateTag(&config, telemetryData, &autils, &client); err != nil {
if err := runAbapEnvironmentCreateTag(&config, &autils, &apiManager); err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runAbapEnvironmentCreateTag(config *abapEnvironmentCreateTagOptions, telemetryData *telemetry.CustomData, com abaputils.Communication, client piperhttp.Sender) error {
func runAbapEnvironmentCreateTag(config *abapEnvironmentCreateTagOptions, com abaputils.Communication, apiManager abaputils.SoftwareComponentApiManagerInterface) error {
connectionDetails, errorGetInfo := com.GetAbapCommunicationArrangementInfo(convertTagConfig(config), "")
if errorGetInfo != nil {
return errors.Wrap(errorGetInfo, "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")
}
client.SetOptions(piperhttp.ClientOptions{
MaxRequestDuration: 180 * time.Second,
CookieJar: cookieJar,
Username: connectionDetails.User,
Password: connectionDetails.Password,
})
backlog, errorPrepare := prepareBacklog(config)
if errorPrepare != nil {
return fmt.Errorf("Something failed during the tag creation: %w", errorPrepare)
}
return createTags(backlog, telemetryData, connectionDetails, client, com)
return createTags(backlog, connectionDetails, apiManager)
}
func createTags(backlog []CreateTagBacklog, telemetryData *telemetry.CustomData, con abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, com abaputils.Communication) (err error) {
connection := con
connection.XCsrfToken = "fetch"
connection.URL = con.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Tags"
resp, err := abaputils.GetHTTPResponse("HEAD", connection, nil, client)
if err != nil {
return abaputils.HandleHTTPError(resp, err, "Authentication on the ABAP system failed", con)
}
defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", connection.URL).Debug("Authentication on the ABAP system successful")
connection.XCsrfToken = resp.Header.Get("X-Csrf-Token")
func createTags(backlog []abaputils.CreateTagBacklog, con abaputils.ConnectionDetailsHTTP, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {
errorOccurred := false
for _, item := range backlog {
err = createTagsForSingleItem(item, telemetryData, connection, client, com)
err = createTagsForSingleItem(item, con, apiManager)
if err != nil {
errorOccurred = true
}
@ -93,11 +68,11 @@ func createTags(backlog []CreateTagBacklog, telemetryData *telemetry.CustomData,
}
func createTagsForSingleItem(item CreateTagBacklog, telemetryData *telemetry.CustomData, con abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, com abaputils.Communication) (err error) {
func createTagsForSingleItem(item abaputils.CreateTagBacklog, con abaputils.ConnectionDetailsHTTP, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {
errorOccurred := false
for index := range item.tags {
err = createSingleTag(item, index, telemetryData, con, client, com)
for index := range item.Tags {
err = createSingleTag(item, index, con, apiManager)
if err != nil {
errorOccurred = true
}
@ -109,79 +84,38 @@ func createTagsForSingleItem(item CreateTagBacklog, telemetryData *telemetry.Cus
return err
}
func createSingleTag(item CreateTagBacklog, index int, telemetryData *telemetry.CustomData, con abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, com abaputils.Communication) (err error) {
func createSingleTag(item abaputils.CreateTagBacklog, index int, con abaputils.ConnectionDetailsHTTP, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {
requestBodyStruct := CreateTagBody{RepositoryName: item.repositoryName, CommitID: item.commitID, Tag: item.tags[index].tagName, Description: item.tags[index].tagDescription}
requestBodyJson, err := json.Marshal(&requestBodyStruct)
if err != nil {
return err
api, errGetAPI := apiManager.GetAPI(con, abaputils.Repository{Name: item.RepositoryName, CommitID: item.CommitID})
if errGetAPI != nil {
return errors.Wrap(errGetAPI, "Could not initialize the connection to the system")
}
log.Entry().Debugf("Request body: %s", requestBodyJson)
resp, err := abaputils.GetHTTPResponse("POST", con, requestBodyJson, client)
if err != nil {
errorMessage := "Could not create tag " + requestBodyStruct.Tag + " for repository " + requestBodyStruct.RepositoryName + " with commitID " + requestBodyStruct.CommitID
err = abaputils.HandleHTTPError(resp, err, errorMessage, con)
return err
}
defer resp.Body.Close()
// Parse response
var createTagResponse CreateTagResponse
var abapResp map[string]*json.RawMessage
bodyText, _ := io.ReadAll(resp.Body)
if err = json.Unmarshal(bodyText, &abapResp); err != nil {
return err
}
if err = json.Unmarshal(*abapResp["d"], &createTagResponse); err != nil {
return err
createTagError := api.CreateTag(item.Tags[index])
if createTagError != nil {
return errors.Wrapf(err, "Creation of Tag failed on the ABAP system")
}
con.URL = con.Host + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull(guid'" + createTagResponse.UUID + "')"
err = checkStatus(con, client, com)
status, errorPollEntity := abaputils.PollEntity(api, apiManager.GetPollIntervall())
if err == nil {
log.Entry().Info("Created tag " + requestBodyStruct.Tag + " for repository " + requestBodyStruct.RepositoryName + " with commitID " + requestBodyStruct.CommitID)
if errorPollEntity == nil && status == "S" {
log.Entry().Info("Created tag " + item.Tags[index].TagName + " for repository " + item.RepositoryName + " with commitID " + item.CommitID)
} else {
log.Entry().Error("NOT created: Tag " + requestBodyStruct.Tag + " for repository " + requestBodyStruct.RepositoryName + " with commitID " + requestBodyStruct.CommitID)
log.Entry().Error("NOT created: Tag " + item.Tags[index].TagName + " for repository " + item.RepositoryName + " with commitID " + item.CommitID)
err = errors.New("Creation of Tag failed on the ABAP system")
}
return err
}
func checkStatus(con abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, com abaputils.Communication) (err error) {
var status string
pollIntervall := com.GetPollIntervall()
count := 0
for {
count += 1
entity, _, err := abaputils.GetStatus("Could not create Tag", con, client)
if err != nil {
return err
}
status = entity.Status
if status != "R" {
if status == "E" {
err = errors.New("Could not create Tag")
}
return err
}
if count >= 200 {
return errors.New("Could not create Tag (Timeout)")
}
time.Sleep(pollIntervall)
}
}
func prepareBacklog(config *abapEnvironmentCreateTagOptions) (backlog []CreateTagBacklog, err error) {
func prepareBacklog(config *abapEnvironmentCreateTagOptions) (backlog []abaputils.CreateTagBacklog, err error) {
if config.Repositories != "" && config.RepositoryName != "" {
return nil, errors.New("Configuring the parameter repositories and the parameter repositoryName at the same time is not allowed")
}
if config.RepositoryName != "" && config.CommitID != "" {
backlog = append(backlog, CreateTagBacklog{repositoryName: config.RepositoryName, commitID: config.CommitID})
backlog = append(backlog, abaputils.CreateTagBacklog{RepositoryName: config.RepositoryName, CommitID: config.CommitID})
}
if config.Repositories != "" {
@ -190,10 +124,10 @@ func prepareBacklog(config *abapEnvironmentCreateTagOptions) (backlog []CreateTa
return nil, err
}
for _, repo := range descriptor.Repositories {
backlogInstance := CreateTagBacklog{repositoryName: repo.Name, commitID: repo.CommitID}
backlogInstance := abaputils.CreateTagBacklog{RepositoryName: repo.Name, CommitID: repo.CommitID}
if config.GenerateTagForAddonComponentVersion && repo.VersionYAML != "" {
tag := Tag{tagName: "v" + repo.VersionYAML, tagDescription: "Generated by the ABAP Environment Pipeline"}
backlogInstance.tags = append(backlogInstance.tags, tag)
tag := abaputils.Tag{TagName: "v" + repo.VersionYAML, TagDescription: "Generated by the ABAP Environment Pipeline"}
backlogInstance.Tags = append(backlogInstance.Tags, tag)
}
backlog = append(backlog, backlogInstance)
}
@ -212,11 +146,11 @@ func prepareBacklog(config *abapEnvironmentCreateTagOptions) (backlog []CreateTa
return backlog, nil
}
func addTagToList(backlog []CreateTagBacklog, tag string, description string) []CreateTagBacklog {
func addTagToList(backlog []abaputils.CreateTagBacklog, tag string, description string) []abaputils.CreateTagBacklog {
for i, item := range backlog {
tag := Tag{tagName: tag, tagDescription: description}
backlog[i].tags = append(item.tags, tag)
tag := abaputils.Tag{TagName: tag, TagDescription: description}
backlog[i].Tags = append(item.Tags, tag)
}
return backlog
}
@ -235,25 +169,3 @@ func convertTagConfig(config *abapEnvironmentCreateTagOptions) abaputils.AbapEnv
return subOptions
}
type CreateTagBacklog struct {
repositoryName string
commitID string
tags []Tag
}
type Tag struct {
tagName string
tagDescription string
}
type CreateTagBody struct {
RepositoryName string `json:"sc_name"`
CommitID string `json:"commit_id"`
Tag string `json:"tag_name"`
Description string `json:"tag_description"`
}
type CreateTagResponse struct {
UUID string `json:"uuid"`
}

View File

@ -4,8 +4,10 @@
package cmd
import (
"encoding/json"
"os"
"testing"
"time"
"github.com/SAP/jenkins-library/pkg/abaputils"
"github.com/SAP/jenkins-library/pkg/log"
@ -13,6 +15,28 @@ import (
"github.com/stretchr/testify/assert"
)
var executionLogStringCreateTag string
var logResultSuccess string
func init() {
logResultSuccess = `{"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 } ] } } ] } } }`
executionLog := abaputils.LogProtocolResults{
Results: []abaputils.LogProtocol{
{
ProtocolLine: 1,
OverviewIndex: 1,
Type: "LogEntry",
Description: "S",
Timestamp: "/Date(1644332299000+0000)/",
},
},
Count: "1",
}
executionLogResponse, _ := json.Marshal(executionLog)
executionLogStringCreateTag = string(executionLogResponse)
}
func TestRunAbapEnvironmentCreateTag(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
@ -56,10 +80,16 @@ repositories:
}
client := &abaputils.ClientMock{
BodyList: []string{
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "Status" : "S" } }`,
`{"d" : { "uuid" : "abc" } }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "Status" : "S" } }`,
`{"d" : { "uuid" : "abc" } }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "Status" : "S" } }`,
`{"d" : { "uuid" : "abc" } }`,
`{"d" : { "empty" : "body" } }`,
@ -71,13 +101,14 @@ repositories:
_, hook := test.NewNullLogger()
log.RegisterHook(hook)
err = runAbapEnvironmentCreateTag(config, nil, autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentCreateTag(config, autils, apiManager)
assert.NoError(t, err, "Did not expect error")
assert.Equal(t, 3, len(hook.Entries), "Expected a different number of entries")
assert.Equal(t, `Created tag v4.5.6 for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[0].Message, "Expected a different message")
assert.Equal(t, `Created tag -DMO-PRODUCT-1.2.3 for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[1].Message, "Expected a different message")
assert.Equal(t, `Created tag tag for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[2].Message, "Expected a different message")
assert.Equal(t, 22, len(hook.Entries), "Expected a different number of entries")
assert.Equal(t, `Created tag v4.5.6 for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[11].Message, "Expected a different message")
assert.Equal(t, `Created tag -DMO-PRODUCT-1.2.3 for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[16].Message, "Expected a different message")
assert.Equal(t, `Created tag tag for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[21].Message, "Expected a different message")
hook.Reset()
})
@ -122,10 +153,18 @@ repositories:
}
client := &abaputils.ClientMock{
BodyList: []string{
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "Status" : "E" } }`,
`{"d" : { "uuid" : "abc" } }`,
`{"d" : { "empty" : "body" } }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "Status" : "E" } }`,
`{"d" : { "uuid" : "abc" } }`,
`{"d" : { "empty" : "body" } }`,
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "Status" : "E" } }`,
`{"d" : { "uuid" : "abc" } }`,
`{"d" : { "empty" : "body" } }`,
@ -137,14 +176,15 @@ repositories:
_, hook := test.NewNullLogger()
log.RegisterHook(hook)
err = runAbapEnvironmentCreateTag(config, nil, autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentCreateTag(config, autils, apiManager)
assert.Error(t, err, "Did expect error")
assert.Equal(t, 4, len(hook.Entries), "Expected a different number of entries")
assert.Equal(t, `NOT created: Tag v4.5.6 for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[0].Message, "Expected a different message")
assert.Equal(t, `NOT created: Tag -DMO-PRODUCT-1.2.3 for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[1].Message, "Expected a different message")
assert.Equal(t, `NOT created: Tag tag for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[2].Message, "Expected a different message")
assert.Equal(t, `At least one tag has not been created`, hook.AllEntries()[3].Message, "Expected a different message")
assert.Equal(t, 37, len(hook.Entries), "Expected a different number of entries")
assert.Equal(t, `NOT created: Tag v4.5.6 for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[11].Message, "Expected a different message")
assert.Equal(t, `NOT created: Tag -DMO-PRODUCT-1.2.3 for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[23].Message, "Expected a different message")
assert.Equal(t, `NOT created: Tag tag for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[35].Message, "Expected a different message")
assert.Equal(t, `At least one tag has not been created`, hook.AllEntries()[36].Message, "Expected a different message")
hook.Reset()
})
@ -175,6 +215,8 @@ func TestRunAbapEnvironmentCreateTagConfigurations(t *testing.T) {
}
client := &abaputils.ClientMock{
BodyList: []string{
`{"d" : ` + executionLogStringClone + `}`,
logResultSuccess,
`{"d" : { "Status" : "S" } }`,
`{"d" : { "uuid" : "abc" } }`,
`{"d" : { "empty" : "body" } }`,
@ -186,11 +228,12 @@ func TestRunAbapEnvironmentCreateTagConfigurations(t *testing.T) {
_, hook := test.NewNullLogger()
log.RegisterHook(hook)
err := runAbapEnvironmentCreateTag(config, nil, autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err := runAbapEnvironmentCreateTag(config, autils, apiManager)
assert.NoError(t, err, "Did not expect error")
assert.Equal(t, 1, len(hook.Entries), "Expected a different number of entries")
assert.Equal(t, `Created tag tag for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[0].Message, "Expected a different message")
assert.Equal(t, 12, len(hook.Entries), "Expected a different number of entries")
assert.Equal(t, `Created tag tag for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[11].Message, "Expected a different message")
hook.Reset()
})
@ -253,7 +296,8 @@ repositories:
StatusCode: 200,
}
err = runAbapEnvironmentCreateTag(config, nil, autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentCreateTag(config, autils, apiManager)
assert.Error(t, err, "Did expect error")
assert.Equal(t, "Something failed during the tag creation: Configuring the parameter repositories and the parameter repositoryName at the same time is not allowed", err.Error(), "Expected different error message")
@ -315,11 +359,12 @@ repositories:
_, hook := test.NewNullLogger()
log.RegisterHook(hook)
err = runAbapEnvironmentCreateTag(config, nil, autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentCreateTag(config, autils, apiManager)
assert.NoError(t, err, "Did not expect error")
assert.Equal(t, 1, len(hook.Entries), "Expected a different number of entries")
assert.Equal(t, `Created tag tag for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[0].Message, "Expected a different message")
assert.Equal(t, 5, len(hook.Entries), "Expected a different number of entries")
assert.Equal(t, `Created tag tag for repository /DMO/SWC with commitID 1234abcd`, hook.AllEntries()[4].Message, "Expected a different message")
hook.Reset()
})

View File

@ -1,11 +1,7 @@
package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http/cookiejar"
"reflect"
"time"
"github.com/SAP/jenkins-library/pkg/abaputils"
@ -28,38 +24,28 @@ func abapEnvironmentPullGitRepo(options abapEnvironmentPullGitRepoOptions, _ *te
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 := runAbapEnvironmentPullGitRepo(&options, &autils, &client)
err := runAbapEnvironmentPullGitRepo(&options, &autils, &apiManager)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runAbapEnvironmentPullGitRepo(options *abapEnvironmentPullGitRepoOptions, com abaputils.Communication, client piperhttp.Sender) (err error) {
func runAbapEnvironmentPullGitRepo(options *abapEnvironmentPullGitRepoOptions, com abaputils.Communication, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {
subOptions := convertPullConfig(options)
// Determine the host, user and password, either via the input parameters or via a cloud foundry service key
connectionDetails, err := com.GetAbapCommunicationArrangementInfo(subOptions, "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull")
connectionDetails, err := com.GetAbapCommunicationArrangementInfo(subOptions, "")
if err != nil {
return errors.Wrap(err, "Parameters for the ABAP Connection not available")
}
cookieJar, err := cookiejar.New(nil)
if err != nil {
return errors.Wrap(err, "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()
var repositories []abaputils.Repository
err = checkPullRepositoryConfiguration(*options)
if err != nil {
@ -71,15 +57,15 @@ func runAbapEnvironmentPullGitRepo(options *abapEnvironmentPullGitRepoOptions, c
return err
}
err = pullRepositories(repositories, connectionDetails, client, pollIntervall)
err = pullRepositories(repositories, connectionDetails, apiManager)
return err
}
func pullRepositories(repositories []abaputils.Repository, pullConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, pollIntervall time.Duration) (err error) {
func pullRepositories(repositories []abaputils.Repository, pullConnectionDetails abaputils.ConnectionDetailsHTTP, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {
log.Entry().Infof("Start pulling %v repositories", len(repositories))
for _, repo := range repositories {
err = handlePull(repo, pullConnectionDetails, client, pollIntervall)
err = handlePull(repo, pullConnectionDetails, apiManager)
if err != nil {
break
}
@ -90,22 +76,27 @@ func pullRepositories(repositories []abaputils.Repository, pullConnectionDetails
return err
}
func handlePull(repo abaputils.Repository, pullConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, pollIntervall time.Duration) (err error) {
func handlePull(repo abaputils.Repository, con abaputils.ConnectionDetailsHTTP, apiManager abaputils.SoftwareComponentApiManagerInterface) (err error) {
logString := repo.GetPullLogString()
errorString := "Pull of the " + logString + " failed on the ABAP system"
abaputils.AddDefaultDashedLine()
abaputils.AddDefaultDashedLine(1)
log.Entry().Info("Start pulling the " + logString)
abaputils.AddDefaultDashedLine()
abaputils.AddDefaultDashedLine(1)
uriConnectionDetails, err := triggerPull(repo, pullConnectionDetails, client)
api, errGetAPI := apiManager.GetAPI(con, repo)
if errGetAPI != nil {
return errors.Wrap(errGetAPI, "Could not initialize the connection to the system")
}
err = api.Pull()
if err != nil {
return errors.Wrapf(err, errorString)
}
// Polling the status of the repository import on the ABAP Environment system
status, errorPollEntity := abaputils.PollEntity(repo.Name, uriConnectionDetails, client, pollIntervall)
status, errorPollEntity := abaputils.PollEntity(api, apiManager.GetPollIntervall())
if errorPollEntity != nil {
return errors.Wrapf(errorPollEntity, errorString)
}
@ -116,61 +107,6 @@ func handlePull(repo abaputils.Repository, pullConnectionDetails abaputils.Conne
return err
}
func triggerPull(repo abaputils.Repository, pullConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (abaputils.ConnectionDetailsHTTP, error) {
uriConnectionDetails := pullConnectionDetails
uriConnectionDetails.URL = ""
pullConnectionDetails.XCsrfToken = "fetch"
// Loging into the ABAP System - getting the x-csrf-token and cookies
resp, err := abaputils.GetHTTPResponse("HEAD", pullConnectionDetails, nil, client)
if err != nil {
err = abaputils.HandleHTTPError(resp, err, "Authentication on the ABAP system failed", pullConnectionDetails)
return uriConnectionDetails, err
}
defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", pullConnectionDetails.URL).Debug("Authentication on the ABAP system successful")
uriConnectionDetails.XCsrfToken = resp.Header.Get("X-Csrf-Token")
pullConnectionDetails.XCsrfToken = uriConnectionDetails.XCsrfToken
// Trigger the Pull of a Repository
if repo.Name == "" {
return uriConnectionDetails, errors.New("An empty string was passed for the parameter 'repositoryName'")
}
jsonBody := []byte(repo.GetPullRequestBody())
resp, err = abaputils.GetHTTPResponse("POST", pullConnectionDetails, jsonBody, client)
if err != nil {
err = abaputils.HandleHTTPError(resp, err, "Could not pull the "+repo.GetPullLogString(), uriConnectionDetails)
return uriConnectionDetails, err
}
defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", repo.Name).WithField("commitID", repo.CommitID).WithField("Tag", repo.Tag).Debug("Triggered Pull of repository / software component")
// 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("repositoryName", repo.Name).WithField("commitID", repo.CommitID).WithField("Tag", repo.Tag).Error("Could not pull the repository / software component")
err := errors.New("Request to ABAP System not successful")
return uriConnectionDetails, err
}
uriConnectionDetails.URL = body.Metadata.URI
return uriConnectionDetails, nil
}
func checkPullRepositoryConfiguration(options abapEnvironmentPullGitRepoOptions) error {
if (len(options.RepositoryNames) > 0 && options.Repositories != "") || (len(options.RepositoryNames) > 0 && options.RepositoryName != "") || (options.RepositoryName != "" && options.Repositories != "") {
@ -183,7 +119,7 @@ func checkPullRepositoryConfiguration(options abapEnvironmentPullGitRepoOptions)
}
func finishPullLogs() {
abaputils.AddDefaultDashedLine()
abaputils.AddDefaultDashedLine(1)
log.Entry().Info("All repositories were pulled successfully")
}

View File

@ -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"
)
@ -58,7 +58,6 @@ func TestPullStep(t *testing.T) {
logResultSuccess := `{"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" : [] }`,
`{"d" : ` + executionLogStringPull + `}`,
logResultSuccess,
`{"d" : { "status" : "S" } }`,
@ -70,7 +69,8 @@ func TestPullStep(t *testing.T) {
StatusCode: 200,
}
err := runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err := runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
assert.NoError(t, err, "Did not expect error")
assert.Equal(t, 0, len(client.BodyList), "Not all requests were done")
})
@ -95,7 +95,9 @@ func TestPullStep(t *testing.T) {
}
config := abapEnvironmentPullGitRepoOptions{}
err := runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err := runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
assert.Equal(t, expectedErrorMessage, err.Error(), "Different error message expected")
})
@ -145,7 +147,8 @@ repositories:
Password: "testPassword",
Repositories: "repositoriesTest.yml",
}
err = runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
assert.NoError(t, err)
})
@ -200,7 +203,8 @@ repositories:
StatusCode: 200,
}
err = runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Pull of the repository / software component '/DMO/REPO_A', commit 'ABCD1234' failed on the ABAP system", err.Error(), "Expected different error message")
}
@ -258,7 +262,8 @@ repositories:
StatusCode: 200,
}
err = runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Pull of the repository / software component '/DMO/REPO_A', tag 'v-1.0.1-build-0001' failed on the ABAP system", err.Error(), "Expected different error message")
}
@ -297,7 +302,8 @@ repositories:
StatusCode: 200,
}
err := runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err := runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Pull of the repository / software component '/DMO/SWC', commit '123456' failed on the ABAP system", err.Error(), "Expected different error message")
}
@ -335,7 +341,8 @@ repositories:
StatusCode: 200,
}
err := runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err := runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
if assert.Error(t, err, "Expected error") {
assert.Equal(t, "Pull of the repository / software component '/DMO/SWC' failed on the ABAP system", err.Error(), "Expected different error message")
}
@ -381,7 +388,8 @@ repositories:
Password: "testPassword",
Repositories: "repositoriesTest.yml",
}
err = runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
assert.EqualError(t, err, expectedErrorMessage)
})
@ -430,66 +438,12 @@ repositories:
Password: "testPassword",
Repositories: "repositoriesTest.yml",
}
err = runAbapEnvironmentPullGitRepo(&config, &autils, client)
apiManager = &abaputils.SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
err = runAbapEnvironmentPullGitRepo(&config, &autils, apiManager)
assert.EqualError(t, err, expectedErrorMessage)
})
}
func TestTriggerPull(t *testing.T) {
t.Run("Test trigger pull: success case", func(t *testing.T) {
receivedURI := "example.com/Entity"
uriExpected := receivedURI
tokenExpected := "myToken"
client := &abaputils.ClientMock{
Body: `{"d" : { "__metadata" : { "uri" : "` + receivedURI + `" } } }`,
Token: tokenExpected,
StatusCode: 200,
}
repoName := "testRepo1"
testCommit := "9caede7f31028cd52333eb496434275687fefb47"
con := abaputils.ConnectionDetailsHTTP{
User: "MY_USER",
Password: "MY_PW",
URL: "https://api.endpoint.com/Entity/",
}
entityConnection, err := triggerPull(abaputils.Repository{Name: repoName, CommitID: testCommit}, con, client)
assert.Nil(t, err)
assert.Equal(t, uriExpected, entityConnection.URL)
assert.Equal(t, tokenExpected, entityConnection.XCsrfToken)
})
t.Run("Test trigger pull: ABAP Error", func(t *testing.T) {
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),
}
repoName := "testRepo1"
testCommit := "9caede7f31028cd52333eb496434275687fefb47"
con := abaputils.ConnectionDetailsHTTP{
User: "MY_USER",
Password: "MY_PW",
URL: "https://api.endpoint.com/Entity/",
}
_, err := triggerPull(abaputils.Repository{Name: repoName, CommitID: testCommit}, con, client)
assert.Equal(t, combinedErrorMessage, err.Error(), "Different error message expected")
})
}
func TestPullConfigChecker(t *testing.T) {
t.Run("Success case: check config file", func(t *testing.T) {
config := abapEnvironmentPullGitRepoOptions{

View File

@ -199,7 +199,7 @@ func fetchXcsrfTokenFromHead(connectionDetails abaputils.ConnectionDetailsHTTP,
// Loging into the ABAP System - getting the x-csrf-token and cookies
resp, err := abaputils.GetHTTPResponse("HEAD", connectionDetails, nil, client)
if err != nil {
err = abaputils.HandleHTTPError(resp, err, "authentication on the ABAP system failed", connectionDetails)
_, err = abaputils.HandleHTTPError(resp, err, "authentication on the ABAP system failed", connectionDetails)
return connectionDetails.XCsrfToken, errors.Errorf("X-Csrf-Token fetch failed for Service ATC System Configuration: %v", err)
}
defer resp.Body.Close()

View File

@ -306,7 +306,7 @@ func runATC(requestType string, details abaputils.ConnectionDetailsHTTP, body []
resp, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
_ = logResponseBody(resp)
if err != nil || (resp != nil && resp.StatusCode == 400) { // send request does not seem to produce error with StatusCode 400!!!
err = abaputils.HandleHTTPError(resp, err, "triggering ATC run failed with Status: "+resp.Status, details)
_, err = abaputils.HandleHTTPError(resp, err, "triggering ATC run failed with Status: "+resp.Status, details)
log.SetErrorCategory(log.ErrorService)
return resp, errors.Errorf("triggering ATC run failed: %v", err)
}

View File

@ -167,6 +167,9 @@ func ReadConfigFile(path string) (file []byte, err error) {
// GetHTTPResponse wraps the SendRequest function of piperhttp
func GetHTTPResponse(requestType string, connectionDetails ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) {
log.Entry().Debugf("Request body: %s", string(body))
log.Entry().Debugf("Request user: %s", connectionDetails.User)
header := make(map[string][]string)
header["Content-Type"] = []string{"application/json"}
header["Accept"] = []string{"application/json"}
@ -182,16 +185,20 @@ func GetHTTPResponse(requestType string, connectionDetails ConnectionDetailsHTTP
// Further error details may be present in the response body of the HTTP response.
// If the response body is parseable, the included details are wrapped around the original error from the HTTP repsponse.
// If this is not possible, the original error is returned.
func HandleHTTPError(resp *http.Response, err error, message string, connectionDetails ConnectionDetailsHTTP) error {
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()
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()
AddDefaultDashedLine(1)
}
log.Entry().Infof("Error message: %s,", err.Error())
@ -201,15 +208,15 @@ func HandleHTTPError(resp *http.Response, err error, message string, connectionD
log.Entry().WithField("StatusCode", resp.Status).WithField("User", connectionDetails.User).WithField("URL", connectionDetails.URL).Error(message)
errorText, errorCode, parsingError := GetErrorDetailsFromResponse(resp)
errorText, errorCode, parsingError = GetErrorDetailsFromResponse(resp)
if parsingError != nil {
return err
return "", err
}
abapError := errors.New(fmt.Sprintf("%s - %s", errorCode, errorText))
err = errors.Wrap(abapError, err.Error())
}
return err
return errorCode, err
}
func GetErrorDetailsFromResponse(resp *http.Response) (errorString string, errorCode string, err error) {
@ -249,8 +256,10 @@ func ConvertTime(logTimeStamp string) time.Time {
}
// AddDefaultDashedLine adds 25 dashes
func AddDefaultDashedLine() {
log.Entry().Infof(strings.Repeat("-", 25))
func AddDefaultDashedLine(j int) {
for i := 1; i <= j; i++ {
log.Entry().Infof(strings.Repeat("-", 25))
}
}
// AddDefaultDebugLine adds 25 dashes in debug
@ -370,6 +379,7 @@ type ClientMock struct {
Error error
NilResponse bool
ErrorInsteadOfDump bool
ErrorList []error
}
// SetOptions sets clientOptions for a client mock
@ -383,8 +393,10 @@ func (c *ClientMock) SendRequest(method, url string, bdy io.Reader, hdr http.Hea
}
var body []byte
var responseError error
if c.Body != "" {
body = []byte(c.Body)
responseError = c.Error
} else {
if c.ErrorInsteadOfDump && len(c.BodyList) == 0 {
return nil, errors.New("No more bodies in the list")
@ -392,6 +404,12 @@ func (c *ClientMock) SendRequest(method, url string, bdy io.Reader, hdr http.Hea
bodyString := c.BodyList[len(c.BodyList)-1]
c.BodyList = c.BodyList[:len(c.BodyList)-1]
body = []byte(bodyString)
if len(c.ErrorList) == 0 {
responseError = c.Error
} else {
responseError = c.ErrorList[len(c.ErrorList)-1]
c.ErrorList = c.ErrorList[:len(c.ErrorList)-1]
}
}
header := http.Header{}
header.Set("X-Csrf-Token", c.Token)
@ -399,7 +417,7 @@ func (c *ClientMock) SendRequest(method, url string, bdy io.Reader, hdr http.Hea
StatusCode: c.StatusCode,
Header: header,
Body: io.NopCloser(bytes.NewReader(body)),
}, c.Error
}, responseError
}
// DownloadFile : Empty file download

View File

@ -309,7 +309,7 @@ func TestHandleHTTPError(t *testing.T) {
receivedErr := errors.New(errorValue)
message := "Custom Error Message"
err := HandleHTTPError(&resp, receivedErr, message, ConnectionDetailsHTTP{})
_, err := HandleHTTPError(&resp, receivedErr, message, ConnectionDetailsHTTP{})
assert.EqualError(t, err, fmt.Sprintf("%s: %s - %s", receivedErr.Error(), abapErrorCode, abapErrorMessage))
log.Entry().Info(err.Error())
})
@ -328,7 +328,7 @@ func TestHandleHTTPError(t *testing.T) {
receivedErr := errors.New(errorValue)
message := "Custom Error Message"
err := HandleHTTPError(&resp, receivedErr, message, ConnectionDetailsHTTP{})
_, err := HandleHTTPError(&resp, receivedErr, message, ConnectionDetailsHTTP{})
assert.EqualError(t, err, fmt.Sprintf("%s", receivedErr.Error()))
log.Entry().Info(err.Error())
})
@ -347,7 +347,7 @@ func TestHandleHTTPError(t *testing.T) {
receivedErr := errors.New(errorValue)
message := "Custom Error Message"
err := HandleHTTPError(&resp, receivedErr, message, ConnectionDetailsHTTP{})
_, err := HandleHTTPError(&resp, receivedErr, message, ConnectionDetailsHTTP{})
assert.EqualError(t, err, fmt.Sprintf("%s", receivedErr.Error()))
log.Entry().Info(err.Error())
})
@ -361,7 +361,7 @@ func TestHandleHTTPError(t *testing.T) {
_, hook := test.NewNullLogger()
log.RegisterHook(hook)
err := HandleHTTPError(nil, receivedErr, message, ConnectionDetailsHTTP{})
_, err := HandleHTTPError(nil, receivedErr, message, ConnectionDetailsHTTP{})
assert.EqualError(t, err, fmt.Sprintf("%s", receivedErr.Error()))
assert.Equal(t, 5, len(hook.Entries), "Expected a different number of entries")

View File

@ -1,51 +1,48 @@
package abaputils
import (
"encoding/json"
"fmt"
"io"
"reflect"
"sort"
"strconv"
"strings"
"time"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/pkg/errors"
)
const failureMessageClonePull = "Could not pull the Repository / Software Component "
const numberOfEntriesPerPage = 100000
const logOutputStatusLength = 10
const logOutputTimestampLength = 29
// PollEntity periodically polls the pull/import entity to get the status. Check if the import is still running
func PollEntity(repositoryName string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender, pollIntervall time.Duration) (string, error) {
// PollEntity periodically polls the action entity to get the status. Check if the import is still running
func PollEntity(api SoftwareComponentApiInterface, pollIntervall time.Duration) (string, error) {
log.Entry().Info("Start polling the status...")
var status string = "R"
var statusCode string = "R"
var err error
for {
pullEntity, responseStatus, err := GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
// pullEntity, responseStatus, err := api.GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
statusCode, err = api.GetAction()
if err != nil {
return status, err
return statusCode, err
}
status = pullEntity.Status
log.Entry().WithField("StatusCode", responseStatus).Info("Status: " + pullEntity.Status + " - " + pullEntity.StatusDescription)
if pullEntity.Status != "R" && pullEntity.Status != "Q" {
PrintLogs(repositoryName, connectionDetails, client)
if statusCode != "R" && statusCode != "Q" {
PrintLogs(api)
break
}
time.Sleep(pollIntervall)
}
return status, nil
return statusCode, nil
}
func PrintLogs(repositoryName string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender) {
connectionDetails.URL = connectionDetails.URL + "?$expand=to_Log_Overview"
entity, _, err := GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
func PrintLogs(api SoftwareComponentApiInterface) {
// connectionDetails.URL = connectionDetails.URL + "?$expand=to_Log_Overview"
entity, err := api.GetLogOverview()
if err != nil || len(entity.ToLogOverview.Results) == 0 {
// return if no logs are available
return
@ -60,14 +57,14 @@ func PrintLogs(repositoryName string, connectionDetails ConnectionDetailsHTTP, c
// Print Details
for _, logEntryForDetails := range entity.ToLogOverview.Results {
printLog(logEntryForDetails, connectionDetails, client)
printLog(logEntryForDetails, api)
}
AddDefaultDashedLine()
AddDefaultDashedLine(1)
return
}
func printOverview(entity PullEntity) {
func printOverview(entity ActionEntity) {
logOutputPhaseLength, logOutputLineLength := calculateLenghts(entity)
@ -85,7 +82,7 @@ func printOverview(entity PullEntity) {
printDashedLine(logOutputLineLength)
}
func calculateLenghts(entity PullEntity) (int, int) {
func calculateLenghts(entity ActionEntity) (int, int) {
phaseLength := 22
for _, logEntry := range entity.ToLogOverview.Results {
if l := len(logEntry.Name); l > phaseLength {
@ -101,24 +98,18 @@ func printDashedLine(i int) {
log.Entry().Infof(strings.Repeat("-", i))
}
func printLog(logOverviewEntry LogResultsV2, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender) {
func printLog(logOverviewEntry LogResultsV2, api SoftwareComponentApiInterface) {
page := 0
printHeader(logOverviewEntry)
for {
connectionDetails.URL = logOverviewEntry.ToLogProtocol.Deferred.URI + getLogProtocolQuery(page)
entity, err := GetProtocol(failureMessageClonePull, connectionDetails, client)
printLogProtocolEntries(logOverviewEntry, entity)
logProtocolEntry, err := api.GetLogProtocol(logOverviewEntry, page)
printLogProtocolEntries(logOverviewEntry, logProtocolEntry)
page += 1
if allLogsHaveBeenPrinted(entity, page, err) {
if allLogsHaveBeenPrinted(logProtocolEntry, page, err) {
break
}
}
}
func printLogProtocolEntries(logEntry LogResultsV2, entity LogProtocolResults) {
@ -126,12 +117,10 @@ func printLogProtocolEntries(logEntry LogResultsV2, entity LogProtocolResults) {
sort.SliceStable(entity.Results, func(i, j int) bool {
return entity.Results[i].ProtocolLine < entity.Results[j].ProtocolLine
})
if logEntry.Status != `Success` {
for _, entry := range entity.Results {
log.Entry().Info(entry.Description)
}
} else {
for _, entry := range entity.Results {
log.Entry().Debug(entry.Description)
@ -144,6 +133,8 @@ func allLogsHaveBeenPrinted(entity LogProtocolResults, page int, err error) bool
numberOfProtocols, errConversion := strconv.Atoi(entity.Count)
if errConversion == nil {
allPagesHaveBeenRead = numberOfProtocols <= page*numberOfEntriesPerPage
} else {
return true
}
return (err != nil || allPagesHaveBeenRead || reflect.DeepEqual(entity.Results, LogProtocolResults{}))
}
@ -151,9 +142,9 @@ func allLogsHaveBeenPrinted(entity LogProtocolResults, page int, err error) bool
func printHeader(logEntry LogResultsV2) {
if logEntry.Status != `Success` {
log.Entry().Infof("\n")
AddDefaultDashedLine()
AddDefaultDashedLine(1)
log.Entry().Infof("%s (%v)", logEntry.Name, ConvertTime(logEntry.Timestamp))
AddDefaultDashedLine()
AddDefaultDashedLine(1)
} else {
log.Entry().Debugf("\n")
AddDebugDashedLine()
@ -169,65 +160,6 @@ func getLogProtocolQuery(page int) string {
return fmt.Sprintf("?$skip=%s&$top=%s&$inlinecount=allpages", fmt.Sprint(skip), fmt.Sprint(top))
}
func GetStatus(failureMessage string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender) (body PullEntity, status string, err error) {
resp, err := GetHTTPResponse("GET", connectionDetails, nil, client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
err = HandleHTTPError(resp, err, failureMessage, connectionDetails)
if resp != nil {
status = resp.Status
}
return body, status, err
}
defer resp.Body.Close()
// Parse response
var abapResp map[string]*json.RawMessage
bodyText, _ := io.ReadAll(resp.Body)
marshallError := json.Unmarshal(bodyText, &abapResp)
if marshallError != nil {
return body, status, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
}
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) {
log.Entry().WithField("StatusCode", resp.Status).Error(failureMessage)
log.SetErrorCategory(log.ErrorInfrastructure)
var err = errors.New("Request to ABAP System not successful")
return body, resp.Status, err
}
return body, resp.Status, nil
}
func GetProtocol(failureMessage string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender) (body LogProtocolResults, err error) {
resp, err := GetHTTPResponse("GET", connectionDetails, nil, client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
err = HandleHTTPError(resp, err, failureMessage, connectionDetails)
return body, err
}
defer resp.Body.Close()
// Parse response
var abapResp map[string]*json.RawMessage
bodyText, _ := io.ReadAll(resp.Body)
marshallError := json.Unmarshal(bodyText, &abapResp)
if marshallError != nil {
return body, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
}
marshallError = json.Unmarshal(*abapResp["d"], &body)
if marshallError != nil {
return body, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
}
return body, nil
}
// GetRepositories for parsing one or multiple branches and repositories from repositories file or branchName and repositoryName configuration
func GetRepositories(config *RepositoriesConfig, branchRequired bool) ([]Repository, error) {
var repositories = make([]Repository, 0)
@ -313,118 +245,3 @@ func (repo *Repository) GetPullLogString() (logString string) {
logString = "repository / software component '" + repo.Name + "'" + commitOrTag
return logString
}
/****************************************
* Structs for the A4C_A2G_GHA service *
****************************************/
// PullEntity struct for the Pull/Import entity A4C_A2G_GHA_SC_IMP
type PullEntity struct {
Metadata AbapMetadata `json:"__metadata"`
UUID string `json:"uuid"`
Namespace string `json:"namepsace"`
ScName string `json:"sc_name"`
ImportType string `json:"import_type"`
BranchName string `json:"branch_name"`
StartedByUser string `json:"user_name"`
Status string `json:"status"`
StatusDescription string `json:"status_descr"`
CommitID string `json:"commit_id"`
StartTime string `json:"start_time"`
ChangeTime string `json:"change_time"`
ToExecutionLog AbapLogs `json:"to_Execution_log"`
ToTransportLog AbapLogs `json:"to_Transport_log"`
ToLogOverview AbapLogsV2 `json:"to_Log_Overview"`
}
// BranchEntity struct for the Branch entity A4C_A2G_GHA_SC_BRANCH
type BranchEntity struct {
Metadata AbapMetadata `json:"__metadata"`
ScName string `json:"sc_name"`
Namespace string `json:"namepsace"`
BranchName string `json:"branch_name"`
ParentBranch string `json:"derived_from"`
CreatedBy string `json:"created_by"`
CreatedOn string `json:"created_on"`
IsActive bool `json:"is_active"`
CommitID string `json:"commit_id"`
CommitMessage string `json:"commit_message"`
LastCommitBy string `json:"last_commit_by"`
LastCommitOn string `json:"last_commit_on"`
}
// CloneEntity struct for the Clone entity A4C_A2G_GHA_SC_CLONE
type CloneEntity struct {
Metadata AbapMetadata `json:"__metadata"`
UUID string `json:"uuid"`
ScName string `json:"sc_name"`
BranchName string `json:"branch_name"`
ImportType string `json:"import_type"`
Namespace string `json:"namepsace"`
Status string `json:"status"`
StatusDescription string `json:"status_descr"`
StartedByUser string `json:"user_name"`
StartTime string `json:"start_time"`
ChangeTime string `json:"change_time"`
}
// AbapLogs struct for ABAP logs
type AbapLogs struct {
Results []LogResults `json:"results"`
}
type AbapLogsV2 struct {
Results []LogResultsV2 `json:"results"`
}
type LogResultsV2 struct {
Metadata AbapMetadata `json:"__metadata"`
Index int `json:"log_index"`
Name string `json:"log_name"`
Status string `json:"type_of_found_issues"`
Timestamp string `json:"timestamp"`
ToLogProtocol LogProtocolDeferred `json:"to_Log_Protocol"`
}
type LogProtocolDeferred struct {
Deferred URI `json:"__deferred"`
}
type URI struct {
URI string `json:"uri"`
}
type LogProtocolResults struct {
Results []LogProtocol `json:"results"`
Count string `json:"__count"`
}
type LogProtocol struct {
Metadata AbapMetadata `json:"__metadata"`
OverviewIndex int `json:"log_index"`
ProtocolLine int `json:"index_no"`
Type string `json:"type"`
Description string `json:"descr"`
Timestamp string `json:"timestamp"`
}
// LogResults struct for Execution and Transport Log entities A4C_A2G_GHA_SC_LOG_EXE and A4C_A2G_GHA_SC_LOG_TP
type LogResults struct {
Index string `json:"index_no"`
Type string `json:"type"`
Description string `json:"descr"`
Timestamp string `json:"timestamp"`
}
// RepositoriesConfig struct for parsing one or multiple branches and repositories configurations
type RepositoriesConfig struct {
BranchName string
CommitID string
RepositoryName string
RepositoryNames []string
Repositories string
}
type EntitySetsForManageGitRepository struct {
EntitySets []string `json:"EntitySets"`
}

View File

@ -10,7 +10,6 @@ import (
"os"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
@ -46,33 +45,25 @@ func TestPollEntity(t *testing.T) {
logResultSuccess,
`{"d" : { "status" : "S" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "Q" } }`,
`{}`,
},
Token: "myToken",
StatusCode: 200,
}
options := AbapEnvironmentOptions{
CfAPIEndpoint: "https://api.endpoint.com",
CfOrg: "testOrg",
CfSpace: "testSpace",
CfServiceInstance: "testInstance",
CfServiceKeyName: "testServiceKey",
Username: "testUser",
Password: "testPassword",
}
config := AbapEnvironmentCheckoutBranchOptions{
AbapEnvOptions: options,
RepositoryName: "testRepo1",
}
con := ConnectionDetailsHTTP{
User: "MY_USER",
Password: "MY_PW",
URL: "https://api.endpoint.com/Entity/",
XCsrfToken: "MY_TOKEN",
}
status, _ := PollEntity(config.RepositoryName, con, client, 0)
swcManager := SoftwareComponentApiManager{Client: client}
repo := Repository{Name: "testRepo1"}
api, _ := swcManager.GetAPI(con, repo)
status, _ := PollEntity(api, 0)
assert.Equal(t, "S", status)
assert.Equal(t, 0, len(client.BodyList), "Not all requests were done")
})
@ -87,33 +78,24 @@ func TestPollEntity(t *testing.T) {
`{"d" : { "status" : "E" } }`,
`{"d" : { "status" : "R" } }`,
`{"d" : { "status" : "Q" } }`,
`{}`,
},
Token: "myToken",
StatusCode: 200,
}
options := AbapEnvironmentOptions{
CfAPIEndpoint: "https://api.endpoint.com",
CfOrg: "testOrg",
CfSpace: "testSpace",
CfServiceInstance: "testInstance",
CfServiceKeyName: "testServiceKey",
Username: "testUser",
Password: "testPassword",
}
config := AbapEnvironmentCheckoutBranchOptions{
AbapEnvOptions: options,
RepositoryName: "testRepo1",
}
con := ConnectionDetailsHTTP{
User: "MY_USER",
Password: "MY_PW",
URL: "https://api.endpoint.com/Entity/",
XCsrfToken: "MY_TOKEN",
}
status, _ := PollEntity(config.RepositoryName, con, client, 0)
swcManager := SoftwareComponentApiManager{Client: client}
repo := Repository{Name: "testRepo1"}
api, _ := swcManager.GetAPI(con, repo)
status, _ := PollEntity(api, 0)
assert.Equal(t, "E", status)
assert.Equal(t, 0, len(client.BodyList), "Not all requests were done")
})
@ -318,22 +300,3 @@ func TestCreateRequestBodies(t *testing.T) {
assert.Equal(t, `{"sc_name":"/DMO/REPO", "tag_name":"myTag"}`, body, "Expected different body")
})
}
func TestGetStatus(t *testing.T) {
t.Run("Graceful Exit", func(t *testing.T) {
client := &ClientMock{
NilResponse: true,
Error: errors.New("Backend Error"),
StatusCode: 500,
}
connectionDetails := ConnectionDetailsHTTP{
URL: "example.com",
}
_, status, err := GetStatus("failure message", connectionDetails, client)
assert.Error(t, err, "Expected Error")
assert.Equal(t, "", status)
})
}

View File

@ -0,0 +1,369 @@
package abaputils
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"reflect"
"strings"
"time"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/pkg/errors"
"k8s.io/utils/strings/slices"
)
type SAP_COM_0510 struct {
con ConnectionDetailsHTTP
client piperhttp.Sender
repository Repository
path string
cloneEntity string
repositoryEntity string
tagsEntity string
checkoutAction string
actionEntity string
uuid string
failureMessage string
maxRetries int
retryBaseSleepUnit time.Duration
retryMaxSleepTime time.Duration
retryAllowedErrorCodes []string
}
func (api *SAP_COM_0510) init(con ConnectionDetailsHTTP, client piperhttp.Sender, repo Repository) {
api.con = con
api.client = client
api.repository = repo
api.path = "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY"
api.cloneEntity = "/Clones"
api.repositoryEntity = "/Repositories"
api.tagsEntity = "/Tags"
api.actionEntity = "/Pull"
api.checkoutAction = "/checkout_branch"
api.failureMessage = "The action of the Repository / Software Component " + api.repository.Name + " failed"
api.maxRetries = 3
api.setSleepTimeConfig(1*time.Second, 120*time.Second)
api.retryAllowedErrorCodes = append(api.retryAllowedErrorCodes, "A4C_A2G/228")
}
func (api *SAP_COM_0510) getUUID() string {
return api.uuid
}
func (api *SAP_COM_0510) CreateTag(tag Tag) error {
if reflect.DeepEqual(Tag{}, tag) {
return errors.New("No Tag provided")
}
con := api.con
con.URL = api.con.URL + api.path + api.tagsEntity
requestBodyStruct := CreateTagBody{RepositoryName: api.repository.Name, CommitID: api.repository.CommitID, Tag: tag.TagName, Description: tag.TagDescription}
jsonBody, err := json.Marshal(&requestBodyStruct)
if err != nil {
return err
}
return api.triggerRequest(con, jsonBody)
}
func (api *SAP_COM_0510) CheckoutBranch() error {
if api.repository.Name == "" || api.repository.Branch == "" {
return fmt.Errorf("Failed to trigger checkout: %w", errors.New("Repository and/or Branch Configuration is empty. Please make sure that you have specified the correct values"))
}
// the request looks like: POST/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/checkout_branch?branch_name='newBranch'&sc_name=/DMO/GIT_REPOSITORY'
checkoutConnectionDetails := api.con
checkoutConnectionDetails.URL = api.con.URL + api.path + api.checkoutAction + `?branch_name='` + api.repository.Branch + `'&sc_name='` + api.repository.Name + `'`
jsonBody := []byte(``)
return api.triggerRequest(checkoutConnectionDetails, jsonBody)
}
func (api *SAP_COM_0510) parseActionResponse(resp *http.Response, err error) (ActionEntity, error) {
var body ActionEntity
var abapResp map[string]*json.RawMessage
bodyText, errRead := io.ReadAll(resp.Body)
if errRead != nil {
return ActionEntity{}, err
}
if err := json.Unmarshal(bodyText, &abapResp); err != nil {
return ActionEntity{}, err
}
if err := json.Unmarshal(*abapResp["d"], &body); err != nil {
return ActionEntity{}, err
}
if reflect.DeepEqual(ActionEntity{}, body) {
log.Entry().WithField("StatusCode", resp.Status).WithField("branchName", api.repository.Branch).Error("Could not switch to specified branch")
err := errors.New("Request to ABAP System not successful")
return ActionEntity{}, err
}
return body, nil
}
func (api *SAP_COM_0510) Pull() error {
// Trigger the Pull of a Repository
if api.repository.Name == "" {
return errors.New("An empty string was passed for the parameter 'repositoryName'")
}
pullConnectionDetails := api.con
pullConnectionDetails.URL = api.con.URL + api.path + api.actionEntity
jsonBody := []byte(api.repository.GetPullRequestBody())
return api.triggerRequest(pullConnectionDetails, jsonBody)
}
func (api *SAP_COM_0510) GetLogProtocol(logOverviewEntry LogResultsV2, page int) (body LogProtocolResults, err error) {
connectionDetails := api.con
connectionDetails.URL = logOverviewEntry.ToLogProtocol.Deferred.URI + getLogProtocolQuery(page)
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
return body, err
}
defer resp.Body.Close()
// Parse response
var abapResp map[string]*json.RawMessage
bodyText, _ := io.ReadAll(resp.Body)
marshallError := json.Unmarshal(bodyText, &abapResp)
if marshallError != nil {
return body, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
}
marshallError = json.Unmarshal(*abapResp["d"], &body)
if marshallError != nil {
return body, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
}
return body, nil
}
func (api *SAP_COM_0510) GetLogOverview() (body ActionEntity, err error) {
connectionDetails := api.con
connectionDetails.URL = api.con.URL + api.path + api.actionEntity + "(uuid=guid'" + api.getUUID() + "')" + "?$expand=to_Log_Overview"
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
return body, err
}
defer resp.Body.Close()
// Parse response
var abapResp map[string]*json.RawMessage
bodyText, _ := io.ReadAll(resp.Body)
marshallError := json.Unmarshal(bodyText, &abapResp)
if marshallError != nil {
return body, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
}
marshallError = json.Unmarshal(*abapResp["d"], &body)
if marshallError != nil {
return body, errors.Wrap(marshallError, "Could not parse response from the ABAP Environment system")
}
if reflect.DeepEqual(ActionEntity{}, body) {
log.Entry().WithField("StatusCode", resp.Status).Error(api.failureMessage)
log.SetErrorCategory(log.ErrorInfrastructure)
var err = errors.New("Request to ABAP System not successful")
return body, err
}
abapStatusCode := body.Status
log.Entry().Info("Status: " + abapStatusCode + " - " + body.StatusDescription)
return body, nil
}
func (api *SAP_COM_0510) GetAction() (string, error) {
connectionDetails := api.con
connectionDetails.URL = api.con.URL + api.path + api.actionEntity + "(uuid=guid'" + api.getUUID() + "')"
resp, err := GetHTTPResponse("GET", connectionDetails, nil, api.client)
if err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
_, err = HandleHTTPError(resp, err, api.failureMessage, connectionDetails)
return "E", err
}
defer resp.Body.Close()
// Parse Response
body, parseError := api.parseActionResponse(resp, err)
if parseError != nil {
return "E", parseError
}
api.uuid = body.UUID
abapStatusCode := body.Status
log.Entry().Info("Status: " + abapStatusCode + " - " + body.StatusDescription)
return abapStatusCode, nil
}
func (api *SAP_COM_0510) GetRepository() (bool, string, error) {
if api.repository.Name == "" {
return false, "", errors.New("An empty string was passed for the parameter 'repositoryName'")
}
swcConnectionDetails := api.con
swcConnectionDetails.URL = api.con.URL + api.path + api.repositoryEntity + "('" + strings.Replace(api.repository.Name, "/", "%2F", -1) + "')"
resp, err := GetHTTPResponse("GET", swcConnectionDetails, nil, api.client)
if err != nil {
_, errRepo := HandleHTTPError(resp, err, "Reading the Repository / Software Component failed", api.con)
return false, "", errRepo
}
defer resp.Body.Close()
var body RepositoryEntity
var abapResp map[string]*json.RawMessage
bodyText, errRead := io.ReadAll(resp.Body)
if errRead != nil {
return false, "", err
}
if err := json.Unmarshal(bodyText, &abapResp); err != nil {
return false, "", err
}
if err := json.Unmarshal(*abapResp["d"], &body); err != nil {
return false, "", err
}
if reflect.DeepEqual(RepositoryEntity{}, body) {
log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", api.repository.Name).WithField("branchName", api.repository.Branch).WithField("commitID", api.repository.CommitID).WithField("Tag", api.repository.Tag).Error("Could not Clone the Repository / Software Component")
err := errors.New("Request to ABAP System not successful")
return false, "", err
}
if body.AvailOnInst {
return true, body.ActiveBranch, nil
}
return false, "", err
}
func (api *SAP_COM_0510) Clone() error {
// Trigger the Clone of a Repository
if api.repository.Name == "" {
return errors.New("An empty string was passed for the parameter 'repositoryName'")
}
cloneConnectionDetails := api.con
cloneConnectionDetails.URL = api.con.URL + api.path + api.cloneEntity
body := []byte(api.repository.GetCloneRequestBody())
return api.triggerRequest(cloneConnectionDetails, body)
}
func (api *SAP_COM_0510) triggerRequest(cloneConnectionDetails ConnectionDetailsHTTP, jsonBody []byte) error {
var err error
var body ActionEntity
var resp *http.Response
var errorCode string
for i := 0; i <= api.maxRetries; i++ {
if i > 0 {
sleepTime, err := api.getSleepTime(i + 5)
if err != nil {
// reached max retry duration
break
}
log.Entry().Infof("Retrying in %s", sleepTime.String())
time.Sleep(sleepTime)
}
resp, err = GetHTTPResponse("POST", cloneConnectionDetails, jsonBody, api.client)
if err != nil {
errorCode, err = HandleHTTPError(resp, err, "Triggering the action failed", api.con)
if slices.Contains(api.retryAllowedErrorCodes, errorCode) {
// Error Code allows for retry
continue
} else {
break
}
}
defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", api.repository.Name).WithField("branchName", api.repository.Branch).WithField("commitID", api.repository.CommitID).WithField("Tag", api.repository.Tag).Info("Triggered action of Repository / Software Component")
body, err = api.parseActionResponse(resp, err)
break
}
api.uuid = body.UUID
return err
}
// initialRequest implements SoftwareComponentApiInterface.
func (api *SAP_COM_0510) initialRequest() error {
// Configuring the HTTP Client and CookieJar
cookieJar, errorCookieJar := cookiejar.New(nil)
if errorCookieJar != nil {
return errors.Wrap(errorCookieJar, "Could not create a Cookie Jar")
}
api.client.SetOptions(piperhttp.ClientOptions{
MaxRequestDuration: 180 * time.Second,
CookieJar: cookieJar,
Username: api.con.User,
Password: api.con.Password,
})
headConnection := api.con
headConnection.XCsrfToken = "fetch"
headConnection.URL = api.con.URL + api.path
// Loging into the ABAP System - getting the x-csrf-token and cookies
resp, err := GetHTTPResponse("HEAD", headConnection, nil, api.client)
if err != nil {
_, err = HandleHTTPError(resp, err, "Authentication on the ABAP system failed", api.con)
return err
}
defer resp.Body.Close()
log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", api.con).Debug("Authentication on the ABAP system successful")
api.con.XCsrfToken = resp.Header.Get("X-Csrf-Token")
return nil
}
// getSleepTime Should return the Fibonacci numbers in the define time unit up to the defined maximum duration
func (api *SAP_COM_0510) getSleepTime(n int) (time.Duration, error) {
if n == 0 {
return 0, nil
} else if n == 1 {
return 1 * api.retryBaseSleepUnit, nil
} else if n < 0 {
return 0, errors.New("Negative numbers are not allowed")
}
var result, i int
prev := 0
next := 1
for i = 2; i <= n; i++ {
result = prev + next
prev = next
next = result
}
sleepTime := time.Duration(result) * api.retryBaseSleepUnit
if sleepTime > api.retryMaxSleepTime {
return 0, errors.New("Exceeded max sleep time")
}
return sleepTime, nil
}
// setSleepTimeConfig sets the time unit (seconds, nanoseconds) and the maximum sleep duration
func (api *SAP_COM_0510) setSleepTimeConfig(timeUnit time.Duration, maxSleepTime time.Duration) {
api.retryBaseSleepUnit = timeUnit
api.retryMaxSleepTime = maxSleepTime
}

View File

@ -0,0 +1,483 @@
//go:build unit
// +build unit
package abaputils
import (
"testing"
"time"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
var con ConnectionDetailsHTTP
var repo Repository
func init() {
con.User = "CC_USER"
con.Password = "123abc"
con.URL = "https://example.com"
repo.Name = "/DMO/REPO"
repo.Branch = "main"
}
func TestRetry(t *testing.T) {
t.Run("Test retry success", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"d" : { "status" : "R", "UUID" : "GUID" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Software component lifecycle activities in progress. Try again later..."} } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
ErrorList: []error{
nil,
errors.New("HTTP 400"),
nil,
},
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api.setSleepTimeConfig(time.Nanosecond, 120*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errAction := api.(*SAP_COM_0510).triggerRequest(ConnectionDetailsHTTP{User: "CC_USER", Password: "abc123", URL: "https://example.com/path"}, []byte("{}"))
assert.NoError(t, errAction)
assert.Equal(t, "GUID", api.getUUID(), "API does not cotain correct UUID")
})
t.Run("Test retry not allowed", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"d" : { "status" : "R", "UUID" : "GUID" } }`,
`{"error" : { "code" : "A4C_A2G/224", "message" : { "lang" : "de", "value" : "Error Text"} } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
ErrorList: []error{
nil,
errors.New("HTTP 400"),
nil,
},
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api.setSleepTimeConfig(time.Nanosecond, 120*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errAction := api.(*SAP_COM_0510).triggerRequest(ConnectionDetailsHTTP{User: "CC_USER", Password: "abc123", URL: "https://example.com/path"}, []byte("{}"))
assert.ErrorContains(t, errAction, "HTTP 400: A4C_A2G/224 - Error Text")
assert.Empty(t, api.getUUID(), "API does not cotain correct UUID")
})
t.Run("Test retry maxSleepTime", func(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"} } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
ErrorList: []error{
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
nil,
},
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api.setSleepTimeConfig(time.Nanosecond, 20*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
api.(*SAP_COM_0510).maxRetries = 20
errAction := api.(*SAP_COM_0510).triggerRequest(ConnectionDetailsHTTP{User: "CC_USER", Password: "abc123", URL: "https://example.com/path"}, []byte("{}"))
assert.ErrorContains(t, errAction, "HTTP 400: A4C_A2G/228 - Error Text")
assert.Empty(t, api.getUUID(), "API does not cotain correct UUID")
assert.Equal(t, 6, len(client.BodyList), "Expected maxSleepTime to limit requests")
})
t.Run("Test retry maxRetries", func(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"} } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
ErrorList: []error{
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
errors.New("HTTP 400"),
nil,
},
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api.setSleepTimeConfig(time.Nanosecond, 999*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
api.(*SAP_COM_0510).maxRetries = 3
errAction := api.(*SAP_COM_0510).triggerRequest(ConnectionDetailsHTTP{User: "CC_USER", Password: "abc123", URL: "https://example.com/path"}, []byte("{}"))
assert.ErrorContains(t, errAction, "HTTP 400: A4C_A2G/228 - Error Text")
assert.Empty(t, api.getUUID(), "API does not cotain correct UUID")
assert.Equal(t, 5, len(client.BodyList), "Expected maxRetries to limit requests")
})
}
func TestClone(t *testing.T) {
t.Run("Test Clone Success", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"d" : { "status" : "R", "UUID" : "GUID" } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errClone := api.Clone()
assert.NoError(t, errClone)
assert.Equal(t, "GUID", api.getUUID(), "API does not cotain correct UUID")
})
t.Run("Test Clone Failure", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{ "d" : {} }`,
`{ "d" : {} }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api.setSleepTimeConfig(time.Nanosecond, 120*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errClone := api.Clone()
assert.ErrorContains(t, errClone, "Request to ABAP System not successful")
assert.Empty(t, api.getUUID(), "API does not cotain correct UUID")
})
t.Run("Test Clone Retry", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"d" : { "status" : "R", "UUID" : "GUID" } }`,
`{"error" : { "code" : "A4C_A2G/228", "message" : { "lang" : "de", "value" : "Software component lifecycle activities in progress. Try again later..."} } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
ErrorList: []error{
nil,
errors.New("HTTP 400"),
nil,
},
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
api.setSleepTimeConfig(time.Nanosecond, 120*time.Nanosecond)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errClone := api.Clone()
assert.NoError(t, errClone)
assert.Equal(t, "GUID", api.getUUID(), "API does not cotain correct UUID")
})
}
func TestPull(t *testing.T) {
t.Run("Test Pull Success", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"d" : { "status" : "R", "UUID" : "GUID" } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errPull := api.Pull()
assert.NoError(t, errPull)
assert.Equal(t, "GUID", api.getUUID(), "API does not cotain correct UUID")
})
t.Run("Test Pull Failure", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{ "d" : {} }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errPull := api.Pull()
assert.ErrorContains(t, errPull, "Request to ABAP System not successful")
assert.Empty(t, api.getUUID(), "API does not cotain correct UUID")
})
}
func TestCheckout(t *testing.T) {
t.Run("Test Checkout Success", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"d" : { "status" : "R", "UUID" : "GUID" } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errCheckout := api.CheckoutBranch()
assert.NoError(t, errCheckout)
assert.Equal(t, "GUID", api.getUUID(), "API does not cotain correct UUID")
})
t.Run("Test Checkout Failure", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{ "d" : {} }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errCheckoput := api.CheckoutBranch()
assert.ErrorContains(t, errCheckoput, "Request to ABAP System not successful")
assert.Empty(t, api.getUUID(), "API does not cotain correct UUID")
})
}
func TestGetRepo(t *testing.T) {
t.Run("Test GetRepo Success", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"d" : { "sc_name" : "testRepo1", "avail_on_inst" : true, "active_branch": "testBranch1" } }`,
`{"d" : [] }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
cloned, activeBranch, errAction := api.GetRepository()
assert.True(t, cloned)
assert.Equal(t, "testBranch1", activeBranch)
assert.NoError(t, errAction)
})
}
func TestCreateTag(t *testing.T) {
t.Run("Test Tag Success", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{"d" : { "status" : "R", "UUID" : "GUID" } }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errCreateTag := api.CreateTag(Tag{TagName: "myTag", TagDescription: "descr"})
assert.NoError(t, errCreateTag)
assert.Equal(t, "GUID", api.getUUID(), "API does not cotain correct UUID")
})
t.Run("Test Tag Failure", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{ "d" : {} }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errCreateTag := api.CreateTag(Tag{TagName: "myTag", TagDescription: "descr"})
assert.ErrorContains(t, errCreateTag, "Request to ABAP System not successful")
assert.Empty(t, api.getUUID(), "API does not cotain correct UUID")
})
t.Run("Test Tag Empty", func(t *testing.T) {
client := &ClientMock{
BodyList: []string{
`{ "d" : {} }`,
`{ }`,
},
Token: "myToken",
StatusCode: 200,
}
apiManager := &SoftwareComponentApiManager{Client: client, PollIntervall: 1 * time.Microsecond}
api, err := apiManager.GetAPI(con, repo)
assert.NoError(t, err)
assert.IsType(t, &SAP_COM_0510{}, api.(*SAP_COM_0510), "API has wrong type")
errCreateTag := api.CreateTag(Tag{})
assert.ErrorContains(t, errCreateTag, "No Tag provided")
assert.Empty(t, api.getUUID(), "API does not cotain correct UUID")
})
}
func TestSleepTime(t *testing.T) {
t.Run("Test Sleep Time", func(t *testing.T) {
api := SAP_COM_0510{
retryMaxSleepTime: 120 * time.Nanosecond,
retryBaseSleepUnit: 1 * time.Nanosecond,
}
expectedResults := make([]time.Duration, 12)
expectedResults[0] = 0
expectedResults[1] = 1
expectedResults[2] = 1
expectedResults[3] = 2
expectedResults[4] = 3
expectedResults[5] = 5
expectedResults[6] = 8
expectedResults[7] = 13
expectedResults[8] = 21
expectedResults[9] = 34
expectedResults[10] = 55
expectedResults[11] = 89
results := make([]time.Duration, 12)
var err error
for i := 0; i <= 11; i++ {
results[i], err = api.getSleepTime(i)
assert.NoError(t, err)
}
assert.ElementsMatch(t, expectedResults, results)
_, err = api.getSleepTime(-10)
assert.Error(t, err)
_, err = api.getSleepTime(12)
assert.ErrorContains(t, err, "Exceeded max sleep time")
})
}

View File

@ -0,0 +1,194 @@
package abaputils
import (
"time"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
)
type SoftwareComponentApiManagerInterface interface {
GetAPI(con ConnectionDetailsHTTP, repo Repository) (SoftwareComponentApiInterface, error)
GetPollIntervall() time.Duration
}
type SoftwareComponentApiManager struct {
Client piperhttp.Sender
PollIntervall time.Duration
}
func (manager *SoftwareComponentApiManager) GetAPI(con ConnectionDetailsHTTP, repo Repository) (SoftwareComponentApiInterface, error) {
sap_com_0510 := SAP_COM_0510{}
sap_com_0510.init(con, manager.Client, repo)
// Initialize all APIs, use the one that returns a response
// Currently SAP_COM_0510, later SAP_COM_0948
err := sap_com_0510.initialRequest()
return &sap_com_0510, err
}
func (manager *SoftwareComponentApiManager) GetPollIntervall() time.Duration {
if manager.PollIntervall == 0 {
manager.PollIntervall = 5 * time.Second
}
return manager.PollIntervall
}
type SoftwareComponentApiInterface interface {
init(con ConnectionDetailsHTTP, client piperhttp.Sender, repo Repository)
initialRequest() error
setSleepTimeConfig(timeUnit time.Duration, maxSleepTime time.Duration)
getSleepTime(n int) (time.Duration, error)
getUUID() string
Clone() error
Pull() error
CheckoutBranch() error
GetRepository() (bool, string, error)
GetAction() (string, error)
GetLogOverview() (ActionEntity, error)
GetLogProtocol(LogResultsV2, int) (body LogProtocolResults, err error)
CreateTag(tag Tag) error
}
/****************************************
* Structs for the A4C_A2G_GHA service *
****************************************/
// ActionEntity struct for the Pull/Import entity A4C_A2G_GHA_SC_IMP
type ActionEntity struct {
Metadata AbapMetadata `json:"__metadata"`
UUID string `json:"uuid"`
Namespace string `json:"namepsace"`
ScName string `json:"sc_name"`
ImportType string `json:"import_type"`
BranchName string `json:"branch_name"`
StartedByUser string `json:"user_name"`
Status string `json:"status"`
StatusDescription string `json:"status_descr"`
CommitID string `json:"commit_id"`
StartTime string `json:"start_time"`
ChangeTime string `json:"change_time"`
ToExecutionLog AbapLogs `json:"to_Execution_log"`
ToTransportLog AbapLogs `json:"to_Transport_log"`
ToLogOverview AbapLogsV2 `json:"to_Log_Overview"`
}
// BranchEntity struct for the Branch entity A4C_A2G_GHA_SC_BRANCH
type BranchEntity struct {
Metadata AbapMetadata `json:"__metadata"`
ScName string `json:"sc_name"`
Namespace string `json:"namepsace"`
BranchName string `json:"branch_name"`
ParentBranch string `json:"derived_from"`
CreatedBy string `json:"created_by"`
CreatedOn string `json:"created_on"`
IsActive bool `json:"is_active"`
CommitID string `json:"commit_id"`
CommitMessage string `json:"commit_message"`
LastCommitBy string `json:"last_commit_by"`
LastCommitOn string `json:"last_commit_on"`
}
// CloneEntity struct for the Clone entity A4C_A2G_GHA_SC_CLONE
type CloneEntity struct {
Metadata AbapMetadata `json:"__metadata"`
UUID string `json:"uuid"`
ScName string `json:"sc_name"`
BranchName string `json:"branch_name"`
ImportType string `json:"import_type"`
Namespace string `json:"namepsace"`
Status string `json:"status"`
StatusDescription string `json:"status_descr"`
StartedByUser string `json:"user_name"`
StartTime string `json:"start_time"`
ChangeTime string `json:"change_time"`
}
type RepositoryEntity struct {
Metadata AbapMetadata `json:"__metadata"`
ScName string `json:"sc_name"`
ActiveBranch string `json:"active_branch"`
AvailOnInst bool `json:"avail_on_inst"`
}
// AbapLogs struct for ABAP logs
type AbapLogs struct {
Results []LogResults `json:"results"`
}
type AbapLogsV2 struct {
Results []LogResultsV2 `json:"results"`
}
type LogResultsV2 struct {
Metadata AbapMetadata `json:"__metadata"`
Index int `json:"log_index"`
Name string `json:"log_name"`
Status string `json:"type_of_found_issues"`
Timestamp string `json:"timestamp"`
ToLogProtocol LogProtocolDeferred `json:"to_Log_Protocol"`
}
type LogProtocolDeferred struct {
Deferred URI `json:"__deferred"`
}
type URI struct {
URI string `json:"uri"`
}
type LogProtocolResults struct {
Results []LogProtocol `json:"results"`
Count string `json:"__count"`
}
type LogProtocol struct {
Metadata AbapMetadata `json:"__metadata"`
OverviewIndex int `json:"log_index"`
ProtocolLine int `json:"index_no"`
Type string `json:"type"`
Description string `json:"descr"`
Timestamp string `json:"timestamp"`
}
// LogResults struct for Execution and Transport Log entities A4C_A2G_GHA_SC_LOG_EXE and A4C_A2G_GHA_SC_LOG_TP
type LogResults struct {
Index string `json:"index_no"`
Type string `json:"type"`
Description string `json:"descr"`
Timestamp string `json:"timestamp"`
}
// RepositoriesConfig struct for parsing one or multiple branches and repositories configurations
type RepositoriesConfig struct {
BranchName string
CommitID string
RepositoryName string
RepositoryNames []string
Repositories string
}
type EntitySetsForManageGitRepository struct {
EntitySets []string `json:"EntitySets"`
}
type CreateTagBacklog struct {
RepositoryName string
CommitID string
Tags []Tag
}
type Tag struct {
TagName string
TagDescription string
}
type CreateTagBody struct {
RepositoryName string `json:"sc_name"`
CommitID string `json:"commit_id"`
Tag string `json:"tag_name"`
Description string `json:"tag_description"`
}
type CreateTagResponse struct {
UUID string `json:"uuid"`
}