Автор статьи: Андрей Поляков

Старший разработчик в Unlimint

Добрый день, коллеги. Jenkins является одной из самых популярных систем CI/CD, которая применяется для построения пайплайнов сборки и доставки.

Jenkins поддерживает построение пайплайнов на основе Groovy скриптов. Использование Groovy дает возможность очень гибко настраивать пайплайны под нужды конкретного проекта.

Рассмотрим основы построения пайплайнов в Jenkins с использованием Groovy скриптов.

Groovy - это объектно-ориентированный язык программирования, использующий платформу JVM. Этот динамический язык имеет множество возможностей, вдохновленных Python, Smalltalk и Ruby. Он также предлагает множество функций для повышения скорости и удобства написания кода, таких как поддержка DSL, замыкания и динамическая типизация.

Почему стоит использовать Groovy в Jenkins?

  1. Groovy — гибкий и динамичный язык. Он имеет бесшовную интеграцию со всеми существующими объектами и библиотеками Java.

  2. В Groovy цикл for очень лаконичный и удобный для чтения. Groovy поддерживает все операторы цикла из Java: while, for, for-in, break, continue, и все это совместимо с Java.

  3. В Groovy можно использовать scopes для определения коллекций или массивов.

  4. В Groovy очень мощный механизм операторов: их можно переопределять, использовать с объектами, а не только с примитивами как в Java.

  5. И многие другие возможности языка: коллекции, продвинутая обработка исключений, и, конечно, DSL.

Jenkins Pipelines

Создать пайплайн в Jenkins с использованием groovy-конфигурации можно двумя способами:

  1. Через классический пользовательский интерфейс / Blue Ocean — вы можете напрямую загрузить файл пайплайна в Jenkins или написать скрипт, используя классический пользовательский интерфейс (или) с помощью пользовательского интерфейса Blue Ocean — вы можете использовать пользовательский интерфейс Blue Ocean, чтобы написать Jenkinsfile Pipeline и отправить его в систему управления версиями.

  2. Через систему контроля версий  — файл Jenkins можно написать вручную и отправить в репозиторий системы управления версиями проекта.

Jenkinsfile, созданный с помощью классического пользовательского интерфейса, сохраняется непосредственно самим Jenkins. Пайплайн, созданный из классического пользовательского интерфейса Jenkins, сохраняется в корневом каталоге Jenkins, а сценарий выполняется в Jenkins script console. Jenkinsfile — это базовый код для Jenkins, который выполняет его как сценарий Groovy в Jenkins script console.

В начале любого Jenkins пайплайна необходимо объявить shebang. shebang определяет файл как сценарий языка Groovy.

Обратите внимание, что In-line Pipeline файлы не имеют shebang, потому что он поставляется “из-коробки”.

Существуют два способа описания пайплайнов на языке Groovy:

  • декларативные пайплайны;

  • scripted пайплайны.

Далее приведем пример декларативного пайплайна:

pipeline {
  agent any
  stages {
    stage('Stage 1') {
      steps {
        echo 'Hello world!'
      }
    }
  }
}

Основные операторы и выражения, допустимые в декларативном конвейере, следуют тем же правилам, что и синтаксис Groovy, со следующими исключениями:

  • На Верхнем уровне пайплайна должен быть блок (closure), а именно: pipeline { }.

  • Нет точек с запятой в качестве разделителей операторов. Каждое выражение должно быть на отдельной строке.

  • Блоки должны состоять только из Sections, Directives, Steps или операторов присваивания.

  • Ссылка на свойство рассматривается как вызов метода без аргументов. Так, например, свойство input обрабатывается как input().

Как видно, у декларативных пайплайнов есть достаточно много ограничений. Scripted пайплайны позволяют использовать все возможности языка Groovy. Scripted пайплайн — это DSL на основе Groovy. Он обеспечивает большую гибкость и масштабируемость для пользователей Jenkins, чем декларативный пайплайн.

Пример кода для scripted пайплайна:

node {
    stage('Example') {
        if (env.BRANCH_NAME == 'master') {
            echo 'I only execute on the master branch'
        } else {
            echo 'I execute elsewhere'
        }
    }
}

Как видно из примера, в scripted пайплайне можно использовать привычные условные операторы if. Также в scripted пайплайне можно использовать и другие операторы groovy, например, try-catch.

Помимо своего собственного кода на groovy в scripted-пайплайнах, можно также подключать сторонние библиотеки и вызывать методы из них. Также по мере того, как пайплайн применяется для все большего числа проектов в организации, вероятно, появляются общие шаблоны пайплайнов. Часто бывает полезно разделить части пайплайнов между различными проектами, чтобы уменьшить избыточность.

Пайплайны Jenkins поддерживает создание общих библиотек, которые можно сохранить во внешних репозиториях системы управления версиями и загрузить в существующие пайплайны.

Интеграция с IDE

