From 38dd28e9ce42760ef43b297a43a8cd0b53f668fc Mon Sep 17 00:00:00 2001 From: Lars Kellogg-Stedman Date: Thu, 13 Feb 2025 20:52:57 -0500 Subject: [PATCH] Initial commit --- .github/dependabot.yml | 9 +++ .github/workflows/pre-commit.yaml | 21 ++++++ .gitignore | 4 ++ .pre-commit-config.yaml | 29 ++++++++ .terraform.lock.hcl | 25 +++++++ .yamllint.yaml | 10 +++ README.md | 93 ++++++++++++++++++++++++ backend.tf | 16 +++++ ci/markup-tofu-errors.sh | 12 ++++ ci/run-tofu-cmd.sh | 29 ++++++++ ci/tofu-fmt.sh | 27 +++++++ locals.tf | 32 +++++++++ main.tf | 3 + members.csv | 68 ++++++++++++++++++ members.tf | 9 +++ modules/common_repository/Makefile | 4 ++ modules/common_repository/README.md | 68 ++++++++++++++++++ modules/common_repository/README.md.in | 31 ++++++++ modules/common_repository/labels.csv | 15 ++++ modules/common_repository/locals.tf | 4 ++ modules/common_repository/main.tf | 98 ++++++++++++++++++++++++++ modules/common_repository/variables.tf | 90 +++++++++++++++++++++++ modules/common_repository/versions.tf | 9 +++ organization.tf | 13 ++++ repositories.tf | 49 +++++++++++++ team-members/instructlab-admins.csv | 23 ++++++ team-members/kruize-admins.csv | 11 +++ team-members/nerc-ai4cloudops.csv | 6 ++ team-members/nerc-curator.csv | 4 ++ team-members/nerc-docs.csv | 7 ++ team-members/nerc-infra-people.csv | 2 + team-members/nerc-logs-metrics.csv | 22 ++++++ team-members/nerc-obs-admins.csv | 10 +++ team-members/nerc-ops.csv | 22 ++++++ team-members/nerc-prod-people.csv | 2 + team-members/nerc-rhods.csv | 15 ++++ team-members/nerc-test-people.csv | 3 + teams.csv | 13 ++++ teams.tf | 60 ++++++++++++++++ versions.tf | 9 +++ 40 files changed, 977 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/pre-commit.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .terraform.lock.hcl create mode 100644 .yamllint.yaml create mode 100644 README.md create mode 100644 backend.tf create mode 100755 ci/markup-tofu-errors.sh create mode 100755 ci/run-tofu-cmd.sh create mode 100755 ci/tofu-fmt.sh create mode 100644 locals.tf create mode 100644 main.tf create mode 100644 members.csv create mode 100644 members.tf create mode 100644 modules/common_repository/Makefile create mode 100644 modules/common_repository/README.md create mode 100644 modules/common_repository/README.md.in create mode 100644 modules/common_repository/labels.csv create mode 100644 modules/common_repository/locals.tf create mode 100644 modules/common_repository/main.tf create mode 100644 modules/common_repository/variables.tf create mode 100644 modules/common_repository/versions.tf create mode 100644 organization.tf create mode 100644 repositories.tf create mode 100644 team-members/instructlab-admins.csv create mode 100644 team-members/kruize-admins.csv create mode 100644 team-members/nerc-ai4cloudops.csv create mode 100644 team-members/nerc-curator.csv create mode 100644 team-members/nerc-docs.csv create mode 100644 team-members/nerc-infra-people.csv create mode 100644 team-members/nerc-logs-metrics.csv create mode 100644 team-members/nerc-obs-admins.csv create mode 100644 team-members/nerc-ops.csv create mode 100644 team-members/nerc-prod-people.csv create mode 100644 team-members/nerc-rhods.csv create mode 100644 team-members/nerc-test-people.csv create mode 100644 teams.csv create mode 100644 teams.tf create mode 100644 versions.tf diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6937de7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..32a291b --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,21 @@ +name: pre-commit + +on: + workflow_dispatch: + pull_request: + push: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - uses: opentofu/setup-opentofu@v1 + with: + tofu_wrapper: false + - uses: pre-commit/action@v3.0.1 + with: + extra_args: --all-files diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3508efb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.terraform +terraform.tfstate +terraform.tfstate.backup +.env diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..14052cb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: check-merge-conflict + - id: end-of-file-fixer + - id: check-added-large-files + - id: check-case-conflict + - id: check-json + - id: check-symlinks + - id: detect-private-key + +- repo: https://github.com/adrienverge/yamllint.git + rev: v1.35.1 + hooks: + - id: yamllint + files: \.(yaml|yml)$ + types: [file, yaml] + entry: yamllint --strict + +- repo: local + hooks: + - id: tofu-fmt + pass_filenames: false + name: tofu-fmt + language: script + files: \.tf$ + entry: ./ci/tofu-fmt.sh diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..ca610cc --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/integrations/github" { + version = "6.5.0" + constraints = "~> 6.0" + hashes = [ + "h1:ikIBPXI5lx7cV4aqLWYuL+LQnHmf+FybvdpcsfAqK4o=", + "zh:3088bfd30c51ebfcb7c8d829465ec7b3c19af684cf1aff1ea1111ad3c6421c11", + "zh:34f9054b0123f9fa7ab8ebc73591d2cf502f1cc75e7594bde42ce799fcac32b6", + "zh:406dc2e63d43a24ac4f1b004e5c60ada3347207ea750bbd51e6199eb7f044f9f", + "zh:43e7b6cb7e5062d9b7b7cf4d23f6ea99fb9605fb014fede62cda307051063c05", + "zh:6a0923ebcc09cb98c488c11582375d2145ba965d1e6f2f69c077be8e1224020b", + "zh:a2331f06b7ed57e83eadb784211067d675826f67cf0ed051c8ab20335d83de9a", + "zh:a3f82213c98319f20438bdb92145ce1b0407cd8b8eec9745c036db10deb3d3a2", + "zh:b4b8db8537d8e6fb3f05ed875726823e1dc6925c479db8749016e71568ebafc4", + "zh:cdcf76f6f6f5c638db540490ab35bb1aacfc27204f1197004da5e950024afc06", + "zh:de36cea60efe2b74cec958f88ec5c39d467ad9443c9c9e311424c3db229c4e78", + "zh:dfb8949edc6722da66c78a19ccb1b81ac855439a28ca3badfdac5c10bbf2190d", + "zh:e1a81734cc81f4f51dd11ca8a62b420f68e72d00835ed54f84d71bd56d19f37f", + "zh:ec0d51640c3e3cf933c73d0ed79ba8b395d1b94fed8117a6438dba872aa5561f", + "zh:ec59b7c420a2358e9750e9c6a8a5ef26ccbb8a2cae417e115e86d63520759ea5", + "zh:fbd1fee2c9df3aa19cf8851ce134dea6e45ea01cb85695c1726670c285797e25", + ] +} diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..88404b7 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,10 @@ +extends: default +rules: + line-length: disable + document-start: disable + indentation: + indent-sequences: whatever + hyphens: + max-spaces-after: 4 + truthy: + check-keys: false diff --git a/README.md b/README.md new file mode 100644 index 0000000..93c5f1f --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# OCP-on-NERC Github configuration + +This repository contains [OpenTofu] plan to manage the [OCP-on-NERC] GitHub organization. + + +[ocp-on-nerc]: https://github.com/ocp-on-nerc +[opentofu]: https://opentofu.org/ + +## How does it work? + +When a commit is pushed to the `main` branch (e.g., when a pull request merges), that triggers the `.github/workflows/apply.yaml` workflow. This workflow acquires necessary credentials from GithHub secrets and from the "Org Config Management" GitHub app, and then uses [OpenTofu] to apply the requested configuration. + +## How do I... + +### Add a new organization member? + +1. Open `members.csv` +1. Add a new row of the form `,`, where `` in almost all cases should be `member`. + +### Add a new team? + +1. Open `teams.csv` +1. Add a new row of the form `,,`, where `` can be either `closed` (visible to all members of the organization) or `secret` (visible to organization owners and members of this team) + +### Add an organization member to a team? + +1. Open `team-members/.csv` +1. Add a new line of the form `,`, where `` should be `member`. + +### Add a new repository? + +1. Open `repositories.tf` +1. Add a new block of the form: + + ``` + module "repo_" { + source = "./modules/common_repository" + name = "" + description = ",,` + +Where `` is `` transformed to be a valid identifier in most common languages: a single word consisting of only alphanumerics and underscores. So e.g. `github-config` would become `github_config`, and `.gitjub` would become something like `dotgithub` (`_github` would also work). + +## The common_repository module + +This module will create: + +- A repository with issues enabled and wikis and projects disabled +- Branch protection rules for the `main` branch requiring at least 2 approvals for pull requests and restricting force pushes to members of the `nerc-org-admins` team +- A standard set of labels + +See the [README file for the common_repository module][common_repository] for more information about customizing repository configuration (including how to make a repository private and how to add collaborators). + +[common_repository]: ./modules/common_repository/ + +## Suggested local pre-commit checks + +You should ensure that you run `tofu fmt` before submitting a pull request. The easiest way of doing this is by installing the `pre-commit` tool on your local system and then running `pre-commit install`. This will configure `.git/hooks/pre-commit` to run the `pre-commit` tool whenever you create a new commit. If there are formatting changes, this will abort the commit and apply the necessary changes to your files. You can then add the modified files and update the commit. + +## Prerequisites for applying the configuration + +In general, you won't need to do this: the configuration is applied when a pull request merges to the `main` branch. These instructions will be useful if is necessary to apply changes manually (this can happen, for example, if someone makes changes to the organization through the GitHub web UI rather than through this repository). + +1. Ensure that you have either Terraform or OpenTofu installed. There are packages for both available on Fedora: + + ``` + dnf install opentofu + ``` + +1. Acquire S3 credentials. + + OpenTofu maintains state information about the target infrastructure; you need this state in order to plan and apply the configuration. We store this information in an S3 bucket provided by the [NERC]. You need appropriate AWS credentials in order for OpenTofu to access the cached state. These should be provided in the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables. + +1. Acquire GitHub credentials. + + In order to apply the configuration, OpenTofu needs administrative access to our organization. You will need a token with at least `admin:org` and `repo` privileges for the `innabox` organization. This should be provided in the `GITHUB_TOKEN` environment variable. + +[nerc]: https://nerc.mghpcc.org/ + +## Additional documentation + +- OpenTofu [introductory documentation](https://opentofu.org/docs/intro/). + +- The OpenTofu [github provider](https://search.opentofu.org/provider/opentofu/github/v6.3.0). + + This includes documentation for most of the resource types used in this repository. diff --git a/backend.tf b/backend.tf new file mode 100644 index 0000000..077328d --- /dev/null +++ b/backend.tf @@ -0,0 +1,16 @@ +# Define where terraform will store the shared state. +terraform { + backend "s3" { + endpoints = { + s3 = "https://stack.nerc.mghpcc.org:13808" + } + bucket = "tfstate" + key = "ocp-on-nerc.tfstate" + region = "main" + skip_credentials_validation = true + skip_requesting_account_id = true + skip_metadata_api_check = true + skip_region_validation = true + use_path_style = true + } +} diff --git a/ci/markup-tofu-errors.sh b/ci/markup-tofu-errors.sh new file mode 100755 index 0000000..b7e0b3c --- /dev/null +++ b/ci/markup-tofu-errors.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +sed ' +/./,$!d +/^Error/ i\ +\ +> [!WARNING]\ +> + +s/^ / / +s/^/> / +' diff --git a/ci/run-tofu-cmd.sh b/ci/run-tofu-cmd.sh new file mode 100755 index 0000000..b4a9aa7 --- /dev/null +++ b/ci/run-tofu-cmd.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +tmpout=$(mktemp applyoutXXXXXX) +tmperr=$(mktemp applyerrXXXXXX) +trap 'rm -f "$tmpout" "$tmperr"' EXIT + +tofu "$@" > "$tmpout" 2> "$tmperr" +exitcode=$? + +if [[ -n "$GITHUB_ACTION" ]]; then + echo "::group::stdout-${RANDOM}" + cat "$tmpout" + echo "::endgroup" + + echo "::group::stderr-${RANDOM}" + cat "$tmperr" + echo "::endgroup" + if grep -q '^Error:' "$tmperr"; then + { + printf "## Errors\n" + ./ci/markup-tofu-errors.sh < "$tmperr" + } >> "$GITHUB_STEP_SUMMARY" + fi +else + cat "$tmpout" + cat "$tmperr" >&2 +fi + +exit "$exitcode" diff --git a/ci/tofu-fmt.sh b/ci/tofu-fmt.sh new file mode 100755 index 0000000..044fc9f --- /dev/null +++ b/ci/tofu-fmt.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +tmpout=$(mktemp applyoutXXXXXX) +tmperr=$(mktemp applyerrXXXXXX) +trap 'rm -f "$tmpout" "$tmperr"' EXIT + +if ! tofu fmt -check -diff -no-color -recursive > "$tmpout" 2> "$tmperr"; then + if [[ -n "$GITHUB_ACTION" ]]; then + if [[ -s "$tmperr" ]]; then + { + printf "## Errors\n" + ./ci/markup-tofu-errors.sh < "$tmperr" + } >> "$GITHUB_STEP_SUMMARY" + fi + if [[ -s "$tmpout" ]]; then + { + printf "## Changes\n" + printf "> [!WARNING]\n> There are formatting problems that must be addressed.\n\n" + echo '```' + cat "$tmpout" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + fi + fi + tofu fmt -diff -recursive + exit 1 +fi diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..f1bc6bc --- /dev/null +++ b/locals.tf @@ -0,0 +1,32 @@ +# Populate variables with contents from local .csv files. + +locals { + # Parse team member files + team_members_path = "team-members" + team_members_files = { + for file in fileset(local.team_members_path, "*.csv") : + trimsuffix(file, ".csv") => csvdecode(file("${local.team_members_path}/${file}")) + } + + # Create temp object that has team ID and CSV contents + team_members_temp = flatten([ + for team, members in local.team_members_files : [ + { + slug = team + members = members + } + ] + ]) + + # Create object for each team-user relationship + team_members = flatten([ + for team in local.team_members_temp : [ + for member in team.members : { + name = "${team.slug}-${member.username}" + team_id = team.slug + username = member.username + role = member.role + } + ] + ]) +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..ae341ed --- /dev/null +++ b/main.tf @@ -0,0 +1,3 @@ +provider "github" { + owner = "ocp-on-nerc" +} diff --git a/members.csv b/members.csv new file mode 100644 index 0000000..643f567 --- /dev/null +++ b/members.csv @@ -0,0 +1,68 @@ +username,role +aabaris,admin +bhanvimenghani,member +bharathappali,member +BillWeaver,member +bnshr,member +computate,member +cooktheryan,member +culbert,member +cybette,member +DanNiESh,member +dheerajodha,member +dinogun,member +dystewart,member +EldritchJS,member +erikerlandson,member +gagansk,member +Gregory-Pereira,member +griffinandrew,member +hakasapl,member +harshil-codes,member +hemajv,member +hpdempsey,admin +ilya-kolchinsky,member +IsaiahStapleton,member +jappavoo,member +jas-li,member +jbasu01,member +joachimweyl,admin +jon-stumpf,member +jtriley,admin +JudeNiroshan,member +khansaad,member +knikolla,member +kusumachalasani,member +larsks,admin +LaVLaS,member +leseb,member +lesleychou,member +memalhot,member +MichaelClifford,member +Milstein,admin +msdisme,member +nathanweeks,member +naved001,admin +piCounter,member +pjd-nu,member +QuanMPhm,member +rebeccaSimmonds19,member +RH-csaggin,member +rohankumar-1,member +sallyom,member +sauagarwa,member +schwesig,member +shekhar316,member +shreyabiradar07,member +Shreyanand,member +skanthed,member +srampal,member +StHeck,member +syedmohdqasim,member +syockel,member +tssala23,member +tumido,member +tzumainn,member +vbelouso,member +zaoxing,member +Zongshun96,member diff --git a/members.tf b/members.tf new file mode 100644 index 0000000..5b78edd --- /dev/null +++ b/members.tf @@ -0,0 +1,9 @@ +resource "github_membership" "all" { + for_each = { + for member in csvdecode(file("members.csv")) : + member.username => member + } + + username = each.value.username + role = each.value.role +} diff --git a/modules/common_repository/Makefile b/modules/common_repository/Makefile new file mode 100644 index 0000000..0ee5c29 --- /dev/null +++ b/modules/common_repository/Makefile @@ -0,0 +1,4 @@ +PLANS = $(wildcard *.tf) + +README.md: README.md.in $(PLANS) + (cat README.md.in; terraform-docs markdown --hide-empty .) > $@ || { rm -f $@; exit 1; } diff --git a/modules/common_repository/README.md b/modules/common_repository/README.md new file mode 100644 index 0000000..88db28d --- /dev/null +++ b/modules/common_repository/README.md @@ -0,0 +1,68 @@ +# OCP-on-NERC common repository configuration + +Create a repository with managed labels, permissions, and branch protection rules. + +## Examples + +### A very typical repository + +``` +module "repo_docs" { + source = "./modules/common_repository" + name = "docs" + description = "Some nifty documentation" +} +``` + +### A repository with a team collaborator + +``` +module "repo_docs" { + source = "./modules/common_repository" + name = "docs" + description = "Some nifty documentation" + teams = [ + { + team_id = "docs-workers" + permission = "push" + } + ] +} +``` +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.9.0 | +| [github](#requirement\_github) | ~> 6.0 | + +## Providers + +| Name | Version | +|------|---------| +| [github](#provider\_github) | ~> 6.0 | + +## Resources + +| Name | Type | +|------|------| +| [github_branch_protection.repo_protection](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_protection) | resource | +| [github_issue_label.repo_labels](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/issue_label) | resource | +| [github_repository.repo](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository) | resource | +| [github_repository_collaborators.repo_collaborators](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_collaborators) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [branch\_protection](#input\_branch\_protection) | Configure branch protection if true | `bool` | `true` | no | +| [description](#input\_description) | Repository description | `string` | `""` | no | +| [homepage\_url](#input\_homepage\_url) | URL for project home page | `string` | `null` | no | +| [is\_template](#input\_is\_template) | Set this to true if this is a template repository | `bool` | `false` | no | +| [labels](#input\_labels) | List of labels to configure on the repository |
list(object({
name = string
color = string
description = string
}))
| `null` | no | +| [name](#input\_name) | The name of the repository | `string` | n/a | yes | +| [required\_approvals](#input\_required\_approvals) | Number of approvals required before merging a pull request | `number` | `2` | no | +| [required\_status\_checks](#input\_required\_status\_checks) | A list of status checks that must pass before a PR can merge | `list(string)` | `[]` | no | +| [teams](#input\_teams) | Teams with access to this repository |
list(object({
team_id = string
permission = string
}))
| `[]` | no | +| [users](#input\_users) | Users with access to this repository |
list(object({
username = string
permission = string
}))
| `[]` | no | +| [visibility](#input\_visibility) | Repository visibility (public or private) | `string` | `"public"` | no | diff --git a/modules/common_repository/README.md.in b/modules/common_repository/README.md.in new file mode 100644 index 0000000..ea59cc7 --- /dev/null +++ b/modules/common_repository/README.md.in @@ -0,0 +1,31 @@ +# OCP-on-NERC common repository configuration + +Create a repository with managed labels, permissions, and branch protection rules. + +## Examples + +### A very typical repository + +``` +module "repo_docs" { + source = "./modules/common_repository" + name = "docs" + description = "Some nifty documentation" +} +``` + +### A repository with a team collaborator + +``` +module "repo_docs" { + source = "./modules/common_repository" + name = "docs" + description = "Some nifty documentation" + teams = [ + { + team_id = "docs-workers" + permission = "push" + } + ] +} +``` diff --git a/modules/common_repository/labels.csv b/modules/common_repository/labels.csv new file mode 100644 index 0000000..7c01cf6 --- /dev/null +++ b/modules/common_repository/labels.csv @@ -0,0 +1,15 @@ +name,color,description +bug,d73a4a,Something isn't working +documentation,0075ca,Improvements or additions to documentation +duplicate,cfd3d7,This issue or pull request already exists +enhancement,a2eeef,New feature or request +good first issue,7057ff,Good for newcomers +help wanted,008672,Extra attention is needed +invalid,e4e669,This doesn't seem right +question,d876e3,Further information is requested +research,6D1C7F,This will consist mostly of information gathering +wontfix,ffffff,This will not be worked on +placeholder,453411,Placeholder for one or more future issues +development,98EE84,This issue requires development work +blocked,873719,Include reason issue is blocked in the description +needs_clarification,F5CA41,This issue needs to be clarified diff --git a/modules/common_repository/locals.tf b/modules/common_repository/locals.tf new file mode 100644 index 0000000..db0276c --- /dev/null +++ b/modules/common_repository/locals.tf @@ -0,0 +1,4 @@ +locals { + common_labels = csvdecode(file("${path.module}/labels.csv")) + labels = var.labels == null ? local.common_labels : var.labels +} diff --git a/modules/common_repository/main.tf b/modules/common_repository/main.tf new file mode 100644 index 0000000..d442c96 --- /dev/null +++ b/modules/common_repository/main.tf @@ -0,0 +1,98 @@ +resource "github_repository" "repo" { + name = var.name + visibility = var.visibility + description = var.description + homepage_url = var.homepage_url + auto_init = true + allow_auto_merge = var.visibility == "private" ? false : true + has_issues = true + has_downloads = false + has_projects = false + has_wiki = false + is_template = var.is_template +} + +resource "github_issue_label" "repo_labels" { + repository = var.name + + # Generate label blocks from the value of local.values, which by default is initialized + # by the contents of the "labels.csv" file. + for_each = { + for label in local.labels : + label.name => label + } + + name = each.value.name + color = each.value.color + description = each.value.description + + depends_on = [github_repository.repo] +} + +resource "github_branch_protection" "repo_protection" { + # This odd looking construct lets us control the creation of the + # branch protection resource with a boolean variable. + count = var.visibility == "private" ? 0 : var.branch_protection ? 1 : 0 + + repository_id = var.name + pattern = "main" + + required_linear_history = true + allows_deletions = false + allows_force_pushes = false + enforce_admins = false + require_conversation_resolution = false + require_signed_commits = false + + force_push_bypassers = [ + "ocp-on-nerc/nerc-org-admins", + ] + + required_pull_request_reviews { + required_approving_review_count = var.required_approvals + } + + required_status_checks { + strict = true + contexts = var.required_status_checks + } + + depends_on = [github_repository.repo, github_repository_collaborators.repo_collaborators] +} + +resource "github_repository_collaborators" "repo_collaborators" { + repository = var.name + + # Always grant nerc-org-admins push (write) access to repository. This is necessary to support the + # force_push_bypassers configuration (above). + team { + team_id = "nerc-org-admins" + permission = "push" + } + + # Generate team blocks from the value of the "teams" input variable. + dynamic "team" { + for_each = { + for team in var.teams : + team.team_id => team + } + content { + team_id = team.value.team_id + permission = team.value.permission + } + } + + # Generate user blocks from the value of the "users" input variable. + dynamic "user" { + for_each = { + for user in var.users : + user.username => user + } + content { + username = user.value.username + permission = user.value.permission + } + } + + depends_on = [github_repository.repo] +} diff --git a/modules/common_repository/variables.tf b/modules/common_repository/variables.tf new file mode 100644 index 0000000..fc51a34 --- /dev/null +++ b/modules/common_repository/variables.tf @@ -0,0 +1,90 @@ +variable "name" { + description = "The name of the repository" + type = string +} + +variable "description" { + description = "Repository description" + type = string + default = "" +} + +variable "required_approvals" { + description = "Number of approvals required before merging a pull request" + type = number + default = 2 +} + +variable "required_status_checks" { + description = "A list of status checks that must pass before a PR can merge" + type = list(string) + default = [] +} + +variable "visibility" { + description = "Repository visibility (public or private)" + type = string + default = "public" + validation { + error_message = "unknown visiblity: must be public or private" + condition = contains(["public", "private"], var.visibility) + } +} + +variable "branch_protection" { + description = "Configure branch protection if true" + type = bool + default = true +} + +variable "labels" { + description = "List of labels to configure on the repository" + type = list(object({ + name = string + color = string + description = string + })) + default = null +} + +variable "teams" { + description = "Teams with access to this repository" + type = list(object({ + team_id = string + permission = string + })) + default = [] + validation { + error_message = "unknown permission: permission must be one of pull, push, maintain, triage, or admin" + condition = alltrue([ + for v in var.teams : contains(["pull", "push", "maintain", "triage", "admin"], v.permission) + ]) + } +} + +variable "users" { + description = "Users with access to this repository" + type = list(object({ + username = string + permission = string + })) + default = [] + validation { + error_message = "unknown permission: permission must be one of pull, push, maintain, triage, or admin" + condition = alltrue([ + for v in var.users : contains(["pull", "push", "maintain", "triage", "admin"], v.permission) + ]) + } +} + +variable "is_template" { + description = "Set this to true if this is a template repository" + type = bool + default = false +} + +variable "homepage_url" { + description = "URL for project home page" + type = string + default = null +} diff --git a/modules/common_repository/versions.tf b/modules/common_repository/versions.tf new file mode 100644 index 0000000..f39d64f --- /dev/null +++ b/modules/common_repository/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.9.0" + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +} diff --git a/organization.tf b/organization.tf new file mode 100644 index 0000000..2c1388f --- /dev/null +++ b/organization.tf @@ -0,0 +1,13 @@ +resource "github_organization_settings" "ocp-on-nerc" { + name = "OCP on NERC" + description = "OpenShift at the New England Research Cloud" + location = "United States of America" + billing_email = "lars@redhat.com" + secret_scanning_enabled_for_new_repositories = true + has_repository_projects = false + default_repository_permission = "read" + + members_can_create_private_repositories = true + members_can_create_public_repositories = true + members_can_create_repositories = true +} diff --git a/repositories.tf b/repositories.tf new file mode 100644 index 0000000..948875f --- /dev/null +++ b/repositories.tf @@ -0,0 +1,49 @@ +module "repo_github_config" { + source = "./modules/common_repository" + name = "github-config" + description = "Repository for managing ocp-on-nerc github organization" + required_status_checks = [ + "pre-commit", + ] +} + +module "repo_openshift_tests" { + source = "./modules/common_repository" + name = "openshift-tests" + description = "Tests for assessing the health of a cluster" + required_status_checks = [ + "pre-commit", + ] +} + +module "repo_nerc_ocp_config" { + source = "./modules/common_repository" + name = "nerc-ocp-config" + description = "Configuration of NERC OpenShift clusters" + homepage_url = "https://nerc.mghpcc.org" + required_status_checks = [ + "run-linters / run-linters", + ] + teams = [ + { + team_id = "nerc-ops" + permission = "push" + } + ] +} + +module "repo_nerc_ocp_apps" { + source = "./modules/common_repository" + name = "nerc-ocp-apps" + description = "ArgoCD apps for NERC OpenShift clusters" + homepage_url = "https://nerc.mghpcc.org" + required_status_checks = [ + "run-linters / run-linters", + ] + teams = [ + { + team_id = "nerc-ops" + permission = "push" + } + ] +} diff --git a/team-members/instructlab-admins.csv b/team-members/instructlab-admins.csv new file mode 100644 index 0000000..796135c --- /dev/null +++ b/team-members/instructlab-admins.csv @@ -0,0 +1,23 @@ +username,role +larsks,maintainer +erikerlandson,member +leseb,member +LaVLaS,member +cybette,member +cooktheryan,member +MichaelClifford,member +hemajv,member +tumido,member +srampal,member +Shreyanand,member +EldritchJS,member +sallyom,member +JudeNiroshan,member +Gregory-Pereira,member +joachimweyl,maintainer +DanNiESh,member +gagansk,member +ilya-kolchinsky,member +tssala23,member +sauagarwa,member +vbelouso,member diff --git a/team-members/kruize-admins.csv b/team-members/kruize-admins.csv new file mode 100644 index 0000000..85e60bc --- /dev/null +++ b/team-members/kruize-admins.csv @@ -0,0 +1,11 @@ +username,role +larsks,maintainer +dinogun,member +khansaad,member +bharathappali,member +kusumachalasani,member +rebeccaSimmonds19,member +bhanvimenghani,member +shekhar316,member +shreyabiradar07,member +schwesig,member diff --git a/team-members/nerc-ai4cloudops.csv b/team-members/nerc-ai4cloudops.csv new file mode 100644 index 0000000..cd8c1f6 --- /dev/null +++ b/team-members/nerc-ai4cloudops.csv @@ -0,0 +1,6 @@ +username,role +larsks,maintainer +zaoxing,member +syedmohdqasim,member +jas-li,member +rohankumar-1,member diff --git a/team-members/nerc-curator.csv b/team-members/nerc-curator.csv new file mode 100644 index 0000000..7fe938e --- /dev/null +++ b/team-members/nerc-curator.csv @@ -0,0 +1,4 @@ +username,role +larsks,maintainer +gagansk,member +skanthed,member diff --git a/team-members/nerc-docs.csv b/team-members/nerc-docs.csv new file mode 100644 index 0000000..a645617 --- /dev/null +++ b/team-members/nerc-docs.csv @@ -0,0 +1,7 @@ +username,role +larsks,maintainer +Milstein,maintainer +msdisme,member +syockel,member +joachimweyl,maintainer +tssala23,member diff --git a/team-members/nerc-infra-people.csv b/team-members/nerc-infra-people.csv new file mode 100644 index 0000000..4eba55b --- /dev/null +++ b/team-members/nerc-infra-people.csv @@ -0,0 +1,2 @@ +username,role +larsks,maintainer diff --git a/team-members/nerc-logs-metrics.csv b/team-members/nerc-logs-metrics.csv new file mode 100644 index 0000000..cec5765 --- /dev/null +++ b/team-members/nerc-logs-metrics.csv @@ -0,0 +1,22 @@ +username,role +jtriley,maintainer +larsks,maintainer +Milstein,maintainer +jappavoo,member +msdisme,member +aabaris,maintainer +EldritchJS,member +syedmohdqasim,member +computate,member +naved001,maintainer +lesleychou,member +joachimweyl,maintainer +Zongshun96,member +dheerajodha,member +hpdempsey,maintainer +tssala23,member +harshil-codes,member +RH-csaggin,member +schwesig,member +jbasu01,member +bnshr,member diff --git a/team-members/nerc-obs-admins.csv b/team-members/nerc-obs-admins.csv new file mode 100644 index 0000000..f706866 --- /dev/null +++ b/team-members/nerc-obs-admins.csv @@ -0,0 +1,10 @@ +username,role +jtriley,maintainer +aabaris,maintainer +computate,member +dheerajodha,member +harshil-codes,member +RH-csaggin,member +schwesig,member +jbasu01,member +bnshr,member diff --git a/team-members/nerc-ops.csv b/team-members/nerc-ops.csv new file mode 100644 index 0000000..3b90db1 --- /dev/null +++ b/team-members/nerc-ops.csv @@ -0,0 +1,22 @@ +username,role +jtriley,maintainer +larsks,maintainer +culbert,member +Milstein,maintainer +tzumainn,member +pjd-nu,member +knikolla,member +msdisme,member +aabaris,maintainer +StHeck,member +computate,member +syockel,member +naved001,maintainer +joachimweyl,maintainer +DanNiESh,member +hakasapl,member +IsaiahStapleton,member +tssala23,member +QuanMPhm,member +dystewart,member +schwesig,member diff --git a/team-members/nerc-prod-people.csv b/team-members/nerc-prod-people.csv new file mode 100644 index 0000000..4eba55b --- /dev/null +++ b/team-members/nerc-prod-people.csv @@ -0,0 +1,2 @@ +username,role +larsks,maintainer diff --git a/team-members/nerc-rhods.csv b/team-members/nerc-rhods.csv new file mode 100644 index 0000000..b5860fe --- /dev/null +++ b/team-members/nerc-rhods.csv @@ -0,0 +1,15 @@ +username,role +larsks,maintainer +Milstein,maintainer +nathanweeks,member +zaoxing,member +syedmohdqasim,member +computate,member +Zongshun96,member +DanNiESh,member +gagansk,member +IsaiahStapleton,member +hpdempsey,maintainer +griffinandrew,member +dystewart,member +memalhot,member diff --git a/team-members/nerc-test-people.csv b/team-members/nerc-test-people.csv new file mode 100644 index 0000000..87465be --- /dev/null +++ b/team-members/nerc-test-people.csv @@ -0,0 +1,3 @@ +username,role +larsks,maintainer +tumido,member diff --git a/teams.csv b/teams.csv new file mode 100644 index 0000000..3132b96 --- /dev/null +++ b/teams.csv @@ -0,0 +1,13 @@ +name,description,privacy +instructlab-admins,experiment with instructlab on OAI,closed +kruize-admins,,closed +nerc-ai4cloudops,,closed +nerc-curator,,closed +nerc-docs,Documentation maintainers,closed +nerc-infra-people,People who can authenticate to nerc-ocp-infra,closed +nerc-logs-metrics,Access to metrics and logs on the nerc-infra cluster,closed +nerc-obs-admins,Admin users for the observability cluster,closed +nerc-ops,NERC system operators,closed +nerc-prod-people,People who can authenticate to nerc-ocp-prod,closed +nerc-rhods,,closed +nerc-test-people,People who can authenticate to nerc-ocp-test,closed diff --git a/teams.tf b/teams.tf new file mode 100644 index 0000000..c715774 --- /dev/null +++ b/teams.tf @@ -0,0 +1,60 @@ +# Create teams from the contents of "teams.csv" +resource "github_team" "all" { + for_each = { + for team in csvdecode(file("teams.csv")) : + team.name => team + } + + name = each.value.name + description = each.value.description + privacy = each.value.privacy +} + +# Populate team members based on the csv files in the team-members directory. +resource "github_team_membership" "members" { + for_each = { for tm in local.team_members : tm.name => tm } + + team_id = each.value.team_id + username = each.value.username + role = each.value.role +} + +## This creates an "all-members" team containing all organization members. +#resource "github_team" "all-members" { +# name = "all-members" +# description = "All organization members" +# privacy = "closed" +#} +# +#resource "github_team_membership" "all-members" { +# for_each = { +# for member in csvdecode(file("members.csv")) : +# member.username => member +# } +# +# team_id = "all-members" +# role = each.value.role == "admin" ? "maintainer" : "member" +# username = each.value.username +# +# depends_on = [github_team.all-members] +#} + +# This populates nerc-org-admins with organization owners +resource "github_team" "nerc-org-admins" { + name = "nerc-org-admins" + description = "Organization admins" + privacy = "closed" +} + +resource "github_team_membership" "nerc-org-admins" { + for_each = { + for member in csvdecode(file("members.csv")) : + member.username => member if member.role == "admin" + } + + team_id = "nerc-org-admins" + role = each.value.role == "admin" ? "maintainer" : "member" + username = each.value.username + + depends_on = [github_team.nerc-org-admins] +} diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..f39d64f --- /dev/null +++ b/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.9.0" + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +}