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

Add new step for Dockerfile linting (#723)

* Add new step for Dockerfile linting

* Add documentation template file

* Remove newlines

* Remove internal URL

* Rephrase comment

* Ammend stash

* Fix test

* move dockerImage to general

* use explicit curl options

* small changes

* small changes

* skip GIT blame

* First comments

* Also add remark to URL parameter

* Second set of comments

* Fix return code handling

* Switch type to set

* Revert unrelated changes

* Avoid modification of config

* add quality gate defaults

* Update hadolintExecute.groovy

* fix code climate issue
This commit is contained in:
Sven Merk 2019-06-04 08:01:43 +02:00 committed by Christopher Fenner
parent f171f88b13
commit 111080cbfe
4 changed files with 202 additions and 1 deletions

View File

@ -0,0 +1,17 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}
## ${docGenConfiguration}
## Exceptions
None
## Examples
```groovy
hadolintExecute script: this
```

View File

@ -251,6 +251,19 @@ steps:
languageRunner: 'ruby'
runCommand: 'bundle install && bundle exec gauge run'
testOptions: 'specs'
hadolintExecute:
configurationFile: '.hadolint.yaml'
configurationUrl: ''
dockerFile: './Dockerfile'
dockerImage: 'hadolint/hadolint:latest-debian'
qualityGates:
- threshold: 1
type: 'TOTAL_ERROR'
unstable: false
reportFile: 'hadolint.xml'
reportName: 'HaDoLint'
stashContent:
- 'buildDescriptor'
handlePipelineStepErrors:
echoDetails: true
failOnError: true
@ -408,7 +421,7 @@ steps:
noDefaultExludes: []
pipelineStashFilesBeforeBuild:
stashIncludes:
buildDescriptor: '**/pom.xml, **/.mvn/**, **/assembly.xml, **/.swagger-codegen-ignore, **/package.json, **/requirements.txt, **/setup.py, **/mta*.y*ml, **/.npmrc, Dockerfile, **/VERSION, **/version.txt, **/Gopkg.*, **/build.sbt, **/sbtDescriptor.json, **/project/*'
buildDescriptor: '**/pom.xml, **/.mvn/**, **/assembly.xml, **/.swagger-codegen-ignore, **/package.json, **/requirements.txt, **/setup.py, **/mta*.y*ml, **/.npmrc, Dockerfile, .hadolint.yaml, **/VERSION, **/version.txt, **/Gopkg.*, **/build.sbt, **/sbtDescriptor.json, **/project/*'
deployDescriptor: '**/manifest*.y*ml, **/*.mtaext.y*ml, **/*.mtaext, **/xs-app.json, helm/**, *.y*ml'
git: '.git/**'
opa5: '**/*.*'

View File

@ -0,0 +1,67 @@
import hudson.AbortException
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsDockerExecuteRule
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsShellCallRule
import util.JenkinsStepRule
import util.Rules
import static org.junit.Assert.assertThat
import static org.hamcrest.Matchers.*
class HadolintExecuteTest extends BasePiperTest {
private ExpectedException thrown = new ExpectedException().none()
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsReadYamlRule yamlRule = new JenkinsReadYamlRule(this)
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
@Rule
public RuleChain ruleChain = Rules
.getCommonRules(this)
.around(thrown)
.around(yamlRule)
.around(dockerExecuteRule)
.around(shellRule)
.around(stepRule)
.around(loggingRule)
@Before
void init() {
helper.registerAllowedMethod 'stash', [String, String], { name, includes -> assertThat(name, is('hadolintConfiguration')); assertThat(includes, is('.hadolint.yaml')) }
helper.registerAllowedMethod 'fileExists', [String], { s -> s == './Dockerfile' }
helper.registerAllowedMethod 'checkStyle', [Map], { m -> assertThat(m.pattern, is('hadolint.xml')); return 'checkstyle' }
helper.registerAllowedMethod 'recordIssues', [Map], { m -> assertThat(m.tools, hasItem('checkstyle')) }
helper.registerAllowedMethod 'archiveArtifacts', [String], { String p -> assertThat('hadolint.xml', is(p)) }
}
@Test
void testHadolintExecute() {
stepRule.step.hadolintExecute(script: nullScript, juStabUtils: utils, dockerImage: 'hadolint/hadolint:latest-debian', configurationUrl: 'https://github.wdf.sap.corp/raw/SGS/Hadolint-Dockerfile/master/.hadolint.yaml')
assertThat(dockerExecuteRule.dockerParams.dockerImage, is('hadolint/hadolint:latest-debian'))
assertThat(loggingRule.log, containsString("Unstash content: buildDescriptor"))
assertThat(shellRule.shell,
hasItems(
"curl --fail --location --output .hadolint.yaml https://github.wdf.sap.corp/raw/SGS/Hadolint-Dockerfile/master/.hadolint.yaml",
"hadolint ./Dockerfile --config .hadolint.yaml --format checkstyle > hadolint.xml"
)
)
}
@Test
void testNoDockerfile() {
helper.registerAllowedMethod 'fileExists', [String], { false }
thrown.expect AbortException
thrown.expectMessage '[hadolintExecute] Dockerfile \'./Dockerfile\' is not found.'
stepRule.step.hadolintExecute(script: nullScript, juStabUtils: utils, dockerImage: 'hadolint/hadolint:latest-debian')
}
}

