From bdaef91251e3d8759747be9b328ca78051858dd0 Mon Sep 17 00:00:00 2001 From: Philippe Caseiro Date: Mon, 19 Jun 2023 16:22:43 +0200 Subject: [PATCH] feat(kube): adding new groovy script for kube projects --- vars/kubecontainer.groovy | 213 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 vars/kubecontainer.groovy diff --git a/vars/kubecontainer.groovy b/vars/kubecontainer.groovy new file mode 100644 index 0000000..b33e52c --- /dev/null +++ b/vars/kubecontainer.groovy @@ -0,0 +1,213 @@ +/** +* 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 "" +* - imageTag - String - Tag apposé sur l'image après construction, par défaut résultat de la commande `git describe --always` +* - 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" +*/ +String buildAndPublishImage(Map options = [:]) { + 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() + String imageTag = options.get('imageTag', gitRef) + String gitCredentialsId = options.get('gitCredentialsId', 'forge-jenkins') + String dockerRepository = options.get('dockerRepository', 'reg.cadoles.com') + String dockerRepositoryCredentialsId = options.get('dockerRepositoryCredentialsId', 'reg.cadoles.com-jenkins') + 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) + + withCredentials([ + usernamePassword([ + credentialsId: dockerRepositoryCredentialsId, + usernameVariable: 'HUB_USERNAME', + passwordVariable: 'HUB_PASSWORD' + ]), + ]) { + stage('Validate Dockerfile with Hadolint') { + utils.when(!skipVerifications) { + runHadolintCheck(dockerfile, projectRepository) + } + } + + stage("Build image '${imageName}:${imageTag}'") { + git.withHTTPCredentials(gitCredentialsId) { + sh """ + make + """ + } + } + + stage('Validate image with Trivy') { + utils.when(!skipVerifications) { + runTrivyCheck("${imageName}:${imageTag}", projectRepository) + } + } + + stage("Publish image '${imageName}:${imageTag}'") { + utils.when(!dryRun) { + retry(2) { + sh """ + make release + """ + } + } + } + } +} + +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)} + |```""" + } 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") + + cache(maxCacheSize: cacheMaxSize, defaultBranch: cacheDefaultBranch, caches: [ + [$class: 'ArbitraryFileCache', path: cacheDirectory, compressionMethod: 'TARGZ'] + ]) { + sh("'${trivyBin}' --cache-dir '${cacheDirectory}' image -o '${reportFile}' ${trivyArgs} '${imageName}'") + } + + 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 +} + +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') + + String trivyBin = sh(returnStdout: true, script: 'which trivy || exit 0').trim() + if (trivyBin == '' || forceDownload) { + sh(""" + mkdir -p '${installDir}' + curl -sfL '${installScriptDownloadUrl}' | sh -s -- -b '${installDir}' v${version} + chmod +x '${installDir}/trivy' + """) + + trivyBin = "${installDir}/trivy" + } + + return trivyBin +}