Já se viu copiando e colando o mesmo código em vários fluxos de trabalho do GitHub? Quando você precisa realizar a mesma tarefa em diferentes repositórios ou fluxos de trabalho, criar uma Ação GitHub compartilhada é o caminho a seguir. Neste tutorial, aprenda como construir uma Ação GitHub JavaScript personalizada do zero que você pode compartilhar em toda a sua organização.
Compreendendo Ações e Fluxos de Trabalho do GitHub
Antes de mergulhar na criação de uma ação personalizada, vamos estabelecer algum contexto. Um fluxo de trabalho do GitHub é um processo automatizado que você pode configurar em seu repositório para construir, testar, empacotar, lançar ou implantar qualquer projeto no GitHub. Esses fluxos de trabalho são compostos por um ou mais jobs que podem ser executados sequencialmente ou em paralelo.
As Ações do GitHub são as tarefas individuais que compõem um fluxo de trabalho. Pense nelas como blocos de construção reutilizáveis – elas lidam com tarefas específicas como verificar o código, executar testes ou implantar em um servidor. O GitHub fornece três tipos de ações:
- Ações de contêiner Docker
- Ações JavaScript
- Ações compostas
Para este tutorial, iremos focar em criar uma ação JavaScript, pois ela é executada diretamente na máquina de execução e pode ser executada rapidamente.
O Problema: Quando Criar uma Ação Personalizada
Vamos explorar quando e por que você gostaria de criar uma Ação GitHub personalizada por meio de um exemplo prático. Ao longo deste tutorial, usaremos um cenário específico – integrando com o Servidor Devolutions (DVLS) para gerenciamento de segredos – para demonstrar o processo, mas os conceitos se aplicam a qualquer situação em que você precise criar uma ação compartilhada e reutilizável.
💡 Nota: Se você tiver o Servidor Devolutions (DVLS) e quiser pular para a parte de uso, você pode encontrar a versão completa no repositório de Ações do Github da Devolutions.
Imagine que você está gerenciando vários fluxos de trabalho do GitHub que precisam interagir com um serviço externo – em nosso exemplo, recuperando segredos do DVLS. Cada fluxo de trabalho que precisa dessa funcionalidade requer as mesmas etapas básicas:
- Conectar ao serviço externo
- Autenticar
- Realizar operações específicas
- Manipular os resultados
Sem uma ação compartilhada, você precisaria duplicar esse código em todos os fluxos de trabalho. Isso não é apenas ineficiente – também é mais difícil de manter e mais propenso a erros.
Por que Criar uma Ação Compartilhada?
A criação de uma Ação Compartilhada no GitHub oferece vários benefícios-chave que se aplicam a qualquer cenário de integração:
- Reutilização de Código: Escreva o código de integração uma vez e use em vários fluxos de trabalho e repositórios
- Manutenção: Atualize a ação em um só lugar para implementar alterações em todos os locais em que é usada
- Padronização: Garanta que todas as equipes sigam o mesmo processo para tarefas comuns
- Controle de Versão: Acompanhe as alterações no código de integração e reverta se necessário
- Complexidade Reduzida: Simplifique os fluxos de trabalho abstraindo detalhes de implementação
Pré-requisitos
Antes de começar este tutorial, certifique-se de ter o seguinte em ordem:
- Um repositório GitHub com um fluxo de trabalho existente
- Conhecimento básico de Git, incluindo clonagem de repositórios e criação de branches
- Acesso de proprietário da organização para criar e gerenciar repositórios compartilhados
- Compreensão básica de JavaScript e Node.js
Para nosso cenário de exemplo, criaremos uma ação que se integra ao DVLS, mas você pode adaptar os conceitos para qualquer serviço externo ou funcionalidade personalizada que precisar.
O que você irá criar
No final deste tutorial, você entenderá como:
- Criar um repositório público no GitHub para ações compartilhadas
- Construir várias ações interconectadas (vamos criar duas como exemplos):
- Uma para lidar com autenticação
- Outra para realizar operações específicas
- Criar um fluxo de trabalho que utiliza suas ações personalizadas
Demonstrar esses conceitos construindo ações que se integram ao DVLS, mas você pode aplicar os mesmos padrões para criar ações para qualquer propósito que sua organização precisar.
Ponto de partida: O fluxo de trabalho existente
Vamos examinar um fluxo de trabalho simples que envia uma notificação no Slack quando uma nova versão é criada. Este fluxo de trabalho atualmente usa segredos do GitHub para armazenar a URL do webhook do 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:" }'
Observe a referência secrets.SLACK_WEBHOOK_URL
. Esta URL de webhook está atualmente armazenada como um segredo do GitHub, mas queremos recuperá-la da nossa instância DVLS. Embora este seja um exemplo simples usando apenas um segredo, imagine ter dezenas de fluxos de trabalho em sua organização, cada um utilizando vários segredos. Gerenciar esses segredos centralmente no DVLS em vez de espalhados pelo GitHub seria muito mais eficiente.
Plano de Implementação
Para converter este fluxo de trabalho de usar segredos do GitHub para DVLS, precisamos:
- Preparar o Ambiente DVLS
- Criar segredos correspondentes no DVLS
- Testar os endpoints da API do DVLS para autenticação e recuperação de segredos
- Criar o Repositório de Ações Compartilhadas
- Construir uma ação para autenticação no DVLS (
dvls-login
) - Construir uma ação para recuperar valores de segredos (
dvls-get-secret-entry
) - Usar o compilador ncc da Vercel para empacotar as ações sem node_modules
- Construir uma ação para autenticação no DVLS (
- Modificar o Fluxo de Trabalho
- Substituir referências a segredos do GitHub por nossas ações personalizadas
- Testar a nova implementação
Cada etapa se baseia na anterior e, ao final, você terá uma solução reutilizável que qualquer fluxo de trabalho em sua organização pode aproveitar. Embora estejamos usando o DVLS como exemplo, você pode adaptar esse mesmo padrão para qualquer serviço externo com o qual seus fluxos de trabalho precisem interagir.
Etapa 1: Explorando a API Externa
Antes de criar uma Ação do GitHub, você precisa entender como interagir com seu serviço externo. Para o nosso exemplo do DVLS, precisamos de duas chaves já configuradas na instância do DVLS:
DVLS_APP_KEY
– A chave de aplicativo para autenticaçãoDVLS_APP_SECRET
– O segredo do aplicativo para autenticação
Testando o Fluxo da API
Vamos usar o PowerShell para explorar a API do DVLS e entender o fluxo que precisaremos implementar em nossa ação. Essa fase de exploração é crucial ao criar qualquer ação personalizada – você precisa entender os requisitos da API antes de implementá-los.
$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
Essa exploração revela o fluxo da API que precisaremos implementar em nossa Ação do GitHub:
- Autenticar com o DVLS usando as credenciais do aplicativo
- Obter as informações do cofre usando o token retornado
- Localizar o ID específico da entrada para nosso segredo
- Recuperar o valor real do segredo
Entender este fluxo é crucial porque precisaremos implementar as mesmas etapas em nossa Ação do GitHub, apenas usando JavaScript em vez de PowerShell.
Ao criar sua própria ação personalizada, você seguirá um processo semelhante:
- Identifique os endpoints da API com os quais precisa interagir
- Teste o processo de autenticação e recuperação de dados
- Documente as etapas que você precisará implementar em sua ação
Etapa 2: Criando a Ação de Autenticação
Agora que entendemos o fluxo da API, vamos criar nossa primeira ação personalizada para lidar com a autenticação. Vamos construir isso em um novo repositório compartilhado.
Configurando a Estrutura da Ação
Primeiro, crie a seguinte estrutura de arquivos em seu repositório:
dvls-actions/ ├── login/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Esta estrutura de arquivos é organizada para criar uma Ação do GitHub modular e de fácil manutenção:
- login/ – Um diretório dedicado para a ação de autenticação, mantendo arquivos relacionados juntos
- index.js – O código principal da ação que contém a lógica de autenticação e interações com a API
- action.yml – Define a interface da ação, incluindo entradas necessárias e como executar a ação
- package.json – Gerencia dependências e metadados do projeto
- README.md – Documentação para usuários da ação
Esta estrutura segue as melhores práticas para as Ações do GitHub, mantendo o código organizado e facilitando a manutenção e atualização da ação ao longo do tempo.
Criando o Código da Ação
Primeiramente, você deve criar o código da ação. Isso envolve a criação do arquivo JavaScript principal que lidará com a lógica de autenticação:
- Crie
index.js
– aqui é onde fica a lógica principal da ação:
// 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();
O código usa o pacote @actions/core
do toolkit do GitHub para lidar com entradas, saídas e registros. Também implementamos um tratamento de erros robusto e registro para facilitar a depuração.
Não se preocupe muito em entender todos os detalhes do código JavaScript aqui! O ponto chave é que este código da GitHub Action precisa fazer uma coisa principal: usar core.setOutput()
para retornar o token de autenticação.
Se você não se sente confortável escrevendo esse JavaScript sozinho, pode usar ferramentas como o ChatGPT para ajudar a gerar o código. A parte mais importante é entender que a ação precisa:
- Obter os valores de entrada (como URL do servidor e credenciais)
- Fazer a solicitação de autenticação
- Retornar o token usando
core.setOutput()
Criando o Pacote NodeJS
Agora que entendemos a estrutura do código e a funcionalidade da nossa ação, vamos configurar a configuração do pacote Node.js. Isso envolve criar os arquivos de pacote necessários e instalar as dependências que nossa ação precisará para funcionar corretamente.
- Crie
package.json
para definir nossas dependências e outros metadados de ação.{ "name": "devolutions-server-login", "version": "1.0.0", "description": "Ação do GitHub para autenticar no Servidor 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" } }
- Instale dependências executando
npm install
.npm install
Após instalar as dependências, você deverá ver um novo diretório
node_modules
criado na pasta do seu projeto. Este diretório contém todos os pacotes necessários para sua ação ser executada.Nota: Enquanto vamos commitar
package.json
epackage-lock.json
no controle de versão, eventualmente excluiremos o diretórionode_modules
usandoncc
para agrupar nossas dependências. - Crie
action.yml
para definir a interface da ação:name: 'Login no Servidor Devolutions' description: 'Autenticar e obter um token do Servidor Devolutions' inputs: server_url: description: 'URL do Servidor Devolutions' required: true app_key: description: 'Chave da aplicação para autenticação' required: true app_secret: description: 'Segredo da aplicação para autenticação' required: true output_variable: description: 'Nome da variável de ambiente para armazenar o token obtido' required: false default: 'DVLS_TOKEN' runs: using: 'node20' main: 'index.js'
O arquivo
action.yml
é crucial, pois define como sua ação funcionará nos fluxos de trabalho do GitHub Actions. Vamos analisar seus principais componentes:- nome e descrição: Fornecem informações básicas sobre o que sua ação faz
- inputs: Define os parâmetros que os usuários podem passar para sua ação:
server_url
: Onde encontrar o Servidor Devolutionsapp_key
eapp_secret
: Credenciais de autenticaçãooutput_variable
: Onde armazenar o token resultante
- runs: Especifica como executar a ação:
using: 'node20'
: Usa a versão Node.js 20main: 'index.js'
: Aponta para o arquivo JavaScript principal
Quando os usuários referenciam esta ação em seus fluxos de trabalho, eles fornecerão esses inputs de acordo com essa definição de interface.
Otimizando a Ação
Para tornar nossa ação mais mantida e eficiente, usaremos o compilador ncc
da Vercel para agrupar todas as dependências em um único arquivo. Isso elimina a necessidade de comprometer o diretório node_modules
:
Incluir node_modules em seu repositório de Ação do GitHub não é recomendado por várias razões:
- O diretório node_modules pode ser muito grande, contendo todas as dependências e suas subdependências, o que inflaria desnecessariamente o tamanho do repositório
- Diferentes sistemas operacionais e ambientes podem lidar com o node_modules de forma diferente, potencialmente causando problemas de compatibilidade
- O uso do compilador ncc da Vercel para agrupar todas as dependências em um único arquivo é uma abordagem melhor porque:
- Cria uma ação mais eficiente e fácil de manter
- Elimina a necessidade de commitar o diretório node_modules
- Instale
ncc
:npm i -g @vercel/ncc
- Construa a versão agrupada:
ncc build index.js --license licenses.txt
- Atualize o
action.yml
para apontar para o arquivo agrupado:runs: using: 'node20' main: 'dist/index.js' # Atualizado para usar a versão agrupada
- Limpeza:
rm -rf node_modules # Remover o diretório node_modules
- Commit os arquivos para o repositório compartilhado.
git add . git commit -m "Commit inicial da ação de login DVLS" git push
Criando o README
Todos amam documentação, certo? Não? Bem, eu também não, então criei um modelo de README para você usar. Certifique-se de preencher isso e incluir com sua ação.
# 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:
steps:
- name: Etapa Pré-Requisito
uses: exemplo/nome-da-acao@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:
- nome: Nome da Etapa
usos: sua-org/nome-da-acao@v1
com:
entradanome: ‘Valor de Entrada’
outroinput: ‘Outro Valor’
## Example Workflow Here's a complete example workflow utilizing this action:
nome: Exemplo do Fluxo de Trabalho
em: [push]
trabalhos:
exemplo-trabalho:
roda-em: ubuntu-ultima-versao
passos:
– nome: Verificar Repositório
usos: 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).
Pontos-chave a Lembrar
Ao criar sua própria ação personalizada:
- Implemente sempre um tratamento de erros detalhado e registro
- Use o pacote
@actions/core
para uma integração adequada com as Ações do GitHub - Agrupe dependências com
ncc
para manter o repositório limpo - Documente claramente as entradas e saídas em seu
action.yml
- Considere as implicações de segurança e mascare valores sensíveis usando
core.setSecret()
Esta ação de autenticação será usada pela nossa próxima ação que obtém segredos. Vamos seguir em frente e criar essa ação.
Etapa 3: Criando a Ação “Obter Segredo”
Você fez o trabalho difícil até este ponto. Agora você sabe como criar uma ação personalizada do Github. Se você estiver acompanhando, agora precisa repetir essas etapas para a entrada de segredo DVLS da ação, conforme a seguir:
A Estrutura da Ação
dvls-actions/ ├── get-secret-entry/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
O Arquivo 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'
Otimizando a Ação
- Compilar o arquivo index.
npm i -g @vercel/ncc ncc build index.js --license licenses.txt
- Atualize
action.yml
para apontar para o arquivo empacotado:runs: using: 'node20' main: 'dist/index.js' # Atualizado para usar a versão empacotada
- Limpeza:
rm -rf node_modules # Remover o diretório node_modules
- Commitar os arquivos para o repositório compartilhado.
git add . git commit -m "Commit inicial da ação de entrada de segredo do DVLS" git push
O Resultado Final
Neste ponto, você deve ter dois repositórios do GitHub:
- o repositório contendo o fluxo de trabalho que você tinha usando segredos do GitHub
- o repositório compartilhado (assumindo que o nome seja dvls-actions) contendo as duas ações com uma estrutura parecida com esta:
dvls-actions/ ├── login/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md ├── get-secret-entry/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Usando as Ações Personalizadas
Depois de configurar essas ações personalizadas, você pode usá-las em seu fluxo de trabalho de chamada original.
Fluxo de trabalho original:
- Usa uma etapa única para enviar uma notificação no Slack
- Faz referência diretamente à URL do webhook dos segredos (
secrets.SLACK_WEBHOOK_URL
)
Novo fluxo de trabalho:
- Adiciona uma etapa de autenticação usando a ação de login personalizada do DVLS
- Recupera a URL do webhook do Slack de forma segura do Servidor Devolutions
- Usa variáveis de ambiente em vez de segredos
- Mantém a mesma funcionalidade de notificação, mas com segurança aprimorada
O novo fluxo de trabalho adiciona dois passos antes da notificação no Slack:
- Autenticação com o Servidor de Devolutions usando a ação
dvls-login
- Recuperação da URL do webhook do Slack usando a ação
dvls-get-secret-entry
- A etapa final de notificação no Slack permanece semelhante, mas usa a URL do webhook recuperada de uma variável de ambiente (
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:" }'
A criação de Ações personalizadas do GitHub permite padronizar e garantir a segurança de seus fluxos de trabalho em vários repositórios. Ao mover operações sensíveis como autenticação e recuperação de segredos para ações dedicadas, você pode:
- Manter melhores práticas de segurança centralizando o gerenciamento de credenciais
- Reduzir a duplicação de código em diferentes fluxos de trabalho
- Simplificar a manutenção e atualizações do fluxo de trabalho
- Garantir a implementação consistente de operações críticas
O exemplo de integração do Servidor de Devolutions com Ações do GitHub demonstra como ações personalizadas podem preencher a lacuna entre diferentes ferramentas, mantendo as melhores práticas de segurança. Esta abordagem pode ser adaptada para várias outras integrações e casos de uso em seus fluxos de trabalho de DevOps.
Source:
https://adamtheautomator.com/custom-github-actions-guide/