diff --git a/git-migration/gh_manage_ss_labels b/git-migration/gh_manage_ss_labels deleted file mode 100755 index 53b6e9eb..00000000 --- a/git-migration/gh_manage_ss_labels +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env bash - -# ---------------------------------------------------------------------------- -# (C) Crown copyright Met Office. All rights reserved. -# The file LICENCE, distributed with this code, contains details of the terms -# under which the code may be used. -# ---------------------------------------------------------------------------- - -# Script to create standard labels in multiple GitHub repositories -# Requires GitHub CLI: https://cli.github.com/ - -set -euo pipefail - -# -- Parse options -ADD_LABELS=true -DELETE_LABELS=false -usage() { - echo "Usage: $0 [--add|--delete] [--help|-h]" - echo "Manage labels in below repository - using this requires admin rights to the repositories" - echo " --add Add labels (default)" - echo " --delete Delete labels" - echo " --help,-h Show this help message" -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --add) - ADD_LABELS=true - DELETE_LABELS=false - shift - ;; - --delete) - ADD_LABELS=false - DELETE_LABELS=true - shift - ;; - --help|-h) - usage - exit 0 - ;; - *) - echo "Unknown option: $1" >&2 - usage - exit 1 - ;; - esac -done - -# -- Label Settings -# Format: "label_name|hex_color|description" -labels=( - "Linked UM|#50bfe6|This PR is linked to a MetOffice/um PR" - "Linked Jules|#66ff66|This PR is linked to a MetOffice/jules PR" - "Linked Apps|#ff6037|This PR is linked to a MetOffice/lfric_apps PR" - "Linked Core|#ffff66|This PR is linked to a MetOffice/lfric_core PR" - "Linked UKCA|#ff00cc|This PR is linked to a MetOffice/ukca PR" - "Linked Casim|#fd5b78|This PR is linked to a MetOffice/casim PR" - "Linked Socrates|#16d0cb|This PR is linked to a MetOffice/socrates PR" - "Linked Mule|#00ffff|This PR is linked to a MetOffice/mule PR" - "Linked Shumlib|#0080ff|This PR is linked to a MetOffice/shumlib PR" - "KGO|#9c27b0|This PR contains changes to KGO" - "macro|#aaf0d1|This PR contains a metadata upgrade macro" - "Accessibility|#1c96b9|Problems with the accessibility of the documentation" - "Discussion|#FBCA04|Issues that require some formal discussion" - "cla-required|#b60205|The CLA has not yet been signed by the author of this PR - added by GA" - "cla-signed|#0052cc|The CLA has been signed as part of this PR - added by GA" -) - -# -- Add labels in relevant repositories -repos=( - "MetOffice/um" - "MetOffice/jules" - "MetOffice/lfric_apps" - "MetOffice/lfric_core" - "MetOffice/ukca" - "MetOffice/casim" - "MetOffice/socrates" - "MetOffice/mule" - "MetOffice/shumlib" - "MetOffice/um_aux" - "MetOffice/um_doc" - "MetOffice/um_meta" - "MetOffice/socrates-spectral" - "MetOffice/moci" -) - -# -- Extract labels and colors - -for repo in "${repos[@]}"; do - echo "Processing labels in repository: $repo" - for label in "${labels[@]}"; do - name=$(echo "$label" | cut -d'|' -f1) - color=$(echo "$label" | cut -d'|' -f2) - description=$(echo "$label" | cut -d'|' -f3) - linked_repo=$(echo "$label" | grep -oP 'MetOffice/\w+' || true) - - [[ "$repo" == "$linked_repo" ]] && continue - - if [[ "$ADD_LABELS" == true ]]; then - gh label create "$name" \ - --color "$color" \ - --description "$description" \ - --force \ - --repo "$repo" - fi - if [[ "$DELETE_LABELS" == true ]]; then - gh label delete "$name" --yes --repo "$repo" || true - fi - done -done - -if [[ "$ADD_LABELS" == true ]]; then - echo "Labels added." -elif [[ "$DELETE_LABELS" == true ]]; then - echo "Labels deleted." -fi diff --git a/sbin/gh_add_user b/sbin/gh_add_user new file mode 100755 index 00000000..d24d66a1 --- /dev/null +++ b/sbin/gh_add_user @@ -0,0 +1,180 @@ +#!/usr/bin/env bash + +# ---------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ---------------------------------------------------------------------------- + +# Add collaborator to a repository +# gh api --method PUT /repos/:owner/:repo/collaborators/:username +# Requires: +# GitHub CLI (gh): https://cli.github.com/ +# Commandline JSON processor (jq): https://jqlang.org/ +# Admin privileges to the repos + +# WARNINGS: +# - This script modifies repository permissions. Use with caution. +# - Always verify the current permissions before making changes. +# - It also impacts API rate limits for the authenticated user. + +set -euo pipefail + +usage() { + cat < [-r ] [options] + -u GitHub username to add/remove + -r Repository name (if omitted, operate on all repos + from ../git-migration/config.json) + -o Repository owner (default: MetOffice) + -p Permission (read, write, admin, maintain, triage; + default: read) + -d Remove user as collaborator instead of adding + -n Print actions without making changes + -h, --help Show this help message + +Examples: + # Add/Remove a user to/from all repositories defined in config.json + ${0##*/} -u [-p ] + ${0##*/} -u -d + + # Add/Remove a user to/from a specific repository + ${0##*/} -u -r [-o ] [-p ] + ${0##*/} -u -r -o -d + +EOF + exit 1 +} + +# -- Defaults +PERMISSION="read" +OWNER="MetOffice" +DELETE=0 +CONFIG_JSON="$(dirname "${BASH_SOURCE[0]}")/../git-migration/config.json" +REPOS=() +DRY_RUN=0 + +# -- Helper functions +check_admin_permission() { + local repo_name="$1" + local repo_api="/repos/${OWNER}/${repo_name}/collaborators/${USERNAME}" + local permission + + # Returns 0 if user is admin, 1 otherwise + permission=$(gh api "${repo_api}/permission" --jq '.role_name' 2>/dev/null) + [[ "$permission" == "admin" ]] +} + +remove_collaborator() { + local repo_name="$1" + local repo_api="/repos/${OWNER}/${repo_name}/collaborators/${USERNAME}" + + if (( DRY_RUN )); then + echo "[DRY-RUN] Would remove '${USERNAME}' from ${OWNER}/${repo_name}." + else + gh api --method DELETE "$repo_api" && \ + echo "Removed '${USERNAME}' from ${OWNER}/${repo_name}." || \ + echo "Failed to remove '${USERNAME}' from ${OWNER}/${repo_name}." >&2 + fi +} + +add_collaborator() { + local repo_name="$1" + local repo_api="/repos/${OWNER}/${repo_name}/collaborators/${USERNAME}" + + if (( DRY_RUN )); then + echo "[DRY-RUN] Would add '${USERNAME}' to ${OWNER}/${repo_name} with" \ + "permission '$PERMISSION'." + else + # PUT method is idempotent: adds user if new, updates permission if exists + # HTTP 201 = created (new), 204 = updated (existing) + if gh api --method PUT "$repo_api" -f permission="$PERMISSION" \ + --silent 2>/dev/null; then + echo "Added/Updated '${USERNAME}' on ${OWNER}/${repo_name} with" \ + "permission '$PERMISSION'." + else + echo "Failed to add '${USERNAME}' to ${OWNER}/${repo_name}." >&2 + return 1 + fi + fi +} + +# -- Parse options +while getopts "u:r:o:p:dnh-:" opt; do + case $opt in + u) USERNAME="$OPTARG" ;; + r) REPO="$OPTARG" ;; + o) OWNER="$OPTARG" ;; + p) + case $OPTARG in + read|pull) PERMISSION="read" ;; + write|push) PERMISSION="write" ;; + admin|maintain|triage) PERMISSION="$OPTARG" ;; + *) echo "Invalid permission: $OPTARG"; usage ;; + esac + ;; + d) DELETE=1 ;; + n) DRY_RUN=1 ;; + h) usage ;; + -) [ "$OPTARG" = "help" ] && usage ;; + *) usage ;; + esac +done + +# -- Validate required args (username is mandatory) +if [ -z "${USERNAME:-}" ]; then + usage +fi + +# -- Populate REPOS array from config.json +# Only populate REPOS from config.json if no explicit -r repo provided +if [ -z "${REPO:-}" ]; then + if command -v jq >/dev/null 2>&1 && [ -f "$CONFIG_JSON" ]; then + mapfile -t REPOS < <(jq -r '.repo[].name' "$CONFIG_JSON") + else + REPOS=() + fi +fi + +# -- Determine target repos +TARGET_REPOS=() +if [ -n "${REPO:-}" ]; then + TARGET_REPOS=("$REPO") +else + # No repo provided: operate on all repos from config.json + if [ ${#REPOS[@]} -eq 0 ]; then + echo "No -r provided and repo list empty/unavailable." >&2 + echo "Make sure 'jq' is installed and config exists at: $CONFIG_JSON" >&2 + exit 1 + fi + TARGET_REPOS=("${REPOS[@]}") +fi + +# -- Get current user once if needed for self-removal check +CURRENT_USER="" +if (( DELETE && !DRY_RUN )); then + CURRENT_USER=$(gh api user --jq '.login' 2>/dev/null) +fi + +# -- Process each target repo +for repo_name in "${TARGET_REPOS[@]}"; do + # Prevent users from removing themselves + if [[ -n "$CURRENT_USER" ]] && [[ "$CURRENT_USER" == "$USERNAME" ]]; then + echo "WARNING: Won't remove (${USERNAME}) from ${OWNER}/${repo_name}" >&2 + echo "Skipping self-removal to prevent loss of your own access." >&2 + continue + fi + + # Check admin permission once before any operation (skip in dry-run mode) + if (( !DRY_RUN )) && check_admin_permission "$repo_name"; then + echo "WARNING: '${USERNAME}' has admin role on ${OWNER}/${repo_name}." >&2 + echo "Skipping to prevent modification of admin permissions." >&2 + continue + fi + + if (( DELETE )); then + remove_collaborator "$repo_name" + else + add_collaborator "$repo_name" + fi +done diff --git a/sbin/gh_manage_labels b/sbin/gh_manage_labels new file mode 100755 index 00000000..15d78b72 --- /dev/null +++ b/sbin/gh_manage_labels @@ -0,0 +1,111 @@ +#!/usr/bin/env bash + +# ---------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ---------------------------------------------------------------------------- + +# Script to create standard labels in multiple GitHub repositories +# Requires GitHub CLI: https://cli.github.com/ and Admin privileges to the repos + +set -euo pipefail + +# -- Parse options +ADD_LABELS=true +DELETE_LABELS=false +usage() { + echo "Usage: $0 [--add|--delete] [--help|-h]" + echo " --add Add labels (default)" + echo " --delete Delete labels" + echo " --help,-h Show this help message" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --add) + ADD_LABELS=true + DELETE_LABELS=false + shift ;; + --delete) + ADD_LABELS=false + DELETE_LABELS=true + shift ;; + --help|-h) + usage + exit 0 ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 ;; + esac +done + +# -- Label Settings +# Format: "label_name|hex_color|description" +labels=( + "Linked UM|#50bfe6|This PR is linked to a MetOffice/um PR" + "Linked JULES|#66ff66|This PR is linked to a MetOffice/jules PR" + "Linked Apps|#ff6037|This PR is linked to a MetOffice/lfric_apps PR" + "Linked Core|#ffff66|This PR is linked to a MetOffice/lfric_core PR" + "Linked UKCA|#ff00cc|This PR is linked to a MetOffice/ukca PR" + "Linked CASIM|#fd5b78|This PR is linked to a MetOffice/casim PR" + "Linked SOCRATES|#16d0cb|This PR is linked to a MetOffice/socrates PR" + "Linked Mule|#00ffff|This PR is linked to a MetOffice/mule PR" + "Linked Shumlib|#0080ff|This PR is linked to a MetOffice/shumlib PR" + "KGO|#9c27b0|This PR contains changes to KGO" + "macro|#aaf0d1|This PR contains a metadata upgrade macro" + "Accessibility|#1c96b9|Problems with the accessibility of the documentation" + "Discussion|#FBCA04|Issues that require some formal discussion" + "cla-required|#b60205|The CLA has not yet been signed by the author of this PR - added by GA" + "cla-signed|#0052cc|The CLA has been signed as part of this PR - added by GA" +) + +# -- Add labels in relevant repositories +repos=( + "MetOffice/um" + "MetOffice/jules" + "MetOffice/lfric_apps" + "MetOffice/lfric_core" + "MetOffice/ukca" + "MetOffice/casim" + "MetOffice/socrates" + "MetOffice/mule" + "MetOffice/shumlib" + "MetOffice/um_aux" + "MetOffice/um_doc" + "MetOffice/um_meta" + "MetOffice/socrates-spectral" + "MetOffice/moci" +) + +# -- Extract labels and colors + +for repo in "${repos[@]}"; do + echo "Processing labels in repository: $repo" + for label in "${labels[@]}"; do + name=$(echo "$label" | cut -d'|' -f1) + color=$(echo "$label" | cut -d'|' -f2) + description=$(echo "$label" | cut -d'|' -f3) + linked_repo=$(echo "$label" | grep -oP 'MetOffice/\w+' || true) + + [[ "$repo" == "$linked_repo" ]] && continue + + if [[ "$ADD_LABELS" == true ]]; then + gh label create "$name" \ + --color "$color" \ + --description "$description" \ + --force \ + --repo "$repo" + fi + if [[ "$DELETE_LABELS" == true ]]; then + gh label delete "$name" --yes --repo "$repo" || true + fi + done +done + +if [[ "$ADD_LABELS" == true ]]; then + echo "Labels added." +elif [[ "$DELETE_LABELS" == true ]]; then + echo "Labels deleted." +fi