6.1 插件系统概述

Jenkins插件架构

插件系统特点:

模块化设计:
- 核心功能最小化
- 功能通过插件扩展
- 插件间可以相互依赖
- 热插拔支持

扩展点机制:
- Extension Points:定义扩展接口
- Extensions:实现扩展功能
- 依赖注入:自动装配
- 生命周期管理:启动、停止、卸载

插件分类:

按功能分类:
├── 源码管理插件
│   ├── Git Plugin
│   ├── Subversion Plugin
│   └── Mercurial Plugin
├── 构建工具插件
│   ├── Maven Integration Plugin
│   ├── Gradle Plugin
│   └── Ant Plugin
├── 部署插件
│   ├── Deploy to Container Plugin
│   ├── Kubernetes Plugin
│   └── AWS CodeDeploy Plugin
├── 通知插件
│   ├── Email Extension Plugin
│   ├── Slack Notification Plugin
│   └── Microsoft Teams Plugin
├── 质量管理插件
│   ├── SonarQube Scanner Plugin
│   ├── Checkstyle Plugin
│   └── JaCoCo Plugin
└── 安全插件
    ├── LDAP Plugin
    ├── Role-based Authorization Strategy
    └── Credentials Plugin

插件生态系统

官方插件统计:

插件总数:1800+
活跃维护:1200+
每月下载:数百万次
社区贡献者:数千人

热门插件Top 10:
1. Git Plugin (95%+ 安装率)
2. Pipeline Plugin
3. Credentials Plugin
4. Build Timeout Plugin
5. Timestamper Plugin
6. Workspace Cleanup Plugin
7. Email Extension Plugin
8. Maven Integration Plugin
9. Ant Plugin
10. JUnit Plugin

插件质量评级:

评级标准:
⭐⭐⭐⭐⭐ 优秀:官方维护,文档完善,广泛使用
⭐⭐⭐⭐ 良好:社区维护,功能稳定,文档较好
⭐⭐⭐ 一般:基本功能,文档一般,使用较少
⭐⭐ 较差:功能有限,文档缺失,维护不足
⭐ 不推荐:已废弃,存在问题,不建议使用

6.2 插件管理

插件安装

通过Web界面安装:

步骤:
1. 进入 "Manage Jenkins" → "Manage Plugins"
2. 选择 "Available" 标签页
3. 搜索或浏览插件
4. 勾选要安装的插件
5. 选择安装方式:
   - "Install without restart":立即安装
   - "Download now and install after restart":重启后安装

通过CLI安装:

# 安装单个插件
java -jar jenkins-cli.jar -s http://jenkins-server:8080/ \
     install-plugin git

# 安装多个插件
java -jar jenkins-cli.jar -s http://jenkins-server:8080/ \
     install-plugin git maven-plugin email-ext

# 从文件安装
java -jar jenkins-cli.jar -s http://jenkins-server:8080/ \
     install-plugin /path/to/plugin.hpi

# 安装指定版本
java -jar jenkins-cli.jar -s http://jenkins-server:8080/ \
     install-plugin git:4.8.3

批量安装脚本:

#!/bin/bash

# 定义必需插件列表
PLUGINS=(
    "git"
    "pipeline-stage-view"
    "workflow-aggregator"
    "credentials"
    "ssh-credentials"
    "build-timeout"
    "timestamper"
    "ws-cleanup"
    "email-ext"
    "maven-plugin"
    "gradle"
    "nodejs"
    "docker-workflow"
    "kubernetes"
    "sonar"
    "jacoco"
    "junit"
    "slack"
    "role-strategy"
    "ldap"
)

JENKINS_URL="http://jenkins-server:8080"
JENKINS_CLI="java -jar jenkins-cli.jar -s ${JENKINS_URL}"

echo "开始安装Jenkins插件..."

for plugin in "${PLUGINS[@]}"; do
    echo "安装插件: ${plugin}"
    ${JENKINS_CLI} install-plugin "${plugin}"
    
    if [ $? -eq 0 ]; then
        echo "✓ ${plugin} 安装成功"
    else
        echo "✗ ${plugin} 安装失败"
    fi
done

echo "插件安装完成,重启Jenkins以生效"
${JENKINS_CLI} restart

Docker环境插件安装:

# Dockerfile中预安装插件
FROM jenkins/jenkins:lts

# 切换到root用户安装插件
USER root

# 复制插件列表
COPY plugins.txt /usr/share/jenkins/ref/plugins.txt

# 安装插件
RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt

# 切换回jenkins用户
USER jenkins
# plugins.txt文件内容
git:4.8.3
pipeline-stage-view:2.25
workflow-aggregator:2.6
credentials:2.6.1
ssh-credentials:1.19
build-timeout:1.24
timestamper:1.13
ws-cleanup:0.39
email-ext:2.87
maven-plugin:3.15.1
gradle:1.36
nodejs:1.5.1
docker-workflow:1.28
kubernetes:3600.v144b_cd192ca_a_
sonar:2.13.1
jacoco:3.3.2
junit:1.60
slack:2.48
role-strategy:3.2.0
ldap:2.7

