👷 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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
pull_request:
|
||||||
- '*'
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: 'read'
|
contents: 'read'
|
||||||
packages: 'write'
|
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
||||||
jobs:
|
jobs:
|
||||||
# This workflow contains a single job called "build"
|
test-scripts:
|
||||||
build-and-publish:
|
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: 'ubuntu-latest'
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository 🛎️'
|
- 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 🏗️'
|
container-build:
|
||||||
id: '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: |
|
run: |
|
||||||
image="$( \
|
echo "image_tags=$(
|
||||||
basename "$(echo "${{ github.repository }}")" \
|
.github/workflows/scripts/get-image-tags.rb \
|
||||||
| tr '[:upper:]' '[:lower:]' \
|
"${{ github.repository }}" \
|
||||||
| sed 's/docker-//' \
|
"${{ github.ref_name }}" \
|
||||||
)"
|
"${{ github.ref_type }}" \
|
||||||
echo "image=$image" >> "$GITHUB_OUTPUT"
|
"${{ 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 🔑'
|
- name: 'Login to GitHub Container Registry 🔑'
|
||||||
uses: 'docker/login-action@v1'
|
uses: 'docker/login-action@v2'
|
||||||
if: '${{ github.ref_name }} == ${{ github.event.repository.default_branch }}'
|
|
||||||
with:
|
with:
|
||||||
registry: 'ghcr.io'
|
registry: 'ghcr.io'
|
||||||
username: '${{ github.repository_owner }}'
|
username: '${{ github.repository_owner }}'
|
||||||
password: '${{ secrets.GITHUB_TOKEN }}'
|
password: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
|
||||||
- name: 'Publish to Registry 🐳'
|
- name: 'Publish to Registry 💨'
|
||||||
if: '${{ github.ref_name }} == ${{ github.event.repository.default_branch }}'
|
uses: 'docker/build-push-action@v3'
|
||||||
run: |
|
with:
|
||||||
image="${{ steps.build.outputs.image }}"
|
file: 'Dockerfile'
|
||||||
repo="ghcr.io/${{ github.repository_owner }}/$image"
|
push: true
|
||||||
branch="$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')"
|
tags: '${{ needs.container-build.outputs.image_tags }}'
|
||||||
version="$branch"
|
cache-from: 'type=gha'
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
|
||||||
|
|
@ -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