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

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