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:
parent
13e5a943b2
commit
c2b4ed819b
@ -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
|
||||
|
@ -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{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
```
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user