Как обстоят дела у Jenkins со средствами разработки?

  • Интеграция с IDE - есть плагины и автокомплит.

  • Unit Test Framework - позволяет тестировать и отлаживать пайплайны.

  • Статический анализ - есть.

  • Library manager - есть.

  • Средства отладки - не очень удобные.

  • Документация - есть довольно подробная документация.

  • Деплоймент скриптов - не очень удобный процесс.

  • Средства миграции на DSL - не поддерживаются из коробки.

Создавать пайплайны удобнее всего в IDE, которая поддерживает авто подстановку и интеграцию с jenkins dsl. Jenkins поддерживает интеграцию со всеми популярными IDE, в том числе eclipse, vscode и intellij idea.

Пример настройки Intellij Idea для интеграции с Jenkins можно посмотреть по ссылке: 

Для интеграции с Intellij Idea можно зайти в любую сборку, выбрать Pipeline Syntax -> Intellij IDEA GDSL. Откроется файл с описанием Jenkins DSL. Файл необходимо сохранить и поместить в корневой директории с исходниками вашего проекта (если вы используете maven или gradle, то это директория src/main/groovy). Обратите внимание, что во всех остальных местах проекта, например, в тестах авто подстановка работать не будет.

Расширение .jenkinsfile по умолчанию не распознается Intellij IDEA, поэтому необходимо в настройках ide ассоциировать этот тип файла с groovy:

Компоненты пайплайна

После того, как ваша IDE настроена для работы с Jenkins скриптами, рассмотрим основные компоненты пайплайнов Jenkins. Jenkins использует предопределенный язык DSL (в частности для декларативных пайплайнов), но этот DSL основан на определенном groovy коде, поэтому при написании пайплайнов (в том числе декларативных), происходит вызов методов groovy.

Самый верхнеуровневый блок пайплайна - это pipeline. Метод pipeline в качестве аргумента принимает closure - замыкание, написанное на языке groovy.

Блок agent  указывает, где будет выполняться весь pipeline или определенный stage в среде Jenkins в зависимости от того, где размещен блок agent.

Пример блока agent для Scripted Pipeline:

.node("myAgent") {
    timeout(unit: 'SECONDS', time: 5) {
        stage("One"){
            sleep 10
            echo 'hello'
        }
    }
}

Пример для декларативного пайплайна:

pipeline {
    agent { docker 'maven:3.8.1-adoptopenjdk-11' } 
    stages {
        stage('Example Build') {
            steps {
                sh 'mvn -B clean verify'
            }
        }
    }
}

Блок post определяет один или несколько дополнительных шагов, которые выполняются после завершения pipeline или stage (в зависимости от расположения блока post в pipeline). post может поддерживать любой из следующих блоков пост-условия:always, changed, fixed, regression, aborted, failure, success, unstable, unsuccessful, и cleanup.

Пример post:

post { 
        always { 
            echo 'I will always say Hello again!'
        }
}

Более подробно с пост-условиями можно ознакомиться по ссылке: https://www.jenkins.io/doc/book/pipeline/syntax/#post-conditions 

Сами шаги пайплайна описываются в секции stages (и этот блок состоит из шагов stage). Здесь описывается сама логика пайплайна: какие действия и в каком порядке необходимо выполнить:

