2020-08-21 14:49:48 +02:00
package cmd
import (
"encoding/json"
2020-10-05 14:38:35 +02:00
"fmt"
2023-08-16 12:57:04 +02:00
"io"
2022-06-30 10:43:33 +02:00
"net/http"
2020-08-21 14:49:48 +02:00
"net/http/cookiejar"
"reflect"
"time"
"github.com/SAP/jenkins-library/pkg/abaputils"
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
)
2021-08-04 17:31:16 +02:00
func abapEnvironmentCloneGitRepo ( config abapEnvironmentCloneGitRepoOptions , _ * telemetry . CustomData ) {
2020-08-21 14:49:48 +02:00
c := command . Command { }
c . Stdout ( log . Writer ( ) )
c . Stderr ( log . Writer ( ) )
var autils = abaputils . AbapUtils {
Exec : & c ,
}
client := piperhttp . Client { }
// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
2021-08-04 17:31:16 +02:00
err := runAbapEnvironmentCloneGitRepo ( & config , & autils , & client )
2020-08-21 14:49:48 +02:00
if err != nil {
log . Entry ( ) . WithError ( err ) . Fatal ( "step execution failed" )
}
}
2021-08-04 17:31:16 +02:00
func runAbapEnvironmentCloneGitRepo ( config * abapEnvironmentCloneGitRepoOptions , com abaputils . Communication , client piperhttp . Sender ) error {
2020-08-21 14:49:48 +02:00
// Mapping for options
2020-11-02 15:17:13 +02:00
subOptions := convertCloneConfig ( config )
2020-08-21 14:49:48 +02:00
// Determine the host, user and password, either via the input parameters or via a cloud foundry service key
2020-12-08 10:31:08 +02:00
connectionDetails , errorGetInfo := com . GetAbapCommunicationArrangementInfo ( subOptions , "" )
2020-08-21 14:49:48 +02:00
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 ,
} )
2022-06-30 10:43:33 +02:00
errConfig := checkConfiguration ( config )
if errConfig != nil {
return errors . Wrap ( errConfig , "The provided configuration is not allowed" )
}
2022-06-28 11:02:15 +02:00
repositories , errGetRepos := abaputils . GetRepositories ( & abaputils . RepositoriesConfig { BranchName : config . BranchName , RepositoryName : config . RepositoryName , Repositories : config . Repositories } , true )
2020-08-21 14:49:48 +02:00
if errGetRepos != nil {
2020-10-05 14:38:35 +02:00
return fmt . Errorf ( "Something failed during the clone: %w" , errGetRepos )
2020-08-21 14:49:48 +02:00
}
log . Entry ( ) . Infof ( "Start cloning %v repositories" , len ( repositories ) )
for _ , repo := range repositories {
2020-11-02 15:17:13 +02:00
2021-12-20 18:58:58 +02:00
logString := repo . GetCloneLogString ( )
errorString := "Clone of " + logString + " failed on the ABAP system"
2020-11-02 15:17:13 +02:00
2023-07-18 09:05:53 +02:00
abaputils . AddDefaultDashedLine ( )
2021-12-20 18:58:58 +02:00
log . Entry ( ) . Info ( "Start cloning " + logString )
2023-07-18 09:05:53 +02:00
abaputils . AddDefaultDashedLine ( )
2020-08-21 14:49:48 +02:00
// Triggering the Clone of the repository into the ABAP Environment system
2022-06-30 10:43:33 +02:00
uriConnectionDetails , errorTriggerClone , didCheckoutPullInstead := triggerClone ( repo , connectionDetails , client )
2020-08-21 14:49:48 +02:00
if errorTriggerClone != nil {
2021-12-20 18:58:58 +02:00
return errors . Wrapf ( errorTriggerClone , errorString )
2020-08-21 14:49:48 +02:00
}
2022-06-30 10:43:33 +02:00
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" )
2020-08-21 14:49:48 +02:00
}
}
2023-07-18 09:05:53 +02:00
abaputils . AddDefaultDashedLine ( )
2020-08-21 14:49:48 +02:00
log . Entry ( ) . Info ( "All repositories were cloned successfully" )
return nil
}
2022-06-30 10:43:33 +02:00
func checkConfiguration ( config * abapEnvironmentCloneGitRepoOptions ) error {
if config . Repositories != "" && config . RepositoryName != "" {
return errors . New ( "It is not allowed to configure the parameters `repositories`and `repositoryName` at the same time" )
}
if config . Repositories == "" && config . RepositoryName == "" {
return errors . New ( "Please provide one of the following parameters: `repositories` or `repositoryName`" )
}
return nil
}
func triggerClone ( repo abaputils . Repository , cloneConnectionDetails abaputils . ConnectionDetailsHTTP , client piperhttp . Sender ) ( abaputils . ConnectionDetailsHTTP , error , bool ) {
2020-08-21 14:49:48 +02:00
uriConnectionDetails := cloneConnectionDetails
cloneConnectionDetails . XCsrfToken = "fetch"
2020-12-08 10:31:08 +02:00
cloneConnectionDetails . URL = cloneConnectionDetails . URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Clones"
2020-08-21 14:49:48 +02:00
// 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 )
2022-06-30 10:43:33 +02:00
return uriConnectionDetails , err , false
2020-08-21 14:49:48 +02:00
}
defer resp . Body . Close ( )
2020-10-08 11:08:58 +02:00
2020-11-02 15:17:13 +02:00
log . Entry ( ) . WithField ( "StatusCode" , resp . Status ) . WithField ( "ABAP Endpoint" , cloneConnectionDetails . URL ) . Debug ( "Authentication on the ABAP system successful" )
2020-08-21 14:49:48 +02:00
uriConnectionDetails . XCsrfToken = resp . Header . Get ( "X-Csrf-Token" )
cloneConnectionDetails . XCsrfToken = uriConnectionDetails . XCsrfToken
// Trigger the Clone of a Repository
2020-11-02 15:17:13 +02:00
if repo . Name == "" {
2022-06-30 10:43:33 +02:00
return uriConnectionDetails , errors . New ( "An empty string was passed for the parameter 'repositoryName'" ) , false
2020-08-21 14:49:48 +02:00
}
2020-11-02 15:17:13 +02:00
2021-12-20 18:58:58 +02:00
jsonBody := [ ] byte ( repo . GetCloneRequestBody ( ) )
2020-08-21 14:49:48 +02:00
resp , err = abaputils . GetHTTPResponse ( "POST" , cloneConnectionDetails , jsonBody , client )
if err != nil {
2022-06-30 10:43:33 +02:00
err , alreadyCloned := handleCloneError ( resp , err , cloneConnectionDetails , client , repo )
return uriConnectionDetails , err , alreadyCloned
2020-08-21 14:49:48 +02:00
}
defer resp . Body . Close ( )
2021-12-20 18:58:58 +02:00
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" )
2020-08-21 14:49:48 +02:00
// Parse Response
var body abaputils . CloneEntity
var abapResp map [ string ] * json . RawMessage
2023-08-16 12:57:04 +02:00
bodyText , errRead := io . ReadAll ( resp . Body )
2020-08-21 14:49:48 +02:00
if errRead != nil {
2022-06-30 10:43:33 +02:00
return uriConnectionDetails , err , false
2020-08-21 14:49:48 +02:00
}
2022-07-06 14:29:04 +02:00
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
}
2020-08-21 14:49:48 +02:00
if reflect . DeepEqual ( abaputils . CloneEntity { } , body ) {
2021-12-20 18:58:58 +02:00
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" )
2020-08-21 14:49:48 +02:00
err := errors . New ( "Request to ABAP System not successful" )
2022-06-30 10:43:33 +02:00
return uriConnectionDetails , err , false
2020-08-21 14:49:48 +02:00
}
// 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
2022-02-11 11:16:40 +02:00
uriConnectionDetails . URL = uriConnectionDetails . URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull(uuid=guid'" + body . UUID + "')"
2022-06-30 10:43:33 +02:00
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
2023-07-18 09:05:53 +02:00
abaputils . AddDefaultDashedLine ( )
abaputils . AddDefaultDashedLine ( )
2022-06-30 10:43:33 +02:00
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" )
2023-07-18 09:05:53 +02:00
abaputils . AddDefaultDashedLine ( )
abaputils . AddDefaultDashedLine ( )
2022-06-30 10:43:33 +02:00
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
}
2023-07-18 09:05:53 +02:00
abaputils . AddDefaultDashedLine ( )
abaputils . AddDefaultDashedLine ( )
2022-07-06 14:29:04 +02:00
pullOptions := abapEnvironmentPullGitRepoOptions {
2022-06-30 10:43:33 +02:00
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
2020-08-21 14:49:48 +02:00
}
2020-11-02 15:17:13 +02:00
func convertCloneConfig ( config * abapEnvironmentCloneGitRepoOptions ) abaputils . AbapEnvironmentOptions {
2020-08-21 14:49:48 +02:00
subOptions := abaputils . AbapEnvironmentOptions { }
subOptions . CfAPIEndpoint = config . CfAPIEndpoint
subOptions . CfServiceInstance = config . CfServiceInstance
subOptions . CfServiceKeyName = config . CfServiceKeyName
subOptions . CfOrg = config . CfOrg
subOptions . CfSpace = config . CfSpace
subOptions . Host = config . Host
subOptions . Password = config . Password
subOptions . Username = config . Username
return subOptions
}