1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-30 05:59:39 +02:00

Atc check extension and more user-friendly displays of results (#1951)

This commit is contained in:
Dominik Lendle 2020-08-26 14:09:00 +02:00 committed by GitHub
parent 13e5a943b2
commit c2b4ed819b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 149 additions and 41 deletions

View File

@ -10,6 +10,7 @@ import (
"net/http/cookiejar"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/SAP/jenkins-library/pkg/abaputils"
@ -53,7 +54,6 @@ func abapEnvironmentRunATCCheck(options abapEnvironmentRunATCCheckOptions, telem
client.SetOptions(clientOptions)
var details abaputils.ConnectionDetailsHTTP
var abapEndpoint string
//If Host flag is empty read ABAP endpoint from Service Key instead. Otherwise take ABAP system endpoint from config instead
if err == nil {
details, err = autils.GetAbapCommunicationArrangementInfo(subOptions, "")
@ -61,7 +61,6 @@ func abapEnvironmentRunATCCheck(options abapEnvironmentRunATCCheckOptions, telem
var resp *http.Response
//Fetch Xcrsf-Token
if err == nil {
abapEndpoint = details.URL
credentialsOptions := piperhttp.ClientOptions{
Username: details.User,
Password: details.Password,
@ -71,20 +70,22 @@ func abapEnvironmentRunATCCheck(options abapEnvironmentRunATCCheckOptions, telem
details.XCsrfToken, err = fetchXcsrfToken("GET", details, nil, &client)
}
if err == nil {
resp, err = triggerATCrun(options, details, &client, abapEndpoint)
resp, err = triggerATCrun(options, details, &client)
}
if err == nil {
err = handleATCresults(resp, details, &client, abapEndpoint)
err = handleATCresults(resp, details, &client, options.AtcResultsFileName)
}
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
log.Entry().Info("ATC run completed succesfully. The respective run results are listed above.")
log.Entry().Info("ATC run completed succesfully. If there are any results from the respective run they will be listed in the logs above as well as being saved in the output .xml file")
}
func handleATCresults(resp *http.Response, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, abapEndpoint string) error {
func handleATCresults(resp *http.Response, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, atcResultFileName string) error {
var err error
var abapEndpoint string
abapEndpoint = details.URL
location := resp.Header.Get("Location")
details.URL = abapEndpoint + location
location, err = pollATCRun(details, nil, client)
@ -99,7 +100,7 @@ func handleATCresults(resp *http.Response, details abaputils.ConnectionDetailsHT
}
if err == nil {
defer resp.Body.Close()
err = parseATCResult(body)
err = parseATCResult(body, atcResultFileName)
}
if err != nil {
return fmt.Errorf("Handling ATC result failed: %w", err)
@ -107,8 +108,9 @@ func handleATCresults(resp *http.Response, details abaputils.ConnectionDetailsHT
return nil
}
func triggerATCrun(config abapEnvironmentRunATCCheckOptions, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, abapEndpoint string) (*http.Response, error) {
func triggerATCrun(config abapEnvironmentRunATCCheckOptions, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (*http.Response, error) {
var atcConfigyamlFile []byte
abapEndpoint := details.URL
filelocation, err := filepath.Glob(config.AtcConfig)
//Parse YAML ATC run configuration as body for ATC run trigger
if err == nil {
@ -121,15 +123,14 @@ func triggerATCrun(config abapEnvironmentRunATCCheckOptions, details abaputils.C
result, err = yaml.YAMLToJSON(atcConfigyamlFile)
json.Unmarshal(result, &ATCConfig)
}
var packageString string
var softwareComponentString string
var checkVariantString, packageString, softwareComponentString string
if err == nil {
packageString, softwareComponentString, err = buildATCCheckBody(ATCConfig)
checkVariantString, packageString, softwareComponentString, err = buildATCCheckBody(ATCConfig)
}
//Trigger ATC run
var resp *http.Response
var bodyString = `<?xml version="1.0" encoding="UTF-8"?><atc:runparameters xmlns:atc="http://www.sap.com/adt/atc" xmlns:obj="http://www.sap.com/adt/objectset"><obj:objectSet>` + softwareComponentString + packageString + `</obj:objectSet></atc:runparameters>`
var bodyString = `<?xml version="1.0" encoding="UTF-8"?><atc:runparameters xmlns:atc="http://www.sap.com/adt/atc" xmlns:obj="http://www.sap.com/adt/objectset"` + checkVariantString + `><obj:objectSet>` + softwareComponentString + packageString + `</obj:objectSet></atc:runparameters>`
var body = []byte(bodyString)
if err == nil {
details.URL = abapEndpoint + "/sap/bc/adt/api/atc/runs?clientWait=false"
@ -141,13 +142,18 @@ func triggerATCrun(config abapEnvironmentRunATCCheckOptions, details abaputils.C
return resp, nil
}
func buildATCCheckBody(ATCConfig ATCconfig) (string, string, error) {
func buildATCCheckBody(ATCConfig ATCconfig) (checkVariantString string, packageString string, softwareComponentString string, err error) {
if len(ATCConfig.Objects.Package) == 0 && len(ATCConfig.Objects.SoftwareComponent) == 0 {
return "", "", fmt.Errorf("Error while parsing ATC run config. Please provide the packages and/or the software components to be checked! %w", errors.New("No Package or Software Component specified. Please provide either one or both of them"))
return "", "", "", fmt.Errorf("Error while parsing ATC run config. Please provide the packages and/or the software components to be checked! %w", errors.New("No Package or Software Component specified. Please provide either one or both of them"))
}
var packageString string
var softwareComponentString string
//Build Check Variant and Configuration XML Body
if len(ATCConfig.CheckVariant) != 0 {
checkVariantString += ` check_variant="` + ATCConfig.CheckVariant + `"`
if len(ATCConfig.Configuration) != 0 {
checkVariantString += ` configuration="` + ATCConfig.Configuration + `"`
}
}
//Build Package XML body
if len(ATCConfig.Objects.Package) != 0 {
@ -166,19 +172,26 @@ func buildATCCheckBody(ATCConfig ATCconfig) (string, string, error) {
}
softwareComponentString += "</obj:softwarecomponents>"
}
return packageString, softwareComponentString, nil
return checkVariantString, packageString, softwareComponentString, nil
}
func parseATCResult(body []byte) error {
func parseATCResult(body []byte, atcResultFileName string) (err error) {
if len(body) == 0 {
return fmt.Errorf("Parsing ATC result failed: %w", errors.New("Body is empty, can't parse empty body"))
}
parsedXML := new(Result)
xml.Unmarshal([]byte(body), &parsedXML)
err := ioutil.WriteFile("ATCResults.xml", body, 0644)
if err == nil {
if len(parsedXML.Files) == 0 {
log.Entry().Info("There were no results from this run, most likely the checked Software Components are empty or contain no ATC findings")
}
s := string(body)
if strings.HasPrefix(s, "<html>") {
return errors.New("The Software Component could not be checked. Please make sure the respective Software Component has been cloned succesfully on the system")
}
err = ioutil.WriteFile(atcResultFileName, body, 0644)
if err == nil && len(parsedXML.Files) > 0 {
var reports []piperutils.Path
reports = append(reports, piperutils.Path{Target: "ATCResults.xml", Name: "ATC Results", Mandatory: true})
reports = append(reports, piperutils.Path{Target: atcResultFileName, Name: "ATC Results", Mandatory: true})
piperutils.PersistReportsAndLinks("abapEnvironmentRunATCCheck", "", reports, nil)
for _, s := range parsedXML.Files {
for _, t := range s.ATCErrors {
@ -288,7 +301,9 @@ func getResultATCRun(requestType string, details abaputils.ConnectionDetailsHTTP
//ATCconfig object for parsing yaml config of software components and packages
type ATCconfig struct {
Objects ATCObjects `json:"atcobjects"`
CheckVariant string `json:"checkvariant,omitempty"`
Configuration string `json:"configuration,omitempty"`
Objects ATCObjects `json:"atcobjects"`
}
//ATCObjects in form of packages and software components to be checked

View File

@ -14,15 +14,16 @@ import (
)
type abapEnvironmentRunATCCheckOptions struct {
AtcConfig string `json:"atcConfig,omitempty"`
CfAPIEndpoint string `json:"cfApiEndpoint,omitempty"`
CfOrg string `json:"cfOrg,omitempty"`
CfServiceInstance string `json:"cfServiceInstance,omitempty"`
CfServiceKeyName string `json:"cfServiceKeyName,omitempty"`
CfSpace string `json:"cfSpace,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Host string `json:"host,omitempty"`
AtcConfig string `json:"atcConfig,omitempty"`
CfAPIEndpoint string `json:"cfApiEndpoint,omitempty"`
CfOrg string `json:"cfOrg,omitempty"`
CfServiceInstance string `json:"cfServiceInstance,omitempty"`
CfServiceKeyName string `json:"cfServiceKeyName,omitempty"`
CfSpace string `json:"cfSpace,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Host string `json:"host,omitempty"`
AtcResultsFileName string `json:"atcResultsFileName,omitempty"`
}
// AbapEnvironmentRunATCCheckCommand Runs an ATC Check
@ -96,6 +97,7 @@ func addAbapEnvironmentRunATCCheckFlags(cmd *cobra.Command, stepConfig *abapEnvi
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User or E-Mail for CF")
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "User Password for CF User")
cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "Specifies the host address of the SAP Cloud Platform ABAP Environment system")
cmd.Flags().StringVar(&stepConfig.AtcResultsFileName, "atcResultsFileName", `ATCResults.xml`, "Specifies output file name for the results from the ATC run")
cmd.MarkFlagRequired("atcConfig")
cmd.MarkFlagRequired("username")
@ -184,6 +186,14 @@ func abapEnvironmentRunATCCheckMetadata() config.StepData {
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "atcResultsFileName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},
},

View File

@ -247,34 +247,54 @@ func TestParseATCResult(t *testing.T) {
</file>
</checkstyle>`
body := []byte(bodyString)
err = parseATCResult(body)
err = parseATCResult(body, "ATCResults.xml")
assert.Equal(t, nil, err)
})
t.Run("failure case: parsing empty xml", func(t *testing.T) {
var bodyString string
body := []byte(bodyString)
err := parseATCResult(body)
err := parseATCResult(body, "ATCResults.xml")
assert.EqualError(t, err, "Parsing ATC result failed: Body is empty, can't parse empty body")
})
t.Run("failure case: html response", func(t *testing.T) {
dir, err := ioutil.TempDir("", "test get result ATC run")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
oldCWD, _ := os.Getwd()
_ = os.Chdir(dir)
// clean up tmp dir
defer func() {
_ = os.Chdir(oldCWD)
_ = os.RemoveAll(dir)
}()
bodyString := `<html><head><title>HTMLTestResponse</title</head></html>`
body := []byte(bodyString)
err = parseATCResult(body, "ATCResults.xml")
assert.EqualError(t, err, "The Software Component could not be checked. Please make sure the respective Software Component has been cloned succesfully on the system")
})
}
func TestBuildATCCheckBody(t *testing.T) {
t.Run("Test build body with no software component and package", func(t *testing.T) {
expectedpackagestring := ""
expectedsoftwarecomponentstring := ""
expectedcheckvariantstring := ""
var err error
var config ATCconfig
var packageString, softwarecomponentString string
var checkVariantString, packageString, softwarecomponentString string
packageString, softwarecomponentString, err = buildATCCheckBody(config)
checkVariantString, packageString, softwarecomponentString, err = buildATCCheckBody(config)
assert.Equal(t, expectedcheckvariantstring, checkVariantString)
assert.Equal(t, expectedpackagestring, packageString)
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
assert.EqualError(t, err, "Error while parsing ATC run config. Please provide the packages and/or the software components to be checked! No Package or Software Component specified. Please provide either one or both of them")
})
t.Run("success case: Test build body with example yaml config", func(t *testing.T) {
expectedcheckvariantstring := ""
expectedpackagestring := "<obj:packages><obj:package value=\"testPackage\" includeSubpackages=\"true\"/><obj:package value=\"testPackage2\" includeSubpackages=\"false\"/></obj:packages>"
expectedsoftwarecomponentstring := "<obj:softwarecomponents><obj:softwarecomponent value=\"testSoftwareComponent\"/><obj:softwarecomponent value=\"testSoftwareComponent2\"/></obj:softwarecomponents>"
@ -282,6 +302,8 @@ func TestBuildATCCheckBody(t *testing.T) {
var config ATCconfig
config = ATCconfig{
"",
"",
ATCObjects{
Package: []Package{
Package{Name: "testPackage", IncludeSubpackages: true},
@ -294,15 +316,17 @@ func TestBuildATCCheckBody(t *testing.T) {
},
}
var packageString, softwarecomponentString string
var checkvariantString, packageString, softwarecomponentString string
packageString, softwarecomponentString, err = buildATCCheckBody(config)
checkvariantString, packageString, softwarecomponentString, err = buildATCCheckBody(config)
assert.Equal(t, expectedcheckvariantstring, checkvariantString)
assert.Equal(t, expectedpackagestring, packageString)
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
assert.Equal(t, nil, err)
})
t.Run("failure case: Test build body with example yaml config with only packages and no software components", func(t *testing.T) {
expectedcheckvariantstring := ""
expectedpackagestring := `<obj:packages><obj:package value="testPackage" includeSubpackages="true"/><obj:package value="testPackage2" includeSubpackages="false"/></obj:packages>`
expectedsoftwarecomponentstring := ""
@ -310,6 +334,8 @@ func TestBuildATCCheckBody(t *testing.T) {
var config ATCconfig
config = ATCconfig{
"",
"",
ATCObjects{
Package: []Package{
Package{Name: "testPackage", IncludeSubpackages: true},
@ -318,16 +344,18 @@ func TestBuildATCCheckBody(t *testing.T) {
},
}
var packageString, softwarecomponentString string
var checkvariantString, packageString, softwarecomponentString string
packageString, softwarecomponentString, err = buildATCCheckBody(config)
checkvariantString, packageString, softwarecomponentString, err = buildATCCheckBody(config)
assert.Equal(t, expectedcheckvariantstring, checkvariantString)
assert.Equal(t, expectedpackagestring, packageString)
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
assert.Equal(t, nil, err)
})
t.Run("success case: Test build body with example yaml config with no packages and only software components", func(t *testing.T) {
expectedcheckvariantstring := ""
expectedpackagestring := ""
expectedsoftwarecomponentstring := `<obj:softwarecomponents><obj:softwarecomponent value="testSoftwareComponent"/><obj:softwarecomponent value="testSoftwareComponent2"/></obj:softwarecomponents>`
@ -335,6 +363,8 @@ func TestBuildATCCheckBody(t *testing.T) {
var config ATCconfig
config = ATCconfig{
"",
"",
ATCObjects{
SoftwareComponent: []SoftwareComponent{
SoftwareComponent{Name: "testSoftwareComponent"},
@ -343,10 +373,43 @@ func TestBuildATCCheckBody(t *testing.T) {
},
}
var packageString, softwarecomponentString string
var checkvariantString, packageString, softwarecomponentString string
packageString, softwarecomponentString, err = buildATCCheckBody(config)
checkvariantString, packageString, softwarecomponentString, err = buildATCCheckBody(config)
assert.Equal(t, expectedcheckvariantstring, checkvariantString)
assert.Equal(t, expectedpackagestring, packageString)
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
assert.Equal(t, nil, err)
})
t.Run("success case: Test build body with example yaml config with check variant configuration", func(t *testing.T) {
expectedcheckvariantstring := ` check_variant="TestVariant" configuration="TestConfiguration"`
expectedpackagestring := `<obj:packages><obj:package value="testPackage" includeSubpackages="true"/><obj:package value="testPackage2" includeSubpackages="false"/></obj:packages>`
expectedsoftwarecomponentstring := `<obj:softwarecomponents><obj:softwarecomponent value="testSoftwareComponent"/><obj:softwarecomponent value="testSoftwareComponent2"/></obj:softwarecomponents>`
var err error
var config ATCconfig
config = ATCconfig{
"TestVariant",
"TestConfiguration",
ATCObjects{
SoftwareComponent: []SoftwareComponent{
SoftwareComponent{Name: "testSoftwareComponent"},
SoftwareComponent{Name: "testSoftwareComponent2"},
},
Package: []Package{
Package{Name: "testPackage", IncludeSubpackages: true},
Package{Name: "testPackage2", IncludeSubpackages: false},
},
},
}
var checkvariantString, packageString, softwarecomponentString string
checkvariantString, packageString, softwarecomponentString, err = buildATCCheckBody(config)
assert.Equal(t, expectedcheckvariantstring, checkvariantString)
assert.Equal(t, expectedpackagestring, packageString)
assert.Equal(t, expectedsoftwarecomponentstring, softwarecomponentString)
assert.Equal(t, nil, err)

View File

@ -123,3 +123,14 @@ atcobjects:
- name: "TestComponent"
- name: "TestComponent2"
```
The following is an example of an `atcconfig.yml` file that supports the check variant and configuration ATC options:
```yaml
checkvariant: "TestCheckVariant"
configuration: "TestConfiguration"
atcobjects:
softwarecomponent:
- name: "TestComponent"
- name: "TestComponent2"
```

View File

@ -109,6 +109,15 @@ spec:
- STEPS
- GENERAL
mandatory: false
- name: atcResultsFileName
type: string
description: Specifies output file name for the results from the ATC run
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: false
default: 'ATCResults.xml'
containers:
- name: cf
image: ppiper/cf-cli