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

Что такое скрипт Apache Groovy?

Но что такое Groovy? Apache Groovy — это объектно-ориентированный язык программирования, используемый для платформы JVM. Этот динамичный язык имеет множество возможностей, источником вдохновения для которого послужил Python, Smalltalk и Ruby. Его можно использовать для оркестрации пайплайна в Jenkins, а также для связки различных языков. Таким образом, команды в вашем проекте могут участвовать в работе на разных языках.

Groovy может легко взаимодействовать с Java, причем синтаксис Java и Groovy очень похож. Если вы забыли синтаксис в процессе написания Groovy, то можете продолжать писать непосредственно на Java. Groovy также можно использовать как один из скриптовых языков для платформы Java. Скрипты Groovy могут быть вызваны в Java, что существенно сокращает время, затрачиваемое для Java-разработки.

Он также предлагает множество полезных фич, таких как поддержка DSL, замыкания и динамическая типизация. В отличие от некоторых других языков, он функционирует как дополнение, а не замена Java. Исходный код Groovy компилируется в JVM Bytecode, поэтому он может работать на любой платформе.

Зачем использовать Groovy?

Вот основные причины, по которым вам стоит рассмотреть возможность применения Groovy — распространенного варианта для создания файлов пайплайна.

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

Groovy — это Java без типов, то есть без определения основных типов данных, таких как int, float, String и т. д. По сути, это то же самое, что и в Java: определение переменной без указания типа. Groovy будет определять тип на основе значения объекта.

def str = "Hello world"
def num =0

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

Например:

0..5 означает, что включены целые числа 0,1,2,3,4,5.

0..<5 означает 0,1,2,3,5, а a..d означает a,b,c,d:

for(x in 1..5){
  println x //0,1,2,3,4,5
}

Groovy также поддерживает значения параметров по умолчанию:

def repeat(val, x=10){
  for(i in 0..<x){
    println val
  }
}

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

def arg = ["Groovy","Java","Python",”nodeJS”]
println arg.class

Метод поиска в Groovy также более гибок

println arg[1]

Groovy также позволяет добавлять или удалять коллекции из коллекций.

def no = [1,2,3,4]
def no2 = no +5 //=[1,2,3,4,5]
def no3 = no - [2,3] //=[1,4]

При добавлении элементов в коллекцию мы можем использовать следующие методы

arg.add("Ruby")
arg << "Smalltalk"
arg[5] = "C++"

4. Еще одним преимуществом Groovy являются его операторы:

Арифметические операторы Groovy, логические операторы, реляционные операторы и побитовые операторы совместимы с такими языками, как nodeJS. Оператор == в Groovy эквивалентен методам equals в Java.

