Add initial codebase

This commit is contained in:
Phil Jay
2021-10-20 11:18:23 +11:00
parent c87131de31
commit 41a040c60e
10 changed files with 732 additions and 0 deletions

64
.github/workflows/test-and-release.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
---
name: Test and Release
on:
push:
branches:
- '**'
tags-ignore:
- '**'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@94e0aab03ca135d11a35e5bfc14e6746dc56e7e9
with:
check_together: 'yes'
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup bats
uses: mig4/setup-bats@af9a00deb21b5d795cabfeaa8d9060410377686d
with:
bats-version: 1.2.1
- name: Test
run: bats tests/*.bats
release:
needs:
- lint
- test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Lookup version
id: version-lookup
run: ./version-lookup.sh
- name: Increment version
id: version-increment
run: ./version-increment.sh
env:
current_version: ${{ steps.version-lookup.outputs.current-version }}
INPUT_SCHEME: calver
- name: Release version
uses: marvinpinto/action-automatic-releases@919008cf3f741b179569b7a6fb4d8860689ab7f0
if: ${{ github.ref == 'refs/heads/main' }}
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
draft: false
prerelease: false
automatic_release_tag: "${{ steps.version-increment.outputs.version }}"

72
.gitignore vendored Normal file
View File

@@ -0,0 +1,72 @@
.tmp_testing/
##-- Vim ignores
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
##-- MacOS ignores
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
##-- Windows ignores
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares

103
README.md Normal file
View File

@@ -0,0 +1,103 @@
# Version Increment
## 📄 Use
### ⌨️ Example
```yaml
- name: Get next version
uses: reecetech/version-increment
id: version
with:
scheme: semver
increment: patch
- name: Build image
uses: docker/build-push-action@v2
with:
push: false
tags: "example/application:${{ steps.version.outputs.version }}"
context: .
```
### 🔖 semver
This action will detect the current latest _normal_ semantic version (semver) from the tags in
a git repository. It will increment the version as directed (by default: +1 to
the patch digit). Both the current latest and the incremented version are
reported back as outputs.
Normal semantic versions are made up of a major, minor and patch digit. Normal
versions do not include pre-release versions, or versions with build meta-data.
e.g. `1.2.7`
See: https://semver.org/spec/v2.0.0.html
### 📅 calver (semver compliant)
Optionally, this action can provide semver compliant calendar versions (calver).
In this calver scheme, the semver major, minor and patch digits map to year,
month and release digits.
Note: to be semver compliant, digits must not have leading zeros.
e.g. `2021.6.2`
| semver | calver | example | note |
| :--- | :--- | :--- | :--- |
| major | year | `2021` |
| minor | month | `6` |
| patch | release | `2` | The *n*th release for the month |
If the current latest normal version is not the current year and month, then the year and month digits will be
set to the current year and month, and the release digit will be reset to 1.
### 🎋 Default branch vs. any other branch
**Default branch**
The action will return a _normal_ version if it is detected that the current commit is on the default branch (usually `main`).
Examples:
* `1.2.7`
* `2021.6.2`
**Any other branch**
The action will return a _pre-release_ version if any other branch is detected (e.g. `new-feature`, `bugfix/foo`, etc). The _pre-release_ portion of the version number will be the literal string `pre.` followed by the git commit ID short reference SHA (trimmed of any leading zeros).
Examples:
* `1.2.7-pre.41218aa78`
* `2021.6.2-pre.32fd19841`
### 📥 Inputs
| name | description | required | default |
| :--- | :--- | :--- | :--- |
| scheme | The versioning scheme in-use, either `semver` or `calver` | No | `semver` |
| increment | The digit to increment, either `major`, `minor` or `patch`, ignored if `scheme` == `calver` | No | `patch` |
### 📤 Outputs
| name | description |
| :--- | :--- |
| current_version | The current latest version detected from the git repositories tags |
| version | The incremented version number (e.g. the next version) |
## 💕 Contributing
Please raise a pull request, but note the testing tools below
### bats
BATS is used to test the logic of the shell scripts.
See: https://github.com/bats-core/bats-core
### shellcheck
Shellcheck is used to lint our shell scripts.
Please use [local ignores](https://stackoverflow.com/a/52659039) if you'd like to skip any particular checks.
See: https://github.com/koalaman/shellcheck

42
action.yml Normal file
View File

@@ -0,0 +1,42 @@
---
name: 'Version Increment'
description: |
Inspects the git tags to determine the current normal version, and returns the
next version number.
A normal version will be returned if the branch is the default branch
(usually `main`), otherwise a pre-release version will be returned.
inputs:
scheme:
description: 'Versioning scheme - semver, or, calver (defaults to semver)'
required: false
default: 'semver'
increment:
description: |
Field to increment - major, minor, or, patch (defaults to patch)
Not applicable to `calver` scheme
required: false
default: 'patch'
outputs:
current_version:
description: 'Current normal version detected'
value: ${{ steps.version-lookup.outputs.current-version }}
version:
description: 'Incremented version calculated'
value: ${{ steps.version-increment.outputs.version }}
runs:
using: "composite"
steps:
- id: version-lookup
run: ${{ github.action_path }}/version-lookup.sh
shell: bash
- id: version-increment
run: ${{ github.action_path }}/version-increment.sh
shell: bash
with:
current_version: ${{ steps.version-lookup.outputs.current-version }}

21
shared.sh Normal file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# shellcheck disable=SC2034
set -euo pipefail
# see: https://semver.org/spec/v2.0.0.html#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
pcre_semver='^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
pcre_master_ver='^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)$'
pcre_allow_vprefix="^v{0,1}${pcre_master_ver:1}"
pcre_old_calver='^(?P<major>0|[1-9]\d*)-0{0,1}(?P<minor>0|[0-9]\d*)-R(?P<patch>0|[1-9]\d*)$'
##==----------------------------------------------------------------------------
## MacOS compatibility - for local testing
export grep="grep"
if [[ "$(uname)" == "Darwin" ]] ; then
export grep="ggrep"
if ! grep --version 1>/dev/null ; then
echo "🛑 GNU grep not installed, try brew install coreutils" 1>&2
exit 9
fi
fi

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# vim: set ft=sh sw=4 :
# shellcheck disable=SC2154
function print_run_info() {
echo "status: ${status}"
echo "output: ${output}"
}

View File

@@ -0,0 +1,147 @@
#!/usr/bin/env bats
# vim: set ft=sh sw=4 :
load helper_print-info
export repo=".tmp_testing/repo"
function init_repo {
rm -rf "${repo}" &&
mkdir -p "${repo}" &&
cd "${repo}" &&
git init &&
git checkout -b main &&
touch README.md &&
git add README.md &&
git config user.email test@example.com &&
git config user.name Tester &&
git commit -m "README" &&
export GITHUB_REF="refs/heads/main"
}
@test "fails if no current_version given" {
init_repo
run ../../version-increment.sh
print_run_info
[ "$status" -eq 5 ] &&
[[ "$output" = *"Environment variable 'current_version' is unset or empty"* ]]
}
@test "fails if invalid current_version given" {
init_repo
export current_version=1.3.5-prerelease
run ../../version-increment.sh
print_run_info
[ "$status" -eq 6 ] &&
[[ "$output" = *"Environment variable 'current_version' is not a valid normal version"* ]]
}
@test "fails if invalid scheme given" {
init_repo
export current_version=1.2.3
export INPUT_SCHEME="foover"
run ../../version-increment.sh
print_run_info
[ "$status" -eq 8 ] &&
[[ "$output" = *"Value of 'scheme' is not valid"* ]]
}
@test "fails if invalid increment given" {
init_repo
export current_version=1.2.3
export INPUT_INCREMENT="critical"
run ../../version-increment.sh
print_run_info
[ "$status" -eq 7 ] &&
[[ "$output" = *"Value of 'increment' is not valid, choose from 'major', 'minor', or 'patch'"* ]]
}
@test "increments the patch digit correctly (semver)" {
init_repo
export current_version=1.2.3
export INPUT_INCREMENT="patch"
run ../../version-increment.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=version::1.2.4"* ]]
}
@test "increments the minor digit correctly (semver)" {
init_repo
export current_version=1.2.3
export INPUT_INCREMENT="minor"
run ../../version-increment.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=version::1.3.0"* ]]
}
@test "increments the major digit correctly (semver)" {
init_repo
export current_version=1.2.3
export INPUT_INCREMENT="major"
run ../../version-increment.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=version::2.0.0"* ]]
}
@test "increments to a new month (calver)" {
init_repo
export current_version=2020.6.4
export INPUT_SCHEME="calver"
run ../../version-increment.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=version::$(date +%Y.%-m.1)"* ]]
}
@test "increments the patch digit within a month (calver)" {
init_repo
export current_version="$(date +%Y.%-m.123)"
export INPUT_SCHEME="calver"
run ../../version-increment.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=version::$(date +%Y.%-m.124)"* ]]
}
@test "appends prerelease information if on a branch" {
init_repo
export current_version=1.2.3
export GITHUB_REF="refs/heads/super-awesome-feature"
export short_ref="$(git rev-parse --short HEAD | sed 's/0*//')"
run ../../version-increment.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=version::1.2.4-pre.${short_ref}"* ]]
}

View File

@@ -0,0 +1,115 @@
#!/usr/bin/env bats
# vim: set ft=sh sw=4 :
load helper_print-info
export repo=".tmp_testing/repo"
function init_repo {
rm -rf "${repo}" &&
mkdir -p "${repo}" &&
cd "${repo}" &&
git init &&
touch README.md &&
git add README.md &&
git config user.email test@example.com &&
git config user.name Tester &&
git commit -m "README"
}
@test "fails if invalid scheme given" {
init_repo
export INPUT_SCHEME="foover"
run ../../version-lookup.sh
print_run_info
[ "$status" -eq 8 ] &&
[[ "$output" = *"Value of 'scheme' is not valid"* ]]
}
@test "finds the current normal version" {
init_repo
git tag 0.0.1
git tag 0.1.1
git tag 0.1.2
run ../../version-lookup.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=current-version::0.1.2"* ]]
}
@test "finds the current normal version even if there's a newer pre-release version" {
init_repo
git tag 1.2.300
git tag 1.2.301-dev.234
run ../../version-lookup.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=current-version::1.2.300"* ]]
}
@test "returns 0.0.0 if no normal version detected" {
init_repo
run ../../version-lookup.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=current-version::0.0.0"* ]]
}
@test "returns 0.0.0 if no normal version detected even if there's a pre-release version" {
init_repo
git tag 0.0.1-dev.999
run ../../version-lookup.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=current-version::0.0.0"* ]]
}
@test "returns a calver if no normal version detected and calver scheme specified" {
init_repo
export INPUT_SCHEME="calver"
run ../../version-lookup.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=current-version::$(date '+%Y.%-m.0')"* ]]
}
@test "converts from older calver scheme automatically" {
init_repo
git tag 2020-09-R2
run ../../version-lookup.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=current-version::2020.9.2"* ]]
}
@test "strips v from the version" {
init_repo
git tag v3.4.5
run ../../version-lookup.sh
print_run_info
[ "$status" -eq 0 ] &&
[[ "$output" = *"::set-output name=current-version::3.4.5"* ]]
}

93
version-increment.sh Executable file
View File

@@ -0,0 +1,93 @@
#!/bin/bash
set -euo pipefail
# https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
# shellcheck source=shared.sh
source "${script_dir}/shared.sh"
if [[ -z "${current_version:-}" ]] ; then
echo "🛑 Environment variable 'current_version' is unset or empty" 1>&2
exit 5
fi
if [[ -z "$(echo "${current_version}" | ${grep} -P "${pcre_master_ver}")" ]] ; then
echo "🛑 Environment variable 'current_version' is not a valid normal version (M.m.p)" 1>&2
exit 6
fi
increment="${INPUT_INCREMENT:-patch}"
if [[ "${increment}" != 'patch' && "${increment}" != 'minor' && "${increment}" != 'major' ]] ; then
echo "🛑 Value of 'increment' is not valid, choose from 'major', 'minor', or 'patch'" 1>&2
exit 7
fi
scheme="${INPUT_SCHEME:-semver}"
if [[ "${scheme}" != 'semver' && "${scheme}" != 'calver' ]] ; then
echo "🛑 Value of 'scheme' is not valid, choose from 'semver' or 'calver'" 1>&2
exit 8
fi
##==----------------------------------------------------------------------------
## Git info - branch names, commit short ref
default_branch='main'
# if we're _not_ testing, then _actually_ check the origin
if [[ -z "${BATS_VERSION:-}" ]] ; then
default_branch="$(git remote show origin | ${grep} 'HEAD branch' | cut -d ' ' -f 5)"
fi
current_ref="${GITHUB_REF:-}"
git_commit="$(git rev-parse --short HEAD | sed 's/0*//')" # trim leading zeros, because semver doesn't allow that in
# the 'pre-release version' part, but we can't use the + char
# to make it 'build metadata' as that's not supported in K8s
# labels
##==----------------------------------------------------------------------------
## Version increment
# increment the month if needed
if [[ "${scheme}" == "calver" ]] ; then
month="$(date '+%Y.%-m.')"
release="${current_version//$month/}"
if [[ "${release}" == "${current_version}" ]] ; then
current_version="$(date '+%Y.%-m.0')"
fi
fi
# increment the patch digit
IFS=" " read -r -a version_array <<< "${current_version//./ }"
if [[ "${increment}" == 'patch' || "${scheme}" == 'calver' ]] ; then
(( ++version_array[2] ))
elif [[ "${increment}" == 'minor' ]] ; then
(( ++version_array[1] ))
version_array[2]='0'
elif [[ "${increment}" == 'major' ]] ; then
(( ++version_array[0] ))
version_array[1]='0'
version_array[2]='0'
fi
new_version="${version_array[0]}.${version_array[1]}.${version_array[2]}"
# check we haven't accidentally forgotten to set scheme to calver
# TODO: provide an override "I know my version numbers are > 2020, but it's semver!" option
if [[ "${version_array[0]}" -gt 2020 && "${scheme}" != "calver" ]] ; then
echo "🛑 The major version number is greater than 2020, but the scheme is not set to 'calver'" 1>&2
exit 11
fi
# add pre-release info to version if not the default branch
if [[ "${current_ref}" != "refs/heads/${default_branch}" ]] ; then
new_version="${new_version}-pre.${git_commit}"
fi
if [[ -z "$(echo "${new_version}" | ${grep} -P "${pcre_semver}")" ]] ; then
echo "🛑 Version incrementing has failed to produce a semver compliant version" 1>&2
echo " See: https://semver.org/spec/v2.0.0.html" 1>&2
echo " Failed version string: '${new_version}'" 1>&2
exit 12
fi
echo " The new version is ${new_version}"
echo "::set-output name=version::${new_version}"

