Skip to content

Chore: [AEA-0000] - new script to setup new repo#94

Open
anthony-nhs wants to merge 12 commits intomainfrom
script_to_set_repos
Open

Chore: [AEA-0000] - new script to setup new repo#94
anthony-nhs wants to merge 12 commits intomainfrom
script_to_set_repos

Conversation

@anthony-nhs
Copy link
Copy Markdown
Collaborator

Summary

  • Routine Change

Details

  • new script to setup repo

Copilot AI review requested due to automatic review settings April 14, 2026 16:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new setup_github_repo package and a wrapper script intended to automate GitHub repository setup (repo settings, team access, environments, and secrets), with supporting unit tests and coverage configuration.

Changes:

  • Add packages/setup_github_repo application code for repo setup orchestration (GitHub + AWS exports + CLI bootstrap).
  • Add a scripts/setup_github_repos.py wrapper entrypoint and update repo config/ignores to support local secret files.
  • Add boto3 dependency plus a new test suite and per-package pytest/coverage config.

Reviewed changes

Copilot reviewed 32 out of 34 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
scripts/setup_github_repos.py Wrapper script to invoke the new repo-setup CLI.
pyproject.toml Adds boto3 dependency for AWS export retrieval.
poetry.lock Locks new dependencies (notably boto3/botocore stack).
packages/setup_github_repo/app/aws_exports.py Fetches CloudFormation exports and maps them into role values.
packages/setup_github_repo/app/cli.py CLI entrypoint: GitHub auth token resolution + AWS credential validation + runner invocation.
packages/setup_github_repo/app/constants.py Central constants for AWS profiles, app IDs, and target hostnames.
packages/setup_github_repo/app/github_access.py Applies standard EPS team access settings to repos.
packages/setup_github_repo/app/github_base.py Shared interactive confirmation + rate-limit pacing helpers.
packages/setup_github_repo/app/github_environments.py Creates GitHub environments with reviewers and deployment policies.
packages/setup_github_repo/app/github_repo_settings.py Applies repo edit options, Actions permissions, and branch protection.
packages/setup_github_repo/app/github_secrets.py Creates Actions/Dependabot/environment secrets for weekly release and other cases.
packages/setup_github_repo/app/github_setup.py Facade coordinating settings/access/environments/secrets setup.
packages/setup_github_repo/app/models.py Dataclasses for repo config, teams, roles, secrets, environments.
packages/setup_github_repo/app/repo_status.py Loads and normalises setup config from NHSDigital/eps-repo-status repos.json.
packages/setup_github_repo/app/runner.py End-to-end orchestration using GitHub + AWS exports + repo-status config.
packages/setup_github_repo/app/secrets_builder.py Builds consolidated secrets payload from files, AWS exports, and env vars.
packages/setup_github_repo/app/init.py Package init for app modules.
packages/setup_github_repo/init.py Top-level package init.
packages/setup_github_repo/main.py Enables running via python -m setup_github_repo.
packages/setup_github_repo/.coveragerc Coverage configuration for the new package.
packages/setup_github_repo/pytest.ini Pytest configuration (test discovery + coverage output).
packages/setup_github_repo/tests/init.py Test package init.
packages/setup_github_repo/tests/test_aws_exports.py Unit tests for AWS export pagination + role mapping.
packages/setup_github_repo/tests/test_cli.py Unit tests for GitHub token/AWS credential bootstrap logic.
packages/setup_github_repo/tests/test_github_access.py Unit tests for team permission application.
packages/setup_github_repo/tests/test_github_environments.py Unit tests for environment creation in weekly/non-weekly paths.
packages/setup_github_repo/tests/test_github_repo_settings.py Unit tests for repo settings + branch protection + Actions permissions.
packages/setup_github_repo/tests/test_github_secrets.py Unit tests for secret creation behavior based on repo config flags.
packages/setup_github_repo/tests/test_github_setup.py Unit tests for facade wiring and manager invocation.
packages/setup_github_repo/tests/test_repo_status.py Unit tests for repos.json payload parsing and loader behavior.
packages/setup_github_repo/tests/test_runner.py Unit tests for runner dependency wiring and repo filtering behavior.
packages/setup_github_repo/tests/test_secrets_builder.py Unit tests for secret payload assembly from files/exports/env.
.gitignore Ignores local .secrets/ directory used by the setup workflow.
.gitallowed Allows additional token-related patterns used by the new CLI/tests.

