Skip to content

Servana/jenkins-shared-library-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 

Repository files navigation

Jenkins Shared Library Tutorial

Learn to build reusable, testable pipeline code that follows the DRY principle

A comprehensive, production-ready guide to Jenkins Shared Libraries with working examples, tests, and Job DSL integration.

📚 Documentation


Getting Started

###What is Jenkins Shared Library?

When we say "CI/CD as code," it should embrace modularity and reusability, following the DRY principle (Don't Repeat Yourself). Jenkins Shared Library makes this possible.

The Problem: Imagine 10 Java microservices, each with its own pipeline. The Maven build step is duplicated across all 10. When you add a service, you copy-paste the pipeline. Need to change Maven parameters? Update all 10+ pipelines manually. This is:

  • ❌ Time-consuming
  • ❌ Error-prone
  • ❌ Not scalable

The Solution: Write the Maven build once, reference it everywhere:

// Before: Duplicated in every pipeline
stage('Build') {
    sh 'mvn clean package -DskipTests'
}

// After: Shared library (one place)
stage('Build') {
    mavenBuild(goals: 'clean package', skipTests: true)
}

Update the library once → all pipelines get the change automatically.

Key Benefits

  • Code Reusability: Write once, use across teams and projects
  • Consistency: Uniform workflows and standards
  • Maintainability: Update once, apply everywhere
  • Testability: Unit tests for pipeline code
  • Versioning: Tag and version your CI/CD code

Quick Start

Prerequisites

  • Jenkins with Pipeline and Job DSL plugins
  • Basic Groovy knowledge
  • Git repository

1. Create Repository Structure

my-shared-library/
├── vars/                    # Global pipeline steps
│   ├── buildApp.groovy
│   └── buildApp.txt        # Documentation
├── src/                     # Reusable classes
│   └── com/company/jenkins/
│       └── utils/
│           └── Helper.groovy
├── resources/               # Static files
│   └── scripts/
│       └── deploy.sh
├── test/                    # Unit tests
│   └── unit/groovy/
└── build.gradle            # Build configuration

2. Write Your First Global Variable

File: vars/buildApp.groovy

/**
 * Build application using specified build tool.
 */
def call(Map config) {
    String buildTool = config.buildTool ?: 'gradle'
    String command = ""

    switch(buildTool) {
        case 'gradle':
            command = './gradlew clean build'
            break
        case 'maven':
            command = 'mvn clean package'
            break
        case 'npm':
            command = 'npm install && npm run build'
            break
        default:
            error("Unsupported build tool: ${buildTool}")
    }

    echo "Building with ${buildTool}..."
    sh command
}

File: vars/buildApp.txt

NAME
    buildApp - Build application using various build tools

SYNOPSIS
    buildApp(buildTool: 'gradle')

PARAMETERS
    buildTool (String, optional, default: 'gradle')
        Build tool to use: 'gradle', 'maven', or 'npm'

EXAMPLES
    buildApp(buildTool: 'maven')
    buildApp(buildTool: 'npm')

3. Configure in Jenkins

  1. Go to Manage JenkinsSystem
  2. Find Global Trusted Pipeline Libraries
  3. Click Add and configure:
    • Name: my-shared-lib
    • Default version: main
    • Retrieval method: Modern SCM → Git
    • Repository URL: https://github.com/your-org/my-shared-library.git
    • Credentials: Select your Git credentials

4. Use in Pipeline

File: Jenkinsfile (in your application repo)

@Library('my-shared-lib@main') _

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                buildApp(buildTool: 'gradle')
            }
        }

        stage('Test') {
            steps {
                sh './gradlew test'
            }
        }
    }
}

That's it! You've created and used your first shared library function.


Repository Structure Deep Dive

vars/ Directory

Global pipeline steps - Each .groovy file becomes a callable function.

Key points:

  • Filename = function name (e.g., buildApp.groovybuildApp())
  • Must have a call() method
  • Optional .txt file for documentation rendered in Jenkins UI

Example structure:

vars/
├── buildApp.groovy       # Implementation
├── buildApp.txt          # Help documentation in Jenkins UI
├── deployToK8s.groovy
├── dockerBuild.groovy
└── log.groovy

src/ Directory

