Skip to content

Commit

Permalink
Add GitHubClient methods for cloning and committing repository conten…
Browse files Browse the repository at this point in the history
…ts; implement integration tests for Lambda handler
  • Loading branch information
Dave Arnold committed Apr 23, 2025
1 parent 2b9b098 commit 3a0f957
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 0 deletions.
194 changes: 194 additions & 0 deletions eks_automation/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,149 @@ def update_repository_topics(self, repo_name, topics):
error_message = f"Failed to update topics for {repo_name}: {response.status_code} - {response.text}"
logger.error(error_message)
raise Exception(error_message)

def clone_repository_contents(self, source_repo, target_dir, branch=None):
"""Clone a repository's contents to a local directory using GitHub API
Args:
source_repo (str): Name of the source repository
target_dir (str): Target directory to download files to
branch (str, optional): Branch to clone from. If None, uses default branch.
Returns:
str: The branch name that was cloned
"""
# Create the target directory if it doesn't exist
os.makedirs(target_dir, exist_ok=True)

try:
if branch:
target_branch = branch
# Try to get the branch's reference directly
tree_sha = self.get_reference_sha(source_repo, f"heads/{target_branch}")
else:
# If no branch specified, use default branch
target_branch = self.get_default_branch(source_repo)
tree_sha = self.get_reference_sha(source_repo, f"heads/{target_branch}")
except Exception as e:
logger.warning(f"Failed to get reference for {branch or 'default branch'}: {str(e)}")
target_branch = branch or "main"
# If we can't get the reference, the branch might not exist yet
tree = {"tree": []}
self.download_repository_files(source_repo, tree, target_dir)
return target_branch

# Get the full tree for the branch
logger.info(f"Getting file tree from {source_repo} for branch {target_branch}")
tree = self.get_tree(source_repo, tree_sha, recursive=True)

# Download all files
logger.info(f"Downloading all files from {source_repo} using ref: heads/{target_branch}")
self.download_repository_files(source_repo, tree, target_dir)

return target_branch

def commit_repository_contents(self, repo_name, work_dir, commit_message, branch=None):
"""Commit all files from a directory to a repository
Args:
repo_name (str): Name of the repository
work_dir (str): Directory containing the files to commit
commit_message (str): Commit message
branch (str, optional): Branch to commit to. If None, uses default branch.
Returns:
str: The branch name that was committed to
"""
# First, get the current state of the target repository
try:
target_branch = branch or self.get_default_branch(repo_name)
except Exception:
# If we can't get the default branch, it might be a new repo
target_branch = branch or "main"

# Upload all files to the repository
tree_items = []

# Add all files from the work directory to the repository
for root, _, files in os.walk(work_dir):
for file in files:
file_path = os.path.join(root, file)
repo_path = os.path.relpath(file_path, work_dir)

# Skip .git directory if it exists
if ".git" in repo_path.split(os.path.sep):
continue

# Read file content
with open(file_path, "rb") as f:
file_content = f.read()

# Create blob for the file
blob_sha = self.create_blob(repo_name, file_content)

# Add to tree items
tree_items.append({
"path": repo_path,
"mode": "100644", # Regular file
"type": "blob",
"sha": blob_sha
})

# Try to get the latest commit SHA from the base branch
base_branch = "main" # Always use main as base when creating new branches
try:
base_commit_sha = self.get_reference_sha(repo_name, f"heads/{base_branch}")
base_commit = self.get_commit(repo_name, base_commit_sha)
base_tree_sha = base_commit["tree"]["sha"]
except Exception:
# If we can't get the reference, assume it's a new repo with no commits
base_tree_sha = None

# Create a new tree with all the files
new_tree_sha = self.create_tree(repo_name, tree_items, base_tree_sha)

# Create a commit with the new tree
if base_tree_sha:
# If we have a base tree, include the parent commit
new_commit_sha = self.create_commit(
repo_name,
commit_message,
new_tree_sha,
[base_commit_sha]
)
else:
# If it's a new repo, create the first commit
new_commit_sha = self.create_commit(
repo_name,
commit_message,
new_tree_sha,
[]
)

