Já se pegou copiando e colando o mesmo código em vários fluxos de trabalho do GitHub? Quando você precisa executar a mesma tarefa em diferentes repositórios ou fluxos de trabalho, criar uma Ação do GitHub compartilhada é a melhor opção. Neste tutorial, aprenda como construir uma Ação do GitHub personalizada em JavaScript do zero que você pode compartilhar em toda a sua organização.
Entendendo as 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 em JavaScript
- Ações compostas
Para este tutorial, vamos nos concentrar em criar uma ação em JavaScript, pois ela é executada diretamente na máquina executora 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 personalizada no GitHub através de um exemplo prático. Ao longo deste tutorial, usaremos um cenário específico – integrando com o Servidor de Devoluções (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 Devolutions Server (DVLS) e gostaria de 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-se ao serviço externo
- Autenticar
- Realizar operações específicas
- Lidar com os resultados
Sem uma ação compartilhada, você precisaria duplicar esse código em cada fluxo 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?
Criar 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-o em vários fluxos de trabalho e repositórios
- Manutenção: Atualize a ação em um só lugar para implementar mudanças em todos os lugares em que é usado
- 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 os detalhes de implementação
Pré-requisitos
Antes de começar este tutorial, certifique-se de ter o seguinte em vigor:
- 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 o nosso cenário de exemplo, vamos criar uma ação que se integra com o DVLS, mas você pode adaptar os conceitos para qualquer serviço externo ou funcionalidade personalizada que você precise.
O Que Você Vai Criar
Até o final deste tutorial, você entenderá como:
- Criar um repositório GitHub público para ações compartilhadas
- Construir múltiplas ações interconectadas (vamos criar duas como exemplos):
- Uma para lidar com a autenticação
- Outra para realizar operações específicas
- Criar um fluxo de trabalho que utiliza suas ações personalizadas
Vamos demonstrar esses conceitos construindo ações que se integram com o DVLS, mas você pode aplicar os mesmos padrões para criar ações para qualquer finalidade que sua organização precise.
Ponto de Partida: O Fluxo de Trabalho Existente
Vamos examinar um fluxo de trabalho simples que envia uma notificação para o 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
. Este URL do webhook está atualmente armazenado como um segredo do GitHub, mas queremos recuperá-lo de nossa instância do DVLS. Enquanto este é um exemplo simples usando apenas um segredo, imagine ter dezenas de fluxos de trabalho em toda a sua organização, cada um usando vários segredos. Gerenciar esses segredos de forma centralizada 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 o DVLS, precisamos de:
- Preparar o Ambiente do 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 do DVLS (
dvls-login
) - Construir uma ação para recuperar valores de segredos (
dvls-get-secret-entry
) - Utilizar o compilador ncc da Vercel para agrupar as ações sem node_modules
- Construir uma ação para autenticação do DVLS (
- Modificar o Fluxo de Trabalho
- Substituir as referências de 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 precisam 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 da aplicação para autenticaçãoDVLS_APP_SECRET
– O segredo da aplicação 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. Esta 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
Esta exploração revela o fluxo da API que precisaremos implementar em nossa Ação do GitHub:
- Autenticar com o DVLS usando credenciais do aplicativo
- Obter as informações do cofre usando o token retornado
- Localizar o ID da entrada específica para nosso segredo
- Recuperar o valor real do segredo
Entender esse 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 você precisa interagir
- Teste o processo de autenticação e recuperação de dados
- Documente as etapas que você precisará implementar em sua ação
Passo 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 está 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 os usuários da ação
Esta estrutura segue as melhores práticas para 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
Primeiro, você deve criar o código da ação. Isso envolve criar o arquivo JavaScript principal que lidará com a lógica de autenticação:
- Criar
index.js
– aqui é onde a lógica principal da ação vive:
// 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 utiliza o pacote @actions/core
do toolkit do GitHub para lidar com entradas, saídas e registro. Também implementamos um tratamento robusto de erros 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 de Ação do GitHub precisa apenas fazer uma coisa principal: usar core.setOutput()
para retornar o token de autenticação.
Se você não se sente confortável escrevendo este JavaScript por conta própria, você 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 de nossa ação, vamos configurar a Node.js package. Isso envolve a criação dos arquivos de pacote necessários e a instalação das dependências que nossa ação precisará para funcionar corretamente.
- Crie o
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 as 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 que sua ação seja executada.Nota: Enquanto iremos fazer commit do
package.json
e dopackage-lock.json
no controle de versão, eventualmente excluiremos o diretórionode_modules
usando oncc
para agrupar nossas dependências. - Crie
action.yml
para definir a interface da ação:name: 'Login no Servidor Devolutions' description: 'Autentica e obtém 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á dentro dos fluxos de trabalho do GitHub Actions. Vamos analisar seus principais componentes:- name e description: 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 essa ação em seus fluxos de trabalho, eles fornecerão essas entradas de acordo com essa definição de interface.
Otimizando a Ação
Para tornar nossa ação mais manutenível 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 no repositório da sua 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 sub-dependências, o que inflaria desnecessariamente o tamanho do repositório
- Diferentes sistemas operacionais e ambientes podem lidar com o node_modules de maneira 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 # Remova o diretório node_modules
- Commitar os arquivos no repositório compartilhado.
git add . git commit -m "Commit inicial da ação de login do DVLS" git push
Criando o README
Todos amam documentação, certo? Não? Bem, eu também não amo, então criei um modelo de README para você usar. Certifique-se de preenchê-lo e incluí-lo 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:
passos:
- nome: Passo Pré-requisito
usa: exemplo/nome-da-acao@v1
com:
inputnome: ${{ secrets.INPUTSECRETO }}
## 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:
passos:
- nome: Nome da Etapa
usa: your-org/action-name@v1
com:
inputnome: ‘Valor de Entrada’
outroinput: ‘Outro Valor’
## Example Workflow Here's a complete example workflow utilizing this action:
nome: Exemplo de Fluxo de Trabalho
em: [push]
jobs:
exemplo-job:
roda em: ubuntu-ultima-versao
passos:
– nome: Verificar Repositório
usa: 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:
- Sempre implemente um tratamento de erros abrangente e registro
- Use o pacote
@actions/core
para integração adequada com o GitHub Actions - Empacote dependências com
ncc
para manter o repositório limpo - Documente claramente as entradas e saídas em seu
action.yml
- Considere implicações de segurança e mascare valores sensíveis usando
core.setSecret()
Esta ação de autenticação será usada por nossa próxima ação que obtém segredos. Vamos avançar para criar essa ação.
Etapa 3: Criando a Ação “Obter Segredo”
Você fez o trabalho árduo até este ponto. Agora você sabe como criar uma ação personalizada no Github. Se você está acompanhando, agora precisa repetir essas etapas para a ação de entrada de segredo DVLS da seguinte forma:
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
- Compile o arquivo index.
npm i -g @vercel/ncc ncc build index.js --license licenses.txt
- Atualize
action.yml
para apontar para o arquivo incluído:runs: using: 'node20' main: 'dist/index.js' # Atualizado para usar a versão incluída
- Limpeza:
rm -rf node_modules # Remover o diretório node_modules
- Commit os arquivos no repositório compartilhado.
git add . git commit -m "Commit inicial da ação de entrada secreta DVLS" git push
O Resultado Final
Neste ponto, você deverá ter dois repositórios no GitHub:
- o repositório contendo o fluxo de trabalho que você tinha usando segredos do GitHub
- o repositório compartilhado (supondo 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 um único passo para enviar uma notificação no Slack
- Referencia diretamente a URL do webhook a partir dos segredos (
secrets.SLACK_WEBHOOK_URL
)
Novo fluxo de trabalho:
- Adiciona um passo de autenticação usando a ação de login personalizada DVLS
- Recupera a URL do webhook do Slack de forma segura do Servidor Devolutions
- Usa variáveis de ambiente ao invés 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
- O passo final de notificação no Slack permanece semelhante, mas utiliza 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 no GitHub permite padronizar e proteger 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 a gestão 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
- Assegurar a implementação consistente de operações críticas
O exemplo de integração do Servidor de Devolutions com as Ações do GitHub demonstra como ações personalizadas podem preencher a lacuna entre diferentes ferramentas, mantendo as melhores práticas de segurança. Essa 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/