Skip to main content

Способы передачи параметров в 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"
}
}