Skip to content

Commit

Permalink
- Basic implementation. Manually tested happy day scenario.
Browse files Browse the repository at this point in the history
  • Loading branch information
miroslavpojer committed Oct 24, 2024
1 parent d71a23c commit 4ee582c
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 1 deletion.
Empty file added .github/workflows/test.yml
Empty file.
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
name: 'Version Tag Check'
description: 'A GH action for validating version tag sequences and ensuring compliance with versioning standards in repositories.'
inputs:
github-repository:
description: 'The GitHub repository to check for the latest version tag. Example: "owner/repo".'
required: true
version-tag:
description: 'The version tag sequence to validate. Example: "v1.2.3".'
required: true
branch:
description: 'The branch to check for the latest version tag.'
description: 'The branch to check for the latest version tag. Example: "master".'
required: true
fails-on-error:
description: 'Set to "true" to fail the action if validation errors are found.'
Expand Down Expand Up @@ -67,6 +70,7 @@ runs:
id: version-tag-check
env:
INPUT_GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }}
INPUT_GITHUB_REPOSITORY: ${{ inputs.github-repository }}
INPUT_VERSION_TAG: ${{ inputs.version-tag }}
INPUT_BRANCH: ${{ inputs.branch }}
INPUT_FAILS_ON_ERROR: ${{ inputs.fails-on-error }}
Expand Down
5 changes: 5 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from version_tag_check.version_tag_check_action import VersionTagCheckAction

if __name__ == '__main__':
action = VersionTagCheckAction()
action.run()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests~=2.31.0
Empty file added tests/__init__.py
Empty file.
Empty file added tests/test_github_repository.py
Empty file.
Empty file added tests/test_version.py
Empty file.
Empty file.
Empty file added tests/test_version_validator.py
Empty file.
Empty file added version_tag_check/__init__.py
Empty file.
41 changes: 41 additions & 0 deletions version_tag_check/github_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import requests

from version_tag_check.version import Version


class GitHubRepository:
def __init__(self, owner: str, repo: str, token: str):
self.owner = owner
self.repo = repo
self.token = token
self.headers = {
'Authorization': f'Bearer {self.token}',
'Accept': 'application/vnd.github.v3+json'
}

def get_all_tags(self) -> list:
tags = []
page = 1
per_page = 100
while True:
response = requests.get(
f'https://api.github.com/repos/{self.owner}/{self.repo}/tags',
headers=self.headers,
params={'per_page': per_page, 'page': page}
)
if response.status_code != 200:
raise Exception(f'Failed to fetch tags: {response.status_code} {response.text}')
page_tags = response.json()
if not page_tags:
break
for tag_info in page_tags:
tag_name = tag_info['name']
try:
version = Version(tag_name)
tags.append(version)
except ValueError:
pass # Ignore tags that are not valid versions
if len(page_tags) < per_page:
break
page += 1
return tags
31 changes: 31 additions & 0 deletions version_tag_check/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import re
from functools import total_ordering

@total_ordering
class Version:
VERSION_REGEX = r'^v(\d+)\.(\d+)\.(\d+)$'

def __init__(self, version_str: str):
self.version_str = version_str
self.major = None
self.minor = None
self.patch = None
self.parse()

def parse(self):
match = re.match(self.VERSION_REGEX, self.version_str)
if not match:
raise ValueError(f'Invalid version format: {self.version_str}')
self.major, self.minor, self.patch = map(int, match.groups())

def is_valid_format(self) -> bool:
return re.match(self.VERSION_REGEX, self.version_str) is not None

def __eq__(self, other):
return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)

def __lt__(self, other):
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)

def __str__(self):
return self.version_str
66 changes: 66 additions & 0 deletions version_tag_check/version_tag_check_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import os
import sys

from version_tag_check.github_repository import GitHubRepository
from version_tag_check.version import Version
from version_tag_check.version_validator import VersionValidator


class VersionTagCheckAction:
def __init__(self):
self.github_token = os.environ.get('INPUT_GITHUB_TOKEN')
self.version_tag_str = os.environ.get('INPUT_VERSION_TAG')
self.branch = os.environ.get('INPUT_BRANCH')
self.fails_on_error = os.environ.get('INPUT_FAILS_ON_ERROR', 'true').lower() == 'true'
self.github_repository = os.environ.get('INPUT_GITHUB_REPOSITORY')

# TODO - extra method & add more validations
if not self.github_token:
print('GITHUB_TOKEN is not set.')
sys.exit(1)
if not self.github_repository:
print('GITHUB_REPOSITORY is not set.')
sys.exit(1)

self.owner, self.repo = self.github_repository.split('/')

def run(self):
try:
new_version = Version(self.version_tag_str)
except ValueError as e:
print(str(e))
self.handle_failure()
return

if not new_version.is_valid_format():
print('Tag does not match the required format "v[0-9]+.[0-9]+.[0-9]+"')
self.handle_failure()
return

repository = GitHubRepository(self.owner, self.repo, self.github_token)
existing_versions = repository.get_all_tags()

validator = VersionValidator(new_version, existing_versions)
if validator.is_valid_increment():
self.write_output('true')
print('New tag is valid.')
sys.exit(0)
else:
latest_version = validator.get_latest_version()
print(f'New tag {self.version_tag_str} is not one version higher than the latest tag {latest_version}.')
self.handle_failure()

def write_output(self, valid_value):
output_file = os.environ.get('GITHUB_OUTPUT')
if output_file:
with open(output_file, 'a') as fh:
print(f'valid={valid_value}', file=fh)
else:
print('GITHUB_OUTPUT is not set.')

def handle_failure(self):
self.write_output('false')
if self.fails_on_error:
sys.exit(1)
else:
sys.exit(0)
35 changes: 35 additions & 0 deletions version_tag_check/version_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Optional

from version_tag_check.version import Version


class VersionValidator:
def __init__(self, new_version: Version, existing_versions: list):
self.new_version = new_version
self.existing_versions = existing_versions

def get_latest_version(self) -> Optional[Version]:
if not self.existing_versions:
return None
return max(self.existing_versions)

def is_valid_increment(self) -> bool:
latest_version = self.get_latest_version()
if not latest_version:
# Any version is valid if no previous versions exist
return True

lv = latest_version
nv = self.new_version

if nv.major == lv.major:
if nv.minor == lv.minor:
return nv.patch == lv.patch + 1
elif nv.minor == lv.minor + 1:
return nv.patch == 0
else:
return False
elif nv.major == lv.major + 1:
return nv.minor == 0 and nv.patch == 0
else:
return False

0 comments on commit 4ee582c

Please sign in to comment.