Skip to content

Commit

Permalink
Refactor GitHub Actions and Docker setup 🛠️
Browse files Browse the repository at this point in the history
Significant changes have been made to the GitHub Actions workflow and Docker setup. The Docker build process has been streamlined and now uses a requirements.txt file for Python dependencies. A new Pytest job has been added to the GitHub Actions workflow for running tests. The aicodebot_action.py script has been refactored for better readability and maintainability. New test files and requirements files have also been added.
  • Loading branch information
TechNickAI committed Jul 16, 2023
1 parent cc79c87 commit 68741b9
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 123 deletions.
42 changes: 29 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,34 @@ name: Build
on: [push, pull_request]

jobs:
docker-build:
name: Docker build
runs-on: ubuntu-latest
# Enable Buildkit and let compose use it to speed up image building
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
timeout-minutes: 5
docker-build:
name: Docker build
runs-on: ubuntu-latest
# Enable Buildkit and let compose use it to speed up image building
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
timeout-minutes: 5

steps:
- name: Checkout Code
uses: actions/checkout@v3
steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Build the Docker images
run: docker build -t aicodebot .
- name: Build the Docker images
run: docker build -t aicodebot .

pytest:
name: Run Pytest
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
cache: pip

- name: Install dependencies
run: pip install -r requirements.txt -r requirements-test.txt
8 changes: 3 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
FROM python:3.11-slim

RUN apt-get update && apt-get install -y git jq
RUN apt-get update && apt-get install -y git

RUN pip install aicodebot
# Additional dependencies for aicodebot_action
RUN pip install PyGithub
COPY requirements.txt /requirements.txt
RUN pip install -r requirements.txt

COPY aicodebot_action.py /aicodebot_action.py

RUN chmod +x /aicodebot_action.py

ENTRYPOINT ["/aicodebot_action.py"]
Expand Down
238 changes: 133 additions & 105 deletions aicodebot_action.py
Original file line number Diff line number Diff line change
@@ -1,114 +1,142 @@
#!/usr/bin/env python3
from aicodebot.cli import cli
from aicodebot.config import get_config_file
from aicodebot.helpers import logger
from click.testing import CliRunner
from github import Github
from pathlib import Path
import json, os, subprocess, sys

# ---------------------------------------------------------------------------- #
# Set up #
# ---------------------------------------------------------------------------- #

# Check if required inputs are set
openai_api_key = os.getenv("INPUT_OPENAI_API_KEY") # Note this is prefixed with INPUT_ through actions
if not openai_api_key:
print("🛑 The OpenAI API Key is not set. This key is REQUIRED for the AICodeBot.")
print("You can get one for free at https://platform.openai.com/account/api-keys")
print()
print("Please set it as a repository secret named 'OPENAI_API_KEY'.")
print("For more information on how to set up repository secrets, visit:")
print("https://docs.github.com/en/actions/security-guides/encrypted-secrets")
sys.exit(1)

# Set the OPENAI_API_KEY environment variable
os.environ["OPENAI_API_KEY"] = openai_api_key

# Set up the personality, defaulting to "Her"
os.environ["AICODEBOT_PERSONALITY"] = os.getenv("INPUT_AICODEBOT_PERSONALITY", "Her")

# Set up the git configuration. Allow the user to override the safe directory
subprocess.run(["git", "config", "--global", "--add", "safe.directory", "/github/workspace"])

# Test the CLI
cli_runner = CliRunner()
result = cli_runner.invoke(cli, ["-V"])
print("AICodeBot version:", result.output)
assert result.exit_code == 0

# ---------------------------------------------------------------------------- #
# Run the code review #
# ---------------------------------------------------------------------------- #

# Set up the aicodebot configuration from the OPENAI_API_KEY
result = cli_runner.invoke(cli, ["configure", "--openai-api-key", openai_api_key])
print(f"Configure: {result.output}")
assert result.exit_code == 0
assert Path(get_config_file()).exists()

# Run a code review on the current commit
result = cli_runner.invoke(cli, ["review", "-c", os.getenv("GITHUB_SHA"), "--output-format", "json"])
print("Review:", result.output)
assert result.exit_code == 0

