Vous êtes-vous déjà retrouvé à copier et coller le même code dans plusieurs workflows GitHub ? Lorsque vous devez effectuer la même tâche dans différents dépôts ou workflows, la création d’une action GitHub partagée est la solution idéale. Dans ce tutoriel, apprenez à construire une action GitHub JavaScript personnalisée depuis le début que vous pouvez partager au sein de votre organisation.
Comprendre les actions et workflows GitHub
Avant de plonger dans la création d’une action personnalisée, établissons un peu de contexte. Un workflow GitHub est un processus automatisé que vous pouvez configurer dans votre dépôt pour construire, tester, empaqueter, publier ou déployer n’importe quel projet sur GitHub. Ces workflows sont composés d’un ou plusieurs jobs qui peuvent s’exécuter de manière séquentielle ou en parallèle.
Les actions GitHub sont les tâches individuelles qui composent un workflow. Pensez-y comme à des blocs de construction réutilisables : elles gèrent des tâches spécifiques comme le checkout de code, l’exécution de tests ou le déploiement sur un serveur. GitHub propose trois types d’actions :
- Actions de conteneur Docker
- Actions JavaScript
- Actions composites
Pour ce tutoriel, nous nous concentrerons sur la création d’une action JavaScript car elle s’exécute directement sur la machine du runner et peut s’exécuter rapidement.
Le Problème : Quand créer une action personnalisée
Explorons quand et pourquoi vous voudriez créer une action GitHub personnalisée à travers un exemple pratique. Tout au long de ce tutoriel, nous utiliserons un scénario spécifique – l’intégration avec Devolutions Server (DVLS) pour la gestion des secrets – pour démontrer le processus, mais les concepts s’appliquent à toute situation où vous devez créer une action partagée et réutilisable.
💡 Remarque : Si vous avez Devolutions Server (DVLS) et que vous souhaitez passer à la partie utilisation, vous pouvez trouver la version complète dans le référentiel Des actions Github de Devolutions.
Imaginez que vous gérez plusieurs flux de travail GitHub qui doivent interagir avec un service externe – dans notre exemple, récupérer des secrets de DVLS. Chaque flux de travail ayant besoin de cette fonctionnalité nécessite les mêmes étapes de base :
- Se connecter au service externe
- S’authentifier
- Effectuer des opérations spécifiques
- Gérer les résultats
Sans une action partagée, vous auriez besoin de dupliquer ce code dans chaque flux de travail. Ce n’est pas seulement inefficace – c’est aussi plus difficile à maintenir et plus sujet aux erreurs.
Pourquoi créer une action partagée ?
Créer une action GitHub partagée offre plusieurs avantages clés qui s’appliquent à tout scénario d’intégration :
- Réutilisabilité du code : Écrivez le code d’intégration une fois et utilisez-le dans plusieurs flux de travail et dépôts
- Maintenabilité : Mettez à jour l’action en un seul endroit pour déployer les modifications partout où elle est utilisée
- Normalisation : Assurez-vous que toutes les équipes suivent le même processus pour les tâches courantes
- Contrôle de version : Suivez les modifications apportées au code d’intégration et revenez en arrière si nécessaire
- Complexité réduite : Simplifiez les flux de travail en abstrayant les détails d’implémentation
Prérequis
Avant de commencer ce tutoriel, assurez-vous d’avoir les éléments suivants en place :
- Un dépôt GitHub avec un flux de travail existant
- Des connaissances de base en Git, y compris le clonage de dépôts et la création de branches
- Un accès en tant que propriétaire de l’organisation pour créer et gérer des dépôts partagés
- Une compréhension de base de JavaScript et de Node.js
Pour notre exemple, nous allons créer une action qui s’intègre avec DVLS, mais vous pouvez adapter les concepts à tout service externe ou toute fonctionnalité personnalisée dont vous avez besoin.
Ce Que Vous Allez Créer
À la fin de ce tutoriel, vous saurez comment :
- Créer un dépôt GitHub public pour les actions partagées
- Construire plusieurs actions interconnectées (nous en créerons deux à titre d’exemple) :
- Une pour gérer l’authentification
- Une autre pour effectuer des opérations spécifiques
- Créer un flux de travail qui utilise vos actions personnalisées
Nous illustrerons ces concepts en construisant des actions qui s’intègrent avec DVLS, mais vous pouvez appliquer les mêmes schémas pour créer des actions pour tout besoin de votre organisation.
Point de Départ : Le Flux de Travail Existant
Examinons un flux de travail simple qui envoie une notification Slack lorsqu’une nouvelle version est créée. Ce flux de travail utilise actuellement des secrets GitHub pour stocker l’URL du webhook 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:" }'
Remarquez la référence secrets.SLACK_WEBHOOK_URL
. Cette URL de webhook est actuellement stockée en tant que secret GitHub, mais nous souhaitons la récupérer depuis notre instance DVLS à la place. Bien qu’il s’agisse d’un exemple simple utilisant un seul secret, imaginez avoir des dizaines de flux de travail dans votre organisation, chacun utilisant plusieurs secrets. Gérer ces secrets de manière centralisée dans DVLS plutôt que de les disperser sur GitHub serait beaucoup plus efficace.
Plan de mise en œuvre
Pour convertir ce flux de travail de l’utilisation des secrets GitHub vers DVLS, nous devons :
- Préparer l’environnement DVLS
- Créer les secrets correspondants dans DVLS
- Tester les points de terminaison API DVLS pour l’authentification et la récupération des secrets
- Créer le dépôt d’actions partagées
- Construire une action pour l’authentification DVLS (
dvls-login
) - Construire une action pour récupérer les valeurs secrètes (
dvls-get-secret-entry
) - Utiliser le compilateur ncc de Vercel pour regrouper les actions sans node_modules
- Construire une action pour l’authentification DVLS (
- Modifier le flux de travail
- Remplacer les références aux secrets GitHub par nos actions personnalisées
- Tester la nouvelle mise en œuvre
Chaque étape se construit sur la précédente, et à la fin, vous aurez une solution réutilisable que n’importe quel flux de travail dans votre organisation peut exploiter. Bien que nous utilisions DVLS comme exemple, vous pouvez adapter ce même modèle pour n’importe quel service externe avec lequel vos flux de travail doivent interagir.
Étape 1 : Exploration de l’API externe
Avant de créer une Action GitHub, vous devez comprendre comment interagir avec votre service externe. Pour notre exemple DVLS, nous avons besoin de deux secrets déjà configurés dans l’instance DVLS :
DVLS_APP_KEY
– La clé d’application pour l’authentificationDVLS_APP_SECRET
– Le secret d’application pour l’authentification
Tester le flux de l’API
Utilisons PowerShell pour explorer l’API DVLS et comprendre le flux que nous devrons implémenter dans notre action. Cette phase d’exploration est cruciale lors de la création de toute action personnalisée – vous devez comprendre les exigences de l’API avant de les implémenter.
$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
Cette exploration révèle le flux de l’API que nous devrons implémenter dans notre Action GitHub :
- S’authentifier avec DVLS en utilisant les identifiants de l’application
- Obtenir les informations du coffre en utilisant le jeton renvoyé
- Localiser l’ID d’entrée spécifique pour notre secret
- Récupérer la valeur réelle du secret
Comprendre ce flux est crucial car nous devrons implémenter les mêmes étapes dans notre Action GitHub, simplement en utilisant JavaScript au lieu de PowerShell.
Lorsque vous créez votre propre action personnalisée, vous suivrez un processus similaire :
- Identifiez les points de terminaison API avec lesquels vous devez interagir
- Testez le processus d’authentification et de récupération de données
- Documentez les étapes que vous devrez mettre en œuvre dans votre action
Étape 2 : Création de l’Action d’Authentification
Maintenant que nous comprenons le flux de l’API, créons notre première action personnalisée pour gérer l’authentification. Nous allons construire cela dans un nouveau dépôt partagé.
Mise en place de la structure de l’action
Tout d’abord, créez la structure de fichiers suivante dans votre dépôt :
dvls-actions/ ├── login/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Cette structure de fichiers est organisée pour créer une Action GitHub modulaire et maintenable :
- login/ – Un répertoire dédié à l’action d’authentification, regroupant les fichiers associés
- index.js – Le code principal de l’action qui contient la logique d’authentification et les interactions API
- action.yml – Définit l’interface de l’action, y compris les entrées requises et comment exécuter l’action
- package.json – Gère les dépendances et les métadonnées du projet
- README.md – Documentation pour les utilisateurs de l’action
Cette structure suit les meilleures pratiques pour les Actions GitHub, gardant le code organisé et facilitant la maintenance et la mise à jour de l’action au fil du temps.
Création du code de l’action
Tout d’abord, vous devez créer le code de l’action. Cela implique de créer le fichier JavaScript principal qui gérera la logique d’authentification :
- Créez
index.js
– c’est ici que réside la logique principale de l’action :
// 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();
Le code utilise le package @actions/core
de la trousse à outils de GitHub pour gérer les entrées, les sorties et le journal. Nous avons également mis en place une gestion des erreurs robuste et un journal pour faciliter le débogage.
Ne vous inquiétez pas trop de comprendre tous les détails du code JavaScript ici! Le point clé est que ce code d’action GitHub doit simplement faire une chose principale : utiliser core.setOutput()
pour renvoyer le jeton d’authentification.
Si vous n’êtes pas à l’aise pour écrire ce JavaScript vous-même, vous pouvez utiliser des outils comme ChatGPT pour vous aider à générer le code. La partie la plus importante est de comprendre que l’action doit :
- Obtenir les valeurs d’entrée (comme l’URL du serveur et les informations d’identification)
- Effectuer la demande d’authentification
- Retourner le jeton en utilisant
core.setOutput()
Création du package NodeJS
Maintenant que nous comprenons la structure du code et la fonctionnalité de notre action, configurons le package Node.js. Cela implique la création des fichiers de package nécessaires et l’installation des dépendances dont notre action aura besoin pour fonctionner correctement.
- Créez
package.json
pour définir nos dépendances et d’autres métadonnées d’action.{ "name": "devolutions-server-login", "version": "1.0.0", "description": "Action GitHub pour s'authentifier sur le serveur 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" } }
- Installez les dépendances en exécutant
npm install
.npm install
Après avoir installé les dépendances, vous devriez voir un nouveau répertoire
node_modules
créé dans le dossier de votre projet. Ce répertoire contient tous les paquets requis pour que votre action puisse s’exécuter.Note : Bien que nous commettions
package.json
etpackage-lock.json
dans le contrôle de version, nous exclurons éventuellement le répertoirenode_modules
en utilisantncc
pour regrouper nos dépendances. - Créez
action.yml
pour définir l’interface de l’action :name: 'Connexion au serveur Devolutions' description: 'Authentifiez-vous et obtenez un jeton du serveur Devolutions' inputs: server_url: description: 'URL du serveur Devolutions' required: true app_key: description: 'Clé d’application pour l’authentification' required: true app_secret: description: 'Secret d’application pour l’authentification' required: true output_variable: description: 'Nom de la variable d’environnement pour stocker le jeton récupéré' required: false default: 'DVLS_TOKEN' runs: using: 'node20' main: 'index.js'
Le fichier
action.yml
est crucial car il définit comment votre action fonctionnera dans les workflows de GitHub Actions. Décomposons ses principaux composants :- nom et description : Cela fournit des informations de base sur ce que fait votre action
- inputs : Définit les paramètres que les utilisateurs peuvent passer à votre action :
server_url
: Où trouver le serveur Devolutionsapp_key
etapp_secret
: Identifiants d’authentificationoutput_variable
: Où stocker le jeton résultant
- runs : Spécifie comment exécuter l’action :
using: 'node20'
: Utilise la version 20 de Node.jsmain: 'index.js'
: Pointe vers le fichier JavaScript principal
Lorsque les utilisateurs font référence à cette action dans leurs workflows, ils fourniront ces inputs selon cette définition d’interface.
Optimisation de l’action
Pour rendre notre action plus maintenable et efficace, nous utiliserons le compilateur ncc
de Vercel pour regrouper toutes les dépendances dans un seul fichier. Cela élimine la nécessité de valider le répertoire node_modules
:
Il n’est pas recommandé d’inclure node_modules dans le dépôt de votre action GitHub pour plusieurs raisons:
- Le répertoire node_modules peut être très volumineux, contenant toutes les dépendances et leurs sous-dépendances, ce qui ferait gonfler inutilement la taille du dépôt
- Différents systèmes d’exploitation et environnements peuvent gérer les node_modules de manière différente, entraînant potentiellement des problèmes de compatibilité
- Utiliser le compilateur ncc de Vercel pour regrouper toutes les dépendances dans un seul fichier est une meilleure approche car cela :
- Crée une action plus efficace et plus facile à maintenir
- Élimine le besoin de commettre le répertoire node_modules
- Installer
ncc
:npm i -g @vercel/ncc
- Construire la version regroupée :
ncc build index.js --license licenses.txt
- Mettre à jour
action.yml
pour pointer vers le fichier regroupé :runs : using: 'node20' main: 'dist/index.js' # Mis à jour pour utiliser la version regroupée
- Nettoyer :
rm -rf node_modules # Supprimer le répertoire node_modules
- Commitez les fichiers dans le dépôt partagé.
git add . git commit -m "Commit initial de l'action de connexion DVLS" git push
Créer le README
Tout le monde aime la documentation, n’est-ce pas ? Non ? Eh bien, moi non plus, alors j’ai créé un modèle de README pour que vous puissiez l’utiliser. Assurez-vous de le remplir et de l’inclure avec votre action.
# 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:
étapes :
- nom: Étape préalable
utilise : example/action-name@v1
avec:
entréenom: ${{ 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:
étapes :
- nom : Nom de l’étape
utilisations : votre-org/action-name@v1
avec:
entréenom : ‘Valeur d’entrée’
une autreentrée : ‘Une autre valeur’
## Example Workflow Here's a complete example workflow utilizing this action:
nom : Exemple de workflow
sur : [push]
emplois :
exemple-emploi :
s’exécute sur : ubuntu-latest
étapes :
– nom : Vérifier le dépôt
utilisations : 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).
Points clés à retenir
Lors de la création de votre propre action personnalisée :
- Implémentez toujours une gestion d’erreurs approfondie et un journalisation
- Utilisez le package
@actions/core
pour une intégration correcte des actions GitHub - Regroupez les dépendances avec
ncc
pour maintenir le dépôt propre - Documentez clairement les entrées et les sorties dans votre
action.yml
- Considérez les implications en matière de sécurité et masquez les valeurs sensibles en utilisant
core.setSecret()
Cette action d’authentification sera utilisée par notre prochaine action qui récupère des secrets. Passons maintenant à la création de cette action.
Étape 3 : Création de l’action « Obtenir le secret »
Vous avez accompli le travail difficile jusqu’à présent. Vous savez maintenant comment créer une action Github personnalisée. Si vous suivez, vous devez maintenant répéter ces étapes pour l’action d’entrée de secret DVLS comme suit :
Structure de l’action
dvls-actions/ ├── get-secret-entry/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Fichier 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'
Optimisation de l’action
- Compiler le fichier index.
npm i -g @vercel/ncc ncc build index.js --license licenses.txt
- Mettre à jour
action.yml
pour pointer vers le fichier groupé :runs: using: 'node20' main: 'dist/index.js' # Mis à jour pour utiliser la version groupée
- Nettoyer :
rm -rf node_modules # Supprimer le répertoire node_modules
- Valider les fichiers dans le dépôt partagé.
git add . git commit -m "Validation initiale de l'action d'entrée secrète DVLS" git push
Résultat final
À ce stade, vous devriez avoir deux dépôts GitHub :
- le dépôt contenant le flux de travail que vous aviez en utilisant les secrets GitHub
- le dépôt partagé (en supposant que le nom est dvls-actions) contenant les deux actions avec une structure ressemblant à ceci :
dvls-actions/ ├── login/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md ├── get-secret-entry/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Utilisation des actions personnalisées
Une fois que vous avez configuré ces actions personnalisées, vous pouvez les utiliser dans votre flux de travail d’appel d’origine.
Flux de travail d’origine :
- Utilise une seule étape pour envoyer une notification Slack
- Référence directement l’URL du webhook à partir des secrets (
secrets.SLACK_WEBHOOK_URL
)
Nouveau flux de travail :
- Ajoute une étape d’authentification en utilisant l’action de connexion DVLS personnalisée
- Récupère de manière sécurisée l’URL du webhook Slack depuis le serveur Devolutions
- Utilise des variables d’environnement au lieu de secrets
- Maintient la même fonctionnalité de notification mais avec une sécurité renforcée
Le nouveau flux de travail ajoute deux étapes avant la notification Slack :
- Authentification avec le serveur Devolutions en utilisant l’action
dvls-login
- Récupération de l’URL du webhook Slack en utilisant l’action
dvls-get-secret-entry
- L’étape finale de notification Slack reste similaire mais utilise l’URL du webhook récupérée à partir d’une variable d’environnement (
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:" }'
La création d’actions GitHub personnalisées vous permet de standardiser et sécuriser vos flux de travail à travers plusieurs dépôts. En déplaçant des opérations sensibles telles que l’authentification et la récupération de secrets dans des actions dédiées, vous pouvez :
- Maintenir de meilleures pratiques de sécurité en centralisant la gestion des identifiants
- Réduire la duplication de code à travers différents flux de travail
- Simplifier la maintenance et les mises à jour des flux de travail
- Garantir une implémentation cohérente des opérations critiques
L’exemple de l’intégration du serveur Devolutions avec les actions GitHub montre comment les actions personnalisées peuvent combler l’écart entre différents outils tout en respectant les meilleures pratiques de sécurité. Cette approche peut être adaptée à diverses autres intégrations et cas d’utilisation dans vos flux de travail DevOps.
Source:
https://adamtheautomator.com/custom-github-actions-guide/