mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-18 05:18:24 +02:00
Add DebugReport facility
The DebugReport is a global instance where steps can store information relevant for diagnosing failed pipelines. In the SDK Pipeline, this is used to generate a debug report within the postActionArchiveDebugLog step. The reason for adding this to Piper is to feed information about extended or overwritten stages in piperStageWrapper into the DebugReport, as was done before in the SDK Pipeline's equivalent runAsStage step.
This commit is contained in:
parent
941eba0472
commit
6e551dfc79
101
resources/debug_report.txt
Normal file
101
resources/debug_report.txt
Normal file
@ -0,0 +1,101 @@
|
||||
# SAP Cloud SDK `<% print shareConfidentialInformation ? "Confidential" : "Redacted" %>` Debug Log
|
||||
|
||||
The debug log is generated with each build and should be included in every support request, don't worry in case you don't understand all of its contents.
|
||||
|
||||
The log was generated at: `$utcTimestamp UTC`
|
||||
|
||||
#### Please be sure that this file doesn't contain any information that you are not allowed to share.
|
||||
|
||||
<% print failedBuild ? "## Unsuccessful Build" : "" %>
|
||||
|
||||
<% print failedBuild ? "#### Failed Stage" : "" %>
|
||||
<% print failedBuild.get("stage") ? "`${failedBuild.get("stage")}`" : "" %>
|
||||
|
||||
<% print failedBuild.get("reason") ? "#### Error Message" : "" %>
|
||||
<% print failedBuild.get("reason") ? "```\n${failedBuild.get("reason")}\n```" : "" %>
|
||||
<% print failedBuild.get("stack_trace") ? "#### Stacktrace" : "" %>
|
||||
<% print failedBuild.get("stack_trace") ? "```" : "" %>
|
||||
<% print failedBuild.get("stack_trace").collect({each -> "${each}"}).join("\n") %>
|
||||
<% print failedBuild.get("stack_trace") ? "```" : "" %>
|
||||
|
||||
## Pipeline Environment
|
||||
|
||||
#### Environment
|
||||
`${environment.get("environment")}`
|
||||
|
||||
#### Environment Variables
|
||||
Environment Variable | Value
|
||||
---------------------|------
|
||||
<% print environment.get("build_details").collect({each -> "${each}"}).join("\n") %>
|
||||
|
||||
#### Docker Image
|
||||
`${environment.get("docker_image")}`
|
||||
|
||||
## Build Tools Environment
|
||||
|
||||
#### Build Tool
|
||||
`${buildTool}`
|
||||
|
||||
<% print modulesMap && shareConfidentialInformation ? "#### MTA Modules" : "" %>
|
||||
|
||||
<% print modulesMap && shareConfidentialInformation ? "Type | Path" : "" %>
|
||||
<% print modulesMap && shareConfidentialInformation ? "-----|-----" : "" %>
|
||||
<% print modulesMap && shareConfidentialInformation ? modulesMap.collect({each -> "${each.key} | ${each.value}"}).join("\n") : "" %>
|
||||
|
||||
<% print npmModules && shareConfidentialInformation ? "#### NPM Modules" : "" %>
|
||||
|
||||
<% print npmModules && shareConfidentialInformation ? "Path | Scripts" : "" %>
|
||||
<% print npmModules && shareConfidentialInformation ? "-----|--------" : "" %>
|
||||
<% print npmModules && shareConfidentialInformation ? npmModules.collect({each -> "${each.basePath} | ${each.npmScripts}"}).join("\n") : "" %>
|
||||
|
||||
|
||||
## Plugins
|
||||
|
||||
<% print plugins ? "" : "No plugins were used for this build." %>
|
||||
|
||||
<details><summary>Full list of plugins</summary>
|
||||
<p>
|
||||
|
||||
<% print plugins ? "Shortname | Version | Displayname" : "" %>
|
||||
<% print plugins ? "----------|---------|------------" : "" %>
|
||||
<% print plugins.collect({each -> "${each}"}).join("\n") %>
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<% print shareConfidentialInformation ? "## Git Repository" : "" %>
|
||||
|
||||
<% print shareConfidentialInformation ? gitRepo ? "" : "Git isn't used for version control" : "" %>
|
||||
|
||||
<% print gitRepo && shareConfidentialInformation ? "Repository | Branch" : "" %>
|
||||
<% print gitRepo && shareConfidentialInformation ? "-----------|-------" : "" %>
|
||||
<% print gitRepo && shareConfidentialInformation ? "${gitRepo.get("URI")} | ${gitRepo.get("branch")}" : "" %>
|
||||
|
||||
## Local Extensions
|
||||
|
||||
<% print localExtensions ? "" : "No local extensions were used" %>
|
||||
|
||||
<% print localExtensions ? "Local Extension | relationToOriginalStage" : "" %>
|
||||
<% print localExtensions ? "----------------|-------" : "" %>
|
||||
<% print localExtensions.collect({each -> "${each.key} | ${each.value}"}).join("\n") %>
|
||||
|
||||
## Global Extension Repository
|
||||
|
||||
<% print shareConfidentialInformation ? globalExtensionRepository ? "${globalExtensionRepository}" : "No global extension repository was loaded." : "" %>
|
||||
<% print globalExtensions ? "The repository included the following extensions:" : "No extension of the global extension repository was used." %>
|
||||
|
||||
<% print globalExtensions ? "Global Extension | relationToOriginalStage" : "" %>
|
||||
<% print globalExtensions ? "-----------------|-------" : "" %>
|
||||
<% print globalExtensions.collect({each -> "${each.key} | ${each.value}"}).join("\n") %>
|
||||
|
||||
Configuration file for global extensions: <% print globalExtensionConfigurationFilePath ?: "Global extensions don't have a configuration file" %>
|
||||
|
||||
Shared project configuration: <% print shareConfidentialInformation ? sharedConfigFilePath ?: "No shared configuration" : sharedConfigFilePath != null %>
|
||||
|
||||
<% print shareConfidentialInformation ? "## Shared Libraries" : "" %>
|
||||
|
||||
<% print shareConfidentialInformation ? additionalSharedLibraries ? "" : "No additional shared libraries where loaded." : "" %>
|
||||
|
||||
<% print additionalSharedLibraries && shareConfidentialInformation ? "name | branch | loadedByExtension" : "" %>
|
||||
<% print additionalSharedLibraries && shareConfidentialInformation ? "-----|--------|-------" : "" %>
|
||||
<% print shareConfidentialInformation ? additionalSharedLibraries.collect({each -> "${each}"}).join("\n") : "" %>
|
132
src/com/sap/piper/DebugReport.groovy
Normal file
132
src/com/sap/piper/DebugReport.groovy
Normal file
@ -0,0 +1,132 @@
|
||||
package com.sap.piper
|
||||
|
||||
import com.cloudbees.groovy.cps.NonCPS
|
||||
import groovy.text.SimpleTemplateEngine
|
||||
|
||||
@Singleton
|
||||
class DebugReport {
|
||||
String fileName
|
||||
String projectIdentifier = null
|
||||
Map environment = ['environment': 'custom']
|
||||
String buildTool = null
|
||||
Map modulesMap = [:]
|
||||
List npmModules = []
|
||||
Set plugins = []
|
||||
Map gitRepo = [:]
|
||||
Map localExtensions = [:]
|
||||
String globalExtensionRepository = null
|
||||
Map globalExtensions = [:]
|
||||
String globalExtensionConfigurationFilePath = null
|
||||
String sharedConfigFilePath = null
|
||||
Set additionalSharedLibraries = []
|
||||
Map failedBuild = [:]
|
||||
boolean shareConfidentialInformation
|
||||
|
||||
/**
|
||||
* Initialize debug report information from the environment variables.
|
||||
*
|
||||
* @param env The Jenkins global 'env' variable.
|
||||
*/
|
||||
void initFromEnvironment(def env) {
|
||||
Set buildDetails = []
|
||||
buildDetails.add('Jenkins Version | ' + env.JENKINS_VERSION)
|
||||
buildDetails.add('JAVA Version | ' + env.JAVA_VERSION)
|
||||
environment.put('build_details', buildDetails)
|
||||
|
||||
if (!Boolean.valueOf(env.ON_K8S) && EnvironmentUtils.cxServerDirectoryExists()) {
|
||||
environment.put('environment', 'cx-server')
|
||||
|
||||
String serverConfigContents = getServerConfigContents(
|
||||
'/var/cx-server/server.cfg',
|
||||
'/workspace/var/cx-server/server.cfg')
|
||||
String docker_image = EnvironmentUtils.getDockerFile(serverConfigContents)
|
||||
environment.put('docker_image', docker_image)
|
||||
}
|
||||
}
|
||||
|
||||
private static String getServerConfigContents(String... possibleFileLocations) {
|
||||
for (String location in possibleFileLocations) {
|
||||
File file = new File(location)
|
||||
if (file.exists())
|
||||
return file.getText('UTF-8')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls and stores repository information from the provided Map for later inclusion in the debug report.
|
||||
*
|
||||
* @param scmCheckoutResult A Map including information about the checked out project,
|
||||
* i.e. as returned by the Jenkins checkout() function.
|
||||
*/
|
||||
void setGitRepoInfo(Map scmCheckoutResult) {
|
||||
if (!scmCheckoutResult.GIT_URL)
|
||||
return
|
||||
|
||||
gitRepo.put('URI', scmCheckoutResult.GIT_URL)
|
||||
if (scmCheckoutResult.GIT_LOCAL_BRANCH) {
|
||||
gitRepo.put('branch', scmCheckoutResult.GIT_LOCAL_BRANCH)
|
||||
} else {
|
||||
gitRepo.put('branch', scmCheckoutResult.GIT_BRANCH)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores crash information for a failed step. Multiple calls to this method overwrite already
|
||||
* stored information, only the information stored last will appear in the debug report. In the
|
||||
* current use-case where this can be called multiple times, all 'unstable' steps are listed in
|
||||
* the 'unstableSteps' entry of the commonPipelineEnvironment.
|
||||
*
|
||||
* @param stepName The name of the crashed step or stage
|
||||
* @param err The Throwable that was thrown
|
||||
* @param isResilientAndNotMandatory
|
||||
* Whether the step configuration indicated that this as
|
||||
* "resilient" step which is not included in the list of mandatory steps.
|
||||
*/
|
||||
void storeStepFailure(String stepName, Throwable err, boolean isResilientAndNotMandatory) {
|
||||
failedBuild.put('stage', stepName)
|
||||
failedBuild.put('reason', err)
|
||||
failedBuild.put('stack_trace', err.getStackTrace())
|
||||
if (isResilientAndNotMandatory) {
|
||||
failedBuild.put('isResilient', 'true')
|
||||
} else {
|
||||
failedBuild.remove('isResilient')
|
||||
}
|
||||
}
|
||||
|
||||
String generateReport(Script script) {
|
||||
String template = script.libraryResource 'debug_report.txt'
|
||||
|
||||
if (!projectIdentifier) {
|
||||
projectIdentifier = 'NOT_SET'
|
||||
}
|
||||
|
||||
try {
|
||||
Jenkins.instance.getPluginManager().getPlugins().each {
|
||||
plugins.add("${it.getShortName()} | ${it.getVersion()} | ${it.getDisplayName()}")
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
Map binding = getProperties()
|
||||
Date now = new Date()
|
||||
|
||||
binding.utcTimestamp = now.format('yyyy-MM-dd HH:mm', TimeZone.getTimeZone('UTC'))
|
||||
String fileNameTimestamp = now.format('yyyy-MM-dd-HH-mm', TimeZone.getTimeZone('UTC'))
|
||||
|
||||
if (shareConfidentialInformation) {
|
||||
fileName = "confidential_debug_log_${fileNameTimestamp}_${projectIdentifier}.txt"
|
||||
} else {
|
||||
fileName = "redacted_debug_log_${fileNameTimestamp}_${projectIdentifier}.txt"
|
||||
}
|
||||
|
||||
return fillTemplate(template, binding)
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
private String fillTemplate(String template, binding) {
|
||||
def engine = new SimpleTemplateEngine()
|
||||
return engine.createTemplate(template).make(binding)
|
||||
}
|
||||
}
|
91
test/groovy/com/sap/piper/DebugReportTest.groovy
Normal file
91
test/groovy/com/sap/piper/DebugReportTest.groovy
Normal file
@ -0,0 +1,91 @@
|
||||
package com.sap.piper
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import util.BasePiperTest
|
||||
import util.Rules
|
||||
|
||||
class DebugReportTest extends BasePiperTest {
|
||||
|
||||
@Rule
|
||||
public RuleChain ruleChain = Rules.getCommonRules(this)
|
||||
|
||||
@Test
|
||||
void testInitFromEnvironment() {
|
||||
Map env = createEnv()
|
||||
DebugReport.instance.initFromEnvironment(env)
|
||||
|
||||
Assert.assertTrue(DebugReport.instance.environment.containsKey('build_details'))
|
||||
Assert.assertEquals('custom', DebugReport.instance.environment.get('environment'))
|
||||
|
||||
Set<String> buildDetails = DebugReport.instance.environment.build_details as Set<String>
|
||||
Assert.assertTrue(buildDetails.size() > 0)
|
||||
|
||||
boolean foundJenkinsVersion = false
|
||||
boolean foundJavaVersion = false
|
||||
|
||||
for (String details in buildDetails) {
|
||||
if (details.contains(env.get('JENKINS_VERSION') as String))
|
||||
foundJenkinsVersion = true
|
||||
if (details.contains(env.get('JAVA_VERSION') as String))
|
||||
foundJavaVersion = true
|
||||
}
|
||||
Assert.assertTrue(foundJenkinsVersion)
|
||||
Assert.assertTrue(foundJavaVersion)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogOutput() {
|
||||
DebugReport.instance.initFromEnvironment(createEnv())
|
||||
DebugReport.instance.setGitRepoInfo('GIT_URL' : 'git://url', 'GIT_LOCAL_BRANCH' : 'some-branch')
|
||||
|
||||
String debugReport = DebugReport.instance.generateReport(mockScript())
|
||||
|
||||
Assert.assertTrue(debugReport.contains('## Pipeline Environment'))
|
||||
Assert.assertTrue(debugReport.contains('## Local Extensions'))
|
||||
Assert.assertTrue(debugReport.contains('#### Environment\n' +
|
||||
'`custom`'))
|
||||
Assert.assertFalse(debugReport.contains('Repository | Branch'))
|
||||
Assert.assertFalse(debugReport.contains('some-branch'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogOutputConfidential() {
|
||||
DebugReport.instance.initFromEnvironment(createEnv())
|
||||
DebugReport.instance.setGitRepoInfo('GIT_URL' : 'git://url', 'GIT_LOCAL_BRANCH' : 'some-branch')
|
||||
DebugReport.instance.shareConfidentialInformation = true
|
||||
|
||||
String debugReport = DebugReport.instance.generateReport(mockScript())
|
||||
|
||||
Assert.assertTrue(debugReport.contains('## Pipeline Environment'))
|
||||
Assert.assertTrue(debugReport.contains('## Local Extensions'))
|
||||
Assert.assertTrue(debugReport.contains('#### Environment\n' +
|
||||
'`custom`'))
|
||||
Assert.assertTrue(debugReport.contains('Repository | Branch'))
|
||||
Assert.assertTrue(debugReport.contains('some-branch'))
|
||||
}
|
||||
|
||||
private Script mockScript() {
|
||||
helper.registerAllowedMethod("libraryResource", [String.class], { path ->
|
||||
|
||||
File resource = new File(new File('resources'), path)
|
||||
if (resource.exists()) {
|
||||
return resource.getText()
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
return nullScript
|
||||
}
|
||||
|
||||
private static Map createEnv() {
|
||||
Map env = [:]
|
||||
env.put('JENKINS_VERSION', '42')
|
||||
env.put('JAVA_VERSION', '8')
|
||||
env.put('ON_K8S', 'true')
|
||||
return env
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user