From 0a5e56cb06cd5038de85deb4dfcd7866f3cb3023 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Aug 2021 16:05:55 +0200 Subject: [PATCH] =?utf8?q?=F0=9F=91=B7=20Add=20custom=20GitHub=20actions?= =?utf8?q?=20to=20preview=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../comment-docs-preview-in-pr/Dockerfile | 7 ++ .../comment-docs-preview-in-pr/action.yml | 13 +++ .../comment-docs-preview-in-pr/app/main.py | 70 ++++++++++++ .github/actions/watch-previews/Dockerfile | 7 ++ .github/actions/watch-previews/action.yml | 10 ++ .github/actions/watch-previews/app/main.py | 102 ++++++++++++++++++ 6 files changed, 209 insertions(+) create mode 100644 .github/actions/comment-docs-preview-in-pr/Dockerfile create mode 100644 .github/actions/comment-docs-preview-in-pr/action.yml create mode 100644 .github/actions/comment-docs-preview-in-pr/app/main.py create mode 100644 .github/actions/watch-previews/Dockerfile create mode 100644 .github/actions/watch-previews/action.yml create mode 100644 .github/actions/watch-previews/app/main.py diff --git a/.github/actions/comment-docs-preview-in-pr/Dockerfile b/.github/actions/comment-docs-preview-in-pr/Dockerfile new file mode 100644 index 00000000..4f20c5f1 --- /dev/null +++ b/.github/actions/comment-docs-preview-in-pr/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.7 + +RUN pip install httpx "pydantic==1.5.1" pygithub + +COPY ./app /app + +CMD ["python", "/app/main.py"] diff --git a/.github/actions/comment-docs-preview-in-pr/action.yml b/.github/actions/comment-docs-preview-in-pr/action.yml new file mode 100644 index 00000000..0eb64402 --- /dev/null +++ b/.github/actions/comment-docs-preview-in-pr/action.yml @@ -0,0 +1,13 @@ +name: Comment Docs Preview in PR +description: Comment with the docs URL preview in the PR +author: Sebastián Ramírez +inputs: + token: + description: Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }} + required: true + deploy_url: + description: The deployment URL to comment in the PR + required: true +runs: + using: docker + image: Dockerfile diff --git a/.github/actions/comment-docs-preview-in-pr/app/main.py b/.github/actions/comment-docs-preview-in-pr/app/main.py new file mode 100644 index 00000000..3b10e0ee --- /dev/null +++ b/.github/actions/comment-docs-preview-in-pr/app/main.py @@ -0,0 +1,70 @@ +import logging +import sys +from pathlib import Path +from typing import Optional + +import httpx +from github import Github +from github.PullRequest import PullRequest +from pydantic import BaseModel, BaseSettings, SecretStr, ValidationError + +github_api = "https://api.github.com" + + +class Settings(BaseSettings): + github_repository: str + github_event_path: Path + github_event_name: Optional[str] = None + input_token: SecretStr + input_deploy_url: str + + +class PartialGithubEventHeadCommit(BaseModel): + id: str + + +class PartialGithubEventWorkflowRun(BaseModel): + head_commit: PartialGithubEventHeadCommit + + +class PartialGithubEvent(BaseModel): + workflow_run: PartialGithubEventWorkflowRun + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + settings = Settings() + logging.info(f"Using config: {settings.json()}") + g = Github(settings.input_token.get_secret_value()) + repo = g.get_repo(settings.github_repository) + try: + event = PartialGithubEvent.parse_file(settings.github_event_path) + except ValidationError as e: + logging.error(f"Error parsing event file: {e.errors()}") + sys.exit(0) + use_pr: Optional[PullRequest] = None + for pr in repo.get_pulls(): + if pr.head.sha == event.workflow_run.head_commit.id: + use_pr = pr + break + if not use_pr: + logging.error( + f"No PR found for hash: {event.workflow_run.head_commit.id}" + ) + sys.exit(0) + github_headers = { + "Authorization": f"token {settings.input_token.get_secret_value()}" + } + url = f"{github_api}/repos/{settings.github_repository}/issues/{use_pr.number}/comments" + logging.info(f"Using comments URL: {url}") + response = httpx.post( + url, + headers=github_headers, + json={ + "body": f"📝 Docs preview for commit {use_pr.head.sha} at: {settings.input_deploy_url}" + }, + ) + if not (200 <= response.status_code <= 300): + logging.error(f"Error posting comment: {response.text}") + sys.exit(1) + logging.info("Finished") diff --git a/.github/actions/watch-previews/Dockerfile b/.github/actions/watch-previews/Dockerfile new file mode 100644 index 00000000..b8cc64d9 --- /dev/null +++ b/.github/actions/watch-previews/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.7 + +RUN pip install httpx PyGithub "pydantic==1.5.1" + +COPY ./app /app + +CMD ["python", "/app/main.py"] diff --git a/.github/actions/watch-previews/action.yml b/.github/actions/watch-previews/action.yml new file mode 100644 index 00000000..a9c4b3f7 --- /dev/null +++ b/.github/actions/watch-previews/action.yml @@ -0,0 +1,10 @@ +name: Watch docs previews in PRs +description: Check PRs and trigger new docs deploys +author: "Sebastián Ramírez " +inputs: + token: + description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}' + required: true +runs: + using: docker + image: Dockerfile diff --git a/.github/actions/watch-previews/app/main.py b/.github/actions/watch-previews/app/main.py new file mode 100644 index 00000000..8a6d4a25 --- /dev/null +++ b/.github/actions/watch-previews/app/main.py @@ -0,0 +1,102 @@ +import logging +from datetime import datetime +from pathlib import Path +from typing import List, Optional + +import httpx +from github import Github +from github.NamedUser import NamedUser +from pydantic import BaseModel, BaseSettings, SecretStr + +github_api = "https://api.github.com" +netlify_api = "https://api.netlify.com" +main_branch = "main" + + +class Settings(BaseSettings): + input_token: SecretStr + github_repository: str + github_event_path: Path + github_event_name: Optional[str] = None + + +class Artifact(BaseModel): + id: int + node_id: str + name: str + size_in_bytes: int + url: str + archive_download_url: str + expired: bool + created_at: datetime + updated_at: datetime + + +class ArtifactResponse(BaseModel): + total_count: int + artifacts: List[Artifact] + + +def get_message(commit: str) -> str: + return f"Docs preview for commit {commit} at" + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + settings = Settings() + logging.info(f"Using config: {settings.json()}") + g = Github(settings.input_token.get_secret_value()) + repo = g.get_repo(settings.github_repository) + owner: NamedUser = repo.owner + headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} + prs = list(repo.get_pulls(state="open")) + response = httpx.get( + f"{github_api}/repos/{settings.github_repository}/actions/artifacts", + headers=headers, + ) + data = response.json() + artifacts_response = ArtifactResponse.parse_obj(data) + for pr in prs: + logging.info("-----") + logging.info(f"Processing PR #{pr.number}: {pr.title}") + pr_comments = list(pr.get_issue_comments()) + pr_commits = list(pr.get_commits()) + last_commit = pr_commits[0] + for pr_commit in pr_commits: + if pr_commit.commit.author.date > last_commit.commit.author.date: + last_commit = pr_commit + commit = last_commit.commit.sha + logging.info(f"Last commit: {commit}") + message = get_message(commit) + notified = False + for pr_comment in pr_comments: + if message in pr_comment.body: + notified = True + logging.info(f"Docs preview was notified: {notified}") + if not notified: + artifact_name = f"docs-zip-{commit}" + use_artifact: Optional[Artifact] = None + for artifact in artifacts_response.artifacts: + if artifact.name == artifact_name: + use_artifact = artifact + break + if not use_artifact: + logging.info("Artifact not available") + else: + logging.info(f"Existing artifact: {use_artifact.name}") + response = httpx.post( + f"{github_api}/repos/{settings.github_repository}/actions/workflows/preview-docs.yml/dispatches", + headers=headers, + json={ + "ref": main_branch, + "inputs": { + "pr": f"{pr.number}", + "name": artifact_name, + "commit": commit, + }, + }, + ) + logging.info( + f"Trigger sent, response status: {response.status_code} - content: {response.content}" + ) + logging.info("Finished") -- 2.47.2