Reusable Groovy classes - Complex, object-oriented code.

When to use:

  • Complex business logic
  • Utility classes
  • When vars/ DSLs aren't flexible enough

Example structure:

src/
└── com/company/jenkins/
    ├── builders/
    │   └── DockerBuilder.groovy
    ├── deployers/
    │   └── KubernetesDeployer.groovy
    └── utils/
        ├── StringHelper.groovy
        └── DateHelper.groovy

Usage:

import com.company.jenkins.builders.DockerBuilder

def builder = new DockerBuilder()
builder.build(imageName: 'myapp')

resources/ Directory

Static files - Scripts, templates, config files.

Common use cases:

  • Shell scripts for deployment
  • HTML email templates
  • JSON/XML configuration files

Example structure:

resources/
├── scripts/
│   ├── deploy.sh
│   └── healthcheck.sh
└── templates/
    └── notification.html

Usage:

// Load and use resource
def script = libraryResource 'scripts/deploy.sh'
writeFile file: 'deploy.sh', text: script
sh 'chmod +x deploy.sh && ./deploy.sh'

test/ Directory

Unit and integration tests - Keep pipelines reliable.

test/
└── unit/groovy/
    └── com/company/jenkins/
        ├── builders/
        │   └── DockerBuilderSpec.groovy
        └── utils/
            └── StringHelperSpec.groovy

Writing Reusable Classes

Example: String Helper Utility

File: src/com/company/jenkins/utils/StringHelper.groovy

package com.company.jenkins.utils

class StringHelper {

    static String toCamelCase(String text) {
        return text.replaceAll(/[^a-zA-Z0-9]+([a-zA-Z0-9])/) {
            full, firstLetter -> firstLetter.toUpperCase()
        }
    }

    static String toSnakeCase(String text) {
        return text.replaceAll(/([a-z])([A-Z])/, '$1_$2').toLowerCase()
    }

    static String slugify(String text) {
        return text.toLowerCase()
                   .replaceAll(/[^a-z0-9]+/, '-')
                   .replaceAll(/^-+|-+$/, '')
    }
}

Example: Bash Wrapper

File: src/com/company/jenkins/shell/Bash.groovy

package com.company.jenkins.shell

class Bash implements Serializable {

    private Script steps
    private boolean silent
    private boolean debug

    Bash(Script steps, boolean silent = false, boolean debug = false) {
        this.steps = steps
        this.silent = silent
        this.debug = debug
    }

    def call(String description, String script) {
        steps.echo("Executing: ${description}")

        String formattedScript = formatScript(script)

        int exitCode = steps.sh(
            script: formattedScript,
            returnStatus: true
        )

        if (exitCode != 0) {
            steps.error("Script failed with exit code: ${exitCode}")
        }
    }

    String formatScript(String script) {
        List<String> lines = []

        if (debug) {
            lines.add('set -x')  // Enable debug mode
        }

        lines.add('set -e')      // Exit on error
        lines.add(script)

        return lines.join('\n')
    }
}

Usage in vars:

// vars/bash.groovy
import com.company.jenkins.shell.Bash

def call(String script, Map options = [:]) {
    Bash bash = new Bash(
        this,
        options.silent ?: false,
        options.debug ?: false
    )
    bash.call('Bash execution', script)
}

Simple Working Examples

Example 1: Logging with Color

File: vars/log.groovy

def call(String message, Map options = [:]) {
    Integer color = options.color ?: 37  // White default

    ansiColor('xterm') {
        echo "\033[${color}m${message}\033[0m"
    }
}

def info(String message) {
    call(message, [color: 36])  // Cyan
}

def warn(String message) {
    call(message, [color: 33])  // Yellow
}

def error(String message) {
    call(message, [color: 31])  // Red
}

def success(String message) {
    call(message, [color: 32])  // Green
}

Usage:

log.info('Starting build')
log.warn('Deprecated feature')
log.error('Build failed')
log.success('Deployment complete')

Example 2: Docker Build

File: vars/buildDockerImage.groovy

