2022-10-17 16:35:03 +02:00
|
|
|
/**
|
|
|
|
* Construit, valide et publie (optionnellement) une image Docker sur le registre Cadoles (par défaut)
|
|
|
|
*
|
|
|
|
* Options disponibles:
|
|
|
|
*
|
|
|
|
* - dockerfile - String - Chemin vers le fichier Dockerfile à utiliser pour construire l'image, par défaut "./Dockerfile"
|
|
|
|
* - contextDir - String - Répertoire servant de "contexte" pour la construction de l'image, par défault "./"
|
|
|
|
* - imageName - String - Nom de l'image à construire, par défaut ""
|
2023-11-15 11:57:33 +01:00
|
|
|
* - imageTags - String - Tag(s) apposé(s) sur l'image après construction, par défaut tags générés par la méthode utils.getProjectVersionTags()
|
2022-10-17 16:35:03 +02:00
|
|
|
* - gitCredentialsId - String - Identifiant des "credentials" Jenkins utilisés pour cloner le dépôt Git, par défaut "forge-jenkins"
|
|
|
|
* - dockerRepository - String - Nom d'hôte du registre Docker sur lequel publier l'image, par défaut "reg.cadoles.com"
|
|
|
|
* - dockerRepositoryCredentialsId - String - Identifiant des "credentials" Jenkins utilisés pour déployer l'image sur le registre Docker, par défault "reg.cadoles.com-jenkins"
|
|
|
|
* - dryRun - Boolean - Désactiver/activer la publication de l'image sur le registre Docker, par défaut "true"
|
|
|
|
* - skipVerifications - Boolean - Désactiver/activer les étapes de vérifications de qualité/sécurité de l'image Docker, par défaut "false"
|
|
|
|
*/
|
2022-10-03 14:34:17 +02:00
|
|
|
String buildAndPublishImage(Map options = [:]) {
|
2022-10-10 11:56:55 +02:00
|
|
|
String dockerfile = options.get('dockerfile', './Dockerfile')
|
|
|
|
String contextDir = options.get('contextDir', '.')
|
|
|
|
String imageName = options.get('imageName', '')
|
|
|
|
String gitRef = sh(returnStdout: true, script: 'git describe --always').trim()
|
2023-11-15 11:57:33 +01:00
|
|
|
|
|
|
|
List<String> imageTags = options.get('imageTags', utils.getProjectVersionTags())
|
|
|
|
// Handle legacy imageTag parameter
|
|
|
|
if (options.containsKey('imageTag')) {
|
|
|
|
imageTags = [ options.get("imageTag", gitRef) ]
|
|
|
|
}
|
|
|
|
|
2022-10-10 11:56:55 +02:00
|
|
|
String gitCredentialsId = options.get('gitCredentialsId', 'forge-jenkins')
|
2022-10-17 16:04:23 +02:00
|
|
|
String dockerRepository = options.get('dockerRepository', 'reg.cadoles.com')
|
|
|
|
String dockerRepositoryCredentialsId = options.get('dockerRepositoryCredentialsId', 'reg.cadoles.com-jenkins')
|
2022-10-10 11:56:55 +02:00
|
|
|
Boolean dryRun = options.get('dryRun', true)
|
|
|
|
Boolean skipVerifications = options.get('skipVerification', false)
|
|
|
|
|
|
|
|
String projectRepository = env.JOB_NAME
|
|
|
|
if (env.BRANCH_NAME ==~ /^PR-.*$/) {
|
|
|
|
projectRepository = env.JOB_NAME - "/${env.JOB_BASE_NAME}"
|
|
|
|
}
|
|
|
|
projectRepository = options.get('projectRepository', projectRepository)
|
2022-10-03 14:34:17 +02:00
|
|
|
|
|
|
|
withCredentials([
|
|
|
|
usernamePassword([
|
|
|
|
credentialsId: dockerRepositoryCredentialsId,
|
|
|
|
usernameVariable: 'HUB_USERNAME',
|
|
|
|
passwordVariable: 'HUB_PASSWORD'
|
|
|
|
]),
|
|
|
|
]) {
|
2022-10-10 11:56:55 +02:00
|
|
|
stage('Validate Dockerfile with Hadolint') {
|
|
|
|
utils.when(!skipVerifications) {
|
|
|
|
runHadolintCheck(dockerfile, projectRepository)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-15 11:57:33 +01:00
|
|
|
String primaryImageTag = imageTags[0]
|
|
|
|
|
|
|
|
stage("Build image '${imageName}:${primaryImageTag}'") {
|
2022-10-03 14:34:17 +02:00
|
|
|
git.withHTTPCredentials(gitCredentialsId) {
|
|
|
|
sh """
|
|
|
|
docker build \
|
|
|
|
--build-arg="GIT_USERNAME=${env.GIT_USERNAME}" \
|
|
|
|
--build-arg="GIT_PASSWORD=${env.GIT_PASSWORD}" \
|
2023-11-15 11:57:33 +01:00
|
|
|
-t '${imageName}:${primaryImageTag}' \
|
2022-10-03 14:34:17 +02:00
|
|
|
-f '${dockerfile}' \
|
|
|
|
'${contextDir}'
|
|
|
|
"""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-10 11:56:55 +02:00
|
|
|
stage('Validate image with Trivy') {
|
|
|
|
utils.when(!skipVerifications) {
|
2023-11-15 11:57:33 +01:00
|
|
|
runTrivyCheck("${imageName}:${primaryImageTag}", projectRepository)
|
2022-10-10 11:56:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-15 11:57:33 +01:00
|
|
|
stage("Login with image repository") {
|
2022-10-10 11:56:55 +02:00
|
|
|
utils.when(!dryRun) {
|
2023-11-15 11:57:33 +01:00
|
|
|
sh """
|
|
|
|
echo ${env.HUB_PASSWORD} | docker login -u '${env.HUB_USERNAME}' --password-stdin '${dockerRepository}'
|
|
|
|
"""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
imageTags.each { imageTag ->
|
|
|
|
stage("Publish image '${imageName}:${imageTag}'") {
|
|
|
|
utils.when(!dryRun) {
|
2022-10-10 11:56:55 +02:00
|
|
|
sh """
|
2023-11-15 11:57:33 +01:00
|
|
|
docker tag "${imageName}:${primaryImageTag}" "${imageName}:${imageTag}"
|
2022-10-10 11:56:55 +02:00
|
|
|
"""
|
2023-11-15 11:57:33 +01:00
|
|
|
retry(2) {
|
|
|
|
sh """
|
|
|
|
docker push '${imageName}:${imageTag}'
|
|
|
|
"""
|
|
|
|
}
|
2022-10-10 11:56:55 +02:00
|
|
|
}
|
2022-10-03 14:34:17 +02:00
|
|
|
}
|
2023-11-15 11:57:33 +01:00
|
|
|
}
|
|
|
|
|
2022-10-03 14:34:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-10 11:56:55 +02:00
|
|
|
void runHadolintCheck(String dockerfile, String projectRepository) {
|
|
|
|
String reportFile = ".hadolint-report-${currentBuild.startTimeInMillis}.txt"
|
|
|
|
|
|
|
|
try {
|
|
|
|
validateDockerfileWithHadolint(dockerfile, ['reportFile': reportFile])
|
|
|
|
} catch (err) {
|
|
|
|
unstable("Dockerfile '${dockerfile}' failed linting !")
|
|
|
|
} finally {
|
|
|
|
String lintReport = ''
|
|
|
|
|
|
|
|
if (fileExists(reportFile)) {
|
|
|
|
lintReport = """${lintReport}
|
|
|
|
|
|
|
|
|
|```
|
|
|
|
|${readFile(reportFile)}
|
|
|
|
|```"""
|
|
|
|
} else {
|
|
|
|
lintReport = """${lintReport}
|
|
|
|
|
|
|
|
|
|_Vérification échouée mais aucun rapport trouvé !?_ :thinking:"""
|
|
|
|
}
|
|
|
|
|
|
|
|
String defaultReport = '_Rien à signaler !_ :thumbsup:'
|
|
|
|
String report = """## Validation du Dockerfile `${dockerfile}`
|
|
|
|
|
|
|
|
|
|${lintReport ?: defaultReport}
|
|
|
|
""".stripMargin()
|
|
|
|
|
|
|
|
print report
|
|
|
|
|
|
|
|
if (env.CHANGE_ID) {
|
|
|
|
gitea.commentPullRequest(projectRepository, env.CHANGE_ID, report)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String validateDockerfileWithHadolint(String dockerfile, Map options = [:]) {
|
|
|
|
String hadolintBin = getOrInstallHadolint(options)
|
|
|
|
String hadolintArgs = options.get('hadolintArgs', '--no-color')
|
|
|
|
String reportFile = options.get('reportFile', ".hadolint-report-${currentBuild.startTimeInMillis}.txt")
|
|
|
|
|
|
|
|
sh("""#!/bin/bash
|
|
|
|
set -eo pipefail
|
|
|
|
'${hadolintBin}' '${dockerfile}' ${hadolintArgs} | tee '${reportFile}'
|
|
|
|
""")
|
|
|
|
|
|
|
|
return reportFile
|
|
|
|
}
|
|
|
|
|
|
|
|
void runTrivyCheck(String imageName, String projectRepository, Map options = [:]) {
|
|
|
|
String reportFile = ".trivy-report-${currentBuild.startTimeInMillis}.txt"
|
|
|
|
|
|
|
|
try {
|
|
|
|
validateImageWithTrivy(imageName, ['reportFile': reportFile])
|
|
|
|
} catch (err) {
|
|
|
|
unstable("Image '${imageName}' failed validation !")
|
|
|
|
} finally {
|
|
|
|
String lintReport = ''
|
|
|
|
|
|
|
|
if (fileExists(reportFile)) {
|
|
|
|
lintReport = """${lintReport}
|
|
|
|
|
|
|
|
|
|${readFile(reportFile)}
|
2023-11-15 11:57:33 +01:00
|
|
|
|"""
|
2022-10-10 11:56:55 +02:00
|
|
|
} else {
|
|
|
|
lintReport = """${lintReport}
|
|
|
|
|
|
|
|
|
|_Vérification échouée mais aucun rapport trouvé !?_ :thinking:"""
|
|
|
|
}
|
|
|
|
|
|
|
|
String defaultReport = '_Rien à signaler !_ :thumbsup:'
|
|
|
|
String report = """## Validation de l'image `${imageName}`
|
|
|
|
|
|
|
|
|
|${lintReport ?: defaultReport}
|
|
|
|
""".stripMargin()
|
|
|
|
|
|
|
|
print report
|
|
|
|
|
|
|
|
if (env.CHANGE_ID) {
|
|
|
|
gitea.commentPullRequest(projectRepository, env.CHANGE_ID, report)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String validateImageWithTrivy(String imageName, Map options = [:]) {
|
|
|
|
String trivyBin = getOrInstallTrivy(options)
|
|
|
|
String trivyArgs = options.get('trivyArgs', '--exit-code 1')
|
|
|
|
String cacheDirectory = options.get('cacheDirectory', '.trivy/.cache')
|
|
|
|
String cacheDefaultBranch = options.get('cacheDefaultBranch', 'develop')
|
|
|
|
Integer cacheMaxSize = options.get('cacheMaxSize', 250)
|
|
|
|
String reportFile = options.get('reportFile', ".trivy-report-${currentBuild.startTimeInMillis}.txt")
|
|
|
|
|
2023-11-15 11:57:33 +01:00
|
|
|
def markdownTemplate = libraryResource 'com/cadoles/trivy/templates/markdown.tpl'
|
|
|
|
|
2022-10-10 11:56:55 +02:00
|
|
|
cache(maxCacheSize: cacheMaxSize, defaultBranch: cacheDefaultBranch, caches: [
|
|
|
|
[$class: 'ArbitraryFileCache', path: cacheDirectory, compressionMethod: 'TARGZ']
|
|
|
|
]) {
|
2023-11-15 11:57:33 +01:00
|
|
|
sh("'${trivyBin}' --cache-dir '${cacheDirectory}' image --format template --template '@${markdownTemplate}' -o '${reportFile}' ${trivyArgs} '${imageName}'")
|
2022-10-10 11:56:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return reportFile
|
|
|
|
}
|
|
|
|
|
|
|
|
String getOrInstallHadolint(Map options = [:]) {
|
|
|
|
String installDir = options.get('installDir', '/usr/local/bin')
|
|
|
|
String version = options.get('version', '2.10.0')
|
|
|
|
String forceDownload = options.get('forceDownload', false)
|
|
|
|
String downloadUrl = options.get('downloadUrl', "https://github.com/hadolint/hadolint/releases/download/v${version}/hadolint-Linux-x86_64")
|
|
|
|
|
|
|
|
String hadolintBin = sh(returnStdout: true, script: 'which hadolint || exit 0').trim()
|
|
|
|
if (hadolintBin == '' || forceDownload) {
|
|
|
|
sh("""
|
|
|
|
mkdir -p '${installDir}'
|
|
|
|
curl -o '${installDir}/hadolint' -sSL '${downloadUrl}'
|
|
|
|
chmod +x '${installDir}/hadolint'
|
|
|
|
""")
|
|
|
|
|
|
|
|
hadolintBin = "${installDir}/hadolint"
|
|
|
|
}
|
|
|
|
|
|
|
|
return hadolintBin
|
2022-10-03 14:34:17 +02:00
|
|
|
}
|
|
|
|
|
2022-10-10 11:56:55 +02:00
|
|
|
String getOrInstallTrivy(Map options = [:]) {
|
|
|
|
String installDir = options.get('installDir', '/usr/local/bin')
|
|
|
|
String version = options.get('version', '0.27.1')
|
|
|
|
String forceDownload = options.get('forceDownload', false)
|
|
|
|
String installScriptDownloadUrl = options.get('downloadUrl', 'https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh')
|
2022-10-03 14:34:17 +02:00
|
|
|
|
2022-10-10 11:56:55 +02:00
|
|
|
String trivyBin = sh(returnStdout: true, script: 'which trivy || exit 0').trim()
|
|
|
|
if (trivyBin == '' || forceDownload) {
|
2022-10-03 14:34:17 +02:00
|
|
|
sh("""
|
|
|
|
mkdir -p '${installDir}'
|
2022-10-10 11:56:55 +02:00
|
|
|
curl -sfL '${installScriptDownloadUrl}' | sh -s -- -b '${installDir}' v${version}
|
|
|
|
chmod +x '${installDir}/trivy'
|
2022-10-03 14:34:17 +02:00
|
|
|
""")
|
|
|
|
|
2022-10-10 11:56:55 +02:00
|
|
|
trivyBin = "${installDir}/trivy"
|
2022-10-03 14:34:17 +02:00
|
|
|
}
|
|
|
|
|
2022-10-10 11:56:55 +02:00
|
|
|
return trivyBin
|
2022-10-03 14:34:17 +02:00
|
|
|
}
|