You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-07-17 01:42:43 +02:00
Be more failsafe when creating documentation
we do not fail immediatly, instead we continue with the next step. Of course we emit a warning/error into the log in case of a failure. In case there is a failure the script returns with != 0
This commit is contained in:
@ -4,169 +4,53 @@ import org.codehaus.groovy.control.CompilerConfiguration
|
||||
import com.sap.piper.DefaultValueCache
|
||||
import java.util.regex.Matcher
|
||||
|
||||
roots = [
|
||||
'vars',
|
||||
'src',
|
||||
]
|
||||
|
||||
def stepsDir
|
||||
def outDir
|
||||
def stepDocuDir
|
||||
|
||||
//
|
||||
// assign parameters
|
||||
|
||||
if(args.length >= 1)
|
||||
stepsDir = new File(args[0])
|
||||
|
||||
stepsDir = stepsDir ?: new File('vars')
|
||||
|
||||
if(args.length >= 2)
|
||||
outDir = new File(args[1])
|
||||
|
||||
outDir = outDir ?: new File('out')
|
||||
|
||||
if(args.length >= 3)
|
||||
stepsDocuDir = new File(args[2])
|
||||
|
||||
stepsDocuDir = stepsDocuDir ?: new File('documentation/docs/steps')
|
||||
|
||||
// assign parameters
|
||||
// Collects helper functions for rendering the docu
|
||||
//
|
||||
class TemplateHelper {
|
||||
|
||||
//
|
||||
// sanity checks
|
||||
static createParametersTable(Map parameters) {
|
||||
|
||||
if( ! outDir.exists() ) {
|
||||
if(! outDir.mkdirs()) {
|
||||
System.err << "Cannot create output direcrory '${outDir}'.\n"
|
||||
System.exit(1)
|
||||
def t = ''
|
||||
t += '| name | mandatory | default | possible values |\n'
|
||||
|
||||
parameters.keySet().toSorted().each {
|
||||
|
||||
def props = parameters.get(it)
|
||||
t += "| `${it}` | ${props.required ? 'yes' : 'no'} | `${(props.defaultValue ?: 'n/a') }` | ${props.value ?: 'n/a'} |\n"
|
||||
}
|
||||
|
||||
t
|
||||
}
|
||||
|
||||
static createParameterDescriptionSection(Map parameters) {
|
||||
def t = ''
|
||||
parameters.keySet().toSorted().each {
|
||||
def props = parameters.get(it)
|
||||
t += "* `${it}` - ${props.docu ?: 'n/a'}\n"
|
||||
}
|
||||
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
if( !stepsDocuDir.exists() ) {
|
||||
System.err << "Steps docu dir '${stepsDocuDir}' does not exist.\n"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
if( !stepsDir.exists() ) {
|
||||
System.err << "Steps dir '${stepsDir}' does not exist.\n"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
// sanity checks
|
||||
//
|
||||
|
||||
def blacklist = [
|
||||
'toolValidate',
|
||||
'transportRequestCreate',
|
||||
'cloudFoundryDeploy',
|
||||
'artifactSetVersion',
|
||||
'testsPublishResults',
|
||||
'transportRequestUploadFile',
|
||||
'snykExecute', // docu does not exist
|
||||
'batsExecuteTests',
|
||||
'transportRequestRelease',
|
||||
'setupCommonPipelineEnvironment',
|
||||
'dockerExecute',
|
||||
'durationMeasure',
|
||||
'prepareDefaultValues',
|
||||
'pipelineStashFilesAfterBuild',
|
||||
'pipelineStashFiles',
|
||||
'handlePipelineStepErrors',
|
||||
'newmanExecute',
|
||||
'commonPipelineEnvironment',
|
||||
'pipelineStashFilesBeforeBuild',
|
||||
'seleniumExecuteTests',
|
||||
'pipelineExecute',
|
||||
]
|
||||
|
||||
List steps = []
|
||||
|
||||
// Collects generic helper functions
|
||||
//
|
||||
// find all the steps we have to document
|
||||
stepsDir.traverse(type: FileType.FILES, maxDepth: 0)
|
||||
{ if(it.getName().endsWith('.groovy')) steps << (it =~ /vars\/(.*)\.groovy/)[0][1] }
|
||||
class Helper {
|
||||
|
||||
def gse = new GroovyScriptEngine( [ stepsDir.getName() ] as String[] , getClass().getClassLoader() )
|
||||
|
||||
def prepareDefaultValuesStep = getPrepareDefaultValuesStep(gse)
|
||||
|
||||
for (step in steps) {
|
||||
|
||||
if(blacklist.contains(step)) {
|
||||
// better: simply copy over the input file to the output file directory ...
|
||||
System.err << "[INFO] Step '${step}' is blacklisted. No docu will be created for that step.\n"
|
||||
continue
|
||||
}
|
||||
|
||||
System.err << "[INFO] Handling step '${step}'.\n"
|
||||
|
||||
def defaultConfig = getConfigHelper().loadStepDefaults(getDummyScript(prepareDefaultValuesStep, step)).use()
|
||||
|
||||
def params = [] as Set
|
||||
|
||||
//
|
||||
// scopedParameters is a map containing the scope as key and the parameters
|
||||
// defined with that scope as a set of strings.
|
||||
|
||||
def scopedParameters
|
||||
|
||||
try {
|
||||
scopedParameters = getScopedParameters(gse.createScript( "${step}.groovy", new Binding() ))
|
||||
scopedParameters.each { k, v -> params.addAll(v) }
|
||||
} catch(Exception e) {
|
||||
System.err << "Step '${step}' violates naming convention for scoped parameters: ${e}."
|
||||
System.exit(1)
|
||||
}
|
||||
def requiredParameters = getRequiredParameters(new File(stepsDir, "${step}.groovy"))
|
||||
|
||||
params.addAll(requiredParameters)
|
||||
|
||||
|
||||
def parameters = [:]
|
||||
|
||||
normalize(params).toSorted().each {
|
||||
|
||||
it ->
|
||||
|
||||
def parameterProperties = [
|
||||
defaultValue: getValue(defaultConfig, it.split('/')),
|
||||
required: requiredParameters.contains((it as String))
|
||||
]
|
||||
|
||||
parameters.put(it, parameterProperties)
|
||||
|
||||
// The scope is only defined for the first level of a hierarchical configuration.
|
||||
// If the first part is found, all nested parameters are allowed with that scope.
|
||||
def firstPart = it.split('/').head()
|
||||
scopedParameters.each { key, val ->
|
||||
parameterProperties.put(key, val.contains(firstPart))
|
||||
}
|
||||
}
|
||||
|
||||
scanDocu(new File(stepsDir, "${step}.groovy"), parameters)
|
||||
|
||||
def text = new File(stepsDocuDir, "${step}.md").text
|
||||
text = text.replace('__PARAMETER_TABLE__', createParametersTable(parameters))
|
||||
text = text.replace('__PARAMETER_DESCRIPTION__', createParameterDescriptionSection(parameters))
|
||||
|
||||
new File(outDir, "${step}.md").withWriter { w -> w.write text }
|
||||
}
|
||||
|
||||
System.err << "[INFO] done.\n"
|
||||
|
||||
def getConfigHelper() {
|
||||
static getConfigHelper(classLoader, roots) {
|
||||
|
||||
def compilerConfig = new CompilerConfiguration()
|
||||
compilerConfig.setClasspathList( roots )
|
||||
|
||||
new GroovyClassLoader(getClass().getClassLoader(), compilerConfig, true)
|
||||
new GroovyClassLoader(classLoader, compilerConfig, true)
|
||||
.parseClass(new File('src/com/sap/piper/ConfigurationHelper.groovy'))
|
||||
.newInstance()
|
||||
}
|
||||
}
|
||||
|
||||
def getPrepareDefaultValuesStep(def gse) {
|
||||
|
||||
static getPrepareDefaultValuesStep(def gse) {
|
||||
|
||||
def prepareDefaultValuesStep = gse.createScript('prepareDefaultValues.groovy', new Binding())
|
||||
|
||||
@ -182,9 +66,9 @@ def getPrepareDefaultValuesStep(def gse) {
|
||||
}
|
||||
|
||||
prepareDefaultValuesStep
|
||||
}
|
||||
}
|
||||
|
||||
def getDummyScript(def prepareDefaultValuesStep, def stepName) {
|
||||
static getDummyScript(def prepareDefaultValuesStep, def stepName) {
|
||||
|
||||
def _prepareDefaultValuesStep = prepareDefaultValuesStep
|
||||
def _stepName = stepName
|
||||
@ -201,34 +85,9 @@ def getDummyScript(def prepareDefaultValuesStep, def stepName) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createParametersTable(Map parameters) {
|
||||
|
||||
def t = ''
|
||||
t += '| name | mandatory | default | possible values |\n'
|
||||
|
||||
parameters.keySet().toSorted().each {
|
||||
|
||||
def props = parameters.get(it)
|
||||
t += "| `${it}` | ${props.required ? 'yes' : 'no'} | `${(props.defaultValue ?: 'n/a') }` | ${props.value ?: 'n/a'} |\n"
|
||||
}
|
||||
|
||||
t
|
||||
}
|
||||
|
||||
def createParameterDescriptionSection(Map parameters) {
|
||||
def t = ''
|
||||
parameters.keySet().toSorted().each {
|
||||
def props = parameters.get(it)
|
||||
t += "* `${it}` - ${props.docu ?: 'n/a'}\n"
|
||||
}
|
||||
|
||||
t
|
||||
}
|
||||
|
||||
|
||||
def normalize(Set p) {
|
||||
private static normalize(Set p) {
|
||||
|
||||
def normalized = [] as Set
|
||||
|
||||
@ -241,9 +100,17 @@ def normalize(Set p) {
|
||||
interim.each { k, v -> flatten (normalized, k, v) }
|
||||
|
||||
normalized
|
||||
}
|
||||
}
|
||||
|
||||
def flatten(Set flat, def key, Map interim) {
|
||||
private static void _normalize(List parts, Map interim) {
|
||||
if( parts.size >= 1) {
|
||||
if( ! interim[parts.head()]) interim[parts.head()] = [:]
|
||||
_normalize(parts.tail(), interim[parts.head()])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static flatten(Set flat, def key, Map interim) {
|
||||
|
||||
if( ! interim ) flat << (key as String)
|
||||
|
||||
@ -257,17 +124,9 @@ def flatten(Set flat, def key, Map interim) {
|
||||
flat << (_key as String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def _normalize(List parts, Map interim) {
|
||||
if( parts.size >= 1) {
|
||||
if( ! interim[parts.head()]) interim[parts.head()] = [:]
|
||||
_normalize(parts.tail(), interim[parts.head()])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void scanDocu(File f, Map params) {
|
||||
static void scanDocu(File f, Map params) {
|
||||
|
||||
boolean docu = false,
|
||||
value = false,
|
||||
@ -325,9 +184,10 @@ void scanDocu(File f, Map params) {
|
||||
scanNextLineForParamName = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getScopedParameters(def script) {
|
||||
|
||||
static getScopedParameters(def script) {
|
||||
|
||||
def params = [:]
|
||||
|
||||
@ -336,9 +196,9 @@ def getScopedParameters(def script) {
|
||||
params.put('PARAMS', script.PARAMETER_KEYS ?: [] )
|
||||
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
||||
def getRequiredParameters(File f) {
|
||||
static getRequiredParameters(File f) {
|
||||
def params = [] as Set
|
||||
f.eachLine {
|
||||
line ->
|
||||
@ -348,12 +208,180 @@ def getRequiredParameters(File f) {
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
||||
def getValue(Map config, def pPath) {
|
||||
static getValue(Map config, def pPath) {
|
||||
def p =config[pPath.head()]
|
||||
if(pPath.size() == 1) return p // there is no tail
|
||||
if(p in Map) getValue(p, pPath.tail())
|
||||
else return p
|
||||
}
|
||||
}
|
||||
|
||||
roots = [
|
||||
'vars',
|
||||
'src',
|
||||
]
|
||||
|
||||
stepsDir = null
|
||||
outDir = null
|
||||
stepsDocuDir = null
|
||||
|
||||
//
|
||||
// assign parameters
|
||||
|
||||
if(args.length >= 1)
|
||||
stepsDir = new File(args[0])
|
||||
|
||||
stepsDir = stepsDir ?: new File('vars')
|
||||
|
||||
if(args.length >= 2)
|
||||
outDir = new File(args[1])
|
||||
|
||||
outDir = outDir ?: new File('out')
|
||||
|
||||
if(args.length >= 3)
|
||||
stepsDocuDir = new File(args[2])
|
||||
|
||||
stepsDocuDir = stepsDocuDir ?: new File('documentation/docs/steps')
|
||||
|
||||
// assign parameters
|
||||
//
|
||||
|
||||
//
|
||||
// sanity checks
|
||||
|
||||
if( ! outDir.exists() ) {
|
||||
if(! outDir.mkdirs()) {
|
||||
System.err << "Cannot create output direcrory '${outDir}'.\n"
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if( !stepsDocuDir.exists() ) {
|
||||
System.err << "Steps docu dir '${stepsDocuDir}' does not exist.\n"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
if( !stepsDir.exists() ) {
|
||||
System.err << "Steps dir '${stepsDir}' does not exist.\n"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
// sanity checks
|
||||
//
|
||||
|
||||
blacklist = [
|
||||
'toolValidate',
|
||||
'setupCommonPipelineEnvironment',
|
||||
'durationMeasure',
|
||||
'prepareDefaultValues',
|
||||
'pipelineStashFilesAfterBuild',
|
||||
'pipelineStashFiles',
|
||||
'handlePipelineStepErrors',
|
||||
'commonPipelineEnvironment',
|
||||
'pipelineExecute',
|
||||
]
|
||||
|
||||
List steps = []
|
||||
|
||||
//
|
||||
// find all the steps we have to document
|
||||
stepsDir.traverse(type: FileType.FILES, maxDepth: 0)
|
||||
{ if(it.getName().endsWith('.groovy')) steps << (it =~ /vars\/(.*)\.groovy/)[0][1] }
|
||||
|
||||
def gse = new GroovyScriptEngine( [ stepsDir.getName() ] as String[] , getClass().getClassLoader() )
|
||||
|
||||
def prepareDefaultValuesStep = Helper.getPrepareDefaultValuesStep(gse)
|
||||
|
||||
boolean exceptionCaught = false
|
||||
|
||||
for (step in steps) {
|
||||
try {
|
||||
handleStep(step, prepareDefaultValuesStep, gse)
|
||||
} catch(Exception e) {
|
||||
exceptionCaught = true
|
||||
System.err << "${e.getClass().getName()} caught while handling step '${step}'."
|
||||
}
|
||||
}
|
||||
if(exceptionCaught) {
|
||||
System.err << "[ERROR] Exception caught during generating documentation. Check earlier log for details.\n"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
|
||||
void handleStep(step, prepareDefaultValuesStep, gse) {
|
||||
File theStep = new File(stepsDir, "${step}.groovy")
|
||||
File theStepDocuInput = new File(stepsDocuDir, "${step}.md")
|
||||
File theGeneratedStepDocu = new File(outDir, "${step}.md")
|
||||
|
||||
if(!theStepDocuInput.exists()) {
|
||||
System.err << "[WARNING] step docu input file for step '${step}' is missing.\n"
|
||||
return
|
||||
}
|
||||
|
||||
if(blacklist.contains(step)) {
|
||||
// in case a file is blacklisted from docu generation we simply copy over
|
||||
// the docu input file in order to have always a complete set of step docu files.
|
||||
// we do not touch the content of the file since we assume it has been carefully handcrafted.
|
||||
theGeneratedStepDocu << theStepDocuInput.text
|
||||
|
||||
System.err << "[INFO] Step '${step}' is blacklisted. No docu will be created for that step.\n"
|
||||
return
|
||||
}
|
||||
|
||||
System.err << "[INFO] Handling step '${step}'.\n"
|
||||
|
||||
def defaultConfig = Helper.getConfigHelper(getClass().getClassLoader(), roots).loadStepDefaults(Helper.getDummyScript(prepareDefaultValuesStep, step)).use()
|
||||
|
||||
def params = [] as Set
|
||||
|
||||
//
|
||||
// scopedParameters is a map containing the scope as key and the parameters
|
||||
// defined with that scope as a set of strings.
|
||||
|
||||
def scopedParameters
|
||||
|
||||
try {
|
||||
scopedParameters = Helper.getScopedParameters(gse.createScript( "${step}.groovy", new Binding() ))
|
||||
scopedParameters.each { k, v -> params.addAll(v) }
|
||||
} catch(Exception e) {
|
||||
System.err << "[ERROR] Step '${step}' violates naming convention for scoped parameters: ${e}.\n"
|
||||
throw e
|
||||
}
|
||||
def requiredParameters = Helper.getRequiredParameters(theStep)
|
||||
|
||||
params.addAll(requiredParameters)
|
||||
|
||||
|
||||
def parameters = [:]
|
||||
|
||||
Helper.normalize(params).toSorted().each {
|
||||
|
||||
it ->
|
||||
|
||||
def parameterProperties = [
|
||||
defaultValue: Helper.getValue(defaultConfig, it.split('/')),
|
||||
required: requiredParameters.contains((it as String))
|
||||
]
|
||||
|
||||
parameters.put(it, parameterProperties)
|
||||
|
||||
// The scope is only defined for the first level of a hierarchical configuration.
|
||||
// If the first part is found, all nested parameters are allowed with that scope.
|
||||
def firstPart = it.split('/').head()
|
||||
scopedParameters.each { key, val ->
|
||||
parameterProperties.put(key, val.contains(firstPart))
|
||||
}
|
||||
}
|
||||
|
||||
Helper.scanDocu(theStep, parameters)
|
||||
|
||||
def text = theStepDocuInput.text
|
||||
text = text.replace('__PARAMETER_TABLE__', TemplateHelper.createParametersTable(parameters))
|
||||
text = text.replace('__PARAMETER_DESCRIPTION__', TemplateHelper.createParameterDescriptionSection(parameters))
|
||||
|
||||
theGeneratedStepDocu.withWriter { w -> w.write text }
|
||||
}
|
||||
|
||||
System.err << "[INFO] done.\n"
|
||||
|
Reference in New Issue
Block a user