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