Способы передачи параметров в Pipeline
Как и положено любой хорошей программе, код пайплайна не должен содержать в себе конкретных значений, привязывающих его к конкретной среде развёртывания. Эти значения должны быть вынесены в параметры пайплайна, причём удобным для обслуживания образом - параметров может быть много и указывать все из них при каждой отгрузке, или же дублировать глобальные параметры в десятках пайплайнов одной среды будет нехорошо.
Можно выделить следующие типы параметров:
- параметры среды развёртывания, общие для всех сервисов, например адрес СУБД
- параметры отдельного сервиса в рамках среды, например имя базы данных этого сервиса
- параметры отгрузки, например ветка, которую нужно отгрузить
Параметры среды развёртывания
Эта функция реализована как комбинация из нескольких других.
Первая - возможность создавать т.н. Config Files, т.е. прикреплять к пайплайну некоторый текст, который можно будет использовать в пайплайне. Мы используем Config File типа Custom File, чтобы записать в него список переменных в формате, очень похожем на формат .env файлов.
Вторая - плагин Folders. Позволяет группировать пайплайны в "папки", и что самое главное - создавать Config Files на уровне папки, которые будут доступны во всех дочерних пайплайнах.
Третья - возможность примонтировать Config File как файл в рабочей директории пайплайна. Функция configFileProvider позволяет это сделать, достаточно указать ей id файла конфигурации и путь, куда его надо положить.
Четвёртая - плагин Pipeline Utility Steps,
а точнее даже одну его функцию - readProperties
которая читает указанный файл и возвращает хэшмап в котором ключ это название переменной, а значение - значение переменной.
По счастливому стечению обстоятельств, формат java properties очень похож на тот который нам нужен.
Совмещая всё вместе получаем вот такую функцию, которая читает файл конфигурации и перекладывает найденные в нём значения в глобальный список переменных vars
.
def loadConfigFile(configFileCode) {
try {
configFileProvider([configFile(fileId: configFileCode, targetLocation: "./${configFileCode}.txt")]) {
def propsFromFile = readProperties(file: "./${configFileCode}.txt")
for (prop in propsFromFile) {
vars."${prop.key}" = "${prop.value}"
}
}
} catch (Exception e) {}
}
Далее нам остаётся только вызывать загрузку переменных их Config Files которые ожидает пайлайн, после чего можно получать значения из
объекта vars
.
pipeline {
step {
loadConfigFile('deployment-params')
// ...
sh "kubectl -n ${vars.NAMESPACE} apply -f deployment.yaml"
}
}
Важно отметить, что чтение переменных их файла доступно только внутри секций stage и step, т.е. в контексте агента. Загрузить переменные выше, чтобы, например, таким образом задать параметры самого пайплайна, вы не можете, т.к. этот код выполняется в контексте самого дженкинса, где монтирование файлов невозможно.
Параметры сервиса
Эта функция полагается на предыдующую - пайплайн последовательно загружает два файла конфигурации, один для среды, второй для севриса.
Все значения попадают в один объект vars
, что позволяет не только добавлять значения специфичные для сервиса, но и переопределять глобальные параметры.
pipeline {
step {
loadConfigFile('folder-env')
loadConfigFile('service-env')
// ...
}
}
Параметры отгрузки
Эта функциональность есть в Jenkins изначально - Jenkins Parameters.
Использовать такие параметры очень просто - нужно в пайплайне описать какие параметры вы хотите вводить при отгрузке,
и далее, в коде пайплайна можно обращаться к объекту params
чтобы получить введённые значения.
pipeline {
parameters {
string(name: 'BRANCH', defaultValue: 'master', description: 'Git branch for deploy')
booleanParam(name: 'SKIP_TESTING', defaultValue: false, description: 'Do not run any tests')
}
stages {
//...
if (params.SKIP_TESTING) {
// ...
}
}
}
После того как вы разместите подобный код в пайплайне, при нажатии на кнопку "Собрать сейчас", вы сначала увидите страницу, на которой вам будет предложено задать значения параметров.
Здесь есть, однако, некоторое количество подводных камней.
Первый: если пайплайн располагается в самом git репозитории, то первый запуск пайплайна, в том числе первый запуск для каждой ветки в Multibranch Pipeline, не будет показывать страницу заполнения параметров. Jenkins использует закешированную с прошлого раза информацию о наличии и списке параметров пайплайна, которой нет при первом запуске. Это нужно иметь в виду, т.к. значений по умолчанию тоже не будет. Необходимо всегда проверять значение парметра на null.
Второй: вы не можете использовать значения их Config Files при определениеи параметров, т.к. этот блок кода выполняется в контексте дженкинса.
Jenkins Env Variables для папки
Не хардкодить значения параметров отгрузки пайплайна можно если использовать Jenkins Env Variables.
Они задаются в настройках дженкиса и доступны как env переменные во всех пайплайнах вообще.
Использование этих переменных разрешено в контексте дженкинса, поэтому их можно использовать при определении блока parameters
.
Чтобы сделать код пайплайна более гибким, используется следующий подход - значения берутся не по полному имени переменной, а по некоторой строке, которая автоматически дополняется названием текущей папки. Это позволяет получать в пайплайне разные значения, когда он выполняется в разных папках.
def getNamespacedEnv(name) {
def pathParts = env["JOB_NAME"].split("/") as List
while (pathParts.size() > 0) {
def path = pathParts.join("_")
def key = "PROP_${path}_${name}"
def value = env[key]
if (value) {
return value
} else {
pathParts.remove(pathParts.size() - 1)
}
}
return null
}
Код, чуть более сложный, т.к. он дополнительно выполняет поиск переменной восходя по папкам вверх.
Т.е. если у нас есть вложенные папки, например staging/catalog/indexer и мы хотим получать переменную "DOMAIN",
то сначала будет попытка получить значение переменной PROP_staging_catalog_indexer_DOMAIN
,
если такой переменной нет, то PROP_staging_catalog_DOMAIN
, далее PROP_staging_DOMAIN
,
ну и если в этом случае значение не найдно, то возвращается null.
Это позволяет переопределять переменные для конкретных подпапок.
Единый интерфейс для получения параметров
Выше уже упоминался объект vars. Однако обращаться к нему напрямую не рекомендуется.
Значения параметров нужно получать вызывая метод get(name)
.
Это не просто глупый геттер. В нём происходит поиск значения во всех возможных местах:
- env переменные для папки
- параметры отгрузки
- значения из Config Files
Дополнительные возможности
Функция checkDefined(requiredKeys)
принимает список имён переменных, и проверяет что все они есть в vars
(заданы через Config Files).
Если хотя бы одной переменной нет, то вызывается fail()
.
Это предотвращает выполнение пайплайна если не была произведена базовая настройка.
Функция getAsList(name)
берёт значение переменной через get(name)
и разбивает его по символу "," на массив значений.
Организация кода
Все вышеописанные функции выделены в т.н. shared library, что позволяет не дублировать их в каждом пайплайне, а просто подключить как библиотеку.
@Library('ru.greensight@v1.0.2')_
import ru.greensight.Options
def options = new Options(script:this)
pipeline {
step {
options.loadConfigFile('folder-env')
options.loadConfigFile('service-env')
// ...
}
step {
// ...
sh "kubectl -n ${options.get('NAMESPACE')} apply -f deployment.yaml"
}
}