При использовании Jenkins в компании с большим количеством проектов, рано или поздно вы заметите, что ваши описания пайплайнов (Pipeline) имеют много общего. И, возможно, вам захочется избавиться от избыточности и следовать принципу DRY (Don’t Repeat Yourself) — давайте разберемся!
Помимо принципа DRY, важна также возможность внести изменения в код пайплайна один раз и автоматически использовать обновленный пайплайн в 50-100 других проектах.
Для этой цели как нельзя лучше подходят Shared Libraries — общие библиотеки, которые могут быть определены в отдельном репозитории системы управления версиями и загружены в описании пайплайна.
Структура каталогов в репозитории общих библиотек должна выглядеть следующим образом:
+- src # Source files
| +- org
| +- foo
| +- Bar.groovy # for org.foo.Bar class
+- vars
| +- foo.groovy # for global 'foo' variable
| +- foo.txt # help for 'foo' variable
+- resources # resource files (external libraries only)
| +- org
| +- foo
| +- bar.json # static helper data for org.foo.Bar
Примечание. В данной статье мы будем использовать только каталоги vars
и resources
.
Для нас наибольший интерес представляет директория vars
— в ней можно разместить глобальные функции и переменные, доступные в пайплайнах. Согласно документации, имена файлов должны быть в camelCase
формате (без дефисов/подчеркиваний и т.д. — это важно), и иметь расширение .groovy
(именно эти файлы нас и интересуют) или .txt
(для документации).
В каталоге resources
можно разместить любые другие (не Java) файлы (например, .yaml
или .json
), которые будут загружаться в описании пайплайна с помощью шага libraryResource
.
Итак, рассмотрим несколько примеров. Допустим, у нас есть простенький проект, в котором описание пайплайна (файл Jenkinsfile
) выглядит так:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
script {
sh '''
sudo docker version
sudo docker build -t ealebed/hellonode:latest .
sudo docker image ls
'''
}
}
}
}
}
Кроме описания пайплайна в репозитории находится еще два файла — Dockerfile
:
FROM node:6.9
COPY server.js .
EXPOSE 8080
CMD node server.js
и файл server.js
следующего содержания:
var http = require('http');
var handleRequest = function(request, response) {
response.writeHead(200);
response.end("Hello World!");
}
var www = http.createServer(handleRequest);
www.listen(8080);
В первой итерации выделим в отдельные функции части пайплайна, которые можно будет использовать в других проектах. Для этого:
- создаем отдельный git-репозиторий для наших общих библиотек;
- в репозитории создаем каталог
vars
; - в каталоге
vars
размещаем скриптdockerCmd.groovy
.
Содержимое dockerCmd.groovy
:
def call(args) {
assert args != null
sh(script: "sudo docker ${args}")
}
Настраиваем использование Shared Libraries на Jenkins (пример с картинками). Теперь в нашем проекте пайплайн можно переписать так:
@Library('jenkins-shared-libs@master') _
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
dockerCmd 'version'
dockerCmd 'build -t ealebed/hellonode:latest .'
dockerCmd 'image ls'
}
}
}
}
Продолжаем. Во второй итерации избавимся от необходимости хранить Dockerfile
в репозитории проекта. Для этого в git-репозитории с общими библиотеками создаем каталог resources
и переносим в него Dockerfile
из основного проекта. Далее, в каталоге vars
размещаем скрипт createDockerfile.groovy
следующего содержания:
def call() {
def file = libraryResource 'Dockerfile'
writeFile file: 'Dockerfile', text: file
}
Описание пайплайна в основном проекте изменяем на следующее:
@Library('jenkins-shared-libs@master') _
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Get Dockerfile') {
steps {
createDockerfile()
}
}
stage('Build') {
steps {
dockerCmd 'version'
dockerCmd 'build -t ealebed/hellonode:latest .'
dockerCmd 'image ls'
}
}
}
}
Но что, если мы хотим использовать в других проектах не только отдельные функции, а весь пайплайн целиком? Нет ничего проще!
В git-репозитории с общими библиотеками в каталоге vars
создаем скрипт allPipeline.groovy
следующего содержания:
def call(body) {
def pipelineParams= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = pipelineParams
body()
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Get Dockerfile') {
steps {
script {
def tmpFile = libraryResource 'Dockerfile'
writeFile file: 'Dockerfile', text: tmpFile
}
}
}
stage('Build') {
steps {
script {
sh '''
docker version
docker build -t ealebed/hellonode:latest .
docker image ls
'''
}
}
}
}
}
}
Теперь, в содержимое файла Jenkinsfile
(описание пайплайна) в основном репозитории проекта невероятно упрощается:
@Library('jenkins-shared-libs@master') _
allPipeline {}
Больше интересных примеров можно найти в официальной документации, а также здесь, здесь и здесь.
Don’t Repeat Yourself!
Источник: https://ealebed.github.io/posts/2018/jenkins-использование-shared-libraries/