pipeline {
    agent any
    stages { 
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

Для выполнения пайплайна могут понадобиться внешние инструменты (например, указать, какую версию maven использовать). Для этого используется блок tools. Рассмотрим пример:

tools {
        // Install the Maven version configured as "M3" and add it to the path.
        maven "M3"
}

В примере M3 - название версии maven, добавленной в настройках Jenkins.

Возможна ситуация, когда необходимо выполнить несколько шагов сборки в параллельном режиме. Для этого используется блок parallel.

Например, есть 3 проекта batching, streaming и rest, каждый из которых зависит от проекта common-libs.

В итоге мы хотим получить пайплайн:

Для этого достаточно указать секцию parallel:

stage('build deploy target') {
            parallel {
                stage('streaming') {
                    stages {

                        stage('build streaming') {
                            steps {
                                git 'https://github.com/a-poliakov/streaming.git'

                                sh "git checkout ${params.BRANCH}"

                                sh "mvn clean package"

                            }
                        }

                        stage('deploy streaming') {
                            steps {
                                script {
                                    project_version = sh script: 'mvn help:evaluate -Dexpression=project.version -q -DforceStdout', returnStdout: true
                                    echo project_version
                                }

                                sh """cd target
                                      curl -i -X PUT "http://namenode:9870/webhdfs/v1/streaming_${params.BRANCH}/common-libs-${project_version}.jar?op=CREATE&overwrite=true"
                                      curl -i -X PUT -T test_jenkins_mvn-1.0-SNAPSHOT.jar "http://datanode:9864/webhdfs/v1/streaming_${params.BRANCH}/common-libs-${project_version}.jar?op=CREATE&namenoderpcaddress=namenode:9000&createflag=&createparent=true&overwrite=true"
                                   """
                            }
                        }
                    }
                }

                stage('batch') {
                    stages {

                        stage('build batching') {
                            steps {
                                git 'https://github.com/a-poliakov/batching.git'

                                sh "git checkout ${params.BRANCH}"

                                sh "mvn clean package"
                            }
                        }

                        stage('deploy batching') {
                            steps {
                                sh """cd target
                                  curl -i -X PUT "http://namenode:9870/webhdfs/v1/batch_${params.BRANCH}/batching-1.0-SNAPSHOT.jar?op=CREATE&overwrite=true"
                                  curl -i -X PUT -T test_jenkins_mvn-1.0-SNAPSHOT.jar "http://datanode:9864/webhdfs/v1/batch_${params.BRANCH}/batching-1.0-SNAPSHOT.jar?op=CREATE&namenoderpcaddress=namenode:9000&createflag=&createparent=true&overwrite=true"
                                   """
                            }
                        }

                    }
                }

                stage('rest') {
                    stages {

                        stage('update DB') {
                            steps {
                                echo 'Starting cassandra loader'
                            }
                        }

                        stage('build Rest') {
                            steps {
                                echo 'cloned and built dao'
                            }
                        }

                        stage('deploy Rest into Tomcat') {
                            steps {
                                echo 'test dao'
                            }
                        }

                        stage('start Jmeter') {
                            steps {
                                echo '.... JMeter Test OK'
                            }
                        }
                    }
                }
            }
        }

Тестирование пайплайнов

После того, как код пайплайна написан, хотелось бы его протестировать. Для этого существует очень удобная библиотека JenkinsPipelineUnit Testing Framework

Основное преимущество JenkinsPipelineUnit - простота настройки и написания тестов. Другое важное преимущество - мы получаем возможность тестировать и отлаживать пайплайны перед внедрением в продуктовой среде.

Рассмотрим пример:

class BasePipelineTestTest extends BasePipelineTest {
    @BeforeEach
    @Override
    void setUp() throws Exception {
        scriptRoots += 'src/test/groovy/jenkins/jenkinsfiles'
        scriptExtension = ''
        super.setUp()
    }


    @Test
    void buildStatusSuccess() {
        runScript('BuildStatus_Success_Jenkinsfile')
        assertThat(binding.getVariable('currentBuild').result).isEqualTo('SUCCESS')
        assertThat(binding.getVariable('currentBuild').currentResult).isEqualTo('SUCCESS')
    }
}

В тесте мы запускаем пайплайн, который лежит в файле 'BuildStatus_Success_Jenkinsfile'. Что в этом файле?

node {
    stage('Success') {
        echo 'should succeed'
    }
}

Как видно, единственное, что в данном пайплайне происходит, это вывод сообщения  'should succeed'. Пайплайн состоит всего из одного шага.

В тесте проверяется, что в результате запуска этого пайплайна статус сборки будет успешным (result = Success).

Еще одно преимущество использования JenkinsPipelineUnit  - возможность продебажить пайплайн и понять, что происходит на том или ином шаге. Например:

Выводы

Таким образом, пайплайны в Jenkins можно очень гибко сконфигурировать с помощью groovy. Существуют два вида пайплайнов - декларативные и scripted. И те, и те используют Groovy DSL. Есть большое инструментов для интеграции с инструментами разработки. Самые удобные - автокомплит в ide и отладка пайплайнов через тесты.

В заключение приглашаю всех на бесплатный вебинар, где рассмотрим как можно расширить функциональность тестового плана в JMeter с помощью groovy-скриптов. Узнаем в каких случаях их стоит применять и как применять.

Комментарии (8)


  1. Tzimie
    13.12.2022 16:30

    А вот скажите, зачем множить сущности? Я когда принес Jenkins для определенной цели, смотрел на Groovy и он мне показался очень некрасивым.

    К счастью, из Jenkins можно вызвать буду Шелл скрипт и прекрасно обойтись без Groovy


    1. Oz_Alex
      14.12.2022 03:01

      А зачем вам Jenkins в принципе, если вы можете использовать shell-скрипт?


      1. Samhuawei
        14.12.2022 09:53
        +1

        Ну как минимум выдать права на Jenkins какому-нибудь .NET разработчику который командную строку в гробу видал и научить жмакать кнопку Build.


      1. Tzimie
        14.12.2022 10:28

        Для меня это хороший интерфейс (права, аудит, веб, система секретов ) который может быть дан юзерам для выполнения скриптов, для которых у них нет и не может быть прав (и даже на сервер этот доступа нет)


    1. slonopotamus
      14.12.2022 10:02

      Нельзя прекрасно обойтись без Groovy в дженкинсе если, например, вам нужно собрать билд-процедуру, затрагивающую более одного билдагента.


      1. Tzimie
        14.12.2022 10:29

        Я не использую Jenkins для работы с build агентами)


    1. JuriM
      14.12.2022 12:13

      Груви скрипты выполняются на самом дженкинсе, в отличии от шел скриптов?


      1. Tzimie
        14.12.2022 14:31
        -1

        А какая разница потребителю, если account наследуется от Jenkins?