Aangepaste GitHub-acties maken: Een complete gids voor DevOps-teams

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:

  1. Verbinden met de externe service
  2. Authenticeren
  3. Specifieke bewerkingen uitvoeren
  4. 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:

  1. Een openbare GitHub repository voor gedeelde acties maakt
  2. 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
  3. 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:

  1. DVLS-omgeving voorbereiden
    • Bijbehorende geheimen in DVLS maken
    • DVLS API-eindpunten testen voor authenticatie en geheimen ophalen
  2. 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
  3. 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 authenticatie
  • DVLS_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:

  1. Authenticeren bij DVLS met behulp van app-gegevens
  2. Ontvang de kluisinformatie met behulp van het geretourneerde token
  3. Zoek de specifieke invoer-ID voor ons geheim
  4. 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:

  1. Identificeer de API-eindpunten waarmee je moet communiceren
  2. Test het authenticatie- en gegevensophaalproces
  3. 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:

  1. 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.

  1. 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"
        }
    }
    
  2. 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 en package-lock.json naar versiebeheer zullen committen, zullen we uiteindelijk de node_modules map uitsluiten door ncc te gebruiken om onze afhankelijkheden te bundelen.

  3. 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 is
      • app_key en app_secret: Authenticatie referenties
      • output_variable: Waar het resulterende token op te slaan
    • runs: Specificeert hoe de actie uitgevoerd moet worden:
      • using: 'node20': Maakt gebruik van Node.js versie 20
      • main: '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
  1. Installeer ncc:
    npm i -g @vercel/ncc
    
  2. Bouw de gebundelde versie:
    ncc build index.js --license licenses.txt
    
  3. Update action.yml om naar het gebundelde bestand te verwijzen:
    runs:
      using: 'node20'
      main: 'dist/index.js'  # Bijgewerkt om de gebundelde versie te gebruiken
    
  4. Opruimen:
    rm -rf node_modules  # Verwijder de node_modules directory
    
  5. 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’
    een
    andere 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:

  1. Implementeer altijd grondige foutafhandeling en logging
  2. Gebruik het @actions/core pakket voor een goede integratie met GitHub Actions
  3. Bundel afhankelijkheden met ncc om het repository schoon te houden
  4. Documenteer invoer- en uitvoerwaarden duidelijk in je action.yml
  5. 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

  1. Compileer het indexbestand.
    npm i -g @vercel/ncc
    ncc build index.js --license licenses.txt
    
  2. Update action.yml om naar het gebundelde bestand te verwijzen:
    runs:
      using: 'node20'
      main: 'dist/index.js'  # Bijgewerkt om de gebundelde versie te gebruiken
    
  3. Opruimen:
    rm -rf node_modules  # Verwijder de node_modules directory
    
  4. 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:

  1. Authenticatie met Devolutions Server met behulp van de dvls-login-actie
  2. Ophalen van de Slack webhook-URL met behulp van de dvls-get-secret-entry-actie
  3. 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/