Skip to content

Commit

Permalink
Merge 9c9797d into 11f28e0
Browse files Browse the repository at this point in the history
  • Loading branch information
CasperWA authored Oct 30, 2023
2 parents 11f28e0 + 9c9797d commit 895eed3
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ jobs:
token: '${{ secrets.CI_PUSH_TO_PROTECTED_BRANCH }}'
branch: protected
unprotect_reviews: true
debug: true

- name: Pushing to a protected branch without any changes
uses: ./
with:
token: '${{ secrets.CI_PUSH_TO_PROTECTED_BRANCH }}'
ref: refs/heads/protected
unprotect_reviews: true
debug: true

force-pushing:
needs: [protected]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ All input names in **bold** are _required_.
| `unprotect_reviews` | Momentarily remove pull request review protection from target branch.<br>**Note**: One needs administrative access to the repository to be able to use this feature. This means two things need to match up: The PAT must represent a user with administrative rights, and these rights need to be granted to the usage scope of the PAT. | `False` |
| `debug` | Set `set -x` in `entrypoint.sh` when running the action. This is for debugging the action. | `False` |
| `path` | A path to the working directory of the action. This should be relative to the `$GITHUB_WORKSPACE`. | `.` |
| `gh_rest_api_base_url` | The base URL for the GitHub REST API. This is useful for GitHub Enterprise users.</br>Note, `/api/v3` will be appended to this value if it does not already exist. See the note [here](https://docs.github.com/en/[email protected]/rest/quickstart?apiVersion=2022-11-28&tool=curl#using-curl-commands-in-github-actions). | `https://api.github.com` |

## License

Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ inputs:
description: 'A path to the working directory of the action. This should be relative to the $GITHUB_WORKSPACE.'
required: false
default: '.'
gh_rest_api_base_url:
description: 'The base URL for the GitHub REST API. This is useful for GitHub Enterprise users. Note, `/api/v3` will be appended to this value if it does not already exist. See the note here: https://docs.github.com/en/[email protected]/rest/quickstart?apiVersion=2022-11-28&tool=curl#using-curl-commands-in-github-actions.'
required: false
default: 'https://api.github.com'
runs:
using: 'docker'
image: 'Dockerfile'
9 changes: 6 additions & 3 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ esac
set -e

# Utility functions
ere_quote() {
sed 's/[][\.|$(){}?+*^]/\\&/g' <<< "$*"
}
unprotect () {
case ${INPUT_UNPROTECT_REVIEWS} in
y | Y | yes | Yes | YES | true | True | TRUE | on | On | ON)
Expand Down Expand Up @@ -102,9 +105,9 @@ git config --global --add safe.directory ${PWD}

# Retrieve target repository
echo -e "\nFetching the latest information from '${GITHUB_REPOSITORY}' ..."
git config --local --name-only --get-regexp "http\.https\:\/\/github\.com\/\.extraheader" && git config --local --unset-all "http.https://github.com/.extraheader" || :
git submodule foreach --recursive 'git config --local --name-only --get-regexp "http\.https\:\/\/github\.com\/\.extraheader" && git config --local --unset-all "http.https://github.com/.extraheader" || :'
git remote set-url origin https://${GITHUB_ACTOR}:${INPUT_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
git config --local --name-only --get-regexp "http\.$(ere_quote "${GITHUB_SERVER_URL}")\/\.extraheader" && git config --local --unset-all "http.${GITHUB_SERVER_URL}/.extraheader" || :
git submodule foreach --recursive 'git config --local --name-only --get-regexp "http\.$(ere_quote "${GITHUB_SERVER_URL}")\/\.extraheader" && git config --local --unset-all "http.${GITHUB_SERVER_URL}/.extraheader" || :'
git remote set-url origin $(push-action --token "null" --ref "null" --temp-branch "null" -- create_origin_url)
git fetch --unshallow -tp origin || :
echo "Fetching the latest information from '${GITHUB_REPOSITORY}' ... DONE!"

Expand Down
10 changes: 10 additions & 0 deletions push_action/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
This Python module is a helper module to interact with the GitHub API.
It is meant to only be used in the `push-protected` GitHub action.
"""
import logging
import os
import sys

__version__ = "2.14.0"
__author__ = "Casper Welzel Andersen"


LOGGER = logging.getLogger("push_action")

if os.getenv("INPUT_DEBUG", "").lower() in ("true", "1"):
LOGGER.setLevel(logging.DEBUG)
LOGGER.addHandler(logging.StreamHandler(sys.stderr))
2 changes: 1 addition & 1 deletion push_action/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""
from typing import TYPE_CHECKING

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from typing import Any, Iterator


Expand Down
58 changes: 54 additions & 4 deletions push_action/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
1) Get required statuses for branch (GitHub Actions jobs / third party status checks)
from:
https://api.github.com/repos/:owner/:repo/branches/:branch
{input_gh_rest_api_base_url}/repos/:owner/:repo/branches/:branch
protection -> required_status_checks -> contexts
2) Get GitHub Actions runs for specific workflow:
https://api.github.com/repos/:owner/:repo/actions/workflows/:workflow_id/runs
{input_gh_rest_api_base_url}/repos/:owner/:repo/actions/workflows/:workflow_id/runs
:workflow_id can also be :workflow_file_name (e.g., 'main.yml')
Get :run_id from this
3) Get names and statuses of jobs in specific run:
https://api.github.com/repos/:owner/:repo/actions/runs/:run_id/jobs
{input_gh_rest_api_base_url}/repos/:owner/:repo/actions/runs/:run_id/jobs
Match found required GitHub Actions runs found in 1)
4) Wait and do 3) again until required GitHub Actions jobs have "status": "completed"
Expand All @@ -25,10 +25,12 @@
"""
import argparse
import json
import logging
import os
import sys
from time import sleep, time
from typing import TYPE_CHECKING
from urllib.parse import urlsplit

from push_action.cache import IN_MEMORY_CACHE
from push_action.utils import (
Expand All @@ -40,10 +42,13 @@
remove_branch,
)

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from typing import Any, Dict


LOGGER = logging.getLogger("push_action.run")


def wait() -> None:
"""Wait until status checks have finished"""
required_statuses = get_branch_statuses(IN_MEMORY_CACHE["args"].ref)
Expand Down Expand Up @@ -197,6 +202,46 @@ def protected_branch(branch: str) -> str:
return "protected" if response["protected"] else ""


def compile_origin_url() -> str:
"""Compile the git remote 'origin' URL for the repository."""
compiled_url = ""

for required_env_vars in [
"GITHUB_SERVER_URL",
"GITHUB_REPOSITORY",
"GITHUB_ACTOR",
"INPUT_TOKEN",
]:
if required_env_vars not in os.environ:
raise RuntimeError(
f"Required rnvironment variable {required_env_vars} is not set."
)

base_url = os.getenv("GITHUB_SERVER_URL", "")
split_base_url = urlsplit(base_url)

if not (split_base_url.scheme or split_base_url.netloc):
raise RuntimeError(
f"Could not determine scheme and netloc from GITHUB_SERVER_URL: {base_url}"
)

# Add scheme
compiled_url += f"{split_base_url.scheme}://"

# Add username and token
compiled_url += os.getenv("GITHUB_ACTOR", "")
compiled_url += ":"
compiled_url += os.getenv("INPUT_TOKEN", "")

# Add netloc
compiled_url += f"@{split_base_url.netloc}"

# Add path (repository)
compiled_url += f"/{os.getenv('GITHUB_REPOSITORY', '')}.git"

return compiled_url


def main() -> None:
"""Main function to run this module"""
# Handle inputs
Expand Down Expand Up @@ -247,11 +292,14 @@ def main() -> None:
"unprotect_reviews",
"protect_reviews",
"protected_branch",
"create_origin_url",
],
)

IN_MEMORY_CACHE["args"] = parser.parse_args()

LOGGER.debug("Parsed args: %s", IN_MEMORY_CACHE["args"])

fail = ""
try:
if IN_MEMORY_CACHE["args"].ACTION == "wait_for_checks":
Expand All @@ -264,6 +312,8 @@ def main() -> None:
protect_reviews()
elif IN_MEMORY_CACHE["args"].ACTION == "protected_branch":
print(protected_branch(IN_MEMORY_CACHE["args"].ref), end="", flush=True)
elif IN_MEMORY_CACHE["args"].ACTION == "create_origin_url":
print(compile_origin_url(), end="", flush=True)
else:
raise RuntimeError(f"Unknown ACTIONS {IN_MEMORY_CACHE['args'].ACTION!r}")
except Exception as exc: # pylint: disable=broad-except
Expand Down
31 changes: 22 additions & 9 deletions push_action/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Utility functions for use in the `push_action.run` module.
"""
from enum import Enum
import logging
import os
from time import time
from typing import TYPE_CHECKING
Expand All @@ -17,13 +18,18 @@
import requests

from push_action.cache import IN_MEMORY_CACHE
from push_action.validate import validate_rest_api_base_url

if TYPE_CHECKING:
from typing import Callable, List, Optional, Union
if TYPE_CHECKING: # pragma: no cover
from typing import Callable, List, Union


LOGGER = logging.getLogger("push_action.utils")


REQUEST_TIMEOUT = 10 # in seconds
API_V3_BASE = "https://api.github.com"
API_V3_BASE = validate_rest_api_base_url(os.getenv("INPUT_GH_REST_API_BASE_URL", ""))
API_VERSION = "2022-11-28"


class RepoRole(Enum):
Expand Down Expand Up @@ -73,6 +79,7 @@ def api_request(
headers={
"Authorization": f"Bearer {IN_MEMORY_CACHE['args'].token}",
"Accept": "application/vnd.github.v3+json",
"X-GitHub-Api-Version": API_VERSION,
},
timeout=REQUEST_TIMEOUT,
**kwargs,
Expand Down Expand Up @@ -115,6 +122,12 @@ def api_request(
except json.JSONDecodeError as exc:
raise RuntimeError(f"Failed to jsonify response.\n{exc!r}") from exc

LOGGER.debug(
"API Call to: %s\nResponse: %s",
url,
response.text if isinstance(response, requests.Response) else response,
)

return response


Expand Down Expand Up @@ -186,9 +199,9 @@ def get_workflow_runs(workflow_id: int, new_request: bool = False) -> "List[dict
)

workflow_runs = [
_
for _ in response.get("workflow_runs", [])
if _.get("head_branch", "") == IN_MEMORY_CACHE["args"].temp_branch
run
for run in response.get("workflow_runs", [])
if run.get("head_branch", "") == IN_MEMORY_CACHE["args"].temp_branch
]

if cache_name in IN_MEMORY_CACHE:
Expand Down Expand Up @@ -247,16 +260,16 @@ def get_required_actions(
f"{type(response)}"
)

runs = []
runs: "List[dict]" = []
for workflow in response["workflows"]:
runs.extend(get_workflow_runs(workflow["id"]))

jobs = []
jobs: "List[dict]" = []
for run in runs:
jobs.extend(get_workflow_run_jobs(run["id"]))

IN_MEMORY_CACHE[cache_name] = [
_ for _ in jobs if _.get("name", "") in statuses
job for job in jobs if job.get("name", "") in statuses
]

return IN_MEMORY_CACHE[cache_name]
Expand Down
40 changes: 40 additions & 0 deletions push_action/validate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Validate inputs."""
from urllib.parse import urlsplit

GITHUB_FREE_REST_API_BASE_URL = "https://api.github.com"
"""Base URL for GitHub Free API."""

GITHUB_ENTERPRISE_API_PREFIX = "/api/v3"
"""Prefix for GitHub Enterprise API URLs.
See the note for more information here:
https://docs.github.com/en/[email protected]/rest/quickstart?apiVersion=2022-11-28&tool=curl#using-curl-commands-in-github-actions.
"""


def validate_rest_api_base_url(base_url: str) -> str:
"""Validate and parse the `gh_rest_api_base_url` input."""
split_base_url = urlsplit(base_url)

if not split_base_url.scheme or not split_base_url.netloc:
raise ValueError(
"Invalid URL provided for `gh_rest_api_base_url` input (missing scheme "
"and/or netloc)."
)

compiled_url = split_base_url.scheme + "://" + split_base_url.netloc

if compiled_url == GITHUB_FREE_REST_API_BASE_URL:
return compiled_url

url_path = split_base_url.path.rstrip("/")

if url_path and not split_base_url.path.endswith(GITHUB_ENTERPRISE_API_PREFIX):
raise ValueError(
"Invalid URL provided for `gh_rest_api_base_url` input (path must end with "
f"{GITHUB_ENTERPRISE_API_PREFIX!r})."
)

if not url_path:
compiled_url += GITHUB_ENTERPRISE_API_PREFIX

return compiled_url

0 comments on commit 895eed3

Please sign in to comment.