mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-03-03 15:02:35 +02:00
Implement archiving the debug report as step (#1152)
* Implement archiving the debug report as step
This commit is contained in:
parent
c37df0d55e
commit
c628d208c7
@ -17,7 +17,7 @@ The log was generated at: `$utcTimestamp UTC`
|
|||||||
<% print failedBuild.get("stack_trace") ? "```" : "" %>
|
<% print failedBuild.get("stack_trace") ? "```" : "" %>
|
||||||
<% print failedBuild.get("stack_trace").collect({each -> "${each}"}).join("\n") %>
|
<% print failedBuild.get("stack_trace").collect({each -> "${each}"}).join("\n") %>
|
||||||
<% print failedBuild.get("stack_trace") ? "```" : "" %>
|
<% print failedBuild.get("stack_trace") ? "```" : "" %>
|
||||||
<% print failedBuild.get("fatal") ? "#### Failure was fatal." : "" %>
|
<% print failedBuild.get("fatal") ? "#### Failure was fatal." : "#### Failure was non-fatal." %>
|
||||||
|
|
||||||
## Pipeline Environment
|
## Pipeline Environment
|
||||||
|
|
||||||
|
@ -199,6 +199,8 @@ steps:
|
|||||||
cloudFoundryCreateServiceKey:
|
cloudFoundryCreateServiceKey:
|
||||||
dockerImage: 'ppiper/cf-cli'
|
dockerImage: 'ppiper/cf-cli'
|
||||||
dockerWorkspace: '/home/piper'
|
dockerWorkspace: '/home/piper'
|
||||||
|
debugReportArchive:
|
||||||
|
shareConfidentialInformation: false
|
||||||
detectExecuteScan:
|
detectExecuteScan:
|
||||||
detect:
|
detect:
|
||||||
projectVersion: '1'
|
projectVersion: '1'
|
||||||
|
@ -5,7 +5,6 @@ import groovy.text.SimpleTemplateEngine
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class DebugReport {
|
class DebugReport {
|
||||||
String fileName
|
|
||||||
String projectIdentifier = null
|
String projectIdentifier = null
|
||||||
Map environment = ['environment': 'custom']
|
Map environment = ['environment': 'custom']
|
||||||
String buildTool = null
|
String buildTool = null
|
||||||
@ -20,7 +19,6 @@ class DebugReport {
|
|||||||
String sharedConfigFilePath = null
|
String sharedConfigFilePath = null
|
||||||
Set additionalSharedLibraries = []
|
Set additionalSharedLibraries = []
|
||||||
Map failedBuild = [:]
|
Map failedBuild = [:]
|
||||||
boolean shareConfidentialInformation
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize debug report information from the environment variables.
|
* Initialize debug report information from the environment variables.
|
||||||
@ -85,14 +83,10 @@ class DebugReport {
|
|||||||
failedBuild.put('step', stepName)
|
failedBuild.put('step', stepName)
|
||||||
failedBuild.put('reason', err)
|
failedBuild.put('reason', err)
|
||||||
failedBuild.put('stack_trace', err.getStackTrace())
|
failedBuild.put('stack_trace', err.getStackTrace())
|
||||||
if (failedOnError) {
|
failedBuild.put('fatal', failedOnError ? 'true' : 'false')
|
||||||
failedBuild.put('fatal', 'true')
|
|
||||||
} else {
|
|
||||||
failedBuild.remove('fatal')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String generateReport(Script script) {
|
Map generateReport(Script script, boolean shareConfidentialInformation) {
|
||||||
String template = script.libraryResource 'debug_report.txt'
|
String template = script.libraryResource 'debug_report.txt'
|
||||||
|
|
||||||
if (!projectIdentifier) {
|
if (!projectIdentifier) {
|
||||||
@ -107,19 +101,34 @@ class DebugReport {
|
|||||||
script.echo "Failed to retrieve Jenkins plugins for debug report (${t.getMessage()})"
|
script.echo "Failed to retrieve Jenkins plugins for debug report (${t.getMessage()})"
|
||||||
}
|
}
|
||||||
|
|
||||||
Map binding = getProperties()
|
|
||||||
Date now = new Date()
|
Date now = new Date()
|
||||||
|
|
||||||
binding.utcTimestamp = now.format('yyyy-MM-dd HH:mm', TimeZone.getTimeZone('UTC'))
|
Map binding = [
|
||||||
|
'projectIdentifier' : projectIdentifier,
|
||||||
|
'environment' : environment,
|
||||||
|
'buildTool': buildTool,
|
||||||
|
'modulesMap' : modulesMap,
|
||||||
|
'npmModules' : npmModules,
|
||||||
|
'plugins' : plugins,
|
||||||
|
'gitRepo' : gitRepo,
|
||||||
|
'localExtensions' : localExtensions,
|
||||||
|
'globalExtensionRepository' : globalExtensionRepository,
|
||||||
|
'globalExtensions' : globalExtensions,
|
||||||
|
'globalExtensionConfigurationFilePath' : globalExtensionConfigurationFilePath,
|
||||||
|
'sharedConfigFilePath' : sharedConfigFilePath,
|
||||||
|
'additionalSharedLibraries' : additionalSharedLibraries,
|
||||||
|
'failedBuild' : failedBuild,
|
||||||
|
'shareConfidentialInformation' : shareConfidentialInformation,
|
||||||
|
'utcTimestamp' : now.format('yyyy-MM-dd HH:mm', TimeZone.getTimeZone('UTC'))
|
||||||
|
]
|
||||||
|
|
||||||
String fileNameTimestamp = now.format('yyyy-MM-dd-HH-mm', TimeZone.getTimeZone('UTC'))
|
String fileNameTimestamp = now.format('yyyy-MM-dd-HH-mm', TimeZone.getTimeZone('UTC'))
|
||||||
|
String fileNamePrefix = shareConfidentialInformation ? 'confidential' : 'redacted'
|
||||||
|
|
||||||
if (shareConfidentialInformation) {
|
Map result = [:]
|
||||||
fileName = "confidential_debug_log_${fileNameTimestamp}_${projectIdentifier}.txt"
|
result.fileName = "${fileNamePrefix}_debug_log_${fileNameTimestamp}_${projectIdentifier}.txt"
|
||||||
} else {
|
result.contents = fillTemplate(template, binding)
|
||||||
fileName = "redacted_debug_log_${fileNameTimestamp}_${projectIdentifier}.txt"
|
return result
|
||||||
}
|
|
||||||
|
|
||||||
return fillTemplate(template, binding)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonCPS
|
@NonCPS
|
||||||
|
58
test/groovy/DebugReportArchiveTest.groovy
Normal file
58
test/groovy/DebugReportArchiveTest.groovy
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.RuleChain
|
||||||
|
import util.BasePiperTest
|
||||||
|
import util.JenkinsLoggingRule
|
||||||
|
import util.JenkinsReadYamlRule
|
||||||
|
import util.JenkinsStepRule
|
||||||
|
import util.JenkinsWriteFileRule
|
||||||
|
import util.Rules
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString
|
||||||
|
import static org.junit.Assert.assertThat
|
||||||
|
|
||||||
|
class DebugReportArchiveTest extends BasePiperTest {
|
||||||
|
|
||||||
|
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
|
||||||
|
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
|
||||||
|
private JenkinsWriteFileRule writeFileRule = new JenkinsWriteFileRule(this)
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public RuleChain ruleChain = Rules
|
||||||
|
.getCommonRules(this)
|
||||||
|
.around(new JenkinsReadYamlRule(this))
|
||||||
|
.around(loggingRule)
|
||||||
|
.around(writeFileRule)
|
||||||
|
.around(stepRule)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
void init() {
|
||||||
|
helper.registerAllowedMethod("libraryResource", [String.class], { path ->
|
||||||
|
|
||||||
|
File resource = new File(new File('resources'), path)
|
||||||
|
if (resource.exists()) {
|
||||||
|
return resource.getText()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDebugReportArchive() {
|
||||||
|
stepRule.step.debugReportArchive(
|
||||||
|
script: nullScript,
|
||||||
|
juStabUtils: utils,
|
||||||
|
stageName: 'test',
|
||||||
|
printToConsole: true
|
||||||
|
)
|
||||||
|
|
||||||
|
String debugReportSnippet = 'The debug log is generated with each build and should be included in every support request'
|
||||||
|
|
||||||
|
assertThat(loggingRule.log, containsString('Successfully archived debug report'))
|
||||||
|
assertThat(loggingRule.log, containsString(debugReportSnippet))
|
||||||
|
|
||||||
|
assertThat(writeFileRule.files.find({ it.toString().contains('debug_log') }) as String, containsString(debugReportSnippet))
|
||||||
|
}
|
||||||
|
}
|
@ -43,7 +43,7 @@ class DebugReportTest extends BasePiperTest {
|
|||||||
DebugReport.instance.initFromEnvironment(createEnv())
|
DebugReport.instance.initFromEnvironment(createEnv())
|
||||||
DebugReport.instance.setGitRepoInfo('GIT_URL' : 'git://url', 'GIT_LOCAL_BRANCH' : 'some-branch')
|
DebugReport.instance.setGitRepoInfo('GIT_URL' : 'git://url', 'GIT_LOCAL_BRANCH' : 'some-branch')
|
||||||
|
|
||||||
String debugReport = DebugReport.instance.generateReport(mockScript())
|
String debugReport = DebugReport.instance.generateReport(mockScript(), false)
|
||||||
|
|
||||||
Assert.assertTrue(debugReport.contains('## Pipeline Environment'))
|
Assert.assertTrue(debugReport.contains('## Pipeline Environment'))
|
||||||
Assert.assertTrue(debugReport.contains('## Local Extensions'))
|
Assert.assertTrue(debugReport.contains('## Local Extensions'))
|
||||||
@ -57,9 +57,8 @@ class DebugReportTest extends BasePiperTest {
|
|||||||
void testLogOutputConfidential() {
|
void testLogOutputConfidential() {
|
||||||
DebugReport.instance.initFromEnvironment(createEnv())
|
DebugReport.instance.initFromEnvironment(createEnv())
|
||||||
DebugReport.instance.setGitRepoInfo('GIT_URL' : 'git://url', 'GIT_LOCAL_BRANCH' : 'some-branch')
|
DebugReport.instance.setGitRepoInfo('GIT_URL' : 'git://url', 'GIT_LOCAL_BRANCH' : 'some-branch')
|
||||||
DebugReport.instance.shareConfidentialInformation = true
|
|
||||||
|
|
||||||
String debugReport = DebugReport.instance.generateReport(mockScript())
|
String debugReport = DebugReport.instance.generateReport(mockScript(), true)
|
||||||
|
|
||||||
Assert.assertTrue(debugReport.contains('## Pipeline Environment'))
|
Assert.assertTrue(debugReport.contains('## Pipeline Environment'))
|
||||||
Assert.assertTrue(debugReport.contains('## Local Extensions'))
|
Assert.assertTrue(debugReport.contains('## Local Extensions'))
|
||||||
|
73
vars/debugReportArchive.groovy
Normal file
73
vars/debugReportArchive.groovy
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import com.sap.piper.ConfigurationHelper
|
||||||
|
import com.sap.piper.DebugReport
|
||||||
|
import com.sap.piper.GenerateDocumentation
|
||||||
|
import com.sap.piper.Utils
|
||||||
|
import groovy.transform.Field
|
||||||
|
|
||||||
|
import static com.sap.piper.Prerequisites.checkScript
|
||||||
|
|
||||||
|
@Field def STEP_NAME = getClass().getName()
|
||||||
|
@Field Set GENERAL_CONFIG_KEYS = []
|
||||||
|
@Field Set STEP_CONFIG_KEYS = [
|
||||||
|
/**
|
||||||
|
* Flag to control whether potentially confidential information will be included in the
|
||||||
|
* debug_report.txt. Default value is `false`. Additional information written to the log
|
||||||
|
* when this flag is `true` includes MTA modules, NPM modules, the GitHub repository and
|
||||||
|
* branch, the global extension repository if used, a shared config file path, and all
|
||||||
|
* used global and local shared libraries.
|
||||||
|
* @possibleValues `true`, `false`
|
||||||
|
*/
|
||||||
|
'shareConfidentialInformation'
|
||||||
|
]
|
||||||
|
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS + [
|
||||||
|
/**
|
||||||
|
* Flag to enable printing the generated debug_report.txt also to the console.
|
||||||
|
*/
|
||||||
|
'printToConsole'
|
||||||
|
]
|
||||||
|
/**
|
||||||
|
* Archives the debug_report.txt artifact which facilitates analyzing pipeline errors by collecting
|
||||||
|
* information about the Jenkins environment in which the pipeline was run. There is a single
|
||||||
|
* config option 'shareConfidentialInformation' to enable including (possibly) confidential
|
||||||
|
* information in the debug report, which could be helpful depending on the specific error.
|
||||||
|
* By default this information is not included.
|
||||||
|
*/
|
||||||
|
@GenerateDocumentation
|
||||||
|
void call(Map parameters = [:]) {
|
||||||
|
final script = checkScript(this, parameters) ?: this
|
||||||
|
try {
|
||||||
|
String stageName = parameters.stageName ?: env.STAGE_NAME
|
||||||
|
// ease handling extension
|
||||||
|
stageName = stageName?.replace('Declarative: ', '')
|
||||||
|
def utils = parameters.juStabUtils ?: new Utils()
|
||||||
|
|
||||||
|
Map configuration = ConfigurationHelper.newInstance(this)
|
||||||
|
.loadStepDefaults()
|
||||||
|
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
|
||||||
|
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
|
||||||
|
.mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS)
|
||||||
|
.mixin(parameters, PARAMETER_KEYS)
|
||||||
|
.use()
|
||||||
|
|
||||||
|
utils.pushToSWA([
|
||||||
|
step: STEP_NAME,
|
||||||
|
stepParamKey1: 'scriptMissing',
|
||||||
|
stepParam1: parameters?.script == null
|
||||||
|
], configuration)
|
||||||
|
|
||||||
|
boolean shareConfidentialInformation = configuration?.get('shareConfidentialInformation') ?: false
|
||||||
|
|
||||||
|
Map result = DebugReport.instance.generateReport(script, shareConfidentialInformation)
|
||||||
|
|
||||||
|
if (parameters.printToConsole) {
|
||||||
|
echo result.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
script.writeFile file: result.fileName, text: result
|
||||||
|
script.archiveArtifacts artifacts: result.fileName
|
||||||
|
echo "Successfully archived debug report as '${result.fileName}'"
|
||||||
|
} catch (Exception e) {
|
||||||
|
println("WARNING: The debug report was not created, it threw the following error message:")
|
||||||
|
println("${e}")
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user