
Jenkinsfile Generator
- 468 installs
- 286 repo stars
- Updated July 26, 2026
- akin-ozer/cc-devops-skills
jenkinsfile-generator is a Claude Code skill that scaffolds validated declarative and scripted Jenkinsfiles with build, test, scan, and deploy stages for developers onboarding repos to Jenkins CI/CD.
About
jenkinsfile-generator is a DevOps skill from akin-ozer/cc-devops-skills that produces production-ready Jenkinsfiles for Maven, Gradle, npm, Docker, and Kubernetes workloads. It defaults to declarative pipelines, switches to scripted Groovy only when dynamic stages are required, and always validates output through the paired jenkinsfile-validator skill. Three Python generators—generate_declarative.py, generate_scripted.py, and generate_shared_library.py—plus template assets cover parallel fail-fast tests, matrix axes, credential binding, SonarQube and OWASP scans, and Kubernetes pod agents. Developers reach for jenkinsfile-generator when onboarding microservices or monoliths to Jenkins, standardizing CI across teams, or adding approval-gated production deploy stages without hand-writing Groovy from scratch.
- Declarative pipeline scaffolding
- Build, test, and deploy stages
- Agent and workspace configuration
- Credential and artifact hooks
- Repo onboarding acceleration
Jenkinsfile Generator by the numbers
- 468 all-time installs (skills.sh)
- Ranked #267 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akin-ozer/cc-devops-skills --skill jenkinsfile-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 468 |
|---|---|
| repo stars | ★ 286 |
| Last updated | July 26, 2026 |
| Repository | akin-ozer/cc-devops-skills ↗ |
How do you scaffold a Jenkins CI pipeline?
Scaffold Jenkins pipelines for build, test, scan, and deploy stages when onboarding repos to Jenkins or standardizing CI across microservices and monoliths.
Who is it for?
Platform and application developers onboarding Java, Node, or containerized services to Jenkins with standardized CI/CD stages.
Skip if: GitHub Actions or GitLab CI workflows where Jenkins is not the target automation server.
When should I use this skill?
A developer asks to generate, create, or scaffold a Jenkinsfile, Jenkins shared library, or parallel CI pipeline.
What you get
Validated Jenkinsfile, optional shared-library scaffold, parallel or matrix stage blocks, and post-build artifact and notification configuration.
- Jenkinsfile
- shared library scaffold
- validated CI/CD pipeline config
By the numbers
- Includes 3 Python generator scripts for declarative, scripted, and shared-library output
- References 15+ Jenkins plugins in common_plugins.md including SonarQube and OWASP Dependency-Check
Files
Jenkinsfile Generator Skill
Generate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill.
Trigger Phrases
- "Generate a CI pipeline for Maven/Gradle/npm"
- "Create a Jenkins deployment pipeline with approvals"
- "Build a Jenkinsfile with parallel test stages"
- "Create a scripted pipeline with dynamic stage logic"
- "Scaffold a Jenkins shared library"
- "Generate a Jenkinsfile for Docker or Kubernetes agents"
When to Use
- Creating new Jenkinsfiles (declarative or scripted)
- CI/CD pipelines, Docker/Kubernetes deployments
- Parallel execution, matrix builds, parameterized pipelines
- DevSecOps pipelines with security scanning
- Shared library scaffolding
Declarative vs Scripted Decision Tree
1. Choose Declarative by default when stage order and behavior are mostly static. 2. Choose Scripted when runtime-generated stages, complex loops, or dynamic control flow are required. 3. Choose Shared Library scaffolding when request is about reusable pipeline functions (vars/, src/, resources/). 4. If unsure, start Declarative and only switch to Scripted if requirements cannot be expressed cleanly.
Template Map
| Template | Path | Use When |
|---|---|---|
| Declarative basic | assets/templates/declarative/basic.Jenkinsfile | Standard CI/CD with predictable stages |
| Declarative parallel example | examples/declarative-parallel.Jenkinsfile | Parallel test/build branches with fail-fast behavior |
| Declarative kubernetes example | examples/declarative-kubernetes.Jenkinsfile | Kubernetes agent execution using pod templates |
| Scripted basic | assets/templates/scripted/basic.Jenkinsfile | Complex conditional logic or generated stages |
| Shared library scaffold | Generated by scripts/generate_shared_library.py | Reusable pipeline functions and organization-wide patterns |
Quick Reference
// Minimal Declarative Pipeline
pipeline {
agent any
stages {
stage('Build') { steps { sh 'make' } }
stage('Test') { steps { sh 'make test' } }
}
}
// Error-tolerant stage
stage('Flaky Tests') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
sh 'run-flaky-tests.sh'
}
}
}
// Conditional deployment with approval
stage('Deploy') {
when { branch 'main'; beforeAgent true }
input { message 'Deploy to production?' }
steps { sh './deploy.sh' }
}| Option | Purpose |
|---|---|
timeout(time: 1, unit: 'HOURS') | Prevent hung builds |
buildDiscarder(logRotator(numToKeepStr: '10')) | Manage disk space |
disableConcurrentBuilds() | Prevent race conditions |
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') | Continue on error |
Core Capabilities
1. Declarative Pipelines (RECOMMENDED)
Process: 1. Read templates for structure reference:
- Read
assets/templates/declarative/basic.Jenkinsfileto understand the standard structure - Templates show the expected sections: pipeline → agent → environment → options → parameters → stages → post
- For complex requests, adapt the structure rather than copying verbatim
2. Consult reference documentation:
- Read
references/best_practices.mdfor performance, security, and reliability patterns - Read
references/common_plugins.mdfor plugin-specific syntax
3. Generate with required elements:
- Proper stages with descriptive names
- Environment block with credentials binding (never hardcode secrets)
- Options: timeout, buildDiscarder, timestamps, disableConcurrentBuilds
- Post conditions: always (cleanup), success (artifacts), failure (notifications)
- Always add `failFast true` or `parallelsAlwaysFailFast()` for parallel blocks
- Always include `fingerprint: true` when using `archiveArtifacts`
4. ALWAYS validate using devops-skills:jenkinsfile-validator skill
2. Scripted Pipelines
When: Complex conditional logic, dynamic generation, full Groovy control Process: 1. Read templates for structure reference:
- Read
assets/templates/scripted/basic.Jenkinsfilefor node/stage patterns - Understand try-catch-finally structure for error handling
2. Implement try-catch-finally for error handling 3. ALWAYS validate using devops-skills:jenkinsfile-validator skill
3. Parallel/Matrix Pipelines
Use parallel {} block or matrix {} with axes {} for multi-dimensional builds.
- Default behavior is fail-fast for generated parallel pipelines (
parallelsAlwaysFailFast()or stage-levelfailFast true).
4. Security Scanning (DevSecOps)
Add SonarQube, OWASP Dependency-Check, Trivy stages with fail thresholds.
5. Shared Library Scaffolding
python3 scripts/generate_shared_library.py --name my-library --package org.exampleDeclarative Syntax Reference
Agent Types
agent any // Any available agent
agent { label 'linux && docker' } // Label-based
agent { docker { image 'maven:3.9.11-eclipse-temurin-21' } }
agent { kubernetes { yaml '...' } } // K8s pod template
agent { kubernetes { yamlFile 'pod.yaml' } } // External YAMLEnvironment & Credentials
environment {
VERSION = '1.0.0'
AWS_KEY = credentials('aws-key-id') // Creates _USR and _PSW vars
}Options
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 1, unit: 'HOURS')
disableConcurrentBuilds()
timestamps()
parallelsAlwaysFailFast()
durabilityHint('PERFORMANCE_OPTIMIZED') // 2-6x faster for simple pipelines
}Parameters
parameters {
string(name: 'VERSION', defaultValue: '1.0.0')
choice(name: 'ENV', choices: ['dev', 'staging', 'prod'])
booleanParam(name: 'SKIP_TESTS', defaultValue: false)
}When Conditions
| Condition | Example |
|---|---|
branch | branch 'main' or branch pattern: 'release/*', comparator: 'GLOB' |
tag | tag pattern: 'v*', comparator: 'GLOB' |
changeRequest | changeRequest target: 'main' |
changeset | changeset 'src/**/*.java' |
expression | expression { env.DEPLOY == 'true' } |
allOf/anyOf/not | Combine conditions |
Add beforeAgent true to skip agent allocation if condition fails.
Error Handling
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { sh '...' }
warnError('msg') { sh '...' } // Mark UNSTABLE but continue
unstable(message: 'Coverage low') // Explicit UNSTABLE
error('Config missing') // Fail without stack tracePost Section
post {
always { junit '**/target/*.xml'; cleanWs() }
success { archiveArtifacts artifacts: '**/*.jar', fingerprint: true }
failure { slackSend color: 'danger', message: 'Build failed' }
fixed { echo 'Build fixed!' }
}Order: always → changed → fixed → regression → failure → success → unstable → cleanup
NOTE: Always use fingerprint: true with archiveArtifacts for build traceability and artifact tracking.
Parallel & Matrix
IMPORTANT: Always ensure parallel blocks fail fast on first failure using one of these approaches:
Option 1: Global (RECOMMENDED) - Use parallelsAlwaysFailFast() in pipeline options:
options {
parallelsAlwaysFailFast() // Applies to ALL parallel blocks in pipeline
}This is the preferred approach as it covers all parallel blocks automatically.
Option 2: Per-block - Use failFast true on individual parallel stages:
stage('Tests') {
failFast true // Only affects this parallel block
parallel {
stage('Unit') { steps { sh 'npm test:unit' } }
stage('E2E') { steps { sh 'npm test:e2e' } }
}
}NOTE: When parallelsAlwaysFailFast() is set in options, explicit failFast true on individual parallel blocks is redundant.
stage('Matrix') {
failFast true
matrix {
axes {
axis { name 'PLATFORM'; values 'linux', 'windows' }
axis { name 'BROWSER'; values 'chrome', 'firefox' }
}
excludes { exclude { axis { name 'PLATFORM'; values 'linux' }; axis { name 'BROWSER'; values 'safari' } } }
stages { stage('Test') { steps { echo "Testing ${PLATFORM}/${BROWSER}" } } }
}
}Input (Manual Approval)
stage('Deploy') {
input { message 'Deploy?'; ok 'Deploy'; submitter 'admin,ops' }
steps { sh './deploy.sh' }
}IMPORTANT: Place input outside steps to avoid holding agents.
Scripted Syntax Reference
node('agent-label') {
try {
stage('Build') { sh 'make build' }
stage('Test') { sh 'make test' }
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
deleteDir()
}
}
// Parallel
parallel(
'Unit': { node { sh 'npm test:unit' } },
'E2E': { node { sh 'npm test:e2e' } }
)
// Environment
withEnv(['VERSION=1.0.0']) { sh 'echo $VERSION' }
withCredentials([string(credentialsId: 'key', variable: 'KEY')]) { sh 'curl -H "Auth: $KEY" ...' }@NonCPS for Non-Serializable Operations
@NonCPS
def parseJson(String json) {
new groovy.json.JsonSlurper().parseText(json)
}Rules: No pipeline steps (sh, echo) inside @NonCPS. Use for JsonSlurper, iterators, regex Matchers.
Docker & Kubernetes
Docker Agent
agent { docker { image 'maven:3.9.11'; args '-v $HOME/.m2:/root/.m2'; reuseNode true } }Build & Push
def img = docker.build("myapp:${BUILD_NUMBER}")
docker.withRegistry('https://registry.example.com', 'creds') { img.push(); img.push('latest') }Kubernetes Pod
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9.11-eclipse-temurin-21
command: [sleep, 99d]
'''
}
}
// Use: container('maven') { sh 'mvn package' }Shared Libraries
@Library('my-shared-library') _
// or dynamically: library 'my-library@1.0.0'
// vars/log.groovy
def info(msg) { echo "INFO: ${msg}" }
// Usage
log.info 'Starting build'Validation Workflow
CRITICAL: ALWAYS validate using devops-skills:jenkinsfile-validator skill:
1. Generate Jenkinsfile 2. Invoke devops-skills:jenkinsfile-validator skill 3. Handle validation results by severity:
- ERRORS: MUST fix before presenting to user - these break the pipeline
- WARNINGS: SHOULD fix - these indicate potential issues
- INFO/SUGGESTIONS: Consider applying based on use case:
failFast truefor parallel blocks → apply by default- Build triggers → ask user if they want automated builds
- Other optimizations → apply if they improve the pipeline
4. Re-validate after fixes 5. Only present validated Jenkinsfiles to user
Validation commands:
# Full validation (syntax + security + best practices)
bash ../jenkinsfile-validator/scripts/validate_jenkinsfile.sh Jenkinsfile
# Syntax only (fastest)
bash ../jenkinsfile-validator/scripts/validate_jenkinsfile.sh --syntax-only JenkinsfileGenerator Scripts
When to use scripts vs manual generation:
- Use scripts for: Simple, standard pipelines with common patterns (basic CI, straightforward CD)
- Use manual generation for: Complex pipelines with multiple features (parallel tests + security scanning + Docker + K8s deployments), custom logic, or non-standard requirements
Script Arguments: Required vs Optional
generate_declarative.py- Required:
--output - Optional:
--stages,--agent,--build-tool,--build-cmd,--test-cmd,--deploy-*,--notification-*,--archive-artifacts,--k8s-yaml - Notes:
--k8s-yamlaccepts either inline YAML content or a path to an existing.yaml/.ymlfile.- Stage keys are validated (
[a-z0-9_-]) and shell commands are emitted as escaped Groovy literals. generate_scripted.py- Required:
--output - Optional: stage/agent/SCM/notification parameters depending on requested pipeline features.
generate_shared_library.py- Required:
--name - Optional:
--package,--output - Shared library deployment helper now includes explicit rollout target (
deployment/<name>) and notification helper emits valid HTML email bodies.
# Declarative (simple pipelines)
python3 scripts/generate_declarative.py --output Jenkinsfile --stages build,test,deploy --agent docker
# Scripted (simple pipelines)
python3 scripts/generate_scripted.py --output Jenkinsfile --stages build,test --agent label:linux
# Shared Library (always use script for scaffolding)
python3 scripts/generate_shared_library.py --name my-library --package com.exampleDone Criteria
- Pipeline style selection (Declarative vs Scripted) is explicit and justified.
- Generated Jenkinsfiles pass smoke validation with executable validator commands.
- Parallel pipelines are fail-fast by default unless user explicitly requests otherwise.
- Custom stage names and shell commands are safely emitted (no unescaped Groovy literals).
--k8s-yamlworks with both inline YAML and existing file paths.- Notification-enabled post blocks still archive artifacts when requested.
Plugin Documentation Lookup
Always consult Context7 or WebSearch for:
- Plugins NOT covered in
references/common_plugins.md - Version-specific documentation requests
- Complex plugin configurations or advanced options
- When user explicitly asks for latest documentation
May skip external lookup when:
- Using basic plugin syntax already documented in
references/common_plugins.md - Simple, well-documented plugin steps (e.g., basic
sh,checkout scm,junit)
Plugins covered in common_plugins.md: Git, Docker, Kubernetes, Credentials, JUnit, Slack, SonarQube, OWASP Dependency-Check, Email, AWS, Azure, HTTP Request, Microsoft Teams, Nexus, Artifactory, GitHub
Lookup methods (in order of preference): 1. Context7: mcp__context7__resolve-library-id with /jenkinsci/<plugin-name>-plugin 2. WebSearch: Jenkins [plugin-name] plugin documentation 2025 3. Official: plugins.jenkins.io, jenkins.io/doc/pipeline/steps/
References
references/best_practices.md- Performance, security, reliability patternsreferences/common_plugins.md- Git, Docker, K8s, credentials, notificationsassets/templates/- Declarative and scripted templatesdevops-skills:jenkinsfile-validatorskill - Syntax and best practices validation
Always prefer Declarative unless scripted flexibility is required.
rendered/
output/
# Test artifacts
generated/
// Declarative Pipeline Template
// Replace [PLACEHOLDER] values with your configuration
// Generated by Jenkinsfile Generator
pipeline {
// Agent Configuration
// Options: any, none, label, docker, dockerfile, kubernetes
agent [AGENT_TYPE]
// Environment Variables (optional)
environment {
// Static variables
BUILD_ENV = '[ENVIRONMENT]'
VERSION = '[VERSION]'
// Credential bindings
// DOCKER_CREDENTIALS = credentials('[CREDENTIALS_ID]')
}
// Pipeline Parameters (optional)
parameters {
string(name: 'BRANCH', defaultValue: 'main', description: 'Branch to build')
choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'production'], description: 'Target environment')
booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: 'Skip tests')
}
// Pipeline Options
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
timeout(time: 1, unit: 'HOURS')
disableConcurrentBuilds()
}
// Build Triggers (optional)
// triggers {
// cron('H */4 * * 1-5')
// pollSCM('H/15 * * * *')
// }
// Tool Configuration (optional)
// tools {
// maven 'Maven-3.9.9'
// jdk 'JDK-21'
// }
stages {
stage('Checkout') {
steps {
checkout scm
// Or explicit checkout:
// git branch: 'main', url: '[REPOSITORY_URL]'
}
}
stage('Build') {
steps {
sh '[BUILD_COMMAND]'
// Examples:
// sh 'mvn clean compile'
// sh 'npm install && npm run build'
// sh 'go build ./...'
}
}
stage('Test') {
when {
not {
expression { params.SKIP_TESTS }
}
}
steps {
sh '[TEST_COMMAND]'
// Examples:
// sh 'mvn test'
// sh 'npm test'
// sh 'pytest'
}
post {
always {
junit '[TEST_RESULTS_PATH]'
// Examples:
// junit '**/target/surefire-reports/*.xml'
// junit '**/test-results/*.xml'
}
}
}
stage('Package') {
steps {
sh '[PACKAGE_COMMAND]'
// Examples:
// sh 'mvn package -DskipTests'
// sh 'npm run build:prod'
}
}
// Optional: Docker Build Stage
// stage('Docker Build') {
// steps {
// script {
// docker.build("[IMAGE_NAME]:${BUILD_NUMBER}")
// }
// }
// }
// Optional: Deploy Stage with Approval
stage('Deploy') {
when {
branch 'main'
}
input {
message 'Deploy to [ENVIRONMENT]?'
ok 'Deploy'
submitter '[APPROVERS]'
}
steps {
sh '[DEPLOY_COMMAND]'
// Examples:
// sh './deploy.sh production'
// sh 'kubectl apply -f k8s/'
}
}
}
post {
always {
// Always cleanup workspace
cleanWs()
}
success {
// Archive artifacts on success
archiveArtifacts artifacts: '[ARTIFACT_PATTERN]', fingerprint: true
// Examples:
// archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
// Optional: Slack notification
// slackSend color: 'good', message: "Build ${env.BUILD_NUMBER} succeeded"
}
failure {
// Notify on failure
echo 'Pipeline failed!'
// Optional: Email notification
// emailext(
// subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
// body: "Check ${env.BUILD_URL}",
// to: '[EMAIL_RECIPIENTS]'
// )
}
}
}// Scripted Pipeline Template
// Replace [PLACEHOLDER] values with your configuration
// Generated by Jenkinsfile Generator
// Optional: Load shared library
// @Library('my-shared-library') _
// Node selection (optional: specify label)
node('[AGENT_LABEL]') {
// Environment setup
def buildVersion = "${env.BUILD_NUMBER}"
def artifactName = "[ARTIFACT_NAME]"
try {
stage('Checkout') {
checkout scm
// Or explicit checkout:
// git branch: 'main', url: '[REPOSITORY_URL]'
}
stage('Build') {
// Optional: Run inside Docker container
// docker.image('[DOCKER_IMAGE]').inside {
// sh '[BUILD_COMMAND]'
// }
sh '[BUILD_COMMAND]'
// Examples:
// sh 'mvn clean compile'
// sh 'npm install && npm run build'
// sh 'go build ./...'
}
stage('Test') {
sh '[TEST_COMMAND]'
// Examples:
// sh 'mvn test'
// sh 'npm test'
// sh 'pytest --junitxml=test-results.xml'
// Publish test results
junit '[TEST_RESULTS_PATH]'
// Examples:
// junit '**/target/surefire-reports/*.xml'
// junit '**/test-results.xml'
}
stage('Package') {
sh '[PACKAGE_COMMAND]'
// Examples:
// sh 'mvn package -DskipTests'
// sh 'npm run build:prod'
}
// Optional: Docker Build and Push
// stage('Docker Build') {
// def image = docker.build("[IMAGE_NAME]:${buildVersion}")
//
// stage('Docker Push') {
// docker.withRegistry('[REGISTRY_URL]', '[REGISTRY_CREDENTIALS]') {
// image.push()
// image.push('latest')
// }
// }
// }
// Optional: Parallel Testing
// stage('Parallel Tests') {
// parallel(
// 'Unit Tests': {
// node {
// sh 'npm run test:unit'
// }
// },
// 'Integration Tests': {
// node {
// sh 'npm run test:integration'
// }
// }
// )
// }
// Conditional deployment based on branch
if (env.BRANCH_NAME == 'main') {
stage('Deploy to Production') {
// Wait for approval
input message: 'Deploy to production?', submitter: '[APPROVERS]'
sh '[DEPLOY_COMMAND]'
// Examples:
// sh './deploy.sh production'
// sh 'kubectl apply -f k8s/'
}
} else if (env.BRANCH_NAME == 'develop') {
stage('Deploy to Staging') {
sh '[DEPLOY_STAGING_COMMAND]'
}
}
// Archive artifacts on success
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
archiveArtifacts artifacts: '[ARTIFACT_PATTERN]', fingerprint: true
// Examples:
// archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
}
} catch (Exception e) {
currentBuild.result = 'FAILURE'
echo "Pipeline failed: ${e.message}"
// Optional: Send failure notification
// emailext(
// subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
// body: "Error: ${e.message}\nCheck ${env.BUILD_URL}",
// to: '[EMAIL_RECIPIENTS]'
// )
// Optional: Slack notification
// slackSend color: 'danger', message: "Build ${env.BUILD_NUMBER} failed"
throw e
} finally {
// Always cleanup workspace
deleteDir()
}
}// Declarative Pipeline - Basic CI Example
// Generated by Jenkinsfile Generator
// Date: 2025-01-18
pipeline {
agent any
tools {
maven 'Maven-3.9.9'
jdk 'JDK-21'
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('Package') {
steps {
sh 'mvn package'
}
}
}
post {
success {
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
}
always {
deleteDir()
}
failure {
echo 'Pipeline failed!'
}
}
}// Declarative Pipeline - Docker Build and Push
// Generated by Jenkinsfile Generator
// Date: 2025-01-18
pipeline {
agent any
environment {
DOCKER_IMAGE = "${JOB_NAME}"
DOCKER_REGISTRY = 'registry.example.com'
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build Application') {
agent {
docker {
image 'maven:3.9.9-eclipse-temurin-21'
args '-v $HOME/.m2:/root/.m2'
}
}
steps {
sh 'mvn clean package'
}
}
stage('Build Docker Image') {
steps {
script {
// Build once and store reference - avoid rebuilding for each tag
def customImage = docker.build("${DOCKER_IMAGE}:${BUILD_NUMBER}")
// Tag with 'latest' using the same built image
customImage.tag('latest')
}
}
}
stage('Push to Registry') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
def customImage = docker.image("${DOCKER_IMAGE}:${BUILD_NUMBER}")
// Push both tags - build number and latest
customImage.push()
customImage.push('latest')
}
}
}
}
}
post {
success {
echo "Successfully built and pushed ${DOCKER_IMAGE}:${BUILD_NUMBER}"
}
always {
deleteDir()
}
}
}// Declarative Pipeline - Kubernetes Agent with Multi-Container Build
// Generated by Jenkinsfile Generator
// Date: 2025-01-18
// Updated: Using modern YAML syntax (recommended approach)
pipeline {
agent {
kubernetes {
// Modern YAML syntax - recommended for Kubernetes plugin
yaml '''
apiVersion: v1
kind: Pod
metadata:
labels:
jenkins-build: true
spec:
containers:
- name: maven
image: maven:3.9.9-eclipse-temurin-21
command:
- sleep
args:
- 99d
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
- name: kubectl
image: bitnami/kubectl:latest
command:
- sleep
args:
- 99d
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "200m"
'''
}
}
environment {
DEPLOYMENT_NAME = 'myapp'
NAMESPACE = 'production'
}
options {
timeout(time: 1, unit: 'HOURS')
timestamps()
buildDiscarder(logRotator(numToKeepStr: '10'))
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
container('maven') {
sh 'mvn clean package -DskipTests'
}
}
}
stage('Test') {
steps {
container('maven') {
sh 'mvn test'
}
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('Deploy to Kubernetes') {
when {
branch 'main'
beforeAgent true
}
steps {
container('kubectl') {
sh "kubectl apply -f k8s/deployment.yaml -n ${NAMESPACE}"
sh "kubectl set image deployment/${DEPLOYMENT_NAME} app=\${DOCKER_IMAGE}:\${BUILD_NUMBER} -n ${NAMESPACE}"
sh "kubectl rollout status deployment/${DEPLOYMENT_NAME} -n ${NAMESPACE} --timeout=300s"
}
}
}
}
post {
success {
echo "Successfully deployed to Kubernetes namespace: ${NAMESPACE}"
}
failure {
echo "Pipeline failed - check logs for details"
}
always {
cleanWs()
}
}
}// Declarative Pipeline - Matrix Build Example
// Generated by Jenkinsfile Generator
// Demonstrates multi-dimensional matrix builds across platforms and browsers
pipeline {
agent none
parameters {
choice(
name: 'PLATFORM_FILTER',
choices: ['all', 'linux', 'windows', 'mac'],
description: 'Run on specific platform only'
)
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
timeout(time: 2, unit: 'HOURS')
}
stages {
stage('Checkout') {
agent any
steps {
checkout scm
stash name: 'source', includes: '**/*'
}
}
stage('Build and Test Matrix') {
matrix {
agent {
label "${PLATFORM}-agent"
}
// Only run cells matching the platform filter parameter
when {
anyOf {
expression { params.PLATFORM_FILTER == 'all' }
expression { params.PLATFORM_FILTER == env.PLATFORM }
}
}
// Define the matrix axes
axes {
axis {
name 'PLATFORM'
values 'linux', 'windows', 'mac'
}
axis {
name 'BROWSER'
values 'chrome', 'firefox', 'safari', 'edge'
}
axis {
name 'NODE_VERSION'
values '20', '22', '24'
}
}
// Exclude invalid combinations
excludes {
// Safari is not available on Linux
exclude {
axis {
name 'PLATFORM'
values 'linux'
}
axis {
name 'BROWSER'
values 'safari'
}
}
// Edge is only available on Windows
exclude {
axis {
name 'PLATFORM'
notValues 'windows'
}
axis {
name 'BROWSER'
values 'edge'
}
}
// Safari is not available on Windows
exclude {
axis {
name 'PLATFORM'
values 'windows'
}
axis {
name 'BROWSER'
values 'safari'
}
}
}
stages {
stage('Setup') {
steps {
unstash 'source'
echo "Setting up Node ${NODE_VERSION} on ${PLATFORM}"
// Platform-specific setup
script {
if (env.PLATFORM == 'windows') {
bat "echo Setting up Node ${NODE_VERSION}"
} else {
sh "echo Setting up Node ${NODE_VERSION}"
}
}
}
}
stage('Install Dependencies') {
steps {
script {
if (env.PLATFORM == 'windows') {
bat 'npm ci'
} else {
sh 'npm ci'
}
}
}
}
stage('Build') {
steps {
echo "Building for ${PLATFORM} with Node ${NODE_VERSION}"
script {
if (env.PLATFORM == 'windows') {
bat 'npm run build'
} else {
sh 'npm run build'
}
}
}
}
stage('Test') {
steps {
echo "Testing on ${PLATFORM} with ${BROWSER} using Node ${NODE_VERSION}"
script {
if (env.PLATFORM == 'windows') {
bat "npm run test:e2e -- --browser ${BROWSER}"
} else {
sh "npm run test:e2e -- --browser ${BROWSER}"
}
}
}
post {
always {
junit "**/test-results/${PLATFORM}-${BROWSER}-${NODE_VERSION}/*.xml"
}
}
}
}
}
}
stage('Aggregate Results') {
agent any
steps {
echo 'Aggregating test results from all matrix cells...'
}
post {
always {
// Archive all test results
archiveArtifacts artifacts: '**/test-results/**/*.xml', allowEmptyArchive: true
}
}
}
}
post {
success {
echo 'All matrix builds completed successfully!'
}
failure {
echo 'One or more matrix cells failed!'
}
}
}// Declarative Pipeline - Parallel Test Execution
// Generated by Jenkinsfile Generator
// Demonstrates native parallel execution in Declarative Pipeline
pipeline {
agent none
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
// Abort all parallel stages if one fails
parallelsAlwaysFailFast()
}
stages {
stage('Checkout') {
agent any
steps {
checkout scm
stash name: 'source', includes: '**/*'
}
}
stage('Build') {
agent {
docker {
image 'node:20-alpine'
}
}
steps {
unstash 'source'
sh 'npm ci'
sh 'npm run build'
stash name: 'build-artifacts', includes: 'dist/**/*'
}
}
stage('Parallel Tests') {
// Native parallel execution in Declarative Pipeline
// All stages inside parallel {} run concurrently
parallel {
stage('Unit Tests') {
agent {
docker {
image 'node:20-alpine'
}
}
steps {
unstash 'source'
sh 'npm ci'
sh 'npm run test:unit'
}
post {
always {
junit '**/test-results/unit/*.xml'
}
}
}
stage('Integration Tests') {
agent {
docker {
image 'node:20-alpine'
}
}
steps {
unstash 'source'
sh 'npm ci'
sh 'npm run test:integration'
}
post {
always {
junit '**/test-results/integration/*.xml'
}
}
}
stage('E2E Tests') {
agent {
docker {
image 'cypress/included:13.6.0'
}
}
steps {
unstash 'source'
unstash 'build-artifacts'
sh 'npm ci'
sh 'npm run test:e2e'
}
post {
always {
junit '**/test-results/e2e/*.xml'
}
failure {
archiveArtifacts artifacts: 'cypress/screenshots/**/*', allowEmptyArchive: true
archiveArtifacts artifacts: 'cypress/videos/**/*', allowEmptyArchive: true
}
}
}
stage('Security Scan') {
agent any
steps {
unstash 'source'
sh 'npm audit --audit-level=high || true'
}
}
}
}
stage('Code Coverage Report') {
agent any
steps {
unstash 'source'
echo 'Aggregating test coverage reports...'
}
post {
always {
publishHTML([
allowMissing: true,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
}
post {
success {
echo 'All parallel tests passed!'
}
failure {
echo 'One or more parallel tests failed!'
}
}
}// Declarative Pipeline - DevSecOps Security Scanning
// Generated by Jenkinsfile Generator
// Integrates SonarQube, OWASP Dependency-Check, and Trivy for comprehensive security scanning
pipeline {
agent any
environment {
DOCKER_IMAGE = "${JOB_NAME}"
DOCKER_REGISTRY = 'registry.example.com'
SONARQUBE_SERVER = 'sonarqube-server'
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
timeout(time: 1, unit: 'HOURS')
}
tools {
maven 'Maven-3.9.9'
jdk 'JDK-21'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile -DskipTests'
}
}
stage('Security Scans') {
parallel {
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv("${SONARQUBE_SERVER}") {
sh '''
mvn sonar:sonar \
-Dsonar.projectKey=${JOB_NAME} \
-Dsonar.projectName="${JOB_NAME}" \
-Dsonar.java.binaries=target/classes
'''
}
}
}
stage('OWASP Dependency Check') {
steps {
// Using OWASP Dependency-Check Plugin
dependencyCheck(
additionalArguments: '''
--scan .
--format HTML
--format XML
--format JSON
--out dependency-check-report
--suppression suppression.xml
--failOnCVSS 7
''',
odcInstallation: 'OWASP-Dependency-Check'
)
}
post {
always {
dependencyCheckPublisher(
pattern: 'dependency-check-report/dependency-check-report.xml',
failedTotalCritical: 0,
failedTotalHigh: 5,
unstableTotalCritical: 0,
unstableTotalHigh: 3
)
}
}
}
stage('Secret Detection') {
steps {
sh '''
# Using gitleaks for secret detection
docker run --rm -v "$(pwd):/repo" \
zricethezav/gitleaks:latest \
detect --source="/repo" \
--report-path="/repo/gitleaks-report.json" \
--report-format json \
--exit-code 1 || true
'''
}
post {
always {
archiveArtifacts artifacts: 'gitleaks-report.json', allowEmptyArchive: true
}
}
}
}
}
stage('SonarQube Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
stage('Unit Tests') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
jacoco(
execPattern: '**/target/jacoco.exec',
classPattern: '**/target/classes',
sourcePattern: '**/src/main/java',
exclusionPattern: '**/test/**'
)
}
}
}
stage('Package') {
steps {
sh 'mvn package -DskipTests'
}
}
stage('Build Docker Image') {
steps {
script {
def customImage = docker.build("${DOCKER_IMAGE}:${BUILD_NUMBER}")
customImage.tag('latest')
}
}
}
stage('Trivy Container Scan') {
steps {
sh '''
# Trivy container image vulnerability scan
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ${WORKSPACE}/trivy-cache:/root/.cache/ \
aquasec/trivy:latest image \
--severity CRITICAL,HIGH \
--exit-code 1 \
--ignore-unfixed \
--format table \
--output trivy-report.txt \
${DOCKER_IMAGE}:${BUILD_NUMBER} || true
# Generate JSON report for archiving
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ${WORKSPACE}/trivy-cache:/root/.cache/ \
aquasec/trivy:latest image \
--severity CRITICAL,HIGH,MEDIUM \
--format json \
--output trivy-report.json \
${DOCKER_IMAGE}:${BUILD_NUMBER} || true
'''
}
post {
always {
archiveArtifacts artifacts: 'trivy-report.*', allowEmptyArchive: true
}
}
}
stage('Trivy Filesystem Scan') {
steps {
sh '''
# Trivy filesystem scan for IaC misconfigurations
docker run --rm \
-v ${WORKSPACE}:/workspace \
aquasec/trivy:latest fs \
--severity CRITICAL,HIGH \
--format table \
--scanners vuln,secret,misconfig \
/workspace
'''
}
}
stage('Push to Registry') {
when {
allOf {
branch 'main'
// Only push if all security scans passed
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' }
}
}
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
def customImage = docker.image("${DOCKER_IMAGE}:${BUILD_NUMBER}")
customImage.push()
customImage.push('latest')
}
}
}
}
}
post {
always {
// Archive all security reports
archiveArtifacts artifacts: '**/dependency-check-report/**/*', allowEmptyArchive: true
// Clean workspace
deleteDir()
}
success {
echo 'All security scans passed! Pipeline completed successfully.'
}
failure {
echo 'Security vulnerabilities detected or pipeline failed!'
// Send notification on security failures
// emailext(
// subject: "Security Scan Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
// body: "Security vulnerabilities were detected. Check ${env.BUILD_URL} for details.",
// to: 'security-team@example.com'
// )
}
unstable {
echo 'Security scan found warnings. Review the reports.'
}
}
}// Declarative Pipeline - Using Shared Library
// Generated by Jenkinsfile Generator
// Demonstrates shared library usage patterns
@Library('my-jenkins-library') _
import org.example.Utils
import org.example.Docker
import org.example.Notifications
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'registry.example.com'
DOCKER_IMAGE = 'myapp'
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
timeout(time: 30, unit: 'MINUTES')
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
// Using shared library utility class
env.GIT_BRANCH = Utils.getBranchName(this)
env.GIT_COMMIT_SHORT = Utils.getShortCommitSha(this)
log.info "Building branch: ${env.GIT_BRANCH} at commit: ${env.GIT_COMMIT_SHORT}"
}
}
}
stage('Validate Environment') {
steps {
script {
// Validate required environment variables using shared library
Utils.validateEnvVars(this, ['DOCKER_REGISTRY', 'DOCKER_IMAGE'])
log.success 'Environment validation passed'
}
}
}
stage('Build') {
steps {
script {
log.info 'Starting build process...'
sh 'mvn clean package -DskipTests'
log.success 'Build completed'
}
}
}
stage('Test') {
steps {
script {
log.info 'Running tests...'
sh 'mvn test'
log.success 'Tests passed'
}
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('Security Scan') {
steps {
script {
// Using shared library security scanning function
securityScan(
sonarqube: true,
sonarqubeServer: 'sonarqube-server',
owaspDependencyCheck: true,
failOnCritical: true
)
}
}
}
stage('Docker Build') {
steps {
script {
// Using shared library Docker class
def dockerUtil = new Docker(
this,
env.DOCKER_REGISTRY,
'docker-registry-credentials'
)
// Build with commit SHA tag
def image = dockerUtil.build(
env.DOCKER_IMAGE,
env.GIT_COMMIT_SHORT
)
// Store image reference for later stages
env.DOCKER_IMAGE_TAG = "${env.DOCKER_REGISTRY}/${env.DOCKER_IMAGE}:${env.GIT_COMMIT_SHORT}"
log.success "Docker image built: ${env.DOCKER_IMAGE_TAG}"
}
}
}
stage('Container Security Scan') {
steps {
script {
// Scan the built container image
securityScan(
trivy: true,
trivyImage: env.DOCKER_IMAGE_TAG,
failOnCritical: true
)
}
}
}
stage('Push to Registry') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
script {
// Using shared library Docker build/push function
dockerBuild(
imageName: env.DOCKER_IMAGE,
registry: env.DOCKER_REGISTRY,
credentialsId: 'docker-registry-credentials',
tags: [env.GIT_COMMIT_SHORT, env.GIT_BRANCH, 'latest'],
push: true
)
log.success 'Docker image pushed to registry'
}
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
script {
// Using shared library deployment function
deployApp(
environment: 'staging',
kubeConfig: 'kubeconfig-staging',
namespace: 'myapp-staging',
manifests: 'k8s/staging/',
approval: false
)
}
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
script {
// Deployment with approval gate
deployApp(
environment: 'production',
kubeConfig: 'kubeconfig-production',
namespace: 'myapp-production',
manifests: 'k8s/production/',
approval: true
)
}
}
}
}
post {
always {
script {
// Using shared library notification class
def notify = new Notifications(this)
notify.buildStatus('#builds', 'devops-team@example.com')
}
cleanWs()
}
success {
script {
log.success "Pipeline completed successfully for ${env.GIT_BRANCH}"
}
}
failure {
script {
log.error "Pipeline failed for ${env.GIT_BRANCH}"
}
}
}
}// Scripted Pipeline - Basic CI Example
// Generated by Jenkinsfile Generator
// Date: 2025-01-18
node {
try {
stage('Checkout') {
checkout scm
}
stage('Build') {
sh 'mvn clean compile'
}
stage('Test') {
sh 'mvn test'
junit '**/target/surefire-reports/*.xml'
}
stage('Package') {
sh 'mvn package'
}
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
}
} catch (Exception e) {
currentBuild.result = 'FAILURE'
echo "Pipeline failed: ${e.message}"
throw e
} finally {
deleteDir()
}
}// Scripted Pipeline - Conditional Deployment
// Generated by Jenkinsfile Generator
// Date: 2025-01-18
node {
try {
stage('Checkout') {
checkout scm
}
stage('Build') {
sh 'npm install'
sh 'npm run build'
}
stage('Test') {
sh 'npm test'
junit '**/test-results/*.xml'
}
// Conditional deployment based on branch
if (env.BRANCH_NAME == 'main') {
stage('Deploy to Production') {
input message: 'Deploy to production?', submitter: 'admin,ops-team'
sh './deploy.sh production'
echo 'Deployed to production'
}
} else if (env.BRANCH_NAME ==~ /release\/.*/) {
stage('Deploy to Staging') {
sh './deploy.sh staging'
echo 'Deployed to staging'
}
} else if (env.BRANCH_NAME ==~ /feature\/.*/) {
stage('Deploy to Dev') {
sh './deploy.sh dev'
echo 'Deployed to dev environment'
}
} else {
echo "Branch ${env.BRANCH_NAME} - no deployment"
}
} catch (Exception e) {
currentBuild.result = 'FAILURE'
emailext(
subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: "Error: ${e.message}\nCheck console output at ${env.BUILD_URL}",
to: 'team@example.com'
)
throw e
} finally {
deleteDir()
}
}// Scripted Pipeline - Docker Build with Sidecar
// Generated by Jenkinsfile Generator
// Date: 2025-01-18
node {
try {
stage('Checkout') {
checkout scm
}
stage('Build') {
docker.image('maven:3.9.9-eclipse-temurin-21').inside('-v $HOME/.m2:/root/.m2') {
sh 'mvn clean package'
}
}
stage('Integration Tests with Database') {
docker.image('mysql:8-oracle').withRun('-e "MYSQL_ROOT_PASSWORD=test123" -p 3306:3306') { db ->
// Wait for MySQL to be ready
sh 'while ! mysqladmin ping -h0.0.0.0 --silent; do sleep 1; done'
// Run tests
docker.image('maven:3.9.9-eclipse-temurin-21').inside("--link ${db.id}:mysql") {
sh 'mvn verify -Dspring.datasource.url=jdbc:mysql://mysql:3306/test'
}
}
}
stage('Build Docker Image') {
def customImage = docker.build("myapp:${env.BUILD_NUMBER}")
stage('Test Docker Image') {
customImage.inside {
sh 'node --version'
}
}
stage('Push Docker Image') {
docker.withRegistry('https://registry.example.com', 'docker-credentials') {
customImage.push()
customImage.push('latest')
}
}
}
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
deleteDir()
}
}Jenkins Pipeline Best Practices - Generator Reference
Quick reference for generating best-practice Jenkinsfiles.
Performance Best Practices
1. Combine Shell Commands
Bad:
sh 'echo "Starting build"'
sh 'mkdir build'
sh 'cd build && cmake ..'
sh 'make'Good:
sh '''
echo "Starting build"
mkdir build
cd build && cmake ..
make
'''2. Use Agent-Based Operations
Bad (runs on controller):
def data = readFile('data.json')
def parsed = new groovy.json.JsonSlurper().parseText(data)Good (runs on agent):
def result = sh(script: 'jq ".field" data.json', returnStdout: true).trim()3. Minimize Controller Memory Usage
Bad:
def logFile = readFile('huge-log.txt') // Loads entire fileGood:
def errorCount = sh(script: 'grep ERROR huge-log.txt | wc -l', returnStdout: true).trim()---
Security Best Practices
1. Never Hardcode Credentials
Bad:
sh 'docker login -u admin -p password123'
sh 'curl -H "Authorization: Bearer abc123xyz" https://api.example.com'Good:
withCredentials([usernamePassword(
credentialsId: 'docker-hub',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS'
}2. Use Environment Credentials Binding
environment {
DOCKER_CREDENTIALS = credentials('docker-hub-credentials')
// Creates DOCKER_CREDENTIALS_USR and DOCKER_CREDENTIALS_PSW
API_KEY = credentials('api-key')
}3. Validate User Input
Bad:
sh "git checkout ${params.BRANCH}" // Injection risk!Good:
parameters {
choice(name: 'BRANCH', choices: ['main', 'develop', 'release'], description: 'Branch to build')
}
// Or validate
def branch = params.BRANCH
if (!branch.matches(/^[a-zA-Z0-9_\-\/]+$/)) {
error "Invalid branch name: ${branch}"
}---
Reliability Best Practices
1. Always Use Timeouts
// Pipeline level
options {
timeout(time: 1, unit: 'HOURS')
}
// Stage level
stage('Long Running') {
options {
timeout(time: 30, unit: 'MINUTES')
}
steps {
sh './long-task.sh'
}
}2. Implement Error Handling
Declarative:
post {
always {
cleanWs()
}
success {
slackSend color: 'good', message: "Build succeeded"
}
failure {
slackSend color: 'danger', message: "Build failed"
}
}Scripted:
node {
try {
stage('Build') { sh 'make build' }
stage('Test') { sh 'make test' }
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
cleanWs()
}
}3. Use catchError for Resilient Pipelines
Allow pipelines to continue after non-critical failures:
catchError - Continue on Failure:
// Mark stage as failed but continue pipeline
stage('Non-Critical Tests') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh 'npm run test:experimental'
}
}
}
// Mark build as unstable if integration tests fail
stage('Integration Tests') {
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') {
sh 'npm run test:integration'
}
}
}warnError - Quick Unstable Pattern:
stage('Code Analysis') {
steps {
warnError('Linting warnings detected') {
sh 'npm run lint'
}
}
}unstable - Explicit Unstable Status:
stage('Coverage Check') {
steps {
script {
def coverage = sh(script: 'get-coverage.sh', returnStdout: true).trim().toInteger()
if (coverage < 80) {
unstable(message: "Code coverage ${coverage}% is below 80% threshold")
}
}
}
}error - Fail Without Stack Trace:
stage('Validation') {
steps {
script {
if (!fileExists('config.json')) {
error('Configuration file not found')
}
}
}
}Combined Error Handling Pattern (Recommended):
stage('Test Suite') {
steps {
// Critical - fail build if unit tests fail
sh 'npm run test:unit'
// Important - mark unstable if integration tests fail
catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') {
sh 'npm run test:integration'
}
// Non-critical - warn only
warnError('Smoke tests had warnings') {
sh 'npm run test:smoke'
}
// Optional - continue regardless
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh 'npm run test:experimental'
}
}
}catchError Parameters:
| Parameter | Values | Description |
|---|---|---|
buildResult | SUCCESS, UNSTABLE, FAILURE, NOT_BUILT, ABORTED | Overall build result on error |
stageResult | SUCCESS, UNSTABLE, FAILURE, NOT_BUILT, ABORTED | Stage result on error |
message | String | Message logged on error |
catchInterruptions | true/false | Whether to catch timeout/abort exceptions (default: true) |
3. Clean Workspace
post {
always {
cleanWs()
}
}
// Or use deleteDir()
post {
cleanup {
deleteDir()
}
}4. Implement Retries
retry(3) {
sh 'curl -f https://flaky-api.example.com/data'
}
// With backoff
script {
def attempts = 0
retry(3) {
attempts++
if (attempts > 1) {
sleep time: attempts * 10, unit: 'SECONDS'
}
sh 'flaky-command'
}
}---
Pipeline Structure Best Practices
1. Use Descriptive Stage Names
Bad:
stage('Step 1') { }
stage('Step 2') { }Good:
stage('Build Application') { }
stage('Run Unit Tests') { }
stage('Build Docker Image') { }
stage('Deploy to Staging') { }2. Use Nested Stages for Organization
stages {
stage('Build') {
stages {
stage('Compile') { }
stage('Package') { }
}
}
stage('Quality Checks') {
parallel {
stage('Unit Tests') { }
stage('Integration Tests') { }
stage('Code Analysis') { }
}
}
}3. Use Parallel Execution
stage('Tests') {
parallel {
stage('Unit Tests') {
steps { sh 'mvn test' }
}
stage('Integration Tests') {
steps { sh 'mvn verify' }
}
stage('E2E Tests') {
steps { sh 'npm run e2e' }
}
}
}4. Use failFast with Parallel
stage('Deploy') {
failFast true
parallel {
stage('Region 1') { }
stage('Region 2') { }
stage('Region 3') { }
}
}---
Options Best Practices
Recommended Pipeline Options
options {
buildDiscarder(logRotator(
numToKeepStr: '10', // Keep last 10 builds
daysToKeepStr: '30', // Keep builds from last 30 days
artifactNumToKeepStr: '5' // Keep artifacts from last 5 builds
))
timestamps() // Add timestamps to console
timeout(time: 1, unit: 'HOURS') // Pipeline timeout
disableConcurrentBuilds() // No concurrent builds
parallelsAlwaysFailFast() // Fail fast in parallel stages
}---
Docker Best Practices
1. Use Docker Agents
agent {
docker {
image 'maven:3.9.9-eclipse-temurin-21'
args '-v $HOME/.m2:/root/.m2'
reuseNode true
}
}2. Reuse Docker Images
Bad:
sh 'docker run maven:3.9.9 mvn clean'
sh 'docker run maven:3.9.9 mvn compile'
sh 'docker run maven:3.9.9 mvn package'Good:
docker.image('maven:3.9.9').inside {
sh 'mvn clean compile package'
}3. Build Once, Deploy Many
stage('Build') {
steps {
script {
dockerImage = docker.build("myapp:${env.BUILD_NUMBER}")
}
}
}
stage('Test') {
steps {
script {
dockerImage.inside { sh 'run-tests.sh' }
}
}
}
stage('Deploy') {
steps {
script {
dockerImage.push()
dockerImage.push('latest')
}
}
}4. Docker Image Selection Best Practices
Node.js Images
Per Snyk's Node.js Docker best practices:
| Image Type | Recommendation | Use Case |
|---|---|---|
node:22-bookworm-slim | Recommended for production | Minimal size, stable Debian base |
node:22-alpine | Use with caution | Smallest size, but Alpine is experimental in Node.js |
node:22 | Development only | Large image, includes unnecessary tools |
node:lts | Avoid in CI/CD | Tag changes over time, not reproducible |
Best Practice:
agent {
docker {
// Use specific version for reproducibility
image 'node:22.11.0-bookworm-slim' // Specific + slim
}
}
// Alternative for size-sensitive builds (with caution)
agent {
docker {
image 'node:22-alpine' // Note: Alpine is experimental in Node.js
}
}Why avoid Alpine for Node.js?
- Node.js marks Alpine as "experimental" in their official documentation
- Uses musl libc instead of glibc (potential compatibility issues)
- Native dependencies may require recompilation
- Some npm packages may not work correctly
Java/Maven Images
// Recommended: Eclipse Temurin (successor to AdoptOpenJDK)
agent {
docker { image 'maven:3.9.11-eclipse-temurin-21' }
}
// For smaller images
agent {
docker { image 'maven:3.9.11-eclipse-temurin-21-alpine' }
}Python Images
// Recommended: Slim variant with Debian Bookworm
agent {
docker { image 'python:3.12-slim-bookworm' }
}
// Alpine (smaller but may need additional build tools)
agent {
docker { image 'python:3.12-alpine' }
}Go Images
// Alpine works well for Go (statically compiled)
agent {
docker { image 'golang:1.23-alpine' }
}General Rules: 1. Always use specific version tags (not latest or lts) 2. Prefer -slim or -bookworm-slim variants for production 3. Use Alpine only when you understand the trade-offs 4. Test native dependencies before switching to Alpine
---
Kubernetes Best Practices
1. Set Resource Limits
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9.9
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
'''
}
}2. Use Service Accounts
agent {
kubernetes {
yaml '''
spec:
serviceAccountName: jenkins-agent
'''
}
}---
Testing Best Practices
1. Always Publish Test Results
post {
always {
junit '**/target/surefire-reports/*.xml'
publishHTML([
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}2. Archive Artifacts
post {
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}3. Separate Build and Test Stages
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
}---
Notification Best Practices
Send Notifications for Important Events
post {
failure {
slackSend(
color: 'danger',
message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
fixed {
slackSend(
color: 'good',
message: "Build FIXED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
}Include Relevant Information
post {
failure {
mail to: 'team@example.com',
subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
Build: ${env.BUILD_URL}
Branch: ${env.BRANCH_NAME}
Commit: ${env.GIT_COMMIT}
"""
}
}---
Multi-Branch Pipeline Best Practices
Use Branch-Specific Logic
stage('Deploy') {
when {
branch 'main'
}
steps {
sh 'deploy-production.sh'
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh 'deploy-staging.sh'
}
}Use PR Triggers
stage('PR Validation') {
when {
changeRequest()
}
steps {
sh 'run-pr-checks.sh'
}
}---
Input Best Practices
Free Agents During Input
Good (input outside agent):
stage('Approval') {
input {
message 'Deploy to production?'
ok 'Deploy'
submitter 'admin,ops-team'
}
steps {
sh './deploy.sh'
}
}Bad (holds agent during input):
stage('Approval') {
steps {
input 'Deploy to production?'
sh './deploy.sh'
}
}---
Summary Checklist
- [ ] Combine multiple shell commands into single steps
- [ ] Use agent-based operations, not controller-based
- [ ] Never hardcode credentials
- [ ] Implement timeouts for all builds
- [ ] Add proper error handling (try-catch, post blocks)
- [ ] Clean workspace after builds
- [ ] Use parallel execution for independent tasks
- [ ] Publish test results and artifacts
- [ ] Send notifications for important events
- [ ] Use descriptive stage names
- [ ] Configure build discarder
- [ ] Use Docker for consistent build environment
- [ ] Set resource limits for Kubernetes pods
- [ ] Validate user input
- [ ] Use least-privilege credentials
- [ ] Free agents during input
---
References
Common Jenkins Plugins - Generator Reference
Quick reference for generating Jenkinsfiles with popular plugin steps.
Table of Contents
1. Git Plugin 2. Docker Plugin 3. Kubernetes Plugin 4. Credentials Plugin 5. Pipeline Utility Steps 6. JUnit Plugin 7. Slack Notification Plugin 8. Email Extension Plugin 9. Build Timeout Plugin 10. Workspace Cleanup Plugin 11. AWS Steps Plugin 12. Azure CLI Plugin 13. SonarQube Plugin 14. HTTP Request Plugin 15. Microsoft Teams Notification Plugin 16. Nexus Artifact Uploader Plugin 17. Artifactory Plugin 18. OWASP Dependency-Check Plugin 19. GitHub Plugin
---
Git Plugin
Basic Checkout
// Auto-detect SCM
checkout scm
// Explicit URL
git branch: 'main', url: 'https://github.com/user/repo.git'
// With credentials
git branch: 'main',
url: 'https://github.com/user/repo.git',
credentialsId: 'github-credentials'Advanced Checkout
checkout scmGit(
branches: [[name: '*/main']],
userRemoteConfigs: [[
url: 'https://github.com/user/repo.git',
credentialsId: 'github-credentials'
]],
extensions: [
cloneOption(shallow: true, depth: 1),
submodule(recursiveSubmodules: true)
]
)Git Environment Variables
GIT_COMMIT- Current commit hashGIT_BRANCH- Branch nameGIT_URL- Repository URLGIT_AUTHOR_NAME- Commit author
---
Docker Plugin
Docker Agent (Declarative)
agent {
docker {
image 'maven:3.9.11-eclipse-temurin-21'
args '-v $HOME/.m2:/root/.m2'
reuseNode true
}
}Docker Agent with Dockerfile
agent {
dockerfile {
filename 'Dockerfile.build'
dir 'docker'
additionalBuildArgs '--build-arg VERSION=1.0'
}
}Docker in Scripted Pipeline
node {
docker.image('maven:3.9.11').inside('-v $HOME/.m2:/root/.m2') {
sh 'mvn clean package'
}
}Build and Push Docker Image
node {
def image = docker.build("myapp:${env.BUILD_NUMBER}")
docker.withRegistry('https://registry.example.com', 'docker-credentials') {
image.push()
image.push('latest')
}
}Sidecar Container
docker.image('mysql:8').withRun('-e MYSQL_ROOT_PASSWORD=secret') { db ->
docker.image('maven:3.9.11').inside("--link ${db.id}:mysql") {
sh 'mvn verify'
}
}---
Kubernetes Plugin
Pod Template (Declarative)
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9.11-eclipse-temurin-21
command: ['sleep']
args: ['99d']
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
- name: docker
image: docker:latest
command: ['sleep']
args: ['99d']
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock
'''
}
}Container Step
stage('Build') {
steps {
container('maven') {
sh 'mvn clean package'
}
}
}Scripted Pod Template
podTemplate(
containers: [
containerTemplate(name: 'maven', image: 'maven:3.9.11', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'kubectl', image: 'bitnami/kubectl:latest', ttyEnabled: true, command: 'cat')
],
volumes: [
secretVolume(secretName: 'kubeconfig', mountPath: '/root/.kube')
]
) {
node(POD_LABEL) {
container('maven') {
sh 'mvn clean package'
}
}
}---
Credentials Plugin
Username/Password
withCredentials([usernamePassword(
credentialsId: 'docker-hub',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS'
}Secret Text
withCredentials([string(credentialsId: 'api-token', variable: 'API_TOKEN')]) {
sh 'curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com'
}SSH Key
withCredentials([sshUserPrivateKey(
credentialsId: 'ssh-key',
keyFileVariable: 'SSH_KEY',
usernameVariable: 'SSH_USER'
)]) {
sh 'ssh -i $SSH_KEY $SSH_USER@server.example.com "deploy.sh"'
}File Credential
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
sh 'kubectl --kubeconfig=$KUBECONFIG get pods'
}Environment Binding (Declarative)
environment {
DOCKER_CREDENTIALS = credentials('docker-hub-credentials')
// Creates DOCKER_CREDENTIALS_USR and DOCKER_CREDENTIALS_PSW
API_KEY = credentials('api-key')
}---
Pipeline Utility Steps
File Operations
// Read file
def content = readFile(file: 'version.txt')
// Write file
writeFile(file: 'output.txt', text: 'Hello World')
// Read JSON
def json = readJSON(file: 'config.json')
// Write JSON
writeJSON(file: 'output.json', json: [name: 'Jenkins', version: '2.0'])
// Read YAML
def yaml = readYAML(file: 'config.yaml')
// Write YAML
writeYAML(file: 'output.yaml', data: [name: 'Jenkins'])
// Check if file exists
if (fileExists('path/to/file')) {
echo 'File exists'
}
// Find files
def files = findFiles(glob: '**/*.jar')ZIP Operations
// Create ZIP
zip(zipFile: 'archive.zip', dir: 'target')
// Unzip
unzip(zipFile: 'archive.zip', dir: 'output')---
JUnit Plugin
post {
always {
junit(
testResults: '**/target/surefire-reports/*.xml',
allowEmptyResults: true,
keepLongStdio: true
)
}
}---
Slack Notification Plugin
// Simple notification
slackSend(color: 'good', message: 'Build succeeded!')
// With details
slackSend(
color: currentBuild.result == 'SUCCESS' ? 'good' : 'danger',
message: "Build: ${env.JOB_NAME} #${env.BUILD_NUMBER}\nStatus: ${currentBuild.result}",
channel: '#builds',
tokenCredentialId: 'slack-token'
)
// Post conditions
post {
success {
slackSend color: 'good', message: "Build ${env.BUILD_NUMBER} succeeded"
}
failure {
slackSend color: 'danger', message: "Build ${env.BUILD_NUMBER} failed"
}
}---
Email Extension Plugin
emailext(
subject: "Build ${currentBuild.result}: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
<h2>Build ${currentBuild.result}</h2>
<p><strong>Job:</strong> ${env.JOB_NAME}</p>
<p><strong>Build Number:</strong> ${env.BUILD_NUMBER}</p>
<p><strong>Build URL:</strong> <a href="${env.BUILD_URL}">${env.BUILD_URL}</a></p>
""",
to: 'team@example.com',
mimeType: 'text/html',
attachLog: true
)
// With recipient providers
post {
failure {
emailext(
subject: "Build Failed: ${env.JOB_NAME}",
body: "Check ${env.BUILD_URL}",
recipientProviders: [developers(), culprits(), requestor()]
)
}
}---
Build Timeout Plugin
// Declarative
options {
timeout(time: 1, unit: 'HOURS')
}
// Per-stage
stage('Long Running') {
options {
timeout(time: 30, unit: 'MINUTES')
}
steps {
sh './long-task.sh'
}
}
// Scripted
timeout(time: 30, unit: 'MINUTES') {
node {
// steps
}
}---
Workspace Cleanup Plugin
// Clean workspace
cleanWs()
// In post block
post {
always {
cleanWs()
}
}
// With options
cleanWs(
deleteDirs: true,
patterns: [
[pattern: 'target', type: 'INCLUDE'],
[pattern: '*.log', type: 'INCLUDE']
]
)
// Simple delete
deleteDir()---
AWS Steps Plugin
withAWS(credentials: 'aws-credentials', region: 'us-east-1') {
// S3 operations
s3Upload(bucket: 'my-bucket', path: 'artifacts/', includePathPattern: '**/*.jar')
s3Download(bucket: 'my-bucket', path: 'config/', file: 'config.json')
// ECR login
def login = ecrLogin()
sh "${login}"
// ECS deploy
ecsDeployTaskDefinition(taskDefinition: 'my-task', cluster: 'my-cluster')
}---
Azure CLI Plugin
withCredentials([azureServicePrincipal('azure-sp')]) {
sh '''
az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID
az account set --subscription $AZURE_SUBSCRIPTION_ID
az webapp deploy --resource-group mygroup --name myapp --src-path app.zip
'''
}---
SonarQube Plugin
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('sonarqube-server') {
sh 'mvn sonar:sonar'
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}Common Gotcha: Ensure the SonarQube Server URL in Jenkins configuration does NOT have a trailing slash (e.g., http://sonarqube:9000 not http://sonarqube:9000/).
---
HTTP Request Plugin
Make HTTP/HTTPS requests from pipeline with full control over method, headers, and response handling.
Basic GET Request
def response = httpRequest 'https://api.example.com/status'
echo "Status: ${response.status}"
echo "Content: ${response.content}"POST with JSON Body
def response = httpRequest(
url: 'https://api.example.com/deploy',
httpMode: 'POST',
contentType: 'APPLICATION_JSON',
requestBody: '{"environment": "production", "version": "1.0.0"}',
validResponseCodes: '200:299'
)With Authentication
withCredentials([string(credentialsId: 'api-token', variable: 'API_TOKEN')]) {
def response = httpRequest(
url: 'https://api.example.com/data',
httpMode: 'GET',
customHeaders: [[name: 'Authorization', value: "Bearer ${API_TOKEN}"]],
timeout: 30
)
}Response Handling Options
// Don't read response body (for large responses)
def response = httpRequest(
url: 'https://api.example.com/large-file',
responseHandle: 'NONE'
)
// Keep connection open for streaming
def response = httpRequest(
url: 'https://api.example.com/stream',
responseHandle: 'LEAVE_OPEN'
)
// Must close manually:
response.close()Advanced Options
def response = httpRequest(
url: 'https://api.example.com/upload',
httpMode: 'PUT',
uploadFile: './report.html',
validResponseCodes: '200,201,204',
ignoreSslErrors: true,
httpProxy: 'http://proxy.local:8080',
timeout: 60,
consoleLogResponseBody: true
)HTTP Methods Available
GET- Retrieve data (default)POST- Submit dataPUT- Update/upload resourcePATCH- Partial updateDELETE- Remove resourceHEAD- Retrieve headers onlyOPTIONS- Query available methods
---
Microsoft Teams Notification Plugin
// Simple notification
office365ConnectorSend(
webhookUrl: 'https://outlook.office.com/webhook/...',
message: 'Build completed!',
color: '00FF00'
)
// With card formatting
office365ConnectorSend(
webhookUrl: "${TEAMS_WEBHOOK}",
message: "Build ${currentBuild.result}",
status: currentBuild.result,
factDefinitions: [
[name: 'Job', value: env.JOB_NAME],
[name: 'Build', value: "#${env.BUILD_NUMBER}"],
[name: 'Duration', value: "${currentBuild.durationString}"]
],
potentialAction: [[
'@type': 'OpenUri',
'name': 'View Build',
'targets': [[
'os': 'default',
'uri': env.BUILD_URL
]]
]]
)
// In post block
post {
success {
office365ConnectorSend(
webhookUrl: "${TEAMS_WEBHOOK}",
message: "Build succeeded",
color: '00FF00'
)
}
failure {
office365ConnectorSend(
webhookUrl: "${TEAMS_WEBHOOK}",
message: "Build failed",
color: 'FF0000'
)
}
}---
Nexus Artifact Uploader Plugin
nexusArtifactUploader(
nexusVersion: 'nexus3',
protocol: 'https',
nexusUrl: 'nexus.example.com',
repository: 'maven-releases',
credentialsId: 'nexus-credentials',
groupId: 'com.example',
version: '1.0.0',
artifacts: [
[artifactId: 'myapp', classifier: '', file: 'target/myapp.jar', type: 'jar'],
[artifactId: 'myapp', classifier: '', file: 'pom.xml', type: 'pom']
]
)---
Artifactory Plugin
// Configure Artifactory server
def server = Artifactory.server('artifactory-server')
def uploadSpec = """{
"files": [{
"pattern": "target/*.jar",
"target": "libs-release-local/com/example/myapp/1.0.0/"
}]
}"""
// Upload
server.upload(uploadSpec)
// Download
def downloadSpec = """{
"files": [{
"pattern": "libs-release-local/com/example/myapp/1.0.0/*.jar",
"target": "dependencies/"
}]
}"""
server.download(downloadSpec)
// Publish build info
def buildInfo = Artifactory.newBuildInfo()
server.upload spec: uploadSpec, buildInfo: buildInfo
server.publishBuildInfo buildInfo---
OWASP Dependency-Check Plugin
stage('Dependency Check') {
steps {
dependencyCheck(
additionalArguments: '''
--scan .
--format HTML
--format XML
--format JSON
--out dependency-check-report
--suppression suppression.xml
--failOnCVSS 7
''',
odcInstallation: 'OWASP-Dependency-Check'
)
}
post {
always {
dependencyCheckPublisher(
pattern: '**/dependency-check-report.xml',
failedTotalCritical: 0,
failedTotalHigh: 5,
unstableTotalMedium: 10
)
}
}
}---
GitHub Plugin
Set Commit Status
// Using step
githubNotify(
status: 'PENDING',
description: 'Build in progress',
context: 'jenkins/build'
)
// After build
post {
success {
githubNotify status: 'SUCCESS', description: 'Build passed'
}
failure {
githubNotify status: 'FAILURE', description: 'Build failed'
}
}Create/Update PR Comment
// Using GitHub API via sh
withCredentials([string(credentialsId: 'github-token', variable: 'GITHUB_TOKEN')]) {
sh '''
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/owner/repo/issues/${CHANGE_ID}/comments \
-d '{"body": "Build succeeded!"}'
'''
}---
Common Build Steps
Archive Artifacts
archiveArtifacts(
artifacts: '**/*.jar',
fingerprint: true,
onlyIfSuccessful: true
)Stash/Unstash
// Stash
stash(name: 'build-artifacts', includes: 'target/*.jar')
// Unstash
unstash 'build-artifacts'Build Job
build(
job: 'downstream-job',
parameters: [
string(name: 'ENVIRONMENT', value: 'production'),
booleanParam(name: 'RUN_TESTS', value: true)
],
wait: true,
propagate: true
)Input
def userInput = input(
message: 'Deploy to production?',
ok: 'Deploy',
parameters: [
choice(name: 'ENVIRONMENT', choices: ['staging', 'production']),
string(name: 'VERSION', defaultValue: '1.0')
],
submitter: 'admin,ops'
)Retry
retry(3) {
sh 'flaky-command'
}Sleep
sleep(time: 30, unit: 'SECONDS')---
Plugin Documentation Lookup
For unlisted plugins:
1. Context7: Search for /jenkinsci/<plugin-name>-plugin 2. Web Search: "Jenkins <plugin-name> plugin documentation" 3. Official Plugins: https://plugins.jenkins.io/ 4. Pipeline Steps: https://www.jenkins.io/doc/pipeline/steps/
---
References
#!/usr/bin/env python3
"""
Generate Declarative Jenkins Pipeline
This script generates a Declarative Jenkinsfile with specified configuration.
"""
import argparse
import re
import sys
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent / 'lib'))
from common_patterns import PipelinePatterns, StageTemplates, PostConditions, EnvironmentTemplates
from syntax_helpers import DeclarativeSyntax, FormattingHelpers, GroovySyntax, ValidationHelpers
_INLINE_YAML_KEY_PATTERN = re.compile(r'^\s*[\w.\-"\']+\s*:\s*.*$')
def _looks_like_inline_yaml(value):
"""Return True if the input resembles inline YAML content."""
stripped = value.strip()
if not stripped:
return False
if '\n' in value:
return True
if stripped.startswith(('---', '{', '[')):
return True
return bool(_INLINE_YAML_KEY_PATTERN.match(stripped))
def resolve_k8s_yaml(k8s_yaml_value):
"""Resolve --k8s-yaml as either inline YAML or a path to an existing file."""
if not k8s_yaml_value:
return ''
candidate_path = Path(k8s_yaml_value).expanduser()
if candidate_path.is_file():
return candidate_path.read_text(encoding='utf-8')
if _looks_like_inline_yaml(k8s_yaml_value):
return k8s_yaml_value
raise ValueError(
f"--k8s-yaml must be inline YAML content or an existing file path: {k8s_yaml_value}"
)
class DeclarativePipelineGenerator:
"""Generator for Declarative Jenkins Pipelines"""
def __init__(self, config):
self.config = config
self.pipeline_parts = []
def generate(self):
"""Generate complete declarative pipeline"""
# Start pipeline block
self.pipeline_parts.append("pipeline {")
# Add agent
self._add_agent()
# Add environment (if specified)
self._add_environment()
# Add parameters (if specified)
self._add_parameters()
# Add options (if specified)
self._add_options()
# Add triggers (if specified)
self._add_triggers()
# Add tools (if specified)
self._add_tools()
# Add stages
self._add_stages()
# Add post conditions
self._add_post()
# Close pipeline block
self.pipeline_parts.append("}")
# Format and return
content = '\n'.join(self.pipeline_parts)
return FormattingHelpers.format_jenkinsfile(
FormattingHelpers.add_header_comment(
content,
f"Declarative Pipeline - {self.config.get('name', 'Generated Pipeline')}"
)
)
def _add_agent(self):
"""Add agent configuration"""
agent_type = self.config.get('agent', 'any')
if agent_type == 'docker':
agent_block = DeclarativeSyntax.agent_block(
'docker',
image=self.config.get('docker_image', 'ubuntu:latest'),
args=self.config.get('docker_args', ''),
reuseNode=self.config.get('docker_reuse_node', False)
)
elif agent_type == 'dockerfile':
agent_block = DeclarativeSyntax.agent_block(
'dockerfile',
filename=self.config.get('dockerfile', 'Dockerfile'),
dir=self.config.get('dockerfile_dir', '.'),
additionalBuildArgs=self.config.get('dockerfile_build_args', '')
)
elif agent_type == 'kubernetes':
agent_block = DeclarativeSyntax.agent_block(
'kubernetes',
yaml=self.config.get('k8s_yaml', ''),
inheritFrom=self.config.get('k8s_inherit_from', '')
)
elif agent_type == 'label':
agent_block = DeclarativeSyntax.agent_block(
'label',
label=self.config.get('agent_label', 'linux')
)
elif agent_type == 'none':
agent_block = DeclarativeSyntax.agent_block('none')
else:
agent_block = DeclarativeSyntax.agent_block('any')
self.pipeline_parts.append(agent_block)
def _add_environment(self):
"""Add environment variables"""
env_vars = self.config.get('environment', {})
credentials = self.config.get('credentials', {})
if env_vars or credentials:
env_block = DeclarativeSyntax.environment_block(env_vars, credentials)
if env_block:
self.pipeline_parts.append("")
self.pipeline_parts.append(env_block)
def _add_parameters(self):
"""Add parameters"""
parameters = self.config.get('parameters', [])
if parameters:
param_block = DeclarativeSyntax.parameters_block(parameters)
if param_block:
self.pipeline_parts.append("")
self.pipeline_parts.append(param_block)
def _add_options(self):
"""Add options"""
options = self.config.get('options', {})
if options:
options_block = DeclarativeSyntax.options_block(options)
if options_block:
self.pipeline_parts.append("")
self.pipeline_parts.append(options_block)
def _add_triggers(self):
"""Add triggers"""
triggers = self.config.get('triggers', {})
if triggers:
triggers_block = DeclarativeSyntax.triggers_block(triggers)
if triggers_block:
self.pipeline_parts.append("")
self.pipeline_parts.append(triggers_block)
def _add_tools(self):
"""Add tools"""
tools = self.config.get('tools', {})
if tools:
tools_block = DeclarativeSyntax.tools_block(tools)
if tools_block:
self.pipeline_parts.append("")
self.pipeline_parts.append(tools_block)
def _add_stages(self):
"""Add stages based on configuration"""
self.pipeline_parts.append("")
self.pipeline_parts.append(" stages {")
# Get stage list from config or use default
stages = self.config.get('stages', ['build', 'test'])
# Get build tool pattern if specified
build_tool = self.config.get('build_tool', 'maven')
pattern = PipelinePatterns.ci_pattern(build_tool)
# Generate stages based on type
for stage in stages:
if stage == 'checkout':
self.pipeline_parts.append(StageTemplates.checkout_stage(
scm_url=self.config.get('scm_url'),
branch=self.config.get('branch', 'main'),
credentials=self.config.get('scm_credentials')
))
elif stage == 'build':
build_cmd = self.config.get('build_cmd', pattern['build_cmd'])
self.pipeline_parts.append(StageTemplates.build_stage(build_cmd))
elif stage == 'test':
test_cmd = self.config.get('test_cmd', pattern['test_cmd'])
test_results = self.config.get('test_results', pattern['test_results'])
self.pipeline_parts.append(StageTemplates.test_stage(test_cmd, test_results))
elif stage == 'deploy':
deploy_cmd = self.config.get('deploy_cmd', './deploy.sh')
environment = self.config.get('deploy_env', 'production')
approval = self.config.get('deploy_approval', True)
approvers = self.config.get('deploy_approvers', 'admin')
self.pipeline_parts.append(StageTemplates.deploy_stage(
environment, deploy_cmd, approval, approvers
))
elif stage == 'docker-build':
image_name = self.config.get('docker_image_name', 'myapp')
dockerfile = self.config.get('dockerfile', 'Dockerfile')
self.pipeline_parts.append(StageTemplates.docker_build_stage(image_name, dockerfile))
elif stage == 'docker-push':
image_name = self.config.get('docker_image_name', 'myapp')
registry = self.config.get('docker_registry')
registry_creds = self.config.get('docker_registry_credentials')
self.pipeline_parts.append(StageTemplates.docker_push_stage(
image_name, registry, registry_creds
))
elif stage == 'parallel-tests':
test_types = self.config.get('test_types', ['unit', 'integration'])
# Avoid redundant failFast true at stage level when
# parallelsAlwaysFailFast() is already set globally in options
has_global_fail_fast = self.config.get('options', {}).get('parallelsAlwaysFailFast', False)
stage_fail_fast = self.config.get('parallel_fail_fast', True) and not has_global_fail_fast
self.pipeline_parts.append(StageTemplates.parallel_test_stage(
test_types,
fail_fast=stage_fail_fast,
))
else:
# Custom stage
stage_name = ValidationHelpers.normalize_stage_name(
stage.replace('-', ' ').replace('_', ' ').title()
)
stage_name_literal = GroovySyntax.single_quoted_literal(stage_name)
custom_cmd = self.config.get(f'{stage}_cmd', f'echo "Running {stage_name}"')
custom_cmd_literal = GroovySyntax.single_quoted_literal(custom_cmd)
self.pipeline_parts.append(f"""
stage({stage_name_literal}) {{
steps {{
sh {custom_cmd_literal}
}}
}}""")
self.pipeline_parts.append(" }")
def _add_post(self):
"""Add post conditions"""
artifacts = self.config.get('archive_artifacts')
cleanup = self.config.get('cleanup', True)
email = self.config.get('notification_email')
slack = self.config.get('notification_slack')
if email or slack:
post_block = PostConditions.notification_post(
email,
slack,
archive_artifacts=artifacts,
cleanup=cleanup,
)
else:
post_block = PostConditions.standard_post(artifacts, cleanup)
if post_block:
self.pipeline_parts.append("")
self.pipeline_parts.append(post_block)
def parse_args():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(
description='Generate Declarative Jenkins Pipeline',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
Examples:
# Basic CI pipeline
%(prog)s --output Jenkinsfile --stages build,test --build-tool maven
# Docker-based pipeline
%(prog)s --output Jenkinsfile --agent docker --docker-image maven:3.9.9-eclipse-temurin-21
# Full CD pipeline with deployment
%(prog)s --output Jenkinsfile --stages checkout,build,test,deploy --deploy-env production
# Kubernetes agent pipeline
%(prog)s --output Jenkinsfile --agent kubernetes --k8s-yaml pod.yaml
'''
)
# Required arguments
parser.add_argument('--output', '-o', required=True,
help='Output Jenkinsfile path')
# Pipeline configuration
parser.add_argument('--name', default='Generated Pipeline',
help='Pipeline name (for header comment)')
parser.add_argument('--stages', default='build,test',
help='Comma-separated list of stages (build,test,deploy,etc.)')
# Agent configuration
parser.add_argument('--agent', default='any',
choices=['any', 'none', 'label', 'docker', 'dockerfile', 'kubernetes'],
help='Agent type')
parser.add_argument('--agent-label', default='linux',
help='Agent label (for --agent label)')
parser.add_argument('--docker-image', default='ubuntu:latest',
help='Docker image (for --agent docker)')
parser.add_argument('--docker-args', default='',
help='Docker arguments (for --agent docker)')
parser.add_argument('--dockerfile', default='Dockerfile',
help='Dockerfile name (for --agent dockerfile)')
parser.add_argument('--k8s-yaml', default='',
help='Kubernetes YAML (inline) or path to an existing file')
# Build configuration
parser.add_argument('--build-tool', default='maven',
choices=['maven', 'gradle', 'npm', 'python', 'go'],
help='Build tool (determines default commands)')
parser.add_argument('--build-cmd', help='Custom build command')
parser.add_argument('--test-cmd', help='Custom test command')
parser.add_argument('--deploy-cmd', default='./deploy.sh',
help='Deploy command')
# SCM configuration
parser.add_argument('--scm-url', help='Git repository URL')
parser.add_argument('--branch', default='main', help='Git branch')
parser.add_argument('--scm-credentials', help='SCM credentials ID')
# Options
parser.add_argument('--timeout', type=int, help='Pipeline timeout in hours')
parser.add_argument('--build-discarder', type=int, default=10,
help='Number of builds to keep')
parser.add_argument('--disable-concurrent', action='store_true',
help='Disable concurrent builds')
parser.add_argument('--timestamps', action='store_true',
help='Add timestamps to console output')
parser.add_argument('--preserve-stashes', type=int, metavar='N',
help='Preserve stashes for N builds (for stage restarting)')
parser.add_argument('--durability-hint',
choices=['PERFORMANCE_OPTIMIZED', 'SURVIVABLE_NONATOMIC', 'MAX_SURVIVABILITY'],
help='Pipeline durability hint (trade performance for durability)')
parser.add_argument('--quiet-period', type=int,
help='Override global quiet period in seconds')
parser.add_argument('--skip-stages-after-unstable', action='store_true',
help='Skip remaining stages if build becomes unstable')
parser.add_argument('--disable-resume', action='store_true',
help='Do not allow pipeline to resume if controller restarts')
parallel_fail_fast_group = parser.add_mutually_exclusive_group()
parallel_fail_fast_group.add_argument('--parallels-fail-fast', dest='parallels_fail_fast',
action='store_true',
help='Abort all parallel stages when one fails (default)')
parallel_fail_fast_group.add_argument('--no-parallels-fail-fast', dest='parallels_fail_fast',
action='store_false',
help='Allow parallel branches to continue after a failure')
parser.set_defaults(parallels_fail_fast=True)
# Deployment
parser.add_argument('--deploy-env', default='production',
help='Deployment environment name')
parser.add_argument('--no-deploy-approval', action='store_true',
help='Skip deployment approval')
parser.add_argument('--deploy-approvers', default='admin',
help='Comma-separated list of deployment approvers')
# Notifications
parser.add_argument('--notification-email', help='Email for notifications')
parser.add_argument('--notification-slack', help='Slack channel for notifications')
# Docker
parser.add_argument('--docker-image-name', default='myapp',
help='Docker image name for docker-build/push stages')
parser.add_argument('--docker-registry', help='Docker registry URL')
parser.add_argument('--docker-registry-credentials', help='Docker registry credentials ID')
# Post-build
parser.add_argument('--archive-artifacts', help='Artifacts pattern to archive')
parser.add_argument('--no-cleanup', action='store_true',
help='Disable workspace cleanup')
return parser.parse_args()
def main():
"""Main entry point"""
args = parse_args()
try:
stages = ValidationHelpers.parse_stage_list(args.stages)
k8s_yaml = resolve_k8s_yaml(args.k8s_yaml)
except ValueError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 2
# Build configuration from args
config = {
'name': args.name,
'stages': stages,
'agent': args.agent,
'agent_label': args.agent_label,
'docker_image': args.docker_image,
'docker_args': args.docker_args,
'dockerfile': args.dockerfile,
'k8s_yaml': k8s_yaml,
'build_tool': args.build_tool,
'scm_url': args.scm_url,
'branch': args.branch,
'scm_credentials': args.scm_credentials,
'deploy_cmd': args.deploy_cmd,
'deploy_env': args.deploy_env,
'deploy_approval': not args.no_deploy_approval,
'deploy_approvers': args.deploy_approvers,
'notification_email': args.notification_email,
'notification_slack': args.notification_slack,
'docker_image_name': args.docker_image_name,
'docker_registry': args.docker_registry,
'docker_registry_credentials': args.docker_registry_credentials,
'archive_artifacts': args.archive_artifacts,
'cleanup': not args.no_cleanup,
'parallel_fail_fast': args.parallels_fail_fast,
}
# Add custom commands if specified
if args.build_cmd:
config['build_cmd'] = args.build_cmd
if args.test_cmd:
config['test_cmd'] = args.test_cmd
# Add options
options = {}
if args.timeout:
options['timeout'] = {'time': args.timeout, 'unit': 'HOURS'}
if args.build_discarder:
options['buildDiscarder'] = {'numToKeepStr': str(args.build_discarder)}
if args.disable_concurrent:
options['disableConcurrentBuilds'] = True
if args.timestamps:
options['timestamps'] = True
if args.preserve_stashes:
options['preserveStashes'] = {'buildCount': args.preserve_stashes}
if args.durability_hint:
options['durabilityHint'] = args.durability_hint
if args.quiet_period:
options['quietPeriod'] = args.quiet_period
if args.skip_stages_after_unstable:
options['skipStagesAfterUnstable'] = True
if args.disable_resume:
options['disableResume'] = True
if args.parallels_fail_fast and 'parallel-tests' in stages:
options['parallelsAlwaysFailFast'] = True
if options:
config['options'] = options
# Generate pipeline
generator = DeclarativePipelineGenerator(config)
jenkinsfile_content = generator.generate()
# Write output
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(jenkinsfile_content)
print(f"✓ Generated Declarative Jenkinsfile: {args.output}")
print(f" Pipeline: {args.name}")
print(f" Stages: {', '.join(config['stages'])}")
print(f" Agent: {args.agent}")
print("\n" + "="*60)
print("NEXT STEP: Validate using jenkinsfile-validator skill")
print(f" bash devops-skills-plugin/skills/jenkinsfile-validator/scripts/validate_jenkinsfile.sh {args.output}")
print("="*60)
return 0
if __name__ == '__main__':
sys.exit(main())
#!/usr/bin/env python3
"""
Generate Scripted Jenkins Pipeline
This script generates a Scripted Jenkinsfile with specified configuration.
"""
import argparse
import sys
import os
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent / 'lib'))
from common_patterns import PipelinePatterns
from syntax_helpers import ScriptedSyntax, FormattingHelpers, ValidationHelpers
class ScriptedPipelineGenerator:
"""Generator for Scripted Jenkins Pipelines"""
def __init__(self, config):
self.config = config
self.pipeline_parts = []
def generate(self):
"""Generate complete scripted pipeline"""
# Get agent/node configuration
node_label = self.config.get('agent_label')
# Build node content
node_content = self._build_node_content()
# Create node block
if node_label:
pipeline = ScriptedSyntax.node_block(node_label, node_content)
else:
pipeline = ScriptedSyntax.node_block(content=node_content)
# Format and return
return FormattingHelpers.format_jenkinsfile(
FormattingHelpers.add_header_comment(
pipeline,
f"Scripted Pipeline - {self.config.get('name', 'Generated Pipeline')}"
)
)
@staticmethod
def _indent_lines(text, spaces=4):
"""Indent all non-empty lines by the given number of spaces."""
prefix = ' ' * spaces
return '\n'.join(
prefix + line if line.strip() else line
for line in text.split('\n')
)
def _build_node_content(self):
"""Build content inside node block"""
parts = []
# Get stages
stages = self.config.get('stages', ['build', 'test'])
build_tool = self.config.get('build_tool', 'maven')
pattern = PipelinePatterns.ci_pattern(build_tool)
# Determine if we need try-catch-finally
use_error_handling = self.config.get('error_handling', True)
if use_error_handling:
# Build try block content.
# Re-indent by 4 extra spaces so stages sit inside try {} correctly.
raw_try_content = self._build_stages_content(stages, pattern)
try_content = self._indent_lines(raw_try_content, 4)
# Build catch block
catch_content = """ currentBuild.result = 'FAILURE'
echo "Pipeline failed: ${e.message}"
throw e"""
# Build finally block (cleanup)
finally_content = ""
if self.config.get('cleanup', True):
finally_content = " deleteDir()"
# Add notification in catch if configured
if self.config.get('notification_email'):
catch_content = f""" currentBuild.result = 'FAILURE'
emailext(
subject: "Build Failed: ${{env.JOB_NAME}} #${{env.BUILD_NUMBER}}",
body: "Error: ${{e.message}}\\nCheck console output at ${{env.BUILD_URL}}",
to: '{self.config.get('notification_email')}'
)
throw e"""
node_content = ScriptedSyntax.try_catch_finally(
try_content, catch_content, finally_content
)
else:
# Simple stages without error handling
node_content = self._build_stages_content(stages, pattern)
if self.config.get('cleanup', True):
node_content += "\n\n deleteDir()"
return node_content
def _build_stages_content(self, stages, pattern):
"""Build stages content"""
stage_blocks = []
for stage in stages:
if stage == 'checkout':
stage_content = self._generate_checkout_stage()
elif stage == 'build':
stage_content = self._generate_build_stage(pattern)
elif stage == 'test':
stage_content = self._generate_test_stage(pattern)
elif stage == 'deploy':
stage_content = self._generate_deploy_stage()
elif stage == 'docker-build':
stage_content = self._generate_docker_build_stage()
elif stage == 'docker-push':
stage_content = self._generate_docker_push_stage()
elif stage == 'parallel-tests':
stage_content = self._generate_parallel_tests_stage()
else:
# Custom stage
custom_cmd = self.config.get(f'{stage}_cmd', f'echo "Running {stage}"')
stage_content = f""" sh '{custom_cmd}'"""
stage_display_name = stage.replace('-', ' ').replace('_', ' ').title()
stage_block = ScriptedSyntax.stage_block(
stage_display_name,
stage_content
)
stage_blocks.append(stage_block)
return '\n\n'.join(stage_blocks)
def _generate_checkout_stage(self):
"""Generate checkout stage"""
scm_url = self.config.get('scm_url')
branch = self.config.get('branch', 'main')
credentials = self.config.get('scm_credentials')
if scm_url:
if credentials:
return f""" checkout scmGit(
branches: [[name: '*/{branch}']],
userRemoteConfigs: [[
url: '{scm_url}',
credentialsId: '{credentials}'
]]
)"""
else:
return f""" git branch: '{branch}', url: '{scm_url}'"""
else:
return """ checkout scm"""
def _generate_build_stage(self, pattern):
"""Generate build stage"""
build_cmd = self.config.get('build_cmd', pattern['build_cmd'])
# Check if using Docker
if self.config.get('docker_image'):
docker_image = self.config.get('docker_image')
docker_args = self.config.get('docker_args', '')
content = f""" sh '{build_cmd}'"""
return ScriptedSyntax.docker_inside_block(docker_image, content, docker_args)
else:
return f""" sh '{build_cmd}'"""
def _generate_test_stage(self, pattern):
"""Generate test stage"""
test_cmd = self.config.get('test_cmd', pattern['test_cmd'])
test_results = self.config.get('test_results', pattern['test_results'])
content = f""" sh '{test_cmd}'
junit '{test_results}'"""
# Check if using Docker
if self.config.get('docker_image'):
docker_image = self.config.get('docker_image')
docker_args = self.config.get('docker_args', '')
return ScriptedSyntax.docker_inside_block(docker_image, content, docker_args)
else:
return content
def _generate_deploy_stage(self):
"""Generate deploy stage"""
deploy_cmd = self.config.get('deploy_cmd', './deploy.sh')
deploy_env = self.config.get('deploy_env', 'production')
approval = self.config.get('deploy_approval', True)
approvers = self.config.get('deploy_approvers', 'admin')
if approval:
return f""" input message: 'Deploy to {deploy_env}?', submitter: '{approvers}'
sh '{deploy_cmd}'"""
else:
return f""" sh '{deploy_cmd}'"""
def _generate_docker_build_stage(self):
"""Generate Docker build stage"""
image_name = self.config.get('docker_image_name', 'myapp')
dockerfile = self.config.get('dockerfile', 'Dockerfile')
return f""" def customImage = docker.build('{image_name}:${{BUILD_NUMBER}}', '-f {dockerfile} .')"""
def _generate_docker_push_stage(self):
"""Generate Docker push stage"""
image_name = self.config.get('docker_image_name', 'myapp')
registry = self.config.get('docker_registry')
registry_creds = self.config.get('docker_registry_credentials')
if registry and registry_creds:
return f""" docker.withRegistry('{registry}', '{registry_creds}') {{
docker.image('{image_name}:${{BUILD_NUMBER}}').push()
docker.image('{image_name}:${{BUILD_NUMBER}}').push('latest')
}}"""
else:
return f""" docker.image('{image_name}:${{BUILD_NUMBER}}').push()
docker.image('{image_name}:${{BUILD_NUMBER}}').push('latest')"""
def _generate_parallel_tests_stage(self):
"""Generate parallel test stages"""
test_types = self.config.get('test_types', ['unit', 'integration'])
parallel_stages = {}
for test_type in test_types:
parallel_stages[f'{test_type.capitalize()} Tests'] = f""" sh 'npm run test:{test_type}'"""
# Return just the content without the stage wrapper
# because ScriptedSyntax.parallel_block will be wrapped in a stage
parallel_content = []
for name, content in parallel_stages.items():
parallel_content.append(f""" '{name}': {{
node {{
{content}
}}
}}""")
return f""" parallel(
{','.join(parallel_content)}
)"""
def parse_args():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(
description='Generate Scripted Jenkins Pipeline',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
Examples:
# Basic CI pipeline
%(prog)s --output Jenkinsfile --stages build,test --build-tool maven
# Docker-based pipeline
%(prog)s --output Jenkinsfile --docker-image maven:3.9.9-eclipse-temurin-21
# Full CD pipeline with deployment
%(prog)s --output Jenkinsfile --stages checkout,build,test,deploy --deploy-env production
# Pipeline with specific node label
%(prog)s --output Jenkinsfile --agent-label linux-docker --stages build,test
'''
)
# Required arguments
parser.add_argument('--output', '-o', required=True,
help='Output Jenkinsfile path')
# Pipeline configuration
parser.add_argument('--name', default='Generated Pipeline',
help='Pipeline name (for header comment)')
parser.add_argument('--stages', default='build,test',
help='Comma-separated list of stages (build,test,deploy,etc.)')
# Agent configuration
parser.add_argument('--agent-label', help='Node label for agent selection')
# Build configuration
parser.add_argument('--build-tool', default='maven',
choices=['maven', 'gradle', 'npm', 'python', 'go'],
help='Build tool (determines default commands)')
parser.add_argument('--build-cmd', help='Custom build command')
parser.add_argument('--test-cmd', help='Custom test command')
parser.add_argument('--deploy-cmd', default='./deploy.sh',
help='Deploy command')
# Docker configuration
parser.add_argument('--docker-image', help='Docker image to use for build')
parser.add_argument('--docker-args', default='',
help='Docker run arguments')
parser.add_argument('--docker-image-name', default='myapp',
help='Docker image name for docker-build/push stages')
parser.add_argument('--docker-registry', help='Docker registry URL')
parser.add_argument('--docker-registry-credentials', help='Docker registry credentials ID')
parser.add_argument('--dockerfile', default='Dockerfile',
help='Dockerfile name')
# SCM configuration
parser.add_argument('--scm-url', help='Git repository URL')
parser.add_argument('--branch', default='main', help='Git branch')
parser.add_argument('--scm-credentials', help='SCM credentials ID')
# Deployment
parser.add_argument('--deploy-env', default='production',
help='Deployment environment name')
parser.add_argument('--no-deploy-approval', action='store_true',
help='Skip deployment approval')
parser.add_argument('--deploy-approvers', default='admin',
help='Comma-separated list of deployment approvers')
# Error handling and cleanup
parser.add_argument('--no-error-handling', action='store_true',
help='Disable try-catch-finally error handling')
parser.add_argument('--no-cleanup', action='store_true',
help='Disable workspace cleanup')
# Notifications
parser.add_argument('--notification-email', help='Email for notifications')
# Test configuration
parser.add_argument('--test-types', default='unit,integration',
help='Comma-separated test types for parallel-tests stage')
return parser.parse_args()
def main():
"""Main entry point"""
args = parse_args()
try:
stages = ValidationHelpers.parse_stage_list(args.stages)
except ValueError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 2
# Build configuration from args
config = {
'name': args.name,
'stages': stages,
'agent_label': args.agent_label,
'build_tool': args.build_tool,
'docker_image': args.docker_image,
'docker_args': args.docker_args,
'docker_image_name': args.docker_image_name,
'docker_registry': args.docker_registry,
'docker_registry_credentials': args.docker_registry_credentials,
'dockerfile': args.dockerfile,
'scm_url': args.scm_url,
'branch': args.branch,
'scm_credentials': args.scm_credentials,
'deploy_cmd': args.deploy_cmd,
'deploy_env': args.deploy_env,
'deploy_approval': not args.no_deploy_approval,
'deploy_approvers': args.deploy_approvers,
'error_handling': not args.no_error_handling,
'cleanup': not args.no_cleanup,
'notification_email': args.notification_email,
'test_types': args.test_types.split(','),
}
# Add custom commands if specified
if args.build_cmd:
config['build_cmd'] = args.build_cmd
if args.test_cmd:
config['test_cmd'] = args.test_cmd
# Determine test results pattern
pattern = PipelinePatterns.ci_pattern(args.build_tool)
config['test_results'] = pattern['test_results']
# Generate pipeline
generator = ScriptedPipelineGenerator(config)
jenkinsfile_content = generator.generate()
# Write output
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(jenkinsfile_content)
print(f"✓ Generated Scripted Jenkinsfile: {args.output}")
print(f" Pipeline: {args.name}")
print(f" Stages: {', '.join(config['stages'])}")
if args.agent_label:
print(f" Agent Label: {args.agent_label}")
print("\n" + "="*60)
print("NEXT STEP: Validate the generated Jenkinsfile")
print(" Use: jenkinsfile-validator skill")
print("="*60)
return 0
if __name__ == '__main__':
sys.exit(main())
"""
Shared utilities for Jenkinsfile generation
"""
from .common_patterns import (
PipelinePatterns,
StageTemplates,
PostConditions,
EnvironmentTemplates,
)
from .syntax_helpers import (
GroovySyntax,
DeclarativeSyntax,
ScriptedSyntax,
ValidationHelpers,
FormattingHelpers,
)
__all__ = [
# Common patterns
'PipelinePatterns',
'StageTemplates',
'PostConditions',
'EnvironmentTemplates',
# Syntax helpers
'GroovySyntax',
'DeclarativeSyntax',
'ScriptedSyntax',
'ValidationHelpers',
'FormattingHelpers',
]
#!/usr/bin/env python3
"""Regression tests for generate_declarative.py behavior."""
from pathlib import Path
import sys
import tempfile
import unittest
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))
sys.path.insert(0, str(SCRIPT_DIR / "lib"))
from generate_declarative import DeclarativePipelineGenerator, resolve_k8s_yaml # noqa: E402
from syntax_helpers import ValidationHelpers # noqa: E402
class ResolveK8sYamlRegressionTests(unittest.TestCase):
"""Cover path-vs-inline resolution behavior for --k8s-yaml."""
def test_reads_existing_file_without_extension(self):
with tempfile.TemporaryDirectory() as temp_dir:
yaml_path = Path(temp_dir) / "pod-template"
yaml_content = "apiVersion: v1\nkind: Pod\nmetadata:\n name: demo\n"
yaml_path.write_text(yaml_content, encoding="utf-8")
self.assertEqual(resolve_k8s_yaml(str(yaml_path)), yaml_content)
def test_missing_non_inline_value_raises_error(self):
with tempfile.TemporaryDirectory() as temp_dir:
missing_path = Path(temp_dir) / "does-not-exist"
with self.assertRaisesRegex(
ValueError,
"--k8s-yaml must be inline YAML content or an existing file path",
):
resolve_k8s_yaml(str(missing_path))
def test_missing_single_token_value_raises_error(self):
with self.assertRaisesRegex(
ValueError,
"--k8s-yaml must be inline YAML content or an existing file path",
):
resolve_k8s_yaml("definitely-missing-k8s-yaml-token")
def test_inline_yaml_is_preserved(self):
inline_yaml = "apiVersion: v1\nkind: Pod\nmetadata:\n name: inline"
self.assertEqual(resolve_k8s_yaml(inline_yaml), inline_yaml)
class StageListParsingRegressionTests(unittest.TestCase):
"""Lock stage key normalization and validation rules."""
def test_parse_stage_list_normalizes_case_and_spacing(self):
stages = ValidationHelpers.parse_stage_list(" Build,TEST,parallel-tests,custom_stage ")
self.assertEqual(stages, ["build", "test", "parallel-tests", "custom_stage"])
def test_parse_stage_list_rejects_invalid_keys(self):
with self.assertRaisesRegex(ValueError, "Invalid stage key"):
ValidationHelpers.parse_stage_list("build,Security Scan")
def test_parse_stage_list_requires_at_least_one_stage(self):
with self.assertRaisesRegex(ValueError, "At least one stage must be provided"):
ValidationHelpers.parse_stage_list(" , , ")
class ParallelFailFastRegressionTests(unittest.TestCase):
"""Verify parallel fail-fast rendering combinations."""
@staticmethod
def _render_pipeline(options=None, parallel_fail_fast=True):
config = {
"name": "Parallel Fail Fast Regression",
"stages": ["parallel-tests"],
"parallel_fail_fast": parallel_fail_fast,
}
if options is not None:
config["options"] = options
return DeclarativePipelineGenerator(config).generate()
def test_parallel_stage_includes_fail_fast_by_default(self):
output = self._render_pipeline()
self.assertIn("failFast true", output)
def test_global_fail_fast_option_omits_redundant_stage_flag(self):
output = self._render_pipeline(options={"parallelsAlwaysFailFast": True})
self.assertIn("parallelsAlwaysFailFast()", output)
self.assertNotIn("failFast true", output)
def test_parallel_stage_can_disable_fail_fast(self):
output = self._render_pipeline(parallel_fail_fast=False)
self.assertNotIn("failFast true", output)
self.assertNotIn("parallelsAlwaysFailFast()", output)
if __name__ == "__main__":
unittest.main()
#!/usr/bin/env python3
"""Regression tests for generate_shared_library.py."""
from pathlib import Path
import sys
import tempfile
import unittest
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))
from generate_shared_library import SharedLibraryGenerator # noqa: E402
class SharedLibraryDeployTemplateRegressionTests(unittest.TestCase):
"""Ensure generated deploy helper remains Kubernetes-safe."""
def _render_deploy_template(self) -> str:
with tempfile.TemporaryDirectory() as temp_dir:
generator = SharedLibraryGenerator(
name="example-lib",
package="org.example",
output_dir=temp_dir,
)
generator.generate()
deploy_path = Path(temp_dir) / "example-lib" / "vars" / "deployApp.groovy"
return deploy_path.read_text(encoding="utf-8")
def test_deployment_name_uses_job_base_name_and_dns_sanitization(self):
text = self._render_deploy_template()
self.assertIn("config.get('deploymentName', env.JOB_BASE_NAME ?: '')", text)
self.assertNotIn("config.get('deploymentName', env.JOB_NAME)", text)
self.assertIn(".replaceAll(/[^a-z0-9-]/, '-')", text)
self.assertIn(".replaceAll(/-+/, '-')", text)
self.assertIn(".replaceAll(/^-|-$/, '')", text)
self.assertIn("if (deploymentName.length() > 63)", text)
self.assertIn(
"deploymentName is required and must resolve to a valid Kubernetes deployment name",
text,
)
def test_rollout_command_uses_quoted_env_variables(self):
text = self._render_deploy_template()
self.assertIn("withEnv([", text)
self.assertIn("\"KUBE_NAMESPACE=${namespace}\"", text)
self.assertIn("\"DEPLOYMENT_NAME=${deploymentName}\"", text)
self.assertIn("\"MANIFESTS_PATH=${manifests}\"", text)
self.assertIn("set -euo pipefail", text)
self.assertIn("kubectl apply -f \"$MANIFESTS_PATH\" -n \"$KUBE_NAMESPACE\"", text)
self.assertIn(
"kubectl rollout status \"deployment/$DEPLOYMENT_NAME\" -n \"$KUBE_NAMESPACE\" --timeout=300s",
text,
)
self.assertNotIn("deployment/${deploymentName}", text)
if __name__ == "__main__":
unittest.main()
Related skills
How it compares
Choose jenkinsfile-generator for Jenkins-specific Groovy pipelines rather than generic GitHub Actions workflow skills.
FAQ
Does jenkinsfile-generator validate generated pipelines?
jenkinsfile-generator always invokes the devops-skills:jenkinsfile-validator skill after generation. Errors must be fixed before delivery; warnings and info suggestions cover fail-fast parallel blocks, triggers, and artifact fingerprinting.
When should jenkinsfile-generator choose scripted over declarative?
jenkinsfile-generator defaults to declarative pipelines for static stage order. It switches to scripted Groovy when stages must be generated at runtime or require complex conditional control flow that declarative syntax cannot express cleanly.