github.com/jaylevin/jenkins-library@v1.230.4/pkg/abaputils/manageGitRepositoryUtils.go (about)

     1  package abaputils
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"reflect"
     8  	"sort"
     9  	"strings"
    10  	"time"
    11  
    12  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    13  	"github.com/SAP/jenkins-library/pkg/log"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  const failureMessageClonePull = "Could not pull the Repository / Software Component "
    18  
    19  // PollEntity periodically polls the pull/import entity to get the status. Check if the import is still running
    20  func PollEntity(repositoryName string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender, pollIntervall time.Duration) (string, error) {
    21  
    22  	log.Entry().Info("Start polling the status...")
    23  	var status string = "R"
    24  
    25  	for {
    26  		pullEntity, responseStatus, err := GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
    27  		if err != nil {
    28  			return status, err
    29  		}
    30  		status = pullEntity.Status
    31  		log.Entry().WithField("StatusCode", responseStatus).Info("Pull Status: " + pullEntity.StatusDescription)
    32  		if pullEntity.Status != "R" {
    33  
    34  			if serviceContainsNewLogEntities(connectionDetails, client) {
    35  				PrintLogs(repositoryName, connectionDetails, client)
    36  			} else {
    37  				// Fallback
    38  				if pullEntity.Status == "E" {
    39  					log.SetErrorCategory(log.ErrorUndefined)
    40  					PrintLegacyLogs(repositoryName, connectionDetails, client, true)
    41  				} else {
    42  					PrintLegacyLogs(repositoryName, connectionDetails, client, false)
    43  				}
    44  			}
    45  			break
    46  		}
    47  		time.Sleep(pollIntervall)
    48  	}
    49  	return status, nil
    50  }
    51  
    52  func serviceContainsNewLogEntities(connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender) (newLogEntitiesAvailable bool) {
    53  
    54  	newLogEntitiesAvailable = false
    55  	details := connectionDetails
    56  	details.URL = details.Host + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/"
    57  	resp, err := GetHTTPResponse("GET", details, nil, client)
    58  	if err != nil {
    59  		return
    60  	}
    61  	defer resp.Body.Close()
    62  
    63  	var entitySet EntitySetsForManageGitRepository
    64  
    65  	// Parse response
    66  	var abapResp map[string]*json.RawMessage
    67  	bodyText, _ := ioutil.ReadAll(resp.Body)
    68  
    69  	json.Unmarshal(bodyText, &abapResp)
    70  	json.Unmarshal(*abapResp["d"], &entitySet)
    71  
    72  	for _, entitySet := range entitySet.EntitySets {
    73  		if entitySet == "LogOverviews" || entitySet == "LogProtocols" {
    74  			return true
    75  		}
    76  	}
    77  	return
    78  
    79  }
    80  
    81  func PrintLogs(repositoryName string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender) {
    82  	connectionDetails.URL = connectionDetails.URL + "?$expand=to_Log_Overview,to_Log_Overview/to_Log_Protocol"
    83  	entity, _, err := GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
    84  	if err != nil {
    85  		return
    86  	}
    87  
    88  	if len(entity.ToLogOverview.Results) == 0 {
    89  		// return if no logs are available
    90  		return
    91  	}
    92  
    93  	// Sort logs
    94  	sort.SliceStable(entity.ToLogOverview.Results, func(i, j int) bool {
    95  		return entity.ToLogOverview.Results[i].Index < entity.ToLogOverview.Results[j].Index
    96  	})
    97  
    98  	// Get Lengths
    99  	phaseLength := 22 // minimum default length
   100  	for _, logEntry := range entity.ToLogOverview.Results {
   101  		if l := len(logEntry.Name); l > phaseLength {
   102  			phaseLength = l
   103  		}
   104  	}
   105  	statusLength := 10
   106  	timestampLength := 29
   107  
   108  	// Dashed Line Length
   109  	lineLength := 10 + phaseLength + statusLength + timestampLength
   110  
   111  	// Print Overview
   112  	log.Entry().Infof("\n")
   113  	dashedLine(lineLength)
   114  	log.Entry().Infof("| %-"+fmt.Sprint(phaseLength)+"s | %"+fmt.Sprint(statusLength)+"s | %-"+fmt.Sprint(timestampLength)+"s |", "Phase", "Status", "Timestamp")
   115  	dashedLine(lineLength)
   116  	for _, logEntry := range entity.ToLogOverview.Results {
   117  		log.Entry().Infof("| %-"+fmt.Sprint(phaseLength)+"s | %"+fmt.Sprint(statusLength)+"s | %-"+fmt.Sprint(timestampLength)+"s |", logEntry.Name, logEntry.Status, ConvertTime(logEntry.Timestamp))
   118  	}
   119  	dashedLine(lineLength)
   120  
   121  	// Print Details
   122  	for _, logEntryForDetails := range entity.ToLogOverview.Results {
   123  		printLog(logEntryForDetails)
   124  	}
   125  	log.Entry().Infof("-------------------------")
   126  
   127  	return
   128  }
   129  
   130  func dashedLine(i int) {
   131  	log.Entry().Infof(strings.Repeat("-", i))
   132  }
   133  
   134  func printLog(logEntry LogResultsV2) {
   135  
   136  	sort.SliceStable(logEntry.ToLogProtocol.Results, func(i, j int) bool {
   137  		return logEntry.ToLogProtocol.Results[i].ProtocolLine < logEntry.ToLogProtocol.Results[j].ProtocolLine
   138  	})
   139  
   140  	if logEntry.Status != `Success` {
   141  		log.Entry().Infof("\n")
   142  		log.Entry().Infof("-------------------------")
   143  		log.Entry().Infof("%s (%v)", logEntry.Name, ConvertTime(logEntry.Timestamp))
   144  		log.Entry().Infof("-------------------------")
   145  
   146  		for _, entry := range logEntry.ToLogProtocol.Results {
   147  			log.Entry().Info(entry.Description)
   148  		}
   149  
   150  	} else {
   151  		log.Entry().Debugf("\n")
   152  		log.Entry().Debugf("-------------------------")
   153  		log.Entry().Debugf("%s (%v)", logEntry.Name, ConvertTime(logEntry.Timestamp))
   154  		log.Entry().Debugf("-------------------------")
   155  
   156  		for _, entry := range logEntry.ToLogProtocol.Results {
   157  			log.Entry().Debug(entry.Description)
   158  		}
   159  	}
   160  
   161  }
   162  
   163  // PrintLegacyLogs sorts and formats the received transport and execution log of an import; Deprecated with SAP BTP, ABAP Environment release 2205
   164  func PrintLegacyLogs(repositoryName string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender, errorOnSystem bool) {
   165  
   166  	connectionDetails.URL = connectionDetails.URL + "?$expand=to_Transport_log,to_Execution_log"
   167  	entity, _, err := GetStatus(failureMessageClonePull+repositoryName, connectionDetails, client)
   168  	if err != nil {
   169  		return
   170  	}
   171  	// Sort logs
   172  	sort.SliceStable(entity.ToExecutionLog.Results, func(i, j int) bool {
   173  		return entity.ToExecutionLog.Results[i].Index < entity.ToExecutionLog.Results[j].Index
   174  	})
   175  
   176  	sort.SliceStable(entity.ToTransportLog.Results, func(i, j int) bool {
   177  		return entity.ToTransportLog.Results[i].Index < entity.ToTransportLog.Results[j].Index
   178  	})
   179  
   180  	// Show transport and execution log if either the action was erroenous on the system or the log level is set to "debug" (verbose = true)
   181  	if errorOnSystem {
   182  		log.Entry().Info("-------------------------")
   183  		log.Entry().Info("Transport Log")
   184  		log.Entry().Info("-------------------------")
   185  		for _, logEntry := range entity.ToTransportLog.Results {
   186  
   187  			log.Entry().WithField("Timestamp", ConvertTime(logEntry.Timestamp)).Info(logEntry.Description)
   188  		}
   189  
   190  		log.Entry().Info("-------------------------")
   191  		log.Entry().Info("Execution Log")
   192  		log.Entry().Info("-------------------------")
   193  		for _, logEntry := range entity.ToExecutionLog.Results {
   194  			log.Entry().WithField("Timestamp", ConvertTime(logEntry.Timestamp)).Info(logEntry.Description)
   195  		}
   196  		log.Entry().Info("-------------------------")
   197  	} else {
   198  		log.Entry().Debug("-------------------------")
   199  		log.Entry().Debug("Transport Log")
   200  		log.Entry().Debug("-------------------------")
   201  		for _, logEntry := range entity.ToTransportLog.Results {
   202  
   203  			log.Entry().WithField("Timestamp", ConvertTime(logEntry.Timestamp)).Debug(logEntry.Description)
   204  		}
   205  
   206  		log.Entry().Debug("-------------------------")
   207  		log.Entry().Debug("Execution Log")
   208  		log.Entry().Debug("-------------------------")
   209  		for _, logEntry := range entity.ToExecutionLog.Results {
   210  			log.Entry().WithField("Timestamp", ConvertTime(logEntry.Timestamp)).Debug(logEntry.Description)
   211  		}
   212  		log.Entry().Debug("-------------------------")
   213  	}
   214  
   215  }
   216  
   217  func GetStatus(failureMessage string, connectionDetails ConnectionDetailsHTTP, client piperhttp.Sender) (body PullEntity, status string, err error) {
   218  	resp, err := GetHTTPResponse("GET", connectionDetails, nil, client)
   219  	if err != nil {
   220  		log.SetErrorCategory(log.ErrorInfrastructure)
   221  		err = HandleHTTPError(resp, err, failureMessage, connectionDetails)
   222  		if resp != nil {
   223  			status = resp.Status
   224  		}
   225  		return body, status, err
   226  	}
   227  	defer resp.Body.Close()
   228  
   229  	// Parse response
   230  	var abapResp map[string]*json.RawMessage
   231  	bodyText, _ := ioutil.ReadAll(resp.Body)
   232  
   233  	json.Unmarshal(bodyText, &abapResp)
   234  	json.Unmarshal(*abapResp["d"], &body)
   235  
   236  	if reflect.DeepEqual(PullEntity{}, body) {
   237  		log.Entry().WithField("StatusCode", resp.Status).Error(failureMessage)
   238  		log.SetErrorCategory(log.ErrorInfrastructure)
   239  		var err = errors.New("Request to ABAP System not successful")
   240  		return body, resp.Status, err
   241  	}
   242  	return body, resp.Status, nil
   243  }
   244  
   245  //GetRepositories for parsing  one or multiple branches and repositories from repositories file or branchName and repositoryName configuration
   246  func GetRepositories(config *RepositoriesConfig) ([]Repository, error) {
   247  	var repositories = make([]Repository, 0)
   248  	if reflect.DeepEqual(RepositoriesConfig{}, config) {
   249  		log.SetErrorCategory(log.ErrorConfiguration)
   250  		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"))
   251  	}
   252  	if config.RepositoryName == "" && config.BranchName == "" && config.Repositories == "" && len(config.RepositoryNames) == 0 {
   253  		log.SetErrorCategory(log.ErrorConfiguration)
   254  		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"))
   255  	}
   256  	if config.Repositories != "" {
   257  		descriptor, err := ReadAddonDescriptor(config.Repositories)
   258  		if err != nil {
   259  			log.SetErrorCategory(log.ErrorConfiguration)
   260  			return repositories, err
   261  		}
   262  		err = CheckAddonDescriptorForRepositories(descriptor)
   263  		if err != nil {
   264  			log.SetErrorCategory(log.ErrorConfiguration)
   265  			return repositories, fmt.Errorf("Error in config file %v, %w", config.Repositories, err)
   266  		}
   267  		repositories = descriptor.Repositories
   268  	}
   269  	if config.RepositoryName != "" && config.BranchName != "" {
   270  		repositories = append(repositories, Repository{Name: config.RepositoryName, Branch: config.BranchName})
   271  	}
   272  	if len(config.RepositoryNames) > 0 {
   273  		for _, repository := range config.RepositoryNames {
   274  			repositories = append(repositories, Repository{Name: repository})
   275  		}
   276  	}
   277  	return repositories, nil
   278  }
   279  
   280  func (repo *Repository) GetRequestBodyForCommitOrTag() (requestBodyString string) {
   281  	if repo.CommitID != "" {
   282  		requestBodyString = `, "commit_id":"` + repo.CommitID + `"`
   283  	} else if repo.Tag != "" {
   284  		requestBodyString = `, "tag_name":"` + repo.Tag + `"`
   285  	}
   286  	return requestBodyString
   287  }
   288  
   289  func (repo *Repository) GetLogStringForCommitOrTag() (logString string) {
   290  	if repo.CommitID != "" {
   291  		logString = ", commit '" + repo.CommitID + "'"
   292  	} else if repo.Tag != "" {
   293  		logString = ", tag '" + repo.Tag + "'"
   294  	}
   295  	return logString
   296  }
   297  
   298  func (repo *Repository) GetCloneRequestBody() (body string) {
   299  	if repo.CommitID != "" && repo.Tag != "" {
   300  		log.Entry().WithField("Tag", repo.Tag).WithField("Commit ID", repo.CommitID).Info("The commit ID takes precedence over the tag")
   301  	}
   302  	requestBodyString := repo.GetRequestBodyForCommitOrTag()
   303  	body = `{"sc_name":"` + repo.Name + `", "branch_name":"` + repo.Branch + `"` + requestBodyString + `}`
   304  	return body
   305  }
   306  
   307  func (repo *Repository) GetCloneLogString() (logString string) {
   308  	commitOrTag := repo.GetLogStringForCommitOrTag()
   309  	logString = "repository / software component '" + repo.Name + "', branch '" + repo.Branch + "'" + commitOrTag
   310  	return logString
   311  }
   312  
   313  func (repo *Repository) GetPullRequestBody() (body string) {
   314  	if repo.CommitID != "" && repo.Tag != "" {
   315  		log.Entry().WithField("Tag", repo.Tag).WithField("Commit ID", repo.CommitID).Info("The commit ID takes precedence over the tag")
   316  	}
   317  	requestBodyString := repo.GetRequestBodyForCommitOrTag()
   318  	body = `{"sc_name":"` + repo.Name + `"` + requestBodyString + `}`
   319  	return body
   320  }
   321  
   322  func (repo *Repository) GetPullLogString() (logString string) {
   323  	commitOrTag := repo.GetLogStringForCommitOrTag()
   324  	logString = "repository / software component '" + repo.Name + "'" + commitOrTag
   325  	return logString
   326  }
   327  
   328  /****************************************
   329   *	Structs for the A4C_A2G_GHA service *
   330   ****************************************/
   331  
   332  // PullEntity struct for the Pull/Import entity A4C_A2G_GHA_SC_IMP
   333  type PullEntity struct {
   334  	Metadata          AbapMetadata `json:"__metadata"`
   335  	UUID              string       `json:"uuid"`
   336  	Namespace         string       `json:"namepsace"`
   337  	ScName            string       `json:"sc_name"`
   338  	ImportType        string       `json:"import_type"`
   339  	BranchName        string       `json:"branch_name"`
   340  	StartedByUser     string       `json:"user_name"`
   341  	Status            string       `json:"status"`
   342  	StatusDescription string       `json:"status_descr"`
   343  	CommitID          string       `json:"commit_id"`
   344  	StartTime         string       `json:"start_time"`
   345  	ChangeTime        string       `json:"change_time"`
   346  	ToExecutionLog    AbapLogs     `json:"to_Execution_log"`
   347  	ToTransportLog    AbapLogs     `json:"to_Transport_log"`
   348  	ToLogOverview     AbapLogsV2   `json:"to_Log_Overview"`
   349  }
   350  
   351  // BranchEntity struct for the Branch entity A4C_A2G_GHA_SC_BRANCH
   352  type BranchEntity struct {
   353  	Metadata      AbapMetadata `json:"__metadata"`
   354  	ScName        string       `json:"sc_name"`
   355  	Namespace     string       `json:"namepsace"`
   356  	BranchName    string       `json:"branch_name"`
   357  	ParentBranch  string       `json:"derived_from"`
   358  	CreatedBy     string       `json:"created_by"`
   359  	CreatedOn     string       `json:"created_on"`
   360  	IsActive      bool         `json:"is_active"`
   361  	CommitID      string       `json:"commit_id"`
   362  	CommitMessage string       `json:"commit_message"`
   363  	LastCommitBy  string       `json:"last_commit_by"`
   364  	LastCommitOn  string       `json:"last_commit_on"`
   365  }
   366  
   367  // CloneEntity struct for the Clone entity A4C_A2G_GHA_SC_CLONE
   368  type CloneEntity struct {
   369  	Metadata          AbapMetadata `json:"__metadata"`
   370  	UUID              string       `json:"uuid"`
   371  	ScName            string       `json:"sc_name"`
   372  	BranchName        string       `json:"branch_name"`
   373  	ImportType        string       `json:"import_type"`
   374  	Namespace         string       `json:"namepsace"`
   375  	Status            string       `json:"status"`
   376  	StatusDescription string       `json:"status_descr"`
   377  	StartedByUser     string       `json:"user_name"`
   378  	StartTime         string       `json:"start_time"`
   379  	ChangeTime        string       `json:"change_time"`
   380  }
   381  
   382  // AbapLogs struct for ABAP logs
   383  type AbapLogs struct {
   384  	Results []LogResults `json:"results"`
   385  }
   386  
   387  type AbapLogsV2 struct {
   388  	Results []LogResultsV2 `json:"results"`
   389  }
   390  
   391  type LogResultsV2 struct {
   392  	Metadata      AbapMetadata       `json:"__metadata"`
   393  	Index         int                `json:"log_index"`
   394  	Name          string             `json:"log_name"`
   395  	Status        string             `json:"type_of_found_issues"`
   396  	Timestamp     string             `json:"timestamp"`
   397  	ToLogProtocol LogProtocolResults `json:"to_Log_Protocol"`
   398  }
   399  
   400  type LogProtocolResults struct {
   401  	Results []LogProtocol `json:"results"`
   402  }
   403  
   404  type LogProtocol struct {
   405  	Metadata      AbapMetadata `json:"__metadata"`
   406  	OverviewIndex int          `json:"log_index"`
   407  	ProtocolLine  int          `json:"index_no"`
   408  	Type          string       `json:"type"`
   409  	Description   string       `json:"descr"`
   410  }
   411  
   412  // LogResults struct for Execution and Transport Log entities A4C_A2G_GHA_SC_LOG_EXE and A4C_A2G_GHA_SC_LOG_TP
   413  type LogResults struct {
   414  	Index       string `json:"index_no"`
   415  	Type        string `json:"type"`
   416  	Description string `json:"descr"`
   417  	Timestamp   string `json:"timestamp"`
   418  }
   419  
   420  //RepositoriesConfig struct for parsing one or multiple branches and repositories configurations
   421  type RepositoriesConfig struct {
   422  	BranchName      string
   423  	RepositoryName  string
   424  	RepositoryNames []string
   425  	Repositories    string
   426  }
   427  
   428  type EntitySetsForManageGitRepository struct {
   429  	EntitySets []string `json:"EntitySets"`
   430  }