import org.jenkinsci.plugins.pipeline.modeldefinition.Utils def call(String baseImage = 'ubuntu:22.04', Map options = [:]) { Map hooks = options.get('hooks', [:]) String jobHistory = options.get('jobHistory', '10') node { properties([ buildDiscarder(logRotator(daysToKeepStr: jobHistory, numToKeepStr: jobHistory)), ]) stage('Cancel older jobs') { def buildNumber = env.BUILD_NUMBER as int if (buildNumber > 1) milestone(buildNumber - 1) milestone(buildNumber) } stage('Checkout project') { checkout(scm) } stage('Run pre hooks') { runHook(hooks, 'preSymfonyAppPipeline') } stage('Run in Symfony image') { def symfonyImage = buildDockerImage(baseImage, hooks) symfonyImage.inside() { def repo = env.JOB_NAME if (env.BRANCH_NAME ==~ /^PR-.*$/) { repo = env.JOB_NAME - "/${env.JOB_BASE_NAME}" } stage('Install composer dependencies') { sh ''' symfony composer install ''' } parallel([ 'php-security-check': { stage('Check PHP security issues') { catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { def auditReport = sh(script: 'local-php-security-checker --format=markdown || true', returnStdout: true) if (auditReport.trim() != '') { if (env.CHANGE_ID) { gitea.commentPullRequest(repo, env.CHANGE_ID, auditReport) } else { print auditReport } } if (!auditReport.contains('No packages have known vulnerabilities.')) { throw new Exception('Dependencies check failed !') } } } }, 'php-cs-fixer': { stage('Run PHP-CS-Fixer on modified code') { catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { if ( !fileExists('.php-cs-fixer.dist.php') ) { def phpCsFixerConfig = libraryResource 'com/cadoles/symfony/.php-cs-fixer.dist.php' writeFile file:'.php-cs-fixer.dist.php', text:phpCsFixerConfig } sh ''' CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "HEAD~..HEAD" | fgrep ".php" | tr "\n" " ") if ! echo "${CHANGED_FILES}" | grep -qE "^(\\.php-cs-fixer(\\.dist)\\.php?|composer\\.lock)$"; then EXTRA_ARGS=$(printf -- '--path-mode=intersection -- %s' "${CHANGED_FILES}"); else EXTRA_ARGS=''; fi symfony php $(which php-cs-fixer) fix --config=.php-cs-fixer.dist.php -v --dry-run --using-cache=no --format junit ${EXTRA_ARGS} > php-cs-fixer.xml || true ''' def report = sh(script: 'junit2md php-cs-fixer.xml', returnStdout: true) if (env.CHANGE_ID) { gitea.commentPullRequest(repo, env.CHANGE_ID, report) } else { print report } } } }, 'phpstan': { stage('Run phpstan') { catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { if ( !fileExists('phpstan.neon') ) { def phpStanConfig = libraryResource 'com/cadoles/symfony/phpstan.neon' writeFile file:'phpstan.neon', text:phpStanConfig } sh ''' symfony php $(which phpstan) analyze -l 1 --error-format=table src > phpstan.txt || true ''' def report = sh(script: 'cat phpstan.txt', returnStdout: true) report = '## Rapport PHPStan\n\n```\n' + report report = report + '\n```\n' if (env.CHANGE_ID) { gitea.commentPullRequest(repo, env.CHANGE_ID, report) } else { print report } } } } ]) } } stage('Run post hooks') { runHook(hooks, 'postSymfonyAppPipeline') } } } void buildDockerImage(String baseImage, Map hooks) { def imageName = 'cadoles-symfony-ci' dir(".${imageName}") { def dockerfile = libraryResource 'com/cadoles/symfony/Dockerfile' writeFile file:'Dockerfile', text: "FROM ${baseImage}\n\n" + dockerfile def addLetsEncryptCA = libraryResource 'com/cadoles/common/add-letsencrypt-ca.sh' writeFile file:'add-letsencrypt-ca.sh', text:addLetsEncryptCA runHook(hooks, 'buildSymfonyImage') def safeJobName = URLDecoder.decode(env.JOB_NAME).toLowerCase().replace('/', '-').replace(' ', '-') def imageTag = "${safeJobName}-${env.BUILD_ID}" return docker.build("${imageName}:${imageTag}", '.') } } void runHook(Map hooks, String name) { if (!hooks[name]) { println("No hook '${name}' defined. Skipping.") return } if (hooks[name] instanceof Closure) { hooks[name]() } else { error("Hook '${name}' seems to be defined but is not a closure !") } }