Когда вам приходилось копировать и вставлять один и тот же код в несколько рабочих процессов на GitHub? Когда вам нужно выполнять одну и ту же задачу в разных репозиториях или рабочих процессах, создание общего действия GitHub – это правильный путь. В этом руководстве вы узнаете, как создать настраиваемое действие GitHub на JavaScript с нуля, которое можно использовать в вашей организации.
Понимание действий и рабочих процессов GitHub
Прежде чем приступить к созданию настраиваемого действия, давайте установим контекст. Рабочий процесс GitHub – это автоматизированный процесс, который вы можете настроить в своем репозитории для сборки, тестирования, упаковки, выпуска или развертывания любого проекта на GitHub. Эти рабочие процессы состоят из одной или нескольких задач, которые могут выполняться последовательно или параллельно.
Действия GitHub – это отдельные задачи, из которых состоит рабочий процесс. Представьте их как многоразовые строительные блоки – они выполняют конкретные задачи, такие как проверка кода, запуск тестов или развертывание на сервер. GitHub предоставляет три типа действий:
- Действия контейнера Docker
- Действия JavaScript
- Композитные действия
В этом руководстве мы сосредоточимся на создании действия на JavaScript, поскольку оно выполняется непосредственно на машине исполнителя и может быстро выполняться.
Проблема: когда создавать настраиваемое действие
Давайте рассмотрим, когда и почему вам может понадобиться создать настраиваемое действие GitHub на практическом примере. На протяжении этого руководства мы будем использовать конкретный сценарий – интеграцию с сервером Devolutions (DVLS) для управления секретами – чтобы продемонстрировать процесс, но концепции применимы к любой ситуации, где вам нужно создать общее, многоразовое действие.
💡 Примечание: Если у вас есть Devolutions Server (DVLS) и вы хотите перейти к использованию, вы можете найти завершенную версию в репозитории Devolutions Github Actions.
Представьте, что вы управляете несколькими рабочими процессами GitHub, которые должны взаимодействовать с внешним сервисом – в нашем примере, получать секреты из DVLS. Каждый рабочий процесс, который нуждается в этой функциональности, требует выполнения тех же основных шагов:
- Подключение к внешнему сервису
- Аутентификация
- Выполнение конкретных операций
- Обработка результатов
Без общего действия вам пришлось бы дублировать этот код в каждом рабочем процессе. Это не только неэффективно, но также сложнее поддерживать и более подвержено ошибкам.
Почему создавать общее действие?
Создание общего действия GitHub предлагает несколько ключевых преимуществ, которые применимы к любому сценарию интеграции:
- Повторное использование кода: Напишите код интеграции один раз и используйте его в нескольких рабочих процессах и репозиториях
- Поддерживаемость: Обновляйте действие в одном месте, чтобы распространить изменения повсюду, где оно используется
- Стандартизация: Обеспечьте соблюдение всех команд одного процесса для общих задач
- Управление версиями: Отслеживайте изменения в коде интеграции и откатывайтесь при необходимости
- Снижение сложности: Упростите рабочие процессы, абстрагируя детали реализации
Предварительные требования
Перед началом этого учебного пособия убедитесь, что у вас есть следующее:
- Репозиторий GitHub с существующим рабочим процессом
- Базовые знания Git, включая клонирование репозиториев и создание веток
- Доступ владельца организации для создания и управления общими репозиториями
- Базовое понимание JavaScript и Node.js
Для нашего примера мы создадим действие, которое интегрируется с DVLS, но вы можете адаптировать концепции к любому внешнему сервису или необходимой вам пользовательской функциональности.
Что вы создадите
К концу этого учебного пособия вы поймете, как:
- Создать общедоступный репозиторий GitHub для общих действий
- Построить несколько взаимосвязанных действий (мы создадим два в качестве примеров):
- Одно для обработки аутентификации
- Другое для выполнения определенных операций
- Создать рабочий процесс, использующий ваши пользовательские действия
Мы продемонстрируем эти концепции, создав действия, интегрирующиеся с DVLS, но вы можете применить те же шаблоны для создания действий для любых целей, необходимых вашей организации.
Исходная точка: Существующий рабочий процесс
Давайте рассмотрим простой рабочий процесс, который отправляет уведомление в Slack при создании нового релиза. В этом рабочем процессе в настоящее время используются секреты GitHub для хранения URL вебхука Slack:
name: Release Notification on: release: types: [published] jobs: notify: runs-on: ubuntu-latest steps: - name: Send Slack Notification run: | curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \\ -H "Content-Type: application/json" \\ --data '{ "text": "New release ${{ github.event.release.tag_name }} published!", "username": "GitHub Release Bot", "icon_emoji": ":rocket:" }'
Обратите внимание на ссылку secrets.SLACK_WEBHOOK_URL
. Этот URL вебхука в настоящее время хранится как секрет GitHub, но мы хотим получить его из нашего экземпляра DVLS. Хотя это простой пример, использующий только один секрет, представьте, что у вашей организации есть десятки рабочих процессов, каждый из которых использует несколько секретов. Управление этими секретами централизованно в DVLS вместо их разброса по GitHub было бы гораздо более эффективным.
План внедрения
Для преобразования этого рабочего процесса с использованием секретов GitHub в DVLS необходимо:
- Подготовить среду DVLS
- Создать соответствующие секреты в DVLS
- Протестировать конечные точки API DVLS для аутентификации и извлечения секретов
- Создать репозиторий общих действий
- Создать действие для аутентификации в DVLS (
dvls-login
) - Создать действие для извлечения значений секретов (
dvls-get-secret-entry
) - Использовать компилятор ncc от Vercel для объединения действий без node_modules
- Создать действие для аутентификации в DVLS (
- Изменить рабочий процесс
- Заменить ссылки на секреты GitHub на наши собственные действия
- Протестировать новую реализацию
Каждый шаг основывается на предыдущем, и в конце вы получите многоразовое решение, которое может использовать любой рабочий процесс в вашей организации. Хотя мы используем DVLS в качестве примера, вы можете адаптировать эту же схему для любой внешней службы, с которой ваши рабочие процессы должны взаимодействовать.
Шаг 1: Изучение внешнего API
Перед созданием действия GitHub вам необходимо понять, как взаимодействовать с вашей внешней службой. Для нашего примера с DVLS нам нужны две конфиденциальные информации, уже настроенные в экземпляре DVLS:
DVLS_APP_KEY
– ключ приложения для аутентификацииDVLS_APP_SECRET
– секрет приложения для аутентификации
Тестирование потока API
Давайте используем PowerShell для изучения API DVLS и понимания потока, который нам нужно будет реализовать в нашем действии. Этот этап исследования имеет решающее значение при создании любого пользовательского действия – вам нужно понять требования API перед их реализацией.
$dvlsUrl = '<https://1.1.1.1/dvls>' $appId = 'xxxx' $appSecret = 'xxxxx' # Step 1: Authentication $loginResult = Invoke-RestMethod -Uri "$dvlsUrl/api/v1/login" ` -Body @{ 'appKey' = $appId 'appSecret' = $appSecret } ` -Method Post ` -SkipCertificateCheck # Step 2: Get Vault Information $vaultResult = Invoke-RestMethod -Uri "$dvlsUrl/api/v1/vault" ` -Headers @{ 'tokenId' = $loginResult.tokenId } ` -SkipCertificateCheck $vault = $vaultResult.data.where({$_.name -eq 'DevOpsSecrets'}) # Step 3: Get Entry ID $entryResponse = Invoke-RestMethod -Uri "$dvlsUrl/api/v1/vault/$($vault.id)/entry" ` -Headers @{ tokenId = $loginResult.tokenId } ` -Body @{ name = 'azure-acr' } ` -SkipCertificateCheck # Step 4: Retrieve Secret Value $passwordResponse = Invoke-RestMethod -Uri "$dvlsUrl/api/v1/vault/$($vault.id)/entry/$($entryResponse.data[0].id)" ` -Headers @{ tokenId = $loginResult.tokenId } ` -Body @{ includeSensitiveData = $true } ` -SkipCertificateCheck $passwordResponse.data.password
Это исследование выявляет поток API, который нам нужно будет реализовать в нашем действии GitHub:
- Аутентификация с DVLS с использованием учетных данных приложения
- Получение информации о хранилище с использованием возвращенного токена
- Нахождение конкретного идентификатора записи для нашего секрета
- Получение фактического значения секрета
Понимание этого потока имеет решающее значение, потому что нам нужно будет реализовать те же шаги в нашем действии GitHub, просто используя JavaScript вместо PowerShell.
Создавая свое собственное пользовательское действие, вы будете следовать аналогичному процессу:
- Определите конечные точки API, с которыми вам нужно взаимодействовать
- Проверьте процесс аутентификации и извлечения данных
- Документируйте шаги, которые вам нужно будет реализовать в вашем действии
Шаг 2: Создание действия аутентификации
Теперь, когда мы понимаем поток API, давайте создадим наше первое пользовательское действие для обработки аутентификации. Мы создадим это в новом общем репозитории.
Настройка структуры действия
Сначала создайте следующую структуру файлов в вашем репозитории:
dvls-actions/ ├── login/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Эта структура файлов организована для создания модульного и поддерживаемого действия GitHub:
- login/ – Специальный каталог для действия аутентификации, который объединяет связанные файлы
- index.js – Основной код действия, который содержит логику аутентификации и взаимодействие с API
- action.yml – Определяет интерфейс действия, включая необходимые входные данные и способ выполнения действия
- package.json – Управляет зависимостями и метаданными проекта
- README.md – Документация для пользователей действия
Эта структура соответствует лучшим практикам для действий GitHub, сохраняя код организованным и упрощая поддержку и обновление действия с течением времени.
Создание кода действия
Сначала вам нужно создать код действия. Это включает в себя создание основного файла JavaScript, который будет обрабатывать логику аутентификации:
- Создайте
index.js
– здесь находится основная логика действия:
// Required dependencies // @actions/core - GitHub Actions toolkit for input/output operations const core = require('@actions/core'); // axios - HTTP client for making API requests const axios = require('axios'); // https - Node.js HTTPS module for SSL/TLS support const https = require('https'); // Create an axios instance with SSL verification disabled // This is useful when dealing with self-signed certificates const axiosInstance = axios.create({ httpsAgent: new https.Agent({ rejectUnauthorized: false }) }); /** * Authenticates with the Devolutions Server and retrieves an auth token * @param {string} serverUrl - The base URL of the Devolutions Server * @param {string} appKey - Application key for authentication * @param {string} appSecret - Application secret for authentication * @returns {Promise<string>} The authentication token */ async function getAuthToken(serverUrl, appKey, appSecret) { core.info(`Attempting to get auth token from ${serverUrl}/api/v1/login`); const response = await axiosInstance.post(`${serverUrl}/api/v1/login`, { appKey: appKey, appSecret: appSecret }); core.info('Successfully obtained auth token'); return response.data.tokenId; } /** * Wrapper function for making HTTP requests with detailed error handling * @param {string} description - Description of the request for logging * @param {Function} requestFn - Async function that performs the actual request * @returns {Promise<any>} The result of the request * @throws {Error} Enhanced error with detailed debugging information */ async function makeRequest(description, requestFn) { try { core.info(`Starting request: ${description}`); const result = await requestFn(); core.info(`Successfully completed request: ${description}`); return result; } catch (error) { // Detailed error logging for debugging purposes core.error('=== Error Details ==='); core.error(`Error Message: ${error.message}`); core.error(` core.error(`Status Text: ${error.response?.statusText}`); // Log response data if available if (error.response?.data) { core.error('Response Data:'); core.error(JSON.stringify(error.response.data, null, 2)); } // Log request configuration details if (error.config) { core.error('Request Details:'); core.error(`URL: ${error.config.url}`); core.error(`Method: ${error.config.method}`); core.error('Request Data:'); core.error(JSON.stringify(error.config.data, null, 2)); } core.error('=== End Error Details ==='); // Throw enhanced error with API message if available const apiMessage = error.response?.data?.message; throw new Error(`${description} failed: ${apiMessage || error.message} ( } } /** * Main execution function for the GitHub Action * This action authenticates with a Devolutions Server and exports the token * for use in subsequent steps */ async function run() { try { core.info('Starting Devolutions Server Login action'); // Get input parameters from the workflow const serverUrl = core.getInput('server_url'); const appKey = core.getInput('app_key'); const appSecret = core.getInput('app_secret'); const outputVariable = core.getInput('output_variable'); core.info(`Server URL: ${serverUrl}`); core.info('Attempting authentication...'); // Authenticate and get token const token = await makeRequest('Authentication', () => getAuthToken(serverUrl, appKey, appSecret) ); // Mask the token in logs for security core.setSecret(token); // Make token available as environment variable core.exportVariable(outputVariable, token); // Set token as output for other steps core.setOutput('token', token); core.info('Action completed successfully'); } catch (error) { // Handle any errors that occur during execution core.error(`Action failed: ${error.message}`); core.setFailed(error.message); } } // Execute the action run();
Код использует пакет @actions/core
из набора инструментов GitHub для обработки входных данных, выходных данных и ведения журнала. Мы также реализовали надежную обработку ошибок и ведение журнала, чтобы упростить отладку.
Не беспокойтесь слишком сильно о понимании всех деталей кода JavaScript здесь! Ключевой момент заключается в том, что этот код GitHub Action должен выполнить одну основную задачу: использовать core.setOutput()
для возврата токена аутентификации.
Если вам некомфортно писать этот JavaScript самостоятельно, вы можете использовать инструменты, такие как ChatGPT, чтобы помочь сгенерировать код. Самая важная часть заключается в понимании того, что действие должно:
- Получить входные значения (такие как URL сервера и учетные данные)
- Сделать запрос на аутентификацию
- Вернуть токен, используя
core.setOutput()
Создание пакета NodeJS
Теперь, когда мы понимаем структуру кода и функциональность нашего действия, давайте настроим конфигурацию пакета Node.js. Это включает в себя создание необходимых файлов пакета и установку зависимостей, которые нашему действию понадобятся для правильной работы.
- Создайте
package.json
для определения наших зависимостей и других метаданных действия.{ "name": "devolutions-server-login", "version": "1.0.0", "description": "GitHub-действие для аутентификации на сервере Devolutions", "main": "index.js", "scripts": { "test": "echo \\"Error: no test specified\\" && exit 1" }, "keywords": [ "devolutions_server" ], "author": "Adam Bertram", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", "axios": "^1.6.7" } }
- Установите зависимости, выполнив команду
npm install
.npm install
После установки зависимостей вы должны увидеть новый каталог
node_modules
, созданный в папке вашего проекта. В этом каталоге содержатся все необходимые пакеты, которые ваше действие должно использовать для выполнения.Примечание: Хотя мы занесем
package.json
иpackage-lock.json
в систему контроля версий, мы в конечном итоге исключим каталогnode_modules
, используяncc
для сборки наших зависимостей. - Создайте
action.yml
, чтобы определить интерфейс действия:name: 'Вход в Devolutions Server' description: 'Аутентификация и получение токена от Devolutions Server' inputs: server_url: description: 'URL Devolutions Server' required: true app_key: description: 'Ключ приложения для аутентификации' required: true app_secret: description: 'Секрет приложения для аутентификации' required: true output_variable: description: 'Имя переменной окружения для хранения полученного токена' required: false default: 'DVLS_TOKEN' runs: using: 'node20' main: 'index.js'
Файл
action.yml
имеет ключевое значение, так как он определяет, как ваше действие будет работать в рабочих процессах GitHub Actions. Давайте разберем его ключевые компоненты:- name и description: Эти поля предоставляют основную информацию о том, что делает ваше действие
- inputs: Определяет параметры, которые пользователи могут передавать вашему действию:
server_url
: Где найти Devolutions Serverapp_key
иapp_secret
: Учетные данные для аутентификацииoutput_variable
: Где хранить полученный токен
- runs: Указывает, как выполнить действие:
using: 'node20'
: Использует версию Node.js 20main: 'index.js'
: Указывает на основной файл JavaScript
Когда пользователи ссылаются на это действие в своих рабочих процессах, они предоставят эти входные данные в соответствии с определением интерфейса.
Оптимизация действия
Чтобы сделать наше действие более удобным для сопровождения и эффективным, мы будем использовать компилятор ncc
от Vercel для объединения всех зависимостей в один файл. Это устраняет необходимость коммитить директорию node_modules
:
Включение node_modules в ваш репозиторий GitHub Action не рекомендуется по нескольким причинам:
- Директория node_modules может быть очень большой, содержащей все зависимости и их подзависимости, что нелепо увеличивает размер репозитория
- Разные операционные системы и среды могут по-разному обрабатывать node_modules, потенциально вызывая проблемы совместимости
- Использование компилятора ncc от Vercel для объединения всех зависимостей в один файл — это лучший подход, потому что он:
- Создает более эффективное и удобное в обслуживании действие
- Устраняет необходимость коммитить директорию node_modules
- Установите
ncc
:npm i -g @vercel/ncc
- Соберите объединенную версию:
ncc build index.js --license licenses.txt
- Обновите
action.yml
, чтобы указать на объединенный файл:runs: using: 'node20' main: 'dist/index.js' # Обновлено для использования объединенной версии
- Очистка:
rm -rf node_modules # Удалить директорию node_modules
- Закоммитьте файлы в общий репозиторий.
git add . git commit -m "Первый коммит действия входа DVLS" git push
Создание README
Все любят документацию, верно? Нет? Ну, я тоже не люблю, поэтому я создал шаблон README для вас. Обязательно заполните его и включите в ваше действие.
# GitHub Action Template This template provides a standardized structure for documenting any GitHub Action. Replace the placeholders with details specific to your action. --- # Action Name A brief description of what this GitHub Action does. ## Prerequisites Outline any setup or configuration required before using the action. For example:
шаги:
- name: Предварительный шаг
uses: example/action-name@v1
with:
inputname: ${{ secrets.INPUTSECRET }}
## Inputs | Input Name | Description | Required | Default | |-------------------|------------------------------------------------|----------|----------------| | `input_name` | Description of the input parameter | Yes/No | Default Value | | `another_input` | Description of another input parameter | Yes/No | Default Value | ## Outputs | Output Name | Description | |-------------------|------------------------------------------------| | `output_name` | Description of the output parameter | | `another_output` | Description of another output parameter | ## Usage Provide an example of how to use this action in a workflow:
steps:
- name: Шаг Name
использует: your-org/action-name@v1
с:
вводname: ‘Input Value’
другойввод: ‘Another Value’
## Example Workflow Here's a complete example workflow utilizing this action:
name: Пример рабочего процесса
при: [push]
работы:
пример-работа:
запускается на: ubuntu-latest
шаги:
– name: Проверка Репозитория
использует: actions/checkout@v3
- name: Run Action uses: your-org/action-name@v1 with: input_name: 'Input Value' another_input: 'Another Value' - name: Use Output run: | echo "Output value: ${{ steps.step_id.outputs.output_name }}"
## Security Notes - Highlight best practices for using sensitive data, such as storing secrets in GitHub Secrets. - Remind users not to expose sensitive information in logs. ## License Include the license details for this action, e.g., MIT License: This GitHub Action is available under the [MIT License](LICENSE).
Ключевые моменты, которые нужно помнить
При создании собственного пользовательского действия:
- Всегда реализуйте тщательное обработку ошибок и ведение журнала
- Используйте пакет
@actions/core
для правильной интеграции с GitHub Actions - Упаковывайте зависимости с помощью
ncc
, чтобы поддерживать репозиторий в чистоте - Четко документируйте вводы и выводы в своем
action.yml
- Учитывайте последствия для безопасности и маскируйте чувствительные значения с помощью
core.setSecret()
Это действие аутентификации будет использоваться нашим следующим действием, которое извлекает секреты. Перейдем к созданию этого действия.
Шаг 3: Создание действия “Получить секрет”
Вы проделали тяжелую работу до этого момента. Теперь вы знаете, как создать пользовательское действие Github. Если вы следите за мной, вам теперь нужно повторить эти шаги для действия ввода секрета DVLS следующим образом:
Структура Действия
dvls-actions/ ├── get-secret-entry/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Файл index.js
// Required dependencies const core = require('@actions/core'); // GitHub Actions toolkit for action functionality const axios = require('axios'); // HTTP client for making API requests const https = require('https'); // Node.js HTTPS module for SSL/TLS support // Create an axios instance that accepts self-signed certificates const axiosInstance = axios.create({ httpsAgent: new https.Agent({ rejectUnauthorized: false }) }); /** * Retrieves the vault ID for a given vault name from the DVLS server * @param {string} serverUrl - The URL of the DVLS server * @param {string} token - Authentication token for API access * @param {string} vaultName - Name of the vault to find * @returns {string|null} - Returns the vault ID if found, null otherwise */ async function getVaultId(serverUrl, token, vaultName) { core.debug(`Attempting to get vault ID for vault: ${vaultName}`); const response = await axiosInstance.get(`${serverUrl}/api/v1/vault`, { headers: { tokenId: token } }); core.debug(`Found ${response.data.data.length} vaults`); // Find the vault with matching name const vault = response.data.data.find(v => v.name === vaultName); if (vault) { core.debug(`Found vault ID: ${vault.id}`); } else { // Log available vaults for debugging purposes core.debug(`Available vaults: ${response.data.data.map(v => v.name).join(', ')}`); } return vault ? vault.id : null; } /** * Retrieves the entry ID for a given entry name within a vault * @param {string} serverUrl - The URL of the DVLS server * @param {string} token - Authentication token for API access * @param {string} vaultId - ID of the vault containing the entry * @param {string} entryName - Name of the entry to find * @returns {string} - Returns the entry ID * @throws {Error} - Throws if entry is not found */ async function getEntryId(serverUrl, token, vaultId, entryName) { core.debug(`Attempting to get entry ID for entry: ${entryName} in vault: ${vaultId}`); const response = await axiosInstance.get( `${serverUrl}/api/v1/vault/${vaultId}/entry`, { headers: { tokenId: token }, data: { name: entryName }, params: { name: entryName } } ); const entryId = response.data.data[0].id; if (!entryId) { // Log full response for debugging if entry not found core.debug('Response data:'); core.debug(JSON.stringify(response.data, null, 2)); throw new Error(`Entry '${entryName}' not found`); } core.debug(`Found entry ID: ${entryId}`); return entryId; } /** * Retrieves the password for a specific entry in a vault * @param {string} serverUrl - The URL of the DVLS server * @param {string} token - Authentication token for API access * @param {string} vaultId - ID of the vault containing the entry * @param {string} entryId - ID of the entry containing the password * @returns {string} - Returns the password */ async function getPassword(serverUrl, token, vaultId, entryId) { core.debug(`Attempting to get password for entry: ${entryId} in vault: ${vaultId}`); const response = await axiosInstance.get( `${serverUrl}/api/v1/vault/${vaultId}/entry/${entryId}`, { headers: { tokenId: token }, data: { includeSensitiveData: true }, params: { includeSensitiveData: true } } ); core.debug('Successfully retrieved password'); return response.data.data.password; } /** * Generic request wrapper with enhanced error handling and debugging * @param {string} description - Description of the request for logging * @param {Function} requestFn - Async function containing the request to execute * @returns {Promise<any>} - Returns the result of the request function * @throws {Error} - Throws enhanced error with API response details */ async function makeRequest(description, requestFn) { try { core.debug(`Starting request: ${description}`); const result = await requestFn(); core.debug(`Successfully completed request: ${description}`); return result; } catch (error) { // Log detailed error information for debugging core.debug('Full error object:'); core.debug(JSON.stringify({ message: error.message, status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, headers: error.response?.headers, url: error.config?.url, method: error.config?.method, requestData: error.config?.data, queryParams: error.config?.params }, null, 2)); const apiMessage = error.response?.data?.message; throw new Error(`${description} failed: ${apiMessage || error.message} ( } } /** * Main execution function for the GitHub Action * Retrieves a password from DVLS and sets it as an output/environment variable */ async function run() { try { core.debug('Starting action execution'); // Get input parameters from GitHub Actions const serverUrl = core.getInput('server_url'); const token = core.getInput('token'); const vaultName = core.getInput('vault_name'); const entryName = core.getInput('entry_name'); const outputVariable = core.getInput('output_variable'); core.debug(`Server URL: ${serverUrl}`); core.debug(`Vault Name: ${vaultName}`); core.debug(`Entry Name: ${entryName}`); // Sequential API calls to retrieve password const vaultId = await makeRequest('Get Vault ID', () => getVaultId(serverUrl, token, vaultName) ); if (!vaultId) { throw new Error(`Vault '${vaultName}' not found`); } const entryId = await makeRequest('Get Entry ID', () => getEntryId(serverUrl, token, vaultId, entryName) ); const password = await makeRequest('Get Password', () => getPassword(serverUrl, token, vaultId, entryId) ); // Set the password as a secret and output core.setSecret(password); // Mask password in logs core.exportVariable(outputVariable, password); // Set as environment variable core.setOutput('password', password); // Set as action output core.debug('Action completed successfully'); } catch (error) { core.debug(`Action failed: ${error.message}`); core.setFailed(error.message); } } // Execute the action run();
Package.json
{ "name": "devolutions-server-get-entry", "version": "1.0.0", "description": "GitHub Action to retrieve entries from Devolutions Server", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "devolutions_server" ], "author": "Adam Bertram", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", "axios": "^1.6.7" } }
Action.yml
name: 'Devolutions Server Get SecretEntry' description: 'Authenticate and get a secret entry from Devolutions Server' inputs: server_url: description: 'URL of the Devolutions Server' required: true token: description: 'Token for authentication' required: true vault_name: description: 'Name of the vault containing the secret entry' required: true entry_name: description: 'Name of the secret entry to retrieve' required: true output_variable: description: 'Name of the environment variable to store the retrieved secret' required: false default: 'DVLS_ENTRY_SECRET' runs: using: 'node20' main: 'index.js'
Оптимизация Действия
- Скомпилируйте файл index.
npm i -g @vercel/ncc ncc build index.js --license licenses.txt
- Обновите
action.yml
, чтобы указать на упакованный файл:runs: using: 'node20' main: 'dist/index.js' # Обновлено для использования упакованной версии
- Очистка:
rm -rf node_modules # Удалить каталог node_modules
- Зафиксировать изменения в общем репозитории.
git add . git commit -m "Первоначальное добавление действия получения секрета DVLS" git push
Итог
На этом этапе у вас должно быть два репозитория GitHub:
- репозиторий с рабочим процессом, в котором использовались секреты GitHub
- общий репозиторий (предполагается, что его имя – dvls-actions), содержащий два действия со структурой, похожей на эту:
dvls-actions/ ├── login/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md ├── get-secret-entry/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Использование Пользовательских Действий
После того как вы настроили эти пользовательские действия, вы можете использовать их в вашем исходном рабочем процессе.
Исходный рабочий процесс:
- Использует один шаг для отправки уведомления в Slack
- Прямо ссылается на URL вебхука из секретов (
secrets.SLACK_WEBHOOK_URL
)
Новый рабочий процесс:
- Добавляет шаг аутентификации с использованием пользовательского действия входа DVLS
- Безопасно извлекает URL вебхука Slack с сервера Devolutions
- Использует переменные окружения вместо секретов
- Поддерживает ту же функциональность уведомлений, но с улучшенной безопасностью
Новый рабочий процесс добавляет два шага перед уведомлением Slack:
- Аутентификация с сервером Devolutions с использованием действия
dvls-login
- Получение URL вебхука Slack с использованием действия
dvls-get-secret-entry
- Последний шаг уведомления Slack остается похожим, но использует полученный URL вебхука из переменной окружения (
env.SLACK_WEBHOOK_URL
)
name: Release Notification on: release: types: [published] jobs: notify: runs-on: ubuntu-latest steps: - name: Login to Devolutions Server uses: devolutions-community/dvls-login@main with: server_url: 'https://1.1.1.1/dvls' app_key: ${{ vars.DVLS_APP_KEY }} app_secret: ${{ vars.DVLS_APP_SECRET }} - name: Get Slack Webhook URL uses: devolutions-community/dvls-get-secret-entry@main with: server_url: 'https://1.1.1.1/dvls' token: ${{ env.DVLS_TOKEN }} vault_name: 'DevOpsSecrets' entry_name: 'slack-webhook' output_variable: 'SLACK_WEBHOOK_URL' - name: Send Slack Notification run: | curl -X POST ${{ env.SLACK_WEBHOOK_URL }} \ -H "Content-Type: application/json" \ --data '{ "text": "New release ${{ github.event.release.tag_name }} published!", "username": "GitHub Release Bot", "icon_emoji": ":rocket:" }'
Создание пользовательских действий GitHub позволяет стандартизировать и обеспечить безопасность ваших рабочих процессов в нескольких репозиториях. Перемещая чувствительные операции, такие как аутентификация и получение секретов, в специальные действия, вы можете:
- Поддерживать лучшие практики безопасности, централизуя управление учетными данными
- Сократить дублирование кода в различных рабочих процессах
- Упростить обслуживание и обновление рабочих процессов
- Обеспечить последовательную реализацию критических операций
Пример интеграции сервера Devolutions с GitHub Actions демонстрирует, как пользовательские действия могут соединить разные инструменты, сохраняя при этом лучшие практики безопасности. Этот подход может быть адаптирован для различных других интеграций и случаев использования в ваших DevOps рабочих процессах.
Source:
https://adamtheautomator.com/custom-github-actions-guide/