├── .github └── workflows │ ├── release.yml │ └── sync_readme.yml ├── .gitignore ├── Dockerfile ├── README.md ├── _config.yml ├── action.yml └── docker_readme.py /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: 5 | push: 6 | branches: 7 | - whale 8 | schedule: 9 | - cron: "0 0 * * *" # daily 10 | 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Build 21 | uses: docker/build-push-action@v1 22 | with: 23 | username: ${{ secrets.DOCKER_USERNAME }} 24 | password: ${{ secrets.DOCKER_PASSWORD }} 25 | repository: ${{ secrets.DOCKER_USERNAME }}/sync-dockerhub-readme 26 | tags: latest 27 | Dockerfile: Dockerfile 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/sync_readme.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Sync Readme 3 | 4 | on: 5 | push: 6 | branches: 7 | - whale 8 | schedule: 9 | - cron: "0 0 * * *" # daily 10 | 11 | 12 | jobs: 13 | sync: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Sync 21 | uses: ms-jpq/sync-dockerhub-readme@v1 22 | with: 23 | username: ${{ secrets.DOCKER_USERNAME }} 24 | password: ${{ secrets.DOCKER_PASSWORD }} 25 | repository: ${{ secrets.DOCKER_USERNAME }}/sync-dockerhub-readme 26 | readme: "./README.md" 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.git/ 2 | /.venv/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:alpine 2 | 3 | COPY docker_readme.py / 4 | ENTRYPOINT [ "/docker_readme.py" ] 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Sync Dockerhub Readme](https://ms-jpq.github.io/sync-dockerhub-readme) 2 | 3 | Sync to Dockerhub 4 | 5 | ## Github Action 6 | 7 | ```yaml 8 | - name: Sync 9 | uses: ms-jpq/sync-dockerhub-readme@v1 10 | with: 11 | username: 12 | password: 13 | repository: 14 | readme: "./README.md" 15 | 16 | ``` 17 | 18 | ## Docker Image 19 | 20 | [![Docker Pulls](https://img.shields.io/docker/pulls/msjpq/sync-dockerhub-readme.svg)](https://hub.docker.com/r/msjpq/sync-dockerhub-readme/) 21 | 22 | ```sh 23 | docker run -it --rm msjpq/sync-dockerhub-readme \ 24 | --username \ 25 | --password \ 26 | --repo \ 27 | --readme './README.md' 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dockerhub Sync Readme 3 | 4 | showcase: True 5 | 6 | images: [] 7 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dockerhub Readme 3 | author: ms-jpq 4 | description: |- 5 | sync Github with Dockerhub 6 | 7 | inputs: 8 | username: 9 | required: True 10 | description: |- 11 | Dockerhub username 12 | 13 | password: 14 | required: True 15 | description: |- 16 | Dockerhub password 17 | 18 | readme: 19 | required: True 20 | description: |- 21 | path to readme markdown 22 | 23 | repository: 24 | required: True 25 | description: |- 26 | list of repositories to update 27 | (without username) 28 | 29 | runs: 30 | using: docker 31 | image: "Dockerfile" 32 | env: 33 | DOCKER_USERNAME: "${{ inputs.username }}" 34 | DOCKER_PASSWORD: "${{ inputs.password }}" 35 | args: 36 | - "--readme" 37 | - "${{ inputs.readme }}" 38 | - "--repo" 39 | - "${{ inputs.repository }}" 40 | 41 | branding: 42 | icon: anchor 43 | color: blue 44 | 45 | -------------------------------------------------------------------------------- /docker_readme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from argparse import ArgumentParser, Namespace 4 | from json import dumps, loads 5 | from os import environ 6 | from shutil import get_terminal_size 7 | from urllib.request import Request, urlopen 8 | 9 | 10 | def big_print(msg: str) -> None: 11 | _, cols = get_terminal_size() 12 | print(cols * "-") 13 | print(msg) 14 | print(cols * "-") 15 | 16 | 17 | def slurp(path: str) -> str: 18 | with open(path) as fd: 19 | return fd.read() 20 | 21 | 22 | def parse_args() -> Namespace: 23 | parser = ArgumentParser() 24 | 25 | username = environ.get("DOCKER_USERNAME") 26 | parser.add_argument("--username", required=not username, default=username) 27 | 28 | password = environ.get("DOCKER_PASSWORD") 29 | parser.add_argument("--password", required=not password, default=password) 30 | 31 | parser.add_argument("--readme", required=True) 32 | parser.add_argument("--repo", required=True) 33 | 34 | args = parser.parse_args() 35 | return args 36 | 37 | 38 | def login(username: str, password: str) -> str: 39 | uri = "https://hub.docker.com/v2/users/login/" 40 | data = {"username": username, "password": password} 41 | req = Request( 42 | uri, 43 | method="POST", 44 | headers={"Content-Type": "application/json"}, 45 | data=dumps(data).encode(), 46 | ) 47 | with urlopen(req) as resp: 48 | msg = resp.read().decode() 49 | return loads(msg)["token"] 50 | 51 | 52 | def set_repo(token: str, repo: str, readme: str) -> str: 53 | uri = f"https://hub.docker.com/v2/repositories/{repo}/" 54 | data = {"full_description": readme} 55 | req = Request( 56 | uri, 57 | method="PATCH", 58 | headers={ 59 | "Content-Type": "application/json", 60 | "Authorization": f"Bearer {token}", 61 | }, 62 | data=dumps(data).encode(), 63 | ) 64 | with urlopen(req) as resp: 65 | msg = resp.read().decode() 66 | return loads(msg)["full_description"] 67 | 68 | 69 | def main() -> None: 70 | args = parse_args() 71 | readme = slurp(args.readme) 72 | token = login(args.username, args.password) 73 | desc = set_repo(token=token, repo=args.repo, readme=readme,) 74 | big_print(desc) 75 | 76 | 77 | main() 78 | --------------------------------------------------------------------------------