Skip to content

Commit

Permalink
Update auto-merge with test suite
Browse files Browse the repository at this point in the history
Signed-off-by: Petr "Stone" Hracek <[email protected]>
  • Loading branch information
phracek committed Oct 2, 2024
1 parent 13a8d8a commit 9c0702e
Show file tree
Hide file tree
Showing 24 changed files with 621 additions and 23 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/python-automergere-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
on:
push:
branches:
- master
pull_request:
branches:
- master

name: Run Tox tests on ci-scripts/auto-merger
jobs:
tox_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: fedora-python/tox-github-action@main
with:
tox_env: py312
workdir: "auto-merger/"
...
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches:
- master

name: Run Tox tests on ci-scripts
name: Run Tox tests on ci-scripts/ocp-stream-generator
jobs:
tox_test:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ repos:
- id: trailing-whitespace
- id: flake8
args:
- --max-line-length=100
- --max-line-length=120
- --per-file-ignores=files/packit.wsgi:F401,E402
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.711
Expand Down
33 changes: 33 additions & 0 deletions auto-merger/auto-merger
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2016-2018 CWT Authors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Authors: Petr Hracek <[email protected]>

from auto_merger.merger import AutoMerger


if __name__ == "__main__":
auto_merger = AutoMerger()
auto_merger.check_all_containers()
40 changes: 40 additions & 0 deletions auto-merger/auto_merger/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env python3

# MIT License
#
# Copyright (c) 2024 Red Hat, Inc.

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

UPSTREAM_REPOS = [
"s2i-base-container",
"s2i-perl-container",
"s2i-nodejs-container",
"s2i-php-container",
"s2i-ruby-container",
"s2i-python-container",
"nginx-container",
"mysql-container",
"postgresql-container",
"mariadb-container",
"redis-container",
"valkey-container",
"varnish-container",
"httpd-container",
]
138 changes: 120 additions & 18 deletions auto-merger/auto_merger/merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,139 @@


import json
import sys
import subprocess
import os
import shutil

from typing import List

from pathlib import Path

from auto_merger import utils
from auto_merger.constants import UPSTREAM_REPOS
from auto_merger.utils import setup_logger


class AutoMerger:
repo_data: List = []
pr_to_merge: List[int] = []
container_name: str = ""
container_dir: Path
current_dir = os.getcwd()

def __init__(self, container_name: str):
self.container_name = container_name
def __init__(self):
self.logger = setup_logger("AutoMerger")

def get_gh_pr_list(self):
cmd = ["gh pr list -s open --json number,title,labels"]
def is_correct_repo(self) -> bool:
cmd = ["gh repo view --json name"]
repo_name = AutoMerger.get_gh_json_output(cmd=cmd)
self.logger.debug(repo_name)
if repo_name["name"] == self.container_name:
return True
return False

@staticmethod
def get_gh_json_output(cmd):
gh_repo_list = utils.run_command(cmd=cmd, return_output=True)
self.repo_data = json.loads(gh_repo_list)
return json.loads(gh_repo_list)

def get_gh_pr_list(self):
cmd = ["gh pr list -s open --json number,title,labels,reviews"]
self.repo_data = AutoMerger.get_gh_json_output(cmd=cmd)

def check_pr_labels(self, labels_to_check) -> bool:
self.logger.debug(f"Labels to check: {labels_to_check}")
if not labels_to_check:
return False
pr_failed_tags = ["pr/missing_review", "pr/failing-ci"]
pr_present = ["READY-to-MERGE"]
failed_pr = True
for label in labels_to_check:
if label["name"] in pr_failed_tags:
failed_pr = False
if label["name"] not in pr_present:
failed_pr = False
return failed_pr

def check_pr_approvals(self, reviews_to_check) -> bool:
self.logger.debug(f"Approvals to check: {reviews_to_check}")
if not reviews_to_check:
return False
approval = "APPROVED"
approval_cnt = 0
for review in reviews_to_check:
if review["state"] == approval:
approval_cnt += 1
if approval_cnt < 2:
self.logger.debug(f"Approval count: {approval_cnt}")
return False
return True

def check_pr_to_merge(self) -> bool:
if len(self.repo_data) == 0:
return False
pr_to_merge = []
for pr in self.repo_data:
self.logger.debug(f"PR status: {pr}")
if "labels" not in pr:
continue
if not self.check_pr_labels(pr["labels"]):
self.logger.info(
f"PR {pr['number']} does not have valid flag to merging in repo {self.container_name}."
)
continue
if not self.check_pr_approvals(pr["reviews"]):
self.logger.info(
f"PR {pr['number']} does not have enought APPROVALS to merging in repo {self.container_name}."
)
continue
pr_to_merge.append(pr["number"])
self.logger.debug(f"PR to merge {pr_to_merge}")
if not pr_to_merge:
return False
self.pr_to_merge = pr_to_merge
return True

