👷 rewrite CI build system
This commit is contained in:
parent
350efafa5b
commit
de768d12c1
|
|
@ -1,67 +1,72 @@
|
|||
name: 'Publish Container Image'
|
||||
name: 'Build & Publish Container Image'
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull
|
||||
# request events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
packages: 'write'
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build-and-publish:
|
||||
# The type of runner that the job will run on
|
||||
test-scripts:
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- name: 'Checkout Repository 🛎️'
|
||||
uses: 'actions/checkout@v2'
|
||||
uses: 'actions/checkout@v3'
|
||||
- name: 'Test 🧪'
|
||||
run: 'ruby .github/workflows/scripts/test/get-image-tags.unit.rb'
|
||||
|
||||
- name: 'Build 🏗️'
|
||||
id: 'build'
|
||||
container-build:
|
||||
needs: 'test-scripts'
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: 'Checkout Repository 🛎️'
|
||||
uses: 'actions/checkout@v3'
|
||||
- uses: 'docker/setup-buildx-action@v2'
|
||||
|
||||
- name: 'Get image tag names 🏷️'
|
||||
id: 'tag-image'
|
||||
run: |
|
||||
image="$( \
|
||||
basename "$(echo "${{ github.repository }}")" \
|
||||
| tr '[:upper:]' '[:lower:]' \
|
||||
| sed 's/docker-//' \
|
||||
)"
|
||||
echo "image=$image" >> "$GITHUB_OUTPUT"
|
||||
echo "image_tags=$(
|
||||
.github/workflows/scripts/get-image-tags.rb \
|
||||
"${{ github.repository }}" \
|
||||
"${{ github.ref_name }}" \
|
||||
"${{ github.ref_type }}" \
|
||||
"${{ github.event.repository.default_branch }}" \
|
||||
)" >> "$GITHUB_OUTPUT"
|
||||
- name: 'Build container 🐳'
|
||||
uses: 'docker/build-push-action@v3'
|
||||
with:
|
||||
file: 'Dockerfile'
|
||||
tags: '${{ steps.tag-image.outputs.image_tags }}'
|
||||
cache-from: 'type=gha'
|
||||
cache-to: 'type=gha,mode=max'
|
||||
|
||||
docker build --file "Dockerfile" --tag "$image" .
|
||||
outputs:
|
||||
image_tags: '${{ steps.tag-image.outputs.image_tags }}'
|
||||
|
||||
publish:
|
||||
needs:
|
||||
- 'container-build'
|
||||
if: contains(needs.container-build.outputs.image_tags, ':latest')
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
packages: 'write'
|
||||
steps:
|
||||
- name: 'Checkout Repository 🛎️'
|
||||
uses: 'actions/checkout@v3'
|
||||
- uses: 'docker/setup-buildx-action@v2'
|
||||
- name: 'Login to GitHub Container Registry 🔑'
|
||||
uses: 'docker/login-action@v1'
|
||||
if: '${{ github.ref_name }} == ${{ github.event.repository.default_branch }}'
|
||||
uses: 'docker/login-action@v2'
|
||||
with:
|
||||
registry: 'ghcr.io'
|
||||
username: '${{ github.repository_owner }}'
|
||||
password: '${{ secrets.GITHUB_TOKEN }}'
|
||||
|
||||
- name: 'Publish to Registry 🐳'
|
||||
if: '${{ github.ref_name }} == ${{ github.event.repository.default_branch }}'
|
||||
run: |
|
||||
image="${{ steps.build.outputs.image }}"
|
||||
repo="ghcr.io/${{ github.repository_owner }}/$image"
|
||||
branch="$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')"
|
||||
version="$branch"
|
||||
|
||||
echo "repo = $repo"
|
||||
echo "image = $image"
|
||||
echo "version = $version"
|
||||
|
||||
docker tag "$image" "$repo/$image:$version"
|
||||
docker push "$repo/$image:$version"
|
||||
|
||||
# Use Docker `latest` tag convention
|
||||
if [[ "${{ github.event.repository.default_branch }}" == "$branch" ]]; then
|
||||
docker tag "$image" "$repo/$image:latest"
|
||||
docker push "$repo/$image:latest"
|
||||
fi
|
||||
|
||||
echo "registry_uri=$repo/$image" >> "$GITHUB_OUTPUT"
|
||||
- name: 'Publish to Registry 💨'
|
||||
uses: 'docker/build-push-action@v3'
|
||||
with:
|
||||
file: 'Dockerfile'
|
||||
push: true
|
||||
tags: '${{ needs.container-build.outputs.image_tags }}'
|
||||
cache-from: 'type=gha'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'json'
|
||||
require_relative 'lib'
|
||||
|
||||
def main
|
||||
git_repo = ARGV[0]
|
||||
git_ref_name = ARGV[1]
|
||||
git_ref_type = ARGV[2]
|
||||
git_default_branch = ARGV[3]
|
||||
|
||||
# log to stderr so that stdout only contains the full tags
|
||||
$stderr.puts "'#{git_repo}', '#{git_ref_name}', '#{git_ref_type}', '#{git_default_branch}'"
|
||||
|
||||
tags =
|
||||
get_image_tags(
|
||||
git_repo: git_repo,
|
||||
git_ref_name: git_ref_name,
|
||||
git_ref_type: git_ref_type,
|
||||
git_default_branch: git_default_branch,
|
||||
package: JSON.parse(File.read('package.json')),
|
||||
).to_a.join(',')
|
||||
|
||||
# log to stderr so that stdout only contains the full tags
|
||||
$stderr.puts tags
|
||||
|
||||
puts tags
|
||||
end
|
||||
|
||||
main()
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
require 'set'
|
||||
|
||||
# @param git_repo [String]
|
||||
# @param git_ref_name [String]
|
||||
# @param git_ref_type [String]
|
||||
# @param git_default_branch [String]
|
||||
# @return [Set[String]]
|
||||
def get_image_tags(
|
||||
git_repo: nil,
|
||||
git_ref_name: nil,
|
||||
git_ref_type: nil,
|
||||
git_default_branch: nil,
|
||||
package: nil
|
||||
)
|
||||
container_repo = "ghcr.io/#{git_repo.downcase}"
|
||||
versions = Set[]
|
||||
|
||||
if git_ref_type == 'branch'
|
||||
# add safe branch name
|
||||
versions.add(git_ref_name.downcase.gsub(/[^a-z0-9._\n]+/, '-'))
|
||||
elsif git_ref_type == 'tag'
|
||||
# add version tag
|
||||
versions.add(package['version'])
|
||||
# TODO: check that this is actually latest
|
||||
parsed = parse_semver(package['version'])
|
||||
if parsed.pre == nil
|
||||
versions.add(parsed.major)
|
||||
versions.add("#{parsed.major}.#{parsed.minor}")
|
||||
versions.add("#{parsed.major}.#{parsed.minor}.#{parsed.patch}")
|
||||
end
|
||||
|
||||
# TODO: if the tag was made on a non-default branch, we still tag with default branch
|
||||
versions.add(git_default_branch)
|
||||
end
|
||||
|
||||
# TODO: if `tag`, check that this is actually latest
|
||||
if git_ref_name == git_default_branch or git_ref_type == 'tag'
|
||||
# Use Docker `latest` tag convention, only tagging `latest` on default branch.
|
||||
versions.add('latest')
|
||||
end
|
||||
|
||||
return versions.map! { |v| "#{container_repo}/bot:#{v}" }
|
||||
end
|
||||
|
||||
Semver = Struct.new('Semver', :major, :minor, :patch, :pre, :build)
|
||||
|
||||
# @param version [String]
|
||||
# @return [Semver]
|
||||
def parse_semver(version)
|
||||
# Ruby extracts regex named groups to local vars (but only if the regex is inlined).
|
||||
/^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<pre>[0-9A-Za-z\-.]+))?(?:\+(?<build>[0-9A-Za-z\-]+))?$/ =~
|
||||
version
|
||||
|
||||
Semver.new(major.to_i, minor.to_i, patch.to_i, pre, build)
|
||||
end
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
require 'test/unit'
|
||||
require 'json'
|
||||
require 'set'
|
||||
|
||||
require_relative '../lib'
|
||||
|
||||
class TestGetImageTags < Test::Unit::TestCase
|
||||
def test_simple_branch
|
||||
assert_equal(
|
||||
Set['ghcr.io/virginity-bot/virginity-bot/bot:feat-foo-bar'],
|
||||
get_image_tags(
|
||||
git_repo: 'Virginity-Bot/virginity-bot',
|
||||
git_ref_name: 'feat/foo-bar',
|
||||
git_ref_type: 'branch',
|
||||
git_default_branch: 'master',
|
||||
package: JSON.parse('{"version": "1.0.0"}'),
|
||||
),
|
||||
)
|
||||
|
||||
assert_equal(
|
||||
Set[
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:latest',
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:master'
|
||||
],
|
||||
get_image_tags(
|
||||
git_repo: 'Virginity-Bot/virginity.bot',
|
||||
git_ref_name: 'master',
|
||||
git_ref_type: 'branch',
|
||||
git_default_branch: 'master',
|
||||
package: JSON.parse('{"version": "1.0.0"}'),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
def test_simple_tag
|
||||
assert_equal(
|
||||
Set[
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:latest',
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:master',
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:1.0.0',
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:1.0',
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:1'
|
||||
],
|
||||
get_image_tags(
|
||||
git_repo: 'Virginity-Bot/virginity.bot',
|
||||
git_ref_name: '1.0.0',
|
||||
git_ref_type: 'tag',
|
||||
git_default_branch: 'master',
|
||||
package: JSON.parse('{"version": "1.0.0"}'),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
def test_pre_tag
|
||||
assert_equal(
|
||||
Set[
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:latest',
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:master',
|
||||
'ghcr.io/virginity-bot/virginity.bot/bot:1.0.0-pre'
|
||||
],
|
||||
get_image_tags(
|
||||
git_repo: 'Virginity-Bot/virginity.bot',
|
||||
git_ref_name: '1.0.0',
|
||||
git_ref_type: 'tag',
|
||||
git_default_branch: 'master',
|
||||
package: JSON.parse('{"version": "1.0.0-pre"}'),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
def test_unsafe_branch_name
|
||||
assert_equal(
|
||||
Set['ghcr.io/virginity-bot/virginity.bot/bot:feat-foo-bar'],
|
||||
get_image_tags(
|
||||
git_repo: 'Virginity-Bot/virginity.bot',
|
||||
git_ref_name: 'feat/Foo---bar',
|
||||
git_ref_type: 'branch',
|
||||
git_default_branch: 'master',
|
||||
package: JSON.parse('{"version": "1.0.0"}'),
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
class TestParseSemver < Test::Unit::TestCase
|
||||
def test_parse_basic
|
||||
parsed = parse_semver('1.2.3')
|
||||
assert_equal(1, parsed.major)
|
||||
assert_equal(2, parsed.minor)
|
||||
assert_equal(3, parsed.patch)
|
||||
assert_equal(nil, parsed.pre)
|
||||
assert_equal(nil, parsed.build)
|
||||
end
|
||||
|
||||
def test_parse_pre
|
||||
parsed = parse_semver('1.2.3-p.re')
|
||||
assert_equal(1, parsed.major)
|
||||
assert_equal(2, parsed.minor)
|
||||
assert_equal(3, parsed.patch)
|
||||
assert_equal('p.re', parsed.pre)
|
||||
assert_equal(nil, parsed.build)
|
||||
end
|
||||
|
||||
def test_parse_full_semver
|
||||
parsed = parse_semver('1.2.3-p.re+build')
|
||||
assert_equal(1, parsed.major)
|
||||
assert_equal(2, parsed.minor)
|
||||
assert_equal(3, parsed.patch)
|
||||
assert_equal('p.re', parsed.pre)
|
||||
assert_equal('build', parsed.build)
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue