github.com/xgoffin/jenkins-library@v1.154.0/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  }