Integration With GitHub Actions
How to integrate Plural with GitHub Actions and other CI providers
Plural is generally meant to implement a GitOps-based workflow that is somewhat tangential to general CI strategies. The main point of friction is that CI usually runs against commits to application code, typically resulting in a complete Docker image build, but GitOps workflows require a follow-on commit in a secondary infrastructure config repo (or subsection of a monorepo).
That follow-on step is rarely automated, so teams end up manually managing config files and executing tedious processes. You also don't want to compromise the security of your GitOps controller by exposing its credentials to a public CI solution like GitHub Actions. Plural takes the following approach:
- You can delegate federated credentials to issue temporary JWTs against your Plural instance to authenticate, with scopes usually limited to minimal changes (e.g. create a PR)
- We provide prebuilt actions to make integration as simple as possible.
Let's show how it's done.
Create a federated credential
The first step is creating a federated credential to allow a token exchange between GitHub's OIDC provider and Plural:
apiVersion: deployments.plural.sh/v1alpha1
kind: FederatedCredential
metadata:
name: gh-actions
spec:
issuer: https://token.actions.githubusercontent.com # the oidc issuer url for gh actions, can be swapped for any other platform with oidc federation support
user: ci-bot@example.com # should point to the user email you want the federated credential to auth as
# aud: https://console.example.com # optional. When set, only tokens with this audience are exchanged
scopes:
- catalog.write
claimsLike:
sub: "repo:pluralsh/console:ref:refs/heads/master" # any regex is supported, and must match the whole claimEach claimsLike value is a regex matched against the corresponding claim in the incoming JWT, and every listed claim must match. Regexes are always anchored, so they must match the entire claim value: the pattern above won't accept tokens from a branch like master-evil. Use .* explicitly where you want a wildcard, e.g. repo:pluralsh/console:ref:refs/heads/.*. Claims whose value is a list, like aud, match if any element matches.
aud is optional. Set it to the audience this credential should accept, and tokens minted for a different audience are rejected before they are exchanged. Leave it unset to keep accepting any audience. A token from a trusted issuer that was minted for another service can otherwise be replayed here.
Define the PR Automation
In this case, we're going to have GitHub Actions trigger a PR automation, though it can also perform other API actions, like kicking off a Plural Pipeline. To show how it works, here's an example PR automation spec:
apiVersion: deployments.plural.sh/v1alpha1
kind: PrAutomation
metadata:
name: console-updater
spec:
name: console-updater
documentation: Updates the console image version
updates:
yamlOverlays:
- file: "bootstrap/console.yaml"
yaml: |
spec:
helm:
values:
image:
tag: {{ context.tag }}
scmConnectionRef:
name: plural
title: "Updating console to use {{ context.tag }}"
message: "Updating console to use {{ context.tag }}"
identifier: mgmt
configuration:
- name: tag
type: STRING
documentation: The new image tag to useNotice this is just overlaying some templated YAML on the existing bootstrap/console.yaml file, which deploys our console service. Nothing too crazy; image updates are usually simple.
Define the GitHub Action to trigger a PR
Your GitHub Actions workflow file can then look something like this; in this case, it runs on all pushes to master:
name: CI / Trigger PR
env:
# this configures docker/metadata-action to use the true commit sha when tagging
DOCKER_METADATA_PR_HEAD_SHA: 'true'
on:
push:
branches:
- master
jobs:
pr:
permissions:
id-token: write # this is necessary to get a gh id token
contents: read
packages: write
name: Generate PR
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
################################################################
# Boilerplate to build a docker image, feel free to skip below #
################################################################
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
# list of Docker images to use as base name for tags
images: ghcr.io/pluralsh/console
# generate Docker tags based on the following events/attributes
tags: |
type=sha
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Test Build console image
uses: docker/build-push-action@v3
with:
context: "."
file: "./Dockerfile"
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64
cache-from: type=gha
cache-to: type=gha,mode=max
###############################################################
# Plural CLI Setup and trigger-pull-request action invocation #
###############################################################
- name: setup plural
id: plural
uses: pluralsh/setup-plural@v2
with:
email: ci-bot@example.com # must match the user on the federated credential
consoleUrl: https://my.console.cloud.plural.sh
- name: Set outputs
id: sha
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT # unfortunately gh actions doesn't expose this natively lol
- name: Trigger PR
uses: pluralsh/trigger-pull-request@v2
with:
url: https://my.console.cloud.plural.sh
token: ${{ steps.plural.outputs.consoleToken }}
branch: plrl/console/update-${{ steps.sha.outputs.sha_short }}
prAutomation: console-updater # the pr automation name above
context: |
{
"tag": "sha-${{ steps.sha.outputs.sha_short }}"
}