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