String str1 = "123";
String str2 = new String("123");
if(str1 == str2){
  println(“equal");
}else{
  println("Not equal");
}

5. Методы List (списка), доступные в Groovy, являются еще одним преимуществом.

  • Add( ) Добавляет новое значение в конец этого списка.

  • Get( ) Возвращает элемент в указанной позиции в этом списке.

  • Contains( ) Возвращает true, если этот список содержит указанное значение.

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

  • Plus( ) Создает новый список из исходных элементов списка и указанного элемента.

  • Pop( ) Удаляет последний элемент из данного списка.

  • Remove( ) Удаляет элементы с указанной позиции в списке

  • Reverse( ) Создает новый список, противоположный элементам исходного списка.

  • Size( ) Получает количество элементов в этом списке.

  • Sort( ) Возвращает отсортированную копию исходного списка.

def list = [];
list = [1, 2, 3, 4];
list.add(5); //add
list.pop(); //pop

6. В любом другом объектно-ориентированном языке существуют понятия объектов и классов для представления объектно-ориентированной природы языка программирования. Groovy неявно создает методы геттеров, сеттеров и предоставляет конструкторы с аргументами.

class Person {
  String name;
  int ID;
}
class Test {
  static void main(String) {
    def emp = new emp(name: 'name’')
    println emp.getName();
    emp.setYR(2019);
    println emp.getYR(); // 2019
  }
}

7. Обработка исключений  в Groovy такая же, как и в Java, с использованием try, catch для перехвата исключений.

Try{
.....
}catch(Exception1 exp){
.....
}finally{
......
}

В Groovy есть много продвинутых тем (работа с JSON, методы, карты и т.д. и т.п.), с которыми можно ознакомиться в официальной документации Apache.

Groovy также сочетает в себе особенности Python, Ruby и других скриптовых языков. В его грамматике много синтаксического сахара. При разработке на Java необходимо написать много кода, который в Groovy можно опустить, как видно из предыдущих сниппетов.

В заключение: ключевое преимущество Groovy заключается в том, что вы можете написать одну и ту же функцию с меньшим количеством кода. В моем понимании это означает более краткий и осмысленный код по сравнению с Java.

Каковы недостатки использования Groovy?

Некоторые скажут, что более простая инфраструктура в виде кода с декларативными файлами, которую не всегда можно представить с помощью Groovy, является лучшим вариантом для DevOps в долгосрочной перспективе. Более того, Apache Groovy поначалу вообще может показаться языком для хакеров.

Однако на практике я видел, как установка Groovy сплотила команды в организациях, которые раньше не стремились к DevOps. Это позволило им работать независимо, одновременно увеличивая количество общих библиотек.

Для более детального изучения Groovy обратитесь к документации.

Jenkins Pipeline в Jenkins File

Давайте поговорим о подходе Jenkins Pipeline в Jenkins и правильном синтаксисе пайплайна. Благодаря встроенной в Jenkins поддержке Groovy, а также богатому справочнику библиотеки pipeline Step, вы можете реализовать сложный процесс сборки и релиза, написав кастомные скрипты пайплайна.

Jenkins Pipeline может быть создан следующими способами:

  • Через классический пользовательский интерфейс (UI) / Blue Ocean - вы можете напрямую ввести базовый Pipeline в Jenkins, используя классический UI. (Или) используя Blue Ocean UI - вы можете применить Blue Ocean UI для написания Pipeline’s Jenkinsfile и отправки его в систему контроля исходного кода [здесь более подробная информация о Blue Ocean].

  • В SCM - Jenkinsfile может быть написан вручную и отправлен в репозиторий контроля исходного кода проекта.

Вместо того, чтобы полагаться на создание произвольных джобов их настройку, Jenkins пайплайн имеет набор инструкций для выполнения джоба Jenkins.

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

Анатомия Jenkins File

В первой строке shebang (#!) определяет файл как скрипт на языке Groovy:

#!/usr/bin/env groovy

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

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

Тот же самый выше написанный пример (декларативный пайплайн) может быть написан и в коде скриптового пайплайна.

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

Скрипты Groovy не всегда подходят для всех пользователей, поэтому с помощью Jenkins был создан декларативный пайплайн. Синтаксис декларативного пайплайна более строгий. В нем необходимо использовать предопределенную структуру DSL Jenkins, которая обеспечивает более простой и ассертивный синтаксис для написания пайплайнов Jenkins.

// Jenkinsfile (Scripted Pipeline)
node { // node/agent
  stage('Stage 1') {
    echo 'Hello World' // echo Hello World
  }
}

Node/agent (узел/агент)

Это определяет, где и на какой машине выполнять код.

pipeline {
  node any
}

или

pipeline {
  agent { node { label 'labelName' } }
}

Секция должна быть определена на верхнем уровне внутри блока пайплайна, но использование на уровне stage (этап) необязательно.

Для контекста: В Jenkins есть понятие мастер- и рабочих узлов, т.е. ведомых (slave).

Зачем нам нужно несколько узлов

1. Чтобы распределить рабочую нагрузку на Jenkins

2. У нас есть несколько узлов для разных типов проектов.

Например, предположим, что у нас есть два разных типа проектов:

  • Java

  • iOS

Для сборки Java-джоба нам нужно использовать Java Build Machine / Node.

Аналогично для сборки iOS нам нужен MAC Slave / Machine / Windows.

Stage 

В нашей сборке присутствует несколько этапов (stages), в каждом из которых есть свои шаги (steps) и команды. При выполнении большей части работы в Jenkins Pipeline она содержит последовательность из одного или нескольких этапов. Рекомендуется, чтобы этапы содержали хотя бы одну директиву stage для соединения различных процессов доставки, таких как сборка, тестирование и деплой.

stages {
  stage('Build') {
    steps {
      sh 'echo "This is my first step"'
    }
  }
  stage('Test') {
    steps
      sh 'echo "This is my Test step"'
    }
  }
  stage('Deploy') {
    steps {
      sh 'echo "This is my Deploy step"'
    }
  }
}

Parallel Stages

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

stage("Parallel") {
  steps {
    parallel (
      "Taskone" : {
        //do some stuff
      },
      "Tasktwo" : {
        // Do some other stuff in parallel
      }
    )
  }
}

Steps 

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

Скриптовый пайплайн конкретно не описывает steps как часть своего синтаксиса. Однако в справочнике пайплайна по steps подробно описаны шаги, задействованные в пайплайне и его плагинах.

Jenkins пайплайн позволяет компоновать многочисленные steps, что помогает создать любой процесс автоматизации. Считайте, что step - это одна команда, которая выполняет отдельное действие. При успешном выполнении step происходит переход к следующему. Если step не выполнится правильно, пайплайн не будет работать.

Когда все steps в пайплайне успешно пройдены, он считается полностью выполненным.

stages {
  stage('Build') {
    steps {
      sh 'echo "Step 1"'
      sh 'echo "Step 2"'
      sh 'echo "Step 3"'
    }
  }
}

Таймаут и повторные попытки (Timeout and retries) 

pipeline {
  agent any
  stages {
    stage('Dev Deployment') {
      steps {
        retry(x) { // It Retries x number of times mentioned until its successful
          sh './dev-deploy.sh'
        }
        timeout(time: x, unit: 'MINUTES') { // Time out option will make the step wait for x mins to execute if it takes more than that it will fail
          sh './readiness-check.sh'
        }
      }
    }
  }
}

Триггеры

Директива triggers определяет, каким образом Pipeline автоматизирует триггеры. Согласно документации Jenkins, в настоящее время доступны следующие триггеры: cron, pollSCM и upstream.

Cron

Принимает строку в стиле cron для определения регулярного интервала, который запускает пайплайн, например:

triggers { cron('H */2 * * 1-3') } 

pollSCM

Принимает строку в стиле cron и определяет Jenkins для проверки изменений в источнике SCM (регулярный интервал). Если есть новые изменения, то пайплайн будет перезапущен.

pipeline {
  agent any
  triggers {
    cron('H */2 * * 1-3')
  }
  stages {
    stage('Hello World') {
      steps {
        echo 'Hello World'
      }
    }
  }
}

When

Команда when позволяет пайплайну определить, следует ли выполнять эту фазу, исходя из заданных условий. when должна содержать как минимум одно условие. Более сложные условные структуры пайплайна могут быть построены с использованием вложенных условий: not, allOf или anyOf. Если директива when содержит несколько условий, то для выполнения этапа все подусловия должны возвращать true. Вложенные условия могут располагаться на любой глубине.

В основном это используется для пропуска или выполнения step/stage (шага/этапа)

when { anyOf { branch 'develop'; branch 'test' } }

  stage('Sample Deploy') {
    when {
      branch 'production'
      anyOf {
        environment name: 'DEPLOY_TO', value: 'prod'
        environment name: 'DEPLOY_TO', value: 'test'
      }
    }
    steps {
      echo 'Deploying application'
    }
  }

Секции post-build (после сборки) 

Определите операцию в конце пайплайна или выполнения stage. Вам может понадобиться запустить шаги очистки или выполнить некоторые действия в зависимости от результатов работы пайплайна. Блок пост-условия поддерживает следующие компоненты: always, change, failure, success, unstable и aborted. Эти блоки позволяют выполнять steps в конце работы пайплайна или выполнения stage в зависимости от состояния пайплайна.

pipeline {
  agent any
  stages {
    stage('Test') {
      steps {
        sh 'echo "Fail!"; exit 1'
      }
    }
  }
  post {
    always {
      echo 'always runs regardless of the completion status of the Pipeline run'
    }
    success {
      echo 'step will run only if the build is successful'
    }
    failure {
      echo 'only when the Pipeline is currently in a "failed" state run, usually expressed in the Web UI with the red indicator.'
    }
    unstable {
      echo 'current Pipeline has "unstable" state, usually by a failed test, code violations and other causes, in order to run. Usually represented in a web UI with a yellow indication.'
    }
    changed {
      echo 'can only be run if the current Pipeline is running at a different state than the previously completed Pipeline'
    }
  }
}

Переменные окружения

Переменные окружения могут быть заданы глобально, как в примере ниже, или для каждого stage. Как следствие, установка переменных окружения для каждого stage означает, что они будут применяться только к тому этапу, на котором они определены.

pipeline {
  agent any

  environment {
    USE_JDK = 'true'
    JDK ='c:/Java/jdk1.8'
  }

  stages {
    stage('Build') {
      steps {
        sh 'printenv'
      }
    }
  }
}

Обработка исключений в Jenkins Pipeline

Для обработки исключений теперь можно использовать блок try catch, который позволяет выполнить step и перехватить исключения.

node {
  stage('SampleTryCatch') {
    try {
      sh 'exit 1'
    }
    catch (exc) {
      echo 'Something didn't work and got some exceptions'
      throw
    }
  }
}

Запись тестов и артефактов

Jenkins может записывать и агрегировать результаты тестов, если ваш test runner может выводить файлы их результатов. Jenkins обычно поставляется в комплекте со JUnit step. Но если ваши инструменты тестирования не могут выводить XML-отчеты в стиле JUnit, то существуют дополнительные плагины, позволяющие обрабатывать практически любой широко используемый формат тестовых отчетов.

Для сбора результатов тестирования и артефактов мы будем использовать секцию post.

pipeline {
  agent any
  stages {
    stage('Test') {
      steps {
        sh './gradlew test'
      }
    }
  }
  post {
    always {
      junit 'build/reports/**/*.xml'    }  } }

Отправка уведомлений

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

Уведомление по электронной почте

post {
  failure {
    mail to: 'team@test.com',
      subject: "Pipeline has failed: ${currentBuild.fullDisplayName}",
      body: "Error in ${env.BUILD_URL}"
  }
}

Уведомление в Slack

post {
  success {
    slackSend channel: '#Devops-team',
      color: 'green',
      message: "The pipeline ${currentBuild.fullDisplayName} completed successfully."
}

Jenkins Pipeline Groovy: Общие библиотеки

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

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

Общая библиотека определяется именем, способом получения исходного кода (например, с помощью SCM), и, по желанию, версией по умолчанию. Имя следует выбирать в виде короткого идентификатора, поскольку оно будет использоваться в скриптах.

Рассмотрим следующую структуру папок: (прямо из документации jenkins)

(root)
+- src # Groovy source files
|
+- vars
|
+- test # unit test files
|
+- resources # resource files
|

Каталог src должен выглядеть как стандартная структура исходного каталога Java. Он добавляется в classpath при выполнении пайплайнов.

В каталоге vars размещаются файлы скриптов, которые отображаются как переменная в пайплайнах. Имя файла - это имя переменной в пайплайне. Для более подробного изучения ознакомьтесь с определением общей библиотеки в Jenkins. Существует больше опций и других доступных сниппетов, которые мы можем попробовать создать и запустить в системе Jenkins с помощью файла Jenkins.

Каталог resources, в котором можно управлять всеми не-Groovy файлами/вспомогательными скриптами, необходимыми для пайплайна. По приведенной выше структуре показан пример. JSON шаблон хранится в папке resources и может быть вызван в общей библиотеке с помощью функции libraryResource.

Вы можете использовать или вызывать созданную вами библиотеку с помощью

@Library('my-shared-library') _

Пример написания функций в общей библиотеке Groovy

// src/org/foo/Zot.groovy
#!/usr/bin/groovy
package org.foo

def myFunction(repo) {
  sh "echo this is my function in Shared Library"
}

return this

Определение глобальных переменных общей библиотеки

В каталоге vars размещаются скрипты, определяющие глобальные переменные, доступные из пайплайна.

+- vars
| +- myVariables.groovy

Внутри, скрипты в каталоге vars инстанцируются по требованию как синглтоны. Это позволяет для удобства определить несколько методов в одном .Groovy-файле.

Доступ к steps

Классы общих библиотек Groovy не могут напрямую вызывать steps, такие как sh или git. В явном виде мы можем передать в метод класса определенные глобальные переменные env (содержит все текущие переменные окружения) и steps (содержит все стандартные steps конвейера). Однако они могут реализовывать методы, выходящие за рамки вложенного класса, которые, в свою очередь, вызывают steps пайплайна.

Например:

// src/org/mycode/mors.groovy
#!/usr/bin/groovy
package org.mycode

def checkOutFrom(repo) {
  git url: "git@github.com:jenkinsci/${repo}"
}
return this

Которые затем могут быть вызваны из скриптового пайплайна:

def z = new org.mycode.mors()
z.checkOutFrom(repo)

Полный пример структуры Groovy-файла Jenkins Groovy Script выглядит следующим образом

@Library(['github.com/shared-library']) _

pipeline {
  agent { label 'labelname' }

  options {
    timeout(time: 60, unit: 'MINUTES')
    timestamps()
    buildDiscarder(logRotator(numToKeepStr: '5'))
  }

  stages {

    stage('Clean Workspace') {
      steps {
        // Clean the workspace
      }
    }

    stage('Checkout') {
      steps {
        // clone your project from Git/SVN etc
      }
    }

    stage('Build') {
      steps {
        // build, build stages can be made in parallel aswell
        // build stage can call other stages
        // can trigger other jenkins pipelines and copy artifact from that pipeline
      }
    }

    stage('Test') {
      steps {
        // Test (Unit test / Automation test(Selenium/Robot framework) / etc.)
      }
    }

    stage('Code Analysis') {
      steps {
        // Static Code analysis (Coverity/ SonarQube /openvas/Nessus etc.)
      }
    }

    stage('Generate Release Notes') {
      steps {
        // Release note generation .
      }
    }

    stage('Tagging') {
      steps {
        // Tagging specific version number
      }
    }

    stage('Release') {
      steps {
        // release specific versions(Snapshot / release / etc.)
      }
    }

    stage('Deploy') {
      steps {
        // Deploy to cloud providers /local drives /artifactory etc.
        // Deploy to Deploy/prod /test/ etc
      }
    }
  }

  post {
    success {
      echo "SUCCESS"
    }
    failure {
      echo "FAILURE"
    }
    changed {
      echo "Status Changed: [From: $currentBuild.previousBuild.result, To: $currentBuild.result]"
    }
    always {
      script {
        def result = currentBuild.result
        if (result == null) {
          result = "SUCCESS"
        }
      }
    }
  }
}

Для получения дополнительной информации по Jenkins-Groovy, пожалуйста, обратитесь к репо Dennyzhang на GitHub.

Надеюсь, это помогло вам в освоении Groovy!


В заключение приглашаем на открытый урок «Shared Libraries в Jenkins», который пройдет уже завтра, 4 июля. На этом занятии посмотрим, как расширять пайплайны в Jenkins с помощью внешних библиотек и научимся их писать. Занятие будет полезно DevOps-инженерам и Java-разработчикам, которые хотят научиться создавать и настраивать сборки Java Backend проектов.

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