def call(Map config) {
    String imageName = config.imageName
    List tags = config.tags ?: ['latest']
    Map buildArgs = config.buildArgs ?: [:]

    // Build image
    String buildArgsString = buildArgs.collect { k, v -> "--build-arg ${k}=${v}" }.join(' ')
    sh "docker build ${buildArgsString} -t ${imageName}:${tags[0]} ."

    // Tag additional versions
    tags.drop(1).each { tag ->
        sh "docker tag ${imageName}:${tags[0]} ${imageName}:${tag}"
    }

    echo "Built image: ${imageName} with tags: ${tags.join(', ')}"
}

Usage:

buildDockerImage(
    imageName: 'myapp',
    tags: ['v1.0.0', 'latest'],
    buildArgs: [VERSION: '1.0.0', BUILD_DATE: '2025-01-15']
)

Example 3: Kubernetes Deployment

File: vars/deployToK8s.groovy

def call(Map config) {
    String namespace = config.namespace ?: 'default'
    String deployment = config.deployment
    String image = config.image

    withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
        sh """
            kubectl config use-context ${config.cluster}
            kubectl set image deployment/${deployment} ${deployment}=${image} -n ${namespace}
            kubectl rollout status deployment/${deployment} -n ${namespace}
        """
    }

    echo "Deployed ${image} to ${namespace}/${deployment}"
}

Usage:

deployToK8s(
    cluster: 'production',
    namespace: 'apps',
    deployment: 'user-service',
    image: 'registry.company.com/user-service:v1.0.0'
)

Testing Your Library

Setup Test Dependencies

File: build.gradle

plugins {
    id 'groovy'
    id 'codenarc'
    id 'jacoco'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.codehaus.groovy:groovy-all:3.0.9'

    testImplementation 'org.spockframework:spock-core:2.1-groovy-3.0'
    testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.17'
    testImplementation 'junit:junit:4.13.2'
}

test {
    useJUnit()
}

Write a Simple Test

File: test/unit/groovy/com/company/jenkins/utils/StringHelperSpec.groovy

package com.company.jenkins.utils

import spock.lang.Specification
import spock.lang.Unroll

class StringHelperSpec extends Specification {

    @Unroll
    def 'toCamelCase converts "#input" to "#expected"'() {
        expect:
        StringHelper.toCamelCase(input) == expected

        where:
        input           || expected
        'hello-world'   || 'helloWorld'
        'foo_bar_baz'   || 'fooBarBaz'
        'test'          || 'test'
    }

    @Unroll
    def 'slugify converts "#input" to "#expected"'() {
        expect:
        StringHelper.slugify(input) == expected

        where:
        input           || expected
        'Hello World'   || 'hello-world'
        'Foo  Bar!'     || 'foo-bar'
        'test'          || 'test'
    }
}

Run Tests

# Run all tests
./gradlew test

# Run specific test
./gradlew test --tests "StringHelperSpec"

# Generate coverage report
./gradlew jacocoTestReport
open build/reports/jacoco/test/html/index.html

Best Practices

1. Always Version Your Library

// ❌ Bad - unpredictable
@Library('my-shared-lib') _

// ✅ Good - specific version
@Library('[email protected]') _

// ✅ Good for development
@Library('my-shared-lib@develop') _

2. Validate Input Parameters

def call(Map config) {
    // Validate required parameters
    List required = ['param1', 'param2']
    List missing = required.findAll { !config.containsKey(it) }

    if (missing) {
        error("Missing required parameters: ${missing.join(', ')}")
    }

    // Your logic here
}

3. Handle Errors Gracefully

def call(Map config) {
    try {
        // Your logic
        performAction(config)
    } catch (Exception e) {
        log.error("Failed: ${e.message}")
        currentBuild.result = 'FAILURE'
        throw e
    } finally {
        // Cleanup
        cleanup()
    }
}

4. Document Everything

Create .txt files for all global variables:

NAME
    myFunction - Brief description

SYNOPSIS
    myFunction(config)

PARAMETERS
    config (Map):
        - param1 (String, required): Description
        - param2 (Boolean, optional, default: false): Description

EXAMPLES
    myFunction(param1: 'value')

5. Keep Functions Small

// ❌ Bad - doing too much
def deployAll(config) {
    build()
    test()
    dockerBuild()
    deploy()
}

// ✅ Good - focused
def buildAndTest(config) {
    build(config)
    test(config)
}

