Heb je jezelf ooit betrapt op het kopiëren en plakken van dezelfde code over meerdere GitHub-workflows? Wanneer je dezelfde taak moet uitvoeren in verschillende repositories of workflows, is het maken van een gedeelde GitHub-actie de juiste keuze. In deze tutorial leer je hoe je een aangepaste JavaScript GitHub-actie vanaf de basis kunt opbouwen die je binnen je organisatie kunt delen.
Begrip van GitHub Actions en Workflows
Voordat we ingaan op het maken van een aangepaste actie, laten we wat context vaststellen. Een GitHub-workflow is een geautomatiseerd proces dat je kunt instellen in je repository om elk project op GitHub te bouwen, testen, verpakken, uitbrengen of implementeren. Deze workflows bestaan uit één of meer taken die sequentieel of parallel kunnen worden uitgevoerd.
GitHub Actions zijn de individuele taken die een workflow vormen. Denk aan hen als herbruikbare bouwstenen – ze behandelen specifieke taken zoals code uitchecken, tests uitvoeren of implementeren naar een server. GitHub biedt drie soorten acties:
- Docker-containeracties
- JavaScript-acties
- Samengestelde acties
Voor deze tutorial zullen we ons richten op het maken van een JavaScript-actie omdat deze rechtstreeks op de runner-machine wordt uitgevoerd en snel kan worden uitgevoerd.
Het Probleem: Wanneer een aangepaste actie maken
Laten we verkennen wanneer en waarom je een aangepaste GitHub-actie zou willen maken aan de hand van een praktisch voorbeeld. Gedurende deze tutorial zullen we een specifiek scenario gebruiken – integreren met Devolutions Server (DVLS) voor geheimbeheer – om het proces te demonstreren, maar de concepten zijn van toepassing op elke situatie waarin je een gedeelde, herbruikbare actie moet maken.
💡 Opmerking: Als je Devolutions Server (DVLS) hebt en je wilt naar het gebruiksgedeelte overspringen, kun je de voltooide versie vinden in de Devolutions Github Actions-repo.
Stel je voor dat je meerdere GitHub-workflows beheert die moeten communiceren met een externe service – in ons voorbeeld, het ophalen van geheimen van DVLS. Elke workflow die deze functionaliteit nodig heeft, vereist dezelfde basisstappen:
- Verbinden met de externe service
- Authenticeren
- Specifieke bewerkingen uitvoeren
- De resultaten verwerken
Zonder een gedeelde actie moet je deze code in elke workflow dupliceren. Dat is niet alleen inefficiënt – het is ook moeilijker te onderhouden en vatbaarder voor fouten.
Waarom een Gedeelde Actie Maken?
Het creëren van een gedeelde GitHub Actie biedt verschillende belangrijke voordelen die van toepassing zijn op elke integratiescenario:
- Code Herbruikbaarheid: Schrijf de integratiecode één keer en gebruik deze in meerdere workflows en repositories
- Onderhoudbaarheid: Werk de actie op één plek bij om wijzigingen overal waar deze wordt gebruikt door te voeren
- Standaardisatie: Zorg ervoor dat alle teams hetzelfde proces volgen voor veelvoorkomende taken
- Versiebeheer: Volg wijzigingen in de integratiecode en rol zo nodig terug
- Verminderde Complexiteit: Vereenvoudig workflows door implementatiedetails te abstraheren
Vereisten
Voordat je met deze tutorial begint, zorg ervoor dat je het volgende hebt:
- Een GitHub repository met een bestaande workflow
- Basiskennis van Git, inclusief het klonen van repositories en het maken van branches
- Toegang als eigenaar van de organisatie om gedeelde repositories te maken en beheren
- Basiskennis van JavaScript en Node.js
Voor ons voorbeeldscenario zullen we een actie maken die integreert met DVLS, maar je kunt de concepten aanpassen naar elke externe service of aangepaste functionaliteit die je nodig hebt.
Wat je zult maken
Aan het einde van deze tutorial zul je begrijpen hoe je:
- Een openbare GitHub repository voor gedeelde acties maakt
- Meerdere onderling verbonden acties bouwt (we zullen er twee als voorbeeld maken):
- Een om authenticatie te verwerken
- Nog een om specifieke bewerkingen uit te voeren
- Een workflow maakt die jouw aangepaste acties gebruikt
We zullen deze concepten demonstreren door acties te bouwen die integreren met DVLS, maar je kunt dezelfde patronen toepassen om acties te maken voor elk doel dat jouw organisatie nodig heeft.
Startpunt: De bestaande workflow
Laten we een eenvoudige workflow bekijken die een Slack-melding verstuurt wanneer er een nieuwe release wordt gemaakt. Deze workflow maakt momenteel gebruik van GitHub-geheimen om de Slack-webhook-URL op te slaan:
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:" }'
Let op de secrets.SLACK_WEBHOOK_URL
verwijzing. Deze webhook-URL is momenteel opgeslagen als een GitHub-geheim, maar we willen deze in plaats daarvan ophalen uit onze DVLS-instantie. Hoewel dit een eenvoudig voorbeeld is met slechts één geheim, stel je voor dat er tientallen workflows in je organisatie zijn, elk met meerdere geheimen. Het centraal beheren van deze geheimen in DVLS in plaats van verspreid over GitHub zou veel efficiënter zijn.
Implementatieplan
Om deze workflow om te zetten van het gebruik van GitHub-geheimen naar DVLS, moeten we:
- DVLS-omgeving voorbereiden
- Bijbehorende geheimen in DVLS maken
- DVLS API-eindpunten testen voor authenticatie en geheimen ophalen
- Maak de gedeelde actiesrepository
- Bouwe een actie voor DVLS-authenticatie (
dvls-login
) - Bouwe een actie voor het ophalen van geheimwaarden (
dvls-get-secret-entry
) - Gebruik de ncc-compiler van Vercel om de acties te bundelen zonder node_modules
- Bouwe een actie voor DVLS-authenticatie (
- Wijzig de workflow
- Vervang de verwijzingen naar GitHub-geheimen door onze aangepaste acties
- Test de nieuwe implementatie
Elke stap bouwt voort op de vorige, en tegen het einde heb je een herbruikbare oplossing die elke workflow in jouw organisatie kan benutten. Hoewel we DVLS als ons voorbeeld gebruiken, kun je ditzelfde patroon aanpassen voor elke externe service waarmee jouw workflows moeten communiceren.
Stap 1: Het Externe API Verkennen
Voordat je een GitHub Actie maakt, moet je begrijpen hoe je moet communiceren met jouw externe service. Voor ons DVLS voorbeeld hebben we twee geheimen al geconfigureerd in de DVLS-instantie:
DVLS_APP_KEY
– De applicatiesleutel voor authenticatieDVLS_APP_SECRET
– Het toepassingsgeheim voor authenticatie
Het testen van de API-stroom
Laten we PowerShell gebruiken om de DVLS API te verkennen en de stroom te begrijpen die we moeten implementeren in onze actie. Deze verkenning is cruciaal bij het maken van elke aangepaste actie – je moet de API-vereisten begrijpen voordat je ze implementeert.
$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
Deze verkenning onthult de API-stroom die we moeten implementeren in onze GitHub Actie:
- Authenticeren bij DVLS met behulp van app-gegevens
- Ontvang de kluisinformatie met behulp van het geretourneerde token
- Zoek de specifieke invoer-ID voor ons geheim
- Haal de daadwerkelijke geheime waarde op
Het begrijpen van deze stroom is cruciaal omdat we dezelfde stappen moeten implementeren in onze GitHub Actie, maar dan met behulp van JavaScript in plaats van PowerShell.
Wanneer je jouw eigen aangepaste actie maakt, volg je een vergelijkbaar proces:
- Identificeer de API-eindpunten waarmee je moet communiceren
- Test het authenticatie- en gegevensophaalproces
- Documenteer de stappen die je moet implementeren in je actie
Stap 2: Creëren van de Authenticatie Actie
Nu we de API-stroom begrijpen, laten we onze eerste aangepaste actie maken voor het afhandelen van authenticatie. We zullen dit bouwen in een nieuwe gedeelde repository.
De Actiestructuur Instellen
Eerst, maak de volgende bestandsstructuur in je repository:
dvls-actions/ ├── login/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Deze bestandsstructuur is georganiseerd om een modulaire en onderhoudbare GitHub Actie te creëren:
- login/ – Een speciale map voor de authenticatieactie, waarbij gerelateerde bestanden bij elkaar worden gehouden
- index.js – De belangrijkste actiecodering die de authenticatielogica en API-interacties bevat
- action.yml – Definieert de interface van de actie, inclusief vereiste invoeren en hoe de actie moet worden uitgevoerd
- package.json – Beheert afhankelijkheden en projectmetadata
- README.md – Documentatie voor gebruikers van de actie
Deze structuur volgt best practices voor GitHub Acties, houdt de code georganiseerd en maakt het gemakkelijk om de actie in de loop van de tijd te onderhouden en te updaten.
De Actiecode Aanmaken
Eerst moet je de actiecode creëren. Dit houdt in dat je het hoofd JavaScript-bestand maakt dat de authenticatielogica zal afhandelen:
- Aanmaken van
index.js
– dit is waar de belangrijkste actielogica zich bevindt:
// 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();
De code maakt gebruik van het @actions/core
pakket van GitHub’s toolkit om invoer, uitvoer en logging te verwerken. We hebben ook robuuste foutafhandeling en logging geïmplementeerd om het debuggen gemakkelijker te maken.
Maak je niet te veel zorgen over het begrijpen van alle JavaScript-code details hier! Het belangrijkste punt is dat deze GitHub Action-code slechts één hoofddoel heeft: gebruik core.setOutput()
om de authenticatietoken terug te geven.
Als je niet comfortabel bent met het schrijven van deze JavaScript zelf, kun je tools zoals ChatGPT gebruiken om de code te genereren. Het belangrijkste is te begrijpen dat de actie moet:
- De invoerwaarden ophalen (zoals server-URL en referenties)
- De authenticatieaanvraag maken
- Het token teruggeven met behulp van
core.setOutput()
Het NodeJS-pakket maken
Nu we de codestructuur en functionaliteit van onze actie begrijpen, laten we de Node.js-pakketconfiguratie instellen. Dit omvat het maken van de noodzakelijke pakketbestanden en het installeren van afhankelijkheden die onze actie nodig zal hebben om correct te functioneren.
- Maak
package.json
om onze afhankelijkheden en andere actie metadata te definiëren.{ "name": "devolutions-server-login", "version": "1.0.0", "description": "GitHub Actie om te authenticeren bij Devolutions Server", "main": "index.js", "scripts": { "test": "echo \\"Fout: geen test gespecificeerd\\" && exit 1" }, "keywords": [ "devolutions_server" ], "author": "Adam Bertram", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", "axios": "^1.6.7" } }
- Installeer afhankelijkheden door
npm install
uit te voeren.npm install
Na het installeren van de afhankelijkheden, zou je een nieuwe
node_modules
map moeten zien die is aangemaakt in je projectmap. Deze map bevat alle vereiste pakketten die jouw actie nodig heeft om te draaien.Opmerking: Hoewel we
package.json
enpackage-lock.json
naar versiebeheer zullen committen, zullen we uiteindelijk denode_modules
map uitsluiten doorncc
te gebruiken om onze afhankelijkheden te bundelen. - Maak
action.yml
aan om de interface van de actie te definiëren:name: 'Devolutions Server Login' description: 'Authenticeren en een token van Devolutions Server ophalen' inputs: server_url: description: 'URL van de Devolutions Server' required: true app_key: description: 'Applicatiesleutel voor authenticatie' required: true app_secret: description: 'Applicatiegeheim voor authenticatie' required: true output_variable: description: 'Naam van de omgevingsvariabele om het opgehaalde token op te slaan' required: false default: 'DVLS_TOKEN' runs: using: 'node20' main: 'index.js'
Het
action.yml
bestand is cruciaal omdat het definieert hoe jouw actie zal werken binnen GitHub Actions workflows. Laten we de belangrijkste onderdelen ervan bekijken:- naam en beschrijving: Deze bieden basisinformatie over wat jouw actie doet
- inputs: Definieert de parameters die gebruikers aan jouw actie kunnen doorgeven:
server_url
: Waar de Devolutions Server te vinden isapp_key
enapp_secret
: Authenticatie referentiesoutput_variable
: Waar het resulterende token op te slaan
- runs: Specificeert hoe de actie uitgevoerd moet worden:
using: 'node20'
: Maakt gebruik van Node.js versie 20main: 'index.js'
: Verwijst naar het hoofd JavaScript bestand
Wanneer gebruikers naar deze actie verwijzen in hun workflows, zullen ze deze inputs verstrekken volgens deze interface definitie.
Optimaliseren van de actie
Om onze actie onderhoudbaarder en efficiënter te maken, zullen we Vercel’s ncc
compiler gebruiken om alle afhankelijkheden in één bestand te bundelen. Hierdoor is het niet meer nodig om de node_modules
map te committeren:
Het opnemen van node_modules in uw GitHub Actie repository wordt om verschillende redenen niet aanbevolen:
- De node_modules directory kan erg groot zijn, met alle afhankelijkheden en hun sub-afhankelijkheden, wat de opslaggrootte onnodig zou opblazen
- Verschillende besturingssystemen en omgevingen kunnen node_modules anders behandelen, wat mogelijk compatibiliteitsproblemen kan veroorzaken
- Het gebruik van Vercel’s ncc-compiler om alle afhankelijkheden in één bestand te bundelen is een betere aanpak omdat het:
- Een efficiëntere en onderhoudbare actie creëert
- De noodzaak om de node_modules directory te committen, elimineert
- Installeer
ncc
:npm i -g @vercel/ncc
- Bouw de gebundelde versie:
ncc build index.js --license licenses.txt
- Update
action.yml
om naar het gebundelde bestand te verwijzen:runs: using: 'node20' main: 'dist/index.js' # Bijgewerkt om de gebundelde versie te gebruiken
- Opruimen:
rm -rf node_modules # Verwijder de node_modules directory
- Commit de bestanden naar de gedeelde repo.
git add . git commit -m "Eerste commit van DVLS login actie" git push
Een README maken
Iedereen houdt van documentatie, toch? Nee? Nou, ik ook niet, dus ik heb een README-sjabloon voor je gemaakt om te gebruiken. Zorg ervoor dat je dit invult en het bij je actie voegt.
# 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:
stappen:
- naam: Vereiste Stap
gebruikt: voorbeeld/actie-naam@v1
met:
invoernaam: ${{ 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:
stappen:
- naam: Stap Naam
gebruikt: your-org/action-name@v1
met:
inputnaam: ‘Invoerwaarde’
eenandere invoer: ‘Een andere waarde’
## Example Workflow Here's a complete example workflow utilizing this action:
naam: Voorbeeld Workflow
op: [push]
banen:
voorbeeld-baan:
draait op: ubuntu-latest
stappen:
– naam: Repository controleren
gebruikt: 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).
Belangrijke punten om te onthouden
Bij het maken van je eigen aangepaste actie:
- Implementeer altijd grondige foutafhandeling en logging
- Gebruik het
@actions/core
pakket voor een goede integratie met GitHub Actions - Bundel afhankelijkheden met
ncc
om het repository schoon te houden - Documenteer invoer- en uitvoerwaarden duidelijk in je
action.yml
- Houd rekening met beveiligingsimplicaties en masker gevoelige waarden met
core.setSecret()
Deze authenticatieactie zal worden gebruikt door onze volgende actie die geheimen ophaalt. Laten we doorgaan met het maken van die actie.
Stap 3: Het maken van de “Geheime ophalen” Actie
Je hebt tot nu toe het zware werk gedaan. Je weet nu hoe je een aangepaste Github-actie kunt maken. Als je meedoet, moet je nu die stappen herhalen voor de DVLS geheime invoeractie zoals hieronder beschreven:
De Actie Structuur
dvls-actions/ ├── get-secret-entry/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Het index.js Bestand
// 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'
Optimaliseer de Actie
- Compileer het indexbestand.
npm i -g @vercel/ncc ncc build index.js --license licenses.txt
- Update
action.yml
om naar het gebundelde bestand te verwijzen:runs: using: 'node20' main: 'dist/index.js' # Bijgewerkt om de gebundelde versie te gebruiken
- Opruimen:
rm -rf node_modules # Verwijder de node_modules directory
- Commit de bestanden naar de gedeelde repo.
git add . git commit -m "Initiële commit van DVLS get secret entry actie" git push
Het Eindresultaat
Op dit punt zou je twee GitHub-repos moeten hebben:
- de repo met de workflow die je had met GitHub-secrets
- de gedeelde repo (ervan uitgaande dat de naam dvls-actions is) die de twee acties bevat met een structuur die er als volgt uitziet:
dvls-actions/ ├── login/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md ├── get-secret-entry/ │ ├── index.js │ ├── action.yml │ ├── package.json │ └── README.md
Gebruik van de Aangepaste Acties
Zodra je deze aangepaste acties hebt ingesteld, kun je ze gebruiken in je oorspronkelijke aanroepworkflow.
Oorspronkelijke workflow:
- Gebruikt een enkele stap om een Slack-notificatie te verzenden
- Verwijst direct naar de webhook-URL vanuit secrets (
secrets.SLACK_WEBHOOK_URL
)
Nieuwe workflow:
- Voegt een authenticatiestap toe met behulp van de aangepaste DVLS login actie
- Haal de Slack webhook-URL veilig op van Devolutions Server
- Gebruik omgeving variabelen in plaats van secrets
- Behoudt dezelfde meldingsfunctionaliteit maar met verbeterde beveiliging
De nieuwe workflow voegt twee stappen toe vóór de Slack-melding:
- Authenticatie met Devolutions Server met behulp van de
dvls-login
-actie - Ophalen van de Slack webhook-URL met behulp van de
dvls-get-secret-entry
-actie - De uiteindelijke Slack-meldingsstap blijft vergelijkbaar maar maakt gebruik van de opgehaalde webhook-URL uit een omgevingsvariabele (
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:" }'
Het creëren van aangepaste GitHub-acties stelt u in staat om uw workflows te standaardiseren en te beveiligen over meerdere repositories. Door gevoelige bewerkingen zoals authenticatie en het ophalen van geheimen naar toegewijde acties te verplaatsen, kunt u:
- Betere beveiligingspraktijken handhaven door het centraliseren van referentiebeheer
- Code duplicatie verminderen over verschillende workflows
- Workflow-onderhoud en updates vereenvoudigen
- Zorgen voor consistente implementatie van kritieke bewerkingen
Het voorbeeld van het integreren van Devolutions Server met GitHub-acties toont hoe aangepaste acties de kloof tussen verschillende tools kunnen overbruggen terwijl beveiligingsbest practices worden gehandhaafd. Deze aanpak kan worden aangepast voor verschillende andere integraties en use cases in uw DevOps-workflows.
Source:
https://adamtheautomator.com/custom-github-actions-guide/