Skip to content

Instantly share code, notes, and snippets.

@josh-stillman
Last active December 3, 2020 21:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save josh-stillman/4dd0388c96868669ee7b6e123a9c37e5 to your computer and use it in GitHub Desktop.
Save josh-stillman/4dd0388c96868669ee7b6e123a9c37e5 to your computer and use it in GitHub Desktop.
GitLab Slackbot Script. Posts daily MRs to Slack to keep environments in sync. Blog here: https://medium.com/giant-machines/slackbot-to-the-rescue-keeping-gitlab-branches-in-sync-with-a-slackbot-ff84f80eaf5f
#!/usr/bin/env bash
# Function that will be called for each repo. Script exectuion begins below.
handle_repo () {
#1. Set up variables.
local REPO=$1 # first function argument
local PROJECT_ID=$2 # second function argument
local SOURCE_BRANCH_DISPLAY=${3:-"QA"} # default value
local TARGET_BRANCH_DISPLAY=${4:-"Dev"} # default value
# Use exact (lowercase) branch name for Gitlab API calls
local SOURCE_BRANCH=$(echo "$SOURCE_BRANCH_DISPLAY" | tr '[:upper:]' '[:lower:]' )
local TARGET_BRANCH=$(echo "$TARGET_BRANCH_DISPLAY" | tr '[:upper:]' '[:lower:]' )
local MR_TITLE="${SOURCE_BRANCH_DISPLAY} => ${TARGET_BRANCH_DISPLAY}"
local SLACKBOT_BRANCH="slackbot%2fqa-to-dev" # URL escaped for API calls
local SLACKBOT_BRANCH_DISPLAY="slackbot/qa-to-dev"
if [ -z "$REPO" ] || [ -z "$SOURCE_BRANCH" ] || [ -z "$TARGET_BRANCH" ] || [ -z "$PROJECT_ID" ]; then
echo "Missing environment variables: REPO: $REPO, SOURCE_BRANCH: $SOURCE_BRANCH, TARGET_BRANCH: $TARGET_BRANCH, PROJECT_ID: $PROJECT_ID"
return 1
fi
echo "Running handle_repo for $REPO: From $SOURCE_BRANCH to $TARGET_BRANCH for project $PROJECT_ID"
#2. Delete existing slackbot branch
DELETE_BRANCH_RESP=$(curl -X DELETE --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$PROJECT_ID/repository/branches/$SLACKBOT_BRANCH")
echo "delete branch resp is $DELETE_BRANCH_RESP"
#3. Create remote slackbot branch off of source branch
CREATE_BRANCH_RESP=$(curl -X POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$PROJECT_ID/repository/branches?branch=$SLACKBOT_BRANCH&ref=$SOURCE_BRANCH")
echo "create branch resp is $CREATE_BRANCH_RESP"
#4. Create merge request to target branch
CREATE_MR_RESP=$(curl -X POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$PROJECT_ID/merge_requests?source_branch=$SLACKBOT_BRANCH&target_branch=$TARGET_BRANCH&simple=true" --data-urlencode "title=$MR_TITLE")
echo "create MR resp is $CREATE_MR_RESP"
# Handle Gitlab occasionally retaining yesterday's unmerged MR after delete/create
ALREADY_EXISTS=$(echo "$CREATE_MR_RESP" | grep 'already exists')
# 5. Get MR ID
if [ -n "$ALREADY_EXISTS" ]; then
MR_ID="${CREATE_MR_RESP//[^0-9]/}" # extract numbers from response string
echo "MR already exists $MR_ID"
else
MR_ID=$(echo "$CREATE_MR_RESP" | python3 -c "import sys, json; print(json.load(sys.stdin)['iid'])")
fi
# 6. Get MR info from GitLab API
sleep 10 # Wait for GitLab to calculate merge conflicts info
MR_INFO_RESP=$(curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$PROJECT_ID/merge_requests/$MR_ID")
echo "MR info resp is $MR_INFO_RESP"
# 7. Get MR URL
URL=$(echo "$MR_INFO_RESP" | python3 -c "import sys, json; print(json.load(sys.stdin)['web_url'])")
# 8. Set initial slack message
MSG="$REPO: $MR_TITLE: $URL"
echo "New MR: $MSG"
# 9. Report on merge conflicts
CONFLICTS=$(echo "$MR_INFO_RESP" | python3 -c "import sys, json; print(json.load(sys.stdin)['has_conflicts'])")
if [ "$CONFLICTS" == 'True' ]; then
MSG="$MSG. *Merge Conflicts!* 🙀 _Pull down $SLACKBOT_BRANCH_DISPLAY, merge ${TARGET_BRANCH} into it locally, resolve conflicts, then push back up._ 😸"
fi
# 10. If MR is empty, close it
CHANGES=$(echo "$MR_INFO_RESP" | python3 -c "import sys, json; print(json.load(sys.stdin)['changes_count'])")
echo "changes count is $CHANGES"
if [ "$CHANGES" == "None" ]; then
echo "No changes, closing MR"
MSG="$REPO: $SOURCE_BRANCH_DISPLAY is synced with $TARGET_BRANCH_DISPLAY! 🎉"
curl -X PUT --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$PROJECT_ID/merge_requests/$MR_ID?state_event=close"
fi
# 11. Send to Slack
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$MSG\"}" "$SLACK_URL"
}
# Begin script execution here:
# Setup env vars. Located in the Gitlab project settings console. For running locally, use a .env file at project root and export the vars.
if [ -f "./.env" ]; then
source ./.env
echo "Importing env vars from .env file"
fi
if [ -z "$SLACK_URL" ] || [ -z "$GITLAB_TOKEN" ] || [ -z "$REPO_DISPLAY_NAMES" ]; then
echo "Missing environment Variables: SLACK_URL: $SLACK_URL, GITLAB_TOKEN: $GITLAB_TOKEN, REPO_DISPLAY_NAMES: $REPO_DISPLAY_NAMES"
exit 1
fi
# Create array from comma-separated string of repo names
IFS=', ' read -r -a REPO_ARRAY <<< "$REPO_DISPLAY_NAMES"
# Call function for each repo. For each repo name in the REPO_ARRAY, a matching variable with the GitLab project ID named with the repo name in all caps followed by _ID is required
for REPO in "${REPO_ARRAY[@]}"; do
REPO_ID="$(echo "$REPO" | tr '[:lower:]' '[:upper:]')_ID" # Capitalize repo display name and add "_ID" to get project id's variable name
handle_repo "$REPO" "${!REPO_ID}" # Indirect variable. REPO_ID's value is another variable's name, such as FRONTEND_ID, and we access that second variable's value.
# https://stackoverflow.com/questions/16553089/dynamic-variable-names-in-bash
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment