diff --git a/.github/workflows/validate-source-repos.yml b/.github/workflows/validate-source-repos.yml new file mode 100644 index 00000000..fb04f541 --- /dev/null +++ b/.github/workflows/validate-source-repos.yml @@ -0,0 +1,30 @@ +--- +name: Validate source repositories + +on: + push: + branches: + - main + pull_request: +jobs: + validate-source-repos: + name: Validate source repositories + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # Python version must be pinned because of issue with Ubuntu permissions + # See https://github.com/actions/runner-images/issues/11499 + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.12' + + - name: Install pip dependencies + run: | + pip install -r requirements.txt + + - name: Validate source repositories + run: python3 scripts/validate-source-repos.py diff --git a/ansible/inventory/group_vars/all/source-repositories b/ansible/inventory/group_vars/all/source-repositories index f41f7a77..b4278831 100644 --- a/ansible/inventory/group_vars/all/source-repositories +++ b/ansible/inventory/group_vars/all/source-repositories @@ -26,6 +26,8 @@ ansible_workflows: - publish-role community_files: codeowners: + admins: | + * @stackhpc/admins ansible: | * @stackhpc/ansible batch: | @@ -412,6 +414,14 @@ source_repositories: - codeowners: content: "{{ community_files.codeowners.openstack }}" dest: ".github/CODEOWNERS" + # Admins team + terraform-bastion: + repository_type: "single-branch" + workflows: [] + community_files: + - codeowners: + content: "{{ community_files.codeowners.admins }}" + dest: ".github/CODEOWNERS" # Ansible team ansible-role-libvirt-host: repository_type: "ansible" @@ -713,3 +723,25 @@ source_repositories: - codeowners: content: "{{ community_files.codeowners.release_train }}" dest: ".github/CODEOWNERS" + # SMSLab team + smslab-azimuth-config: + repository_type: "ansible" + workflows: [] + community_files: + - codeowners: + content: "{{ community_files.codeowners.sms_lab }}" + dest: ".github/CODEOWNERS" + smslab-config: + repository_type: "ansible" + workflows: [] + community_files: + - codeowners: + content: "{{ community_files.codeowners.sms_lab }}" + dest: ".github/CODEOWNERS" + smslab-kayobe-config: + repository_type: "ansible" + workflows: [] + community_files: + - codeowners: + content: "{{ community_files.codeowners.sms_lab }}" + dest: ".github/CODEOWNERS" diff --git a/scripts/validate-source-repos.py b/scripts/validate-source-repos.py new file mode 100644 index 00000000..8481212f --- /dev/null +++ b/scripts/validate-source-repos.py @@ -0,0 +1,87 @@ +import json +import sys +import yaml + + +def read_repos_data(): + terraform_repositories = {} + ansible_repositories = {} + + with open('terraform/github/terraform.tfvars.json', 'r') as file: + data = json.load(file) + terraform_repositories = data['repositories'] + + with open('ansible/inventory/group_vars/all/source-repositories', + 'r') as file: + data = yaml.safe_load(file) + ansible_repositories = data['source_repositories'] + + return terraform_repositories, ansible_repositories + + +def get_repos_diff(tf_repos, ans_repos): + ans_repos_list = list(ans_repos.keys()) + + tf_repos_list = [] + for team in tf_repos: + tf_repos_list += tf_repos[team] + + diff = list(set(tf_repos_list).symmetric_difference(ans_repos_list)) + + return diff + + +def _extract_group_from_community_files(files): + for file in files: + if 'codeowners' in file: + content = file['codeowners']['content'] + return content.strip('{ }').replace('_', '').split('.')[-1] + + +def _group_ans_repos_by_group(ans_repos): + ans_repos_by_group = {} + for repo in ans_repos: + group = _extract_group_from_community_files( + ans_repos[repo]['community_files']) + if group in ans_repos_by_group: + ans_repos_by_group[group].append(repo) + else: + ans_repos_by_group[group] = [repo] + return ans_repos_by_group + + +def get_mismatched_repos(tf_repos, ans_repos, repos_missing): + ans_repos_new = _group_ans_repos_by_group(ans_repos) + tf_repos_new = {k.lower(): v for k, v in tf_repos.items()} + + mismatched_repos = [] + for group in tf_repos_new: + if len(tf_repos_new[group]) == 0 or group not in ans_repos_new: + continue + else: + diff = list(set(tf_repos_new[group]).symmetric_difference( + set(ans_repos_new[group]))) + mismatched_repos.extend(diff) + return list(set(mismatched_repos).difference(set(repos_missing))) + + +def main(): + terraform_repos, ansible_repos = read_repos_data() + + repos_missing = get_repos_diff(terraform_repos, ansible_repos) + + print('The following repos are only present in one of the Ansible ' + f'source-repositories and the Terraform tfvars: {repos_missing}') + + mismatched_repos = get_mismatched_repos(terraform_repos, ansible_repos, + repos_missing) + + print('The following repos are assigned to different codeowner groups in ' + 'the Ansible source-repositories and the Terraform tfvars: ' + f'{mismatched_repos}') + + return len(repos_missing) > 0 or len(mismatched_repos) > 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/terraform/github/branches.tf b/terraform/github/branches.tf index 5454fe8f..8b8408ac 100644 --- a/terraform/github/branches.tf +++ b/terraform/github/branches.tf @@ -1,3 +1,35 @@ +resource "github_branch_protection" "admins_branch_protection" { + for_each = toset(var.repositories["Admins"]) + repository_id = data.github_repository.repositories[each.key].node_id + + pattern = "master" + require_conversation_resolution = true + allows_deletions = false + allows_force_pushes = false + + required_pull_request_reviews { + dismiss_stale_reviews = true + require_code_owner_reviews = true + required_approving_review_count = 1 + } + + restrict_pushes { + blocks_creations = false + push_allowances = [ + resource.github_team.organisation_teams["Developers"].node_id + ] + } + + required_status_checks { + contexts = lookup(var.required_status_checks, each.key, { "default" : [] }).default + strict = false + } + + lifecycle { + prevent_destroy = true + } +} + resource "github_branch_protection" "ansible_branch_protection" { for_each = toset(var.repositories["Ansible"]) repository_id = data.github_repository.repositories[each.key].node_id diff --git a/terraform/github/terraform.tfvars.json b/terraform/github/terraform.tfvars.json index 44b74dbd..1c1b0ded 100644 --- a/terraform/github/terraform.tfvars.json +++ b/terraform/github/terraform.tfvars.json @@ -1,6 +1,9 @@ { "owner": "stackhpc", "repositories": { + "Admins": [ + "terraform-bastion" + ], "Ansible": [ "ansible-role-libvirt-host", "ansible-role-libvirt-vm", @@ -66,6 +69,7 @@ "horizon", "ironic-python-agent", "ironic-ui", + "keystone", "magnum", "magnum-ui", "manila", @@ -88,8 +92,7 @@ "SMSLab": [ "smslab-azimuth-config", "smslab-config", - "smslab-kayobe-config", - "terraform-bastion" + "smslab-kayobe-config" ] }, "teams": {