Comment thread packages/setup_github_repo/app/github_secrets.py Outdated
Comment thread packages/setup_github_repo/app/runner.py Outdated
Comment thread .gitallowed Outdated
Comment thread packages/setup_github_repo/app/github_secrets.py
Comment thread packages/setup_github_repo/app/github_environments.py
Comment thread scripts/setup_github_repos.py
Comment thread packages/setup_github_repo/app/github_environments.py Outdated
Comment thread packages/setup_github_repo/app/github_environments.py
Comment thread packages/setup_github_repo/app/github_access.py
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 4 comments.

Comment on lines +22 to +39
self._set_environment_secret(
repo=repo,
environment_name="create_pull_request",
secret_name="AUTOMERGE_PEM",
secret_value=secrets.automerge_pem,
)
self._set_environment_secret(
repo=repo,
environment_name="create_pull_request",
secret_name="AUTOMERGE_APP_ID",
secret_value=AUTOMERGE_APP_ID,
)
self._set_secret(
repo=repo,
secret_name="DEPENDABOT_TOKEN",
secret_value=secrets.dependabot_token,
set_dependabot=True,
)
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests in test_github_secrets.py expect AUTOMERGE_PEM and AUTOMERGE_APP_ID to be created via _set_secret (repo-level Actions/Dependabot secrets), but the implementation sets them as environment secrets on create_pull_request. This mismatch will fail tests and may also break workflows that read repo-level secrets. Decide which scope is correct and make code + tests consistent (typically: use _set_secret for AUTOMERGE_*, and reserve environment secrets for environment-specific values).