review_output = json.loads(result.output)
review_status = review_output["review_status"]
review_comments = review_output["review_comments"]

# Magic to set the output variables for github workflows
with open(os.getenv("GITHUB_STATE"), "a") as f: # noqa: PTH123
f.write(f"{review_status}={review_status}\n")

# ---------------------------------------------------------------------------- #
# Set up the github client #
# ---------------------------------------------------------------------------- #

github_token = os.getenv("INPUT_GITHUB_TOKEN") # Note this is prefixed with INPUT_ through actions
assert github_token, "🛑 The GITHUB_TOKEN is not set. This key is REQUIRED for the AICodeBot."

g = Github(github_token)

# Get the repository
repo = g.get_repo(os.getenv("GITHUB_REPOSITORY"))
print(f"Repo: {repo}")
print(f"Repository name: {repo.name}")
print(f"Repository owner: {repo.owner.login}")
print(f"Repository pushed at: {repo.pushed_at}")

# Get the commit
commit = repo.get_commit(os.getenv("GITHUB_SHA"))
print(f"Commit: {commit}")
print(f"Commit message: {commit.commit.message}")
print(f"Commit author: {commit.commit.author.name}")
print(f"Commit date: {commit.commit.author.date}")

# ---------------------------------------------------------------------------- #
# Leave a comment on the commit #
# ---------------------------------------------------------------------------- #

# First leave a comment on the commit
if review_comments:
print(f"Review: {review_comments}")

comment = (
"🤖 AICodeBot Review Comments:\n\n"
+ review_comments
+ "\n\nCode review automatically created with [AICodeBot](https://github.com/gorillamania/AICodeBot)"
)

# Then add a reaction to the comment
if review_status == "PASSED":
if os.getenv("INPUT_COMMENT_ON_PASSED"):

def main(comment_on_commit=True):
"""Run the AICodeBot action"""
cli_runner = setup_cli()
review_status, review_comments = review_code(cli_runner)
if review_status == "FAILED":
exit_status = 1
else:
exit_status = 0

if comment_on_commit:
comment_on_commit(review_status, review_comments)
sys.exit(exit_status)


def setup_cli():
"""Set up and configure the AICodeBot CLI"""

# Check if required inputs are set
openai_api_key = os.getenv("INPUT_OPENAI_API_KEY") # Note this is prefixed with INPUT_ through actions
if not openai_api_key:
print(
"""
🛑 The OPENAI_API_KEY is not set. This key is required for the AICodeBot.
You can get one for free at https://platform.openai.com/account/api-keys
Please set it as a repository secret named 'OPENAI_API_KEY'.
For more information on how to set up repository secrets, visit:
https://docs.github.com/en/actions/security-guides/encrypted-secrets
"""
)
sys.exit(1)

# Set the OPENAI_API_KEY environment variable
os.environ["OPENAI_API_KEY"] = openai_api_key

# Set up the personality, defaulting to "Her"
os.environ["AICODEBOT_PERSONALITY"] = os.getenv("INPUT_AICODEBOT_PERSONALITY", "Her")

# Set up the git configuration. Allow the user to override the safe directory
subprocess.run(["git", "config", "--global", "--add", "safe.directory", "/github/workspace"])

cli_runner = CliRunner()
result = cli_runner.invoke(cli, ["-V"])
logger.info("AICodeBot version:", result.output)
assert result.exit_code == 0, f"🛑 Error running AICodeBot version: {result.output}"

# Set up the aicodebot configuration from the OPENAI_API_KEY
logger.debug(f"Configuring AICodeBot to write config file to {os.getenv('AICODEBOT_CONFIG_FILE')}")
result = cli_runner.invoke(cli, ["configure", "--openai-api-key", os.getenv("OPENAI_API_KEY")])
logger.debug("Configure:", result.output)
assert result.exit_code == 0, f"🛑 Error running AICodeBot configure: {result.output}"
config_file = get_config_file()
assert Path(config_file).exists(), f"🛑 Failed to create the AICodeBot configuration file: {config_file}"

return cli_runner


def review_code(cli_runner):
"""Run a code review on the current commit"""

