bytelyst-devops-tools/remove_user_interactive.sh
Saravana Dhandapani 6a6bfb5d12 feat: add interactive user removal tool with wildcard matching
- Add remove_user_interactive.sh with rich visual logging and progress tracking
- Support wildcard patterns for repository matching (*-go-api*, frontend-*, etc.)
- Handle both root account and organization repositories
- Implement flexible confirmation system (yes/no/all/skip/quit)
- Add comprehensive documentation and usage examples
- Update README.md with tool overview and usage instructions
- Create CLAUDE.md for repository guidance
- Add detailed documentation in docs/remove_user_interactive.md

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-04 12:49:05 -07:00

416 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Interactive User Removal Tool for GitHub Repositories
# Supports wildcard matching and handles both user and organization repositories
set -euo pipefail
# Color definitions
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly PURPLE='\033[0;35m'
readonly BOLD='\033[1m'
readonly NC='\033[0m' # No Color
# Global variables
GITHUB_TOKEN=""
ROOT_USER=""
USER_TO_REMOVE=""
REPO_PATTERN=""
CONFIRMATION_MODE="ask" # ask, yes_all, no_all
TOTAL_REPOS=0
PROCESSED_REPOS=0
SUCCESSFUL_REMOVALS=0
FAILED_REMOVALS=0
SKIPPED_REMOVALS=0
# Required tools
REQUIRED_TOOLS=(curl jq)
# Utility functions
log_info() {
echo -e "${BLUE} $1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
log_progress() {
echo -e "${CYAN}🔄 $1${NC}"
}
log_header() {
echo -e "\n${BOLD}${PURPLE}$1${NC}"
echo -e "${PURPLE}$(printf '=%.0s' {1..50})${NC}"
}
# Check required tools
check_dependencies() {
log_header "Checking Dependencies"
for tool in "${REQUIRED_TOOLS[@]}"; do
if ! command -v "$tool" &>/dev/null; then
log_error "Required tool '$tool' is not installed. Please install it and try again."
exit 1
fi
log_success "$tool is available"
done
}
# Input collection functions
collect_github_token() {
log_header "GitHub Authentication"
echo -e "${YELLOW}Enter your GitHub Personal Access Token:${NC}"
echo -e "${CYAN}(Token needs 'repo' and 'admin:org' permissions)${NC}"
read -s -r -p "Token: " GITHUB_TOKEN
echo
if [[ -z "$GITHUB_TOKEN" ]]; then
log_error "GitHub token cannot be empty"
exit 1
fi
# Validate token
log_progress "Validating GitHub token..."
local response
response=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user)
local http_code="${response: -3}"
if [[ "$http_code" != "200" ]]; then
log_error "Invalid GitHub token or insufficient permissions"
exit 1
fi
log_success "GitHub token validated successfully"
}
collect_root_user() {
log_header "Root Account Information"
echo -e "${YELLOW}Enter the root GitHub username/organization:${NC}"
read -r -p "Root user: " ROOT_USER
if [[ -z "$ROOT_USER" ]]; then
log_error "Root user cannot be empty"
exit 1
fi
log_success "Root user set to: $ROOT_USER"
}
collect_user_to_remove() {
log_header "User Removal Target"
echo -e "${YELLOW}Enter the username to remove from repositories:${NC}"
read -r -p "User to remove: " USER_TO_REMOVE
if [[ -z "$USER_TO_REMOVE" ]]; then
log_error "User to remove cannot be empty"
exit 1
fi
log_success "Target user: $USER_TO_REMOVE"
}
collect_repo_pattern() {
log_header "Repository Pattern"
echo -e "${YELLOW}Enter repository pattern (supports wildcards):${NC}"
echo -e "${CYAN}Examples: *-go-api*, frontend-*, *-service, specific-repo${NC}"
read -r -p "Repository pattern: " REPO_PATTERN
if [[ -z "$REPO_PATTERN" ]]; then
log_error "Repository pattern cannot be empty"
exit 1
fi
log_success "Repository pattern: $REPO_PATTERN"
}
# GitHub API functions
fetch_user_repos() {
local user="$1"
local page=1
local all_repos=()
log_progress "Fetching repositories for user: $user"
while true; do
local response
response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/users/$user/repos?per_page=100&page=$page&type=all")
local repos
repos=$(echo "$response" | jq -r '.[].full_name // empty')
if [[ -z "$repos" ]]; then
break
fi
while IFS= read -r repo; do
all_repos+=("$repo")
done <<< "$repos"
((page++))
done
printf '%s\n' "${all_repos[@]}"
}
fetch_org_repos() {
local org="$1"
local page=1
local all_repos=()
log_progress "Fetching repositories for organization: $org"
while true; do
local response
response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/orgs/$org/repos?per_page=100&page=$page&type=all")
local repos
repos=$(echo "$response" | jq -r '.[].full_name // empty')
if [[ -z "$repos" ]]; then
break
fi
while IFS= read -r repo; do
all_repos+=("$repo")
done <<< "$repos"
((page++))
done
printf '%s\n' "${all_repos[@]}"
}
fetch_user_orgs() {
local user="$1"
log_progress "Fetching organizations for user: $user"
local response
response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/users/$user/orgs")
echo "$response" | jq -r '.[].login // empty'
}
# Repository matching function
match_repo_pattern() {
local repo_name="$1"
local pattern="$2"
# Convert shell wildcard pattern to regex
local regex_pattern
regex_pattern="${pattern//\*/.*}"
if [[ "$repo_name" =~ $regex_pattern ]]; then
return 0
else
return 1
fi
}
# User confirmation functions
get_confirmation() {
local repo="$1"
local current="$2"
local total="$3"
case "$CONFIRMATION_MODE" in
"yes_all")
return 0
;;
"no_all")
return 1
;;
"ask")
echo -e "\n${YELLOW}[$current/$total] Remove user '$USER_TO_REMOVE' from '$repo'?${NC}"
echo -e "${CYAN}Options: [y]es, [n]o, [a]ll yes, [q]uit, [s]kip all${NC}"
read -p "Choice: " -n 1 -r choice
echo
case "$choice" in
y|Y)
return 0
;;
n|N)
return 1
;;
a|A)
CONFIRMATION_MODE="yes_all"
log_info "Switched to 'yes to all' mode"
return 0
;;
s|S)
CONFIRMATION_MODE="no_all"
log_info "Switched to 'skip all' mode"
return 1
;;
q|Q)
log_warning "Operation cancelled by user"
exit 0
;;
*)
log_warning "Invalid choice, skipping..."
return 1
;;
esac
;;
esac
}
# User removal function
remove_user_from_repo() {
local repo="$1"
log_progress "Removing $USER_TO_REMOVE from $repo..."
local response
response=$(curl -s -w "%{http_code}" -X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$repo/collaborators/$USER_TO_REMOVE")
local http_code="${response: -3}"
case "$http_code" in
204)
log_success "Successfully removed $USER_TO_REMOVE from $repo"
((SUCCESSFUL_REMOVALS++))
return 0
;;
404)
log_warning "$USER_TO_REMOVE is not a collaborator on $repo or repo not found"
((SKIPPED_REMOVALS++))
return 1
;;
*)
log_error "Failed to remove $USER_TO_REMOVE from $repo (HTTP: $http_code)"
((FAILED_REMOVALS++))
return 1
;;
esac
}
# Progress display
show_progress() {
local current="$1"
local total="$2"
local percentage=$((current * 100 / total))
printf "\r${CYAN}Progress: [%3d%%] %d/%d repositories processed${NC}" "$percentage" "$current" "$total"
}
# Summary report
show_summary() {
log_header "Operation Summary"
echo -e "${BOLD}Total repositories processed: $PROCESSED_REPOS${NC}"
echo -e "${GREEN}Successful removals: $SUCCESSFUL_REMOVALS${NC}"
echo -e "${RED}Failed removals: $FAILED_REMOVALS${NC}"
echo -e "${YELLOW}Skipped removals: $SKIPPED_REMOVALS${NC}"
echo -e "${CYAN}Success rate: $(( SUCCESSFUL_REMOVALS * 100 / (SUCCESSFUL_REMOVALS + FAILED_REMOVALS + SKIPPED_REMOVALS) ))%${NC}"
}
# Main execution function
main() {
log_header "Interactive GitHub User Removal Tool"
# Check dependencies
check_dependencies
# Collect inputs
collect_github_token
collect_root_user
collect_user_to_remove
collect_repo_pattern
# Collect all repositories
log_header "Repository Discovery"
local all_repos=()
# Fetch root user repositories
while IFS= read -r repo; do
[[ -n "$repo" ]] && all_repos+=("$repo")
done < <(fetch_user_repos "$ROOT_USER")
# Fetch organization repositories
while IFS= read -r org; do
if [[ -n "$org" ]]; then
log_info "Found organization: $org"
while IFS= read -r repo; do
[[ -n "$repo" ]] && all_repos+=("$repo")
done < <(fetch_org_repos "$org")
fi
done < <(fetch_user_orgs "$ROOT_USER")
# Filter repositories by pattern
log_header "Repository Filtering"
local matching_repos=()
for repo in "${all_repos[@]}"; do
local repo_name
repo_name=$(basename "$repo")
if match_repo_pattern "$repo_name" "$REPO_PATTERN"; then
matching_repos+=("$repo")
log_info "Matched: $repo"
fi
done
TOTAL_REPOS=${#matching_repos[@]}
if [[ $TOTAL_REPOS -eq 0 ]]; then
log_warning "No repositories match the pattern '$REPO_PATTERN'"
exit 0
fi
log_success "Found $TOTAL_REPOS matching repositories"
# Confirmation before proceeding
echo -e "\n${YELLOW}About to remove user '$USER_TO_REMOVE' from $TOTAL_REPOS repositories matching '$REPO_PATTERN'${NC}"
read -p "Continue? [y/N]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_warning "Operation cancelled"
exit 0
fi
# Process repositories
log_header "User Removal Process"
for repo in "${matching_repos[@]}"; do
((PROCESSED_REPOS++))
show_progress "$PROCESSED_REPOS" "$TOTAL_REPOS"
if get_confirmation "$repo" "$PROCESSED_REPOS" "$TOTAL_REPOS"; then
remove_user_from_repo "$repo"
else
log_info "Skipped: $repo"
((SKIPPED_REMOVALS++))
fi
done
echo # New line after progress
# Show final summary
show_summary
log_success "Operation completed successfully!"
}
# Script execution
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi