Custom Step Development
Custom Step Development
Overview
Custom steps allow you to extend Jenkins pipeline functionality by creating reusable, parameterized steps that can be used across different pipelines. This lesson covers the development, testing, and implementation of custom pipeline steps.
Step Development Fundamentals
Basic Step Structure
// vars/customStep.groovy
def call(Map params = [:]) {
// Parameter defaults
def config = [
timeout: 30,
retries: 3,
failFast: true
] + params
// Step implementation
retry(config.retries) {
timeout(time: config.timeout, unit: 'MINUTES') {
// Step logic here
}
}
}
Advanced Step Patterns
1. Parameterized Steps
// vars/deployToEnvironment.groovy
def call(Map params) {
def config = [
environment: 'staging',
version: 'latest',
namespace: 'default',
timeout: 10,
waitForRollout: true
] + params
pipeline {
agent any
stages {
stage('Validate Parameters') {
steps {
script {
validateDeployParams(config)
}
}
}
stage('Deploy') {
steps {
script {
performDeploy(config)
}
}
}
stage('Verify') {
when { expression { config.waitForRollout } }
steps {
script {
verifyDeployment(config)
}
}
}
}
}
}
def validateDeployParams(config) {
assert config.environment in ['dev', 'staging', 'prod']
assert config.version?.trim()
assert config.namespace?.trim()
}
def performDeploy(config) {
echo "Deploying version ${config.version} to ${config.environment}"
// Deployment implementation
}
def verifyDeployment(config) {
echo "Verifying deployment in ${config.environment}"
// Verification implementation
}
2. Conditional Step Execution
// vars/conditionalBuild.groovy
def call(Map params = [:], Closure body) {
def config = [
branch: env.BRANCH_NAME,
skipTests: false,
requireApproval: false
] + params
pipeline {
agent any
stages {
stage('Pre-build Checks') {
steps {
script {
if (config.requireApproval && config.branch == 'main') {
input 'Proceed with build?'
}
}
}
}
stage('Build') {
steps {
script {
body()
}
}
}
stage('Test') {
when { expression { !config.skipTests } }
steps {
runTests()
}
}
}
}
}
3. Error Handling and Recovery
// vars/robustStep.groovy
def call(Map params = [:]) {
def config = [
maxRetries: 3,
backoffFactor: 2,
initialDelay: 5,
failFast: false
] + params
def attempt = 1
def delay = config.initialDelay
while (attempt <= config.maxRetries) {
try {
// Step implementation
return // Success
} catch (Exception e) {
if (attempt == config.maxRetries || config.failFast) {
throw e
}
echo "Attempt ${attempt} failed: ${e.message}"
sleep(delay)
attempt++
delay *= config.backoffFactor
}
}
}
Testing Custom Steps
1. Unit Testing
// test/vars/deployToEnvironmentTest.groovy
import org.junit.*
import com.lesfurets.jenkins.unit.*
class DeployToEnvironmentTest extends BasePipelineTest {
def deployToEnvironment
@Before
void setUp() {
super.setUp()
deployToEnvironment = loadScript('vars/deployToEnvironment.groovy')
}
@Test
void 'test deployment to staging'() {
def params = [
environment: 'staging',
version: '1.0.0',
namespace: 'myapp'
]
when(deployToEnvironment(params))
.then { result ->
assert result.environment == 'staging'
assert result.version == '1.0.0'
}
}
@Test(expected = AssertionError)
void 'test invalid environment'() {
deployToEnvironment([
environment: 'invalid',
version: '1.0.0'
])
}
}
2. Integration Testing
// test/integration/deployIntegrationTest.groovy
node {
stage('Integration Test') {
// Setup test environment
def testParams = [
environment: 'test',
version: '1.0.0-test',
namespace: 'integration-tests'
]
try {
// Run the step
deployToEnvironment(testParams)
// Verify deployment
assert sh(script: 'kubectl get deployment -n integration-tests', returnStatus: true) == 0
} finally {
// Cleanup test resources
sh 'kubectl delete namespace integration-tests --ignore-not-found'
}
}
}
Best Practices
- Parameter Validation
def validateParameters(Map params) { assert params.name?.trim() : 'Parameter "name" is required' assert params.version?.trim() : 'Parameter "version" is required' assert params.timeout > 0 : 'Timeout must be positive' }
- Documentation
// vars/deployToEnvironment.groovy /** * Deploys an application to the specified environment * * @param params Map of parameters: * - environment: Target environment (dev/staging/prod) * - version: Version to deploy * - namespace: Kubernetes namespace * - timeout: Deployment timeout in minutes (default: 10) * - waitForRollout: Whether to wait for rollout completion (default: true) * @return Map containing deployment results */ def call(Map params) { // Implementation }
- Logging and Monitoring
def logStep(String step, String message) { echo "[${step}] ${message}" // Add additional logging/monitoring }
Hands-on Exercise
Exercise 1: Basic Step Creation
- Create a custom step for building and testing a Node.js application
- Implement parameter validation
- Add error handling
- Write unit tests
- Document the step
Exercise 2: Advanced Step Features
- Add conditional execution based on branch
- Implement retry logic
- Add progress monitoring
- Create integration tests
- Add performance metrics
Additional Resources
- Jenkins Pipeline Steps Reference
- Jenkins Pipeline Unit Testing Framework
- Groovy Testing Guide
- Jenkins Pipeline Best Practices
Next Steps
- Explore global variables implementation
- Learn about library management
- Practice with real-world scenarios