2020-08-04 17:52:28 +02:00
package abaputils
import (
"encoding/json"
2020-10-05 14:38:35 +02:00
"fmt"
2020-08-04 17:52:28 +02:00
"io/ioutil"
"reflect"
"sort"
2022-05-19 16:59:37 +02:00
"strings"
2020-08-04 17:52:28 +02:00
"time"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/pkg/errors"
)
2022-05-23 15:15:22 +02:00
const failureMessageClonePull = "Could not pull the Repository / Software Component "
2020-08-04 17:52:28 +02:00
// 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 ) {
log . Entry ( ) . Info ( "Start polling the status..." )
var status string = "R"
for {
2022-05-23 15:15:22 +02:00
pullEntity , responseStatus , err := GetStatus ( failureMessageClonePull + repositoryName , connectionDetails , client )
2020-08-04 17:52:28 +02:00
if err != nil {
2022-02-11 11:16:40 +02:00
return status , err
2020-08-04 17:52:28 +02:00
}
2022-02-11 11:16:40 +02:00
status = pullEntity . Status
2022-06-30 10:43:33 +02:00
log . Entry ( ) . WithField ( "StatusCode" , responseStatus ) . Info ( "Status: " + pullEntity . StatusDescription )
2022-02-11 11:16:40 +02:00
if pullEntity . Status != "R" {
2022-06-29 15:50:33 +02:00
printTransportLogs := true
2022-02-11 11:16:40 +02:00
if serviceContainsNewLogEntities ( connectionDetails , client ) {
PrintLogs ( repositoryName , connectionDetails , client )
2022-06-29 15:50:33 +02:00
printTransportLogs = false
}
if pullEntity . Status == "E" {
log . SetErrorCategory ( log . ErrorUndefined )
PrintLegacyLogs ( repositoryName , connectionDetails , client , true , printTransportLogs )
2020-11-02 15:17:13 +02:00
} else {
2022-06-29 15:50:33 +02:00
PrintLegacyLogs ( repositoryName , connectionDetails , client , false , printTransportLogs )
2020-11-02 15:17:13 +02:00
}
2020-08-04 17:52:28 +02:00
break
}
time . Sleep ( pollIntervall )
}
return status , nil
}
2022-02-11 11:16:40 +02:00
func serviceContainsNewLogEntities ( connectionDetails ConnectionDetailsHTTP , client piperhttp . Sender ) ( newLogEntitiesAvailable bool ) {
newLogEntitiesAvailable = false
details := connectionDetails
details . URL = details . Host + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/"
resp , err := GetHTTPResponse ( "GET" , details , nil , client )
if err != nil {
return
}
defer resp . Body . Close ( )
var entitySet EntitySetsForManageGitRepository
// Parse response
var abapResp map [ string ] * json . RawMessage
bodyText , _ := ioutil . ReadAll ( resp . Body )
json . Unmarshal ( bodyText , & abapResp )
json . Unmarshal ( * abapResp [ "d" ] , & entitySet )
for _ , entitySet := range entitySet . EntitySets {
if entitySet == "LogOverviews" || entitySet == "LogProtocols" {
return true
}
}
return
}
func PrintLogs ( repositoryName string , connectionDetails ConnectionDetailsHTTP , client piperhttp . Sender ) {
connectionDetails . URL = connectionDetails . URL + "?$expand=to_Log_Overview,to_Log_Overview/to_Log_Protocol"
2022-05-23 15:15:22 +02:00
entity , _ , err := GetStatus ( failureMessageClonePull + repositoryName , connectionDetails , client )
2022-02-11 11:16:40 +02:00
if err != nil {
return
}
if len ( entity . ToLogOverview . Results ) == 0 {
// return if no logs are available
return
}
// Sort logs
sort . SliceStable ( entity . ToLogOverview . Results , func ( i , j int ) bool {
return entity . ToLogOverview . Results [ i ] . Index < entity . ToLogOverview . Results [ j ] . Index
} )
2022-05-19 16:59:37 +02:00
// Get Lengths
phaseLength := 22 // minimum default length
for _ , logEntry := range entity . ToLogOverview . Results {
if l := len ( logEntry . Name ) ; l > phaseLength {
phaseLength = l
}
}
statusLength := 10
timestampLength := 29
// Dashed Line Length
lineLength := 10 + phaseLength + statusLength + timestampLength
2022-02-11 11:16:40 +02:00
// Print Overview
log . Entry ( ) . Infof ( "\n" )
2022-05-19 16:59:37 +02:00
dashedLine ( lineLength )
log . Entry ( ) . Infof ( "| %-" + fmt . Sprint ( phaseLength ) + "s | %" + fmt . Sprint ( statusLength ) + "s | %-" + fmt . Sprint ( timestampLength ) + "s |" , "Phase" , "Status" , "Timestamp" )
dashedLine ( lineLength )
2022-02-11 11:16:40 +02:00
for _ , logEntry := range entity . ToLogOverview . Results {
2022-05-19 16:59:37 +02:00
log . Entry ( ) . Infof ( "| %-" + fmt . Sprint ( phaseLength ) + "s | %" + fmt . Sprint ( statusLength ) + "s | %-" + fmt . Sprint ( timestampLength ) + "s |" , logEntry . Name , logEntry . Status , ConvertTime ( logEntry . Timestamp ) )
2022-02-11 11:16:40 +02:00
}
2022-05-19 16:59:37 +02:00
dashedLine ( lineLength )
2022-02-11 11:16:40 +02:00
// Print Details
for _ , logEntryForDetails := range entity . ToLogOverview . Results {
printLog ( logEntryForDetails )
}
log . Entry ( ) . Infof ( "-------------------------" )
return
}
2022-05-19 16:59:37 +02:00
func dashedLine ( i int ) {
log . Entry ( ) . Infof ( strings . Repeat ( "-" , i ) )
}
2022-02-11 11:16:40 +02:00
func printLog ( logEntry LogResultsV2 ) {
sort . SliceStable ( logEntry . ToLogProtocol . Results , func ( i , j int ) bool {
return logEntry . ToLogProtocol . Results [ i ] . ProtocolLine < logEntry . ToLogProtocol . Results [ j ] . ProtocolLine
} )
if logEntry . Status != ` Success ` {
log . Entry ( ) . Infof ( "\n" )
log . Entry ( ) . Infof ( "-------------------------" )
log . Entry ( ) . Infof ( "%s (%v)" , logEntry . Name , ConvertTime ( logEntry . Timestamp ) )
log . Entry ( ) . Infof ( "-------------------------" )
for _ , entry := range logEntry . ToLogProtocol . Results {
log . Entry ( ) . Info ( entry . Description )
}
} else {
log . Entry ( ) . Debugf ( "\n" )
log . Entry ( ) . Debugf ( "-------------------------" )
log . Entry ( ) . Debugf ( "%s (%v)" , logEntry . Name , ConvertTime ( logEntry . Timestamp ) )
log . Entry ( ) . Debugf ( "-------------------------" )
for _ , entry := range logEntry . ToLogProtocol . Results {
log . Entry ( ) . Debug ( entry . Description )
}
}
}
// PrintLegacyLogs sorts and formats the received transport and execution log of an import; Deprecated with SAP BTP, ABAP Environment release 2205
2022-06-29 15:50:33 +02:00
func PrintLegacyLogs ( repositoryName string , connectionDetails ConnectionDetailsHTTP , client piperhttp . Sender , errorOnSystem bool , includeTransportLog bool ) {
2020-08-04 17:52:28 +02:00
2022-02-11 11:16:40 +02:00
connectionDetails . URL = connectionDetails . URL + "?$expand=to_Transport_log,to_Execution_log"
2022-05-23 15:15:22 +02:00
entity , _ , err := GetStatus ( failureMessageClonePull + repositoryName , connectionDetails , client )
2022-02-11 11:16:40 +02:00
if err != nil {
return
}
2020-08-04 17:52:28 +02:00
// 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
} )
2020-11-02 15:17:13 +02:00
// Show transport and execution log if either the action was erroenous on the system or the log level is set to "debug" (verbose = true)
if errorOnSystem {
2022-06-29 15:50:33 +02:00
if includeTransportLog {
log . Entry ( ) . Info ( "-------------------------" )
log . Entry ( ) . Info ( "Transport Log" )
log . Entry ( ) . Info ( "-------------------------" )
for _ , logEntry := range entity . ToTransportLog . Results {
2020-08-04 17:52:28 +02:00
2022-06-29 15:50:33 +02:00
log . Entry ( ) . WithField ( "Timestamp" , ConvertTime ( logEntry . Timestamp ) ) . Info ( logEntry . Description )
}
2020-11-02 15:17:13 +02:00
}
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 ( "-------------------------" )
} else {
2022-06-29 15:50:33 +02:00
if includeTransportLog {
log . Entry ( ) . Debug ( "-------------------------" )
log . Entry ( ) . Debug ( "Transport Log" )
log . Entry ( ) . Debug ( "-------------------------" )
for _ , logEntry := range entity . ToTransportLog . Results {
2020-11-02 15:17:13 +02:00
2022-06-29 15:50:33 +02:00
log . Entry ( ) . WithField ( "Timestamp" , ConvertTime ( logEntry . Timestamp ) ) . Debug ( logEntry . Description )
}
2020-11-02 15:17:13 +02:00
}
2020-08-04 17:52:28 +02:00
2020-11-02 15:17:13 +02:00
log . Entry ( ) . Debug ( "-------------------------" )
log . Entry ( ) . Debug ( "Execution Log" )
log . Entry ( ) . Debug ( "-------------------------" )
for _ , logEntry := range entity . ToExecutionLog . Results {
log . Entry ( ) . WithField ( "Timestamp" , ConvertTime ( logEntry . Timestamp ) ) . Debug ( logEntry . Description )
}
log . Entry ( ) . Debug ( "-------------------------" )
2020-08-04 17:52:28 +02:00
}
2020-11-02 15:17:13 +02:00
2020-08-04 17:52:28 +02:00
}
2022-05-23 15:15:22 +02:00
func GetStatus ( failureMessage string , connectionDetails ConnectionDetailsHTTP , client piperhttp . Sender ) ( body PullEntity , status string , err error ) {
2022-02-11 11:16:40 +02:00
resp , err := GetHTTPResponse ( "GET" , connectionDetails , nil , client )
if err != nil {
log . SetErrorCategory ( log . ErrorInfrastructure )
2022-05-23 15:15:22 +02:00
err = HandleHTTPError ( resp , err , failureMessage , connectionDetails )
2022-05-25 13:57:13 +02:00
if resp != nil {
status = resp . Status
}
return body , status , err
2022-02-11 11:16:40 +02:00
}
defer resp . Body . Close ( )
// Parse response
var abapResp map [ string ] * json . RawMessage
bodyText , _ := ioutil . ReadAll ( resp . Body )
2022-06-30 10:43:33 +02:00
marshallError := json . Unmarshal ( bodyText , & abapResp )
if marshallError != nil {
return body , status , errors . Wrap ( marshallError , "Could not parse response from the ABAP Environment system" )
2022-06-29 15:50:33 +02:00
}
2022-06-30 10:43:33 +02:00
marshallError = json . Unmarshal ( * abapResp [ "d" ] , & body )
if marshallError != nil {
return body , status , errors . Wrap ( marshallError , "Could not parse response from the ABAP Environment system" )
2022-06-29 15:50:33 +02:00
}
2022-02-11 11:16:40 +02:00
if reflect . DeepEqual ( PullEntity { } , body ) {
2022-05-23 15:15:22 +02:00
log . Entry ( ) . WithField ( "StatusCode" , resp . Status ) . Error ( failureMessage )
2022-02-11 11:16:40 +02:00
log . SetErrorCategory ( log . ErrorInfrastructure )
var err = errors . New ( "Request to ABAP System not successful" )
return body , resp . Status , err
}
return body , resp . Status , nil
}
2020-10-05 14:38:35 +02:00
//GetRepositories for parsing one or multiple branches and repositories from repositories file or branchName and repositoryName configuration
2022-06-28 11:02:15 +02:00
func GetRepositories ( config * RepositoriesConfig , branchRequired bool ) ( [ ] Repository , error ) {
2020-10-05 14:38:35 +02:00
var repositories = make ( [ ] Repository , 0 )
if reflect . DeepEqual ( RepositoriesConfig { } , config ) {
2021-08-04 17:31:16 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
2020-10-05 14:38:35 +02:00
return repositories , fmt . Errorf ( "Failed to read repository configuration: %w" , errors . New ( "Eror in configuration, most likely you have entered empty or wrong configuration values. Please make sure that you have correctly specified them. For more information please read the User documentation" ) )
}
if config . RepositoryName == "" && config . BranchName == "" && config . Repositories == "" && len ( config . RepositoryNames ) == 0 {
2021-08-04 17:31:16 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
2020-10-05 14:38:35 +02:00
return repositories , fmt . Errorf ( "Failed to read repository configuration: %w" , errors . New ( "You have not specified any repository configuration. Please make sure that you have correctly specified it. For more information please read the User documentation" ) )
}
if config . Repositories != "" {
descriptor , err := ReadAddonDescriptor ( config . Repositories )
if err != nil {
2021-08-04 17:31:16 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
2020-10-05 14:38:35 +02:00
return repositories , err
}
err = CheckAddonDescriptorForRepositories ( descriptor )
if err != nil {
2021-08-04 17:31:16 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
2020-10-05 14:38:35 +02:00
return repositories , fmt . Errorf ( "Error in config file %v, %w" , config . Repositories , err )
}
repositories = descriptor . Repositories
}
if config . RepositoryName != "" && config . BranchName != "" {
repositories = append ( repositories , Repository { Name : config . RepositoryName , Branch : config . BranchName } )
}
2022-06-28 11:02:15 +02:00
if config . RepositoryName != "" && ! branchRequired {
repositories = append ( repositories , Repository { Name : config . RepositoryName , CommitID : config . CommitID } )
}
2020-10-05 14:38:35 +02:00
if len ( config . RepositoryNames ) > 0 {
for _ , repository := range config . RepositoryNames {
repositories = append ( repositories , Repository { Name : repository } )
}
}
return repositories , nil
}
2021-12-20 18:58:58 +02:00
func ( repo * Repository ) GetRequestBodyForCommitOrTag ( ) ( requestBodyString string ) {
if repo . CommitID != "" {
requestBodyString = ` , "commit_id":" ` + repo . CommitID + ` " `
} else if repo . Tag != "" {
requestBodyString = ` , "tag_name":" ` + repo . Tag + ` " `
2020-11-02 15:17:13 +02:00
}
2021-12-20 18:58:58 +02:00
return requestBodyString
}
func ( repo * Repository ) GetLogStringForCommitOrTag ( ) ( logString string ) {
if repo . CommitID != "" {
logString = ", commit '" + repo . CommitID + "'"
} else if repo . Tag != "" {
logString = ", tag '" + repo . Tag + "'"
}
return logString
}
func ( repo * Repository ) GetCloneRequestBody ( ) ( body string ) {
if repo . CommitID != "" && repo . Tag != "" {
log . Entry ( ) . WithField ( "Tag" , repo . Tag ) . WithField ( "Commit ID" , repo . CommitID ) . Info ( "The commit ID takes precedence over the tag" )
}
requestBodyString := repo . GetRequestBodyForCommitOrTag ( )
body = ` { "sc_name":" ` + repo . Name + ` ", "branch_name":" ` + repo . Branch + ` " ` + requestBodyString + ` } `
return body
}
func ( repo * Repository ) GetCloneLogString ( ) ( logString string ) {
commitOrTag := repo . GetLogStringForCommitOrTag ( )
logString = "repository / software component '" + repo . Name + "', branch '" + repo . Branch + "'" + commitOrTag
return logString
}
func ( repo * Repository ) GetPullRequestBody ( ) ( body string ) {
if repo . CommitID != "" && repo . Tag != "" {
log . Entry ( ) . WithField ( "Tag" , repo . Tag ) . WithField ( "Commit ID" , repo . CommitID ) . Info ( "The commit ID takes precedence over the tag" )
}
requestBodyString := repo . GetRequestBodyForCommitOrTag ( )
body = ` { "sc_name":" ` + repo . Name + ` " ` + requestBodyString + ` } `
return body
}
func ( repo * Repository ) GetPullLogString ( ) ( logString string ) {
commitOrTag := repo . GetLogStringForCommitOrTag ( )
logString = "repository / software component '" + repo . Name + "'" + commitOrTag
return logString
2020-11-02 15:17:13 +02:00
}
2020-08-04 17:52:28 +02:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 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" `
2022-02-11 11:16:40 +02:00
ToLogOverview AbapLogsV2 ` json:"to_Log_Overview" `
2020-08-04 17:52:28 +02:00
}
// 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" `
}
2020-08-21 14:49:48 +02:00
// CloneEntity struct for the Clone entity A4C_A2G_GHA_SC_CLONE
type CloneEntity struct {
Metadata AbapMetadata ` json:"__metadata" `
2020-12-08 10:31:08 +02:00
UUID string ` json:"uuid" `
2020-08-21 14:49:48 +02:00
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" `
}
2020-08-04 17:52:28 +02:00
// AbapLogs struct for ABAP logs
type AbapLogs struct {
Results [ ] LogResults ` json:"results" `
}
2022-02-11 11:16:40 +02:00
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 LogProtocolResults ` json:"to_Log_Protocol" `
}
type LogProtocolResults struct {
Results [ ] LogProtocol ` json:"results" `
}
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" `
}
2020-08-04 17:52:28 +02:00
// 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" `
}
2020-10-05 14:38:35 +02:00
//RepositoriesConfig struct for parsing one or multiple branches and repositories configurations
type RepositoriesConfig struct {
BranchName string
2022-06-28 11:02:15 +02:00
CommitID string
2020-10-05 14:38:35 +02:00
RepositoryName string
RepositoryNames [ ] string
Repositories string
}
2022-02-11 11:16:40 +02:00
type EntitySetsForManageGitRepository struct {
EntitySets [ ] string ` json:"EntitySets" `
}