插件更新

检查更新:

自动检查:
- Jenkins每24小时检查一次更新
- 在"Manage Plugins"页面显示可用更新
- 安全更新会特别标注

手动检查:
- 点击"Check now"按钮
- 强制刷新插件更新信息

更新策略:

#!/bin/bash

# 插件更新脚本
JENKINS_URL="http://jenkins-server:8080"
JENKINS_CLI="java -jar jenkins-cli.jar -s ${JENKINS_URL}"

echo "检查插件更新..."

# 获取可更新插件列表
UPDATABLE_PLUGINS=$(${JENKINS_CLI} list-plugins | grep ')$' | awk '{print $1}')

if [ -z "${UPDATABLE_PLUGINS}" ]; then
    echo "所有插件都是最新版本"
    exit 0
fi

echo "发现可更新插件:"
echo "${UPDATABLE_PLUGINS}"

# 备份当前插件版本
echo "备份当前插件版本信息..."
${JENKINS_CLI} list-plugins > "plugins-backup-$(date +%Y%m%d-%H%M%S).txt"

# 更新插件
echo "开始更新插件..."
for plugin in ${UPDATABLE_PLUGINS}; do
    echo "更新插件: ${plugin}"
    ${JENKINS_CLI} install-plugin "${plugin}"
done

echo "插件更新完成,建议重启Jenkins"

安全更新处理:

#!/bin/bash

# 安全更新检查脚本
JENKINS_URL="http://jenkins-server:8080"

# 获取安全公告
curl -s "https://www.jenkins.io/security/advisories/" | \
grep -o 'SECURITY-[0-9]*' | \
sort -u > security-advisories.txt

# 检查已安装插件的安全状态
echo "检查插件安全状态..."

# 这里需要根据实际的安全公告API来实现
# 示例逻辑:检查插件是否在安全公告列表中

echo "安全检查完成"

插件卸载

安全卸载步骤:

卸载前检查:
1. 确认插件依赖关系
2. 检查是否有项目在使用
3. 备份相关配置
4. 通知相关团队

卸载步骤:
1. 停止使用该插件的所有任务
2. 在"Installed"标签页找到插件
3. 点击"Uninstall"按钮
4. 重启Jenkins
5. 验证卸载结果

依赖关系检查:

// 检查插件依赖的Groovy脚本
import jenkins.model.Jenkins
import hudson.PluginWrapper

def pluginName = "target-plugin-name"
def jenkins = Jenkins.instance
def pluginManager = jenkins.pluginManager

// 查找目标插件
def targetPlugin = pluginManager.getPlugin(pluginName)
if (!targetPlugin) {
    println "插件 ${pluginName} 未找到"
    return
}

println "插件: ${targetPlugin.shortName} (${targetPlugin.version})"
println "依赖此插件的其他插件:"

// 检查哪些插件依赖目标插件
pluginManager.plugins.each { plugin ->
    plugin.dependencies.each { dependency ->
        if (dependency.shortName == pluginName) {
            println "  - ${plugin.shortName} (${plugin.version})"
        }
    }
}

println "\n此插件依赖的其他插件:"
targetPlugin.dependencies.each { dependency ->
    println "  - ${dependency.shortName} (${dependency.version})"
}

6.3 常用插件介绍

源码管理插件

Git Plugin:

功能特性:
- Git仓库集成
- 分支和标签支持
- 子模块处理
- 浅克隆优化
- 多仓库支持

配置示例:
仓库URL: https://github.com/company/myapp.git
凭据: github-credentials
分支: */main, */develop, */release/*
高级选项:
  ✓ 浅克隆 (深度: 1)
  ✓ 清理工作空间
  ✓ 检出到子目录: src

GitHub Plugin:

功能特性:
- GitHub集成
- Webhook自动触发
- Pull Request构建
- 状态回报
- 发布管理

配置示例:
GitHub Server: https://api.github.com
Credentials: github-token
Webhook URL: http://jenkins-server:8080/github-webhook/

触发器配置:
✓ GitHub hook trigger for GITScm polling
✓ Build when a change is pushed to GitHub
✓ Build pull requests

构建工具插件

Maven Integration Plugin:

<!-- Maven项目配置 -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.company</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M7</version>
                <configuration>
                    <includes>
                        <include>**/*Test.java</include>
                        <include>**/*Tests.java</include>
                    </includes>
                    <reportFormat>xml</reportFormat>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.7</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Gradle Plugin:

// build.gradle配置
plugins {
    id 'java'
    id 'application'
    id 'jacoco'
    id 'org.sonarqube' version '3.3'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.7.0'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:2.7.0'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
}

test {
    useJUnitPlatform()
    finalizedBy jacocoTestReport
    
    reports {
        junitXml.enabled = true
        html.enabled = true
    }
}

jacocoTestReport {
    dependsOn test
    reports {
        xml.enabled = true
        html.enabled = true
    }
}

// Jenkins构建任务配置
task jenkinsBuild {
    dependsOn clean, test, jacocoTestReport, build
    
    doLast {
        println "Jenkins构建完成"
    }
}

Pipeline插件

Pipeline Plugin套件:

核心插件:
- workflow-aggregator: Pipeline核心功能
- pipeline-stage-view: 阶段视图
- pipeline-graph-analysis: 图形分析
- pipeline-rest-api: REST API
- pipeline-build-step: 构建步骤

扩展插件:
- pipeline-utility-steps: 实用步骤
- pipeline-aws: AWS集成
- pipeline-kubernetes: Kubernetes集成
- pipeline-docker: Docker集成

Pipeline实用步骤:

pipeline {
    agent any
    
    stages {
        stage('Utility Steps Demo') {
            steps {
                script {
                    // 读取文件
                    def content = readFile 'README.md'
                    echo "README内容长度: ${content.length()}"
                    
                    // 写入文件
                    writeFile file: 'build-info.txt', text: """
                        构建时间: ${new Date()}
                        构建编号: ${env.BUILD_NUMBER}
                        Git提交: ${env.GIT_COMMIT}
                    """
                    
                    // 读取JSON
                    def packageJson = readJSON file: 'package.json'
                    echo "应用名称: ${packageJson.name}"
                    echo "应用版本: ${packageJson.version}"
                    
                    // 写入JSON
                    def buildInfo = [
                        buildNumber: env.BUILD_NUMBER,
                        gitCommit: env.GIT_COMMIT,
                        timestamp: new Date().format('yyyy-MM-dd HH:mm:ss')
                    ]
                    writeJSON file: 'build-info.json', json: buildInfo
                    
                    // 读取YAML
                    def config = readYaml file: 'config.yaml'
                    echo "环境: ${config.environment}"
                    
                    // 压缩文件
                    zip zipFile: 'artifacts.zip', archive: false, dir: 'target'
                    
                    // 解压文件
                    unzip zipFile: 'artifacts.zip', dir: 'extracted'
                    
                    // 查找文件
                    def jarFiles = findFiles glob: 'target/*.jar'
                    jarFiles.each { file ->
                        echo "找到JAR文件: ${file.name}"
                    }
                    
                    // 文件操作
                    if (fileExists('Dockerfile')) {
                        echo 'Dockerfile存在,可以构建Docker镜像'
                    }
                    
                    // 目录操作
                    dir('subproject') {
                        sh 'pwd'
                        sh 'ls -la'
                    }
                }
            }
        }
    }
}

部署插件

Kubernetes Plugin:

# kubernetes-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:${BUILD_NUMBER}
        ports:
        - containerPort: 8080
        env:
        - name: ENVIRONMENT
          value: "production"
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: LoadBalancer
// Pipeline中使用Kubernetes插件
pipeline {
    agent {
        kubernetes {
            yaml """
                apiVersion: v1
                kind: Pod
                spec:
                  containers:
                  - name: maven
                    image: maven:3.8.6-openjdk-11
                    command:
                    - cat
                    tty: true
                    volumeMounts:
                    - name: docker-sock
                      mountPath: /var/run/docker.sock
                  - name: docker
                    image: docker:20.10.17
                    command:
                    - cat
                    tty: true
                    volumeMounts:
                    - name: docker-sock
                      mountPath: /var/run/docker.sock
                  volumes:
                  - name: docker-sock
                    hostPath:
                      path: /var/run/docker.sock
            """
        }
    }
    
    stages {
        stage('Build') {
            steps {
                container('maven') {
                    sh 'mvn clean package'
                }
            }
        }
        
        stage('Docker Build') {
            steps {
                container('docker') {
                    sh 'docker build -t myapp:${BUILD_NUMBER} .'
                }
            }
        }
        
        stage('Deploy') {
            steps {
                script {
                    kubernetesDeploy(
                        configs: 'kubernetes-deployment.yaml',
                        kubeconfigId: 'kubeconfig-credentials'
                    )
                }
            }
        }
    }
}

Docker Plugin:

// Docker Pipeline示例
pipeline {
    agent any
    
    environment {
        DOCKER_REGISTRY = 'registry.company.com'
        IMAGE_NAME = 'myapp'
        IMAGE_TAG = "${env.BUILD_NUMBER}"
    }
    
    stages {
        stage('Build Docker Image') {
            steps {
                script {
                    // 构建Docker镜像
                    def image = docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}")
                    
                    // 标记为latest
                    image.tag('latest')
                    
                    // 推送到仓库
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }
        
        stage('Run Tests in Container') {
            steps {
                script {
                    // 在容器中运行测试
                    docker.image("${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}").inside {
                        sh 'npm test'
                    }
                }
            }
        }
        
        stage('Deploy') {
            steps {
                script {
                    // 部署到Docker Swarm
                    sh """
                        docker service update \
                            --image ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
                            myapp-service
                    """
                }
            }
        }
    }
    
    post {
        always {
            // 清理本地镜像
            sh "docker rmi ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} || true"
            sh "docker rmi ${DOCKER_REGISTRY}/${IMAGE_NAME}:latest || true"
        }
    }
}

通知插件

Email Extension Plugin:

// 邮件通知配置
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
    }
    
    post {
        always {
            // 总是发送邮件
            emailext (
                subject: "构建通知: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: """
                    <h2>构建报告</h2>
                    <p><strong>项目:</strong> ${env.JOB_NAME}</p>
                    <p><strong>构建号:</strong> ${env.BUILD_NUMBER}</p>
                    <p><strong>状态:</strong> ${currentBuild.result ?: 'SUCCESS'}</p>
                    <p><strong>持续时间:</strong> ${currentBuild.durationString}</p>
                    <p><strong>构建URL:</strong> <a href="${env.BUILD_URL}">${env.BUILD_URL}</a></p>
                    
                    <h3>变更集</h3>
                    <pre>${env.CHANGES ?: '无变更'}</pre>
                    
                    <h3>控制台输出</h3>
                    <p><a href="${env.BUILD_URL}console">查看完整日志</a></p>
                """,
                mimeType: 'text/html',
                to: '${DEFAULT_RECIPIENTS}',
                cc: 'dev-team@company.com',
                attachLog: true,
                compressLog: true
            )
        }
        
        failure {
            // 构建失败时发送详细邮件
            emailext (
                subject: "🚨 构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: """
                    <h2 style="color: red;">构建失败报告</h2>
                    <p><strong>项目:</strong> ${env.JOB_NAME}</p>
                    <p><strong>构建号:</strong> ${env.BUILD_NUMBER}</p>
                    <p><strong>失败阶段:</strong> ${env.STAGE_NAME ?: '未知'}</p>
                    <p><strong>失败时间:</strong> ${new Date()}</p>
                    <p><strong>构建URL:</strong> <a href="${env.BUILD_URL}">${env.BUILD_URL}</a></p>
                    
                    <h3>可能的解决方案</h3>
                    <ul>
                        <li>检查代码变更</li>
                        <li>查看构建日志</li>
                        <li>验证依赖项</li>
                        <li>检查环境配置</li>
                    </ul>
                    
                    <p><strong>需要立即处理!</strong></p>
                """,
                mimeType: 'text/html',
                to: '${CHANGE_AUTHOR_EMAIL}, ${DEFAULT_RECIPIENTS}',
                cc: 'dev-lead@company.com',
                recipientProviders: [
                    culprits(),
                    developers(),
                    requestor()
                ]
            )
        }
        
        unstable {
            // 构建不稳定时发送警告邮件
            emailext (
                subject: "⚠️ 构建不稳定: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: """
                    <h2 style="color: orange;">构建不稳定报告</h2>
                    <p>构建完成但存在问题,请检查测试结果和质量报告。</p>
                    <p><strong>构建URL:</strong> <a href="${env.BUILD_URL}">${env.BUILD_URL}</a></p>
                    <p><strong>测试报告:</strong> <a href="${env.BUILD_URL}testReport">查看测试结果</a></p>
                """,
                mimeType: 'text/html',
                to: '${DEFAULT_RECIPIENTS}'
            )
        }
        
        fixed {
            // 构建修复时发送庆祝邮件
            emailext (
                subject: "✅ 构建已修复: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: """
                    <h2 style="color: green;">构建修复报告</h2>
                    <p>🎉 恭喜!构建已经修复。</p>
                    <p><strong>修复者:</strong> ${env.CHANGE_AUTHOR_DISPLAY_NAME ?: '未知'}</p>
                    <p><strong>构建URL:</strong> <a href="${env.BUILD_URL}">${env.BUILD_URL}</a></p>
                """,
                mimeType: 'text/html',
                to: 'dev-team@company.com'
            )
        }
    }
}

Slack Notification Plugin:

// Slack通知配置
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                // 发送开始构建通知
                slackSend(
                    channel: '#ci-cd',
                    color: 'warning',
                    message: "🚀 开始构建: ${env.JOB_NAME} - ${env.BUILD_NUMBER} (<${env.BUILD_URL}|查看详情>)"
                )
                
                sh 'mvn clean package'
            }
        }
    }
    
    post {
        success {
            slackSend(
                channel: '#ci-cd',
                color: 'good',
                message: "✅ 构建成功: ${env.JOB_NAME} - ${env.BUILD_NUMBER} (${currentBuild.durationString})",
                blocks: [
                    [
                        type: 'section',
                        text: [
                            type: 'mrkdwn',
                            text: "*构建成功* :white_check_mark:\n*项目:* ${env.JOB_NAME}\n*构建号:* ${env.BUILD_NUMBER}\n*持续时间:* ${currentBuild.durationString}"
                        ],
                        accessory: [
                            type: 'button',
                            text: [
                                type: 'plain_text',
                                text: '查看详情'
                            ],
                            url: env.BUILD_URL
                        ]
                    ]
                ]
            )
        }
        
        failure {
            slackSend(
                channel: '#ci-cd',
                color: 'danger',
                message: "❌ 构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                blocks: [
                    [
                        type: 'section',
                        text: [
                            type: 'mrkdwn',
                            text: "*构建失败* :x:\n*项目:* ${env.JOB_NAME}\n*构建号:* ${env.BUILD_NUMBER}\n*失败阶段:* ${env.STAGE_NAME ?: '未知'}"
                        ]
                    ],
                    [
                        type: 'actions',
                        elements: [
                            [
                                type: 'button',
                                text: [
                                    type: 'plain_text',
                                    text: '查看日志'
                                ],
                                url: "${env.BUILD_URL}console",
                                style: 'danger'
                            ],
                            [
                                type: 'button',
                                text: [
                                    type: 'plain_text',
                                    text: '重新构建'
                                ],
                                url: "${env.BUILD_URL}rebuild"
                            ]
                        ]
                    ]
                ]
            )
        }
    }
}

质量管理插件

SonarQube Scanner Plugin:

// SonarQube集成
pipeline {
    agent any
    
    environment {
        SONAR_PROJECT_KEY = 'myapp'
        SONAR_PROJECT_NAME = 'My Application'
    }
    
    stages {
        stage('Code Quality Analysis') {
            steps {
                // SonarQube分析
                withSonarQubeEnv('SonarQube') {
                    script {
                        if (fileExists('pom.xml')) {
                            // Maven项目
                            sh """
                                mvn clean compile sonar:sonar \
                                    -Dsonar.projectKey=${SONAR_PROJECT_KEY} \
                                    -Dsonar.projectName="${SONAR_PROJECT_NAME}" \
                                    -Dsonar.projectVersion=${env.BUILD_NUMBER}
                            """
                        } else if (fileExists('build.gradle')) {
                            // Gradle项目
                            sh """
                                ./gradlew sonarqube \
                                    -Dsonar.projectKey=${SONAR_PROJECT_KEY} \
                                    -Dsonar.projectName="${SONAR_PROJECT_NAME}" \
                                    -Dsonar.projectVersion=${env.BUILD_NUMBER}
                            """
                        } else {
                            // 通用扫描
                            sh """
                                sonar-scanner \
                                    -Dsonar.projectKey=${SONAR_PROJECT_KEY} \
                                    -Dsonar.projectName="${SONAR_PROJECT_NAME}" \
                                    -Dsonar.projectVersion=${env.BUILD_NUMBER} \
                                    -Dsonar.sources=src \
                                    -Dsonar.tests=test \
                                    -Dsonar.java.binaries=target/classes \
                                    -Dsonar.junit.reportPaths=target/surefire-reports/*.xml \
                                    -Dsonar.jacoco.reportPaths=target/jacoco.exec
                            """
                        }
                    }
                }
            }
        }
        
        stage('Quality Gate') {
            steps {
                // 等待质量门禁结果
                timeout(time: 10, unit: 'MINUTES') {
                    script {
                        def qg = waitForQualityGate()
                        if (qg.status != 'OK') {
                            error "质量门禁失败: ${qg.status}"
                        } else {
                            echo "质量门禁通过: ${qg.status}"
                        }
                    }
                }
            }
        }
    }
    
    post {
        always {
            // 发布SonarQube报告链接
            script {
                def sonarUrl = "${env.SONAR_HOST_URL}/dashboard?id=${SONAR_PROJECT_KEY}"
                echo "SonarQube报告: ${sonarUrl}"
                
                // 添加构建描述
                currentBuild.description = "<a href='${sonarUrl}'>SonarQube报告</a>"
            }
        }
    }
}

JaCoCo Plugin:

// JaCoCo代码覆盖率
pipeline {
    agent any
    
    stages {
        stage('Test with Coverage') {
            steps {
                sh 'mvn clean test jacoco:report'
            }
            post {
                always {
                    // 发布JaCoCo报告
                    jacoco(
                        execPattern: 'target/jacoco.exec',
                        classPattern: 'target/classes',
                        sourcePattern: 'src/main/java',
                        exclusionPattern: '**/test/**,**/generated/**'
                    )
                    
                    // 发布HTML报告
                    publishHTML([
                        allowMissing: false,
                        alwaysLinkToLastBuild: true,
                        keepAll: true,
                        reportDir: 'target/site/jacoco',
                        reportFiles: 'index.html',
                        reportName: 'JaCoCo Coverage Report'
                    ])
                }
            }
        }
    }
    
    post {
        always {
            script {
                // 检查覆盖率阈值
                def coverage = readFile('target/site/jacoco/index.html')
                def matcher = coverage =~ /Total.*?(\d+)%/
                if (matcher) {
                    def coveragePercent = matcher[0][1] as Integer
                    echo "代码覆盖率: ${coveragePercent}%"
                    
                    if (coveragePercent < 80) {
                        currentBuild.result = 'UNSTABLE'
                        echo "警告: 代码覆盖率低于80%"
                    }
                }
            }
        }
    }
}

6.4 插件开发基础

开发环境搭建

Maven项目结构:

myplugin/
├── pom.xml
├── src/
│   └── main/
│       ├── java/
│       │   └── com/
│       │       └── company/
│       │           └── jenkins/
│       │               └── plugins/
│       │                   └── myplugin/
│       │                       ├── MyPlugin.java
│       │                       ├── MyBuilder.java
│       │                       └── MyAction.java
│       └── resources/
│           ├── index.jelly
│           └── com/
│               └── company/
│                   └── jenkins/
│                       └── plugins/
│                           └── myplugin/
│                               ├── MyBuilder/
│                               │   ├── config.jelly
│                               │   └── help-command.html
│                               └── Messages.properties
└── target/
    └── myplugin.hpi

pom.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.jenkins-ci.plugins</groupId>
        <artifactId>plugin</artifactId>
        <version>4.40</version>
        <relativePath />
    </parent>
    
    <groupId>com.company.jenkins.plugins</groupId>
    <artifactId>myplugin</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>hpi</packaging>
    
    <name>My Jenkins Plugin</name>
    <description>A sample Jenkins plugin</description>
    <url>https://github.com/company/jenkins-myplugin</url>
    
    <properties>
        <jenkins.version>2.361.4</jenkins.version>
        <java.level>11</java.level>
        <gitHubRepo>company/jenkins-myplugin</gitHubRepo>
    </properties>
    
    <licenses>
        <license>
            <name>MIT License</name>
            <url>https://opensource.org/licenses/MIT</url>
        </license>
    </licenses>
    
    <developers>
        <developer>
            <id>developer</id>
            <name>Developer Name</name>
            <email>developer@company.com</email>
        </developer>
    </developers>
    
    <scm>
        <connection>scm:git:git://github.com/${gitHubRepo}.git</connection>
        <developerConnection>scm:git:git@github.com:${gitHubRepo}.git</developerConnection>
        <url>https://github.com/${gitHubRepo}</url>
        <tag>HEAD</tag>
    </scm>
    
    <dependencies>
        <dependency>
            <groupId>org.jenkins-ci.plugins</groupId>
            <artifactId>credentials</artifactId>
            <version>2.6.1</version>
        </dependency>
        
        <dependency>
            <groupId>org.jenkins-ci.plugins.workflow</groupId>
            <artifactId>workflow-step-api</artifactId>
            <version>2.24</version>
        </dependency>
        
        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.jenkins-ci.plugins.workflow</groupId>
            <artifactId>workflow-cps</artifactId>
            <version>2.92</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.jenkins-ci.plugins.workflow</groupId>
            <artifactId>workflow-job</artifactId>
            <version>2.42</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <repositories>
        <repository>
            <id>repo.jenkins-ci.org</id>
            <url>https://repo.jenkins-ci.org/public/</url>
        </repository>
    </repositories>
    
    <pluginRepositories>
        <pluginRepository>
            <id>repo.jenkins-ci.org</id>
            <url>https://repo.jenkins-ci.org/public/</url>
        </pluginRepository>
    </pluginRepositories>
</project>

简单插件示例

构建步骤插件:

// MyBuilder.java
package com.company.jenkins.plugins.myplugin;

import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import jenkins.tasks.SimpleBuildStep;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

import javax.annotation.Nonnull;
import java.io.IOException;

public class MyBuilder extends Builder implements SimpleBuildStep {
    
    private final String command;
    private final boolean failOnError;
    
    @DataBoundConstructor
    public MyBuilder(String command, boolean failOnError) {
        this.command = command;
        this.failOnError = failOnError;
    }
    
    public String getCommand() {
        return command;
    }
    
    public boolean isFailOnError() {
        return failOnError;
    }
    
    @Override
    public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath workspace,
                       @Nonnull Launcher launcher, @Nonnull TaskListener listener)
            throws InterruptedException, IOException {
        
        listener.getLogger().println("执行自定义命令: " + command);
        
        try {
            // 执行命令
            int exitCode = launcher.launch()
                    .cmdAsSingleString(command)
                    .stdout(listener)
                    .stderr(listener.getLogger())
                    .pwd(workspace)
                    .join();
            
            if (exitCode != 0) {
                String message = "命令执行失败,退出码: " + exitCode;
                listener.getLogger().println(message);
                
                if (failOnError) {
                    throw new IOException(message);
                }
            } else {
                listener.getLogger().println("命令执行成功");
            }
            
        } catch (IOException e) {
            listener.getLogger().println("命令执行异常: " + e.getMessage());
            if (failOnError) {
                throw e;
            }
        }
    }
    
    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
        
        @Override
        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            return true;
        }
        
        @Override
        public String getDisplayName() {
            return "执行自定义命令";
        }
        
        // 表单验证
        public FormValidation doCheckCommand(@QueryParameter String value) {
            if (value == null || value.trim().isEmpty()) {
                return FormValidation.error("命令不能为空");
            }
            
            if (value.contains("rm -rf /")) {
                return FormValidation.error("危险命令,禁止使用");
            }
            
            return FormValidation.ok();
        }
    }
}

Pipeline步骤插件:

// MyPipelineStep.java
package com.company.jenkins.plugins.myplugin;

import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.Run;
import hudson.model.TaskListener;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Set;

public class MyPipelineStep extends Step {
    
    private final String message;
    private boolean uppercase = false;
    
    @DataBoundConstructor
    public MyPipelineStep(String message) {
        this.message = message;
    }
    
    public String getMessage() {
        return message;
    }
    
    public boolean isUppercase() {
        return uppercase;
    }
    
    @DataBoundSetter
    public void setUppercase(boolean uppercase) {
        this.uppercase = uppercase;
    }
    
    @Override
    public StepExecution start(StepContext context) throws Exception {
        return new MyPipelineStepExecution(this, context);
    }
    
    public static class MyPipelineStepExecution extends SynchronousNonBlockingStepExecution<String> {
        
        private final MyPipelineStep step;
        
        public MyPipelineStepExecution(MyPipelineStep step, StepContext context) {
            super(context);
            this.step = step;
        }
        
        @Override
        protected String run() throws Exception {
            TaskListener listener = getContext().get(TaskListener.class);
            
            String message = step.getMessage();
            if (step.isUppercase()) {
                message = message.toUpperCase();
            }
            
            listener.getLogger().println("自定义步骤输出: " + message);
            
            return message;
        }
    }
    
    @Extension
    public static class DescriptorImpl extends StepDescriptor {
        
        @Override
        public Set<? extends Class<?>> getRequiredContext() {
            return Collections.singleton(TaskListener.class);
        }
        
        @Override
        public String getFunctionName() {
            return "myStep";
        }
        
        @Override
        public String getDisplayName() {
            return "My Custom Pipeline Step";
        }
    }
}

配置界面(Jelly):

<!-- src/main/resources/com/company/jenkins/plugins/myplugin/MyBuilder/config.jelly -->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
    
    <f:entry title="${%Command}" field="command">
        <f:textbox />
    </f:entry>
    
    <f:entry title="${%Fail on Error}" field="failOnError">
        <f:checkbox />
    </f:entry>
    
</j:jelly>

帮助文档:

<!-- src/main/resources/com/company/jenkins/plugins/myplugin/MyBuilder/help-command.html -->
<div>
    输入要执行的命令。支持环境变量替换,例如:
    <ul>
        <li><code>echo "Build number: ${BUILD_NUMBER}"</code></li>
        <li><code>ls -la ${WORKSPACE}</code></li>
        <li><code>mvn clean package</code></li>
    </ul>
    
    <p><strong>注意:</strong>避免使用危险命令,如 <code>rm -rf</code> 等。</p>
</div>

插件测试

单元测试:

// src/test/java/com/company/jenkins/plugins/myplugin/MyBuilderTest.java
package com.company.jenkins.plugins.myplugin;

import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Result;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

import static org.junit.Assert.*;

public class MyBuilderTest {
    
    @Rule
    public JenkinsRule jenkins = new JenkinsRule();
    
    @Test
    public void testSuccessfulCommand() throws Exception {
        // 创建项目
        FreeStyleProject project = jenkins.createFreeStyleProject();
        
        // 添加构建步骤
        MyBuilder builder = new MyBuilder("echo 'Hello World'", true);
        project.getBuildersList().add(builder);
        
        // 执行构建
        FreeStyleBuild build = jenkins.buildAndAssertSuccess(project);
        
        // 验证日志
        jenkins.assertLogContains("Hello World", build);
        jenkins.assertLogContains("命令执行成功", build);
    }
    
    @Test
    public void testFailedCommand() throws Exception {
        // 创建项目
        FreeStyleProject project = jenkins.createFreeStyleProject();
        
        // 添加会失败的构建步骤
        MyBuilder builder = new MyBuilder("exit 1", true);
        project.getBuildersList().add(builder);
        
        // 执行构建并验证失败
        FreeStyleBuild build = jenkins.buildAndAssertStatus(Result.FAILURE, project);
        
        // 验证错误日志
        jenkins.assertLogContains("命令执行失败,退出码: 1", build);
    }
    
    @Test
    public void testFailedCommandWithoutFailOnError() throws Exception {
        // 创建项目
        FreeStyleProject project = jenkins.createFreeStyleProject();
        
        // 添加会失败但不中断构建的步骤
        MyBuilder builder = new MyBuilder("exit 1", false);
        project.getBuildersList().add(builder);
        
        // 执行构建并验证成功(因为failOnError=false)
        FreeStyleBuild build = jenkins.buildAndAssertSuccess(project);
        
        // 验证日志
        jenkins.assertLogContains("命令执行失败,退出码: 1", build);
    }
}

Pipeline测试:

// src/test/java/com/company/jenkins/plugins/myplugin/MyPipelineStepTest.java
package com.company.jenkins.plugins.myplugin;

import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

public class MyPipelineStepTest {
    
    @Rule
    public JenkinsRule jenkins = new JenkinsRule();
    
    @Test
    public void testPipelineStep() throws Exception {
        // 创建Pipeline项目
        WorkflowJob project = jenkins.createProject(WorkflowJob.class, "test-pipeline");
        
        // 设置Pipeline脚本
        project.setDefinition(new CpsFlowDefinition(
            "node {\n" +
            "  def result = myStep('Hello Pipeline')\n" +
            "  echo \"返回值: ${result}\"\n" +
            "}", true));
        
        // 执行构建
        WorkflowRun build = jenkins.buildAndAssertSuccess(project);
        
        // 验证日志
        jenkins.assertLogContains("自定义步骤输出: Hello Pipeline", build);
        jenkins.assertLogContains("返回值: Hello Pipeline", build);
    }
    
    @Test
    public void testPipelineStepWithUppercase() throws Exception {
        // 创建Pipeline项目
        WorkflowJob project = jenkins.createProject(WorkflowJob.class, "test-pipeline-uppercase");
        
        // 设置Pipeline脚本
        project.setDefinition(new CpsFlowDefinition(
            "node {\n" +
            "  def result = myStep(message: 'hello world', uppercase: true)\n" +
            "  echo \"返回值: ${result}\"\n" +
            "}", true));
        
        // 执行构建
        WorkflowRun build = jenkins.buildAndAssertSuccess(project);
        
        // 验证日志
        jenkins.assertLogContains("自定义步骤输出: HELLO WORLD", build);
        jenkins.assertLogContains("返回值: HELLO WORLD", build);
    }
}

插件构建和发布

本地构建:

# 编译插件
mvn clean compile

# 运行测试
mvn test

# 打包插件
mvn package

# 本地测试运行
mvn hpi:run

# 访问测试实例
# http://localhost:8080/jenkins

发布到Jenkins插件中心:

# 1. 配置Maven settings.xml
# 添加Jenkins仓库凭据

# 2. 发布到快照仓库
mvn deploy

# 3. 发布正式版本
mvn release:prepare release:perform

# 4. 提交到Jenkins插件索引
# 创建Pull Request到jenkins-infra/update-center2

6.5 本章小结

本章全面介绍了Jenkins插件系统:

插件系统理解: 1. 架构设计:模块化、扩展点机制 2. 插件分类:功能分类和质量评级 3. 生态系统:社区贡献和维护模式

插件管理技能: 1. 安装方式:Web界面、CLI、批量安装 2. 更新策略:自动检查、安全更新 3. 卸载流程:依赖检查、安全卸载

常用插件掌握: 1. 源码管理:Git、GitHub集成 2. 构建工具:Maven、Gradle支持 3. 部署工具:Kubernetes、Docker集成 4. 通知系统:邮件、Slack通知 5. 质量管理:SonarQube、JaCoCo集成

开发能力: 1. 环境搭建:Maven项目结构 2. 插件开发:构建步骤、Pipeline步骤 3. 测试编写:单元测试、集成测试 4. 发布流程:本地测试、正式发布

下一章预告: 下一章将介绍Jenkins的安全配置,包括用户管理、权限控制和安全最佳实践。

6.6 练习与思考

实践练习

  1. 插件管理

    • 安装常用插件套件
    • 配置插件自动更新策略
    • 编写插件安装脚本
  2. 插件配置

    • 配置Git和GitHub插件
    • 设置SonarQube集成
    • 配置Slack通知
  3. 插件开发

    • 开发简单的构建步骤插件
    • 创建Pipeline步骤插件
    • 编写插件测试用例
  4. 插件优化

    • 分析插件性能影响
    • 优化插件配置
    • 监控插件使用情况

思考题

  1. 架构思考

    • Jenkins插件系统的设计优势是什么?
    • 如何平衡插件功能和系统性能?
    • 插件依赖管理的最佳实践?
  2. 安全考虑

    • 如何评估插件的安全性?
    • 插件权限管理的重要性?
    • 如何处理插件安全漏洞?
  3. 开发实践

    • 插件开发的设计原则?
    • 如何确保插件的兼容性?
    • 插件测试策略的制定?
  4. 运维管理

    • 企业环境下的插件管理策略?
    • 如何制定插件更新计划?
    • 插件故障的排查方法?