104
vars/hadolintExecute.groovy Normal file
View File

@ -0,0 +1,104 @@
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import groovy.transform.Field
@Field def STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/**
* Dockerfile to be used for the assessment.
*/
'dockerFile',
/**
* Name of the docker image that should be used, in which node should be installed and configured. Default value is 'hadolint/hadolint:latest-debian'.
*/
'dockerImage'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* Name of the configuration file used locally within the step. If a file with this name is detected as part of your repo downloading the central configuration via `configurationUrl` will be skipped. If you change the file's name make sure your stashing configuration also reflects this.
*/
'configurationFile',
/**
* URL pointing to the .hadolint.yaml exclude configuration to be used for linting. Also have a look at `configurationFile` which could avoid central configuration download in case the file is part of your repository.
*/
'configurationUrl',
/**
* Docker options to be set when starting the container.
*/
'dockerOptions',
/**
* Quality Gates to fail the build, see [warnings-ng plugin documentation](https://github.com/jenkinsci/warnings-plugin/blob/master/doc/Documentation.md#quality-gate-configuration).
*/
'qualityGates',
/**
* Name of the result file used locally within the step.
*/
'reportFile'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* Executes the Haskell Dockerfile Linter which is a smarter Dockerfile linter that helps you build [best practice](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) Docker images.
* The linter is parsing the Dockerfile into an abstract syntax tree (AST) and performs rules on top of the AST.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
final script = checkScript(this, parameters) ?: this
final utils = parameters.juStabUtils ?: new Utils()
// load default & individual configuration
Map configuration = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.use()
new Utils().pushToSWA([
step: STEP_NAME,
stepParamKey1: 'scriptMissing',
stepParam1: parameters?.script == null
], configuration)
def existingStashes = utils.unstashAll(configuration.stashContent)
if (!fileExists(configuration.dockerFile)) {
error "[${STEP_NAME}] Dockerfile '${configuration.dockerFile}' is not found."
}
if(!fileExists(configuration.configurationFile) && configuration.configurationUrl) {
sh "curl --fail --location --output ${configuration.configurationFile} ${configuration.configurationUrl}"
if(existingStashes) {
def stashName = 'hadolintConfiguration'
stash name: stashName, includes: configuration.configurationFile
existingStashes += stashName
}
}
def options = [
"--config ${configuration.configurationFile}",
"--format checkstyle > ${configuration.reportFile}"
]
dockerExecute(
script: script,
dockerImage: configuration.dockerImage,
dockerOptions: configuration.dockerOptions,
stashContent: existingStashes
) {
// HaDoLint status code is ignore, results will be handled by recordIssues / archiveArtifacts
def ignore = sh returnStatus: true, script: "hadolint ${configuration.dockerFile} ${options.join(' ')}"
archiveArtifacts configuration.reportFile
recordIssues(
tools: [checkStyle(name: configuration.reportName, pattern: configuration.reportFile)],
qualityGates: configuration.qualityGates,
enabledForFailure: true,
blameDisabled: true
)
}
}
}