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 }