Skip to content

Instantly share code, notes, and snippets.

@eliashaeussler
Last active February 28, 2025 14:37
Show Gist options
  • Save eliashaeussler/0fe48b07a380a137d4e1d751582a8196 to your computer and use it in GitHub Desktop.
Save eliashaeussler/0fe48b07a380a137d4e1d751582a8196 to your computer and use it in GitHub Desktop.
Bulk archiving of projects of a GitLab group

About

This script enables the mass archiving of all projects in a GitLab group. Projects from subgroups are also taken into account.

Requirements

Usage

./archive_gitlab_group_projects.sh <groupId> [<apiToken>] [<gitlabHost>] [<dryRun>] [<limit>]

Arguments:
  groupId     GitLab group ID (can be found in group settings) or slug
  apiToken    GitLab access token for API (needs at least "api" privileges); env: $API_TOKEN
  gitlabHost  Hostname of the GitLab instance, defaults to gitlab.com; env: $GITLAB_HOST
  dryRun      Can be used to enable (0, default) or disable (1) write operations; env: $DRY_RUN
  limit       Define how many projects should be archived in one iteration (default: 50, max: 100); env: $LIMIT

Example

export API_TOKEN="xxx"
export GITLAB_HOST="gitlab.example.com"

# Enable to run script in dry-run mode
#export DRY_RUN=1

# Enable to set custom limit
#export LIMIT=100

# Archive projects by group id
./archive_gitlab_group_projects.sh 246

# Archive projects by group slug
./archive_gitlab_group_projects.sh foo/bar/baz
#!/usr/bin/env bash
# shellcheck disable=SC2155
set -e
RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[0;33m"
CYAN="\033[1;36m"
GRAY="\033[0;90m"
NC="\033[0m"
readonly groupId="$1"
readonly apiToken="${2:-$API_TOKEN}"
readonly gitlabHost="${3:-${GITLAB_HOST:-gitlab.com}}"
readonly dryRun="${4:-${DRY_RUN:-0}}"
readonly limit="${5:-${LIMIT:-50}}"
function _usage() {
echo
echo -e "Usage:"
echo -e " ${CYAN}$0${NC} <groupId> [<apiToken>] [<gitlabHost>] [<dryRun>] [<limit>]"
echo
echo -e "Arguments:"
echo -e " ${GREEN}groupId${NC} GitLab group ID (can be found in group settings) or slug"
echo -e " ${GREEN}apiToken${NC} GitLab access token for API (needs at least \"api\" privileges); env: \$API_TOKEN"
echo -e " ${GREEN}gitlabHost${NC} Hostname of the GitLab instance, defaults to gitlab.com; env: \$GITLAB_HOST"
echo -e " ${GREEN}dryRun${NC} Can be used to enable (0, default) or disable (1) write operations; env: \$DRY_RUN"
echo -e " ${GREEN}limit${NC} Define how many projects should be archived in one iteration (default: 50, max: 100); env: \$LIMIT"
echo
}
# Validate arguments
if [ -z "$groupId" ]; then
>&2 echo -e "${RED}Error: No group id given.${NC}"
_usage
exit 1
fi
if [ -z "$apiToken" ]; then
>&2 echo -e "${RED}Error: No API token given.${NC}"
_usage
exit 1
fi
if [ -z "$gitlabHost" ]; then
>&2 echo -e "${RED}Error: No GitLab hostname given.${NC}"
_usage
exit 1
fi
if [ "$limit" -le 0 ]; then
>&2 echo -e "${RED}Error: Limit must be at least 1.${NC}"
_usage
exit 1
elif [ "$limit" -gt 100 ]; then
>&2 echo -e "${RED}Error: Limit must not be higher than 100.${NC}"
_usage
exit 1
fi
# Validate required libraries
if ! which jq >/dev/null; then
>&2 echo -e "${RED}Error: \`jq\` is not installed.${NC}"
exit 1
fi
if ! which curl >/dev/null; then
>&2 echo -e "${RED}Error: \`curl\` is not installed.${NC}"
exit 1
fi
# Check dry-run mode
if [ "$dryRun" -ne 0 ]; then
echo -e "${CYAN}No API changes will be performed (dry-run).${NC}"
fi
printf "Fetching up to %d projects... " "$limit"
# Fetch non-archived projects
readonly sanitizedGroupId="$(printf %s "$groupId" | jq -sRr @uri)"
readonly projects=$(curl -s "https://${gitlabHost}/api/v4/groups/${sanitizedGroupId}/projects?include_subgroups=true&archived=false&per_page=${limit}" -H "PRIVATE-TOKEN: ${apiToken}" | jq -cr 'if type == "array" then .[] | {id, web_url} elif has("message") then {message} elif has("error_description") then {message: .error_description} else {message: .error} end')
# Early return if no projects were found
if [ -z "$projects" ]; then
echo -e "${GREEN}Done${NC}"
echo -e "${GREEN}No active projects found in group.${NC}"
exit
fi
# Early return on API error
error="$(echo "$projects" | jq -r '.message // empty')"
if [ -n "$error" ]; then
echo -e "${RED}Failed${NC}"
>&2 echo -e "${RED}API error: ${error}${NC}"
exit 1
fi
echo -e "${GREEN}Done${NC}"
counter=0
# Archive projects
for project in $projects; do
(( counter += 1 ))
projectId="$(echo "$project" | jq -r '.id')"
projectUrl="$(echo "$project" | jq -r '.web_url')"
printf "${GRAY}[#%d]${NC} Archiving project ${YELLOW}%s${NC} (%s)... " "$counter" "$projectId" "$projectUrl"
if [ "$dryRun" -eq 0 ]; then
response="$(curl -s -X POST "https://${gitlabHost}/api/v4/projects/${projectId}/archive" -H "PRIVATE-TOKEN: ${apiToken}" | jq -r 'if has("error_description") then .error_description else empty end')"
if [ -n "$response" ]; then
echo -e "${RED}Failed${NC}"
>&2 echo -e "${RED}API error: ${response}${NC}"
else
echo -e "${GREEN}Done${NC}"
fi
else
echo -e "${YELLOW}Skipped${NC}"
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment