]> git.ipfire.org Git - thirdparty/fastapi/sqlmodel.git/commitdiff
👷 Add custom GitHub actions to preview docs
authorSebastián Ramírez <tiangolo@gmail.com>
Tue, 24 Aug 2021 14:05:55 +0000 (16:05 +0200)
committerSebastián Ramírez <tiangolo@gmail.com>
Tue, 24 Aug 2021 14:05:55 +0000 (16:05 +0200)
.github/actions/comment-docs-preview-in-pr/Dockerfile [new file with mode: 0644]
.github/actions/comment-docs-preview-in-pr/action.yml [new file with mode: 0644]
.github/actions/comment-docs-preview-in-pr/app/main.py [new file with mode: 0644]
.github/actions/watch-previews/Dockerfile [new file with mode: 0644]
.github/actions/watch-previews/action.yml [new file with mode: 0644]
.github/actions/watch-previews/app/main.py [new file with mode: 0644]

diff --git a/.github/actions/comment-docs-preview-in-pr/Dockerfile b/.github/actions/comment-docs-preview-in-pr/Dockerfile
new file mode 100644 (file)
index 0000000..4f20c5f
--- /dev/null
@@ -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 (file)
index 0000000..0eb6440
--- /dev/null
@@ -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 <tiangolo@gmail.com>
+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 (file)
index 0000000..3b10e0e
--- /dev/null
@@ -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 (file)
index 0000000..b8cc64d
--- /dev/null
@@ -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 (file)
index 0000000..a9c4b3f
--- /dev/null
@@ -0,0 +1,10 @@
+name: Watch docs previews in PRs
+description: Check PRs and trigger new docs deploys
+author: "Sebastián Ramírez <tiangolo@gmail.com>"
+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 (file)
index 0000000..8a6d4a2
--- /dev/null
@@ -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")