Merge 26ae5311d9 into 7a5eeb03e8
This commit is contained in:
commit
ccb34675bf
|
|
@ -0,0 +1,95 @@
|
|||
name: 'Build & Publish Container Image'
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
|
||||
jobs:
|
||||
test-scripts:
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: 'Checkout Repository 🛎️'
|
||||
uses: 'actions/checkout@v3'
|
||||
- name: 'Test 🧪'
|
||||
run: 'ruby .github/workflows/scripts/test/get-image-tags.unit.rb'
|
||||
|
||||
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: |
|
||||
echo "image_tags=$(
|
||||
.github/workflows/scripts/get-image-tags.rb \
|
||||
"${{ github.repository_owner }}" \
|
||||
"${{ github.event.repository.name }}" \
|
||||
"${{ github.ref_name }}" \
|
||||
"${{ github.ref_type }}" \
|
||||
"${{ github.event.repository.default_branch }}" \
|
||||
)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 'Set up QEMU 🦅'
|
||||
id: 'qemu'
|
||||
uses: 'docker/setup-qemu-action@v2'
|
||||
with:
|
||||
platforms: 'arm64'
|
||||
- name: 'Set target build platforms 📝'
|
||||
id: 'target-platforms'
|
||||
run: |
|
||||
qemu_platforms="$(.github/workflows/scripts/convert-arch-to-platform.sh "${{ steps.qemu.outputs.platforms }}")"
|
||||
echo "target_platforms=$qemu_platforms" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 'Build container 🐳'
|
||||
id: 'build'
|
||||
uses: 'docker/build-push-action@v3'
|
||||
with:
|
||||
file: 'Dockerfile'
|
||||
tags: '${{ steps.tag-image.outputs.image_tags }}'
|
||||
platforms: '${{ steps.target-platforms.outputs.target_platforms }}'
|
||||
cache-from: 'type=gha'
|
||||
cache-to: 'type=gha,mode=max'
|
||||
|
||||
outputs:
|
||||
image_tags: '${{ steps.tag-image.outputs.image_tags }}'
|
||||
qemu_platforms: '${{ steps.qemu.outputs.platforms }}'
|
||||
docker_platforms: '${{ steps.target-platforms.outputs.target_platforms }}'
|
||||
|
||||
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@v2'
|
||||
with:
|
||||
registry: 'ghcr.io'
|
||||
username: '${{ github.repository_owner }}'
|
||||
password: '${{ secrets.GITHUB_TOKEN }}'
|
||||
|
||||
- name: 'Set up QEMU 🦅'
|
||||
uses: 'docker/setup-qemu-action@v2'
|
||||
with:
|
||||
platforms: '${{ needs.container-build.outputs.qemu_platforms }}'
|
||||
|
||||
- name: 'Publish to Registry 💨'
|
||||
uses: 'docker/build-push-action@v3'
|
||||
with:
|
||||
file: 'Dockerfile'
|
||||
push: true
|
||||
tags: '${{ needs.container-build.outputs.image_tags }}'
|
||||
platforms: '${{ needs.container-build.outputs.docker_platforms }}'
|
||||
cache-from: 'type=gha'
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
qemu_platforms=""
|
||||
|
||||
for p in $(echo "$1" | tr ',' '\n'); do
|
||||
v=""
|
||||
case "$p" in
|
||||
"linux/arm64")
|
||||
v="$p/v8" ;;
|
||||
# Skip platforms we don't need
|
||||
"linux/386") ;;
|
||||
*)
|
||||
v="$p" ;;
|
||||
esac
|
||||
|
||||
if [ -z "$qemu_platforms" ]; then
|
||||
qemu_platforms="$v"
|
||||
else
|
||||
qemu_platforms="$qemu_platforms,$v"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$qemu_platforms"
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'json'
|
||||
require_relative 'lib'
|
||||
|
||||
def main
|
||||
repo_owner = ARGV[0]
|
||||
repo_name = ARGV[1]
|
||||
git_ref_name = ARGV[2]
|
||||
git_ref_type = ARGV[3]
|
||||
git_default_branch = ARGV[4]
|
||||
|
||||
# log to stderr so that stdout only contains the full tags
|
||||
$stderr.puts "'#{repo_owner}', '#{repo_name}', '#{git_ref_name}', '#{git_ref_type}', '#{git_default_branch}'"
|
||||
|
||||
image_name = get_image_name(username: repo_owner, project_name: repo_name)
|
||||
|
||||
tags =
|
||||
get_image_tags(
|
||||
git_ref_name: git_ref_name,
|
||||
git_ref_type: git_ref_type,
|
||||
git_default_branch: git_default_branch,
|
||||
semver: '0.0.0',
|
||||
)
|
||||
.to_a
|
||||
.map {|tag| "#{image_name}:#{tag}" }
|
||||
.join(',')
|
||||
|
||||
# log to stderr so that stdout only contains the full tags
|
||||
$stderr.puts tags
|
||||
|
||||
puts tags
|
||||
end
|
||||
|
||||
main()
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
require 'set'
|
||||
|
||||
# @param git_ref_name [String]
|
||||
# @param git_ref_type [String]
|
||||
# @param git_default_branch [String]
|
||||
# @return [Set[String]]
|
||||
def get_image_tags(
|
||||
git_ref_name: nil,
|
||||
git_ref_type: nil,
|
||||
git_default_branch: nil,
|
||||
semver: nil
|
||||
)
|
||||
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(semver)
|
||||
# TODO: check that this is actually latest
|
||||
parsed = parse_semver(semver)
|
||||
if parsed.pre == nil
|
||||
versions.add(parsed.major.to_s)
|
||||
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
|
||||
end
|
||||
|
||||
# @param registry [String]
|
||||
# @param username [String]
|
||||
# @param sub_image [String?]
|
||||
# @return String
|
||||
def get_image_name(
|
||||
registry: 'ghcr.io',
|
||||
username: nil,
|
||||
project_name: nil,
|
||||
sub_image: nil
|
||||
)
|
||||
username = username.downcase
|
||||
project_name = project_name.downcase.gsub(/^docker-/, '')
|
||||
|
||||
case registry
|
||||
when 'ghcr.io'
|
||||
container_repo = "#{registry}/#{username}/#{project_name}/#{sub_image ? sub_image : project_name}"
|
||||
when 'docker.io'
|
||||
container_repo = "#{registry}/#{username}/#{project_name}#{sub_image ? "-#{sub_image}" : ''}"
|
||||
end
|
||||
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,153 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'test/unit'
|
||||
require 'json'
|
||||
require 'set'
|
||||
|
||||
require_relative '../lib'
|
||||
|
||||
class TestGetImageTags < Test::Unit::TestCase
|
||||
def test_simple_branch
|
||||
assert_equal(
|
||||
Set['feat-foo-bar'],
|
||||
get_image_tags(
|
||||
git_ref_name: 'feat/foo-bar',
|
||||
git_ref_type: 'branch',
|
||||
git_default_branch: 'master',
|
||||
semver: '1.0.0',
|
||||
),
|
||||
)
|
||||
|
||||
assert_equal(
|
||||
Set['latest', 'master'],
|
||||
get_image_tags(
|
||||
git_ref_name: 'master',
|
||||
git_ref_type: 'branch',
|
||||
git_default_branch: 'master',
|
||||
semver: '1.0.0',
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
def test_simple_tag
|
||||
assert_equal(
|
||||
Set['latest', 'master', '1.0.0', '1.0', '1'],
|
||||
get_image_tags(
|
||||
git_ref_name: '1.0.0',
|
||||
git_ref_type: 'tag',
|
||||
git_default_branch: 'master',
|
||||
semver: '1.0.0',
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
def test_pre_tag
|
||||
assert_equal(
|
||||
Set['latest', 'master', '1.0.0-pre'],
|
||||
get_image_tags(
|
||||
git_ref_name: '1.0.0',
|
||||
git_ref_type: 'tag',
|
||||
git_default_branch: 'master',
|
||||
semver: '1.0.0-pre',
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
def test_unsafe_branch_name
|
||||
assert_equal(
|
||||
Set['feat-foo-bar'],
|
||||
get_image_tags(
|
||||
git_ref_name: 'feat/Foo---bar',
|
||||
git_ref_type: 'branch',
|
||||
git_default_branch: 'master',
|
||||
semver: '1.0.0',
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
class TestGetImageName < Test::Unit::TestCase
|
||||
def test_basic
|
||||
assert_equal(
|
||||
'ghcr.io/octocat/hello-world/hello-world',
|
||||
get_image_name(
|
||||
username: 'Octocat',
|
||||
project_name: 'hello-world',
|
||||
),
|
||||
)
|
||||
|
||||
assert_equal(
|
||||
'ghcr.io/octocat/hello-world/hello-world',
|
||||
get_image_name(
|
||||
username: 'Octocat',
|
||||
project_name: 'docker-hello-world',
|
||||
),
|
||||
)
|
||||
|
||||
assert_equal(
|
||||
'ghcr.io/octocat/hello-world/foobar',
|
||||
get_image_name(
|
||||
username: 'Octocat',
|
||||
project_name: 'hello-world',
|
||||
sub_image: 'foobar',
|
||||
),
|
||||
)
|
||||
|
||||
assert_equal(
|
||||
'ghcr.io/octocat/hello-world/foo',
|
||||
get_image_name(
|
||||
username: 'Octocat',
|
||||
project_name: 'hello-world',
|
||||
sub_image: 'foo'
|
||||
),
|
||||
)
|
||||
|
||||
assert_equal(
|
||||
'docker.io/octocat/hello-world',
|
||||
get_image_name(
|
||||
registry: 'docker.io',
|
||||
username: 'Octocat',
|
||||
project_name: 'hello-world',
|
||||
),
|
||||
)
|
||||
|
||||
assert_equal(
|
||||
'docker.io/octocat/hello-world-foo',
|
||||
get_image_name(
|
||||
registry: 'docker.io',
|
||||
username: 'Octocat',
|
||||
project_name: 'hello-world',
|
||||
sub_image: 'foo'
|
||||
),
|
||||
)
|
||||
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
|
||||
19
Dockerfile
19
Dockerfile
|
|
@ -1,11 +1,16 @@
|
|||
FROM frolvlad/alpine-python3
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG BASE_IMAGE=docker.io/library/python:3-alpine
|
||||
ARG APP_DIR=/hoster
|
||||
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
RUN pip3 install docker
|
||||
RUN mkdir /hoster
|
||||
WORKDIR /hoster
|
||||
ADD hoster.py /hoster/
|
||||
|
||||
ARG APP_DIR=/hoster
|
||||
WORKDIR ${APP_DIR}
|
||||
|
||||
ADD hoster.py ./
|
||||
|
||||
CMD ["python3", "-u", "hoster.py"]
|
||||
|
||||
|
||||
|
||||
# CMD "whoami"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
# This file replaces `docker build -t docker-hoster .`
|
||||
# Run this with `docker-compose build`
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
host-hostnames:
|
||||
container_name: 'host-hostnames'
|
||||
build:
|
||||
context: '.'
|
||||
volumes:
|
||||
- '/var/run/docker.sock:/tmp/docker.sock:ro'
|
||||
- '/etc/hosts:/tmp/hosts'
|
||||
Loading…
Reference in New Issue