Docker build: Support logins to multiple registries

This commit is contained in:
Bernhard Ehlers 2023-06-04 10:11:24 +02:00
parent 78f5477e37
commit 3a5d352f1c
3 changed files with 92 additions and 47 deletions

View File

@ -108,7 +108,7 @@ def parse_repository(repository):
match = RE_REPOSITORY.fullmatch(repository) match = RE_REPOSITORY.fullmatch(repository)
if not match: if not match:
raise ValueError("invalid reference format") raise ValueError("invalid reference format")
registry = match.group('host') or "docker.io" registry = (match.group('host') or "docker.io").lower()
repo = match.group('repo') repo = match.group('repo')
tag = match.group('digest') or match.group('tag') or "latest" tag = match.group('digest') or match.group('tag') or "latest"
len_registry = len(registry) len_registry = len(registry)
@ -123,17 +123,12 @@ def parse_repository(repository):
return registry, repo, tag return registry, repo, tag
def docker_auth_user(docker, response): def docker_auth(docker, response):
""" authenticate with user/password """ """ authenticate docker access """
docker.authenticate(docker_login["user"], docker_login["password"], docker.authenticate(docker.registry_auth[0], docker.registry_auth[1],
response=response) response=response)
def docker_auth_none(docker, response):
""" public access """
docker.authenticate(None, None, response=response)
def get_time_layers(repository): def get_time_layers(repository):
""" """
get created time and layer info from the docker registry get created time and layer info from the docker registry
@ -144,14 +139,10 @@ def get_time_layers(repository):
try: try:
registry, repo, tag = parse_repository(repository) registry, repo, tag = parse_repository(repository)
if registry == docker_login["registry"] and \
docker_login["user"] and docker_login["password"]:
docker_auth = docker_auth_user
else:
docker_auth = docker_auth_none
# open docker connection # open docker connection
with dxf.DXF(registry, repo, docker_auth, timeout=30) as docker: with dxf.DXF(registry, repo, docker_auth, timeout=30) as docker:
docker.registry_auth = docker_login.get(registry, [None, None])
# get config digest # get config digest
try: try:
digest = docker.get_digest(tag, platform="linux/amd64") digest = docker.get_digest(tag, platform="linux/amd64")
@ -188,11 +179,11 @@ def expand_base_image(base_name):
match = re.match(r"\$\{?DOCKER_REPOSITORY\}?/(.+)", base_name) match = re.match(r"\$\{?DOCKER_REPOSITORY\}?/(.+)", base_name)
if not match: if not match:
return (base_name, []) return (base_name, [])
if not docker_login["repository"]: if not docker_env["repository"]:
raise ValueError("Environment variable DOCKER_REPOSITORY " raise ValueError("Environment variable DOCKER_REPOSITORY "
"is not defined or is empty") "is not defined or is empty")
base_name = docker_login["repository"] + "/" + match.group(1) base_name = docker_env["repository"] + "/" + match.group(1)
options = ["--build-arg", "DOCKER_REPOSITORY=" + docker_login["repository"]] options = ["--build-arg", "DOCKER_REPOSITORY=" + docker_env["repository"]]
return (base_name, options) return (base_name, options)
@ -200,10 +191,10 @@ def full_image_name(image_name):
""" get full image name """ """ get full image name """
if "/" in image_name: if "/" in image_name:
return image_name return image_name
if not docker_login["repository"]: if not docker_env["repository"]:
raise ValueError("Environment variable DOCKER_REPOSITORY " raise ValueError("Environment variable DOCKER_REPOSITORY "
"is not defined or is empty") "is not defined or is empty")
return docker_login["repository"] + "/" + image_name return docker_env["repository"] + "/" + image_name
def dockerfile_base(directory): def dockerfile_base(directory):
@ -398,6 +389,24 @@ def build(image):
sys.exit(f"{image['name']}: Can't get image layers") sys.exit(f"{image['name']}: Can't get image layers")
def fill_login_table():
""" fill login table from DOCKER_LOGIN* environment variables """
login_table = {}
for key, val in list(os.environ.items()):
if key.startswith("DOCKER_LOGIN"):
val_split = val.strip().split(maxsplit=2)
if len(val_split) != 3:
sys.exit(f"{key} requires 3 fields: registry user password")
registry = val_split[0].lower()
if registry == "docker.io":
registry = "registry-1.docker.io"
if registry in login_table:
sys.exit(f"DOCKER_LOGIN: {registry} defined multiple times")
login_table[registry] = val_split[1:3]
del os.environ[key]
return login_table
def xor(*params): def xor(*params):
""" logical xor """ """ logical xor """
result = False result = False
@ -410,18 +419,19 @@ def xor(*params):
args = parser.parse_args() args = parser.parse_args()
sys.stdout.reconfigure(line_buffering=True) sys.stdout.reconfigure(line_buffering=True)
docker_login = {"repository": os.environ.get("DOCKER_REPOSITORY", "").lower(), # DOCKER_REPOSITORY environment
"user": os.environ.pop("DOCKER_USERNAME", None), docker_env = {"repository": os.environ.get("DOCKER_REPOSITORY", "")
"password": os.environ.pop("DOCKER_PASSWORD", None)} .lower().rstrip("/")}
if docker_login["repository"]: if docker_env["repository"]:
docker_login["repository"] = docker_login["repository"].rstrip("/")
try: try:
docker_login["registry"], *_ = \ docker_env["registry"], *_ = parse_repository(docker_env["repository"])
parse_repository(docker_login["repository"])
except ValueError as err_info: except ValueError as err_info:
sys.exit(f"DOCKER_REPOSITORY={docker_login['repository']}: {err_info}") sys.exit(f"DOCKER_REPOSITORY={docker_env['repository']}: {err_info}")
else: else:
docker_login["registry"] = None docker_env["repository"] = docker_env["registry"] = None
# fill user/password table
docker_login = fill_login_table()
if args.dir: if args.dir:
try: try:

