Jenkins: использование shared libraries

При использовании 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/



2023-01-03T00:10:25
DevOps