====== Pipelines ======
===== Общее =====
if (params.getOrDefault('BOOLEAN_PARAM_NAME', true))
if (params.CREDENTIAL_ID.toString().isEmpty()) {
currentBuild.result = 'ABORTED'
error("CREDENTIAL_ID is empty")
}
===== Переменные =====
:!: Примеры
pipeline {
agent any
stages {
stage("Variables") {
steps {
my_var = sh label: 'my echo', returnStdout: true, script: 'echo edede'
}
}
stage("Variables") {
steps {
script{
// хотя хз, чет не сработало
script: '''
echo "$my_var"
'''
}
}
}
}
}
==== Переменные среды ====
[[https://e.printstacktrace.blog/jenkins-pipeline-environment-variables-the-definitive-guide/|Дока]]
Глобальная переменная, доступная через **env**. Объявляется в любом месте, либо в блоке **environment** либо в **script**\\
Хранится только в формате строки, в т.ч. и булевы значения\\
Обратится можно и без "env" но если нет обычной переменной с таким же именем иначе будет путаница\\
:!: Лист всех переменных
pipeline {
agent any
stages {
stage("Env Variables") {
steps {
sh "printenv"
// можно
sh "printenv | sort"
}
}
}
}
:!: Установка переменных
Переменные среды могут быть установлены декларативно с помощью environment { }блока, императивно с помощью env.VARIABLE_NAMEили с помощью withEnv(["VARIABLE_NAME=value"]) {}блока.
pipeline {
agent any
environment {
FOO = "bar"
}
stages {
stage("Env Variables") {
environment {
NAME = "Alan"
}
steps {
echo "FOO = ${env.FOO}"
echo "NAME = ${env.NAME}"
script {
env.TEST_VARIABLE = "some test value"
}
echo "TEST_VARIABLE = ${env.TEST_VARIABLE}"
withEnv(["ANOTHER_ENV_VAR=here is some value"]) {
echo "ANOTHER_ENV_VAR = ${env.ANOTHER_ENV_VAR}"
}
}
}
}
}
:!: Переопределение переменных
* Блок withEnv(["env=value]) { }может переопределить любую переменную среды.
* Переменные, заданные с помощью environment {}блока, не могут быть переопределены с помощью императивного env.VAR = "value"присваивания.
* Императивное env.VAR = "value"присваивание может переопределить только переменные среды, созданные с помощью императивного присваивания.
pipeline {
agent any
environment {
FOO = "bar"
NAME = "Joe"
}
stages {
stage("Env Variables") {
environment {
NAME = "Alan" // overrides pipeline level NAME env variable
BUILD_NUMBER = "2" // overrides the default BUILD_NUMBER
}
steps {
echo "FOO = ${env.FOO}" // prints "FOO = bar"
echo "NAME = ${env.NAME}" // prints "NAME = Alan"
echo "BUILD_NUMBER = ${env.BUILD_NUMBER}" // prints "BUILD_NUMBER = 2"
script {
env.SOMETHING = "1" // creates env.SOMETHING variable
}
}
}
stage("Override Variables") {
steps {
script {
env.FOO = "IT DOES NOT WORK!" // it can't override env.FOO declared at the pipeline (or stage) level
env.SOMETHING = "2" // it can override env variable created imperatively
}
echo "FOO = ${env.FOO}" // prints "FOO = bar"
echo "SOMETHING = ${env.SOMETHING}" // prints "SOMETHING = 2"
withEnv(["FOO=foobar"]) { // it can override any env variable
echo "FOO = ${env.FOO}" // prints "FOO = foobar"
}
withEnv(["BUILD_NUMBER=1"]) {
echo "BUILD_NUMBER = ${env.BUILD_NUMBER}" // prints "BUILD_NUMBER = 1"
}
}
}
}
}
==== Переменные ====
Обращаться к переменным следует всегда с префиксами если они есть, например для переменных окружения это "env", для входных параметров это "params". т.к. переменная легко может подменится локальной с таким же именем, не имеющей отношения к переменной окружения или параметру\\
:!: Работа с переменными, модуль sh
Модуль **sh** по всей видимости выполняется напрямую в оболочке, поэтому ему доступны только переменные окружения среды\\
Задать переменные окружения из скрипта можно в блоке **Environment**\\
// Входящий параметр (видимо по умолчанию доступны в переменных окружения)
stage('Clone source') {
steps {
echo params.MY_BRANCH
sh label: 'repo sync', script: '''
repo init -u ssh://myrepository.ru
repo sync
if [ $MY_BRANCH != "master" ] ; then
repo forall -c git checkout $MY_BRANCH
fi
'''
}
}
// Объявление в скрипте
stage('Increment & push MGA_TAG') {
environment{
myLocalVar = getValueFromFunction()
}
steps {
script{
if (env.myLocalVar == null || env.myLocalVar.equals("")) {
currentBuild.result = 'ABORTED'
}
}
sh label: 'Push tag if it is set',
script: '''
cd $WORKSPACE/igas
repo forall -c git tag -a $myLocalVar -m $myLocalVar
repo forall -c git push origin "$myLocalVar"
'''
}
}
//
// Или вот пример
stage('Build') {
environment {
VERSION_STRING = new SimpleDateFormat("yyyy_MM_dd_HH_mm").format(new Date())
}
steps {
sh label: 'my scritp', script: '''
echo variable in other block1 - $VERSION_STRING
echo variable in other block2 - "$VERSION_STRING"
echo variable in other block3 - '$VERSION_STRING' <-- эта не раскрывается
'''
}
}
// Объявление входных параметров
def setParameters() {
properties([
parameters([
gitParameter(
name: 'MY_BRANCH',
description: 'Тег или ветка, откуда делать сборку',
branch: '',
branchFilter: '.*',
defaultValue: 'origin/master',
listSize: '10',
quickFilterEnabled: false,
requiredParameter: true,
selectedValue: 'DEFAULT',
sortMode: 'ASCENDING_SMART',
tagFilter: '[0-9]*',
type: 'PT_BRANCH_TAG'
),
string(
name: 'APP_VERSION',
description: '',
defaultValue: '',
trim: true
),
booleanParam(
name: 'RUNACTION',
description: '',
defaultValue: false
)
])
])
}
:!: multiline
// Так не работает, переменные не раскрываются
script{
echo ''' second $MY_PARAM '''
echo ''' three ${MY_PARAM} '''
echo ''' four ${params.MY_PARAM} '''
}
// Работает в тройных двойных кавычках, раскрылись все переменные
script{
echo """ second $MY_PARAM """
echo """ three ${MY_PARAM} """
echo """ four ${params.MY_PARAM} """
}
===== Дата/Время =====
* **java.time.LocalDateTime** - Дата и время без часового пояса в календарной системе ISO-8601, например 2007-12-03T10:15:30\\ неизменяемый объект даты и времени, с точностью до наносекунды, не хранит и не представляет часовой пояс\\
* **java.time.Instant** - тут что то более жесткое
* **ZoneId** - (java.time.ZoneId) больше связано с правилами преобразования между часовыми поясами\\
* **ZoneOffset** - (java.time.ZoneOffset) тут уже больше про смещение относительно UTC\\
:!: more
import java.time.LocalDateTime
// Локальная ДТ, в формате "2024-02-01T19:34:11.905900287"
def start = LocalDateTime.now()
// Прибавить минуты
def start = LocalDateTime.now().plusMinutes(360)
// Тоже текущая дата, формат такой же, но дженкинс попросил аппрув (хотя не помню с первым возможно тоже был)
import java.time.Instant
def current_instant = Instant.now()
:!: Часовые пояса
[[https://javarush.com/quests/lectures/questsyntaxpro.level16.lecture06|Неплохая статья]]\\
import java.time.LocalDateTime
import java.time.ZoneId
// ZoneId, тут есть явное определение временных зон, пример из джавы
ZoneId zone = ZoneId.of("Asia/Almaty")
ZonedDateTime time = ZonedDateTime.now(zone)
// В груви так работает (тут есть перечень зон https://csharpcoderr.com/5267/ )
def zoned_dt = LocalDateTime.now(ZoneId.of("Asia/Almaty"))
// Offset, собсна "смещение", тут в основном фиксированный какой то сдвиг времени от полученного из системы
// Так работает
def current_dt = LocalDateTime.now()
def offset_dt = LocalDateTime.now(ZoneOffset.ofHours(+6))
// Вот тоже пример, по сути тоже самое
ZoneOffset zoneOffSet = ZoneOffset.of("+02:00");
OffsetTime time = OffsetTime.now(zoneOffSet);
===== Сохранение артефактов =====
:!: Stash
Сохраняет указанные файлы для дальнейшего использования в пайплайне, вне зависимости от агентов\\
Сохраняет в виде TAR архива, поэтому большие файлы не рекомендуется передавать т.к будет проседать ЦП\\
По умолчанию чистится после каждой сборки, но есть плагины позволяющие сохранить между запусками, "preserveStashes()"\\
Можно добавить исключения на добавляемые файлы. Сохраняет указанные файлы в текущем рабочем каталоге, распаковывает сохраняя относительные пути\\
stage("first") {
agent { slave1 }
steps {
(...)
stash includes: 'terraform.tfstate, terraform.backup, .terraform/**', name: DYNAMIC_SLAVE
}
}
stage("second") {
agent { master }
steps {
cleanWs()
unstash name: DYNAMIC_SLAVE
}
}
Указывать нужно с маской, например **includes: "my_dir/*"**, при этом сохраняет всю папку
stage("first stage") {
steps {
script {
sh """
mkdir my_dir
touch my_dir/first_file_${BUILD_NUMBER}
touch my_dir/second_file_${BUILD_NUMBER}
touch my_dir/three_file_${BUILD_NUMBER}
ls -l
"""
stash includes: "my_dir/*", name: "dirr"
}
}
}
stage("second stage") {
steps {
script {
cleanWs()
sh "ls -l"
}
}
}
stage("three stage") {
steps {
script {
unstash name: "dirr"
sh "ls -l my_dir"
}
}
}
:!: archiveArtifacts
Архивирует артефакты сборки, затем они доступны на веб-странице джобы\\
По умолчанию Maven автоматически архивирует произведенные артефакты. Указанные здесь артефакты будут архивированы поверх\\
Файлы указываются так же маской\\
archiveArtifacts artifacts: 'target/*.jar'
archiveArtifacts artifacts: 'target/*.jar, target/*.war'
archiveArtifacts artifacts: '**/*.jar'
# Из примера выше
archiveArtifacts artifacts: 'my_dir/*'
===== Примеры =====
:!: Работа с grafana API
import java.time.LocalDateTime
import java.time.ZoneId
pipeline {
agent {
label 'master'
}
environment {
GRAFANA_API_TOKEN = credentials('sdcsdcdscdsc')
}
stages {
stage('make silence') {
steps {
SetParameters()
script{
//def start_time_silence = LocalDateTime.now(ZoneId.of("Asia/Almaty"))
//def end_time_silence = start_time_silence.plusMinutes(params.DURATION_MIN.toInteger())
def start_time_silence = LocalDateTime.now()
def end_time_silence = start_time_silence.plusMinutes(params.DURATION_MIN.toInteger())
def response = httpRequest acceptType: 'APPLICATION_JSON',
contentType: 'APPLICATION_JSON',
responseHandle: 'NONE',
httpMode: 'POST',
ignoreSslErrors: true,
url: 'https://url-grafana/api/alertmanager/grafana/api/v2/silences',
wrapAsMultipart: false,
authentication: 'dcdcdcdcdc',
requestBody: """{
"comment": "comment_string",
"createdBy": "user_my",
"startsAt": "${start_time_silence}",
"endsAt": "${end_time_silence}",
"matchers": [
{
"isEqual": true,
"isRegex": false,
"name": "alertname",
"value": "${ALERT_NAME}"
}
]
}"""
}
}
}
}
}
def SetParameters() {
properties([
parameters([
string(
name: 'ALERT_NAME',
description: 'Название alert-правила в grafana',
trim: true
),
string(
name: 'DURATION_MIN',
defaultValue: '60',
description: 'Продолжительность режима тишины в минутах',
trim: true
)
])
])
}
:!: Минимальный пример, скачивание репозитория
pipeline {
agent {
label 'main-node'
}
options {
quietPeriod 5
ansiColor('xterm')
timestamps()
buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', numToKeepStr: '10')
}
stages {
stage('Prepare') {
steps {
script {
cleanWs()
sh(
label: 'repo sync',
script: '''
PATH="${HOME}/.bin:${PATH}"
repo init -u ssh://username:port/myproject --depth=1
repo sync
repo forall -c git checkout "master"
'''
)
}
}
}
}
}
#
# Выбор ветки
#
stage('Prepare') {
steps {
script {
SetParameters()
cleanWs()
sh(
label: 'repo sync',
script: '''
PATH="${HOME}/.bin:${PATH}"
repo init -u ssh://username:port/project --depth=1
repo sync
if [ ${BRANCH} != "master" ] ; then
repo forall -c git checkout ${BRANCH}
fi
if [ ${GERRIT_REFSPEC} != "master" ] ; then
cd igs/smodls/i_adm && git fetch ssh://url-repo:port/myproject ${GERRIT_REFSPEC} && git checkout FETCH_HEAD
fi
'''
)
}
}
}
:!: Проверка и откат
- name: "Проверка и откат"
block:
- name: "Перезапускаем сервис"
import_tasks: 60-restart_service.yml
- name: "Делаем паузу и проверяем состояние сервиса"
ansible.builtin.shell:
cmd: "sleep 25 && systemctl is-active myservice@{{ instance_name }}.service"
rescue:
- name: "Откатываемся на бекап"
ansible.builtin.shell:
cmd: 'tar -xzvf ./backups/previous_version.tar.gz -C .'
chdir: '{{ instance.root_path }}/{{ instance_name }}'
- name: "Перезапускаем сервис, уже после отката"
import_tasks: 60-restart_service.yml
- name: "Прерываем выполнение в случае отката"
ansible.builtin.fail:
msg: "!!! Откат изменений после сбоя. Прерываем дальнейшее выполнение !!!"
:!: Выбор инстансов
pipeline {
agent {
label 'ConfigServerExt'
}
options {
quietPeriod 5
ansiColor('xterm')
timestamps()
buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', numToKeepStr: '10')
}
environment {
EXTRAS = getExtras()
}
stages {
stage('Prepare') {
steps {
script {
SetParameters()
cleanWs()
script {
currentBuild.displayName = "${params.VERSION}"
buildDescription "${params.APP_INSTANCE}"
}
sh(
label: 'repo sync',
script: '''
PATH="${HOME}/.bin:${PATH}"
repo init -u ssh://jenkins@gerrit.ru/igs-meta --depth=1
repo sync
if [ ${BRANCH} != "master" ] ; then
repo forall -c git checkout ${BRANCH}
fi
if [ ${GERRIT_REFSPEC} != "master" ] ; then
cd igs/modules/repo && git fetch ssh://gerrit.ru/proj ${GERRIT_REFSPEC} && git checkout FETCH_HEAD
fi
'''
)
}
}
}
stage('Build') {
when {
expression {
!params.SKIP_BUILD
}
}
environment {
NEXUS_CREDS = credentials('11111-1111-1111')
}
steps {
sh label: 'gradle publish', script: '''
cd $WORKSPACE/igas
./gradlew \
"-PnexusUrl=https://nexus.ru/repository" \
"-PnexusUser=${NEXUS_CREDS_USR}" \
"-PnexusPassword=${NEXUS_CREDS_PSW}" \
:my_app:publish
'''
}
}
stage('Deploy') {
steps {
dir("${WORKSPACE}/proj/pathToPlaybook") {
ansiblePlaybook(
playbook: "playbooks-deploy/my_role.yml",
extras: "${env.EXTRAS}",
colorized: true,
)
}
}
}
}
}
def getExtras() {
if ("all".equals(params.APP_INSTANCE)) {
return "-e version=${params.VERSION}"
} else {
return "-e version=${params.VERSION} -e instances=${params.APP_INSTANCE}"
}
}
def SetParameters() {
properties([
parameters([
booleanParam(
name: 'SKIP_BUILD',
description: 'Поставьте галочку и выберите версию если нужен только деплой существующей версии',
defaultValue: false
),
[
$class: 'ChoiceParameter',
choiceType: 'PT_SINGLE_SELECT',
description: 'Ветка для сборки приложения',
name: 'BRANCH',
randomName: 'choice-parameter-73664823423423',
filterable: false,
script: [
$class: 'GroovyScript',
fallbackScript: [
classpath: [],
sandbox: false,
script:
'''
return['Could not get branches list from gerrit']
'''.trim()
],
script: [
classpath: [],
sandbox: false,
script:
'''
def cmd = ['/bin/bash', '-c', 'git ls-remote --quiet --heads ssh://login@gerrit.ru/proj | grep -oP refs.* | sed "s|refs/heads|origin|g; s|master|master:selected|"']
def result = cmd.execute().text.tokenize()
return result
'''.trim()
]
]
],
[
$class: 'ChoiceParameter',
choiceType: 'PT_SINGLE_SELECT',
name: 'VERSION',
description: 'Укажите, если нужен деплой уже существующей версии',
randomName: 'choice-parameter-5631314456178618',
script: [
$class: 'GroovyScript',
script: [
classpath: [],
sandbox: false,
script: '''\
import com.cloudbees.plugins.credentials.CredentialsMatchers
import com.cloudbees.plugins.credentials.CredentialsProvider
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials
import com.cloudbees.plugins.credentials.domains.DomainRequirement
import jenkins.model.Jenkins
import hudson.security.ACL
jenkins = Jenkins.get()
def lookupSystemCredentials = {
credentialsId -> return CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
StandardUsernameCredentials.class,
jenkins,
ACL.SYSTEM,
Collections.emptyList()
),
CredentialsMatchers.withId(credentialsId)
)
}
credential = lookupSystemCredentials("111111-1111-111")
nexusLogin = credential.getUsername()
nexusPasswd = credential.getPassword().getPlainText()
def targetUrl="https://$nexusLogin:$nexusPasswd@nexus.url.ru/repository/myapp-snapshots/com/gmware/applications/myapp/maven-metadata.xml"
def sout = new StringBuilder(), serr = new StringBuilder()
def proc = "curl -X GET $targetUrl".execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
def response=sout.toString()
def metadata = new XmlParser().parseText(response)
def versions = ['latest:selected']
versions.addAll(metadata.versioning.versions.version.collect({it.text()}).reverse())
return versions
'''.stripIndent()
]
]
],
[
$class : 'ChoiceParameter',
choiceType : 'PT_MULTI_SELECT',
filterLength: 1,
filterable : false,
name : 'FM_INSTANCE',
randomName : 'choice-parameter-5631314339613980',
script : [
$class: 'GroovyScript',
script: [
classpath: [],
sandbox : false,
script : '''\
def fm_instances = []
def file1 = new File("/pathToFile/file.txt")
file1.eachLine {
fm_instances.add(it)
}
def result = ["all:selected"]
result.addAll(fm_instances)
return result'''.stripIndent()
]
]
],
string(
defaultValue: 'master',
description: 'Укажите патчсет, например, refs/changes/12/34567/1, откуда забирать плейбуки ansible',
name: 'GERRIT_REFSPEC',
trim: true
)
])
])
}
:!: Минимальный образец пайплайна
pipeline {
environment {
MY_VAR = "123"
}
agent {
node {
label 'my-super-agent'
}
}
parameters {
string description: 'my param', name: 'MY_PARAM', defaultValue: ''
}
stages {
stage("First") {
when {
expression {
MY_VAR == "123"
}
}
steps {
cleanWs()
script {
sh "echo 'this is first stage'"
}
}
}
stage("Second") {
when {
expression {
MY_VAR == "123"
}
}
steps {
script {
sh "echo 'this is second stage'"
}
}
}
}
}
:!: When
stage('first-stage') {
when {
branch 'master'
branch 'feature/*'
}
-----
when {
expression {
return env.BRANCH_NAME != 'master';
inputOptimizer == "OpenCV DNN"
}
}
-----
when {
environment name: 'NAME', value: 'this'
}
-----
steps {
echo 'run this stage - ony if the branch = master branch'
// Так же внутри степа
script {
if (env.BRANCH_NAME == "myBranch") {
echo 'triggered by myBranch'
} else {
echo 'triggered by something else'
}
}
}
}
Вариативность: "not", "allOf" и "anyOf"\\
when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
---
when { anyOf { branch 'master'; branch 'staging' } }
---
when { not { branch 'master' } }
----
// Комбинировать тоже можно
when {
branch 'production'
anyOf {
environment name: 'DEPLOY_TO', value: 'production'
environment name: 'DEPLOY_TO', value: 'staging'
}
}
---
when {
expression { BRANCH_NAME ==~ /(production|staging)/ }
anyOf {
environment name: 'DEPLOY_TO', value: 'production'
environment name: 'DEPLOY_TO', value: 'staging'
}
}
---
Еще можно задать параметры сравнения (comparator): "EQUALS", "REGEXP"\\
// Сравнивать по регулярке
when { branch pattern: "release-\\d+", comparator: "REGEXP"}
// Простое сравнение
when { branch: "release", comparator: "EQUALS"}
:!: Строки могут плохо преобразовываться в логические значения, например "0" или "False" все равно оценится как True тк не пустая строка\\
Вариативность выполнения в зависимости от переменных и параметров\\
В т.ч подходит для инициализации джобы запуском без параметров\\
pipeline {
environment {
MY_VAR = "123"
}
agent any
parameters {
string description: 'my param', name: 'MY_PARAM', defaultValue: ''
string description: 'my param', name: 'MY_PARAM2', defaultValue: ''
string description: 'my param', name: 'MY_PARAM3', defaultValue: ''
}
stages {
stage("First") {
when {
expression { params.MY_VAR }
expression { params.MY_VAR2 != "" }
expression { !params.MY_VAR2 }
expression { params.MY_PARAM == "1" }
}
steps {
cleanWs()
script {
sh "echo 'this is first stage'"
}
}
}
}
}
:!: Использование config файла
// Создание файла
configFileProvider([configFile(fileId: 'kubeconf', targetLocation: 'kubeconf.сщта')]) {
script {
}
}
// Данные в переменной
configFileProvider([configFile(fileId: 'kubeconf', variable: 'kubeconf')]) {
script {
}
}
:!: Запись в файл. Декларативный метод
writeFile file: "${MY_FILENAME}.txt", text: """
text: ${var1}
test: ${var2}
""".stripIndent()
====== Use Gradle ======
:!: Общее
:!: Скрипты справа все таки совпадают с тасками что есть в файле "build.gradle.kts" внутри проекта\\
вот только как он обновляется ? таску вроде прописал, а справа не появляется этого
// Проверка переданной переменной, через аргументы вызова, "-Pversion"
if(!project.hasProperty("version")) throw Exception("no value stop it")
// Обращаться к свойству
project.properties["buildVersion"]
// Вообще таска для ShadowJar
tasks {
"shadowJar"(com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar::class) {
mergeServiceFiles()
isZip64 = true
val serverAppName = "server_app-$serverAppVersion.jar"
archiveFileName.set(serverAppName)
doFirst {
//assert(buildVersion != "non-value") { "Can not get version by git." }
if(!project.hasProperty("version")) throw Exception("no value stop it")
}
}
}
:!: Работа из пайплайна
# Если скритп "gradlew" в папке с проектом
stage('Build jar') {
steps {
sh "bash ./gradlew shadowJar"
}
}
# Если нет, тогда
stage('Build jar') {
steps {
sh "bash ./gradlew myProject:modules:shadowJar" (хотя не точно уже)
}
}
Прерывание
if (!params.MY_VAR.contains("@")) {
currentBuild.result = 'ABORTED'
error("Параметр 'MY_VAR' указан неверно")
}