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
1 change: 1 addition & 0 deletions .github/actions/start-tsp/action.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# tag-version: v1
# These reusable steps start a TSP docker image in the background and wait for it to report healthy
# By default it will start the latest (highest semantic version) TSP from the dev artifact repository.
# This can be overridden with a PR comment in the following form specifying the full GAR image name/tag:
Expand Down
6 changes: 5 additions & 1 deletion .github/bump-version.bump.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ echo "release=${BUMP_VERSION_RELEASE_PREFIX}${RELEASEVERS}" >> "$GITHUB_OUTPUT"

# Derive a new bumped version from the release version.
# Increment the last number in the string.
VERSION="$(echo "${RELEASEVERS}" | gawk '{ start=match($0, /(.*[^0-9])([0-9]+)([^0-9]*)$/, a) ; a[2] += 1 ; printf("%s%s%s", a[1], a[2], a[3]) }')"
if ! [[ ${RELEASEVERS} =~ ^(.*[^0-9])([0-9]+)([^0-9]*)$ ]] ; then
echo "No number to increment in '${RELEASEVERS}'" 1>&2
exit 1
fi
VERSION="${BASH_REMATCH[1]}$(( 10#${BASH_REMATCH[2]} + 1 ))${BASH_REMATCH[3]}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this changed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something changed with a new version of gawk or difference between the CI version and local, and it was causing the shellcheck stuff to fail. This way avoids extra tool drift. I need to triple check the 10# bit there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it's to force it to be decimal, if bash/C reads leading zeros it defaults to octal.

# Replace [-.]rc[-.$] with pre.
VERSION="$(echo "${VERSION}" | sed -E 's/([-.])rc([-.]|$)/\1pre\2/')"
# If no [-.]pre[-.$], then append -pre.
Expand Down
98 changes: 98 additions & 0 deletions .github/move-tags.list.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/bin/bash

# Lists the floating version tags that need to be created or moved so that every
# reusable workflow's and composite action's declared version tag matches its current
# content. See RELEASING.md and move-tags.yaml for details.
#
# Each reusable workflow and composite action declares its major version in a
# "# tag-version: vN" comment in its yaml file. For each declaration, the tag "NAME-vN"
# is printed on stdout if it doesn't exist, or if the workflow's files (the yaml file
# plus any ".github/NAME.*.sh" helper scripts, or the action's directory) differ
# between the tag and HEAD.
#
# A reusable workflow or action without a usable declaration is a misconfiguration:
# it's reported on stderr and the script exits nonzero, after checking everything.
# This repo's own CI workflows don't need declarations and are skipped silently.
#
# Must be run from the repo root, with HEAD at the commit tags should move to and all
# existing tags fetched. The caller is expected to `git tag -f` and force push each
# printed tag.

set -euo pipefail

if [ "$#" -ne 0 ] ; then
echo "Usage: $0" 1>&2
exit 1
fi

TAGS=()
ERRORS=0

# Check one workflow or action. Arguments are its name, the yaml file declaring its
# version, and the pathspecs that make up its content. Appends to TAGS and sets ERRORS.
check() {
local name="$1"
local vfile="$2"
shift 2

local version
version=$(grep -E -o '^# tag-version: v[0-9]+' "$vfile" | head -1 | grep -E -o '[0-9]+$' || true)
if [ -z "$version" ] ; then
# Only reusable workflows and actions need a version; this repo's own CI doesn't.
if [[ "$vfile" == .github/actions/* ]] || grep -q workflow_call "$vfile" ; then
echo "❌ $name: no '# tag-version: vN' comment in $vfile; it will never be released" 1>&2
ERRORS=1
fi
return 0
fi

# Never touch a tag older than the newest existing one. That means the comment is
# stale or was decremented, and moving the old tag would break its consumers.
local newest=""
local t n
while IFS= read -r t ; do
n="${t#"$name"-v}"
[[ "$n" =~ ^[0-9]+$ ]] || continue
if [ -z "$newest" ] || [ "$n" -gt "$newest" ] ; then
newest="$n"
fi
done < <(git tag --list "$name-v*")
if [ -n "$newest" ] && [ "$newest" -gt "$version" ] ; then
echo "❌ $name: tag $name-v$newest exists but $vfile declares v$version; fix the comment" 1>&2
ERRORS=1
return 0
fi

local tag="$name-v$version"
if ! git rev-parse -q --verify "refs/tags/$tag" > /dev/null ; then
TAGS+=("$tag")
elif ! git diff --quiet "refs/tags/$tag" HEAD -- "$@" ; then
TAGS+=("$tag")
fi
}

check_all() {
local f d base name vfile
for f in .github/workflows/*.yaml .github/workflows/*.yml ; do
[ -f "$f" ] || continue
base=$(basename "$f")
name="${base%.*}"
check "$name" "$f" "$f" ".github/$name.*.sh"
done
for d in .github/actions/*/ ; do
[ -d "$d" ] || continue
name=$(basename "$d")
vfile=""
for f in "$d"action.yaml "$d"action.yml ; do
[ -f "$f" ] && vfile="$f"
done
[ -z "$vfile" ] && continue
check "$name" "$vfile" "$d"
done
}

check_all
if [ "${#TAGS[@]}" -gt 0 ] ; then
printf '%s\n' "${TAGS[@]}" | sort
fi
exit "$ERRORS"
203 changes: 203 additions & 0 deletions .github/spec/move_tags_spec.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
Describe 'move-tags.list.sh'
SCRIPT="$PWD/move-tags.list.sh"

cleanup() {
if [ -n "${SANDBOX:-}" ] ; then
cd /
rm -rf "$SANDBOX"
fi
}
After 'cleanup'

# Create a sandbox git repo to run the script in.
setup_repo() {
SANDBOX=$(mktemp -d)
cd "$SANDBOX" || return 1
export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null \
GIT_AUTHOR_NAME=spec GIT_AUTHOR_EMAIL=spec@example.com \
GIT_COMMITTER_NAME=spec GIT_COMMITTER_EMAIL=spec@example.com
git init -q -b main .
mkdir -p .github/workflows
}

commit_all() {
git add -A .
git commit -q -m 'commit'
}

# A minimal reusable workflow declaring the given tag-version.
reusable_workflow() {
printf '# tag-version: %s\non:\n workflow_call:\n' "$1"
}

new_workflow() {
setup_repo
reusable_workflow v1 > .github/workflows/foo.yaml
commit_all
"$SCRIPT"
}
It 'creates the tag for a new workflow'
When call new_workflow
The output should equal 'foo-v1'
The status should be success
End

up_to_date_workflow() {
setup_repo
reusable_workflow v1 > .github/workflows/foo.yaml
commit_all
git tag foo-v1
"$SCRIPT"
}
It 'is quiet when the tag matches the content'
When call up_to_date_workflow
The output should equal ''
The status should be success
End

changed_workflow() {
setup_repo
reusable_workflow v1 > .github/workflows/foo.yaml
commit_all
git tag foo-v1
printf '# a change\n' >> .github/workflows/foo.yaml
commit_all
"$SCRIPT"
}
It 'moves the tag when the workflow file changed'
When call changed_workflow
The output should equal 'foo-v1'
The status should be success
End

unrelated_change() {
setup_repo
reusable_workflow v1 > .github/workflows/foo.yaml
commit_all
git tag foo-v1
printf 'docs\n' > README.md
commit_all
"$SCRIPT"
}
It 'ignores changes outside the workflow files'
When call unrelated_change
The output should equal ''
The status should be success
End

changed_helper_script() {
setup_repo
reusable_workflow v1 > .github/workflows/foo.yaml
commit_all
git tag foo-v1
printf 'echo hi\n' > .github/foo.build.sh
commit_all
"$SCRIPT"
}
It 'moves the tag when a helper script changed'
When call changed_helper_script
The output should equal 'foo-v1'
The status should be success
End

major_bump() {
setup_repo
reusable_workflow v1 > .github/workflows/foo.yaml
commit_all
git tag foo-v1
reusable_workflow v2 > .github/workflows/foo.yaml
commit_all
"$SCRIPT"
}
It 'creates the new tag on a major bump, leaving the old one alone'
When call major_bump
The output should equal 'foo-v2'
The status should be success
End

decremented_version() {
setup_repo
reusable_workflow v2 > .github/workflows/foo.yaml
commit_all
git tag foo-v2
reusable_workflow v1 > .github/workflows/foo.yaml
commit_all
"$SCRIPT"
}
It 'fails rather than move a tag older than the newest existing one'
When call decremented_version
The output should equal ''
The stderr should not equal ''
The status should be failure
End

reusable_without_version() {
setup_repo
printf 'on:\n workflow_call:\n' > .github/workflows/foo.yaml
commit_all
"$SCRIPT"
}
It 'fails for a reusable workflow with no tag-version comment'
When call reusable_without_version
The output should equal ''
The stderr should not equal ''
The status should be failure
End

good_and_bad_workflows() {
setup_repo
reusable_workflow v1 > .github/workflows/foo.yaml
printf 'on:\n workflow_call:\n' > .github/workflows/bad.yaml
commit_all
"$SCRIPT"
}
It 'still lists good tags while failing on a misconfigured workflow'
When call good_and_bad_workflows
The output should equal 'foo-v1'
The stderr should include 'bad'
The status should be failure
End

repo_ci_without_version() {
setup_repo
printf 'on:\n push:\n' > .github/workflows/repo-ci.yaml
commit_all
"$SCRIPT"
}
It 'silently skips non-reusable workflows with no tag-version comment'
When call repo_ci_without_version
The output should equal ''
The stderr should equal ''
The status should be success
End

changed_action() {
setup_repo
mkdir -p .github/actions/tsp
printf '# tag-version: v1\nname: tsp\n' > .github/actions/tsp/action.yaml
commit_all
git tag tsp-v1
printf 'echo hi\n' > .github/actions/tsp/helper.sh
commit_all
"$SCRIPT"
}
It 'moves the tag when any file in a composite action changed'
When call changed_action
The output should equal 'tsp-v1'
The status should be success
End

multiple_workflows() {
setup_repo
reusable_workflow v1 > .github/workflows/foo.yaml
reusable_workflow v2 > .github/workflows/bar.yaml
commit_all
"$SCRIPT"
}
It 'prints multiple tags sorted'
When call multiple_workflows
The line 1 of output should equal 'bar-v2'
The line 2 of output should equal 'foo-v1'
The status should be success
End
End
1 change: 1 addition & 0 deletions .github/workflows/bump-version.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# tag-version: v1
# This workflow does version management, bumping the patch version on the main branch after a PR is merged.

# Requirements:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/check-docker-version.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# tag-version: v0
# This workflow works with bump-version.yaml to ensure that the version in the
# Dockerfile is consistent with what's in the version file. If not, it aborts the
# workflow so a human can fix it. This is meant to run on every commit to a PR.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# tag-version: v1
# This workflow updates files in the deployments repository, to use a new version of a docker image.

# 1. Make a PR to update the generic recipe of the application to use the new container image.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# tag-version: v2
# This workflow builds a docker container, plus more:
# - Performs a virus scan.
# - Pushes the container to a registry.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/get-tsp-ref.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# tag-version: v1
name: Get TSP ref

on:
Expand Down
Loading
Loading