def clone_repo(self):
pass
temp_dir = utils.temporary_dir()
utils.run_command(
f"gh repo clone https://github.com/sclorg/{self.container_name} {temp_dir}/{self.container_name}"
)
self.container_dir = Path(temp_dir) / f"{self.container_name}"
if self.container_dir.exists():
os.chdir(self.container_dir)

def merge_pull_requests(self):
for pr in self.pr_to_merge:
self.logger.debug(f"PR to merge {pr} in repo {self.container_name}.")

def clean_dirs(self):
os.chdir(self.current_dir)
if self.container_dir.exists():
shutil.rmtree(self.container_dir)

def merge_pull_request(self):
pass
def check_all_containers(self):
for container in UPSTREAM_REPOS:
self.pr_to_merge = []
self.container_name = container
self.clone_repo()
if not self.is_correct_repo():
self.logger.error(f"This is not correct repo {self.container_name}.")
self.clean_dirs()
continue
try:
self.get_gh_pr_list()
if self.check_pr_to_merge():
self.logger.info(
f"This pull request can be merged {self.pr_to_merge}"
)
# auto_merger.merge_pull_requests()
except subprocess.CalledProcessError:
self.clean_dirs()
self.logger.error(f"Something went wrong {self.container_name}.")
continue
self.clean_dirs()


if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: merger.py [container-name]")
print('OUTPUT DIR is ".", if not specified otherwise')
sys.exit(5)
auto_merger = AutoMerger(container_name=sys.argv[1])
auto_merger.get_gh_pr_list()
auto_merger.merge_pull_request()
def run():
auto_merger = AutoMerger()
auto_merger.check_all_containers()
37 changes: 35 additions & 2 deletions auto-merger/auto_merger/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

import subprocess
import logging
import tempfile
import sys

logger = logging.getLogger(__name__)

Expand All @@ -44,8 +46,7 @@ def run_command(
:param debug: bool, print command in shell, default is suppressed
:return: None or str
"""
if debug:
logger.debug(f"command: {cmd}")
logger.debug(f"command: {cmd}")
try:
if return_output:
return subprocess.check_output(
Expand All @@ -66,3 +67,35 @@ def run_command(
else:
logger.error(f"failed with code {cpe.returncode} and output:\n{cpe.output}")
raise cpe


def temporary_dir(prefix: str = "automerger") -> str:
temp_file = tempfile.TemporaryDirectory(prefix=prefix)
logger.debug(f"AutoMerger: Temporary dir name: {temp_file.name}")
return temp_file.name


def setup_logger(logger_id, level=logging.DEBUG):
logger = logging.getLogger(logger_id)
logger.setLevel(level)
format_str = "%(name)s - %(levelname)s: %(message)s"
# Debug handler
debug = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(format_str)
debug.setLevel(logging.DEBUG)
debug.addFilter(lambda r: True if r.levelno == logging.DEBUG else False)
debug.setFormatter(formatter)
logger.addHandler(debug)
# Info handler
info = logging.StreamHandler(sys.stdout)
info.setLevel(logging.DEBUG)
info.addFilter(lambda r: True if r.levelno == logging.INFO else False)
logger.addHandler(info)
# Warning, error, critical handler
stderr = logging.StreamHandler(sys.stderr)
formatter = logging.Formatter(format_str)
stderr.setLevel(logging.WARN)
stderr.addFilter(lambda r: True if r.levelno >= logging.WARN else False)
stderr.setFormatter(formatter)
logger.addHandler(stderr)
return logger
3 changes: 3 additions & 0 deletions auto-merger/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest
PyYAML
flexmock
68 changes: 68 additions & 0 deletions auto-merger/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2016-2018 CWT Authors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Authors: Petr Hracek <[email protected]>

from setuptools import setup, find_packages
from pathlib import Path

this_directory = Path(__file__).parent
long_description = (
"Tool for merging sclorg pull request that meets criteria"
) # (this_directory / "README.md").read_text()


def get_requirements():
"""Parse all packages mentioned in the 'requirements.txt' file."""
with open("requirements.txt") as file_stream:
return file_stream.read().splitlines()


setup(
name="auto-merger",
description="Tool for merging sclorg pull request that meets criteria.",
long_description=long_description,
long_description_content_type="text/markdown",
version="0.1.0",
keywords="tool,containers,images,tests",
packages=find_packages(exclude=["tests"]),
url="https://github.com/sclorg/ci-scripts",
license="MIT",
author="Petr Hracek",
author_email="[email protected]",
install_requires=get_requirements(),
scripts=[],
entry_points={"console_scripts": ["auto-merger = auto_merger.merger:run"]},
setup_requires=[],
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python",
"Topic :: Software Development",
],
)
Empty file added auto-merger/tests/__init__.py
Empty file.
Loading

0 comments on commit 9c0702e

Please sign in to comment.