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 }