DeployU
Interviews / DevOps & Cloud Infrastructure / 50 teams are duplicating pipeline code. Design a shared library architecture for the organization.

Questions

50 teams are duplicating pipeline code. Design a shared library architecture for the organization.

architecture Shared Libraries Interactive Quiz Code Examples

The Scenario

Your organization has grown to 50 engineering teams, each with their own Jenkins pipelines. You notice:

  • Every team has copy-pasted the same deployment code with slight variations
  • Security vulnerabilities found in one pipeline exist in 200+ others
  • Teams spend days writing boilerplate instead of shipping features
  • No standardization for notifications, approvals, or rollback procedures

Leadership wants a solution that enables consistency while allowing team flexibility.

The Challenge

Design a shared library architecture that provides reusable, versioned pipeline components while allowing teams to customize behavior for their specific needs.

Wrong Approach

A junior engineer might create one giant shared library with all logic, force all teams to use identical pipelines, or just document best practices and hope teams follow them. These approaches fail because monolithic libraries are hard to maintain, forced standardization kills productivity, and documentation without enforcement leads to drift.

Right Approach

A senior engineer designs a modular shared library with clear separation of concerns, versioned releases for stability, sensible defaults with override capabilities, and comprehensive testing. They implement a governance model that balances standardization with team autonomy.

Step 1: Design Library Structure

jenkins-shared-library/
├── vars/                          # Global variables (pipeline steps)
│   ├── standardPipeline.groovy    # Full pipeline template
│   ├── buildApp.groovy            # Build step
│   ├── deployToK8s.groovy         # Kubernetes deployment
│   ├── notifySlack.groovy         # Slack notifications
│   ├── securityScan.groovy        # Security scanning
│   └── runTests.groovy            # Test execution
├── src/
│   └── org/
│       └── company/
│           └── jenkins/
│               ├── Config.groovy          # Configuration classes
│               ├── DeploymentStrategy.groovy
│               ├── NotificationService.groovy
│               └── SecurityValidator.groovy
├── resources/
│   ├── templates/
│   │   ├── kubernetes/
│   │   │   ├── deployment.yaml
│   │   │   └── service.yaml
│   │   └── docker/
│   │       └── Dockerfile.template
│   └── scripts/
│       └── security-scan.sh
└── test/
    └── groovy/
        └── vars/
            └── StandardPipelineTest.groovy

Step 2: Create Core Pipeline Template

// vars/standardPipeline.groovy
def call(Map config = [:]) {
    // Validate required parameters
    validateConfig(config)

    // Merge with defaults
    def pipelineConfig = mergeWithDefaults(config)

    pipeline {
        agent { label pipelineConfig.agentLabel ?: 'default' }

        options {
            buildDiscarder(logRotator(numToKeepStr: '10'))
            timeout(time: pipelineConfig.timeoutMinutes ?: 60, unit: 'MINUTES')
            timestamps()
            ansiColor('xterm')
        }

        environment {
            APP_NAME = pipelineConfig.appName
            TEAM = pipelineConfig.team
        }

        stages {
            stage('Checkout') {
                steps {
                    checkout scm
                }
            }

            stage('Build') {
                when { expression { pipelineConfig.enableBuild != false } }
                steps {
                    script {
                        buildApp(pipelineConfig.build ?: [:])
                    }
                }
            }

            stage('Test') {
                when { expression { pipelineConfig.enableTests != false } }
                parallel {
                    stage('Unit Tests') {
                        steps {
                            runTests(type: 'unit', config: pipelineConfig.tests?.unit ?: [:])
                        }
                    }
                    stage('Integration Tests') {
                        when { expression { pipelineConfig.tests?.integration?.enabled != false } }
                        steps {
                            runTests(type: 'integration', config: pipelineConfig.tests?.integration ?: [:])
                        }
                    }
                }
            }

            stage('Security Scan') {
                when { expression { pipelineConfig.enableSecurityScan != false } }
                steps {
                    securityScan(pipelineConfig.security ?: [:])
                }
            }

            stage('Deploy to Dev') {
                when {
                    branch 'develop'
                    expression { pipelineConfig.environments?.dev?.enabled != false }
                }
                steps {
                    deployToK8s(
                        environment: 'dev',
                        config: pipelineConfig.environments?.dev ?: [:]
                    )
                }
            }

            stage('Deploy to Production') {
                when {
                    branch 'main'
                    expression { pipelineConfig.environments?.prod?.enabled != false }
                }
                steps {
                    script {
                        // Require approval for production
                        if (pipelineConfig.environments?.prod?.requireApproval != false) {
                            approveDeployment(
                                environment: 'production',
                                approvers: pipelineConfig.environments?.prod?.approvers ?: ['platform-team']
                            )
                        }

                        deployToK8s(
                            environment: 'prod',
                            config: pipelineConfig.environments?.prod ?: [:]
                        )
                    }
                }
            }
        }

        post {
            always {
                script {
                    notifySlack(
                        channel: pipelineConfig.notifications?.slackChannel ?: '#builds',
                        status: currentBuild.result
                    )
                }
            }
            failure {
                script {
                    if (pipelineConfig.notifications?.pagerduty?.enabled) {
                        triggerPagerDuty(pipelineConfig.notifications.pagerduty)
                    }
                }
            }
        }
    }
}

def validateConfig(Map config) {
    def required = ['appName', 'team']
    def missing = required.findAll { !config.containsKey(it) }
    if (missing) {
        error "Missing required configuration: ${missing.join(', ')}"
    }
}

