GitHub Actions: переменные, секреты, артефакты

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

Перед разбором новой темы, освежим память и вспомним, что такое GitHub Actions и какие сущности там есть. GitHub Actions — это сервис от GitHub для автоматизации различных CI/CD-процессов: начиная от тестирования, заканчивая деплоем кода на продакшен серверы. Actions (экшены) — это модули автоматизации различных сценариев, таких как загрузка репозитория на GitHub VM или подключение по SSH.

В GitHub Actions есть три базовые сущности, с которыми мы работаем:

  1. Workflow — это настраиваемый сценарий выполнения потока задач( jobs/джобы);
  2. Jobs (джобы) — это логическое объединение шагов сценария, на котором задается среда выполнения задач;
  3. Steps (шаги) — конкретные действия выполнения сценария.

Каждый из уровней абстракции имеет свои возможности работы с переменными.

Работа с переменными в gitHub Actions

На самом деле переменные могут быть заданы не только на уровне workflow, джобов и шагов, но и на уровне репозитория и организации. Организация — это административная сущность, которая может управлять репозиториями. С помощью организации можно создавать переменные, доступные для всех репозиториев.

Уровень репозитория позволяет создавать переменные доступные для всех workflow в рамках родительского репозитория. Вот схема иерархии переменных с указателем контекста их вызова:

Переменные уровня GitHub содержат данные о репозитории, владельце, workflow и так далее. Они задаются автоматически и их можно только читать. Остальные переменные можно задавать. Наиболее часто придётся работать с переменными уровня workflow. C них и начнём.

Работа с переменными уровня workflow

Переменные уровня workflow задаются вначале файла директивой env: [variable_name]: [variable_value]. Эти переменные доступны из любого подуровня workflow. Такие переменные можно использовать для хранения путей или иной текстовой информации, которую можно переиспользовать множество раз в каждой джобе.[/variable_value][/variable_name]

Вот яркий пример использования переменных уровня workflow для управления путями и именами создаваемых файлов:

name: Test env
on: workflow_dispatch

env:
  work_path: ./project
  project_name: ./pyoops.com
  project_files_set_1: main.py db_python.sql readme.md
  project_files_set_2: numerics.js readme.md base.json

jobs:
  create_workdir_and_files_on_1_vm:
    runs-on: ubuntu-latest
    steps:
      - name: Make work dir and create files
        run: | 
          mkdir ${{ env.work_path }}
          cd ${{ env.work_path }}
          mkdir ${{ env.project_name }}
          cd ${{ env.project_name }}
          touch ${{ env.project_files_set_1 }}
          ls -la

  create_workdir_and_files_on_2_vm:
    runs-on: ubuntu-latest
    steps:
      - name: Make work dir and create files
        run: | 
          mkdir ${{ env.work_path }}
          cd ${{ env.work_path }}
          mkdir ${{ env.project_name }}
          cd ${{ env.project_name }}
          touch ${{ env.project_files_set_2 }}
          ls -la

В данном примере мы создали в начале workflow раздел env с четырьмя переменными:

env:
  work_path: ./project
  project_name: ./pyoops.com
  project_files_set_1: main.py db_python.sql readme.md
  project_files_set_2: numerics.js readme.md base.json

Далее мы обращались к этим переменным из разных джобов с помощью контекста env: ${{ env.project_name }}. Важно понимать, что каждая джоба запускается на отдельной виртуальной машине, соответственно вы не можете использовать переменные созданные в одной джобе в другой.

С переменными уровня workflow — всё, теперь опустимся на один логический уровень ниже и посмотрим как работают переменные на уровне джобы.

Работа с переменными уровня jobs

Специфика в работа с переменными на уровне джобы заключается в том, что эти переменные доступны только в рамках джобы, где были заданы. Задаются переменные также с помощью директивы env:, с указанием имени переменной и значением.

Вот пример задания переменных в джобе:

name: Test env
on: workflow_dispatch
env:
  work_path: ./project
  project_name: ./pyoops.com
  project_files_set_1: main.py db_python.sql readme.md

jobs:
  create_workdir_and_files_on_1_vm:
    runs-on: ubuntu-latest
    env:
      my_project_name: pseudoproject
    steps:
      - name: Make work dir and create files
        run: | 
          mkdir ${{ env.work_path }}
          cd ${{ env.work_path }}
          mkdir ${{ env.my_project_name }}
          cd $my_project_name
          touch ${{ env.project_files_set_1 }}

Если более внимательно рассмотреть блок run, где мы построчно вызываем команды — можно заметить, что к переменной my_project_name мы обратились дважды, но по разному: в первом случае через контекст env — mkdir ${{ env.my_project_name }}, а во втором случае — через вызов переменной Linux-окружения — cd $my_project_name.