result = cli_runner.invoke(cli, ["review", "-c", os.getenv("GITHUB_SHA"), "--output-format", "json"])
logger.debug("Review:", result.output)
assert result.exit_code == 0, f"🛑 Error running AICodeBot review {result.output}, {result.exc_info}"

review_output = json.loads(result.output)
review_status = review_output["review_status"]
review_comments = review_output["review_comments"]

# Magic to set the output variables for github workflows
with open(os.getenv("GITHUB_STATE"), "a") as f: # noqa: PTH123
f.write(f"{review_status}={review_status}\n")

return review_status, review_comments


def comment_on_commit(review_status, review_comments):
"""Comment on the commit with the results of the code review"""

# ---------------------------------------------------------------------------- #
# Set up the github client #
# ---------------------------------------------------------------------------- #

github_token = os.getenv("INPUT_GITHUB_TOKEN") # Note this is prefixed with INPUT_ through actions
assert github_token, "🛑 The GITHUB_TOKEN is not set. This key is REQUIRED for the AICodeBot."

g = Github(github_token)

# Get the repository
repo = g.get_repo(os.getenv("GITHUB_REPOSITORY"))
logger.info(f"Repository: {repo}, name: {repo.name}, owner: {repo.owner.login}")

# Get the commit
commit = repo.get_commit(os.getenv("GITHUB_SHA"))
logger.info(f"Commit: {commit}")
logger.info(f"Commit author: {commit.commit.author.name}")
logger.info(f"Commit message: {commit.commit.message}")
logger.debug(f"Commit date: {commit.commit.author.date}")

# ---------------------------------------------------------------------------- #
# Comment on the commit #
# ---------------------------------------------------------------------------- #

# First leave a comment on the commit
if review_comments:
logger.info("Review comments:", review_comments)

comment = (
"🤖 AICodeBot Review Comments:\n\n"
+ review_comments
+ "\n\nCode review automatically created with [AICodeBot](https://github.com/gorillamania/AICodeBot)"
)

# Then add a reaction to the comment
if review_status == "PASSED":
if os.getenv("INPUT_COMMENT_ON_PASSED"):
commit_comment = commit.create_comment(comment)
commit_comment.create_reaction("heart")
logger.success("Code review passed!")
elif review_status == "FAILED":
commit_comment = commit.create_comment(comment)
commit_comment.create_reaction("heart")
print("✅️ Code review passed!")
elif review_status == "FAILED":
commit_comment = commit.create_comment(comment)
commit_comment.create_reaction("-1")
print("👎 Code review failed!")
sys.exit(1)
elif review_status == "COMMENTS":
commit_comment = commit.create_comment(comment)
commit_comment.create_reaction("eyes")
print("👍 Code review has comments, take a look")
sys.exit(0)
commit_comment.create_reaction("-1")
logger.error("👎 Code review failed!")
elif review_status == "COMMENTS":
commit_comment = commit.create_comment(comment)
commit_comment.create_reaction("eyes")
logger.info("👍 Code review has comments, take a look")
else:
logger.error(f"🛑 Unknown review status: {review_status}")


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pytest
requests_mock
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
aicodebot>=0.11.1
PyGithub
Empty file added tests/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions tests/test_review.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import aicodebot_action, os, pytest, subprocess


@pytest.mark.skipif(not os.getenv("INPUT_OPENAI_API_KEY"), reason="skipping live tests without an api key.")
def test_commit_review(tmp_path):
# Simulate the github environment variables
os.environ["GITHUB_REPOSITORY"] = (
subprocess.check_output(["git", "config", "--get", "remote.origin.url"]).decode().strip()
)
os.environ["GITHUB_SHA"] = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode().strip()
os.environ["GITHUB_STATE"] = str(tmp_path / "GITHUB_STATE")
os.environ["INPUT_GITHUB_TOKEN"] = "test token"

# Override the config file to a tmp path
os.environ["AICODEBOT_CONFIG_FILE"] = str(tmp_path / "aicodebot.yaml")
assert "aicodebot.yaml" in os.getenv("AICODEBOT_CONFIG_FILE")

# TODO: Mock the github client so we can test the comment on commit as well
aicodebot_action.main(comment_on_commit=False)

0 comments on commit 68741b9

Please sign in to comment.