Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,27 @@ jobs:

### Deployment tracking annotations

Whenever the action updates a GitOps file, it stamps the following annotations onto the manifest's `metadata.annotations`:
By default (`deployment-annotations: 'true'`), whenever the action updates a GitOps file it stamps the following annotations onto the manifest's `metadata.annotations`:

| Annotation | Value |
|------------|-------|
| `deploy.staffbase.com/repositoryFullName` | The source repository in `owner/repo` form (`$GITHUB_REPOSITORY`) |
| `deploy.staffbase.com/commitSha` | The commit SHA being deployed (`$GITHUB_SHA`) |
| `deploy.staffbase.com/version` | The image tag written to the GitOps repo — always the **non-timestamped** tag: `dev-<short-sha>` on `dev`, `main-<short-sha>` on `main`, `master-<short-sha>` on `master`, the version without the leading `v` on `v*` tag pushes, and the tag name on other tag pushes. See [GitOps tag](#gitops-tag) below |

These keys mirror the [Swarmia Deployment API](https://help.swarmia.com/settings/organization/configuring-deployments-in-swarmia) field names and are read by `flux-deployment-reporter` to report deployments to Swarmia once Flux finishes reconciling.
These keys mirror the [Swarmia Deployment API](https://help.swarmia.com/settings/organization/configuring-deployments-in-swarmia) field names and are read by `flux-deployment-reporter` to report deployments to Swarmia once Flux finishes reconciling. Set `deployment-annotations: 'false'` to skip them. The `deploy.staffbase.com` namespace is configurable via [`deployment-domain`](#inputs).

### Deployment tracking labels

Enabled by default (`deployment-labels: 'true'`). The same three values are stamped onto the **built Docker image** as OCI labels, using reverse-DNS keys — the [`deployment-domain`](#inputs) reversed (`deploy.staffbase.com` → `com.staffbase.deploy`):

| Label | Value |
|-------|-------|
| `com.staffbase.deploy.repositoryFullName` | The source repository in `owner/repo` form (`$GITHUB_REPOSITORY`) |
| `com.staffbase.deploy.commitSha` | The commit SHA being deployed (`$GITHUB_SHA`) |
| `com.staffbase.deploy.version` | The **non-timestamped** GitOps tag (same value as the `deploy.staffbase.com/version` annotation) |

> **Note:** labels are baked in at **build time**, so they are only applied on builds. Release (`v*`) and custom-tag runs that **retag** an existing image instead of rebuilding (see [Image tags](#image-tags--flux-image-automation)) do not get fresh labels — the retagged image keeps the labels from the branch build it was promoted from. This feature is independent of the annotations above; enable either, both, or neither.

## Inputs

Expand All @@ -138,6 +150,9 @@ These keys mirror the [Swarmia Deployment API](https://help.swarmia.com/settings
| `docker-build-platforms` | Sets the target platforms for build | linux/amd64 |
| `docker-build-provenance` | Generate [provenance](https://docs.docker.com/build/attestations/slsa-provenance/) attestation for the build | `false` |
| `docker-disable-retagging` | Disables retagging of existing images and run a new build instead | `false` |
| `deployment-annotations` | Stamp deployment-tracking annotations (`deploy.staffbase.com/*`) onto updated GitOps manifests. See [Deployment tracking annotations](#deployment-tracking-annotations) | `true` |
| `deployment-domain` | Key namespace for deployment-tracking metadata. Used verbatim for annotation keys (`<domain>/...`) and reversed to reverse-DNS for label keys (`com.staffbase.deploy.*`) | `deploy.staffbase.com` |
| `deployment-labels` | Stamp deployment-tracking labels (`com.staffbase.deploy.*`) onto the built image. Only applied on builds, not on release/custom retags. See [Deployment tracking labels](#deployment-tracking-labels) | `true` |
| `gitops-organization` | GitHub Organization for GitOps | `Staffbase` |
| `gitops-repository` | GitHub Repository for GitOps | `mops` |
| `gitops-user` | GitHub User for GitOps | `Staffbot` |
Expand Down
25 changes: 25 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ inputs:
description: 'Disable retagging of existing images'
required: false
default: 'false'
deployment-annotations:
description: 'Stamp deployment-tracking annotations (deploy.staffbase.com/repositoryFullName, /commitSha, /version) onto the updated GitOps manifests. Enabled by default; set to ''false'' to skip.'
required: false
default: 'true'
deployment-domain:
description: 'Domain used as the key namespace for deployment-tracking metadata. Applied verbatim to GitOps annotation keys (<domain>/repositoryFullName) and reversed to reverse-DNS for image label keys (deploy.staffbase.com -> com.staffbase.deploy.repositoryFullName).'
required: false
default: 'deploy.staffbase.com'
deployment-labels:
description: 'Stamp deployment-tracking labels (repositoryFullName, commitSha, version) onto the built Docker image. Enabled by default; only applied on builds, not on release/custom retags. Set to ''false'' to skip.'
required: false
default: 'true'
gitops-organization:
description: 'GitHub Organization for GitOps'
required: true
Expand Down Expand Up @@ -127,6 +139,13 @@ runs:
INPUT_DOCKER_IMAGE: ${{ inputs.docker-image }}
run: ${{ github.action_path }}/scripts/generate-tags.sh

- name: Generate Deployment Label Prefix
id: deployment_labels
shell: bash
env:
INPUT_DEPLOYMENT_DOMAIN: ${{ inputs.deployment-domain }}
run: ${{ github.action_path }}/scripts/generate-label-prefix.sh

- name: Verify Architecture Match
shell: bash
if: steps.preparation.outputs.build == 'true'
Expand Down Expand Up @@ -158,6 +177,10 @@ runs:
target: ${{ inputs.docker-build-target }}
build-args: ${{ inputs.docker-build-args }}
tags: ${{ steps.preparation.outputs.tag_list }}
labels: |
${{ inputs.deployment-labels == 'true' && format('{0}.repositoryFullName={1}', steps.deployment_labels.outputs.label_prefix, github.repository) || '' }}
${{ inputs.deployment-labels == 'true' && format('{0}.commitSha={1}', steps.deployment_labels.outputs.label_prefix, github.sha) || '' }}
${{ inputs.deployment-labels == 'true' && format('{0}.version={1}', steps.deployment_labels.outputs.label_prefix, steps.preparation.outputs.gitops_tag) || '' }}
secrets: ${{ inputs.docker-build-secrets }}
secret-files: ${{ inputs.docker-build-secret-files }}
platforms: ${{ inputs.docker-build-platforms }}
Expand Down Expand Up @@ -205,6 +228,8 @@ runs:
INPUT_GITOPS_DEV: ${{ inputs.gitops-dev }}
INPUT_GITOPS_STAGE: ${{ inputs.gitops-stage }}
INPUT_GITOPS_PROD: ${{ inputs.gitops-prod }}
INPUT_DEPLOYMENT_ANNOTATIONS: ${{ inputs.deployment-annotations }}
INPUT_DEPLOYMENT_DOMAIN: ${{ inputs.deployment-domain }}
run: ${{ github.action_path }}/scripts/update-gitops.sh

- name: Emit Image Build Event to Upwind.io
Expand Down
14 changes: 14 additions & 0 deletions scripts/generate-label-prefix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Computes the reverse-DNS key prefix for deployment-tracking image labels,
# derived from the deployment domain (deploy.staffbase.com -> com.staffbase.deploy).
# Emitted as the `label_prefix` output and consumed by the Build step's image
# `labels` input. The matching GitOps annotations use the forward "<domain>/..."
# form instead (see scripts/lib/gitops-functions.sh).
#
# Optional env vars: INPUT_DEPLOYMENT_DOMAIN (defaults to "deploy.staffbase.com")

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/common.sh
source "${SCRIPT_DIR}/lib/common.sh"

set_output "label_prefix" "$(reverse_domain "${INPUT_DEPLOYMENT_DOMAIN:-deploy.staffbase.com}")"
17 changes: 16 additions & 1 deletion scripts/lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,23 @@ set_output() {
fi
}

# --- Validation ---
# reverse_domain turns a dotted domain into reverse-DNS order
# (deploy.staffbase.com -> com.staffbase.deploy). Used to derive OCI image
# label keys from the same deployment domain used verbatim for GitOps annotations.
reverse_domain() {
local domain="$1"
local IFS='.'
local -a parts
read -ra parts <<< "$domain"
local out="" i
for (( i=${#parts[@]}-1; i>=0; i-- )); do
out+="${parts[i]}"
(( i > 0 )) && out+="."
done
printf '%s' "$out"
}

# --- Validation ---
require_env() {
local var_name="$1"
if [[ -z "${!var_name:-}" ]]; then
Expand Down
15 changes: 11 additions & 4 deletions scripts/lib/gitops-functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
# INPUT_DOCKER_REGISTRY, INPUT_DOCKER_IMAGE, INPUT_TAG, INPUT_PUSH,
# INPUT_GITOPS_USER, INPUT_GITOPS_TOKEN,
# INPUT_GITOPS_ORGANIZATION, INPUT_GITOPS_REPOSITORY,
# INPUT_DEPLOYMENT_ANNOTATIONS (optional, defaults to "true"),
# INPUT_DEPLOYMENT_DOMAIN (optional, defaults to "deploy.staffbase.com"),
# GITHUB_REPOSITORY, GITHUB_SHA, IMAGE

push_to_gitops_repo() {
Expand Down Expand Up @@ -48,10 +50,15 @@ update_file() {
fi
fi

echo "Writing deployment annotations to ${file}"
yq -i '.metadata.annotations["deploy.staffbase.com/repositoryFullName"] = "'"${GITHUB_REPOSITORY}"'"' "${file}"
yq -i '.metadata.annotations["deploy.staffbase.com/commitSha"] = "'"${GITHUB_SHA}"'"' "${file}"
yq -i '.metadata.annotations["deploy.staffbase.com/version"] = "'"${INPUT_TAG}"'"' "${file}"
if [[ "${INPUT_DEPLOYMENT_ANNOTATIONS:-true}" == "true" ]]; then
local domain="${INPUT_DEPLOYMENT_DOMAIN:-deploy.staffbase.com}"
echo "Writing deployment annotations to ${file}"
yq -i '.metadata.annotations["'"${domain}"'/repositoryFullName"] = "'"${GITHUB_REPOSITORY}"'"' "${file}"
yq -i '.metadata.annotations["'"${domain}"'/commitSha"] = "'"${GITHUB_SHA}"'"' "${file}"
yq -i '.metadata.annotations["'"${domain}"'/version"] = "'"${INPUT_TAG}"'"' "${file}"
else
echo "Deployment annotations disabled (INPUT_DEPLOYMENT_ANNOTATIONS != true); skipping"
fi
}

process_file_updates() {
Expand Down
27 changes: 27 additions & 0 deletions tests/generate-label-prefix.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bats

load 'test_helper/setup'

SCRIPT="${BATS_TEST_DIRNAME}/../scripts/generate-label-prefix.sh"

setup() {
setup_common
}

teardown() {
teardown_common
}

@test "label_prefix defaults to reversed deploy.staffbase.com" {
unset INPUT_DEPLOYMENT_DOMAIN
run "$SCRIPT"
assert_success
assert_output_value "label_prefix" "com.staffbase.deploy"
}

@test "label_prefix reverses a custom deployment domain" {
export INPUT_DEPLOYMENT_DOMAIN="deploy.example.org"
run "$SCRIPT"
assert_success
assert_output_value "label_prefix" "org.example.deploy"
}
20 changes: 20 additions & 0 deletions tests/lib-common.bats
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@ teardown() {
assert_output "OUTPUT key=value"
}

# --- reverse_domain ---

@test "reverse_domain reverses a three-part domain" {
run reverse_domain "deploy.staffbase.com"
assert_success
assert_output "com.staffbase.deploy"
}

@test "reverse_domain reverses an arbitrary domain" {
run reverse_domain "deploy.example.org"
assert_success
assert_output "org.example.deploy"
}

@test "reverse_domain leaves a single segment unchanged" {
run reverse_domain "localhost"
assert_success
assert_output "localhost"
}

# --- require_env ---

@test "require_env succeeds when variable is set" {
Expand Down
40 changes: 40 additions & 0 deletions tests/lib-gitops-functions.bats
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,46 @@ EOF
! grep -qE 'deploy\.staffbase\.com/(repo|sha)"' "${TEST_TEMP_DIR}/yq_calls.log"
}

@test "update_file writes annotations when INPUT_DEPLOYMENT_ANNOTATIONS is unset (default on)" {
unset INPUT_DEPLOYMENT_ANNOTATIONS
update_file "deployment.yaml" "spec.image" "$IMAGE"
grep -q 'deploy.staffbase.com/repositoryFullName' "${TEST_TEMP_DIR}/yq_calls.log"
}

@test "update_file skips annotations when INPUT_DEPLOYMENT_ANNOTATIONS is false" {
export INPUT_DEPLOYMENT_ANNOTATIONS="false"
update_file "deployment.yaml" "spec.image" "$IMAGE"
! grep -q 'deploy.staffbase.com/' "${TEST_TEMP_DIR}/yq_calls.log"
}

@test "update_file still updates the image field when annotations are disabled" {
cat > "${TEST_TEMP_DIR}/mocks/yq" << 'YQ_MOCK'
#!/usr/bin/env bash
echo "yq $*" >> "${MOCK_CALLS_DIR}/yq_calls.log"
if [[ "$*" == *"| type"* ]]; then echo "!!str"; fi
exit 0
YQ_MOCK
chmod +x "${TEST_TEMP_DIR}/mocks/yq"
export INPUT_DEPLOYMENT_ANNOTATIONS="false"
update_file "deployment.yaml" "spec.image" "$IMAGE"
grep -q "${IMAGE}" "${TEST_TEMP_DIR}/yq_calls.log"
}

@test "update_file uses default deploy.staffbase.com domain when unset" {
unset INPUT_DEPLOYMENT_DOMAIN
update_file "deployment.yaml" "spec.image" "$IMAGE"
grep -q 'deploy.staffbase.com/repositoryFullName' "${TEST_TEMP_DIR}/yq_calls.log"
}

@test "update_file uses a custom INPUT_DEPLOYMENT_DOMAIN for annotation keys" {
export INPUT_DEPLOYMENT_DOMAIN="deploy.example.org"
update_file "deployment.yaml" "spec.image" "$IMAGE"
grep -q 'deploy.example.org/repositoryFullName' "${TEST_TEMP_DIR}/yq_calls.log"
grep -q 'deploy.example.org/commitSha' "${TEST_TEMP_DIR}/yq_calls.log"
grep -q 'deploy.example.org/version' "${TEST_TEMP_DIR}/yq_calls.log"
! grep -q 'deploy.staffbase.com/' "${TEST_TEMP_DIR}/yq_calls.log"
}

# --- commit_changes ---

@test "commit_changes commits and pushes when push is true" {
Expand Down
Loading