Skip to content
Merged
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
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Whenever the action updates a GitOps file, it stamps the following annotations o
|------------|-------|
| `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 deployed image tag — `dev-<short-sha>` on `dev`, `main-<short-sha>` on `main`/`master`, the tag name on tag pushes |
| `deploy.staffbase.com/version` | The deployed image tag — `dev-<short-sha>` on `dev`, `main-<short-sha>` on `main`, `master-<short-sha>` on `master` (with `docker-tag-timestamp` a UTC timestamp is inserted before the SHA), the version without the leading `v` on `v*` tag pushes, and the tag name on other tag pushes |

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.

Expand All @@ -126,6 +126,7 @@ These keys mirror the [Swarmia Deployment API](https://help.swarmia.com/settings
| `docker-registry-api` | Docker Registry API (used for retagging without pulling) | `https://registry.staffbase.com/v2/` |
| `docker-image` | Docker Image | |
| `docker-custom-tag` | Docker Custom Tag to be set on the image | |
| `docker-tag-timestamp` | Insert a UTC timestamp into `dev`/`main`/`master` branch tags (`dev-<timestamp>-<short-sha>`) to make them sortable for Flux image automation | `false` |
| `docker-username` | Username for the Docker Registry | |
| `docker-password` | Password for the Docker Registry | |
| `docker-file` | Dockerfile | `./Dockerfile` |
Expand Down Expand Up @@ -153,6 +154,49 @@ These keys mirror the [Swarmia Deployment API](https://help.swarmia.com/settings
| `docker-digest` | Digest of the image |
| `docker-tag` | Tag of the image |

## Image tags & Flux image automation

The generated image tag depends on the Git ref:

| Ref | Tag (default) | Tag (`docker-tag-timestamp: 'true'`) | Floating tag |
|-----|---------------|--------------------------------------|--------------|
| `dev` branch | `dev-<short-sha>` | `dev-<utc-timestamp>-<short-sha>` | `dev` |
| `main` branch | `main-<short-sha>` | `main-<utc-timestamp>-<short-sha>` | `main` |
| `master` branch | `master-<short-sha>` | `master-<utc-timestamp>-<short-sha>` | `master` |
| `v*` tag (prod) | the version, e.g. `2025.50.14` (CalVer) | _(unchanged)_ | `latest` |
| other branch | `<short-sha>` (not pushed) | _(unchanged)_ | — |

By default branch tags keep the legacy `<prefix>-<short-sha>` shape. Set
`docker-tag-timestamp: 'true'` to insert a `YYYYMMDDHHMMSS` (UTC) timestamp before
the SHA. This makes branch tags **sortable** so
[Flux image automation](https://fluxcd.io/flux/components/image/) can pick the
newest build — the Git SHA alone is not orderable. The short SHA is kept for
traceability and Flux sorts on the timestamp only.

With the timestamp enabled, use one `ImagePolicy` per environment, filtering by prefix:

```yaml
# dev (and likewise main-/master- for stage)
spec:
imageRepositoryRef: { name: my-service }
filterTags:
pattern: '^dev-(?P<ts>[0-9]+)-[0-9a-f]+$'
extract: '$ts'
policy:
numerical: { order: asc }
---
# prod — CalVer tags parse as SemVer (no zero-padding!)
spec:
imageRepositoryRef: { name: my-service }
policy:
semver: { range: '>=0.0.0' }
```

> **Note:** the prod `semver` policy only works if CalVer parts are never
> zero-padded (`2025.5.3`, not `2025.05.03`) — SemVer forbids leading zeros.
> Track the immutable `*-<timestamp>-<sha>` tags, not the floating `dev`/`main`
> tags, so deployments keep their provenance.

## Contributing

Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ inputs:
docker-custom-tag:
description: 'Docker Custom Tag'
required: false
docker-tag-timestamp:
description: 'Insert a UTC timestamp into dev/main/master branch tags (e.g. dev-20260602143055-<short-sha>) to make them sortable for Flux image automation. Opt-in; defaults to the legacy <prefix>-<short-sha> format.'
required: false
default: 'false'
Comment thread
monotek marked this conversation as resolved.
docker-username:
description: 'Username for the Docker Registry'
required: false
Expand Down Expand Up @@ -112,6 +116,7 @@ runs:
shell: bash
env:
INPUT_DOCKER_CUSTOM_TAG: ${{ inputs.docker-custom-tag }}
INPUT_DOCKER_TAG_TIMESTAMP: ${{ inputs.docker-tag-timestamp }}
INPUT_DOCKER_DISABLE_RETAGGING: ${{ inputs.docker-disable-retagging }}
INPUT_DOCKER_REGISTRY: ${{ inputs.docker-registry }}
INPUT_DOCKER_IMAGE: ${{ inputs.docker-image }}
Expand Down
26 changes: 23 additions & 3 deletions scripts/generate-tags.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,41 @@ require_env INPUT_DOCKER_IMAGE

BUILD="true"

# branch_tag builds the immutable tag for an environment branch.
#
# When INPUT_DOCKER_TAG_TIMESTAMP is "true" the tag gets a UTC timestamp inserted
# before the short SHA (e.g. dev-20260602143055-abcdef12). This makes branch tags
# sortable by Flux image automation (numerical policy) — the git SHA alone is not
# orderable, so Flux cannot otherwise tell which build is newest. The SHA is kept
# for traceability. When the flag is unset/false the legacy <prefix>-<sha> shape
# is produced, so existing consumers are unaffected.
#
# The timestamp is overridable via BUILD_TIMESTAMP for deterministic tests.
branch_tag() {
local prefix="$1"
if [[ "${INPUT_DOCKER_TAG_TIMESTAMP:-false}" == "true" ]]; then
local ts="${BUILD_TIMESTAMP:-$(date -u +%Y%m%d%H%M%S)}"
echo "${prefix}-${ts}-${GITHUB_SHA::8}"
else
echo "${prefix}-${GITHUB_SHA::8}"
fi
}

if [[ -n "${INPUT_DOCKER_CUSTOM_TAG:-}" ]]; then
TAG="${INPUT_DOCKER_CUSTOM_TAG}"
LATEST="latest"
PUSH="true"
BUILD="${INPUT_DOCKER_DISABLE_RETAGGING:-false}"
elif [[ $GITHUB_REF == refs/heads/master ]]; then
TAG="master-${GITHUB_SHA::8}"
TAG="$(branch_tag master)"
LATEST="master"
PUSH="true"
elif [[ $GITHUB_REF == refs/heads/main ]]; then
TAG="main-${GITHUB_SHA::8}"
TAG="$(branch_tag main)"
LATEST="main"
PUSH="true"
elif [[ $GITHUB_REF == refs/heads/dev ]]; then
TAG="dev-${GITHUB_SHA::8}"
TAG="$(branch_tag dev)"
LATEST="dev"
PUSH="true"
elif [[ $GITHUB_REF == refs/tags/v* ]]; then
Expand Down
58 changes: 58 additions & 0 deletions tests/generate-tags.bats
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ setup() {
export INPUT_DOCKER_IMAGE="my-service"
export INPUT_DOCKER_CUSTOM_TAG=""
export INPUT_DOCKER_DISABLE_RETAGGING="false"
# Timestamp suffix is opt-in; default off so the legacy <prefix>-<sha> format
# is the baseline. Tests that exercise the suffix set the flag explicitly.
unset INPUT_DOCKER_TAG_TIMESTAMP
# Pin the timestamp so the opt-in branch tags are deterministic in tests.
export BUILD_TIMESTAMP="20260602143055"
}

teardown() {
Expand Down Expand Up @@ -53,6 +58,59 @@ teardown() {
assert_output_value "build" "true"
}

# --- timestamp suffix (opt-in, Flux-sortable) ---

@test "dev branch with timestamp flag inserts timestamp before sha" {
export INPUT_DOCKER_TAG_TIMESTAMP="true"
export GITHUB_REF="refs/heads/dev"
run "$SCRIPT"
assert_success
assert_output_value "tag" "dev-20260602143055-abcdef12"
assert_output_value "latest" "dev"
}

@test "main branch with timestamp flag inserts timestamp before sha" {
export INPUT_DOCKER_TAG_TIMESTAMP="true"
export GITHUB_REF="refs/heads/main"
run "$SCRIPT"
assert_success
assert_output_value "tag" "main-20260602143055-abcdef12"
}

@test "master branch with timestamp flag inserts timestamp before sha" {
export INPUT_DOCKER_TAG_TIMESTAMP="true"
export GITHUB_REF="refs/heads/master"
run "$SCRIPT"
assert_success
assert_output_value "tag" "master-20260602143055-abcdef12"
}

@test "timestamp flag defaults to a 14-digit UTC timestamp when not injected" {
export INPUT_DOCKER_TAG_TIMESTAMP="true"
unset BUILD_TIMESTAMP
export GITHUB_REF="refs/heads/dev"
run "$SCRIPT"
assert_success
local tag
tag=$(get_output_value "tag")
[[ "$tag" =~ ^dev-[0-9]{14}-abcdef12$ ]]
}

@test "timestamp flag does not affect version (prod) tags" {
export INPUT_DOCKER_TAG_TIMESTAMP="true"
export GITHUB_REF="refs/tags/v2025.50.14"
run "$SCRIPT"
assert_success
assert_output_value "tag" "2025.50.14"
}

Comment thread
monotek marked this conversation as resolved.
@test "timestamp flag explicitly 'false' produces legacy shape" {
export INPUT_DOCKER_TAG_TIMESTAMP="false"
export GITHUB_REF="refs/heads/dev"
run "$SCRIPT"
assert_success
assert_output_value "tag" "dev-abcdef12"
}
# --- version tag ---

@test "version tag v1.2.3 generates correct tag" {
Expand Down
Loading