Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,20 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: Resolve team from GitHub
id: team
env:
GH_TOKEN: ${{ github.token }}
run: |
TEAM=$(gh api "/repos/${{ github.repository }}/teams" \
--jq '[.[] | select(.slug != "platform-owners")] | .[0].slug // empty' 2>/dev/null || true)
if [ -z "$TEAM" ]; then echo "ERROR: repo not in a team"; exit 1; fi
echo "team=$TEAM" >> "$GITHUB_OUTPUT"

- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/javabin-ci-deploy-${{ github.event.repository.name }}
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/javabin-ci-deploy-${{ steps.team.outputs.team }}
aws-region: ${{ inputs.aws_region }}

- name: Login to ECR
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/ecs-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,20 @@ jobs:
sparse-checkout: scripts
path: .platform

- name: Resolve team from GitHub
id: team
env:
GH_TOKEN: ${{ github.token }}
run: |
TEAM=$(gh api "/repos/${{ github.repository }}/teams" \
--jq '[.[] | select(.slug != "platform-owners")] | .[0].slug // empty' 2>/dev/null || true)
if [ -z "$TEAM" ]; then echo "ERROR: repo not in a team"; exit 1; fi
echo "team=$TEAM" >> "$GITHUB_OUTPUT"

- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/javabin-ci-deploy-${{ github.event.repository.name }}
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/javabin-ci-deploy-${{ steps.team.outputs.team }}
aws-region: ${{ inputs.aws_region }}

- name: Deploy to ECS
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/platform-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ jobs:
aws-region: ${{ env.AWS_REGION }}
role-session-name: javabin-platform-plan-${{ github.run_id }}

- name: Sync registered repos from GitHub teams
- name: Sync registered teams from GitHub org
if: steps.changes.outputs.has_infra_changes == 'true' && github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ github.token }}
run: python3 scripts/sync-registered-repos.py "${{ env.TF_ROOT }}/registered-apps.auto.tfvars" || echo "Sync skipped — will use existing tfvars"
run: python3 scripts/sync-registered-teams.py "${{ env.TF_ROOT }}/registered-teams.auto.tfvars" || echo "Sync skipped — will use existing tfvars"

- name: Terraform Init
if: steps.changes.outputs.has_infra_changes == 'true'
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/tf-plan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,25 @@ jobs:
terraform_version: "1.7"
terraform_wrapper: false

- name: Resolve team from GitHub
id: team
env:
GH_TOKEN: ${{ github.token }}
run: |
TEAM=$(gh api "/repos/${{ github.repository }}/teams" \
--jq '[.[] | select(.slug != "platform-owners")] | .[0].slug // empty' 2>/dev/null || true)
if [ -z "$TEAM" ]; then
echo "ERROR: repo does not belong to any GitHub team"
echo "Add it at https://github.com/orgs/javaBin/teams"
exit 1
fi
echo "Resolved team: $TEAM"
echo "team=$TEAM" >> "$GITHUB_OUTPUT"

- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/javabin-ci-app-${{ github.event.repository.name }}
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/javabin-ci-team-${{ steps.team.outputs.team }}
aws-region: ${{ inputs.aws_region }}

- name: Generate GitHub App token
Expand Down
26 changes: 26 additions & 0 deletions scripts/resolve-team.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
# Resolve which team a repo belongs to via GitHub API.
#
# Usage: resolve-team.sh
#
# Env: GITHUB_REPOSITORY (org/repo), GH_TOKEN or GITHUB_TOKEN
# Output: team={slug} written to GITHUB_OUTPUT
#
# Queries /repos/{owner}/{repo}/teams and picks the first non-platform team.
# Fails if the repo doesn't belong to any team (no IAM role to assume).

set -e

REPO="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY required}"

TEAM=$(gh api "/repos/${REPO}/teams" \
--jq '[.[] | select(.slug != "platform-owners")] | .[0].slug // empty' 2>/dev/null || true)

if [ -z "$TEAM" ]; then
echo "ERROR: repo ${REPO} does not belong to any GitHub team."
echo "Add the repo to your team at https://github.com/orgs/javaBin/teams"
exit 1
fi

echo "Resolved team: ${TEAM}"
echo "team=${TEAM}" >> "${GITHUB_OUTPUT:-/dev/null}"
125 changes: 0 additions & 125 deletions scripts/sync-registered-repos.py

This file was deleted.

104 changes: 104 additions & 0 deletions scripts/sync-registered-teams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Sync registered teams from GitHub org teams.

Queries the GitHub API for all teams in the javaBin org and writes
registered-teams.auto.tfvars. This drives team-scoped IAM role creation.

Source of truth: GitHub teams (created by team-provisioner from registry).

Usage: sync-registered-teams.py <tfvars-output-path>

Requires: GITHUB_TOKEN env var (with org team read access).
"""

import json
import logging
import os
import sys
import urllib.request

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format="%(message)s")

GITHUB_ORG = os.environ.get("GITHUB_ORG", "javaBin")
GITHUB_API = "https://api.github.com"

# Platform-internal teams that don't get app CI roles
EXCLUDED_TEAMS = {"platform-owners"}


def github_get(path, token):
"""Paginated GitHub API GET."""
results = []
url = f"{GITHUB_API}{path}"
while url:
req = urllib.request.Request(url)
req.add_header("Authorization", f"token {token}")
req.add_header("Accept", "application/vnd.github+json")
with urllib.request.urlopen(req) as resp:
results.extend(json.loads(resp.read()))
link = resp.headers.get("Link", "")
url = None
for part in link.split(","):
if 'rel="next"' in part:
url = part.split("<")[1].split(">")[0]
return results


def get_teams(token):
"""Get all org teams, excluding platform-internal ones."""
teams = github_get(f"/orgs/{GITHUB_ORG}/teams", token)
slugs = sorted(t["slug"] for t in teams if t["slug"] not in EXCLUDED_TEAMS)
logger.info("Found %d teams (excluding %s)", len(slugs), ", ".join(EXCLUDED_TEAMS))
for slug in slugs:
logger.info(" %s", slug)
return slugs


def write_tfvars(teams, output_path):
"""Write the registered-teams.auto.tfvars file."""
lines = [
"# GENERATED by sync-registered-teams.py — do not edit manually",
"# Source of truth: GitHub teams in javaBin org",
"",
"registered_teams = [",
]
for team in teams:
lines.append(f' "{team}",')
lines.append("]")
lines.append("")

content = "\n".join(lines)

existing = ""
if os.path.exists(output_path):
with open(output_path) as f:
existing = f.read()

if content == existing:
logger.info("No changes to registered teams")
return False

with open(output_path, "w") as f:
f.write(content)
logger.info("Wrote %d teams to %s", len(teams), output_path)
return True


def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <tfvars-output-path>", file=sys.stderr)
sys.exit(1)

output_path = sys.argv[1]
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if not token:
print("GITHUB_TOKEN or GH_TOKEN required", file=sys.stderr)
sys.exit(1)

teams = get_teams(token)
write_tfvars(teams, output_path)


if __name__ == "__main__":
main()
Loading