Технически конструкция установки переменных вида env: my_project_name: pseudoproject равнозначна ручному назначению переменных из Linux-терминала командой export my_project_name=pseudoproject. То есть GitHub Actions при создании виртуальной машины прогружает в виртуальное окружение Linux переменные заданные в workflow, поэтому мы можем обращаться к переменным как через контекст env, так и просто по имени, также как в терминале Linux.

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

Переменные окружения джобы хранится в специальном контейнере, который обозначается как переменная $GITHUB_ENV. В неё с помощью нехитрой команды echo [variable_name]=[variable.value] > $GITHUB_ENV можно записать пользовательскую переменную с каким-то значением, после её можно вызвать через контекст env или через $.

Вот пример, где мы узнаём внутренний IP адрес виртуальной машины GitHub, передаем его в контейнер $GITHUB_ENV и читаем потом это значение двумя способами:

name: Test env
on: workflow_dispatch

jobs:
  crete_var_env:
    runs-on: ubuntu-latest
    steps:
      - name: create var
        run:
          echo "SERVER_IP='$(hostname -I | awk '{print $1}')'" >> $GITHub_ENV
      - name: read var
        run: |
          echo "Calling var from env using env context: server ip is ${{ env.SERVER_IP }}"
          echo "Calling var from env: server ip is $SERVER_IP"

Вот конструкция, которая создает переменную SERVER_IP, присваивает ей значение и записывает в контейнер $GITHUB_ENV:

run:
    echo "SERVER_IP='$(hostname -I | awk '{print $1}')'" >> $GITHub_ENV

Далее мы обращаемся к переменной SERVER_IP двумя способами: через контекст env — ${{ env.SERVER_IP }} и без него — $SERVER_IP. В обоих случаях мы получаем одинаковый результат, а в выводе консоли выполнения workflow, мы видим, что запись с использованием команды echo равнозначна созданию блока env:

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

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

Работа с секретами GitHub

Секреты GitHub — это переменные, которые могут содержать такую, секретную информацию как API-токены, SHH-ключи, SSH-пароли и другую информацию, которую нужно передать в качестве аргумента в workflow. Фишка секретов в том, что их невозможно вывести на печать в терминале или как-то раскрыть. К их созданию есть доступ только у владельца репозитория или организации. К тому же отображаются данные внутри секрета только один раз, при его создании, потом невозможно будет узнать, что в нём записано.

Вот как можно быстро создать секрет:

Обратиться к секрету из workflow можно через контекст secrets: ${{ secrets.[secret_name] }}. Вот пример, как можно передать SSH-ключ в Python для создания VPS (более подробнее об этом будет в следующей статье):

- name: "Run the VPS create script"
    run: |
    pip install requests
    python3 ./1cloud_create_vps.py ${{ secrets.API_KEY }}

Обратиться к секрету из workflow можно через контекст secrets: ${{ secrets.[secret_name] }}. Вот пример, как можно передать SSH-ключ в Python для создания VPS (более подробнее об этом будет в следующей статье):

- name: "Run the VPS create script"
    run: |
      pip install requests
      python3 ./1cloud_create_vps.py ${{ secrets.API_KEY }}

А вот другой яркий пример использования секретов — подключения по SSH к VPS и скачивания на сервер репозитория:

- name: "Upload repo to VPS"
    uses: appleboy/ssh-action@master
    with:
      host: ${{ env.VPS_IP }}
      username: ${{ secrets.SSH_USER }}
      key: ${{ secrets.SSH_KEY }}
      script: |
        mkdir ${{ env.PROJECT_DIR }}
        cd ${{ env.PROJECT_DIR }}
        git clone ${{ env.REPO_LINK }}

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

Работа с артефактами

Артефакты — это файлы, которые можно передавать между джобами. Артефакты хранятся в специальных контейнерах отдельно от репозиториев в виртуальном пространстве GitHub. По дефолту срок хранения артефактов 90 дней. Как правило, артефакты содержат в себе простую текстовую информацию или данные в формате json.

Перейдём сразу к коду и покажем, как можно создать и прочитать простенький артефакт в виде текстового файла, содержащий одну строку текста:

name: Working with artifacts
on: workflow_dispatch 
env:
  file_name: artifact_body.txt
  path: ./
  artifact_name: artifact_1

jobs:
  create_artifact:
    runs-on: ubuntu-latest
    steps:
      - name: Create artifact data
        run:
          echo "This is body of artifact_1." > ${{ env.path }}${{ env.file_name }}
      - name: Save Artifact
        uses: actions/upload-artifact@v3
        with:
          name: ${{ env.artifact_name }}
          path: ${{ env.path }}${{ env.file_name }}

  read_artifact:
    needs: create_artifact 
    runs-on: ubuntu-latest
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v3
        with:
          name: ${{ env.artifact_name }}
          path: ${{ env.path }}
      - name: Read artifact
        run:
          echo "${{ env.artifact_name }}:" && cat ${{ env.path }}${{ env.file_name }}

В двух словах расскажем, как тут всё работает:

  1. На шаге Create artifact data, с помощью команды echo создаем файл artifact_body.txt и записываем в него "This is body of artifact_1.";
  2. На следующем шаге Save Artifact, мы вызываем готовый экшен actions/upload-artifact@v3 и с помощью директивы with задаём:
    • name — имя артефакта, который будет создан;
    • path — путь до содержимого файла, из которого будут прочитаны данные.

    В результате работы экшена мы увидим следующую информацию:

    Артефакты уникальны для каждого workflow. Просмотреть содержимое артефакта и скачать его на локальную машину можно из раздела выполнения workflow:

  3. В следующей джобе на шаге Download artifact, мы опять используем готовый экшен actions/download-artifact@v3 и с помощью директивы with передаем следующие параметры:

    Если артефакт был найден и загружен, то в терминале GitHub VM будет выведено следующие сообщение:

    • name — название артефакты для скачивания
    • path — директория для скачивания артефакта на GitHub VM
  4. На последнем шаге мы вводим в терминал название артефакта и его содержимое с помощью связки команды echo и cat: echo "${{ env.artifact_name }}:" && cat ${{ env.path }}${{ env.file_name }}.

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

Что нужно знать о переменных, секретах и артефактах?

В GitHub Actions переменные разделяются по уровням доступа: начиная от переменных уровня GitHub, которые доступны с любого шага workflow до переменных окружения джобы, доастых только для текущей джобы. Наиболее часто придется работать с переменными окружения workflow и джобы.

Переменные уровня workflow задаются вначале файла директивой env, с указанием названия переменных и их значением:

env:
  vm_name: test_vps_1
  vm_ip: 192.123.456.18

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

Переменные уровня джобы можно задавать вручную с помощью директивы env или назначать через терминал внутри GitHub VM с помощью следующей конструкции:

run:
  echo "[VAR_NAME]:[VAR_VALUE]" >> $GITHUB_ENV

$GITHUB_ENV — это контейнер для хранения переменных, куда можно добавлять пользовательские переменные. Обращаться к переменным можно через контекст, определяющий их зону действия или на прямую по имени, если это переменные виртуального окружения Linux:

  1. ${{ env.[VARIABLE_NAME] }} — обращение к переменным через контекст env уровня workflow и уровня джобы;
  2. $[VARIABLE_NAME] — прямое обращение к переменным виртуального окружения Linux.

Для обращения к секретам GitHub Actions используется контекст ${{ secrets.[VARIABLE_NAME] }}. Секреты используются для хранения секретной информации, которая не может быть распечатана в терминале и её содержимое появляется только один раз при создании секрета.

К переменным уровня workflow и секретам можно обращаться с любого шага выполнения workflow, но они статичны, для передачи динамических данных между джобами в рамках одного workflow применяются артефакты — это файлы, содержащие текстовую информацию и хранящиеся в отдельных контейнерах на GitHub VM.

Для создания и чтения артефактов используются готовые экшены:

jobs:
  create_artifact:
    runs-on: ubuntu-latest
    steps:
      - name: Create artifact data
        run:
          echo "This is body of artifact_1." > ${{ env.path }}${{ env.file_name }}
      - name: Save Artifact
        uses: actions/upload-artifact@v3
        with:
          name: ${{ env.artifact_name }}
          path: ${{ env.path }}${{ env.file_name }}

  read_artifact:
    needs: create_artifact 
    runs-on: ubuntu-latest
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v3
        with:
          name: ${{ env.artifact_name }}
          path: ${{ env.path }}
      - name: Read artifact
        run:
          echo "${{ env.artifact_name }}:" && cat ${{ env.path }}${{ env.file_name }}

В следующей статье мы объединим все знания о GitHub Actions и создадим pipline автоматического деплоя одного из наших проектов на виртуальный сервер 1cloud. Настоятельно рекомендуем ознакомиться с предыдущими публикациями — они помогут лучше понять текущий материал и подготовится к прочтению следующей статьи.

GitHub Actions: знакомство и первые шаги

Разбираемся в GitHub Actions: что такое GitHub Actions, из каких элементов он состоит и как им пользоваться.

Git: работа с gitHUB

Знакомимся с gitHub: рассматриваем его базовые понятия, органы управления и функционал.

Git: коммиты, ветки и перемещение между ними

Разбираемся с объектами git: создаём ветви, коммиты, мерджим их и работаем с зонами видимости в git.