66
version-lookup.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
set -euo pipefail
# https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
# shellcheck source=shared.sh
source "${script_dir}/shared.sh"
scheme="${INPUT_SCHEME:-semver}"
if [[ "${scheme}" != 'semver' && "${scheme}" != 'calver' ]] ; then
echo "🛑 Value of 'scheme' is not valid, choose from 'semver' or 'calver'" 1>&2
exit 8
fi
##==----------------------------------------------------------------------------
## MacOS compatibility - for local testing
export grep="grep"
if [[ "$(uname)" == "Darwin" ]] ; then
export grep="ggrep"
if ! grep --version 1>/dev/null ; then
echo "🛑 GNU grep not installed, try brew install coreutils" 1>&2
exit 9
fi
fi
##==----------------------------------------------------------------------------
## Get tags from GitHub repo
# Skip if testing, otherwise pull tags
if [[ -z "${BATS_VERSION:-}" ]] ; then
git fetch --quiet origin 'refs/tags/*:refs/tags/*'
fi
##==----------------------------------------------------------------------------
## Version parsing
# detect current version - removing "v" from start of tag if it exists
current_version="$(git tag -l | { ${grep} -P "${pcre_allow_vprefix}" || true; } | sed 's/^v//g' | sort -V --reverse | head -n1)"
# support transition from an old reecetech calver style (yyyy-mm-Rr, where R is the literal `R`, and r is the nth release for the month)
if [[ -z "${current_version:-}" ]] ; then
current_version="$(git tag -l | { ${grep} -P "${pcre_old_calver}" || true; } | sort -V --reverse | head -n1)"
if [[ -n "${current_version:-}" ]] ; then
# convert - to . and drop leading zeros & the R
current_version="$(echo "${current_version}" | sed -r 's/^([0-9]+)-0{0,1}([0-9]+)-R0{0,1}([0-9]+)$/\1.\2.\3/')"
fi
fi
# handle no version detected - start versioning!
if [[ -z "${current_version:-}" ]] ; then
echo "⚠️ No previous release version identified in git tags"
# brand new repo! (probably)
case "${scheme}" in
semver)
current_version="0.0.0"
;;
calver)
current_version="$(date '+%Y.%-m.0')"
;;
esac
fi
echo " The current normal version is ${current_version}"
echo "::set-output name=current-version::${current_version}"