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

Add Html output to ATC step (#2761)

* Adding HTML Output

* testing

* testing

* change logging

* change logging

* change logging

* Refactoring

* Add metadata to HTML file

* Change parameter name from sendEmail to generateHTML

* Add sorting and test

* Increasing sorting performance
This commit is contained in:
Dominik Lendle 2021-04-21 20:13:02 +02:00 committed by GitHub
parent e5dcc21bad
commit 9168757810
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 11 deletions

View File

@ -64,7 +64,7 @@ func abapEnvironmentRunATCCheck(options abapEnvironmentRunATCCheckOptions, telem
resp, err = triggerATCrun(options, details, &client)
}
if err == nil {
err = handleATCresults(resp, details, &client, options.AtcResultsFileName)
err = handleATCresults(resp, details, &client, options.AtcResultsFileName, options.GenerateHTML)
}
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
@ -73,7 +73,7 @@ func abapEnvironmentRunATCCheck(options abapEnvironmentRunATCCheckOptions, telem
log.Entry().Info("ATC run completed successfully. 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, atcResultFileName string) error {
func handleATCresults(resp *http.Response, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, atcResultFileName string, generateHTML bool) error {
var err error
var abapEndpoint string
abapEndpoint = details.URL
@ -91,7 +91,7 @@ func handleATCresults(resp *http.Response, details abaputils.ConnectionDetailsHT
}
if err == nil {
defer resp.Body.Close()
err = parseATCResult(body, atcResultFileName)
err = parseATCResult(body, atcResultFileName, generateHTML)
}
if err != nil {
return fmt.Errorf("Handling ATC result failed: %w", err)
@ -174,7 +174,7 @@ func buildATCCheckBody(ATCConfig ATCconfig) (checkVariantString string, packageS
return checkVariantString, packageString, softwareComponentString, nil
}
func parseATCResult(body []byte, atcResultFileName string) (err error) {
func parseATCResult(body []byte, atcResultFileName string, generateHTML bool) (err error) {
if len(body) == 0 {
return fmt.Errorf("Parsing ATC result failed: %w", errors.New("Body is empty, can't parse empty body"))
}
@ -193,17 +193,28 @@ func parseATCResult(body []byte, atcResultFileName string) (err error) {
err = ioutil.WriteFile(atcResultFileName, body, 0644)
if err == nil {
log.Entry().Infof("Writing %s file was successful", atcResultFileName)
var reports []piperutils.Path
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 {
log.Entry().Infof("%s in file '%s': %s in line %s found by %s", t.Severity, s.Key, t.Message, t.Line, t.Source)
}
}
if generateHTML == true {
htmlString := generateHTMLDocument(parsedXML)
htmlStringByte := []byte(htmlString)
atcResultHTMLFileName := strings.Trim(atcResultFileName, ".xml") + ".html"
err = ioutil.WriteFile(atcResultHTMLFileName, htmlStringByte, 0644)
if err == nil {
log.Entry().Info("Writing " + atcResultHTMLFileName + " file was successful")
reports = append(reports, piperutils.Path{Target: atcResultFileName, Name: "ATC Results HTML file", Mandatory: true})
}
}
piperutils.PersistReportsAndLinks("abapEnvironmentRunATCCheck", "", reports, nil)
}
if err != nil {
return fmt.Errorf("Writing results to XML file failed: %w", err)
return fmt.Errorf("Writing results failed: %w", err)
}
return nil
}
@ -321,6 +332,35 @@ func convertATCOptions(options *abapEnvironmentRunATCCheckOptions) abaputils.Aba
return subOptions
}
func generateHTMLDocument(parsedXML *Result) (htmlDocumentString string) {
htmlDocumentString = `<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><title>ATC Results</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><style>table,th,td {border: 1px solid black;border-collapse:collapse;}th,td{padding: 5px;text-align:left;font-size:medium;}</style></head><body><h1 style="text-align:left;font-size:large">ATC Results</h1><table style="width:100%"><tr><th>Severity</th><th>File</th><th>Message</th><th>Line</th><th>Checked by</th></tr>`
var htmlDocumentStringError, htmlDocumentStringWarning, htmlDocumentStringInfo, htmlDocumentStringDefault string
for _, s := range parsedXML.Files {
for _, t := range s.ATCErrors {
var trBackgroundColor string
if t.Severity == "error" {
trBackgroundColor = "rgba(227,85,0)"
htmlDocumentStringError += `<tr style="background-color: ` + trBackgroundColor + `">` + `<td>` + t.Severity + `</td>` + `<td>` + s.Key + `</td>` + `<td>` + t.Message + `</td>` + `<td style="text-align:center">` + t.Line + `</td>` + `<td>` + t.Source + `</td>` + `</tr>`
}
if t.Severity == "warning" {
trBackgroundColor = "rgba(255,175,0, 0.75)"
htmlDocumentStringWarning += `<tr style="background-color: ` + trBackgroundColor + `">` + `<td>` + t.Severity + `</td>` + `<td>` + s.Key + `</td>` + `<td>` + t.Message + `</td>` + `<td style="text-align:center">` + t.Line + `</td>` + `<td>` + t.Source + `</td>` + `</tr>`
}
if t.Severity == "info" {
trBackgroundColor = "rgba(255,175,0, 0.2)"
htmlDocumentStringInfo += `<tr style="background-color: ` + trBackgroundColor + `">` + `<td>` + t.Severity + `</td>` + `<td>` + s.Key + `</td>` + `<td>` + t.Message + `</td>` + `<td style="text-align:center">` + t.Line + `</td>` + `<td>` + t.Source + `</td>` + `</tr>`
}
if t.Severity != "info" && t.Severity != "warning" && t.Severity != "error" {
trBackgroundColor = "rgba(255,175,0, 0)"
htmlDocumentStringDefault += `<tr style="background-color: ` + trBackgroundColor + `">` + `<td>` + t.Severity + `</td>` + `<td>` + s.Key + `</td>` + `<td>` + t.Message + `</td>` + `<td style="text-align:center">` + t.Line + `</td>` + `<td>` + t.Source + `</td>` + `</tr>`
}
}
}
htmlDocumentString += htmlDocumentStringError + htmlDocumentStringWarning + htmlDocumentStringInfo + htmlDocumentStringDefault + `</table></body></html>`
return htmlDocumentString
}
//ATCconfig object for parsing yaml config of software components and packages
type ATCconfig struct {
CheckVariant string `json:"checkvariant,omitempty"`

View File

@ -24,6 +24,7 @@ type abapEnvironmentRunATCCheckOptions struct {
Password string `json:"password,omitempty"`
Host string `json:"host,omitempty"`
AtcResultsFileName string `json:"atcResultsFileName,omitempty"`
GenerateHTML bool `json:"generateHTML,omitempty"`
}
// AbapEnvironmentRunATCCheckCommand Runs an ATC Check
@ -102,6 +103,7 @@ func addAbapEnvironmentRunATCCheckFlags(cmd *cobra.Command, stepConfig *abapEnvi
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password for either the Cloud Foundry API or the Communication Arrangement for SAP_COM_0510")
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.Flags().BoolVar(&stepConfig.GenerateHTML, "generateHTML", false, "Specifies whether the ATC results should also be generated as an HTML document")
cmd.MarkFlagRequired("atcConfig")
cmd.MarkFlagRequired("username")
@ -211,6 +213,14 @@ func abapEnvironmentRunATCCheckMetadata() config.StepData {
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "generateHTML",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},
Containers: []config.Container{

View File

@ -1,6 +1,7 @@
package cmd
import (
"encoding/xml"
"io/ioutil"
"os"
"testing"
@ -247,7 +248,7 @@ func TestParseATCResult(t *testing.T) {
</file>
</checkstyle>`
body := []byte(bodyString)
err = parseATCResult(body, "ATCResults.xml")
err = parseATCResult(body, "ATCResults.xml", false)
assert.Equal(t, nil, err)
})
t.Run("succes case: test parsing empty XML result", func(t *testing.T) {
@ -266,14 +267,14 @@ func TestParseATCResult(t *testing.T) {
<checkstyle>
</checkstyle>`
body := []byte(bodyString)
err = parseATCResult(body, "ATCResults.xml")
err = parseATCResult(body, "ATCResults.xml", false)
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, "ATCResults.xml")
err := parseATCResult(body, "ATCResults.xml", false)
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) {
@ -290,7 +291,7 @@ func TestParseATCResult(t *testing.T) {
}()
bodyString := `<html><head><title>HTMLTestResponse</title</head></html>`
body := []byte(bodyString)
err = parseATCResult(body, "ATCResults.xml")
err = parseATCResult(body, "ATCResults.xml", false)
assert.EqualError(t, err, "The Software Component could not be checked. Please make sure the respective Software Component has been cloned successfully on the system")
})
}
@ -434,3 +435,31 @@ func TestBuildATCCheckBody(t *testing.T) {
assert.Equal(t, nil, err)
})
}
func TestGenerateHTMLDocument(t *testing.T) {
//Failure case is not needed --> all failing cases would be depended on parsedXML *Result which is covered in TestParseATCResult
t.Run("success case: html response", func(t *testing.T) {
expectedResult := "<!DOCTYPE html><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>ATC Results</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /><style>table,th,td {border: 1px solid black;border-collapse:collapse;}th,td{padding: 5px;text-align:left;font-size:medium;}</style></head><body><h1 style=\"text-align:left;font-size:large\">ATC Results</h1><table style=\"width:100%\"><tr><th>Severity</th><th>File</th><th>Message</th><th>Line</th><th>Checked by</th></tr><tr style=\"background-color: rgba(227,85,0)\"><td>error</td><td>testFile2</td><td>testMessage</td><td style=\"text-align:center\">1</td><td>sourceTester</td></tr><tr style=\"background-color: rgba(255,175,0, 0.75)\"><td>warning</td><td>testFile</td><td>testMessage2</td><td style=\"text-align:center\">2</td><td>sourceTester</td></tr><tr style=\"background-color: rgba(255,175,0, 0.2)\"><td>info</td><td>testFile</td><td>testMessage1</td><td style=\"text-align:center\">1</td><td>sourceTester</td></tr></table></body></html>"
bodyString := `<?xml version="1.0" encoding="UTF-8"?>
<checkstyle>
<file name="testFile">
<error message="testMessage1" source="sourceTester" line="1" severity="info">
</error>
<error message="testMessage2" source="sourceTester" line="2" severity="warning">
</error>
</file>
<file name="testFile2">
<error message="testMessage" source="sourceTester" line="1" severity="error">
</error>
</file>
</checkstyle>`
parsedXML := new(Result)
err := xml.Unmarshal([]byte(bodyString), &parsedXML)
if assert.NoError(t, err) {
htmlDocumentResult := generateHTMLDocument(parsedXML)
assert.Equal(t, expectedResult, htmlDocumentResult)
}
})
}

View File

@ -176,7 +176,7 @@ return this
While `tools: [checkStyle(pattern: '**/**/ATCResults.xml')]` will display the ATC findings using the checkstyle format, `qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]` will set the build result to UNSTABLE in case the ATC results contain at least one warning or error.
!!! caution "Local Jenkins"
If you are using a local Jenkins you may have to [adapt the Jenkins URL](https://stackoverflow.com/a/39543223) in the configuration if the CheckStyl Plugin shows this error: "Can't create fingerprints for some files".
If you are using a local Jenkins you may have to [adapt the Jenkins URL](https://stackoverflow.com/a/39543223) in the configuration if the CheckStyle Plugin shows this error: "Can't create fingerprints for some files".
### Stage Names

View File

@ -128,6 +128,14 @@ spec:
- STEPS
mandatory: false
default: "ATCResults.xml"
- name: generateHTML
type: bool
description: Specifies whether the ATC results should also be generated as an HTML document
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: false
containers:
- name: cf
image: ppiper/cf-cli:7