From 6755ea287835667dddf2194e493795b69ceff9d2 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Mon, 31 May 2021 17:09:26 -0700 Subject: [PATCH] docker: fix publish workflow Since a credential rotation, the GitHub Action responsible for pushing the [src-batch-change-volume-workspace][1] has been failing. This action works as expected when used via Docker outside of the GitHub Action infrastructure; I suspect there's an issue with the handling of a particular character in the action-specific entry script. That said, there's not _much_ reason to pull in a third party action here; the push script already has the required credentials, and the Docker Hub API, while undocumented, is straightforward. I've added the required urllib-foo to make this happen. (I'd normally pull in Python's excellent requests library for this kind of thing, but that means we'd have to start installing dependencies in the GitHub Action that runs this script, and that feels like more effort than I'm really willing to go to here.) Fixes #548. [1]: https://hub.docker.com/r/sourcegraph/src-batch-change-volume-workspace --- .github/workflows/docker.yml | 10 +-- .github/workflows/goreleaser-check.yml | 2 +- .github/workflows/goreleaser.yml | 2 +- docker/batch-change-volume-workspace/push.py | 67 ++++++++++++++++++-- 4 files changed, 65 insertions(+), 16 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 92f8249a38..e7338c4eb6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,15 +28,7 @@ jobs: with: platforms: arm64 - - run: ./docker/batch-change-volume-workspace/push.py -d ./docker/batch-change-volume-workspace/Dockerfile -i sourcegraph/src-batch-change-volume-workspace -p linux/amd64,linux/arm64,linux/386 + - run: ./docker/batch-change-volume-workspace/push.py -d ./docker/batch-change-volume-workspace/Dockerfile -i sourcegraph/src-batch-change-volume-workspace -p linux/amd64,linux/arm64,linux/386 --readme ./docker/batch-change-volume-workspace/README.md env: DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKER_USERNAME: sourcegraphci - - - name: Update Docker Hub description - uses: peter-evans/dockerhub-description@v2 - with: - username: sourcegraphci - password: ${{ secrets.DOCKER_PASSWORD }} - repository: sourcegraph/src-batch-change-volume-workspace - readme-filepath: ./docker/batch-change-volume-workspace/README.md diff --git a/.github/workflows/goreleaser-check.yml b/.github/workflows/goreleaser-check.yml index 4b4d7a7735..356cabbf94 100644 --- a/.github/workflows/goreleaser-check.yml +++ b/.github/workflows/goreleaser-check.yml @@ -1,4 +1,4 @@ -name: GoReleaser +name: GoReleaser check on: - push diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 13d5d43016..a50d121471 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -1,4 +1,4 @@ -name: Goreleaser +name: GoReleaser on: push: diff --git a/docker/batch-change-volume-workspace/push.py b/docker/batch-change-volume-workspace/push.py index ed44845718..719c01e1aa 100755 --- a/docker/batch-change-volume-workspace/push.py +++ b/docker/batch-change-volume-workspace/push.py @@ -29,12 +29,17 @@ # More instructions on this can be found at # https://docs.docker.com/buildx/working-with-buildx/#build-multi-platform-images. +from __future__ import annotations + import argparse import itertools +import json import os import subprocess -from typing import BinaryIO, Optional, Sequence +from ssl import SSLContext +from typing import BinaryIO, Optional, Sequence, TextIO +from urllib.request import urlopen, Request def calculate_tags(ref: str) -> Sequence[str]: @@ -65,7 +70,7 @@ def calculate_tags(ref: str) -> Sequence[str]: return tags -def docker_build( +def docker_cli_build( dockerfile: BinaryIO, platform: Optional[str], image: str, tags: Sequence[str] ): args = ["docker", "buildx", "build", "--push"] @@ -84,7 +89,7 @@ def docker_build( run(args, stdin=dockerfile) -def docker_login(username: str, password: str): +def docker_cli_login(username: str, password: str): run( ["docker", "login", f"-u={username}", "--password-stdin"], input=password, @@ -92,6 +97,47 @@ def docker_login(username: str, password: str): ) +class DockerHub: + context: SSLContext + token: str + + @staticmethod + def login(username: str, password: str) -> DockerHub: + context = SSLContext() + context.load_default_certs() + + with urlopen( + Request( + "https://hub.docker.com/v2/users/login", + method="POST", + data=json.dumps({"username": username, "password": password}).encode( + "utf-8" + ), + headers={"Content-Type": "application/json; charset=utf-8"}, + ), + context=context, + ) as resp: + hub = DockerHub() + hub.context = context + hub.token = json.load(resp)["token"] + + return hub + + def update_description(self, image: str, file: TextIO) -> None: + urlopen( + Request( + f"https://hub.docker.com/v2/repositories/{image}/", + method="PATCH", + data=json.dumps({"full_description": file.read()}).encode("utf-8"), + headers={ + "Content-Type": "application/json; charset=utf-8", + "Authorization": f"JWT {self.token}", + }, + ), + context=self.context, + ) + + def run(args: Sequence[str], /, **kwargs) -> subprocess.CompletedProcess: print(f"+ {' '.join(args)}") return subprocess.run(args, check=True, **kwargs) @@ -114,6 +160,11 @@ def main(): default=os.environ.get("GITHUB_REF"), help="current ref in refs/heads/... or refs/tags/... form", ) + parser.add_argument( + "--readme", + default="./README.md", + help="README to update the Docker Hub description from", + ) args = parser.parse_args() tags = calculate_tags(args.ref) @@ -121,13 +172,19 @@ def main(): print("logging into Docker Hub") try: - docker_login(os.environ["DOCKER_USERNAME"], os.environ["DOCKER_PASSWORD"]) + docker_cli_login(os.environ["DOCKER_USERNAME"], os.environ["DOCKER_PASSWORD"]) except KeyError as e: print(f"error retrieving environment variables: {e}") raise print("building and pushing image") - docker_build(open(args.dockerfile, "rb"), args.platform, args.image, tags) + docker_cli_build(open(args.dockerfile, "rb"), args.platform, args.image, tags) + + print("acquiring token to update description") + hub = DockerHub.login(os.environ["DOCKER_USERNAME"], os.environ["DOCKER_PASSWORD"]) + + print("updating description") + hub.update_description(args.image, open(args.readme, "r")) print("success!")