def mergeWithDefaults(Map config) {
    def defaults = [
        agentLabel: 'docker',
        timeoutMinutes: 60,
        enableBuild: true,
        enableTests: true,
        enableSecurityScan: true
    ]
    return defaults + config
}

Step 3: Create Modular Components

// vars/buildApp.groovy
def call(Map config = [:]) {
    def buildType = config.type ?: detectBuildType()

    switch(buildType) {
        case 'maven':
            buildMaven(config)
            break
        case 'gradle':
            buildGradle(config)
            break
        case 'npm':
            buildNpm(config)
            break
        case 'docker':
            buildDocker(config)
            break
        default:
            error "Unknown build type: ${buildType}"
    }
}

def buildDocker(Map config) {
    def imageName = config.imageName ?: "${env.APP_NAME}"
    def registry = config.registry ?: 'registry.company.com'
    def tag = config.tag ?: "${env.GIT_COMMIT?.take(8) ?: 'latest'}"

    withCredentials([usernamePassword(
        credentialsId: config.registryCredentials ?: 'docker-registry',
        usernameVariable: 'DOCKER_USER',
        passwordVariable: 'DOCKER_PASS'
    )]) {
        sh """
            echo \$DOCKER_PASS | docker login ${registry} -u \$DOCKER_USER --password-stdin
            docker build -t ${registry}/${imageName}:${tag} .
            docker push ${registry}/${imageName}:${tag}
        """
    }

    // Return image reference for downstream stages
    return "${registry}/${imageName}:${tag}"
}

def detectBuildType() {
    if (fileExists('pom.xml')) return 'maven'
    if (fileExists('build.gradle')) return 'gradle'
    if (fileExists('package.json')) return 'npm'
    if (fileExists('Dockerfile')) return 'docker'
    error "Could not detect build type. Please specify config.type"
}
// vars/deployToK8s.groovy
def call(Map args) {
    def environment = args.environment
    def config = args.config ?: [:]

    def namespace = config.namespace ?: "${env.APP_NAME}-${environment}"
    def kubeconfig = config.kubeconfig ?: "kubeconfig-${environment}"

    withCredentials([file(credentialsId: kubeconfig, variable: 'KUBECONFIG')]) {
        script {
            // Apply Kubernetes manifests
            def manifests = config.manifests ?: "k8s/${environment}"

            // Substitute environment variables
            sh """
                envsubst < ${manifests}/deployment.yaml | kubectl apply -f -
                envsubst < ${manifests}/service.yaml | kubectl apply -f -
            """

            // Wait for rollout
            def deploymentName = config.deploymentName ?: env.APP_NAME
            timeout(time: config.rolloutTimeout ?: 10, unit: 'MINUTES') {
                sh """
                    kubectl rollout status deployment/${deploymentName} \
                        -n ${namespace} \
                        --timeout=600s
                """
            }

            // Run smoke tests if configured
            if (config.smokeTest) {
                runSmokeTest(config.smokeTest)
            }
        }
    }
}

Step 4: Enable Team Customization

// Team's Jenkinsfile - Simple usage with defaults
@Library('company-shared-library@v2.0') _

standardPipeline(
    appName: 'user-service',
    team: 'platform'
)
// Team's Jenkinsfile - Full customization
@Library('company-shared-library@v2.0') _

standardPipeline(
    appName: 'payment-service',
    team: 'payments',
    agentLabel: 'secure-agents',
    timeoutMinutes: 90,

    build: [
        type: 'docker',
        imageName: 'payment-service',
        registry: 'registry.company.com/payments'
    ],

    tests: [
        unit: [enabled: true],
        integration: [
            enabled: true,
            database: 'postgres:13'
        ]
    ],

    security: [
        scanners: ['snyk', 'trivy'],
        failOnCritical: true
    ],

    environments: [
        dev: [enabled: true, namespace: 'payments-dev'],
        staging: [enabled: true, namespace: 'payments-staging'],
        prod: [
            enabled: true,
            namespace: 'payments-prod',
            requireApproval: true,
            approvers: ['payments-leads', 'sre-team']
        ]
    ],

    notifications: [
        slackChannel: '#payments-builds',
        pagerduty: [enabled: true, serviceKey: 'payments-pd']
    ]
)

Step 5: Versioning and Release Strategy

// Jenkinsfile for the shared library itself
pipeline {
    agent any

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

        stage('Release') {
            when { branch 'main' }
            steps {
                script {
                    def version = sh(
                        script: 'git describe --tags --abbrev=0',
                        returnStdout: true
                    ).trim()

                    // Tag and push
                    sh """
                        git tag -a v${version} -m "Release ${version}"
                        git push origin v${version}
                    """
                }
            }
        }
    }
}

Configure in Jenkins:

Manage Jenkins > Configure System > Global Pipeline Libraries

Name: company-shared-library
Default version: main
Allow default version to be overridden: true
Include @Library changes in job recent changes: true

Retrieval method: Modern SCM
  - Git
  - Project Repository: https://github.com/company/jenkins-shared-library
  - Behaviors: Discover branches, Discover tags

Shared Library Best Practices

PracticeWhyHow
Semantic versioningTeams can pin to stable versionsUse git tags: v1.0.0, v2.0.0
Comprehensive testingCatch bugs before teams hit themUnit test with JenkinsPipelineUnit
Sensible defaultsTeams shouldn’t need to configure everythingProvide working defaults, allow overrides
Clear documentationTeams need to know what’s availableREADME, examples, changelog
Deprecation policyAllow time for teams to migrateWarn for 2 versions before removing

Practice Question

What is the primary advantage of using vars/ directory over src/ directory for shared library functions in Jenkins?