# Update or create the reference to point to the new commit
try:
# Try to update existing branch
self.update_reference(
repo_name,
f"heads/{target_branch}",
new_commit_sha
)
except Exception:
# If the branch doesn't exist, create it
try:
self.create_reference(
repo_name,
f"refs/heads/{target_branch}",
new_commit_sha
)
except Exception as e:
# If we still can't create the branch, something is wrong
error_message = f"Failed to create or update branch {target_branch} for {repo_name}: {str(e)}"
logger.error(error_message)
raise Exception(error_message)

return target_branch

def operate_github(new_repo_name, eks_settings):
"""Write EKS settings to config.json and create/update repository using GitHub API
Expand Down Expand Up @@ -546,3 +689,54 @@ def remove_readonly(func, path, _):
"""
os.chmod(path, stat.S_IWRITE)
func(path)

# pylint: disable=unused-argument
def lambda_handler(event, context):
"""Main Lambda handler function
Args:
event (dict): Dict containing the Lambda function event data
context (dict): Lambda runtime context
Returns:
dict: Dict containing status message
"""
logger.info(f"Lambda function invoked with RequestId: {context.aws_request_id}")
logger.info(f"Remaining time in milliseconds: {context.get_remaining_time_in_millis()}")
logger.info(f"Received event: {json.dumps(event, indent=2)}")

input_data = event.get("body")
if isinstance(input_data, str):
input_data = json.loads(input_data)
logger.info(f"Extracted input data from event body: {json.dumps(input_data, indent=2)}")

project_name = input_data.get("project_name")
eks_settings = input_data.get("eks_settings")
logger.info(f"Project name: {project_name}")
logger.info(f"EKS settings to be applied: {json.dumps(eks_settings, indent=2)}")

if not project_name:
logger.error("Missing project name in input")
return {
"statusCode": 400,
"body": json.dumps({"error": "Missing project name"})
}

try:
logger.info(f"Starting GitHub operations for project: {project_name}")
operate_github(project_name, eks_settings)
logger.info("GitHub operations completed successfully")
except Exception as e: # pylint: disable=broad-exception-caught
logger.error(f"Error in operate_github: {str(e)}")
logger.error(f"Stack trace: {traceback.format_exc()}")
return {
"statusCode": 400,
"body": json.dumps({"error": str(e)})
}

logger.info("Lambda execution completed successfully")
return {
"statusCode": 200,
"headers": {"Access-Control-Allow-Origin": "*"},
"body": json.dumps({"result": "Success"})
}
62 changes: 62 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import os
import json
import pytest
import uuid
from eks_automation.app import lambda_handler

# Test environment variables
os.environ["SECRET_NAME"] = "github-token" # Uses AWS Secrets Manager
os.environ["GITHUB_API"] = "https://api.github.com"
os.environ["GITHUB_ORG_NAME"] = "your-org-name" # Replace with test org
os.environ["TEMPLATE_REPO_NAME"] = "template-eks-cluster"
os.environ["TEMPLATE_SOURCE_VERSION"] = "main" # Or specific tag/SHA for testing

@pytest.fixture
def test_event():
"""Create test event with unique repository name"""
repo_name = f"test-eks-cluster-{uuid.uuid4().hex[:8]}"
return {
"body": {
"project_name": repo_name,
"eks_settings": {
"cluster_name": "test-cluster",
"kubernetes_version": "1.27",
"region": "us-west-2",
"vpc_config": {
"vpc_id": "vpc-test123",
"subnet_ids": ["subnet-test1", "subnet-test2"]
},
"nodegroups": [{
"name": "test-ng",
"instance_types": ["t3.medium"],
"desired_size": 2,
"min_size": 1,
"max_size": 3
}]
}
}
}

@pytest.fixture
def lambda_context():
"""Mock Lambda context object"""
class MockContext:
def __init__(self):
self.aws_request_id = "test-request-id"
def get_remaining_time_in_millis(self):
return 30000
return MockContext()

def test_lambda_handler_creates_repository(test_event, lambda_context):
"""Test that Lambda handler creates repository with correct settings"""
# Execute Lambda handler
response = lambda_handler(test_event, lambda_context)

assert response["statusCode"] == 200
assert "Success" in response["body"]

# Additional assertions could verify:
# - Repository was created in GitHub
# - Config file contains correct settings
# - Topics were set correctly
# But these require GitHub API access

0 comments on commit 3a0f957

Please sign in to comment.