2020-02-04 12:43:27 +02:00
package cmd
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/cookiejar"
2020-04-08 12:43:41 +02:00
"reflect"
"sort"
"strconv"
2020-02-04 12:43:27 +02:00
"strings"
"time"
"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"
)
func abapEnvironmentPullGitRepo ( config abapEnvironmentPullGitRepoOptions , telemetryData * telemetry . CustomData ) error {
2020-04-08 12:43:41 +02:00
// Determine the host, user and password, either via the input parameters or via a cloud foundry service key
2020-02-04 12:43:27 +02:00
c := command . Command { }
2020-04-08 12:43:41 +02:00
connectionDetails , errorGetInfo := getAbapCommunicationArrangementInfo ( config , & c )
if errorGetInfo != nil {
log . Entry ( ) . WithError ( errorGetInfo ) . Fatal ( "Parameters for the ABAP Connection not available" )
2020-02-04 12:43:27 +02:00
}
2020-04-08 12:43:41 +02:00
// Configuring the HTTP Client and CookieJar
2020-02-04 12:43:27 +02:00
client := piperhttp . Client { }
2020-04-08 12:43:41 +02:00
cookieJar , errorCookieJar := cookiejar . New ( nil )
if errorCookieJar != nil {
log . Entry ( ) . WithError ( errorCookieJar ) . Fatal ( "Could not create a Cookie Jar" )
}
2020-02-04 12:43:27 +02:00
clientOptions := piperhttp . ClientOptions {
2020-03-31 10:56:57 +02:00
MaxRequestDuration : 60 * time . Second ,
2020-03-23 16:02:22 +02:00
CookieJar : cookieJar ,
Username : connectionDetails . User ,
Password : connectionDetails . Password ,
2020-02-04 12:43:27 +02:00
}
client . SetOptions ( clientOptions )
2020-05-07 15:51:11 +02:00
pollIntervall := 10 * time . Second
2020-02-04 12:43:27 +02:00
2020-05-07 15:51:11 +02:00
log . Entry ( ) . Infof ( "Start pulling %v repositories" , len ( config . RepositoryNames ) )
for _ , repositoryName := range config . RepositoryNames {
2020-02-04 12:43:27 +02:00
2020-05-07 15:51:11 +02:00
log . Entry ( ) . Info ( "-------------------------" )
log . Entry ( ) . Info ( "Start pulling " + repositoryName )
log . Entry ( ) . Info ( "-------------------------" )
2020-02-04 12:43:27 +02:00
2020-05-07 15:51:11 +02:00
// Triggering the Pull of the repository into the ABAP Environment system
uriConnectionDetails , errorTriggerPull := triggerPull ( repositoryName , connectionDetails , & client )
if errorTriggerPull != nil {
log . Entry ( ) . WithError ( errorTriggerPull ) . Fatal ( "Pull of " + repositoryName + " failed on the ABAP System" )
}
// Polling the status of the repository import on the ABAP Environment system
status , errorPollEntity := pollEntity ( repositoryName , uriConnectionDetails , & client , pollIntervall )
if errorPollEntity != nil {
log . Entry ( ) . WithError ( errorPollEntity ) . Fatal ( "Pull of " + repositoryName + " failed on the ABAP System" )
}
if status == "E" {
log . Entry ( ) . Fatal ( "Pull of " + repositoryName + " failed on the ABAP System" )
}
log . Entry ( ) . Info ( repositoryName + " was pulled successfully" )
}
log . Entry ( ) . Info ( "-------------------------" )
log . Entry ( ) . Info ( "All repositories were pulled successfully" )
2020-02-04 12:43:27 +02:00
return nil
}
2020-05-07 15:51:11 +02:00
func triggerPull ( repositoryName string , pullConnectionDetails connectionDetailsHTTP , client piperhttp . Sender ) ( connectionDetailsHTTP , error ) {
2020-02-04 12:43:27 +02:00
uriConnectionDetails := pullConnectionDetails
uriConnectionDetails . URL = ""
pullConnectionDetails . XCsrfToken = "fetch"
// Loging into the ABAP System - getting the x-csrf-token and cookies
2020-04-08 12:43:41 +02:00
resp , err := getHTTPResponse ( "HEAD" , pullConnectionDetails , nil , client )
2020-02-04 12:43:27 +02:00
if err != nil {
2020-04-08 12:43:41 +02:00
err = handleHTTPError ( resp , err , "Authentication on the ABAP system failed" , pullConnectionDetails )
2020-02-04 12:43:27 +02:00
return uriConnectionDetails , err
}
2020-03-17 11:54:48 +02:00
defer resp . Body . Close ( )
2020-02-04 12:43:27 +02:00
log . Entry ( ) . WithField ( "StatusCode" , resp . Status ) . WithField ( "ABAP Endpoint" , pullConnectionDetails . URL ) . Info ( "Authentication on the ABAP system successfull" )
uriConnectionDetails . XCsrfToken = resp . Header . Get ( "X-Csrf-Token" )
pullConnectionDetails . XCsrfToken = uriConnectionDetails . XCsrfToken
// Trigger the Pull of a Repository
2020-05-07 15:51:11 +02:00
if repositoryName == "" {
2020-04-08 12:43:41 +02:00
return uriConnectionDetails , errors . New ( "An empty string was passed for the parameter 'repositoryName'" )
}
2020-05-07 15:51:11 +02:00
jsonBody := [ ] byte ( ` { "sc_name":" ` + repositoryName + ` "} ` )
2020-02-04 12:43:27 +02:00
resp , err = getHTTPResponse ( "POST" , pullConnectionDetails , jsonBody , client )
if err != nil {
2020-05-07 15:51:11 +02:00
err = handleHTTPError ( resp , err , "Could not pull the Repository / Software Component " + repositoryName , uriConnectionDetails )
2020-02-04 12:43:27 +02:00
return uriConnectionDetails , err
}
2020-03-17 11:54:48 +02:00
defer resp . Body . Close ( )
2020-05-07 15:51:11 +02:00
log . Entry ( ) . WithField ( "StatusCode" , resp . Status ) . WithField ( "repositoryName" , repositoryName ) . Info ( "Triggered Pull of Repository / Software Component" )
2020-02-04 12:43:27 +02:00
// Parse Response
var body abapEntity
var abapResp map [ string ] * json . RawMessage
2020-04-08 12:43:41 +02:00
bodyText , errRead := ioutil . ReadAll ( resp . Body )
if errRead != nil {
return uriConnectionDetails , err
}
2020-02-04 12:43:27 +02:00
json . Unmarshal ( bodyText , & abapResp )
json . Unmarshal ( * abapResp [ "d" ] , & body )
2020-04-08 12:43:41 +02:00
if reflect . DeepEqual ( abapEntity { } , body ) {
2020-05-07 15:51:11 +02:00
log . Entry ( ) . WithField ( "StatusCode" , resp . Status ) . WithField ( "repositoryName" , repositoryName ) . Error ( "Could not pull the Repository / Software Component" )
2020-04-08 12:43:41 +02:00
err := errors . New ( "Request to ABAP System not successful" )
2020-02-04 12:43:27 +02:00
return uriConnectionDetails , err
}
2020-04-08 12:43:41 +02:00
expandLog := "?$expand=to_Execution_log,to_Transport_log"
uriConnectionDetails . URL = body . Metadata . URI + expandLog
2020-02-04 12:43:27 +02:00
return uriConnectionDetails , nil
}
2020-05-07 15:51:11 +02:00
func pollEntity ( repositoryName string , connectionDetails connectionDetailsHTTP , client piperhttp . Sender , pollIntervall time . Duration ) ( string , error ) {
2020-02-04 12:43:27 +02:00
log . Entry ( ) . Info ( "Start polling the status..." )
var status string = "R"
for {
var resp , err = getHTTPResponse ( "GET" , connectionDetails , nil , client )
if err != nil {
2020-05-07 15:51:11 +02:00
err = handleHTTPError ( resp , err , "Could not pull the Repository / Software Component " + repositoryName , connectionDetails )
2020-02-04 12:43:27 +02:00
return "" , err
}
2020-03-17 11:54:48 +02:00
defer resp . Body . Close ( )
2020-02-04 12:43:27 +02:00
// Parse response
var body abapEntity
bodyText , _ := ioutil . ReadAll ( resp . Body )
var abapResp map [ string ] * json . RawMessage
json . Unmarshal ( bodyText , & abapResp )
json . Unmarshal ( * abapResp [ "d" ] , & body )
2020-04-08 12:43:41 +02:00
if reflect . DeepEqual ( abapEntity { } , body ) {
2020-05-07 15:51:11 +02:00
log . Entry ( ) . WithField ( "StatusCode" , resp . Status ) . WithField ( "repositoryName" , repositoryName ) . Error ( "Could not pull the Repository / Software Component" )
2020-02-04 12:43:27 +02:00
var err = errors . New ( "Request to ABAP System not successful" )
return "" , err
}
status = body . Status
log . Entry ( ) . WithField ( "StatusCode" , resp . Status ) . Info ( "Pull Status: " + body . StatusDescr )
if body . Status != "R" {
2020-04-08 12:43:41 +02:00
printLogs ( body )
2020-02-04 12:43:27 +02:00
break
}
time . Sleep ( pollIntervall )
}
return status , nil
}
func getAbapCommunicationArrangementInfo ( config abapEnvironmentPullGitRepoOptions , c execRunner ) ( connectionDetailsHTTP , error ) {
var connectionDetails connectionDetailsHTTP
var error error
if config . Host != "" {
// Host, User and Password are directly provided
connectionDetails . URL = "https://" + config . Host + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull"
connectionDetails . User = config . Username
connectionDetails . Password = config . Password
} else {
if config . CfAPIEndpoint == "" || config . CfOrg == "" || config . CfSpace == "" || config . CfServiceInstance == "" || config . CfServiceKey == "" {
var err = errors . New ( "Parameters missing. Please provide EITHER the Host of the ABAP server OR the Cloud Foundry ApiEndpoint, Organization, Space, Service Instance and a corresponding Service Key for the Communication Scenario SAP_COM_0510" )
return connectionDetails , err
}
// Url, User and Password should be read from a cf service key
var abapServiceKey , error = readCfServiceKey ( config , c )
if error != nil {
return connectionDetails , errors . Wrap ( error , "Read service key failed" )
}
connectionDetails . URL = abapServiceKey . URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull"
connectionDetails . User = abapServiceKey . Abap . Username
connectionDetails . Password = abapServiceKey . Abap . Password
}
return connectionDetails , error
}
func readCfServiceKey ( config abapEnvironmentPullGitRepoOptions , c execRunner ) ( serviceKey , error ) {
var abapServiceKey serviceKey
2020-05-06 13:35:40 +02:00
c . Stderr ( log . Writer ( ) )
2020-02-04 12:43:27 +02:00
// Logging into the Cloud Foundry via CF CLI
log . Entry ( ) . WithField ( "cfApiEndpoint" , config . CfAPIEndpoint ) . WithField ( "cfSpace" , config . CfSpace ) . WithField ( "cfOrg" , config . CfOrg ) . WithField ( "User" , config . Username ) . Info ( "Cloud Foundry parameters: " )
cfLoginSlice := [ ] string { "login" , "-a" , config . CfAPIEndpoint , "-u" , config . Username , "-p" , config . Password , "-o" , config . CfOrg , "-s" , config . CfSpace }
2020-04-08 12:43:41 +02:00
errorRunExecutable := c . RunExecutable ( "cf" , cfLoginSlice ... )
if errorRunExecutable != nil {
2020-02-04 12:43:27 +02:00
log . Entry ( ) . Error ( "Login at cloud foundry failed." )
2020-04-08 12:43:41 +02:00
return abapServiceKey , errorRunExecutable
2020-02-04 12:43:27 +02:00
}
// Reading the Service Key via CF CLI
var serviceKeyBytes bytes . Buffer
c . Stdout ( & serviceKeyBytes )
cfReadServiceKeySlice := [ ] string { "service-key" , config . CfServiceInstance , config . CfServiceKey }
2020-04-08 12:43:41 +02:00
errorRunExecutable = c . RunExecutable ( "cf" , cfReadServiceKeySlice ... )
2020-02-04 12:43:27 +02:00
var serviceKeyJSON string
if len ( serviceKeyBytes . String ( ) ) > 0 {
var lines [ ] string = strings . Split ( serviceKeyBytes . String ( ) , "\n" )
serviceKeyJSON = strings . Join ( lines [ 2 : ] , "" )
}
2020-04-08 12:43:41 +02:00
if errorRunExecutable != nil {
return abapServiceKey , errorRunExecutable
2020-02-04 12:43:27 +02:00
}
log . Entry ( ) . WithField ( "cfServiceInstance" , config . CfServiceInstance ) . WithField ( "cfServiceKey" , config . CfServiceKey ) . Info ( "Read service key for service instance" )
json . Unmarshal ( [ ] byte ( serviceKeyJSON ) , & abapServiceKey )
if abapServiceKey == ( serviceKey { } ) {
return abapServiceKey , errors . New ( "Parsing the service key failed" )
}
2020-04-08 12:43:41 +02:00
return abapServiceKey , errorRunExecutable
2020-02-04 12:43:27 +02:00
}
func getHTTPResponse ( requestType string , connectionDetails connectionDetailsHTTP , body [ ] byte , client piperhttp . Sender ) ( * http . Response , error ) {
header := make ( map [ string ] [ ] string )
header [ "Content-Type" ] = [ ] string { "application/json" }
header [ "Accept" ] = [ ] string { "application/json" }
header [ "x-csrf-token" ] = [ ] string { connectionDetails . XCsrfToken }
req , err := client . SendRequest ( requestType , connectionDetails . URL , bytes . NewBuffer ( body ) , header , nil )
return req , err
}
2020-04-08 12:43:41 +02:00
func handleHTTPError ( resp * http . Response , err error , message string , connectionDetails connectionDetailsHTTP ) error {
2020-03-17 11:54:48 +02:00
if resp == nil {
2020-04-08 12:43:41 +02:00
// Response is nil in case of a timeout
2020-03-17 11:54:48 +02:00
log . Entry ( ) . WithError ( err ) . WithField ( "ABAP Endpoint" , connectionDetails . URL ) . Error ( "Request failed" )
} else {
log . Entry ( ) . WithField ( "StatusCode" , resp . Status ) . Error ( message )
2020-04-08 12:43:41 +02:00
// Include the error message of the ABAP Environment system, if available
var abapErrorResponse abapError
bodyText , readError := ioutil . ReadAll ( resp . Body )
if readError != nil {
return readError
}
var abapResp map [ string ] * json . RawMessage
json . Unmarshal ( bodyText , & abapResp )
json . Unmarshal ( * abapResp [ "error" ] , & abapErrorResponse )
if ( abapError { } ) != abapErrorResponse {
log . Entry ( ) . WithField ( "ErrorCode" , abapErrorResponse . Code ) . Error ( abapErrorResponse . Message . Value )
abapError := errors . New ( abapErrorResponse . Code + " - " + abapErrorResponse . Message . Value )
err = errors . Wrap ( abapError , err . Error ( ) )
}
2020-03-17 11:54:48 +02:00
resp . Body . Close ( )
}
2020-04-08 12:43:41 +02:00
return err
}
func printLogs ( entity abapEntity ) {
// Sort logs
sort . SliceStable ( entity . ToExecutionLog . Results , func ( i , j int ) bool {
return entity . ToExecutionLog . Results [ i ] . Index < entity . ToExecutionLog . Results [ j ] . Index
} )
sort . SliceStable ( entity . ToTransportLog . Results , func ( i , j int ) bool {
return entity . ToTransportLog . Results [ i ] . Index < entity . ToTransportLog . Results [ j ] . Index
} )
log . Entry ( ) . Info ( "-------------------------" )
log . Entry ( ) . Info ( "Transport Log" )
log . Entry ( ) . Info ( "-------------------------" )
for _ , logEntry := range entity . ToTransportLog . Results {
log . Entry ( ) . WithField ( "Timestamp" , convertTime ( logEntry . Timestamp ) ) . Info ( logEntry . Description )
}
log . Entry ( ) . Info ( "-------------------------" )
log . Entry ( ) . Info ( "Execution Log" )
log . Entry ( ) . Info ( "-------------------------" )
for _ , logEntry := range entity . ToExecutionLog . Results {
log . Entry ( ) . WithField ( "Timestamp" , convertTime ( logEntry . Timestamp ) ) . Info ( logEntry . Description )
}
log . Entry ( ) . Info ( "-------------------------" )
}
func convertTime ( logTimeStamp string ) time . Time {
// The ABAP Environment system returns the date in the following format: /Date(1585576807000+0000)/
seconds := strings . TrimPrefix ( strings . TrimSuffix ( logTimeStamp , "000+0000)/" ) , "/Date(" )
n , error := strconv . ParseInt ( seconds , 10 , 64 )
if error != nil {
return time . Unix ( 0 , 0 ) . UTC ( )
}
t := time . Unix ( n , 0 ) . UTC ( )
return t
2020-03-17 11:54:48 +02:00
}
2020-02-04 12:43:27 +02:00
type abapEntity struct {
Metadata abapMetadata ` json:"__metadata" `
UUID string ` json:"uuid" `
ScName string ` json:"sc_name" `
Namespace string ` json:"namepsace" `
Status string ` json:"status" `
StatusDescr string ` json:"status_descr" `
2020-04-08 12:43:41 +02:00
ToExecutionLog abapLogs ` json:"to_Execution_log" `
ToTransportLog abapLogs ` json:"to_Transport_log" `
2020-02-04 12:43:27 +02:00
}
type abapMetadata struct {
URI string ` json:"uri" `
}
2020-04-08 12:43:41 +02:00
type abapLogs struct {
Results [ ] logResults ` json:"results" `
}
type logResults struct {
Index string ` json:"index_no" `
Type string ` json:"type" `
Description string ` json:"descr" `
Timestamp string ` json:"timestamp" `
}
2020-02-04 12:43:27 +02:00
type serviceKey struct {
Abap abapConenction ` json:"abap" `
Binding abapBinding ` json:"binding" `
Systemid string ` json:"systemid" `
URL string ` json:"url" `
}
type deferred struct {
URI string ` json:"uri" `
}
type abapConenction struct {
CommunicationArrangementID string ` json:"communication_arrangement_id" `
CommunicationScenarioID string ` json:"communication_scenario_id" `
CommunicationSystemID string ` json:"communication_system_id" `
Password string ` json:"password" `
Username string ` json:"username" `
}
type abapBinding struct {
Env string ` json:"env" `
ID string ` json:"id" `
Type string ` json:"type" `
Version string ` json:"version" `
}
type connectionDetailsHTTP struct {
User string ` json:"user" `
Password string ` json:"password" `
URL string ` json:"url" `
XCsrfToken string ` json:"xcsrftoken" `
}
2020-04-08 12:43:41 +02:00
type abapError struct {
Code string ` json:"code" `
Message abapErrorMessage ` json:"message" `
}
type abapErrorMessage struct {
Lang string ` json:"lang" `
Value string ` json:"value" `
}