From 3a5d352f1c02b949854344b959bc18dcfda30984 Mon Sep 17 00:00:00 2001 From: Bernhard Ehlers Date: Sun, 4 Jun 2023 10:11:24 +0200 Subject: [PATCH] Docker build: Support logins to multiple registries --- .github/bin/docker_build | 66 +++++++++++++---------- .github/docker_build.md | 17 +++++- .github/workflows/build-docker-images.yml | 56 ++++++++++++------- 3 files changed, 92 insertions(+), 47 deletions(-) diff --git a/.github/bin/docker_build b/.github/bin/docker_build index 751b260..364fc41 100755 --- a/.github/bin/docker_build +++ b/.github/bin/docker_build @@ -108,7 +108,7 @@ def parse_repository(repository): match = RE_REPOSITORY.fullmatch(repository) if not match: raise ValueError("invalid reference format") - registry = match.group('host') or "docker.io" + registry = (match.group('host') or "docker.io").lower() repo = match.group('repo') tag = match.group('digest') or match.group('tag') or "latest" len_registry = len(registry) @@ -123,17 +123,12 @@ def parse_repository(repository): return registry, repo, tag -def docker_auth_user(docker, response): - """ authenticate with user/password """ - docker.authenticate(docker_login["user"], docker_login["password"], +def docker_auth(docker, response): + """ authenticate docker access """ + docker.authenticate(docker.registry_auth[0], docker.registry_auth[1], response=response) -def docker_auth_none(docker, response): - """ public access """ - docker.authenticate(None, None, response=response) - - def get_time_layers(repository): """ get created time and layer info from the docker registry @@ -144,14 +139,10 @@ def get_time_layers(repository): try: 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 with dxf.DXF(registry, repo, docker_auth, timeout=30) as docker: + docker.registry_auth = docker_login.get(registry, [None, None]) # get config digest try: 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) if not match: return (base_name, []) - if not docker_login["repository"]: + if not docker_env["repository"]: raise ValueError("Environment variable DOCKER_REPOSITORY " "is not defined or is empty") - base_name = docker_login["repository"] + "/" + match.group(1) - options = ["--build-arg", "DOCKER_REPOSITORY=" + docker_login["repository"]] + base_name = docker_env["repository"] + "/" + match.group(1) + options = ["--build-arg", "DOCKER_REPOSITORY=" + docker_env["repository"]] return (base_name, options) @@ -200,10 +191,10 @@ def full_image_name(image_name): """ get full image name """ if "/" in image_name: return image_name - if not docker_login["repository"]: + if not docker_env["repository"]: raise ValueError("Environment variable DOCKER_REPOSITORY " "is not defined or is empty") - return docker_login["repository"] + "/" + image_name + return docker_env["repository"] + "/" + image_name def dockerfile_base(directory): @@ -398,6 +389,24 @@ def build(image): 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): """ logical xor """ result = False @@ -410,18 +419,19 @@ def xor(*params): args = parser.parse_args() sys.stdout.reconfigure(line_buffering=True) -docker_login = {"repository": os.environ.get("DOCKER_REPOSITORY", "").lower(), - "user": os.environ.pop("DOCKER_USERNAME", None), - "password": os.environ.pop("DOCKER_PASSWORD", None)} -if docker_login["repository"]: - docker_login["repository"] = docker_login["repository"].rstrip("/") +# DOCKER_REPOSITORY environment +docker_env = {"repository": os.environ.get("DOCKER_REPOSITORY", "") + .lower().rstrip("/")} +if docker_env["repository"]: try: - docker_login["registry"], *_ = \ - parse_repository(docker_login["repository"]) + docker_env["registry"], *_ = parse_repository(docker_env["repository"]) 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: - docker_login["registry"] = None + docker_env["repository"] = docker_env["registry"] = None + +# fill user/password table +docker_login = fill_login_table() if args.dir: try: diff --git a/.github/docker_build.md b/.github/docker_build.md index f960d33..ffacbc8 100644 --- a/.github/docker_build.md +++ b/.github/docker_build.md @@ -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: ` `. + +Example: + +``` +DOCKER_LOGIN_DH="docker.io dockerhub-user dockerhub-password" +DOCKER_LOGIN_GH="ghcr.io github-user github-password" +``` + + ## Workflow Definition [GitHub Actions](https://docs.github.com/en/actions) @@ -107,7 +122,7 @@ need to be done: * Check out the repository code * Set up QEMU (for multi-arch building) * Set up Docker Buildx -* Login to the Container Registry +* Login to the Container Registries * Install python requirements Then `docker_build` can be executed, diff --git a/.github/workflows/build-docker-images.yml b/.github/workflows/build-docker-images.yml index eac402d..840c252 100644 --- a/.github/workflows/build-docker-images.yml +++ b/.github/workflows/build-docker-images.yml @@ -1,4 +1,5 @@ -name: Build Docker images and upload to DockerHub +name: Build and upload Docker images + on: push: branches: @@ -24,40 +25,59 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Set up QEMU # https://github.com/marketplace/actions/docker-setup-qemu uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx # https://github.com/marketplace/actions/docker-setup-buildx uses: docker/setup-buildx-action@v2 - - name: Login to GitHub Container Registry + + - name: Login to DockerHub Registry # 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 with: - # GitHub Container Registry: - # registry: ghcr.io - # username: ${{ github.repository_owner }} - # password: ${{ secrets.GITHUB_TOKEN }} - # - # DockerHub: username: ${{ secrets.DOCKERHUB_USERNAME }} 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 run: python3 -m pip install --requirement .github/bin/requirements.txt + - name: Build and push images 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: DOCKER_REPOSITORY: ${{ secrets.DOCKERHUB_REPOSITORY }} - # DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - # DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + # GitHub Container Registry: + # 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 }} run: |