mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
6ff4143b08
* Execute checkout & pull when already cloned * Revert * Disallow config overload * Add custom error handler for clone * Implement new pull parameters * Add tests * Formatting * Rename Param * Add comment * Add docu * Adapt testst to merge * Fix Unit Test
444 lines
16 KiB
Go
444 lines
16 KiB
Go
package abaputils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"reflect"
|
|
"sort"
|
|
"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 "
|
|
|
|
// 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 {
|
|
pullEntity, responseStatus, err := GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
|
|
if err != nil {
|
|
return status, err
|
|
}
|
|
status = pullEntity.Status
|
|
log.Entry().WithField("StatusCode", responseStatus).Info("Status: " + pullEntity.StatusDescription)
|
|
if pullEntity.Status != "R" {
|
|
printTransportLogs := true
|
|
if serviceContainsNewLogEntities(connectionDetails, client) {
|
|
PrintLogs(repositoryName, connectionDetails, client)
|
|
printTransportLogs = false
|
|
}
|
|
if pullEntity.Status == "E" {
|
|
log.SetErrorCategory(log.ErrorUndefined)
|
|
PrintLegacyLogs(repositoryName, connectionDetails, client, true, printTransportLogs)
|
|
} else {
|
|
PrintLegacyLogs(repositoryName, connectionDetails, client, false, printTransportLogs)
|
|
}
|
|
break
|
|
}
|
|
time.Sleep(pollIntervall)
|
|
}
|
|
return status, nil
|
|
}
|
|
|
|
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"
|
|
entity, _, err := GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
|
|
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
|
|
})
|
|
|
|
// 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
|
|
|
|
// Print Overview
|
|
log.Entry().Infof("\n")
|
|
dashedLine(lineLength)
|
|
log.Entry().Infof("| %-"+fmt.Sprint(phaseLength)+"s | %"+fmt.Sprint(statusLength)+"s | %-"+fmt.Sprint(timestampLength)+"s |", "Phase", "Status", "Timestamp")
|
|
dashedLine(lineLength)
|
|
for _, logEntry := range entity.ToLogOverview.Results {
|
|
log.Entry().Infof("| %-"+fmt.Sprint(phaseLength)+"s | %"+fmt.Sprint(statusLength)+"s | %-"+fmt.Sprint(timestampLength)+"s |", logEntry.Name, logEntry.Status, ConvertTime(logEntry.Timestamp))
|
|
}
|
|
dashedLine(lineLength)
|
|
|
|
// Print Details
|
|
for _, logEntryForDetails := range entity.ToLogOverview.Results {
|
|
printLog(logEntryForDetails)
|
|
}
|
|
log.Entry().Infof("-------------------------")
|
|
|
|
return
|
|
}
|
|
|
|
func dashedLine(i int) {
|
|
log.Entry().Infof(strings.Repeat("-", i))
|
|
}
|
|
|
|
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
|
|
func PrintLegacyLogs(repositoryName string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender, errorOnSystem bool, includeTransportLog bool) {
|
|
|
|
connectionDetails.URL = connectionDetails.URL + "?$expand=to_Transport_log,to_Execution_log"
|
|
entity, _, err := GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// 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
|
|
})
|
|
|
|
// 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 {
|
|
if includeTransportLog {
|
|
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("-------------------------")
|
|
} else {
|
|
if includeTransportLog {
|
|
log.Entry().Debug("-------------------------")
|
|
log.Entry().Debug("Transport Log")
|
|
log.Entry().Debug("-------------------------")
|
|
for _, logEntry := range entity.ToTransportLog.Results {
|
|
|
|
log.Entry().WithField("Timestamp", ConvertTime(logEntry.Timestamp)).Debug(logEntry.Description)
|
|
}
|
|
}
|
|
|
|
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("-------------------------")
|
|
}
|
|
|
|
}
|
|
|
|
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, _ := ioutil.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
|
|
}
|
|
|
|
//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)
|
|
if reflect.DeepEqual(RepositoriesConfig{}, config) {
|
|
log.SetErrorCategory(log.ErrorConfiguration)
|
|
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 {
|
|
log.SetErrorCategory(log.ErrorConfiguration)
|
|
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 {
|
|
log.SetErrorCategory(log.ErrorConfiguration)
|
|
return repositories, err
|
|
}
|
|
err = CheckAddonDescriptorForRepositories(descriptor)
|
|
if err != nil {
|
|
log.SetErrorCategory(log.ErrorConfiguration)
|
|
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})
|
|
}
|
|
if config.RepositoryName != "" && !branchRequired {
|
|
repositories = append(repositories, Repository{Name: config.RepositoryName, CommitID: config.CommitID})
|
|
}
|
|
if len(config.RepositoryNames) > 0 {
|
|
for _, repository := range config.RepositoryNames {
|
|
repositories = append(repositories, Repository{Name: repository})
|
|
}
|
|
}
|
|
return repositories, nil
|
|
}
|
|
|
|
func (repo *Repository) GetRequestBodyForCommitOrTag() (requestBodyString string) {
|
|
if repo.CommitID != "" {
|
|
requestBodyString = `, "commit_id":"` + repo.CommitID + `"`
|
|
} else if repo.Tag != "" {
|
|
requestBodyString = `, "tag_name":"` + repo.Tag + `"`
|
|
}
|
|
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
|
|
}
|
|
|
|
/****************************************
|
|
* 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 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"`
|
|
}
|
|
|
|
// 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"`
|
|
}
|