github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/abaputils/manageGitRepositoryUtils.go (about)

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