Duplikasi konfigurasi CI/CD di banyak repositori menimbulkan masalah pemeliharaan: perubahan kecil pada tahap linting, testing, atau deployment mengharuskan pembaruan file konfigurasi secara manual di puluhan tempat. GitHub Actions menyediakan Reusable Workflows (melalui trigger workflow_call) untuk mendistribusikan satu definisi pipeline standar ke seluruh repositori dalam organisasi.

Reusable Workflows vs Composite Actions

GitHub Actions menawarkan dua mekanisme modularisasi: Reusable Workflows dan Composite Actions. Pemilihan fitur ini harus disesuaikan dengan kebutuhan abstraksi dan lingkup eksekusi pipeline.

  • Reusable Workflows (workflow_call): Mengabstraksi seluruh job atau multiple jobs. Fitur ini mendukung isolasi runner berbeda untuk tiap job, deployment environments, secret management tingkat job, integrasi matrix build, dan konkurensi native.
  • Composite Actions: Mengabstraksi kumpulan steps di dalam satu job yang sama. Seluruh step berjalan pada runner yang sama, berbagi disk, dan tidak dapat memisahkan job dependency atau mendefinisikan matrix lintas step secara independen.

Gunakan Reusable Workflows ketika Anda ingin mendefinisikan end-to-end pipeline standar organisasi (misal: build, security scan, lalu deploy). Gunakan Composite Actions jika Anda hanya ingin mengemas urutan command pendek yang dapat dipakai ulang di dalam satu step eksekusi (misal: setup runtime dan cache dependency).

Implementasi: Target (Called) Workflow

Target workflow diletakkan pada repositori terpusat (contoh: acme-corp/shared-workflows). File ditempatkan di direktori .github/workflows/. Target workflow menggunakan trigger workflow_call, mendefinisikan inputs, secrets, dan outputs.

# File: .github/workflows/node-build-test.yml pada repositori shared-workflows
name: Reusable Node CI

on:
  workflow_call:
    inputs:
      node-version:
        required: false
        type: string
        default: '20'
      run-linter:
        required: false
        type: boolean
        default: true
    secrets:
      NPM_TOKEN:
        required: false
      SONAR_TOKEN:
        required: true
    outputs:
      build-artifact-name:
        description: "Nama artifact hasil build"
        value: ${{ jobs.build.outputs.artifact-name }}

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        if: ${{ inputs.run-linter }}
        run: npm run lint

      - name: Run Test Suite
        run: npm test
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

  build:
    needs: lint-and-test
    runs-on: ubuntu-latest
    outputs:
      artifact-name: ${{ steps.export-name.outputs.name }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}

      - name: Build Application
        run: npm run build

      - name: Export Artifact Identifier
        id: export-name
        run: echo "name=build-${{ github.sha }}" >> $GITHUB_OUTPUT

Implementasi: Caller Workflow

Repositori aplikasi downstream memanggil workflow terpusat menggunakan sintaks uses: {owner}/{repo}/.github/workflows/{filename}@{ref}. Nilai ref dapat berupa branch, commit SHA, atau Git release tag.

# File: .github/workflows/ci.yml pada repositori downstream (misal: frontend-app)
name: Application CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  run-shared-pipeline:
    uses: acme-corp/shared-workflows/.github/workflows/[email protected]
    with:
      node-version: '20'
      run-linter: true
    secrets:
      SONAR_TOKEN: ${{ secrets.DOWNSTREAM_SONAR_TOKEN }}
      NPM_TOKEN: ${{ secrets.DOWNSTREAM_NPM_TOKEN }}

  notify:
    needs: run-shared-pipeline
    runs-on: ubuntu-latest
    steps:
      - name: Log artifact name from shared workflow
        run: |
          echo "Build output: ${{ needs.run-shared-pipeline.outputs.build-artifact-name }}"

Manajemen Secrets: Explicit vs Inherit

Terdapat dua cara mendistribusikan credentials dari caller ke reusable workflow:

1. Explicit Secret Passing

Secret dipetakan satu per satu via blok secrets: pada caller workflow. Pendekatan ini mengikuti prinsip least privilege, karena workflow target hanya memiliki akses terhadap variabel yang secara eksplisit dideklarasikan.

2. Secret Inheritance (secrets: inherit)

Caller meneruskan semua organization dan repository secrets langsung ke target workflow menggunakan sintaks:

jobs:
  call-pipeline:
    uses: acme-corp/shared-workflows/.github/workflows/node-build-test.yml@v1
    secrets: inherit
Peringatan Keamanan: Penggunaan secrets: inherit mengizinkan reusable workflow mengakses seluruh secrets yang terlihat oleh caller repository. Jika repositori workflow terpusat dikelola oleh tim berbeda atau mengonsumsi reusable actions dari pihak ketiga, pastikan review integritas kode diperketat guna mencegah eksfiltrasi secret.

Strategi Semantic Versioning untuk Mencegah Breaking Change

Pembaruan pada repositori reusable workflow tidak boleh merusak (break) pipeline downstream yang sedang berjalan. Terapkan konvensi Semantic Versioning (vMAJOR.MINOR.PATCH) dengan mekanisme Git tags:

  • Major Pinning (@v1): Caller mereferensikan major tag. Tim platform memperbarui tag v1 secara otomatis (memindahkan pointer git tag) setiap kali ada release versi patch (v1.0.1) atau minor non-breaking (v1.1.0). Caller mendapatkan fitur dan bugfix tanpa intervensi.
  • Exact Tag Pinning (@v1.2.0): Direkomendasikan untuk aplikasi dengan regulasi ketat atau pipeline deployment produksi yang memerlukan determinisme penuh.
  • Commit SHA Pinning (@hash): Cara paling aman untuk mitigasi serangan supply-chain, namun memerlukan otomasi seperti Dependabot atau Renovate untuk pembaruan berkala.

Aturan Depresiasi Parameter

Ketika menambahkan atau mengubah parameter pada target workflow:

  1. Jangan mengubah input yang sudah ada menjadi required: true dalam major release yang sama.
  2. Jika parameter lama diganti, pertahankan parameter lama dengan status opsional dan fallback nilai ke parameter baru selama masa transisi.
  3. Naikkan versi ke major berikutnya (misal: v2.0.0) jika terjadi perubahan struktur output atau penghapusan input lama.

Aksesibilitas Repositori dan Batasan Teknis

  • Tingkat Akses Repositori: Agar repositori privat downstream dapat mengakses target workflow, repositori shared-workflows harus diatur sebagai Internal (jika menggunakan GitHub Enterprise) atau berada dalam organisasi yang sama dengan permission Action access diubah ke: "Accessible from repositories in the 'acme-corp' organization" pada menu Settings > Actions > General.
  • Nesting Limit: Reusable workflow dapat memanggil reusable workflow lain hingga kedalaman maksimum 4 tingkat. Pemanggilan melingkar (circular dependency) akan ditolak oleh parser GitHub Actions saat kompilasi pipeline.
  • Environment Variables: Variabel level workflow (env: di root caller) tidak diwariskan ke reusable workflow. Nilai harus dilewatkan secara eksplisit via inputs.