github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/gctsExecuteABAPQualityChecks.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "encoding/xml" 6 "fmt" 7 "html" 8 "net/http" 9 "net/http/cookiejar" 10 "net/url" 11 "os" 12 "regexp" 13 "strconv" 14 "strings" 15 16 "github.com/SAP/jenkins-library/pkg/command" 17 piperhttp "github.com/SAP/jenkins-library/pkg/http" 18 "github.com/SAP/jenkins-library/pkg/log" 19 "github.com/SAP/jenkins-library/pkg/telemetry" 20 "github.com/pkg/errors" 21 ) 22 23 var atcFailure, aUnitFailure bool 24 25 func gctsExecuteABAPQualityChecks(config gctsExecuteABAPQualityChecksOptions, telemetryData *telemetry.CustomData) { 26 27 // for command execution use Command 28 c := command.Command{} 29 // reroute command output to logging framework 30 c.Stdout(log.Writer()) 31 c.Stderr(log.Writer()) 32 33 httpClient := &piperhttp.Client{} 34 35 // error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end 36 err := rungctsExecuteABAPQualityChecks(&config, httpClient) 37 if err != nil { 38 log.Entry().WithError(err).Fatal("step execution failed") 39 } 40 41 if aUnitFailure || atcFailure { 42 43 log.Entry().Fatal("step execution failed") 44 45 } 46 47 } 48 49 func rungctsExecuteABAPQualityChecks(config *gctsExecuteABAPQualityChecksOptions, httpClient piperhttp.Sender) error { 50 51 const localChangedObjects = "localchangedobjects" 52 const remoteChangedObjects = "remotechangedobjects" 53 const localChangedPackages = "localchangedpackages" 54 const remoteChangedPackages = "remotechangedpackages" 55 const repository = "repository" 56 const packages = "packages" 57 58 cookieJar, cookieErr := cookiejar.New(nil) 59 if cookieErr != nil { 60 return errors.Wrap(cookieErr, "creating a cookie jar failed") 61 } 62 63 maxRetries := -1 64 clientOptions := piperhttp.ClientOptions{ 65 CookieJar: cookieJar, 66 Username: config.Username, 67 Password: config.Password, 68 MaxRetries: maxRetries, 69 TransportSkipVerification: config.SkipSSLVerification, 70 } 71 72 httpClient.SetOptions(clientOptions) 73 74 log.Entry().Infof("start of gctsExecuteABAPQualityChecks step with configuration values: %v", config) 75 76 var objects []repoObject 77 var err error 78 79 log.Entry().Info("scope:", config.Scope) 80 81 switch strings.ToLower(config.Scope) { 82 case localChangedObjects: 83 objects, err = getLocalObjects(config, httpClient) 84 case remoteChangedObjects: 85 objects, err = getRemoteObjects(config, httpClient) 86 case localChangedPackages: 87 objects, err = getLocalPackages(config, httpClient) 88 case remoteChangedPackages: 89 objects, err = getRemotePackages(config, httpClient) 90 case repository: 91 objects, err = getRepositoryObjects(config, httpClient) 92 case packages: 93 objects, err = getPackages(config, httpClient) 94 default: 95 log.Entry().Info("the specified scope does not exists, the default one will be used:" + repository) 96 objects, err = getRepositoryObjects(config, httpClient) 97 } 98 99 if err != nil { 100 log.Entry().WithError(err).Fatal("failure in get objects") 101 } 102 103 if objects == nil { 104 log.Entry().Warning("no object delta was found, therefore the step execution will stop") 105 return nil 106 107 } 108 109 log.Entry().Infof("objects to be checked:") 110 for _, object := range objects { 111 log.Entry().Info(object.Type, " ", object.Object) 112 } 113 114 if config.AUnitTest { 115 116 // wrapper for execution of AUnit Test 117 err := executeAUnitTest(config, httpClient, objects) 118 119 if err != nil { 120 log.Entry().WithError(err) 121 122 } 123 124 if aUnitFailure { 125 126 log.Entry().Error("unit test(s) has/have failed! Check " + config.AUnitResultsFileName + " for more information! If you have enabled Warnings-Next-Generation Plugin, you can see the issues there!") 127 128 } else { 129 130 log.Entry().Info("AUnit test run completed successfully. If there are any results from the run, the results are saved in " + config.AUnitResultsFileName) 131 132 } 133 } 134 135 if config.AtcCheck { 136 137 // wrapper for execution of ATCChecks 138 err = executeATCCheck(config, httpClient, objects) 139 140 if err != nil { 141 log.Entry().WithError(err).Fatal("execute ATC Check failed") 142 } 143 144 if atcFailure { 145 146 log.Entry().Error(" ATC issue(s) found! Check " + config.AtcResultsFileName + " for more information! If you have enabled Warnings-Next-Generation Plugin, you can see the issues there!") 147 148 } else { 149 150 log.Entry().Info("ATCCheck test run completed successfully. If there are any results from the run, the results are saved in " + config.AtcResultsFileName) 151 152 } 153 154 } 155 156 return nil 157 158 } 159 160 func getLocalObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { 161 162 var localObjects []repoObject 163 var localObject repoObject 164 165 log.Entry().Info("get local changed objects started") 166 167 if config.Commit == "" { 168 169 return []repoObject{}, errors.Errorf("For scope: localChangedObjects you need to specify a commit") 170 171 } 172 173 history, err := getHistory(config, client) 174 if err != nil { 175 return []repoObject{}, errors.Wrap(err, "get local changed objects failed") 176 } 177 178 if len(history.Result) == 0 { 179 180 return []repoObject{}, errors.Wrap(err, "no activities (from commit - to commit) were found") 181 } 182 183 fromCommit := history.Result[0].FromCommit 184 log.Entry().Info("from Commit: ", fromCommit) 185 toCommit := history.Result[0].ToCommit 186 log.Entry().Info("to Commit: ", toCommit) 187 188 // object delta between FromCommit and ToCommit retrieved from Activities Tab in gCTS 189 resp, err := getObjectDifference(config, fromCommit, toCommit, client) 190 if err != nil { 191 return []repoObject{}, errors.Wrap(err, "get local changed objects failed") 192 } 193 194 for _, object := range resp.Objects { 195 localObject.Object = object.Name 196 localObject.Type = object.Type 197 localObjects = append(localObjects, localObject) 198 } 199 200 log.Entry().Info("get local changed objects finished") 201 202 return localObjects, nil 203 } 204 205 func getRemoteObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { 206 207 var remoteObjects []repoObject 208 var remoteObject repoObject 209 var currentRemoteCommit string 210 211 log.Entry().Info("get remote changed objects started") 212 213 if config.Commit == "" { 214 215 return []repoObject{}, errors.Errorf("For scope: remoteChangedObjects you need to specify a commit") 216 217 } 218 219 commitList, err := getCommitList(config, client) 220 221 if err != nil { 222 return []repoObject{}, errors.Wrap(err, "get remote changed objects failed") 223 } 224 225 for i, commit := range commitList.Commits { 226 if commit.ID == config.Commit { 227 currentRemoteCommit = commitList.Commits[i+1].ID 228 break 229 } 230 } 231 if currentRemoteCommit == "" { 232 return []repoObject{}, errors.New("current remote commit was not found") 233 234 } 235 log.Entry().Info("current commit in the remote repository: ", currentRemoteCommit) 236 // object delta between the commit that triggered the pipeline and the current commit in the remote repository 237 resp, err := getObjectDifference(config, currentRemoteCommit, config.Commit, client) 238 239 if err != nil { 240 return []repoObject{}, errors.Wrap(err, "get remote changed objects failed") 241 } 242 243 for _, object := range resp.Objects { 244 remoteObject.Object = object.Name 245 remoteObject.Type = object.Type 246 remoteObjects = append(remoteObjects, remoteObject) 247 } 248 249 log.Entry().Info("get remote changed objects finished") 250 251 return remoteObjects, nil 252 } 253 254 func getLocalPackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { 255 256 var localPackages []repoObject 257 var localPackage repoObject 258 259 log.Entry().Info("get local changed packages started") 260 261 if config.Commit == "" { 262 263 return []repoObject{}, errors.Errorf("For scope: localChangedPackages you need to specify a commit") 264 265 } 266 267 history, err := getHistory(config, client) 268 if err != nil { 269 return []repoObject{}, errors.Wrap(err, "get local changed objects failed") 270 } 271 272 if len(history.Result) == 0 { 273 274 return []repoObject{}, errors.Wrap(err, "no activities (from commit - to commit) were found") 275 } 276 277 fromCommit := history.Result[0].FromCommit 278 log.Entry().Info("from Commit: ", fromCommit) 279 toCommit := history.Result[0].ToCommit 280 log.Entry().Info("to Commit: ", toCommit) 281 282 // object delta between FromCommit and ToCommit retrieved from Activities Tab in gCTS 283 resp, err := getObjectDifference(config, fromCommit, config.Commit, client) 284 285 if err != nil { 286 return []repoObject{}, errors.Wrap(err, "get local changed packages failed") 287 288 } 289 290 myPackages := map[string]bool{} 291 292 // objects are resolved into packages(DEVC) 293 for _, object := range resp.Objects { 294 objInfo, err := getObjectInfo(config, client, object.Name, object.Type) 295 if err != nil { 296 return []repoObject{}, errors.Wrap(err, "get local changed packages failed") 297 } 298 if myPackages[objInfo.Devclass] { 299 300 } else { 301 myPackages[objInfo.Devclass] = true 302 localPackage.Object = objInfo.Devclass 303 localPackage.Type = "DEVC" 304 localPackages = append(localPackages, localPackage) 305 } 306 307 } 308 309 log.Entry().Info("get local changed packages finished") 310 return localPackages, nil 311 } 312 313 func getRemotePackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { 314 315 var remotePackages []repoObject 316 var remotePackage repoObject 317 var currentRemoteCommit string 318 319 log.Entry().Info("get remote changed packages started") 320 321 if config.Commit == "" { 322 323 return []repoObject{}, errors.Errorf("For scope: remoteChangedPackages you need to specify a commit") 324 325 } 326 327 commitList, err := getCommitList(config, client) 328 329 if err != nil { 330 return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") 331 } 332 333 for i, commit := range commitList.Commits { 334 if commit.ID == config.Commit { 335 currentRemoteCommit = commitList.Commits[i+1].ID 336 break 337 } 338 } 339 340 if currentRemoteCommit == "" { 341 return []repoObject{}, errors.Wrap(err, "current remote commit was not found") 342 343 } 344 log.Entry().Info("current commit in the remote repository: ", currentRemoteCommit) 345 //object delta between the commit that triggered the pipeline and the current commit in the remote repository 346 resp, err := getObjectDifference(config, currentRemoteCommit, config.Commit, client) 347 if err != nil { 348 return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") 349 } 350 351 myPackages := map[string]bool{} 352 // objects are resolved into packages(DEVC) 353 for _, object := range resp.Objects { 354 objInfo, err := getObjectInfo(config, client, object.Name, object.Type) 355 if err != nil { 356 return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") 357 } 358 if myPackages[objInfo.Devclass] { 359 360 } else { 361 myPackages[objInfo.Devclass] = true 362 remotePackage.Object = objInfo.Devclass 363 remotePackage.Type = "DEVC" 364 remotePackages = append(remotePackages, remotePackage) 365 } 366 367 } 368 log.Entry().Info("get remote changed packages finished") 369 return remotePackages, nil 370 } 371 372 func getRepositoryObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { 373 374 log.Entry().Info("get repository objects started") 375 376 var repoResp repoObjectResponse 377 378 url := config.Host + 379 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 380 "/objects?sap-client=" + config.Client 381 382 url, urlErr := addQueryToURL(url, config.QueryParameters) 383 384 if urlErr != nil { 385 386 return nil, urlErr 387 } 388 389 resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) 390 391 defer func() { 392 if resp != nil && resp.Body != nil { 393 resp.Body.Close() 394 } 395 }() 396 397 if httpErr != nil { 398 return []repoObject{}, errors.Wrap(httpErr, "could not get repository objects") 399 } else if resp == nil { 400 return []repoObject{}, errors.New("could not get repository objects: did not retrieve a HTTP response") 401 } 402 403 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoResp) 404 if parsingErr != nil { 405 return []repoObject{}, errors.Errorf("%v", parsingErr) 406 } 407 408 var repositoryObjects []repoObject 409 410 // remove object type DEVC, because it is already included in scope packages 411 // also if you run ATC Checks for DEVC together with other object types, ATC checks will run only for DEVC 412 for _, object := range repoResp.Objects { 413 414 if object.Type != "DEVC" { 415 repositoryObjects = append(repositoryObjects, object) 416 } 417 418 } 419 420 log.Entry().Info("get repository objects finished") 421 422 // all objects that are part of the local repository 423 return repositoryObjects, nil 424 } 425 426 func getPackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { 427 428 var packages []repoObject 429 430 log.Entry().Info("get packages started") 431 432 var repoResp repoObjectResponse 433 434 url := config.Host + 435 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 436 "/objects?sap-client=" + config.Client 437 438 url, urlErr := addQueryToURL(url, config.QueryParameters) 439 440 if urlErr != nil { 441 442 return nil, urlErr 443 } 444 445 resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) 446 447 defer func() { 448 if resp != nil && resp.Body != nil { 449 resp.Body.Close() 450 } 451 }() 452 453 if httpErr != nil { 454 return []repoObject{}, errors.Wrap(httpErr, "get packages failed: could not get repository objects") 455 } else if resp == nil { 456 return []repoObject{}, errors.New("get packages failed: could not get repository objects: did not retrieve a HTTP response") 457 } 458 459 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoResp) 460 if parsingErr != nil { 461 return []repoObject{}, errors.Errorf("%v", parsingErr) 462 } 463 // chose only DEVC from repository objects 464 for _, object := range repoResp.Objects { 465 466 if object.Type == "DEVC" { 467 packages = append(packages, object) 468 } 469 470 } 471 472 log.Entry().Info("get packages finished") 473 return packages, nil 474 } 475 476 func discoverServer(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (*http.Header, error) { 477 478 url := config.Host + 479 "/sap/bc/adt/core/discovery?sap-client=" + config.Client 480 481 url, urlErr := addQueryToURL(url, config.QueryParameters) 482 483 if urlErr != nil { 484 485 return nil, urlErr 486 } 487 488 header := make(http.Header) 489 header.Add("Accept", "application/atomsvc+xml") 490 header.Add("x-csrf-token", "fetch") 491 header.Add("saml2", "disabled") 492 493 disc, httpErr := client.SendRequest("GET", url, nil, header, nil) 494 495 defer func() { 496 if disc != nil && disc.Body != nil { 497 disc.Body.Close() 498 } 499 }() 500 501 if httpErr != nil { 502 return nil, errors.Wrap(httpErr, "discovery of the ABAP server failed") 503 } else if disc == nil || disc.Header == nil { 504 return nil, errors.New("discovery of the ABAP server failed: did not retrieve a HTTP response") 505 } 506 507 return &disc.Header, nil 508 } 509 510 func executeAUnitTest(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objects []repoObject) error { 511 512 log.Entry().Info("execute ABAP Unit Test started") 513 514 var innerXml string 515 var result runResult 516 517 for _, object := range objects { 518 519 switch object.Type { 520 case "CLAS": 521 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/oo/classes/` + url.QueryEscape(object.Object) + `"/>` 522 case "DEVC": 523 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/repository/informationsystem/virtualfolders?selection=package%3a` + url.QueryEscape(object.Object) + `"/>` 524 525 } 526 527 } 528 529 var xmlBody = []byte(`<?xml version="1.0" encoding="UTF-8"?> 530 <aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit"> 531 <external> 532 <coverage active="false"/> 533 </external> 534 <options> 535 <uriType value="semantic"/> 536 <testDeterminationStrategy appendAssignedTestsPreview="true" assignedTests="false" sameProgram="true"/> 537 <testRiskLevels critical="true" dangerous="true" harmless="true"/> 538 <testDurations long="true" medium="true" short="true"/> 539 <withNavigationUri enabled="false"/> 540 </options> 541 <adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core"> 542 <objectSet kind="inclusive"> 543 <adtcore:objectReferences>` + 544 innerXml + 545 `</adtcore:objectReferences> 546 </objectSet> 547 </adtcore:objectSets> 548 </aunit:runConfiguration>`) 549 550 resp, err := runAUnitTest(config, client, xmlBody) 551 if err != nil { 552 return errors.Wrap(err, "execute of Aunit test has failed") 553 } 554 555 parsingErr := piperhttp.ParseHTTPResponseBodyXML(resp, &result) 556 if parsingErr != nil { 557 log.Entry().Warning(parsingErr) 558 return nil 559 } 560 561 parsedRes, err := parseUnitResult(config, client, &result) 562 563 if err != nil { 564 log.Entry().Warning(err) 565 return nil 566 } 567 568 log.Entry().Info("execute ABAP Unit Test finished.", parsedRes.Text) 569 570 return nil 571 } 572 573 func runAUnitTest(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, xml []byte) (response *http.Response, err error) { 574 575 log.Entry().Info("run ABAP Unit Test started") 576 url := config.Host + 577 "/sap/bc/adt/abapunit/testruns?sap-client=" + config.Client 578 579 url, urlErr := addQueryToURL(url, config.QueryParameters) 580 581 if urlErr != nil { 582 583 return nil, urlErr 584 } 585 586 discHeader, discError := discoverServer(config, client) 587 588 if discError != nil { 589 return response, errors.Wrap(discError, "run of unit tests failed") 590 } 591 592 if discHeader.Get("X-Csrf-Token") == "" { 593 594 return response, errors.Errorf("could not retrieve x-csrf-token from server") 595 } 596 597 header := make(http.Header) 598 header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) 599 header.Add("Accept", "application/xml") 600 header.Add("Content-Type", "application/vnd.sap.adt.abapunit.testruns.result.v1+xml") 601 602 response, httpErr := client.SendRequest("POST", url, bytes.NewBuffer(xml), header, nil) 603 604 if httpErr != nil { 605 return response, errors.Wrap(httpErr, "run of unit tests failed") 606 } else if response == nil { 607 return response, errors.New("run of unit tests failed: did not retrieve a HTTP response") 608 } 609 610 log.Entry().Info("run ABAP Unit Test finished") 611 return response, nil 612 } 613 614 func parseUnitResult(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, aUnitRunResult *runResult) (parsedResult checkstyle, err error) { 615 616 log.Entry().Info("parse ABAP Unit Result started") 617 618 var fileName string 619 var aUnitFile file 620 var aUnitError checkstyleError 621 622 parsedResult.Version = "1.0" 623 624 for _, program := range aUnitRunResult.Program { 625 626 objectType := program.Type[0:4] 627 objectName := program.Name 628 629 //syntax error in unit test or class 630 if program.Alerts.Alert.HasSyntaxErrors == "true" { 631 632 aUnitFailure = true 633 aUnitError.Source = objectName 634 aUnitError.Severity = "error" 635 log.Entry().Info("severity: ", aUnitError.Severity) 636 aUnitError.Message = html.UnescapeString(program.Alerts.Alert.Title + " " + program.Alerts.Alert.Details.Detail.AttrText) 637 log.Entry().Info("message: ", aUnitError.Message) 638 aUnitError.Line, err = findLine(config, client, program.Alerts.Alert.Stack.StackEntry.URI, objectName, objectType) 639 log.Entry().Error("line: ", aUnitError.Line) 640 if err != nil { 641 return parsedResult, errors.Wrap(err, "parse AUnit Result failed") 642 643 } 644 fileName, err = getFileName(config, client, program.Alerts.Alert.Stack.StackEntry.URI, objectName) 645 log.Entry().Error("file path: ", aUnitError.Line) 646 if err != nil { 647 return parsedResult, errors.Wrap(err, "parse AUnit Result failed") 648 649 } 650 651 aUnitFile.Error = append(aUnitFile.Error, aUnitError) 652 aUnitError = checkstyleError{} 653 log.Entry().Error("there is a syntax error", aUnitFile) 654 } 655 656 for _, testClass := range program.TestClasses.TestClass { 657 658 for _, testMethod := range testClass.TestMethods.TestMethod { 659 660 aUnitError.Source = testClass.Name + "/" + testMethod.Name 661 662 // unit test failure 663 if len(testMethod.Alerts.Alert) > 0 { 664 665 for _, testalert := range testMethod.Alerts.Alert { 666 667 switch testalert.Severity { 668 case "fatal": 669 log.Entry().Error("unit test " + aUnitError.Source + " has failed with severity fatal") 670 aUnitFailure = true 671 aUnitError.Severity = "error" 672 case "critical": 673 log.Entry().Error("unit test " + aUnitError.Source + " has failed with severity critical") 674 aUnitFailure = true 675 aUnitError.Severity = "error" 676 case "tolerable": 677 log.Entry().Warning("unit test " + aUnitError.Source + " has failed with severity warning") 678 aUnitError.Severity = "warning" 679 default: 680 aUnitError.Severity = "info" 681 682 } 683 684 //unit test message is spread in different elements 685 for _, detail := range testalert.Details.Detail { 686 aUnitError.Message = aUnitError.Message + " " + detail.AttrText 687 for _, subdetail := range detail.Details.Detail { 688 689 aUnitError.Message = html.UnescapeString(aUnitError.Message + " " + subdetail.AttrText) 690 log.Entry().Info("message: ", aUnitError.Message) 691 } 692 693 } 694 695 aUnitError.Line, err = findLine(config, client, testalert.Stack.StackEntry.URI, objectName, objectType) 696 log.Entry().Info("line: ", aUnitError.Line) 697 if err != nil { 698 699 log.Entry().Warning(err) 700 701 } 702 703 } 704 705 aUnitFile.Error = append(aUnitFile.Error, aUnitError) 706 aUnitError = checkstyleError{} 707 708 } else { 709 710 log.Entry().Info("unit test:", aUnitError.Source, "- was successful") 711 712 } 713 714 } 715 716 fileName, err = getFileName(config, client, testClass.URI, objectName) 717 if err != nil { 718 return parsedResult, errors.Wrap(err, "parse AUnit Result failed") 719 720 } 721 } 722 723 aUnitFile.Name, err = constructPath(config, client, fileName, objectName, objectType) 724 log.Entry().Error("file path: ", aUnitFile.Name) 725 if err != nil { 726 727 return parsedResult, errors.Wrap(err, "parse AUnit Result failed") 728 } 729 parsedResult.File = append(parsedResult.File, aUnitFile) 730 aUnitFile = file{} 731 732 } 733 734 body, _ := xml.Marshal(parsedResult) 735 736 writeErr := os.WriteFile(config.AUnitResultsFileName, body, 0644) 737 738 if writeErr != nil { 739 log.Entry().Error("file AUnitResults.xml could not be created") 740 return parsedResult, fmt.Errorf("handling unit test results failed: %w", writeErr) 741 } 742 743 log.Entry().Info("parse ABAP Unit Result finished") 744 return parsedResult, nil 745 746 } 747 748 func executeATCCheck(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objects []repoObject) (error error) { 749 750 log.Entry().Info("execute ATC Check started") 751 752 var innerXml string 753 var result worklist 754 755 for _, object := range objects { 756 757 switch object.Type { 758 759 case "CLAS": 760 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/oo/classes/` + url.QueryEscape(object.Object) + `"/>` 761 case "INTF": 762 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/oo/interfaces/` + object.Object + `"/>` 763 case "DEVC": 764 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/repository/informationsystem/virtualfolders?selection=package%3a` + url.QueryEscape(object.Object) + `"/>` 765 case "FUGR": 766 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/functions/groups/` + object.Object + `/source/main"/>` 767 case "TABL": 768 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/ddic/tables/` + object.Object + `/source/main"/>` 769 case "DTEL": 770 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/ddic/dataelements/` + object.Object + `"/>` 771 case "DOMA": 772 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/ddic/domains/` + object.Object + `"/>` 773 case "MSAG": 774 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/messageclass/` + object.Object + `"/>` 775 case "PROG": 776 innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/programs/programs/` + object.Object + `/source/main"/>` 777 default: 778 log.Entry().Warning("object Type " + object.Type + " is not supported!") 779 780 } 781 782 } 783 784 var xmlBody = []byte(`<?xml version="1.0" encoding="UTF-8"?> 785 <atc:run xmlns:atc="http://www.sap.com/adt/atc" 786 maximumVerdicts="100"> 787 <objectSets xmlns:adtcore="http://www.sap.com/adt/core"> 788 <objectSet kind="inclusive"> 789 <adtcore:objectReferences>` + innerXml + 790 `</adtcore:objectReferences> 791 </objectSet> 792 </objectSets> 793 </atc:run>`) 794 795 worklist, err := getWorklist(config, client) 796 if err != nil { 797 return errors.Wrap(err, "execution of ATC Checks failed") 798 } 799 800 err = startATCRun(config, client, xmlBody, worklist) 801 802 if err != nil { 803 return errors.Wrap(err, "execution of ATC Checks failed") 804 } 805 806 resp, err := getATCRun(config, client, worklist) 807 808 if err != nil { 809 return errors.Wrap(err, "execution of ATC Checks failed") 810 } 811 812 parsingErr := piperhttp.ParseHTTPResponseBodyXML(resp, &result) 813 if parsingErr != nil { 814 log.Entry().Warning(parsingErr) 815 return nil 816 } 817 818 atcRes, err := parseATCCheckResult(config, client, &result) 819 820 if err != nil { 821 log.Entry().Error(err) 822 return errors.Wrap(err, "execution of ATC Checks failed") 823 } 824 825 log.Entry().Info("execute ATC Checks finished.", atcRes.Text) 826 827 return nil 828 829 } 830 func startATCRun(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, xml []byte, worklistID string) (err error) { 831 832 log.Entry().Info("ATC Run started") 833 834 discHeader, discError := discoverServer(config, client) 835 if discError != nil { 836 return errors.Wrap(discError, "start of ATC run failed") 837 } 838 839 if discHeader.Get("X-Csrf-Token") == "" { 840 return errors.Errorf("could not retrieve x-csrf-token from server") 841 } 842 843 header := make(http.Header) 844 header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) 845 header.Add("Accept", "application/xml") 846 847 url := config.Host + 848 "/sap/bc/adt/atc/runs?worklistId=" + worklistID + "&sap-client=" + config.Client 849 850 url, urlErr := addQueryToURL(url, config.QueryParameters) 851 852 if urlErr != nil { 853 854 return urlErr 855 } 856 857 resp, httpErr := client.SendRequest("POST", url, bytes.NewBuffer(xml), header, nil) 858 859 defer func() { 860 if resp != nil && resp.Body != nil { 861 resp.Body.Close() 862 } 863 }() 864 865 if httpErr != nil { 866 return errors.Wrap(httpErr, "start of ATC run failed") 867 } else if resp == nil { 868 return errors.New("start of ATC run failed: did not retrieve a HTTP response") 869 } 870 871 log.Entry().Info("ATC Run finished") 872 873 return nil 874 875 } 876 877 func getATCRun(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, worklistID string) (response *http.Response, err error) { 878 879 log.Entry().Info("get ATC Run Results started") 880 881 header := make(http.Header) 882 883 url := config.Host + 884 "/sap/bc/adt/atc/worklists/" + worklistID + "?sap-client=" + config.Client 885 886 url, urlErr := addQueryToURL(url, config.QueryParameters) 887 888 if urlErr != nil { 889 890 return nil, urlErr 891 } 892 893 header.Add("Accept", "application/atc.worklist.v1+xml") 894 895 resp, httpErr := client.SendRequest("GET", url, nil, header, nil) 896 897 if httpErr != nil { 898 return response, errors.Wrap(httpErr, "get ATC run failed") 899 } else if resp == nil { 900 return response, errors.New("get ATC run failed: did not retrieve a HTTP response") 901 } 902 log.Entry().Info("get ATC Run Results finished") 903 return resp, nil 904 905 } 906 907 func getWorklist(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (worklistID string, error error) { 908 909 url := config.Host + 910 "/sap/bc/adt/atc/worklists?checkVariant=" + config.AtcVariant + "&sap-client=" + config.Client 911 discHeader, discError := discoverServer(config, client) 912 913 url, urlErr := addQueryToURL(url, config.QueryParameters) 914 915 if urlErr != nil { 916 917 return worklistID, urlErr 918 } 919 920 if discError != nil { 921 return worklistID, errors.Wrap(discError, "get worklist failed") 922 } 923 924 if discHeader.Get("X-Csrf-Token") == "" { 925 return worklistID, errors.Errorf("could not retrieve x-csrf-token from server") 926 } 927 928 header := make(http.Header) 929 header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) 930 header.Add("Accept", "*/*") 931 932 resp, httpErr := client.SendRequest("POST", url, nil, header, nil) 933 defer func() { 934 if resp != nil && resp.Body != nil { 935 resp.Body.Close() 936 } 937 }() 938 939 if httpErr != nil { 940 return worklistID, errors.Wrap(httpErr, "get worklist failed") 941 } else if resp == nil { 942 return worklistID, errors.New("get worklist failed: did not retrieve a HTTP response") 943 } 944 location := resp.Header["Location"][0] 945 locationSlice := strings.Split(location, "/") 946 worklistID = locationSlice[len(locationSlice)-1] 947 log.Entry().Info("worklist id for ATC check: ", worklistID) 948 949 return worklistID, nil 950 } 951 952 func parseATCCheckResult(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, response *worklist) (atcResults checkstyle, error error) { 953 954 log.Entry().Info("parse ATC Check Result started") 955 956 var atcFile file 957 var subObject string 958 var aTCUnitError checkstyleError 959 960 atcResults.Version = "1.0" 961 962 for _, object := range response.Objects.Object { 963 964 objectType := object.Type 965 objectName := object.Name 966 967 for _, atcworklist := range object.Findings.Finding { 968 969 log.Entry().Info("there is atc finding for object type: ", objectType+" object name: "+objectName) 970 971 path, err := url.PathUnescape(atcworklist.Location) 972 973 if err != nil { 974 return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") 975 976 } 977 978 if len(atcworklist.Atcfinding) > 0 { 979 980 priority, err := strconv.Atoi(atcworklist.Priority) 981 982 if err != nil { 983 return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") 984 985 } 986 987 switch priority { 988 case 1: 989 atcFailure = true 990 aTCUnitError.Severity = "error" 991 log.Entry().Error("atc issue with priority: 1 ") 992 case 2: 993 atcFailure = true 994 aTCUnitError.Severity = "error" 995 log.Entry().Error("atc issue with priority: 2 ") 996 case 3: 997 aTCUnitError.Severity = "warning" 998 log.Entry().Warning("atc issue with priority: 3 ") 999 default: 1000 aTCUnitError.Severity = "info" 1001 log.Entry().Info("atc issue with low priority ") 1002 } 1003 1004 log.Entry().Error("severity: ", aTCUnitError.Severity) 1005 1006 if aTCUnitError.Line == "" { 1007 1008 aTCUnitError.Line, err = findLine(config, client, path, objectName, objectType) 1009 log.Entry().Info("line: ", aTCUnitError.Line) 1010 1011 if err != nil { 1012 log.Entry().Info(path) 1013 log.Entry().Warning(err) 1014 1015 } 1016 1017 } 1018 1019 if subObject != "" { 1020 aTCUnitError.Source = objectName + "/" + strings.ToUpper(subObject) 1021 } else { 1022 aTCUnitError.Source = objectName 1023 } 1024 1025 aTCUnitError.Message = html.UnescapeString(atcworklist.CheckTitle + " " + atcworklist.MessageTitle) 1026 log.Entry().Info("message: ", aTCUnitError.Message) 1027 atcFile.Error = append(atcFile.Error, aTCUnitError) 1028 aTCUnitError = checkstyleError{} 1029 } 1030 1031 if atcFile.Error[0].Message != "" { 1032 1033 fileName, err := getFileName(config, client, path, objectName) 1034 1035 if err != nil { 1036 return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") 1037 } 1038 1039 atcFile.Name, err = constructPath(config, client, fileName, objectName, objectType) 1040 log.Entry().Info("file path: ", atcFile.Name) 1041 if err != nil { 1042 return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") 1043 } 1044 atcResults.File = append(atcResults.File, atcFile) 1045 atcFile = file{} 1046 1047 } 1048 1049 } 1050 } 1051 1052 atcBody, _ := xml.Marshal(atcResults) 1053 1054 writeErr := os.WriteFile(config.AtcResultsFileName, atcBody, 0644) 1055 1056 if writeErr != nil { 1057 log.Entry().Error("ATCResults.xml could not be created") 1058 return atcResults, fmt.Errorf("handling atc results failed: %w", writeErr) 1059 } 1060 log.Entry().Info("parsing ATC check results to CheckStyle has finished.") 1061 return atcResults, writeErr 1062 } 1063 1064 func constructPath(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, fileName string, objectName string, objectType string) (filePath string, error error) { 1065 1066 targetDir, err := getTargetDir(config, client) 1067 if err != nil { 1068 return filePath, errors.Wrap(err, "path could not be constructed") 1069 1070 } 1071 1072 filePath = config.Workspace + "/" + targetDir + "/objects/" + strings.ToUpper(objectType) + "/" + strings.ToUpper(objectName) + "/" + fileName 1073 return filePath, nil 1074 1075 } 1076 1077 func findLine(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, path string, objectName string, objectType string) (line string, error error) { 1078 1079 regexLine := regexp.MustCompile(`.start=\d*`) 1080 regexMethod := regexp.MustCompile(`.name=[a-zA-Z0-9_-]*;`) 1081 1082 readableSource, err := checkReadableSource(config, client) 1083 1084 if err != nil { 1085 1086 return line, errors.Wrap(err, "could not find line in source code") 1087 1088 } 1089 1090 fileName, err := getFileName(config, client, path, objectName) 1091 1092 if err != nil { 1093 1094 return line, err 1095 1096 } 1097 1098 filePath, err := constructPath(config, client, fileName, objectName, objectType) 1099 if err != nil { 1100 return line, errors.Wrap(err, objectType+"/"+objectName+"could not find line in source code") 1101 1102 } 1103 1104 var absLine int 1105 if readableSource { 1106 1107 // the error line that we get from UnitTest Run or ATC Check is not aligned for the readable source, we need to calculated it 1108 rawfile, err := os.ReadFile(filePath) 1109 1110 if err != nil { 1111 1112 return line, errors.Wrapf(err, "could not find object in the workspace of your CI/CD tool ") 1113 } 1114 1115 file := string(rawfile) 1116 1117 splittedfile := strings.Split(file, "\n") 1118 1119 // CLAS/OSO - is unique identifier for protection section in CLAS 1120 if strings.Contains(path, "CLAS/OSO") { 1121 1122 for l, line := range splittedfile { 1123 1124 if strings.Contains(line, "protected section.") { 1125 absLine = l 1126 break 1127 } 1128 1129 } 1130 1131 // CLAS/OM - is unique identifier for method section in CLAS 1132 } else if strings.Contains(path, "CLAS/OM") { 1133 1134 methodName := regexMethod.FindString(path) 1135 1136 if methodName != "" { 1137 methodName = methodName[len(`.name=`) : len(methodName)-1] 1138 1139 } 1140 1141 for line, linecontent := range splittedfile { 1142 1143 if strings.Contains(linecontent, "method"+" "+methodName) { 1144 absLine = line 1145 break 1146 } 1147 1148 } 1149 1150 // CLAS/OSI - is unique identifier for private section in CLAS 1151 } else if strings.Contains(path, "CLAS/OSI") { 1152 1153 for line, linecontent := range splittedfile { 1154 1155 if strings.Contains(linecontent, "private section.") { 1156 absLine = line 1157 break 1158 } 1159 1160 } 1161 1162 } 1163 1164 errLine := regexLine.FindString(path) 1165 1166 if errLine != "" { 1167 1168 errLine, err := strconv.Atoi(errLine[len(`.start=`):]) 1169 if err == nil { 1170 line = strconv.Itoa(absLine + errLine) 1171 1172 } 1173 1174 } 1175 1176 } else { 1177 // classic format 1178 errLine := regexLine.FindString(path) 1179 if errLine != "" { 1180 line = errLine[len(`.start=`):] 1181 1182 } 1183 1184 } 1185 1186 return line, nil 1187 } 1188 func getFileName(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, path string, objName string) (fileName string, error error) { 1189 1190 readableSource, err := checkReadableSource(config, client) 1191 if err != nil { 1192 return fileName, errors.Wrap(err, "get file name has failed") 1193 1194 } 1195 1196 path, err = url.PathUnescape(path) 1197 1198 var fileExtension string 1199 fileExtensionLength := 30 - len(objName) 1200 for i := 0; i < fileExtensionLength; i++ { 1201 fileExtension += "=" 1202 } 1203 1204 if err != nil { 1205 return fileName, errors.Wrap(err, "get file name has failed") 1206 1207 } 1208 1209 // INTERFACES 1210 regexInterface := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/interfaces\/\w*`) 1211 intf := regexInterface.FindString(path) 1212 if intf != "" && fileName == "" { 1213 1214 if readableSource { 1215 1216 fileName = strings.ToLower(objName) + ".intf.abap" 1217 } else { 1218 fileName = "REPS " + strings.ToUpper(objName) + fileExtension + "IU.abap" 1219 } 1220 1221 } 1222 // CLASSES DEFINITIONS 1223 regexClasDef := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/definitions\/`) 1224 clasDef := regexClasDef.FindString(path) 1225 if clasDef != "" && fileName == "" { 1226 1227 if readableSource { 1228 1229 fileName = strings.ToLower(objName) + ".clas.definitions.abap" 1230 } else { 1231 fileName = "CINC " + objName + fileExtension + "CCDEF.abap" 1232 } 1233 1234 } 1235 1236 // CLASSES IMPLEMENTATIONS 1237 regexClasImpl := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/implementations\/`) 1238 clasImpl := regexClasImpl.FindString(path) 1239 if clasImpl != "" && fileName == "" { 1240 1241 if readableSource { 1242 1243 fileName = strings.ToLower(objName) + ".clas.implementations.abap" 1244 } else { 1245 fileName = "CINC " + objName + fileExtension + "CCIMP.abap" 1246 } 1247 1248 } 1249 1250 // CLASSES MACROS 1251 regexClasMacro := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/macros\/`) 1252 clasMacro := regexClasMacro.FindString(path) 1253 if clasMacro != "" && fileName == "" { 1254 1255 if readableSource { 1256 1257 fileName = strings.ToLower(objName) + ".clas.macros.abap" 1258 } else { 1259 fileName = "CINC " + objName + fileExtension + "CCMAC.abap" 1260 } 1261 1262 } 1263 1264 // TEST CLASSES 1265 regexTestClass := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*#?\/?\w*\/?testclass`) 1266 testClass := regexTestClass.FindString(path) 1267 if testClass != "" && fileName == "" { 1268 1269 if readableSource { 1270 1271 fileName = strings.ToLower(objName) + ".clas.testclasses.abap" 1272 } else { 1273 fileName = "CINC " + objName + fileExtension + "CCAU.abap" 1274 } 1275 1276 } 1277 1278 // CLASS PROTECTED 1279 regexClasProtected := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OSO`) 1280 classProtected := regexClasProtected.FindString(path) 1281 if classProtected != "" && fileName == "" { 1282 1283 if readableSource { 1284 1285 fileName = strings.ToLower(objName) + ".clas.abap" 1286 } else { 1287 fileName = "CPRO " + objName + ".abap" 1288 } 1289 1290 } 1291 1292 // CLASS PRIVATE 1293 regexClasPrivate := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OSI`) 1294 classPrivate := regexClasPrivate.FindString(path) 1295 if classPrivate != "" && fileName == "" { 1296 1297 if readableSource { 1298 1299 fileName = strings.ToLower(objName) + ".clas.abap" 1300 } else { 1301 fileName = "CPRI " + objName + ".abap" 1302 } 1303 1304 } 1305 1306 // CLASS METHOD 1307 regexClasMethod := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OM`) 1308 classMethod := regexClasMethod.FindString(path) 1309 if classMethod != "" && fileName == "" { 1310 1311 if readableSource { 1312 1313 fileName = strings.ToLower(objName) + ".clas.abap" 1314 } else { 1315 1316 regexmethodName := regexp.MustCompile(`name=\w*`) 1317 methodName := regexmethodName.FindString(path) 1318 1319 fileName = "METH " + methodName[len(`name=`):] + ".abap" 1320 } 1321 1322 } 1323 1324 // CLASS PUBLIC 1325 regexClasPublic := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#start`) 1326 classPublic := regexClasPublic.FindString(path) 1327 if classPublic != "" && fileName == "" { 1328 1329 if readableSource { 1330 1331 fileName = strings.ToLower(objName) + ".clas.abap" 1332 } else { 1333 fileName = "CPUB " + objName + ".abap" 1334 } 1335 1336 } 1337 1338 // FUNCTION INCLUDE 1339 regexFuncIncl := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/includes/\w*`) 1340 1341 funcIncl := regexFuncIncl.FindString(path) 1342 if funcIncl != "" && fileName == "" { 1343 1344 regexSubObj := regexp.MustCompile(`includes\/\w*`) 1345 subObject := regexSubObj.FindString(path) 1346 subObject = subObject[len(`includes/`):] 1347 1348 if readableSource { 1349 1350 fileName = strings.ToLower(objName) + ".fugr." + strings.ToLower(subObject) + ".reps.abap" 1351 } else { 1352 fileName = "REPS " + strings.ToUpper(subObject) + ".abap" 1353 } 1354 1355 } 1356 1357 // FUNCTION GROUP 1358 regexFuncGr := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/source\/main`) 1359 1360 funcGr := regexFuncGr.FindString(path) 1361 if funcGr != "" && fileName == "" { 1362 1363 if readableSource { 1364 1365 fileName = strings.ToLower(objName) + ".fugr.sapl" + strings.ToLower(objName) + ".reps.abap" 1366 } else { 1367 fileName = "REPS SAPL" + objName + ".abap" 1368 } 1369 1370 } 1371 1372 // FUNCTION MODULE 1373 regexFuncMod := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/fmodules/\w*`) 1374 funcMod := regexFuncMod.FindString(path) 1375 if funcMod != "" && fileName == "" { 1376 1377 regexSubObj := regexp.MustCompile(`includes\/\w*`) 1378 subObject := regexSubObj.FindString(path) 1379 subObject = subObject[len(`includes/`):] 1380 1381 if readableSource { 1382 1383 fileName = strings.ToLower(subObject) + ".func.abap" 1384 } else { 1385 fileName = "FUNC " + subObject + ".abap" 1386 } 1387 1388 } 1389 // CLAS 1390 regexClas := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/` + strings.ToLower(objName)) 1391 clas := regexClas.FindString(path) 1392 if clas != "" && fileName == "" { 1393 if readableSource { 1394 1395 fileName = strings.ToLower(objName) + ".clas.abap" 1396 } else { 1397 1398 fileName = "CPUB " + objName + ".abap" 1399 } 1400 1401 } 1402 1403 // PROGRAM 1404 regexProg := regexp.MustCompile(`\/sap\/bc\/adt\/programs\/programs\/` + strings.ToLower(objName)) 1405 prog := regexProg.FindString(path) 1406 if prog != "" && fileName == "" { 1407 1408 fileName = "REPS " + objName + ".abap" 1409 1410 } 1411 1412 // TABLES 1413 regexTab := regexp.MustCompile(`\/sap\/bc\/adt\/ddic\/tables\/` + strings.ToLower(objName)) 1414 tab := regexTab.FindString(path) 1415 if tab != "" && fileName == "" { 1416 1417 fileName = "TABL " + objName + ".asx.json" 1418 1419 } 1420 1421 return fileName, nil 1422 1423 } 1424 1425 func getTargetDir(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (string, error) { 1426 1427 var targetDir string 1428 1429 repository, err := getRepo(config, client) 1430 1431 if err != nil { 1432 return targetDir, err 1433 } 1434 1435 for _, config := range repository.Result.Config { 1436 if config.Key == "VCS_TARGET_DIR" { 1437 targetDir = config.Value 1438 } 1439 } 1440 1441 return targetDir, nil 1442 1443 } 1444 1445 func checkReadableSource(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (readableSource bool, error error) { 1446 1447 repoLayout, err := getRepositoryLayout(config, client) 1448 if err != nil { 1449 return readableSource, errors.Wrap(err, "could not check readable source format") 1450 } 1451 1452 if repoLayout.Layout.ReadableSource == "true" || repoLayout.Layout.ReadableSource == "only" || repoLayout.Layout.ReadableSource == "all" { 1453 1454 readableSource = true 1455 1456 } else { 1457 1458 readableSource = false 1459 1460 } 1461 1462 return readableSource, nil 1463 } 1464 1465 func getRepo(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (repositoryResponse, error) { 1466 1467 var repositoryResp repositoryResponse 1468 url := config.Host + 1469 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 1470 "?sap-client=" + config.Client 1471 1472 url, urlErr := addQueryToURL(url, config.QueryParameters) 1473 1474 if urlErr != nil { 1475 1476 return repositoryResp, urlErr 1477 } 1478 1479 resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) 1480 defer func() { 1481 if resp != nil && resp.Body != nil { 1482 resp.Body.Close() 1483 } 1484 }() 1485 1486 if httpErr != nil { 1487 return repositoryResponse{}, errors.Wrap(httpErr, "could not get repository") 1488 } else if resp == nil { 1489 return repositoryResponse{}, errors.New("could not get repository: did not retrieve a HTTP response") 1490 } 1491 1492 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repositoryResp) 1493 if parsingErr != nil { 1494 return repositoryResponse{}, errors.Errorf("%v", parsingErr) 1495 } 1496 1497 return repositoryResp, nil 1498 1499 } 1500 1501 func getRepositoryLayout(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (layoutResponse, error) { 1502 1503 var repoLayoutResponse layoutResponse 1504 url := config.Host + 1505 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 1506 "/layout?sap-client=" + config.Client 1507 1508 url, urlErr := addQueryToURL(url, config.QueryParameters) 1509 1510 if urlErr != nil { 1511 1512 return repoLayoutResponse, urlErr 1513 } 1514 1515 resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) 1516 1517 defer func() { 1518 if resp != nil && resp.Body != nil { 1519 resp.Body.Close() 1520 } 1521 }() 1522 1523 if httpErr != nil { 1524 return layoutResponse{}, errors.Wrap(httpErr, "could not get repository layout") 1525 } else if resp == nil { 1526 return layoutResponse{}, errors.New("could not get repository layout: did not retrieve a HTTP response") 1527 } 1528 1529 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoLayoutResponse) 1530 if parsingErr != nil { 1531 return layoutResponse{}, errors.Errorf("%v", parsingErr) 1532 } 1533 1534 return repoLayoutResponse, nil 1535 } 1536 1537 func getCommitList(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (commitResponse, error) { 1538 1539 var commitResp commitResponse 1540 url := config.Host + 1541 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 1542 "/getCommit?sap-client=" + config.Client 1543 1544 url, urlErr := addQueryToURL(url, config.QueryParameters) 1545 if urlErr != nil { 1546 1547 return commitResp, urlErr 1548 } 1549 1550 resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) 1551 1552 defer func() { 1553 if resp != nil && resp.Body != nil { 1554 resp.Body.Close() 1555 } 1556 }() 1557 1558 if httpErr != nil { 1559 return commitResponse{}, errors.Wrap(httpErr, "get repository history failed") 1560 } else if resp == nil { 1561 return commitResponse{}, errors.New("get repository history failed: did not retrieve a HTTP response") 1562 } 1563 1564 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &commitResp) 1565 if parsingErr != nil { 1566 return commitResponse{}, errors.Errorf("%v", parsingErr) 1567 } 1568 1569 return commitResp, nil 1570 } 1571 1572 func getObjectDifference(config *gctsExecuteABAPQualityChecksOptions, fromCommit string, toCommit string, client piperhttp.Sender) (objectsResponse, error) { 1573 var objectResponse objectsResponse 1574 1575 url := config.Host + 1576 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 1577 "/compareCommits?fromCommit=" + fromCommit + "&toCommit=" + toCommit + "&sap-client=" + config.Client 1578 1579 url, urlErr := addQueryToURL(url, config.QueryParameters) 1580 1581 if urlErr != nil { 1582 1583 return objectResponse, urlErr 1584 } 1585 1586 resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) 1587 1588 defer func() { 1589 if resp != nil && resp.Body != nil { 1590 resp.Body.Close() 1591 } 1592 }() 1593 1594 if httpErr != nil { 1595 return objectsResponse{}, errors.Wrap(httpErr, "get object difference failed") 1596 } else if resp == nil { 1597 return objectsResponse{}, errors.New("get object difference failed: did not retrieve a HTTP response") 1598 } 1599 1600 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &objectResponse) 1601 if parsingErr != nil { 1602 return objectsResponse{}, errors.Errorf("%v", parsingErr) 1603 } 1604 log.Entry().Info("get object differences: ", objectResponse.Objects) 1605 return objectResponse, nil 1606 } 1607 1608 func getObjectInfo(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objectName string, objectType string) (objectInfo, error) { 1609 1610 var objectMetInfoResponse objectInfo 1611 url := config.Host + 1612 "/sap/bc/cts_abapvcs/objects/" + objectType + "/" + objectName + 1613 "?sap-client=" + config.Client 1614 1615 url, urlErr := addQueryToURL(url, config.QueryParameters) 1616 1617 if urlErr != nil { 1618 1619 return objectMetInfoResponse, urlErr 1620 } 1621 1622 resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) 1623 1624 defer func() { 1625 if resp != nil && resp.Body != nil { 1626 resp.Body.Close() 1627 } 1628 }() 1629 1630 if httpErr != nil { 1631 return objectInfo{}, errors.Wrap(httpErr, "resolve package failed") 1632 } else if resp == nil { 1633 return objectInfo{}, errors.New("resolve package failed: did not retrieve a HTTP response") 1634 } 1635 1636 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &objectMetInfoResponse) 1637 if parsingErr != nil { 1638 return objectInfo{}, errors.Errorf("%v", parsingErr) 1639 } 1640 return objectMetInfoResponse, nil 1641 1642 } 1643 1644 func getHistory(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (historyResponse, error) { 1645 1646 var historyResp historyResponse 1647 url := config.Host + 1648 "/sap/bc/cts_abapvcs/repository/" + config.Repository + "/getHistory?sap-client=" + config.Client 1649 1650 url, urlErr := addQueryToURL(url, config.QueryParameters) 1651 1652 if urlErr != nil { 1653 1654 return historyResp, urlErr 1655 } 1656 1657 resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) 1658 1659 defer func() { 1660 if resp != nil && resp.Body != nil { 1661 resp.Body.Close() 1662 } 1663 }() 1664 if httpErr != nil { 1665 return historyResponse{}, errors.Wrap(httpErr, "get history failed") 1666 } else if resp == nil { 1667 return historyResponse{}, errors.New("get history failed: did not retrieve a HTTP response") 1668 } 1669 1670 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &historyResp) 1671 if parsingErr != nil { 1672 return historyResponse{}, errors.Errorf("%v", parsingErr) 1673 } 1674 1675 return historyResp, nil 1676 } 1677 1678 type worklist struct { 1679 XMLName xml.Name `xml:"worklist"` 1680 Text string `xml:",chardata"` 1681 ID string `xml:"id,attr"` 1682 Timestamp string `xml:"timestamp,attr"` 1683 UsedObjectSet string `xml:"usedObjectSet,attr"` 1684 ObjectSetIsComplete string `xml:"objectSetIsComplete,attr"` 1685 Atcworklist string `xml:"atcworklist,attr"` 1686 ObjectSets struct { 1687 Text string `xml:",chardata"` 1688 ObjectSet []struct { 1689 Text string `xml:",chardata"` 1690 Name string `xml:"name,attr"` 1691 Title string `xml:"title,attr"` 1692 Kind string `xml:"kind,attr"` 1693 } `xml:"objectSet"` 1694 } `xml:"objectSets"` 1695 Objects struct { 1696 Text string `xml:",chardata"` 1697 Object []struct { 1698 Text string `xml:",chardata"` 1699 URI string `xml:"uri,attr"` 1700 Type string `xml:"type,attr"` 1701 Name string `xml:"name,attr"` 1702 PackageName string `xml:"packageName,attr"` 1703 Author string `xml:"author,attr"` 1704 Atcobject string `xml:"atcobject,attr"` 1705 Adtcore string `xml:"adtcore,attr"` 1706 Findings struct { 1707 Text string `xml:",chardata"` 1708 Finding []struct { 1709 Text string `xml:",chardata"` 1710 URI string `xml:"uri,attr"` 1711 Location string `xml:"location,attr"` 1712 Processor string `xml:"processor,attr"` 1713 LastChangedBy string `xml:"lastChangedBy,attr"` 1714 Priority string `xml:"priority,attr"` 1715 CheckId string `xml:"checkId,attr"` 1716 CheckTitle string `xml:"checkTitle,attr"` 1717 MessageId string `xml:"messageId,attr"` 1718 MessageTitle string `xml:"messageTitle,attr"` 1719 ExemptionApproval string `xml:"exemptionApproval,attr"` 1720 ExemptionKind string `xml:"exemptionKind,attr"` 1721 Checksum string `xml:"checksum,attr"` 1722 QuickfixInfo string `xml:"quickfixInfo,attr"` 1723 Atcfinding string `xml:"atcfinding,attr"` 1724 Link struct { 1725 Text string `xml:",chardata"` 1726 Href string `xml:"href,attr"` 1727 Rel string `xml:"rel,attr"` 1728 Type string `xml:"type,attr"` 1729 Atom string `xml:"atom,attr"` 1730 } `xml:"link"` 1731 Quickfixes struct { 1732 Text string `xml:",chardata"` 1733 Manual string `xml:"manual,attr"` 1734 Automatic string `xml:"automatic,attr"` 1735 Pseudo string `xml:"pseudo,attr"` 1736 } `xml:"quickfixes"` 1737 } `xml:"finding"` 1738 } `xml:"findings"` 1739 } `xml:"object"` 1740 } `xml:"objects"` 1741 } 1742 1743 type runResult struct { 1744 XMLName xml.Name `xml:"runResult"` 1745 Text string `xml:",chardata"` 1746 Aunit string `xml:"aunit,attr"` 1747 Program []struct { 1748 Text string `xml:",chardata"` 1749 URI string `xml:"uri,attr"` 1750 Type string `xml:"type,attr"` 1751 Name string `xml:"name,attr"` 1752 URIType string `xml:"uriType,attr"` 1753 Adtcore string `xml:"adtcore,attr"` 1754 Alerts struct { 1755 Text string `xml:",chardata"` 1756 Alert struct { 1757 Text string `xml:",chardata"` 1758 HasSyntaxErrors string `xml:"hasSyntaxErrors,attr"` 1759 Kind string `xml:"kind,attr"` 1760 Severity string `xml:"severity,attr"` 1761 Title string `xml:"title"` 1762 Details struct { 1763 Text string `xml:",chardata"` 1764 Detail struct { 1765 Text string `xml:",chardata"` 1766 AttrText string `xml:"text,attr"` 1767 } `xml:"detail"` 1768 } `xml:"details"` 1769 Stack struct { 1770 Text string `xml:",chardata"` 1771 StackEntry struct { 1772 Text string `xml:",chardata"` 1773 URI string `xml:"uri,attr"` 1774 Description string `xml:"description,attr"` 1775 } `xml:"stackEntry"` 1776 } `xml:"stack"` 1777 } `xml:"alert"` 1778 } `xml:"alerts"` 1779 1780 TestClasses struct { 1781 Text string `xml:",chardata"` 1782 TestClass []struct { 1783 Text string `xml:",chardata"` 1784 URI string `xml:"uri,attr"` 1785 Type string `xml:"type,attr"` 1786 Name string `xml:"name,attr"` 1787 URIType string `xml:"uriType,attr"` 1788 NavigationURI string `xml:"navigationUri,attr"` 1789 DurationCategory string `xml:"durationCategory,attr"` 1790 RiskLevel string `xml:"riskLevel,attr"` 1791 TestMethods struct { 1792 Text string `xml:",chardata"` 1793 TestMethod []struct { 1794 Text string `xml:",chardata"` 1795 URI string `xml:"uri,attr"` 1796 Type string `xml:"type,attr"` 1797 Name string `xml:"name,attr"` 1798 ExecutionTime string `xml:"executionTime,attr"` 1799 URIType string `xml:"uriType,attr"` 1800 NavigationURI string `xml:"navigationUri,attr"` 1801 Unit string `xml:"unit,attr"` 1802 Alerts struct { 1803 Text string `xml:",chardata"` 1804 Alert []struct { 1805 Text string `xml:",chardata"` 1806 Kind string `xml:"kind,attr"` 1807 Severity string `xml:"severity,attr"` 1808 Title string `xml:"title"` 1809 Details struct { 1810 Text string `xml:",chardata"` 1811 Detail []struct { 1812 Text string `xml:",chardata"` 1813 AttrText string `xml:"text,attr"` 1814 Details struct { 1815 Text string `xml:",chardata"` 1816 Detail []struct { 1817 Text string `xml:",chardata"` 1818 AttrText string `xml:"text,attr"` 1819 } `xml:"detail"` 1820 } `xml:"details"` 1821 } `xml:"detail"` 1822 } `xml:"details"` 1823 Stack struct { 1824 Text string `xml:",chardata"` 1825 StackEntry struct { 1826 Text string `xml:",chardata"` 1827 URI string `xml:"uri,attr"` 1828 Type string `xml:"type,attr"` 1829 Name string `xml:"name,attr"` 1830 Description string `xml:"description,attr"` 1831 } `xml:"stackEntry"` 1832 } `xml:"stack"` 1833 } `xml:"alert"` 1834 } `xml:"alerts"` 1835 } `xml:"testMethod"` 1836 } `xml:"testMethods"` 1837 } `xml:"testClass"` 1838 } `xml:"testClasses"` 1839 } `xml:"program"` 1840 } 1841 1842 type gctsException struct { 1843 Message string `json:"message"` 1844 Description string `json:"description"` 1845 Code int `json:"code"` 1846 } 1847 1848 type gctsLogs struct { 1849 Time int `json:"time"` 1850 User string `json:"user"` 1851 Section string `json:"section"` 1852 Action string `json:"action"` 1853 Severity string `json:"severity"` 1854 Message string `json:"message"` 1855 Code string `json:"code"` 1856 } 1857 1858 type commit struct { 1859 ID string `json:"id"` 1860 } 1861 1862 type commitResponse struct { 1863 Commits []commit `json:"commits"` 1864 ErrorLog []gctsLogs `json:"errorLog"` 1865 Log []gctsLogs `json:"log"` 1866 Exception gctsException `json:"exception"` 1867 } 1868 1869 type objectInfo struct { 1870 Pgmid string `json:"pgmid"` 1871 Object string `json:"object"` 1872 ObjName string `json:"objName"` 1873 Srcsystem string `json:"srcsystem"` 1874 Author string `json:"author"` 1875 Devclass string `json:"devclass"` 1876 } 1877 1878 type repoConfig struct { 1879 Key string `json:"key"` 1880 Value string `json:"value"` 1881 Cprivate string `json:"cprivate"` 1882 Cprotected string `json:"cprotected"` 1883 Cvisible string `json:"cvisible"` 1884 Category string `json:"category"` 1885 Scope string `json:"scope"` 1886 ChangedAt float64 `json:"changeAt"` 1887 ChangedBy string `json:"changedBy"` 1888 } 1889 1890 type repository struct { 1891 Rid string `json:"rid"` 1892 Name string `json:"name"` 1893 Role string `json:"role"` 1894 Type string `json:"type"` 1895 Vsid string `json:"vsid"` 1896 PrivateFlag string `json:"privateFlag"` 1897 Status string `json:"status"` 1898 Branch string `json:"branch"` 1899 Url string `json:"url"` 1900 CreatedBy string `json:"createdBy"` 1901 CreatedDate string `json:"createdDate"` 1902 Config []repoConfig `json:"config"` 1903 Objects int `json:"objects"` 1904 CurrentCommit string `json:"currentCommit"` 1905 } 1906 1907 type repositoryResponse struct { 1908 Result repository `json:"result"` 1909 Exception gctsException `json:"exception"` 1910 } 1911 1912 type objects struct { 1913 Name string `json:"name"` 1914 Type string `json:"type"` 1915 Action string `json:"action"` 1916 } 1917 type objectsResponse struct { 1918 Objects []objects `json:"objects"` 1919 Log []gctsLogs `json:"log"` 1920 Exception gctsException `json:"exception"` 1921 ErrorLogs []gctsLogs `json:"errorLog"` 1922 } 1923 1924 type repoObject struct { 1925 Pgmid string `json:"pgmid"` 1926 Object string `json:"object"` 1927 Type string `json:"type"` 1928 Description string `json:"description"` 1929 } 1930 1931 type repoObjectResponse struct { 1932 Objects []repoObject `json:"objects"` 1933 Log []gctsLogs `json:"log"` 1934 Exception gctsException `json:"exception"` 1935 ErrorLogs []gctsLogs `json:"errorLog"` 1936 } 1937 1938 type layout struct { 1939 FormatVersion int `json:"formatVersion"` 1940 Format string `json:"format"` 1941 ObjectStorage string `json:"objectStorage"` 1942 MetaInformation string `json:"metaInformation"` 1943 TableContent string `json:"tableContent"` 1944 Subdirectory string `json:"subdirectory"` 1945 ReadableSource string `json:"readableSource"` 1946 KeepClient string `json:"keepClient"` 1947 } 1948 1949 type layoutResponse struct { 1950 Layout layout `json:"layout"` 1951 Log []gctsLogs `json:"log"` 1952 Exception string `json:"exception"` 1953 ErrorLogs []gctsLogs `json:"errorLog"` 1954 } 1955 1956 type history struct { 1957 Rid string `json:"rid"` 1958 CheckoutTime int `json:"checkoutTime"` 1959 FromCommit string `json:"fromCommit"` 1960 ToCommit string `json:"toCommit"` 1961 Caller string `json:"caller"` 1962 Type string `json:"type"` 1963 } 1964 1965 type historyResponse struct { 1966 Result []history `xml:"result"` 1967 Exception string `json:"exception"` 1968 } 1969 1970 type checkstyleError struct { 1971 Text string `xml:",chardata"` 1972 Message string `xml:"message,attr"` 1973 Source string `xml:"source,attr"` 1974 Line string `xml:"line,attr"` 1975 Severity string `xml:"severity,attr"` 1976 } 1977 1978 type file struct { 1979 Text string `xml:",chardata"` 1980 Name string `xml:"name,attr"` 1981 Error []checkstyleError `xml:"error"` 1982 } 1983 1984 type checkstyle struct { 1985 XMLName xml.Name `xml:"checkstyle"` 1986 Text string `xml:",chardata"` 1987 Version string `xml:"version,attr"` 1988 File []file `xml:"file"` 1989 }