mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
1897 lines
54 KiB
Go
1897 lines
54 KiB
Go
|
package cmd
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/xml"
|
||
|
"fmt"
|
||
|
"html"
|
||
|
"io/ioutil"
|
||
|
"net/http"
|
||
|
"net/http/cookiejar"
|
||
|
"net/url"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/SAP/jenkins-library/pkg/command"
|
||
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||
|
"github.com/SAP/jenkins-library/pkg/log"
|
||
|
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
var atcFailure, aUnitFailure bool
|
||
|
|
||
|
func gctsExecuteABAPQualityChecks(config gctsExecuteABAPQualityChecksOptions, telemetryData *telemetry.CustomData) {
|
||
|
|
||
|
// for command execution use Command
|
||
|
c := command.Command{}
|
||
|
// reroute command output to logging framework
|
||
|
c.Stdout(log.Writer())
|
||
|
c.Stderr(log.Writer())
|
||
|
|
||
|
httpClient := &piperhttp.Client{}
|
||
|
// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
|
||
|
err := rungctsExecuteABAPQualityChecks(&config, httpClient)
|
||
|
if err != nil {
|
||
|
log.Entry().WithError(err).Fatal("step execution failed")
|
||
|
}
|
||
|
|
||
|
if aUnitFailure || atcFailure {
|
||
|
|
||
|
log.Entry().Fatal("step execution failed")
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func rungctsExecuteABAPQualityChecks(config *gctsExecuteABAPQualityChecksOptions, httpClient piperhttp.Sender) error {
|
||
|
|
||
|
const localChangedObjects = "localchangedobjects"
|
||
|
const remoteChangedObjects = "remotechangedobjects"
|
||
|
const localChangedPackages = "localchangedpackages"
|
||
|
const remoteChangedPackages = "remotechangedpackages"
|
||
|
const repository = "repository"
|
||
|
const packages = "packages"
|
||
|
|
||
|
cookieJar, cookieErr := cookiejar.New(nil)
|
||
|
if cookieErr != nil {
|
||
|
return errors.Wrap(cookieErr, "creating a cookie jar failed")
|
||
|
}
|
||
|
|
||
|
maxRetries := -1
|
||
|
clientOptions := piperhttp.ClientOptions{
|
||
|
CookieJar: cookieJar,
|
||
|
Username: config.Username,
|
||
|
Password: config.Password,
|
||
|
MaxRetries: maxRetries,
|
||
|
}
|
||
|
|
||
|
httpClient.SetOptions(clientOptions)
|
||
|
|
||
|
log.Entry().Infof("start of gctsExecuteABAPQualityChecks step with configuration values: %v", config)
|
||
|
|
||
|
var objects []repoObject
|
||
|
var err error
|
||
|
|
||
|
log.Entry().Info("scope:", config.Scope)
|
||
|
|
||
|
switch strings.ToLower(config.Scope) {
|
||
|
case localChangedObjects:
|
||
|
objects, err = getLocalObjects(config, httpClient)
|
||
|
case remoteChangedObjects:
|
||
|
objects, err = getRemoteObjects(config, httpClient)
|
||
|
case localChangedPackages:
|
||
|
objects, err = getLocalPackages(config, httpClient)
|
||
|
case remoteChangedPackages:
|
||
|
objects, err = getRemotePackages(config, httpClient)
|
||
|
case repository:
|
||
|
objects, err = getRepositoryObjects(config, httpClient)
|
||
|
case packages:
|
||
|
objects, err = getPackages(config, httpClient)
|
||
|
default:
|
||
|
log.Entry().Info("the specified scope does not exists, the default one will be used:" + repository)
|
||
|
objects, err = getRepositoryObjects(config, httpClient)
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
log.Entry().WithError(err).Fatal("failure in get objects")
|
||
|
}
|
||
|
|
||
|
if objects == nil {
|
||
|
log.Entry().Warning("no object delta was found, therefore the step execution will stop")
|
||
|
return nil
|
||
|
|
||
|
}
|
||
|
|
||
|
log.Entry().Infof("objects to be checked:")
|
||
|
for _, object := range objects {
|
||
|
log.Entry().Info(object.Type, " ", object.Object)
|
||
|
}
|
||
|
|
||
|
if config.AUnitTest {
|
||
|
|
||
|
// wrapper for execution of AUnit Test
|
||
|
err := executeAUnitTest(config, httpClient, objects)
|
||
|
|
||
|
if err != nil {
|
||
|
log.Entry().WithError(err)
|
||
|
|
||
|
}
|
||
|
|
||
|
if aUnitFailure {
|
||
|
|
||
|
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!")
|
||
|
|
||
|
} else {
|
||
|
|
||
|
log.Entry().Info("AUnit test run completed successfully. If there are any results from the run, the results are saved in " + config.AUnitResultsFileName)
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if config.AtcCheck {
|
||
|
|
||
|
// wrapper for execution of ATCChecks
|
||
|
err = executeATCCheck(config, httpClient, objects)
|
||
|
|
||
|
if err != nil {
|
||
|
log.Entry().WithError(err).Fatal("execute ATC Check failed")
|
||
|
}
|
||
|
|
||
|
if atcFailure {
|
||
|
|
||
|
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!")
|
||
|
|
||
|
} else {
|
||
|
|
||
|
log.Entry().Info("ATCCheck test run completed successfully. If there are any results from the run, the results are saved in " + config.AtcResultsFileName)
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func getLocalObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) {
|
||
|
|
||
|
var localObjects []repoObject
|
||
|
var localObject repoObject
|
||
|
|
||
|
log.Entry().Info("get local changed objects started")
|
||
|
|
||
|
if config.Commit == "" {
|
||
|
|
||
|
return []repoObject{}, errors.Errorf("For scope: localChangedObjects you need to specify a commit")
|
||
|
|
||
|
}
|
||
|
|
||
|
history, err := getHistory(config, client)
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get local changed objects failed")
|
||
|
}
|
||
|
|
||
|
if len(history.Result) == 0 {
|
||
|
|
||
|
return []repoObject{}, errors.Wrap(err, "no activities (from commit - to commit) were found")
|
||
|
}
|
||
|
|
||
|
fromCommit := history.Result[0].FromCommit
|
||
|
log.Entry().Info("from Commit: ", fromCommit)
|
||
|
toCommit := history.Result[0].ToCommit
|
||
|
log.Entry().Info("to Commit: ", toCommit)
|
||
|
|
||
|
// object delta between FromCommit and ToCommit retrieved from Activities Tab in gCTS
|
||
|
resp, err := getObjectDifference(config, fromCommit, toCommit, client)
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get local changed objects failed")
|
||
|
}
|
||
|
|
||
|
for _, object := range resp.Objects {
|
||
|
localObject.Object = object.Name
|
||
|
localObject.Type = object.Type
|
||
|
localObjects = append(localObjects, localObject)
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("get local changed objects finished")
|
||
|
|
||
|
return localObjects, nil
|
||
|
}
|
||
|
|
||
|
func getRemoteObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) {
|
||
|
|
||
|
var remoteObjects []repoObject
|
||
|
var remoteObject repoObject
|
||
|
var currentRemoteCommit string
|
||
|
|
||
|
log.Entry().Info("get remote changed objects started")
|
||
|
|
||
|
if config.Commit == "" {
|
||
|
|
||
|
return []repoObject{}, errors.Errorf("For scope: remoteChangedObjects you need to specify a commit")
|
||
|
|
||
|
}
|
||
|
|
||
|
commitList, err := getCommitList(config, client)
|
||
|
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get remote changed objects failed")
|
||
|
}
|
||
|
|
||
|
for i, commit := range commitList.Commits {
|
||
|
if commit.ID == config.Commit {
|
||
|
currentRemoteCommit = commitList.Commits[i+1].ID
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if currentRemoteCommit == "" {
|
||
|
return []repoObject{}, errors.New("current remote commit was not found")
|
||
|
|
||
|
}
|
||
|
log.Entry().Info("current commit in the remote repository: ", currentRemoteCommit)
|
||
|
// object delta between the commit that triggered the pipeline and the current commit in the remote repository
|
||
|
resp, err := getObjectDifference(config, currentRemoteCommit, config.Commit, client)
|
||
|
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get remote changed objects failed")
|
||
|
}
|
||
|
|
||
|
for _, object := range resp.Objects {
|
||
|
remoteObject.Object = object.Name
|
||
|
remoteObject.Type = object.Type
|
||
|
remoteObjects = append(remoteObjects, remoteObject)
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("get remote changed objects finished")
|
||
|
|
||
|
return remoteObjects, nil
|
||
|
}
|
||
|
|
||
|
func getLocalPackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) {
|
||
|
|
||
|
var localPackages []repoObject
|
||
|
var localPackage repoObject
|
||
|
|
||
|
log.Entry().Info("get local changed packages started")
|
||
|
|
||
|
if config.Commit == "" {
|
||
|
|
||
|
return []repoObject{}, errors.Errorf("For scope: localChangedPackages you need to specify a commit")
|
||
|
|
||
|
}
|
||
|
|
||
|
history, err := getHistory(config, client)
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get local changed objects failed")
|
||
|
}
|
||
|
|
||
|
if len(history.Result) == 0 {
|
||
|
|
||
|
return []repoObject{}, errors.Wrap(err, "no activities (from commit - to commit) were found")
|
||
|
}
|
||
|
|
||
|
fromCommit := history.Result[0].FromCommit
|
||
|
log.Entry().Info("from Commit: ", fromCommit)
|
||
|
toCommit := history.Result[0].ToCommit
|
||
|
log.Entry().Info("to Commit: ", toCommit)
|
||
|
|
||
|
// object delta between FromCommit and ToCommit retrieved from Activities Tab in gCTS
|
||
|
resp, err := getObjectDifference(config, fromCommit, config.Commit, client)
|
||
|
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get local changed packages failed")
|
||
|
|
||
|
}
|
||
|
|
||
|
myPackages := map[string]bool{}
|
||
|
|
||
|
// objects are resolved into packages(DEVC)
|
||
|
for _, object := range resp.Objects {
|
||
|
objInfo, err := getObjectInfo(config, client, object.Name, object.Type)
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get local changed packages failed")
|
||
|
}
|
||
|
if myPackages[objInfo.Devclass] {
|
||
|
|
||
|
} else {
|
||
|
myPackages[objInfo.Devclass] = true
|
||
|
localPackage.Object = objInfo.Devclass
|
||
|
localPackage.Type = "DEVC"
|
||
|
localPackages = append(localPackages, localPackage)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("get local changed packages finished")
|
||
|
return localPackages, nil
|
||
|
}
|
||
|
|
||
|
func getRemotePackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) {
|
||
|
|
||
|
var remotePackages []repoObject
|
||
|
var remotePackage repoObject
|
||
|
var currentRemoteCommit string
|
||
|
|
||
|
log.Entry().Info("get remote changed packages started")
|
||
|
|
||
|
if config.Commit == "" {
|
||
|
|
||
|
return []repoObject{}, errors.Errorf("For scope: remoteChangedPackages you need to specify a commit")
|
||
|
|
||
|
}
|
||
|
|
||
|
commitList, err := getCommitList(config, client)
|
||
|
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get remote changed packages failed")
|
||
|
}
|
||
|
|
||
|
for i, commit := range commitList.Commits {
|
||
|
if commit.ID == config.Commit {
|
||
|
currentRemoteCommit = commitList.Commits[i+1].ID
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if currentRemoteCommit == "" {
|
||
|
return []repoObject{}, errors.Wrap(err, "current remote commit was not found")
|
||
|
|
||
|
}
|
||
|
log.Entry().Info("current commit in the remote repository: ", currentRemoteCommit)
|
||
|
//object delta between the commit that triggered the pipeline and the current commit in the remote repository
|
||
|
resp, err := getObjectDifference(config, currentRemoteCommit, config.Commit, client)
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get remote changed packages failed")
|
||
|
}
|
||
|
|
||
|
myPackages := map[string]bool{}
|
||
|
// objects are resolved into packages(DEVC)
|
||
|
for _, object := range resp.Objects {
|
||
|
objInfo, err := getObjectInfo(config, client, object.Name, object.Type)
|
||
|
if err != nil {
|
||
|
return []repoObject{}, errors.Wrap(err, "get remote changed packages failed")
|
||
|
}
|
||
|
if myPackages[objInfo.Devclass] {
|
||
|
|
||
|
} else {
|
||
|
myPackages[objInfo.Devclass] = true
|
||
|
remotePackage.Object = objInfo.Devclass
|
||
|
remotePackage.Type = "DEVC"
|
||
|
remotePackages = append(remotePackages, remotePackage)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
log.Entry().Info("get remote changed packages finished")
|
||
|
return remotePackages, nil
|
||
|
}
|
||
|
|
||
|
func getRepositoryObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) {
|
||
|
|
||
|
log.Entry().Info("get repository objects started")
|
||
|
|
||
|
var repoResp repoObjectResponse
|
||
|
|
||
|
url := config.Host +
|
||
|
"/sap/bc/cts_abapvcs/repository/" + config.Repository +
|
||
|
"/objects?sap-client=" + config.Client
|
||
|
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, nil, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return []repoObject{}, errors.Wrap(httpErr, "could not get repository objects")
|
||
|
} else if resp == nil {
|
||
|
return []repoObject{}, errors.New("could not get repository objects: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoResp)
|
||
|
if parsingErr != nil {
|
||
|
return []repoObject{}, errors.Errorf("%v", parsingErr)
|
||
|
}
|
||
|
|
||
|
var repositoryObjects []repoObject
|
||
|
|
||
|
// remove object type DEVC, because it is already included in scope packages
|
||
|
// also if you run ATC Checks for DEVC together with other object types, ATC checks will run only for DEVC
|
||
|
for _, object := range repoResp.Objects {
|
||
|
|
||
|
if object.Type != "DEVC" {
|
||
|
repositoryObjects = append(repositoryObjects, object)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("get repository objects finished")
|
||
|
|
||
|
// all objects that are part of the local repository
|
||
|
return repositoryObjects, nil
|
||
|
}
|
||
|
|
||
|
func getPackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) {
|
||
|
|
||
|
var packages []repoObject
|
||
|
|
||
|
log.Entry().Info("get packages started")
|
||
|
|
||
|
var repoResp repoObjectResponse
|
||
|
|
||
|
url := config.Host +
|
||
|
"/sap/bc/cts_abapvcs/repository/" + config.Repository +
|
||
|
"/objects?sap-client=" + config.Client
|
||
|
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, nil, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return []repoObject{}, errors.Wrap(httpErr, "get packages failed: could not get repository objects")
|
||
|
} else if resp == nil {
|
||
|
return []repoObject{}, errors.New("get packages failed: could not get repository objects: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoResp)
|
||
|
if parsingErr != nil {
|
||
|
return []repoObject{}, errors.Errorf("%v", parsingErr)
|
||
|
}
|
||
|
// chose only DEVC from repository objects
|
||
|
for _, object := range repoResp.Objects {
|
||
|
|
||
|
if object.Type == "DEVC" {
|
||
|
packages = append(packages, object)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("get packages finished")
|
||
|
return packages, nil
|
||
|
}
|
||
|
|
||
|
func discoverServer(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (*http.Header, error) {
|
||
|
|
||
|
url := config.Host +
|
||
|
"/sap/bc/adt/core/discovery?sap-client=" + config.Client
|
||
|
|
||
|
header := make(http.Header)
|
||
|
header.Add("Accept", "application/atomsvc+xml")
|
||
|
header.Add("x-csrf-token", "fetch")
|
||
|
header.Add("saml2", "disabled")
|
||
|
|
||
|
disc, httpErr := client.SendRequest("GET", url, nil, header, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if disc != nil && disc.Body != nil {
|
||
|
disc.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return nil, errors.Wrap(httpErr, "discovery of the ABAP server failed")
|
||
|
} else if disc == nil || disc.Header == nil {
|
||
|
return nil, errors.New("discovery of the ABAP server failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
return &disc.Header, nil
|
||
|
}
|
||
|
|
||
|
func executeAUnitTest(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objects []repoObject) error {
|
||
|
|
||
|
log.Entry().Info("execute ABAP Unit Test started")
|
||
|
|
||
|
var innerXml string
|
||
|
var result runResult
|
||
|
|
||
|
for _, object := range objects {
|
||
|
|
||
|
switch object.Type {
|
||
|
case "CLAS":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/oo/classes/` + object.Object + `"/>`
|
||
|
case "DEVC":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/packages/` + object.Object + `"/>`
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
var xmlBody = []byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit">
|
||
|
<external>
|
||
|
<coverage active="false"/>
|
||
|
</external>
|
||
|
<options>
|
||
|
<uriType value="semantic"/>
|
||
|
<testDeterminationStrategy appendAssignedTestsPreview="true" assignedTests="false" sameProgram="true"/>
|
||
|
<testRiskLevels critical="true" dangerous="true" harmless="true"/>
|
||
|
<testDurations long="true" medium="true" short="true"/>
|
||
|
<withNavigationUri enabled="false"/>
|
||
|
</options>
|
||
|
<adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core">
|
||
|
<objectSet kind="inclusive">
|
||
|
<adtcore:objectReferences>` +
|
||
|
innerXml +
|
||
|
`</adtcore:objectReferences>
|
||
|
</objectSet>
|
||
|
</adtcore:objectSets>
|
||
|
</aunit:runConfiguration>`)
|
||
|
|
||
|
resp, err := runAUnitTest(config, client, xmlBody)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "execute of Aunit test has failed")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyXML(resp, &result)
|
||
|
if parsingErr != nil {
|
||
|
log.Entry().Warning(parsingErr)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
parsedRes, err := parseUnitResult(config, client, &result)
|
||
|
|
||
|
if err != nil {
|
||
|
log.Entry().Warning(err)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("execute ABAP Unit Test finished.", parsedRes.Text)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func runAUnitTest(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, xml []byte) (response *http.Response, err error) {
|
||
|
|
||
|
log.Entry().Info("run ABAP Unit Test started")
|
||
|
url := config.Host +
|
||
|
"/sap/bc/adt/abapunit/testruns?sap-client=" + config.Client
|
||
|
|
||
|
discHeader, discError := discoverServer(config, client)
|
||
|
|
||
|
if discError != nil {
|
||
|
return response, errors.Wrap(discError, "run of unit tests failed")
|
||
|
}
|
||
|
|
||
|
if discHeader.Get("X-Csrf-Token") == "" {
|
||
|
|
||
|
return response, errors.Errorf("could not retrieve x-csrf-token from server")
|
||
|
}
|
||
|
|
||
|
header := make(http.Header)
|
||
|
header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token"))
|
||
|
header.Add("Accept", "application/xml")
|
||
|
header.Add("Content-Type", "application/vnd.sap.adt.abapunit.testruns.result.v1+xml")
|
||
|
|
||
|
response, httpErr := client.SendRequest("POST", url, bytes.NewBuffer(xml), header, nil)
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return response, errors.Wrap(httpErr, "run of unit tests failed")
|
||
|
} else if response == nil {
|
||
|
return response, errors.New("run of unit tests failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("run ABAP Unit Test finished")
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
func parseUnitResult(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, aUnitRunResult *runResult) (parsedResult checkstyle, err error) {
|
||
|
|
||
|
log.Entry().Info("parse ABAP Unit Result started")
|
||
|
|
||
|
var fileName string
|
||
|
var aUnitFile file
|
||
|
var aUnitError checkstyleError
|
||
|
|
||
|
parsedResult.Version = "1.0"
|
||
|
|
||
|
for _, program := range aUnitRunResult.Program {
|
||
|
|
||
|
objectType := program.Type[0:4]
|
||
|
objectName := program.Name
|
||
|
|
||
|
//syntax error in unit test or class
|
||
|
if program.Alerts.Alert.HasSyntaxErrors == "true" {
|
||
|
|
||
|
aUnitFailure = true
|
||
|
aUnitError.Source = objectName
|
||
|
aUnitError.Severity = "error"
|
||
|
log.Entry().Info("severity: ", aUnitError.Severity)
|
||
|
aUnitError.Message = html.UnescapeString(program.Alerts.Alert.Title + " " + program.Alerts.Alert.Details.Detail.AttrText)
|
||
|
log.Entry().Info("message: ", aUnitError.Message)
|
||
|
aUnitError.Line, err = findLine(config, client, program.Alerts.Alert.Stack.StackEntry.URI, objectName, objectType)
|
||
|
log.Entry().Error("line: ", aUnitError.Line)
|
||
|
if err != nil {
|
||
|
return parsedResult, errors.Wrap(err, "parse AUnit Result failed")
|
||
|
|
||
|
}
|
||
|
fileName, err = getFileName(config, client, program.Alerts.Alert.Stack.StackEntry.URI, objectName)
|
||
|
log.Entry().Error("file path: ", aUnitError.Line)
|
||
|
if err != nil {
|
||
|
return parsedResult, errors.Wrap(err, "parse AUnit Result failed")
|
||
|
|
||
|
}
|
||
|
|
||
|
aUnitFile.Error = append(aUnitFile.Error, aUnitError)
|
||
|
aUnitError = checkstyleError{}
|
||
|
log.Entry().Error("there is a syntax error", aUnitFile)
|
||
|
}
|
||
|
|
||
|
for _, testClass := range program.TestClasses.TestClass {
|
||
|
|
||
|
for _, testMethod := range testClass.TestMethods.TestMethod {
|
||
|
|
||
|
aUnitError.Source = testClass.Name + "/" + testMethod.Name
|
||
|
|
||
|
// unit test failure
|
||
|
if len(testMethod.Alerts.Alert) > 0 {
|
||
|
|
||
|
for _, testalert := range testMethod.Alerts.Alert {
|
||
|
|
||
|
switch testalert.Severity {
|
||
|
case "fatal":
|
||
|
log.Entry().Error("unit test " + aUnitError.Source + " has failed with severity fatal")
|
||
|
aUnitFailure = true
|
||
|
aUnitError.Severity = "error"
|
||
|
case "critical":
|
||
|
log.Entry().Error("unit test " + aUnitError.Source + " has failed with severity critical")
|
||
|
aUnitFailure = true
|
||
|
aUnitError.Severity = "error"
|
||
|
case "tolerable":
|
||
|
log.Entry().Warning("unit test " + aUnitError.Source + " has failed with severity warning")
|
||
|
aUnitError.Severity = "warning"
|
||
|
default:
|
||
|
aUnitError.Severity = "info"
|
||
|
|
||
|
}
|
||
|
|
||
|
//unit test message is spread in different elements
|
||
|
for _, detail := range testalert.Details.Detail {
|
||
|
aUnitError.Message = aUnitError.Message + " " + detail.AttrText
|
||
|
for _, subdetail := range detail.Details.Detail {
|
||
|
|
||
|
aUnitError.Message = html.UnescapeString(aUnitError.Message + " " + subdetail.AttrText)
|
||
|
log.Entry().Info("message: ", aUnitError.Message)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
aUnitError.Line, err = findLine(config, client, testalert.Stack.StackEntry.URI, objectName, objectType)
|
||
|
log.Entry().Info("line: ", aUnitError.Line)
|
||
|
if err != nil {
|
||
|
|
||
|
log.Entry().Warning(err)
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
aUnitFile.Error = append(aUnitFile.Error, aUnitError)
|
||
|
aUnitError = checkstyleError{}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
log.Entry().Info("unit test:", aUnitError.Source, "- was successful")
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
fileName, err = getFileName(config, client, testClass.URI, objectName)
|
||
|
if err != nil {
|
||
|
return parsedResult, errors.Wrap(err, "parse AUnit Result failed")
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
aUnitFile.Name, err = constructPath(config, client, fileName, objectName, objectType)
|
||
|
log.Entry().Error("file path: ", aUnitFile.Name)
|
||
|
if err != nil {
|
||
|
|
||
|
return parsedResult, errors.Wrap(err, "parse AUnit Result failed")
|
||
|
}
|
||
|
parsedResult.File = append(parsedResult.File, aUnitFile)
|
||
|
aUnitFile = file{}
|
||
|
|
||
|
}
|
||
|
|
||
|
body, _ := xml.Marshal(parsedResult)
|
||
|
|
||
|
writeErr := ioutil.WriteFile(config.AUnitResultsFileName, body, 0644)
|
||
|
|
||
|
if writeErr != nil {
|
||
|
log.Entry().Error("file AUnitResults.xml could not be created")
|
||
|
return parsedResult, fmt.Errorf("handling unit test results failed: %w", writeErr)
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("parse ABAP Unit Result finished")
|
||
|
return parsedResult, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func executeATCCheck(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objects []repoObject) (error error) {
|
||
|
|
||
|
log.Entry().Info("execute ATC Check started")
|
||
|
|
||
|
var innerXml string
|
||
|
var result worklist
|
||
|
|
||
|
for _, object := range objects {
|
||
|
|
||
|
switch object.Type {
|
||
|
|
||
|
case "CLAS":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/oo/classes/` + object.Object + `"/>`
|
||
|
case "INTF":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/oo/interfaces/` + object.Object + `"/>`
|
||
|
case "DEVC":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/packages/` + object.Object + `"/>`
|
||
|
case "FUGR":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/functions/groups/` + object.Object + `/source/main"/>`
|
||
|
case "TABL":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/ddic/tables/` + object.Object + `/source/main"/>`
|
||
|
case "DTEL":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/ddic/dataelements/` + object.Object + `"/>`
|
||
|
case "DOMA":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/ddic/domains/` + object.Object + `"/>`
|
||
|
case "MSAG":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/messageclass/` + object.Object + `"/>`
|
||
|
case "PROG":
|
||
|
innerXml = innerXml + `<adtcore:objectReference adtcore:uri="/sap/bc/adt/programs/programs/` + object.Object + `/source/main"/>`
|
||
|
default:
|
||
|
log.Entry().Warning("object Type " + object.Type + " is not supported!")
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
var xmlBody = []byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<atc:run xmlns:atc="http://www.sap.com/adt/atc"
|
||
|
maximumVerdicts="100">
|
||
|
<objectSets xmlns:adtcore="http://www.sap.com/adt/core">
|
||
|
<objectSet kind="inclusive">
|
||
|
<adtcore:objectReferences>` + innerXml +
|
||
|
`</adtcore:objectReferences>
|
||
|
</objectSet>
|
||
|
</objectSets>
|
||
|
</atc:run>`)
|
||
|
|
||
|
worklist, err := getWorklist(config, client)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "execution of ATC Checks failed")
|
||
|
}
|
||
|
|
||
|
err = startATCRun(config, client, xmlBody, worklist)
|
||
|
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "execution of ATC Checks failed")
|
||
|
}
|
||
|
|
||
|
resp, err := getATCRun(config, client, worklist)
|
||
|
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "execution of ATC Checks failed")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyXML(resp, &result)
|
||
|
if parsingErr != nil {
|
||
|
log.Entry().Warning(parsingErr)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
atcRes, err := parseATCCheckResult(config, client, &result)
|
||
|
|
||
|
if err != nil {
|
||
|
log.Entry().Error(err)
|
||
|
return errors.Wrap(err, "execution of ATC Checks failed")
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("execute ATC Checks finished.", atcRes.Text)
|
||
|
|
||
|
return nil
|
||
|
|
||
|
}
|
||
|
func startATCRun(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, xml []byte, worklistID string) (err error) {
|
||
|
|
||
|
log.Entry().Info("ATC Run started")
|
||
|
|
||
|
discHeader, discError := discoverServer(config, client)
|
||
|
if discError != nil {
|
||
|
return errors.Wrap(discError, "start of ATC run failed")
|
||
|
}
|
||
|
|
||
|
if discHeader.Get("X-Csrf-Token") == "" {
|
||
|
return errors.Errorf("could not retrieve x-csrf-token from server")
|
||
|
}
|
||
|
|
||
|
header := make(http.Header)
|
||
|
header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token"))
|
||
|
header.Add("Accept", "application/xml")
|
||
|
|
||
|
url := config.Host +
|
||
|
"/sap/bc/adt/atc/runs?worklistId=" + worklistID + "&sap-client=" + config.Client
|
||
|
|
||
|
resp, httpErr := client.SendRequest("POST", url, bytes.NewBuffer(xml), header, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return errors.Wrap(httpErr, "start of ATC run failed")
|
||
|
} else if resp == nil {
|
||
|
return errors.New("start of ATC run failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
log.Entry().Info("ATC Run finished")
|
||
|
|
||
|
return nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func getATCRun(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, worklistID string) (response *http.Response, err error) {
|
||
|
|
||
|
log.Entry().Info("get ATC Run Results started")
|
||
|
|
||
|
header := make(http.Header)
|
||
|
|
||
|
url := config.Host +
|
||
|
"/sap/bc/adt/atc/worklists/" + worklistID + "?sap-client=" + config.Client
|
||
|
|
||
|
header.Add("Accept", "application/atc.worklist.v1+xml")
|
||
|
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, header, nil)
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return response, errors.Wrap(httpErr, "get ATC run failed")
|
||
|
} else if resp == nil {
|
||
|
return response, errors.New("get ATC run failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
log.Entry().Info("get ATC Run Results finished")
|
||
|
return resp, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func getWorklist(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (worklistID string, error error) {
|
||
|
|
||
|
url := config.Host +
|
||
|
"/sap/bc/adt/atc/worklists?checkVariant=" + config.AtcVariant + "&sap-client=" + config.Client
|
||
|
discHeader, discError := discoverServer(config, client)
|
||
|
|
||
|
if discError != nil {
|
||
|
return worklistID, errors.Wrap(discError, "get worklist failed")
|
||
|
}
|
||
|
|
||
|
if discHeader.Get("X-Csrf-Token") == "" {
|
||
|
return worklistID, errors.Errorf("could not retrieve x-csrf-token from server")
|
||
|
}
|
||
|
|
||
|
header := make(http.Header)
|
||
|
header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token"))
|
||
|
header.Add("Accept", "*/*")
|
||
|
|
||
|
resp, httpErr := client.SendRequest("POST", url, nil, header, nil)
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return worklistID, errors.Wrap(httpErr, "get worklist failed")
|
||
|
} else if resp == nil {
|
||
|
return worklistID, errors.New("get worklist failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
location := resp.Header["Location"][0]
|
||
|
locationSlice := strings.Split(location, "/")
|
||
|
worklistID = locationSlice[len(locationSlice)-1]
|
||
|
log.Entry().Info("worklist id for ATC check: ", worklistID)
|
||
|
|
||
|
return worklistID, nil
|
||
|
}
|
||
|
|
||
|
func parseATCCheckResult(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, response *worklist) (atcResults checkstyle, error error) {
|
||
|
|
||
|
log.Entry().Info("parse ATC Check Result started")
|
||
|
|
||
|
var atcFile file
|
||
|
var subObject string
|
||
|
var aTCUnitError checkstyleError
|
||
|
|
||
|
atcResults.Version = "1.0"
|
||
|
|
||
|
for _, object := range response.Objects.Object {
|
||
|
|
||
|
objectType := object.Type
|
||
|
objectName := object.Name
|
||
|
|
||
|
for _, atcworklist := range object.Findings.Finding {
|
||
|
|
||
|
log.Entry().Info("there is atc finding for object type: ", objectType+" object name: "+objectName)
|
||
|
|
||
|
path, err := url.PathUnescape(atcworklist.Location)
|
||
|
|
||
|
if err != nil {
|
||
|
return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed")
|
||
|
|
||
|
}
|
||
|
|
||
|
if len(atcworklist.Atcfinding) > 0 {
|
||
|
|
||
|
priority, err := strconv.Atoi(atcworklist.Priority)
|
||
|
|
||
|
if err != nil {
|
||
|
return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed")
|
||
|
|
||
|
}
|
||
|
|
||
|
switch priority {
|
||
|
case 1:
|
||
|
atcFailure = true
|
||
|
aTCUnitError.Severity = "error"
|
||
|
log.Entry().Error("atc issue with priority: 1 ")
|
||
|
case 2:
|
||
|
atcFailure = true
|
||
|
aTCUnitError.Severity = "error"
|
||
|
log.Entry().Error("atc issue with priority: 2 ")
|
||
|
case 3:
|
||
|
aTCUnitError.Severity = "warning"
|
||
|
log.Entry().Warning("atc issue with priority: 3 ")
|
||
|
default:
|
||
|
aTCUnitError.Severity = "info"
|
||
|
log.Entry().Info("atc issue with low priority ")
|
||
|
}
|
||
|
|
||
|
log.Entry().Error("severity: ", aTCUnitError.Severity)
|
||
|
|
||
|
if aTCUnitError.Line == "" {
|
||
|
|
||
|
aTCUnitError.Line, err = findLine(config, client, path, objectName, objectType)
|
||
|
log.Entry().Info("line: ", aTCUnitError.Line)
|
||
|
|
||
|
if err != nil {
|
||
|
log.Entry().Info(path)
|
||
|
log.Entry().Warning(err)
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if subObject != "" {
|
||
|
aTCUnitError.Source = objectName + "/" + strings.ToUpper(subObject)
|
||
|
} else {
|
||
|
aTCUnitError.Source = objectName
|
||
|
}
|
||
|
|
||
|
aTCUnitError.Message = html.UnescapeString(atcworklist.CheckTitle + " " + atcworklist.MessageTitle)
|
||
|
log.Entry().Info("message: ", aTCUnitError.Message)
|
||
|
atcFile.Error = append(atcFile.Error, aTCUnitError)
|
||
|
aTCUnitError = checkstyleError{}
|
||
|
}
|
||
|
|
||
|
if atcFile.Error[0].Message != "" {
|
||
|
|
||
|
fileName, err := getFileName(config, client, path, objectName)
|
||
|
|
||
|
if err != nil {
|
||
|
return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed")
|
||
|
}
|
||
|
|
||
|
atcFile.Name, err = constructPath(config, client, fileName, objectName, objectType)
|
||
|
log.Entry().Info("file path: ", atcFile.Name)
|
||
|
if err != nil {
|
||
|
return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed")
|
||
|
}
|
||
|
atcResults.File = append(atcResults.File, atcFile)
|
||
|
atcFile = file{}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
atcBody, _ := xml.Marshal(atcResults)
|
||
|
|
||
|
writeErr := ioutil.WriteFile(config.AtcResultsFileName, atcBody, 0644)
|
||
|
|
||
|
if writeErr != nil {
|
||
|
log.Entry().Error("ATCResults.xml could not be created")
|
||
|
return atcResults, fmt.Errorf("handling atc results failed: %w", writeErr)
|
||
|
}
|
||
|
log.Entry().Info("parsing ATC check results to CheckStyle has finished.")
|
||
|
return atcResults, writeErr
|
||
|
}
|
||
|
|
||
|
func constructPath(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, fileName string, objectName string, objectType string) (filePath string, error error) {
|
||
|
|
||
|
targetDir, err := getTargetDir(config, client)
|
||
|
if err != nil {
|
||
|
return filePath, errors.Wrap(err, "path could not be constructed")
|
||
|
|
||
|
}
|
||
|
|
||
|
filePath = config.Workspace + "/" + targetDir + "/objects/" + strings.ToUpper(objectType) + "/" + strings.ToUpper(objectName) + "/" + fileName
|
||
|
return filePath, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func findLine(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, path string, objectName string, objectType string) (line string, error error) {
|
||
|
|
||
|
regexLine := regexp.MustCompile(`.start=\d*`)
|
||
|
regexMethod := regexp.MustCompile(`.name=[a-zA-Z0-9_-]*;`)
|
||
|
|
||
|
readableSource, err := checkReadableSource(config, client)
|
||
|
|
||
|
if err != nil {
|
||
|
|
||
|
return line, errors.Wrap(err, "could not find line in source code")
|
||
|
|
||
|
}
|
||
|
|
||
|
fileName, err := getFileName(config, client, path, objectName)
|
||
|
|
||
|
if err != nil {
|
||
|
|
||
|
return line, err
|
||
|
|
||
|
}
|
||
|
|
||
|
filePath, err := constructPath(config, client, fileName, objectName, objectType)
|
||
|
if err != nil {
|
||
|
return line, errors.Wrap(err, objectType+"/"+objectName+"could not find line in source code")
|
||
|
|
||
|
}
|
||
|
|
||
|
var absLine int
|
||
|
if readableSource {
|
||
|
|
||
|
// the error line that we get from UnitTest Run or ATC Check is not aligned for the readable source, we need to calculated it
|
||
|
rawfile, err := ioutil.ReadFile(filePath)
|
||
|
|
||
|
if err != nil {
|
||
|
|
||
|
return line, errors.Wrapf(err, "could not find object in the workspace of your CI/CD tool ")
|
||
|
}
|
||
|
|
||
|
file := string(rawfile)
|
||
|
|
||
|
splittedfile := strings.Split(file, "\n")
|
||
|
|
||
|
// CLAS/OSO - is unique identifier for protection section in CLAS
|
||
|
if strings.Contains(path, "CLAS/OSO") {
|
||
|
|
||
|
for l, line := range splittedfile {
|
||
|
|
||
|
if strings.Contains(line, "protected section.") {
|
||
|
absLine = l
|
||
|
break
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// CLAS/OM - is unique identifier for method section in CLAS
|
||
|
} else if strings.Contains(path, "CLAS/OM") {
|
||
|
|
||
|
methodName := regexMethod.FindString(path)
|
||
|
|
||
|
if methodName != "" {
|
||
|
methodName = methodName[len(`.name=`) : len(methodName)-1]
|
||
|
|
||
|
}
|
||
|
|
||
|
for line, linecontent := range splittedfile {
|
||
|
|
||
|
if strings.Contains(linecontent, "method"+" "+methodName) {
|
||
|
absLine = line
|
||
|
break
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// CLAS/OSI - is unique identifier for private section in CLAS
|
||
|
} else if strings.Contains(path, "CLAS/OSI") {
|
||
|
|
||
|
for line, linecontent := range splittedfile {
|
||
|
|
||
|
if strings.Contains(linecontent, "private section.") {
|
||
|
absLine = line
|
||
|
break
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
errLine := regexLine.FindString(path)
|
||
|
|
||
|
if errLine != "" {
|
||
|
|
||
|
errLine, err := strconv.Atoi(errLine[len(`.start=`):])
|
||
|
if err == nil {
|
||
|
line = strconv.Itoa(absLine + errLine)
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
// classic format
|
||
|
errLine := regexLine.FindString(path)
|
||
|
if errLine != "" {
|
||
|
line = errLine[len(`.start=`):]
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return line, nil
|
||
|
}
|
||
|
func getFileName(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, path string, objName string) (fileName string, error error) {
|
||
|
|
||
|
readableSource, err := checkReadableSource(config, client)
|
||
|
if err != nil {
|
||
|
return fileName, errors.Wrap(err, "get file name has failed")
|
||
|
|
||
|
}
|
||
|
|
||
|
path, err = url.PathUnescape(path)
|
||
|
|
||
|
var fileExtension string
|
||
|
fileExtensionLength := 30 - len(objName)
|
||
|
for i := 0; i < fileExtensionLength; i++ {
|
||
|
fileExtension += "="
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return fileName, errors.Wrap(err, "get file name has failed")
|
||
|
|
||
|
}
|
||
|
|
||
|
// INTERFACES
|
||
|
regexInterface := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/interfaces\/\w*`)
|
||
|
intf := regexInterface.FindString(path)
|
||
|
if intf != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".intf.abap"
|
||
|
} else {
|
||
|
fileName = "REPS " + strings.ToUpper(objName) + fileExtension + "IU.abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
// CLASSES DEFINITIONS
|
||
|
regexClasDef := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/definitions\/`)
|
||
|
clasDef := regexClasDef.FindString(path)
|
||
|
if clasDef != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.definitions.abap"
|
||
|
} else {
|
||
|
fileName = "CINC " + objName + fileExtension + "CCDEF.abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// CLASSES IMPLEMENTATIONS
|
||
|
regexClasImpl := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/implementations\/`)
|
||
|
clasImpl := regexClasImpl.FindString(path)
|
||
|
if clasImpl != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.implementations.abap"
|
||
|
} else {
|
||
|
fileName = "CINC " + objName + fileExtension + "CCIMP.abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// CLASSES MACROS
|
||
|
regexClasMacro := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/macros\/`)
|
||
|
clasMacro := regexClasMacro.FindString(path)
|
||
|
if clasMacro != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.macros.abap"
|
||
|
} else {
|
||
|
fileName = "CINC " + objName + fileExtension + "CCMAC.abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// TEST CLASSES
|
||
|
regexTestClass := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*#?\/?\w*\/?testclass`)
|
||
|
testClass := regexTestClass.FindString(path)
|
||
|
if testClass != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.testclasses.abap"
|
||
|
} else {
|
||
|
fileName = "CINC " + objName + fileExtension + "CCAU.abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// CLASS PROTECTED
|
||
|
regexClasProtected := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OSO`)
|
||
|
classProtected := regexClasProtected.FindString(path)
|
||
|
if classProtected != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.abap"
|
||
|
} else {
|
||
|
fileName = "CPRO " + objName + ".abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// CLASS PRIVATE
|
||
|
regexClasPrivate := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OSI`)
|
||
|
classPrivate := regexClasPrivate.FindString(path)
|
||
|
if classPrivate != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.abap"
|
||
|
} else {
|
||
|
fileName = "CPRI " + objName + ".abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// CLASS METHOD
|
||
|
regexClasMethod := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OM`)
|
||
|
classMethod := regexClasMethod.FindString(path)
|
||
|
if classMethod != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.abap"
|
||
|
} else {
|
||
|
|
||
|
regexmethodName := regexp.MustCompile(`name=\w*`)
|
||
|
methodName := regexmethodName.FindString(path)
|
||
|
|
||
|
fileName = "METH " + methodName[len(`name=`):] + ".abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// CLASS PUBLIC
|
||
|
regexClasPublic := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#start`)
|
||
|
classPublic := regexClasPublic.FindString(path)
|
||
|
if classPublic != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.abap"
|
||
|
} else {
|
||
|
fileName = "CPUB " + objName + ".abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// FUNCTION INCLUDE
|
||
|
regexFuncIncl := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/includes/\w*`)
|
||
|
|
||
|
funcIncl := regexFuncIncl.FindString(path)
|
||
|
if funcIncl != "" && fileName == "" {
|
||
|
|
||
|
regexSubObj := regexp.MustCompile(`includes\/\w*`)
|
||
|
subObject := regexSubObj.FindString(path)
|
||
|
subObject = subObject[len(`includes/`):]
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".fugr." + strings.ToLower(subObject) + ".reps.abap"
|
||
|
} else {
|
||
|
fileName = "REPS " + strings.ToUpper(subObject) + ".abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// FUNCTION GROUP
|
||
|
regexFuncGr := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/source\/main`)
|
||
|
|
||
|
funcGr := regexFuncGr.FindString(path)
|
||
|
if funcGr != "" && fileName == "" {
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".fugr.sapl" + strings.ToLower(objName) + ".reps.abap"
|
||
|
} else {
|
||
|
fileName = "REPS SAPL" + objName + ".abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// FUNCTION MODULE
|
||
|
regexFuncMod := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/fmodules/\w*`)
|
||
|
funcMod := regexFuncMod.FindString(path)
|
||
|
if funcMod != "" && fileName == "" {
|
||
|
|
||
|
regexSubObj := regexp.MustCompile(`includes\/\w*`)
|
||
|
subObject := regexSubObj.FindString(path)
|
||
|
subObject = subObject[len(`includes/`):]
|
||
|
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(subObject) + ".func.abap"
|
||
|
} else {
|
||
|
fileName = "FUNC " + subObject + ".abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
// CLAS
|
||
|
regexClas := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/` + strings.ToLower(objName))
|
||
|
clas := regexClas.FindString(path)
|
||
|
if clas != "" && fileName == "" {
|
||
|
if readableSource {
|
||
|
|
||
|
fileName = strings.ToLower(objName) + ".clas.abap"
|
||
|
} else {
|
||
|
|
||
|
fileName = "CPUB " + objName + ".abap"
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// PROGRAM
|
||
|
regexProg := regexp.MustCompile(`\/sap\/bc\/adt\/programs\/programs\/` + strings.ToLower(objName))
|
||
|
prog := regexProg.FindString(path)
|
||
|
if prog != "" && fileName == "" {
|
||
|
|
||
|
fileName = "REPS " + objName + ".abap"
|
||
|
|
||
|
}
|
||
|
|
||
|
// TABLES
|
||
|
regexTab := regexp.MustCompile(`\/sap\/bc\/adt\/ddic\/tables\/` + strings.ToLower(objName))
|
||
|
tab := regexTab.FindString(path)
|
||
|
if tab != "" && fileName == "" {
|
||
|
|
||
|
fileName = "TABL " + objName + ".asx.json"
|
||
|
|
||
|
}
|
||
|
|
||
|
return fileName, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func getTargetDir(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (string, error) {
|
||
|
|
||
|
var targetDir string
|
||
|
|
||
|
repository, err := getRepo(config, client)
|
||
|
|
||
|
if err != nil {
|
||
|
return targetDir, err
|
||
|
}
|
||
|
|
||
|
for _, config := range repository.Result.Config {
|
||
|
if config.Key == "VCS_TARGET_DIR" {
|
||
|
targetDir = config.Value
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return targetDir, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func checkReadableSource(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (readableSource bool, error error) {
|
||
|
|
||
|
repoLayout, err := getRepositoryLayout(config, client)
|
||
|
if err != nil {
|
||
|
return readableSource, errors.Wrap(err, "could not check readable source format")
|
||
|
}
|
||
|
|
||
|
if repoLayout.Layout.ReadableSource == "true" || repoLayout.Layout.ReadableSource == "only" || repoLayout.Layout.ReadableSource == "all" {
|
||
|
|
||
|
readableSource = true
|
||
|
|
||
|
} else {
|
||
|
|
||
|
readableSource = false
|
||
|
|
||
|
}
|
||
|
|
||
|
return readableSource, nil
|
||
|
}
|
||
|
|
||
|
func getRepo(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (repositoryResponse, error) {
|
||
|
|
||
|
var repositoryResp repositoryResponse
|
||
|
url := config.Host +
|
||
|
"/sap/bc/cts_abapvcs/repository/" + config.Repository +
|
||
|
"?sap-client=" + config.Client
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, nil, nil)
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return repositoryResponse{}, errors.Wrap(httpErr, "could not get repository")
|
||
|
} else if resp == nil {
|
||
|
return repositoryResponse{}, errors.New("could not get repository: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repositoryResp)
|
||
|
if parsingErr != nil {
|
||
|
return repositoryResponse{}, errors.Errorf("%v", parsingErr)
|
||
|
}
|
||
|
|
||
|
return repositoryResp, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func getRepositoryLayout(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (layoutResponse, error) {
|
||
|
|
||
|
var repoLayoutResponse layoutResponse
|
||
|
url := config.Host +
|
||
|
"/sap/bc/cts_abapvcs/repository/" + config.Repository +
|
||
|
"/layout?sap-client=" + config.Client
|
||
|
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, nil, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return layoutResponse{}, errors.Wrap(httpErr, "could not get repository layout")
|
||
|
} else if resp == nil {
|
||
|
return layoutResponse{}, errors.New("could not get repository layout: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoLayoutResponse)
|
||
|
if parsingErr != nil {
|
||
|
return layoutResponse{}, errors.Errorf("%v", parsingErr)
|
||
|
}
|
||
|
|
||
|
return repoLayoutResponse, nil
|
||
|
}
|
||
|
|
||
|
func getCommitList(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (commitResponse, error) {
|
||
|
|
||
|
var commitResp commitResponse
|
||
|
url := config.Host +
|
||
|
"/sap/bc/cts_abapvcs/repository/" + config.Repository +
|
||
|
"/getCommit?sap-client=" + config.Client
|
||
|
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, nil, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return commitResponse{}, errors.Wrap(httpErr, "get repository history failed")
|
||
|
} else if resp == nil {
|
||
|
return commitResponse{}, errors.New("get repository history failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &commitResp)
|
||
|
if parsingErr != nil {
|
||
|
return commitResponse{}, errors.Errorf("%v", parsingErr)
|
||
|
}
|
||
|
|
||
|
return commitResp, nil
|
||
|
}
|
||
|
|
||
|
func getObjectDifference(config *gctsExecuteABAPQualityChecksOptions, fromCommit string, toCommit string, client piperhttp.Sender) (objectsResponse, error) {
|
||
|
var objectResponse objectsResponse
|
||
|
|
||
|
url := config.Host +
|
||
|
"/sap/bc/cts_abapvcs/repository/" + config.Repository +
|
||
|
"/compareCommits?fromCommit=" + fromCommit + "&toCommit=" + toCommit + "&sap-client=" + config.Client
|
||
|
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, nil, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return objectsResponse{}, errors.Wrap(httpErr, "get object difference failed")
|
||
|
} else if resp == nil {
|
||
|
return objectsResponse{}, errors.New("get object difference failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &objectResponse)
|
||
|
if parsingErr != nil {
|
||
|
return objectsResponse{}, errors.Errorf("%v", parsingErr)
|
||
|
}
|
||
|
log.Entry().Info("get object differences: ", objectResponse.Objects)
|
||
|
return objectResponse, nil
|
||
|
}
|
||
|
|
||
|
func getObjectInfo(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objectName string, objectType string) (objectInfo, error) {
|
||
|
|
||
|
var objectMetInfoResponse objectInfo
|
||
|
url := config.Host +
|
||
|
"/sap/bc/cts_abapvcs/objects/" + objectType + "/" + objectName +
|
||
|
"?sap-client=" + config.Client
|
||
|
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, nil, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
if httpErr != nil {
|
||
|
return objectInfo{}, errors.Wrap(httpErr, "resolve package failed")
|
||
|
} else if resp == nil {
|
||
|
return objectInfo{}, errors.New("resolve package failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &objectMetInfoResponse)
|
||
|
if parsingErr != nil {
|
||
|
return objectInfo{}, errors.Errorf("%v", parsingErr)
|
||
|
}
|
||
|
return objectMetInfoResponse, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func getHistory(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (historyResponse, error) {
|
||
|
|
||
|
var historyResp historyResponse
|
||
|
url := config.Host +
|
||
|
"/sap/bc/cts_abapvcs/repository/" + config.Repository + "/getHistory?sap-client=" + config.Client
|
||
|
|
||
|
resp, httpErr := client.SendRequest("GET", url, nil, nil, nil)
|
||
|
|
||
|
defer func() {
|
||
|
if resp != nil && resp.Body != nil {
|
||
|
resp.Body.Close()
|
||
|
}
|
||
|
}()
|
||
|
if httpErr != nil {
|
||
|
return historyResponse{}, errors.Wrap(httpErr, "get history failed")
|
||
|
} else if resp == nil {
|
||
|
return historyResponse{}, errors.New("get history failed: did not retrieve a HTTP response")
|
||
|
}
|
||
|
|
||
|
parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &historyResp)
|
||
|
if parsingErr != nil {
|
||
|
return historyResponse{}, errors.Errorf("%v", parsingErr)
|
||
|
}
|
||
|
|
||
|
return historyResp, nil
|
||
|
}
|
||
|
|
||
|
type worklist struct {
|
||
|
XMLName xml.Name `xml:"worklist"`
|
||
|
Text string `xml:",chardata"`
|
||
|
ID string `xml:"id,attr"`
|
||
|
Timestamp string `xml:"timestamp,attr"`
|
||
|
UsedObjectSet string `xml:"usedObjectSet,attr"`
|
||
|
ObjectSetIsComplete string `xml:"objectSetIsComplete,attr"`
|
||
|
Atcworklist string `xml:"atcworklist,attr"`
|
||
|
ObjectSets struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
ObjectSet []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Name string `xml:"name,attr"`
|
||
|
Title string `xml:"title,attr"`
|
||
|
Kind string `xml:"kind,attr"`
|
||
|
} `xml:"objectSet"`
|
||
|
} `xml:"objectSets"`
|
||
|
Objects struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Object []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
URI string `xml:"uri,attr"`
|
||
|
Type string `xml:"type,attr"`
|
||
|
Name string `xml:"name,attr"`
|
||
|
PackageName string `xml:"packageName,attr"`
|
||
|
Author string `xml:"author,attr"`
|
||
|
Atcobject string `xml:"atcobject,attr"`
|
||
|
Adtcore string `xml:"adtcore,attr"`
|
||
|
Findings struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Finding []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
URI string `xml:"uri,attr"`
|
||
|
Location string `xml:"location,attr"`
|
||
|
Processor string `xml:"processor,attr"`
|
||
|
LastChangedBy string `xml:"lastChangedBy,attr"`
|
||
|
Priority string `xml:"priority,attr"`
|
||
|
CheckId string `xml:"checkId,attr"`
|
||
|
CheckTitle string `xml:"checkTitle,attr"`
|
||
|
MessageId string `xml:"messageId,attr"`
|
||
|
MessageTitle string `xml:"messageTitle,attr"`
|
||
|
ExemptionApproval string `xml:"exemptionApproval,attr"`
|
||
|
ExemptionKind string `xml:"exemptionKind,attr"`
|
||
|
Checksum string `xml:"checksum,attr"`
|
||
|
QuickfixInfo string `xml:"quickfixInfo,attr"`
|
||
|
Atcfinding string `xml:"atcfinding,attr"`
|
||
|
Link struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Href string `xml:"href,attr"`
|
||
|
Rel string `xml:"rel,attr"`
|
||
|
Type string `xml:"type,attr"`
|
||
|
Atom string `xml:"atom,attr"`
|
||
|
} `xml:"link"`
|
||
|
Quickfixes struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Manual string `xml:"manual,attr"`
|
||
|
Automatic string `xml:"automatic,attr"`
|
||
|
Pseudo string `xml:"pseudo,attr"`
|
||
|
} `xml:"quickfixes"`
|
||
|
} `xml:"finding"`
|
||
|
} `xml:"findings"`
|
||
|
} `xml:"object"`
|
||
|
} `xml:"objects"`
|
||
|
}
|
||
|
|
||
|
type runResult struct {
|
||
|
XMLName xml.Name `xml:"runResult"`
|
||
|
Text string `xml:",chardata"`
|
||
|
Aunit string `xml:"aunit,attr"`
|
||
|
Program []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
URI string `xml:"uri,attr"`
|
||
|
Type string `xml:"type,attr"`
|
||
|
Name string `xml:"name,attr"`
|
||
|
URIType string `xml:"uriType,attr"`
|
||
|
Adtcore string `xml:"adtcore,attr"`
|
||
|
Alerts struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Alert struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
HasSyntaxErrors string `xml:"hasSyntaxErrors,attr"`
|
||
|
Kind string `xml:"kind,attr"`
|
||
|
Severity string `xml:"severity,attr"`
|
||
|
Title string `xml:"title"`
|
||
|
Details struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Detail struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
AttrText string `xml:"text,attr"`
|
||
|
} `xml:"detail"`
|
||
|
} `xml:"details"`
|
||
|
Stack struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
StackEntry struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
URI string `xml:"uri,attr"`
|
||
|
Description string `xml:"description,attr"`
|
||
|
} `xml:"stackEntry"`
|
||
|
} `xml:"stack"`
|
||
|
} `xml:"alert"`
|
||
|
} `xml:"alerts"`
|
||
|
|
||
|
TestClasses struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
TestClass []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
URI string `xml:"uri,attr"`
|
||
|
Type string `xml:"type,attr"`
|
||
|
Name string `xml:"name,attr"`
|
||
|
URIType string `xml:"uriType,attr"`
|
||
|
NavigationURI string `xml:"navigationUri,attr"`
|
||
|
DurationCategory string `xml:"durationCategory,attr"`
|
||
|
RiskLevel string `xml:"riskLevel,attr"`
|
||
|
TestMethods struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
TestMethod []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
URI string `xml:"uri,attr"`
|
||
|
Type string `xml:"type,attr"`
|
||
|
Name string `xml:"name,attr"`
|
||
|
ExecutionTime string `xml:"executionTime,attr"`
|
||
|
URIType string `xml:"uriType,attr"`
|
||
|
NavigationURI string `xml:"navigationUri,attr"`
|
||
|
Unit string `xml:"unit,attr"`
|
||
|
Alerts struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Alert []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Kind string `xml:"kind,attr"`
|
||
|
Severity string `xml:"severity,attr"`
|
||
|
Title string `xml:"title"`
|
||
|
Details struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Detail []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
AttrText string `xml:"text,attr"`
|
||
|
Details struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Detail []struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
AttrText string `xml:"text,attr"`
|
||
|
} `xml:"detail"`
|
||
|
} `xml:"details"`
|
||
|
} `xml:"detail"`
|
||
|
} `xml:"details"`
|
||
|
Stack struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
StackEntry struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
URI string `xml:"uri,attr"`
|
||
|
Type string `xml:"type,attr"`
|
||
|
Name string `xml:"name,attr"`
|
||
|
Description string `xml:"description,attr"`
|
||
|
} `xml:"stackEntry"`
|
||
|
} `xml:"stack"`
|
||
|
} `xml:"alert"`
|
||
|
} `xml:"alerts"`
|
||
|
} `xml:"testMethod"`
|
||
|
} `xml:"testMethods"`
|
||
|
} `xml:"testClass"`
|
||
|
} `xml:"testClasses"`
|
||
|
} `xml:"program"`
|
||
|
}
|
||
|
|
||
|
type gctsException struct {
|
||
|
Message string `json:"message"`
|
||
|
Description string `json:"description"`
|
||
|
Code int `json:"code"`
|
||
|
}
|
||
|
|
||
|
type gctsLogs struct {
|
||
|
Time int `json:"time"`
|
||
|
User string `json:"user"`
|
||
|
Section string `json:"section"`
|
||
|
Action string `json:"action"`
|
||
|
Severity string `json:"severity"`
|
||
|
Message string `json:"message"`
|
||
|
Code string `json:"code"`
|
||
|
}
|
||
|
|
||
|
type commit struct {
|
||
|
ID string `json:"id"`
|
||
|
}
|
||
|
|
||
|
type commitResponse struct {
|
||
|
Commits []commit `json:"commits"`
|
||
|
ErrorLog []gctsLogs `json:"errorLog"`
|
||
|
Log []gctsLogs `json:"log"`
|
||
|
Exception gctsException `json:"exception"`
|
||
|
}
|
||
|
|
||
|
type objectInfo struct {
|
||
|
Pgmid string `json:"pgmid"`
|
||
|
Object string `json:"object"`
|
||
|
ObjName string `json:"objName"`
|
||
|
Srcsystem string `json:"srcsystem"`
|
||
|
Author string `json:"author"`
|
||
|
Devclass string `json:"devclass"`
|
||
|
}
|
||
|
|
||
|
type repoConfig struct {
|
||
|
Key string `json:"key"`
|
||
|
Value string `json:"value"`
|
||
|
Cprivate string `json:"cprivate"`
|
||
|
Cprotected string `json:"cprotected"`
|
||
|
Cvisible string `json:"cvisible"`
|
||
|
Category string `json:"category"`
|
||
|
Scope string `json:"scope"`
|
||
|
ChangedAt float64 `json:"changeAt"`
|
||
|
ChangedBy string `json:"changedBy"`
|
||
|
}
|
||
|
|
||
|
type repository struct {
|
||
|
Rid string `json:"rid"`
|
||
|
Name string `json:"name"`
|
||
|
Role string `json:"role"`
|
||
|
Type string `json:"type"`
|
||
|
Vsid string `json:"vsid"`
|
||
|
PrivateFlag string `json:"privateFlag"`
|
||
|
Status string `json:"status"`
|
||
|
Branch string `json:"branch"`
|
||
|
Url string `json:"url"`
|
||
|
CreatedBy string `json:"createdBy"`
|
||
|
CreatedDate string `json:"createdDate"`
|
||
|
Config []repoConfig `json:"config"`
|
||
|
Objects int `json:"objects"`
|
||
|
CurrentCommit string `json:"currentCommit"`
|
||
|
}
|
||
|
|
||
|
type repositoryResponse struct {
|
||
|
Result repository `json:"result"`
|
||
|
Exception gctsException `json:"exception"`
|
||
|
}
|
||
|
|
||
|
type objects struct {
|
||
|
Name string `json:"name"`
|
||
|
Type string `json:"type"`
|
||
|
Action string `json:"action"`
|
||
|
}
|
||
|
type objectsResponse struct {
|
||
|
Objects []objects `json:"objects"`
|
||
|
Log []gctsLogs `json:"log"`
|
||
|
Exception gctsException `json:"exception"`
|
||
|
ErrorLogs []gctsLogs `json:"errorLog"`
|
||
|
}
|
||
|
|
||
|
type repoObject struct {
|
||
|
Pgmid string `json:"pgmid"`
|
||
|
Object string `json:"object"`
|
||
|
Type string `json:"type"`
|
||
|
Description string `json:"description"`
|
||
|
}
|
||
|
|
||
|
type repoObjectResponse struct {
|
||
|
Objects []repoObject `json:"objects"`
|
||
|
Log []gctsLogs `json:"log"`
|
||
|
Exception gctsException `json:"exception"`
|
||
|
ErrorLogs []gctsLogs `json:"errorLog"`
|
||
|
}
|
||
|
|
||
|
type layout struct {
|
||
|
FormatVersion int `json:"formatVersion"`
|
||
|
Format string `json:"format"`
|
||
|
ObjectStorage string `json:"objectStorage"`
|
||
|
MetaInformation string `json:"metaInformation"`
|
||
|
TableContent string `json:"tableContent"`
|
||
|
Subdirectory string `json:"subdirectory"`
|
||
|
ReadableSource string `json:"readableSource"`
|
||
|
KeepClient string `json:"keepClient"`
|
||
|
}
|
||
|
|
||
|
type layoutResponse struct {
|
||
|
Layout layout `json:"layout"`
|
||
|
Log []gctsLogs `json:"log"`
|
||
|
Exception string `json:"exception"`
|
||
|
ErrorLogs []gctsLogs `json:"errorLog"`
|
||
|
}
|
||
|
|
||
|
type history struct {
|
||
|
Rid string `json:"rid"`
|
||
|
CheckoutTime int `json:"checkoutTime"`
|
||
|
FromCommit string `json:"fromCommit"`
|
||
|
ToCommit string `json:"toCommit"`
|
||
|
Caller string `json:"caller"`
|
||
|
Type string `json:"type"`
|
||
|
}
|
||
|
|
||
|
type historyResponse struct {
|
||
|
Result []history `xml:"result"`
|
||
|
Exception string `json:"exception"`
|
||
|
}
|
||
|
|
||
|
type checkstyleError struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Message string `xml:"message,attr"`
|
||
|
Source string `xml:"source,attr"`
|
||
|
Line string `xml:"line,attr"`
|
||
|
Severity string `xml:"severity,attr"`
|
||
|
}
|
||
|
|
||
|
type file struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Name string `xml:"name,attr"`
|
||
|
Error []checkstyleError `xml:"error"`
|
||
|
}
|
||
|
|
||
|
type checkstyle struct {
|
||
|
XMLName xml.Name `xml:"checkstyle"`
|
||
|
Text string `xml:",chardata"`
|
||
|
Version string `xml:"version,attr"`
|
||
|
File []file `xml:"file"`
|
||
|
}
|