def packageAndDeploy(config) {
    dockerBuild(config)
    deploy(config)
}

Next Steps

For Learning

  1. ✅ Complete this getting started guide
  2. 📖 Read Advanced Guide - Testing, Job DSL, patterns
  3. 📚 Review Reference - Complete examples and API
  4. 🧪 Explore Demo Repository - Working code

For Implementation

  1. Clone/fork this repository
  2. Customize package names and structure
  3. Add your organization-specific functions
  4. Configure in your Jenkins
  5. Start converting existing pipelines

Resources


Project Structure

jenkins-shared-library-tutorial/
├── README.md                   ← You are here
├── docs/
│   ├── advanced-guide.md      ← Testing, Job DSL, team-based patterns
│   └── reference.md            ← API reference, troubleshooting
└── demo-repository/
    ├── vars/                   ← Working global variables
    ├── src/                    ← Working classes
    ├── resources/              ← Example resources
    ├── test/                   ← Complete test suite
    ├── jobs/                   ← Team-based Job DSL structure
    │   ├── jobSeed.groovy      ← Seed job for team-based CI/CD
    │   └── jenkins-controllers/
    │       ├── common/         ← Cross-team shared jobs
    │       ├── data-science/   ← ML/AI workflows (MLflow, model serving)
    │       ├── engineering/    ← Software development (CI/CD, testing)
    │       └── devops/         ← Infrastructure (Terraform, Kubernetes)
    └── build.gradle            ← Team-based build configuration

Ready to get started? Run the demo:

cd demo-repository
./gradlew clean test

Team-Based Architecture

This repository demonstrates a modern team-based approach to Jenkins CI/CD, moving beyond traditional environment-based structures (development/production) to domain-specific team organization.

🏗️ Team Structure

Team Focus Technologies Pipeline Examples
Common Shared utilities, monitoring Jenkins, Slack, backups Health checks, security scans
Data Science ML/AI workflows MLflow, SageMaker, Kubernetes Model training, A/B testing, inference
Engineering Software development Docker, Kubernetes, testing CI/CD, deployment strategies, E2E testing
DevOps Infrastructure & platform Terraform, Kubernetes, monitoring Infrastructure as Code, compliance

🚀 Key Benefits

  • Team Autonomy: Each team manages their own CI/CD workflows
  • Domain Expertise: Pipelines tailored to specific technology stacks
  • Scalability: Teams can evolve independently
  • Modern Tooling: Integration with MLflow, Terraform, advanced testing

📁 Team Directory Structure

jobs/jenkins-controllers/
├── common/
│   ├── src/dsl/groovy/commonJobs.groovy
│   └── src/main/groovy/monitoring/
├── data-science/
│   ├── src/dsl/groovy/dataScienceJobs.groovy
│   └── src/main/groovy/
│       ├── ml-experiments/      # Model training, experimentation
│       ├── model-deployment/    # Multi-platform model serving
│       └── data-pipeline/       # Feature engineering, validation
├── engineering/
│   ├── src/dsl/groovy/engineeringJobs.groovy
│   └── src/main/groovy/
│       ├── applications/        # CI/CD, deployment strategies
│       ├── testing/            # E2E, performance, integration
│       └── libraries/          # Shared library development
└── devops/
    ├── src/dsl/groovy/devopsJobs.groovy
    └── src/main/groovy/
        ├── infrastructure/     # Terraform, cloud resources
        ├── platform-engineering/ # Kubernetes, service mesh
        └── compliance/         # Security, auditing

🔧 Team-Specific Features

Data Science Team:

  • MLflow experiment tracking and model registry
  • Multi-platform model deployment (SageMaker, Kubernetes, Lambda)
  • Automated model validation and drift detection
  • A/B testing infrastructure

Engineering Team:

  • Advanced deployment strategies (rolling, blue-green, canary)
  • Comprehensive testing automation (unit, integration, E2E)
  • Container security scanning and vulnerability management
  • Multi-environment application deployment

DevOps Team:

  • Infrastructure as Code with Terraform
  • Kubernetes cluster management and compliance
  • Security scanning and cost optimization
  • Backup, disaster recovery, and monitoring automation

Questions? Check the troubleshooting guide or advanced patterns.


Happy coding! 🚀

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages