This commit is contained in:
Louis Orleans 2023-10-24 11:29:38 -07:00 committed by GitHub
commit ccb34675bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 412 additions and 16 deletions

95
.github/workflows/build-publish.yaml vendored Normal file
View File

@ -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'

View File

@ -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"

35
.github/workflows/scripts/get-image-tags.rb vendored Executable file
View File

@ -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()

73
.github/workflows/scripts/lib.rb vendored Normal file
View File

@ -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

View File

@ -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

View File

@ -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"

12
docker-compose.yaml Normal file
View File

@ -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'

View File

@ -132,7 +132,7 @@ def update_hosts_file():
aux_hosts.writelines(lines)
#replace etc/hosts with aux file, making it atomic
shutil.move(aux_file_path, hosts_path)
shutil.copyfile(aux_file_path, hosts_path)
def parse_args():