Написание общих скриптов для CircleCI Orbs

Эта статья была первоначально опубликована в моем блоге здесь.

Проблема

CircleCI Orbs предоставляет директиву include, которая может быть использована для включения скриптов в командные конфигурации орбов. К сожалению, эта директива не предоставляет очевидного способа ссылаться на другие, общие скрипты внутри включенного скрипта.

На самом деле, оказывается, что директива include — это просто макрос, который побуждает CircleCI заменить директиву include на текстовое тело ссылаемого скрипта. Это означает, что другие скрипты, включенные с помощью директивы include, не могут ссылаться прямым образом, что затрудняет создание общих скриптовых модулей.

Мотивация

В последнее время я централизую инструментарий DevOps для своих проектов в пользовательской сфере CircleCI Orb. Сфера предназначена для абстрагирования от типичных действий над стандартизированными репозиториями, которые я планирую создать для текущих и будущих проектов.

Я работаю над генератором монорепо, используя плагин Nx с пользовательской предустановкой рабочего пространства. Для отчетов о тестовом покрытии с интеграцией Codecov я хотел бы динамически загружать по одному отчету о покрытии для каждого пакета в моем монорепо и присваивать ему соответствующий флаг Codecov.

К сожалению, официальный Codecov CircleCI Orb не достаточно мощный, чтобы самостоятельно справиться с этим сценарием, поэтому в итоге я взял несколько их скриптов, чтобы создать что-то, что понимает конфигурацию рабочего пространства Nx и автоматически выполняет выгрузку покрытия, сегментированного по пакетам.

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

Решение

К счастью, директива include может использоваться в любом месте конфигурации орба, а не только в свойстве command в step.

Я даже могу использовать директиву include, чтобы предоставить тело моего общего скрипта в качестве переменной окружения для какой-либо другой команды.

Не рекомендуется: eval тело разделяемого скрипта.

Моим первым способом решения этой проблемы было использование include для предоставления тела моего разделяемого скрипта в качестве переменной окружения, а затем eval его содержимого.

Очевидно, что это не самый безопасный вариант, и использование eval таким образом немного попахивает. Тем не менее, это решило проблему.

src/commands/upload-monorepo-coverage.yml:

steps:
  - run:
      name: Upload Monorepo Coverage Results
      command: << include(scripts/uploadMonorepoCoverageResults.sh) >>
      environment:
        PARSE_NX_PROJECTS_SCRIPT: << include(scripts/parseNxProjects.sh >>
Войти в полноэкранный режим Выход из полноэкранного режима

src/scripts/parseNxProjects.sh:

#! /usr/bin/env bash

# A common function I'd like to use in another file
parse_nx_projects() {
  # ...
}
Войти в полноэкранный режим Выход из полноэкранного режима

src/scripts/uploadMonorepoCoverageResults.sh:

#! /usr/bin/env bash

eval "$PARSE_NX_PROJECTS_SCRIPT"

# This shared function from the `parseNxProjects.sh` is now callable
parse_nx_projects

# ...
Войти в полноэкранный режим Выход из полноэкранного режима

Немного лучше: запись содержимого разделяемого скрипта на диск

Использование eval в приведенном выше виде оскорбляет мою чувствительность. Хотя я не уверен, что это действительно более безопасно, в итоге я выбрал другой подход, вдохновленный подходом eval. То есть, используя директиву include, я записываю содержимое разделяемого сценария в предсказуемое место на диске, а затем предоставляю путь к разделяемому сценарию всем сценариям, которые должны его использовать.

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

Для этого я написал специальную команду, которую назвал write-shared-script.

Вот как выглядит исходный текст команды:

description: >
  This command writes shared scripts to disk so they can be consumed by other scripts

parameters:
  script-dir:
    type: string
    default: ~/@chiubaka/circleci-orb/scripts
    description: Path to the directory to write shared scripts to.
  script-name:
    type: string
    description: Name of the script to write
  script:
    type: string
    description: The script to write. Should be included here using the include directive.

steps:
  - run:
      name: Write << parameters.script-name >> to disk
      command: << include(scripts/writeSharedScript.sh) >>
      environment:
        SCRIPT: << parameters.script >>
        SCRIPT_DIR: << parameters.script-dir >>
        SCRIPT_NAME: << parameters.script-name >>
Войти в полноэкранный режим Выйти из полноэкранного режима

А вот writeSharedScript.sh:

#! /usr/bin/env bash

SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_NAME"

mkdir -p "$SCRIPT_DIR"
echo "$SCRIPT" > "$SCRIPT_PATH"
chmod +x "$SCRIPT_PATH"
Войти в полноэкранный режим Выйти из полноэкранного режима

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

  - write-shared-script:
      script-name: parseNxProjects.sh
      script: << include(scripts/parseNxProjects.sh) >>
  - run:
      name: Upload Monorepo Coverage Results
      command: << include(scripts/uploadMonorepoCoverageResults.sh) >>
      environment:
        PARSE_NX_PROJECTS_SCRIPT: ~/@chiubaka/circleci-orb/scripts/parseNxProjects.sh
Войти в полноэкранный режим Выйти из полноэкранного режима

Наконец, сценарий uploadMonorepoCoverageResults.sh теперь выглядит следующим образом:

#! /usr/bin/env bash

source "$PARSE_NX_PROJECTS_SCRIPT"

# This shared function from the `parseNxProjects.sh` is now callable
parse_nx_projects

# ...
Войти в полноэкранный режим Выход из полноэкранного режима

Соображения безопасности

Возможно, в контексте CircleCI разница между этими двумя подходами не очень велика. Реалистично, вход в eval здесь всегда находится под моим контролем, пока CircleCI работает нормально. Если бы в CircleCI была брешь в безопасности, позволяющая злоумышленнику контролировать ввод этого оператора eval, то здесь возникает более серьезная проблема, связанная с совершенно другой моделью угрозы.

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

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

Оцените статью
devanswers.ru
Добавить комментарий