Skip to content

Commit

Permalink
fix!: use github context instead of events API
Browse files Browse the repository at this point in the history
  • Loading branch information
ReenigneArcher committed Jul 14, 2024
1 parent c56dda1 commit d040c23
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 114 deletions.
12 changes: 0 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,10 @@ The action does the following:
github_token: ${{ secrets.GITHUB_TOKEN }}
```
## Advanced Usage
```yaml
- name: Setup Release
id: setup_release
uses: LizardByte/setup-release-action@master
with:
fail_on_events_api_error: true
github_token: ${{ secrets.GITHUB_TOKEN }}
```
## Inputs
| Name | Description | Default | Required |
|------------------------------|--------------------------------------------------------------------------------------------|---------|----------|
| dotnet | Whether to create a dotnet version (4 components, e.g. yyyy.mmdd.hhmm.ss). | `false` | `false` |
| event_api_max_attempts | Maximum number of attempts for the GitHub Events API. | `10` | `false` |
| fail_on_events_api_error | Whether to fail if the GitHub Events API returns an error. Will only fail for push events. | `true` | `false` |
| github_token | GitHub token to use for API requests. | | `true` |
| include_tag_prefix_in_output | Whether to include the tag prefix in the output. | `true` | `false` |
| tag_prefix | The tag prefix. This will be used when searching for existing releases in GitHub API. | `v` | `false` |
Expand Down
8 changes: 0 additions & 8 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ inputs:
description: "Whether to create a dotnet version (4 components, e.g. yyyy.mmdd.hhmm.ss)."
default: 'false'
required: false
event_api_max_attempts:
description: "Maximum number of attempts for the GitHub Events API."
default: '30'
required: false
fail_on_events_api_error:
description: "Whether to fail if the GitHub Events API returns an error. Will only fail for push events."
default: 'true'
required: false
github_token:
description: "GitHub token to use for API requests."
required: true
Expand Down
105 changes: 65 additions & 40 deletions action/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import json
import os
import re
import time

# lib imports
from dotenv import load_dotenv
Expand Down Expand Up @@ -93,9 +92,7 @@ def get_github_event() -> dict:
dict
Dictionary containing the GitHub event.
"""
github_event_path = os.getenv(
'GITHUB_EVENT_PATH', os.path.join(ROOT_DIR, 'dummy_github_event.json')
)
github_event_path = os.environ['GITHUB_EVENT_PATH']
with open(github_event_path, 'r') as f:
github_event = json.load(f)
return github_event
Expand All @@ -115,6 +112,44 @@ def get_repo_default_branch() -> str:
return github_event['repository']['default_branch']


def get_repo_squash_and_merge_required() -> bool:
"""
Check if squash and merge is required for the repository.
Returns
-------
bool
True if squash and merge is required, False otherwise.
Raises
------
KeyError
If the required keys are not found in the GitHub API response.
Ensure the token has the `"Metadata" repository permissions (read)` scope.
"""
github_api_url = f'https://api.github.com/repos/{REPOSITORY_NAME}'
response = requests.get(url=github_api_url, headers=GITHUB_HEADERS)
repo_info = response.json()

try:
allow_squash_merge = repo_info['allow_squash_merge']
allow_merge_commit = repo_info['allow_merge_commit']
allow_rebase_merge = repo_info['allow_rebase_merge']
except KeyError:
msg = ('::error:: Could not find the required keys in the GitHub API response. '
'Ensure the token has the `"Metadata" repository permissions (read)` scope.')
print(msg)
raise KeyError(msg)

if allow_squash_merge and not allow_merge_commit and not allow_rebase_merge:
return True
else:
print('::error:: The repository must have ONLY squash and merge enabled.')
print('::warning:: DO NOT re-run this job after changing the repository settings.'
'Wait until a new commit is made.')
return False


def get_push_event_details() -> dict:
"""
Get the details of the GitHub push event from the API.
Expand All @@ -133,15 +168,6 @@ def get_push_event_details() -> dict:
release_version='',
)

github_api_url = f'https://api.github.com/repos/{REPOSITORY_NAME}/events'
# this endpoint can be delayed between 30 seconds and 6 hours...
# https://docs.github.com/en/rest/activity/events?apiVersion=2022-11-28#list-repository-events

# query parameters
params = {
"per_page": 100
}

github_event = get_github_event()

is_pull_request = True if github_event.get("pull_request") else False
Expand All @@ -154,38 +180,37 @@ def get_push_event_details() -> dict:
github_sha = os.environ["GITHUB_SHA"]
push_event_details['release_commit'] = github_sha

if not is_pull_request: # this is a push event
attempt = 0
push_event = None
while push_event is None and attempt < int(os.getenv('INPUT_EVENT_API_MAX_ATTEMPTS', 30)):
time.sleep(10)
response = requests.get(github_api_url, headers=GITHUB_HEADERS, params=params)

for event in response.json():
if event["type"] == "PushEvent" and event["payload"]["head"] == github_sha:
push_event = event
if event["type"] == "PushEvent" and \
event["payload"]["ref"] == f"refs/heads/{get_repo_default_branch()}":
break

attempt += 1

if push_event is None:
msg = f":exclamation: ERROR: Push event not found in the GitHub API after {attempt} attempts."
append_github_step_summary(message=msg)
if os.getenv('INPUT_FAIL_ON_EVENTS_API_ERROR', 'false').lower() == 'true':
raise SystemExit(msg)
else:
return push_event_details
else: # this is a pull request
if is_pull_request:
return push_event_details

# not a pull request
# this is a push event
# check if squash and merge is required
if not get_repo_squash_and_merge_required():
msg = (":exclamation: ERROR: Squash and merge is not enabled for this repository. "
"Please ensure ONLY squash and merge is enabled. "
"**DO NOT** re-run this job after changing the repository settings. Wait until a new commit is made.")
append_github_step_summary(message=msg)
print(f'::error:: {msg}')
raise SystemExit(2)

# ensure there is only 1 commit in the github context
if len(github_event["commits"]) != 1:
msg = (":exclamation: ERROR: This action only supports a single commit push event. "

Check warning on line 198 in action/main.py

View check run for this annotation

Codecov / codecov/patch

action/main.py#L198

Added line #L198 was not covered by tests
"Please ensure ONLY squash and merge is enabled. "
"**DO NOT** re-run this job after changing the repository settings. Wait until a new commit is made.")
append_github_step_summary(message=msg)
print(f'::error:: {msg}')
raise SystemExit(3)

Check warning on line 203 in action/main.py

View check run for this annotation

Codecov / codecov/patch

action/main.py#L201-L203

Added lines #L201 - L203 were not covered by tests

# get the commit
commit = github_event["commits"][0]

# not a pull request, so publish
push_event_details['publish_release'] = True

# use regex and convert created at to yyyy.m.d-hhmmss
# created_at: "2023-1-25T10:43:35Z"
match = re.search(r'(\d{4})-(\d{1,2})-(\d{1,2})T(\d{1,2}):(\d{2}):(\d{2})Z', push_event["created_at"])
# timestamp: "2023-01-25T10:43:35Z"
match = re.search(r'(\d{4})-(\d{1,2})-(\d{1,2})T(\d{1,2}):(\d{2}):(\d{2})Z', commit["timestamp"])

release_version = ''
if match:
Expand Down
5 changes: 0 additions & 5 deletions dummy_github_event.json

This file was deleted.

104 changes: 64 additions & 40 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
import pytest
import requests

# local imports
from action import main

# set environment variables
load_dotenv()
os.environ['GITHUB_REPOSITORY'] = 'LizardByte/setup-release-action'
os.environ['GITHUB_OUTPUT'] = 'github_output.md'
os.environ['GITHUB_STEP_SUMMARY'] = 'github_step_summary.md'
os.environ['GITHUB_WORKSPACE'] = os.path.join(os.getcwd(), 'github', 'workspace')
os.environ['INPUT_EVENT_API_MAX_ATTEMPTS'] = '1'
os.environ['INPUT_FAIL_ON_EVENTS_API_ERROR'] = 'false'

try:
GITHUB_TOKEN = os.environ['INPUT_GITHUB_TOKEN']
Expand Down Expand Up @@ -92,57 +93,38 @@ def latest_commit(github_token):


@pytest.fixture(scope='function')
def dummy_commit():
original_sha = os.environ.get('GITHUB_SHA', '')
os.environ['GITHUB_SHA'] = 'not-a-real-commit'
def dummy_github_pr_event_path():
original_value = os.getenv('GITHUB_EVENT_PATH', os.path.join(DATA_DIRECTORY, 'dummy_github_event.json'))
os.environ['GITHUB_EVENT_PATH'] = os.path.join(DATA_DIRECTORY, 'dummy_github_push_event.json')
yield
os.environ['GITHUB_EVENT_PATH'] = original_value

os.environ['GITHUB_SHA'] = original_sha

@pytest.fixture(scope='function')
def dummy_github_push_event_path():
original_value = os.getenv('GITHUB_EVENT_PATH', os.path.join(DATA_DIRECTORY, 'dummy_github_event.json'))
os.environ['GITHUB_EVENT_PATH'] = os.path.join(DATA_DIRECTORY, 'dummy_github_pr_event.json')
yield
os.environ['GITHUB_EVENT_PATH'] = original_value


@pytest.fixture(scope='function', params=[True, False])
def github_event_path(request):
# true is original file from GitHub context
# false is dummy file

original_value = os.getenv(
'GITHUB_EVENT_PATH',
os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'dummy_github_event.json'
)
)
def github_event_path(request, dummy_github_pr_event_path, dummy_github_push_event_path):
# true is PR event
# false is push event

original_value = os.getenv('GITHUB_EVENT_PATH', os.path.join(DATA_DIRECTORY, 'dummy_github_event.json'))

if request.param:
os.environ['GITHUB_EVENT_PATH'] = os.path.join(DATA_DIRECTORY, 'dummy_github_pr_event.json')
yield
else:
os.environ['GITHUB_EVENT_PATH'] = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'dummy_github_event.json'
)
os.environ['GITHUB_EVENT_PATH'] = os.path.join(DATA_DIRECTORY, 'dummy_github_push_event.json')
yield

os.environ['GITHUB_EVENT_PATH'] = original_value


@pytest.fixture(scope='function')
def dummy_github_event_path():
original_value = os.environ['GITHUB_EVENT_PATH']
os.environ['GITHUB_EVENT_PATH'] = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'dummy_github_event.json'
)
yield
os.environ['GITHUB_EVENT_PATH'] = original_value


@pytest.fixture(scope='function')
def fail_on_events_api_error():
os.environ['INPUT_FAIL_ON_EVENTS_API_ERROR'] = 'true'
yield
os.environ['INPUT_FAIL_ON_EVENTS_API_ERROR'] = 'false'


@pytest.fixture(params=[True, False], scope='function')
def input_dotnet(request):
os.environ['INPUT_DOTNET'] = str(request.param).lower()
Expand All @@ -164,7 +146,7 @@ def requests_get_error():
requests.get = original_get


@pytest.fixture(params=[True, False])
@pytest.fixture(scope='function', params=[True, False])
def mock_get_push_event_details(request):
if request.param:
# If the parameter is True, return a mock
Expand All @@ -180,6 +162,48 @@ def mock_get_push_event_details(request):
yield


@pytest.fixture(scope='function', params=[True, False])
def mock_get_repo_squash_and_merge_required(request):
original_get = requests.get

mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'allow_squash_merge': True,
'allow_merge_commit': not request.param,
'allow_rebase_merge': not request.param,
}

requests.get = Mock(return_value=mock_response)

yield request.param

requests.get = original_get


@pytest.fixture(scope='function')
def mock_get_repo_squash_and_merge_required_key_error():
original_get = requests.get

mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {}

requests.get = Mock(return_value=mock_response)

yield

requests.get = original_get


@pytest.fixture(scope='function')
def mock_get_squash_and_merge_return_value():
original_function = main.get_repo_squash_and_merge_required
main.get_repo_squash_and_merge_required = Mock(return_value=False)
yield
main.get_repo_squash_and_merge_required = original_function


@pytest.fixture(scope='module')
def gh_provided_release_notes_sample():
with open(os.path.join(DATA_DIRECTORY, 'gh_provided_release_notes_sample.md'), 'r') as f:
Expand Down
10 changes: 10 additions & 0 deletions tests/data/dummy_github_event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"repository": {
"default_branch": "master"
},
"commits": [
{
"timestamp": "2020-01-01T00:00:00Z"
}
]
}
11 changes: 11 additions & 0 deletions tests/data/dummy_github_pr_event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"repository": {
"default_branch": "master"
},
"commits": [
{
"timestamp": "2020-01-01T00:00:00Z"
}
],
"pull_request": {}
}
10 changes: 10 additions & 0 deletions tests/data/dummy_github_push_event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"repository": {
"default_branch": "master"
},
"commits": [
{
"timestamp": "2020-01-01T00:00:00Z"
}
]
}
Loading

0 comments on commit d040c23

Please sign in to comment.