Copilot uses AI. Check for mistakes.
)
self._set_secret(
repo=repo,
secret_name=f"{env_name}_CLOUD_FORMATION_CREATE_CHANGESET_ROLE",
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The secret name uses ...CREATE_CHANGESET_ROLE but the underlying export/model field is cloud_formation_prepare_changeset_role (and the CloudFormation export name is CloudFormationPrepareChangesetRole). If downstream workflows expect a key aligned to 'PREPARE_CHANGESET', this will silently omit the expected secret. Align the secret name with the role/export naming (or update the consumer expectations) to avoid missing secrets at runtime.

Suggested change
secret_name=f"{env_name}_CLOUD_FORMATION_CREATE_CHANGESET_ROLE",
secret_name=f"{env_name}_CLOUD_FORMATION_PREPARE_CHANGESET_ROLE",

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +87
root_repos_file = Path(__file__).resolve().parents[3] / "repos.json"
original_content = root_repos_file.read_text(encoding="utf-8")
root_repos_file.write_text(json.dumps(payload), encoding="utf-8")

loader = RepoStatusLoader()

try:
result = loader.load_repo_configs()
finally:
root_repos_file.write_text(original_content, encoding="utf-8")
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test mutates a real repos.json at the repository root. That makes the suite non-hermetic and can become flaky under parallel test execution (or if another process reads repos.json concurrently). A more robust approach is to refactor RepoStatusLoader to accept an explicit repos_path (defaulting to the current location) and in tests pass a temp file (tmp_path) instead of writing into the repo.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +61
def _set_actions_permissions(self, repo: Any, repo_url: str) -> None:
workflow_permissions_endpoint = f"/repos/{repo_url}/actions/permissions/workflow"
_headers, workflow_permissions = repo._requester.requestJsonAndCheck("GET", workflow_permissions_endpoint)
default_workflow_permissions = str(workflow_permissions.get("default_workflow_permissions") or "read")

repo._requester.requestJsonAndCheck(
"PUT",
workflow_permissions_endpoint,
input={
"default_workflow_permissions": default_workflow_permissions,
"can_approve_pull_request_reviews": True,
},
)
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relies on PyGitHub’s private repo._requester API, which is prone to breaking across library versions. If possible, prefer a supported PyGitHub method for Actions permissions; if there isn’t one, consider routing these calls through a small, well-documented adapter (e.g., using the top-level Github requester) so the private API usage is isolated and easier to update.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 6 comments.

testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = --cov=setup_github_repo.app --cov-report=xml:packages/setup_github_repo/coverage/coverage.xml --cov-config=packages/setup_github_repo/.coveragerc
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test suite (and coverage target) references setup_github_repo.*, but in this repo the code lives under the top-level packages/ namespace (e.g. packages.setup_github_repo). Unless the test runner sets PYTHONPATH=packages (or installs the package), these imports will raise ModuleNotFoundError. Consider switching imports/coverage to packages.setup_github_repo.* or adding a test-time path configuration so setup_github_repo is importable.

Suggested change
addopts = --cov=setup_github_repo.app --cov-report=xml:packages/setup_github_repo/coverage/coverage.xml --cov-config=packages/setup_github_repo/.coveragerc
addopts = --cov=packages.setup_github_repo.app --cov-report=xml:packages/setup_github_repo/coverage/coverage.xml --cov-config=packages/setup_github_repo/.coveragerc

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +95
set_secret_names = [call.kwargs["secret_name"] for call in manager._set_secret.call_args_list]
assert set_secret_names == [
"AUTOMERGE_PEM",
"AUTOMERGE_APP_ID",
"DEPENDABOT_TOKEN",
]

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test asserts that AUTOMERGE_* secrets are set via _set_secret, but the implementation sets them via _set_environment_secret for the create_pull_request environment. As written, this assertion will fail; update the expectations (or change the implementation) so the test matches the intended secret scope.

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +103
set_environment_secret_names = [
call.kwargs["secret_name"] for call in manager._set_environment_secret.call_args_list
]
assert set_environment_secret_names == [
"CREATE_PULL_REQUEST_PEM",
"CREATE_PULL_REQUEST_APP_ID",
]

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_set_environment_secret is currently called for AUTOMERGE_* as well as CREATE_PULL_REQUEST_* secrets, but this test only expects the CREATE_PULL_REQUEST_* environment secrets. Update the expected list (or adjust the code) to keep the test aligned with the actual behavior.

Copilot uses AI. Check for mistakes.
assert "REF_ARTILLERY_RUNNER_ROLE" in set_secret_names

role_env_names = [call.kwargs["env_name"] for call in manager._set_role_secrets.call_args_list]
assert role_env_names == ["DEV", "INT", "PROD", "QA", "REF", "REF", "RECOVERY"]
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test expects _set_role_secrets to be called twice for env_name="REF", but the implementation only calls it once (the earlier duplicate was removed). Update role_env_names expectations to match the current call list.

Suggested change
assert role_env_names == ["DEV", "INT", "PROD", "QA", "REF", "REF", "RECOVERY"]
assert role_env_names == ["DEV", "INT", "PROD", "QA", "REF", "RECOVERY"]

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +112
def test_run_sets_up_only_eps_aws_dashboards_repo():
runner = SetupGithubRepoRunner.__new__(SetupGithubRepoRunner)
secrets = _secrets()
runner._secrets_builder = MagicMock()
runner._secrets_builder.build.return_value = secrets
runner._print_setup_summary = MagicMock()
runner._repo_status_loader = MagicMock()
runner._repo_status_loader.load_repo_configs.return_value = [
_repo_config("NHSDigital/eps-aws-dashboards"),
_repo_config("NHSDigital/other-repo"),
]
runner._github_setup = MagicMock()

runner.run()

runner._print_setup_summary.assert_called_once_with(secrets_keys=sorted(asdict(secrets).keys()))
runner._github_setup.setup_repo.assert_called_once_with(
repo_config=_repo_config("NHSDigital/eps-aws-dashboards"),
secrets=secrets,
)

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetupGithubRepoRunner.run() iterates over all repo configs returned by load_repo_configs() and calls setup_repo for each. This test currently expects only one repo to be set up, so it will fail; either update the test to expect calls for both repos or introduce explicit filtering in run().

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +127
def test_run_skips_setup_when_no_matching_repo():
runner = SetupGithubRepoRunner.__new__(SetupGithubRepoRunner)
secrets = _secrets()
runner._secrets_builder = MagicMock()
runner._secrets_builder.build.return_value = secrets
runner._print_setup_summary = MagicMock()
runner._repo_status_loader = MagicMock()
runner._repo_status_loader.load_repo_configs.return_value = [_repo_config("NHSDigital/other-repo")]
runner._github_setup = MagicMock()

runner.run()

runner._github_setup.setup_repo.assert_not_called()

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runner currently does not skip setup when there are repo configs present; it will call setup_repo for every entry returned. This test expects no setup call when the list contains a non-matching repo, but there is no matching logic in run(), so the assertion will fail. Align the test with the runner behavior (or implement filtering).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants