Skip to content

Commit

Permalink
fix: public visibility by default; add collaborator support for repo …
Browse files Browse the repository at this point in the history
…creator

- repository_provider.py: change REPO_VISIBILITY default from 'internal' to
  'public' so created repos are visible to all org members and external
  visitors on GHE
- github_provider.py: change fallback visibility from 'internal' to 'public';
  add add_collaborator() method (PUT /repos/{org}/{repo}/collaborators/{user})
- app.py: add optional creator_username field to CloudFormationResourceInput;
  after team permission grant, also add individual creator as admin collaborator
  when creator_username is provided (non-fatal on failure)
- service-catalog/product-template.yaml: add optional CreatorUsername parameter
  (defaults to empty string, backward-compatible); wire to creator_username
  Lambda property
  • Loading branch information
Your Name committed Apr 2, 2026
1 parent 803168a commit 0a74dd7
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 7 deletions.
11 changes: 11 additions & 0 deletions service-catalog/product-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Metadata:
- Label:
default: "Optional Metadata"
Parameters:
- CreatorUsername
- AdditionalTags

ParameterLabels:
Expand Down Expand Up @@ -67,6 +68,8 @@ Metadata:
default: "FinOps Project Name"
FinOpsProjectNumber:
default: "FinOps Project Number"
CreatorUsername:
default: "Your GitHub Username (for admin access)"
AdditionalTags:
default: "Additional Tags (JSON)"

Expand Down Expand Up @@ -161,6 +164,13 @@ Parameters:
Description: FinOps project number
Default: ""

CreatorUsername:
Type: String
Description: >-
Your GitHub Enterprise username. If provided, you will be granted admin
access to the created repository in addition to the owning team.
Default: ""

AdditionalTags:
Type: String
Description: 'Additional tags as JSON object (e.g., {"key1":"value1"})'
Expand Down Expand Up @@ -201,6 +211,7 @@ Resources:
organization_path: !Ref OrganizationPath
finops_project_name: !Ref FinOpsProjectName
finops_project_number: !Ref FinOpsProjectNumber
creator_username: !Ref CreatorUsername
tags: !Ref AdditionalTags

Outputs:
Expand Down
15 changes: 15 additions & 0 deletions template_automation/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class CloudFormationResourceInput(BaseModel):
"""Input validation model for CloudFormation Custom Resource parameters."""
project_name: str = Field(..., description="Name for the new repository")
owning_team: Optional[str] = Field(default="tf-module-admins", description="Team that should own the repository")
creator_username: Optional[str] = Field(default=None, description="GitHub username of the person provisioning this repo; will be granted admin access")

# EKS-specific fields (present when this is an EKS cluster deployment)
cluster_name: Optional[str] = Field(default=None, description="EKS cluster name")
Expand Down Expand Up @@ -606,6 +607,20 @@ def lambda_handler(event: dict, context) -> dict:
logger.info(f"[{request_id}] Continuing despite team permission error")
else:
logger.info(f"[{request_id}] Skipping team assignment (no owning_team or not GitHub provider)")

# Add the individual creator as admin collaborator (GitHub only)
if cfn_input.creator_username and provider_type == "GitHubProvider":
logger.info(f"[{request_id}] Adding creator '{cfn_input.creator_username}' as admin collaborator")
try:
provider.add_collaborator(cfn_input.project_name, cfn_input.creator_username, permission="admin")
logger.info(f"[{request_id}] Creator '{cfn_input.creator_username}' added as admin collaborator")
except Exception as e:
logger.error(f"[{request_id}] Failed to add creator as collaborator: {str(e)}")
logger.error(f"[{request_id}] Full traceback: {traceback.format_exc()}")
# Non-fatal: repo and team perms are already set
logger.info(f"[{request_id}] Continuing despite creator collaborator error")
else:
logger.info(f"[{request_id}] Skipping creator collaborator (no creator_username provided or not GitHub provider)")

# Give newly created repositories a moment to initialize
if project.get('created_at'):
Expand Down
45 changes: 43 additions & 2 deletions template_automation/github_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,10 @@ def get_repository(
settings = RepositorySettings()

# Set up repository creation data.
# NOTE: 'private' must be False for 'internal' visibility; GHE treats
# NOTE: 'private' must be False for 'internal'/'public' visibility; GHE treats
# private=True as a private-repo request and will 403 if the enterprise
# policy blocks private repo creation for org members.
effective_visibility = settings.visibility or 'internal'
effective_visibility = settings.visibility or 'public'
create_data = {
'name': name,
'private': effective_visibility == 'private',
Expand Down Expand Up @@ -678,6 +678,47 @@ def get_repository_url(self, repo_name: str) -> str:
web_base = web_base[:-len('/api/v3')]
return f"{web_base}/{self.organization}/{repo_name}"

def add_collaborator(
self,
repo_name: str,
username: str,
permission: str = "admin"
) -> Dict[str, Any]:
"""Add an individual user as a collaborator on a repository.
Args:
repo_name: Repository name
username: GitHub username to add
permission: Permission level (pull, push, maintain, triage, admin)
Returns:
Response data (empty dict if invite was accepted immediately)
"""
logger.info(f"Adding user '{username}' as {permission} collaborator on repository {repo_name}")
try:
result = self._request(
'PUT',
f'/repos/{self.organization}/{repo_name}/collaborators/{username}',
json={'permission': permission}
)
logger.info(f"User '{username}' added as {permission} collaborator on {repo_name}")
return result or {}
except requests.exceptions.RequestException as e:
logger.error(f"Failed to add collaborator '{username}': {str(e)}")
if hasattr(e, 'response') and e.response is not None:
status_code = e.response.status_code
if status_code == 404:
logger.error(f"User '{username}' not found on GitHub Enterprise")
elif status_code == 403:
logger.error("Insufficient permissions to add collaborator")
else:
logger.error(f"Error adding collaborator: status code {status_code}")
try:
logger.error(f"Error details: {json.dumps(e.response.json())}")
except Exception:
logger.error(f"Error response: {e.response.text}")
return {}

def set_team_permission(
self,
repo_name: str,
Expand Down
11 changes: 6 additions & 5 deletions template_automation/repository_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@


def _default_visibility() -> str:
"""Read REPO_VISIBILITY env var, defaulting to 'internal'.
"""Read REPO_VISIBILITY env var, defaulting to 'public'.
GHE enterprise policy commonly blocks private repo creation for org members.
'internal' repos are visible to all org members but not to the public,
which is the recommended default for government/enterprise GHE instances.
Repos created by the EKS automation are public within the GitHub Enterprise
organization so all team members can clone and browse them without extra
permission grants. Set REPO_VISIBILITY=internal or REPO_VISIBILITY=private
to override if needed.
Accepted values: private | internal | public
"""
return os.environ.get("REPO_VISIBILITY", "internal")
return os.environ.get("REPO_VISIBILITY", "public")


class RepositorySettings(BaseModel):
Expand Down

0 comments on commit 0a74dd7

Please sign in to comment.