View File

@ -95,6 +95,21 @@ FROM $DOCKER_REPOSITORY/base-image
``` ```
## Environment Variables
In addition to the DOCKER_REPOSITORY variable described above
the build tool uses the environment variables whose names begin
with "DOCKER_LOGIN". Each variable contains the user/password
of a docker registry. The format is: `<registry> <user> <password>`.
Example:
```
DOCKER_LOGIN_DH="docker.io dockerhub-user dockerhub-password"
DOCKER_LOGIN_GH="ghcr.io github-user github-password"
```
## Workflow Definition ## Workflow Definition
[GitHub Actions](https://docs.github.com/en/actions) [GitHub Actions](https://docs.github.com/en/actions)
@ -107,7 +122,7 @@ need to be done:
* Check out the repository code * Check out the repository code
* Set up QEMU (for multi-arch building) * Set up QEMU (for multi-arch building)
* Set up Docker Buildx * Set up Docker Buildx
* Login to the Container Registry * Login to the Container Registries
* Install python requirements * Install python requirements
Then `docker_build` can be executed, Then `docker_build` can be executed,

View File

@ -1,4 +1,5 @@
name: Build Docker images and upload to DockerHub name: Build and upload Docker images
on: on:
push: push:
branches: branches:
@ -24,40 +25,59 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up QEMU - name: Set up QEMU
# https://github.com/marketplace/actions/docker-setup-qemu # https://github.com/marketplace/actions/docker-setup-qemu
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
# https://github.com/marketplace/actions/docker-setup-buildx # https://github.com/marketplace/actions/docker-setup-buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry
- name: Login to DockerHub Registry
# https://github.com/marketplace/actions/docker-login # https://github.com/marketplace/actions/docker-login
# set the condition depending on whether you want to login to Docker.
if: true
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
# GitHub Container Registry:
# registry: ghcr.io
# username: ${{ github.repository_owner }}
# password: ${{ secrets.GITHUB_TOKEN }}
#
# DockerHub:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
# https://github.com/marketplace/actions/docker-login
# set the condition depending on whether you want to login to ghcr.io.
if: false
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install python requirements - name: Install python requirements
run: python3 -m pip install --requirement .github/bin/requirements.txt run: python3 -m pip install --requirement .github/bin/requirements.txt
- name: Build and push images - name: Build and push images
env: env:
# DOCKER_USERNAME and DOCKER_PASSWORD are optional, they
# are only needed to authenticate into private repositories
#
# GitHub Container Registry:
# DOCKER_REPOSITORY: ghcr.io/${{ github.repository_owner }}
# DOCKER_USERNAME: ${{ github.repository_owner }}
# DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
#
# DockerHub: # DockerHub:
DOCKER_REPOSITORY: ${{ secrets.DOCKERHUB_REPOSITORY }} DOCKER_REPOSITORY: ${{ secrets.DOCKERHUB_REPOSITORY }}
# DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} # GitHub Container Registry:
# DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} # DOCKER_REPOSITORY: ghcr.io/${{ github.repository_owner }}
#
# Variables whose name are starting with "DOCKER_LOGIN"
# contain the user/password for a docker registry.
# They are only needed to authenticate into private repositories.
#
# DockerHub:
#DOCKER_LOGIN_DH: >-
# docker.io
# ${{ secrets.DOCKERHUB_USERNAME }}
# ${{ secrets.DOCKERHUB_TOKEN }}
#
# GitHub Container Registry:
#DOCKER_LOGIN_GH: >-
# ghcr.io
# ${{ github.repository_owner }}
# ${{ secrets.GITHUB_TOKEN }}
# #
IMAGES: ${{ inputs.images }} IMAGES: ${{ inputs.images }}
run: | run: |