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 }