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 pip3 install docker
|
||||||
RUN mkdir /hoster
|
|
||||||
WORKDIR /hoster
|
ARG APP_DIR=/hoster
|
||||||
ADD hoster.py /hoster/
|
WORKDIR ${APP_DIR}
|
||||||
|
|
||||||
|
ADD hoster.py ./
|
||||||
|
|
||||||
CMD ["python3", "-u", "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'
|
||||||
18
hoster.py
18
hoster.py
|
|
@ -39,9 +39,9 @@ def main():
|
||||||
|
|
||||||
#listen for events to keep the hosts file updated
|
#listen for events to keep the hosts file updated
|
||||||
for e in events:
|
for e in events:
|
||||||
if e["Type"]!="container":
|
if e["Type"]!="container":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
status = e["status"]
|
status = e["status"]
|
||||||
if status =="start":
|
if status =="start":
|
||||||
container_id = e["id"]
|
container_id = e["id"]
|
||||||
|
|
@ -71,16 +71,16 @@ def get_container_data(dockerClient, container_id):
|
||||||
container_ip = info["NetworkSettings"]["IPAddress"]
|
container_ip = info["NetworkSettings"]["IPAddress"]
|
||||||
if info["Config"]["Domainname"]:
|
if info["Config"]["Domainname"]:
|
||||||
container_hostname = container_hostname + "." + info["Config"]["Domainname"]
|
container_hostname = container_hostname + "." + info["Config"]["Domainname"]
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
for values in info["NetworkSettings"]["Networks"].values():
|
for values in info["NetworkSettings"]["Networks"].values():
|
||||||
|
|
||||||
if not values["Aliases"]:
|
if not values["Aliases"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result.append({
|
result.append({
|
||||||
"ip": values["IPAddress"] ,
|
"ip": values["IPAddress"] ,
|
||||||
"name": container_name,
|
"name": container_name,
|
||||||
"domains": set(values["Aliases"] + [container_name, container_hostname])
|
"domains": set(values["Aliases"] + [container_name, container_hostname])
|
||||||
})
|
})
|
||||||
|
|
@ -119,11 +119,11 @@ def update_hosts_file():
|
||||||
#append all the domain lines
|
#append all the domain lines
|
||||||
if len(hosts)>0:
|
if len(hosts)>0:
|
||||||
lines.append("\n\n"+enclosing_pattern)
|
lines.append("\n\n"+enclosing_pattern)
|
||||||
|
|
||||||
for id, addresses in hosts.items():
|
for id, addresses in hosts.items():
|
||||||
for addr in addresses:
|
for addr in addresses:
|
||||||
lines.append("%s %s\n"%(addr["ip"]," ".join(addr["domains"])))
|
lines.append("%s %s\n"%(addr["ip"]," ".join(addr["domains"])))
|
||||||
|
|
||||||
lines.append("#-----Do-not-add-hosts-after-this-line-----\n\n")
|
lines.append("#-----Do-not-add-hosts-after-this-line-----\n\n")
|
||||||
|
|
||||||
#write it on the auxiliar file
|
#write it on the auxiliar file
|
||||||
|
|
@ -132,7 +132,7 @@ def update_hosts_file():
|
||||||
aux_hosts.writelines(lines)
|
aux_hosts.writelines(lines)
|
||||||
|
|
||||||
#replace etc/hosts with aux file, making it atomic
|
#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():
|
def parse_args():
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue