ExportGitLabPublicSSHKeys

24th July 2017 at 11:08am
Bash CodeSnippets GitLab jq
#!/usr/bin/env bash

set -Euo pipefail
shopt -s extglob
shopt -s nocasematch
shopt -s extdebug

trap 'declare rc=$?;
      >&2 echo "Unexpected error executing $BASH_COMMAND at ${BASH_SOURCE[0]} line $LINENO";
      exit $rc' ERR

exec 9> "${BASH_SOURCE[0]%.sh}.$$.log"
{ # Print debug header to xtrace log file.
    printf "Command line: \"%q\"" "$0" ; printf " \"%q\"" "$@" ; printf "\n"
    printf "\$-: %s\n" "$-"
    printf "BASHOPTS: %s\n" "$BASHOPTS"
    printf "SHELLOPTS: %s\n" "$SHELLOPTS"
    printf "Start time: %(%Y%m%d %H%M%S %z)T\n" -2
    if type -P "git" >/dev/null 2>&1 && [[ -d "${BASH_SOURCE[0]%/*}/.git" ]]
    then
        printf "Git revision: %s\n" \
        "$(git -C "${BASH_SOURCE[0]%/*}" rev-parse --verify HEAD 2>&1)"
    fi
    printf "Hostname: %s\n" "${HOSTNAME:-$(hostname -f 2>&1)}"
    printf "\n" ; env ; printf "\n"
} >&9 2>&9
export BASH_XTRACEFD=9
export PS4='+ $$($BASHPID) +${SECONDS}s (${BASH_SOURCE[0]}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x

log_warn () {
  >&9 echo -e "$*"
  >&2 echo -e "$*"
}

log_error () {
  log_warn "[err]" "$@"
}

log_info () {
  >&9 echo -e "$*"
  echo -e "$*"
}

curl () {
  command curl --header "PRIVATE-TOKEN: $token" -Lk "$@" >&9 2>&9
  command curl --header "PRIVATE-TOKEN: $token" -sSLk "$@"
}

api () {
  if [[ "$*" =~ [[:space:]]page=[0-9]+ ]] ; then
    api_query "$@"
  else
    api_pages "$@"
  fi
}

api_query_url () {
  local IFS="&"
  echo "${gitlab%%/}/api/v3/$(echo "$*" | sed "s/&/?/")"
}

api_query () {
  declare query="$(api_query_url "$@")" rc=0
  curl "$query" || rc+=$?
  [[ $rc -eq 0 ]] || log_error "api_query to $query failed!"
  return $rc
}

api_pages () {
  declare query="$(api_query_url "$@")" rc=0
  curl -I "$query" | egrep "^X-Total-Pages:" | egrep -o "[0-9]+" || rc+=$?
  [[ $rc -eq 0 ]] || log_error "api_pages to $query failed!"
  return $rc
}

ssh_keys () {
  api "users/$1/keys" page=1 | jq -r '.[].key'
}

groups () {
  declare -i pages=1 p=1
  pages=$(api groups per_page=100 "$@") || pages=1
  for (( p=1; p<=pages; p++ )) ; do
    api groups per_page=100 page=$p "$@" | jq -r '.[].id'
  done
}

group_members () {
  declare -i pages=1 p=1
  pages=$(api "groups/$1/members" per_page=100) || pages=5
  for (( p=1; p<=pages; p++ )) ; do
    api "groups/$1/members" per_page=100 page=$p \
      | jq -r '.[] as $u|[$u.id|tostring,$u.username]|join(" ")'
  done
}

main () {
  declare token="${1:-}"
  declare gitlab="${2:-https://my.gitlab.local}"
  declare group_name="${3:-my_group}"

  # Validate command line arguments (token is the important one).
  if [[ -z "${token:-}" || -z "${gitlab:-}" || -z "${group_name:-}" ]] ; then
    log_warn "Syntax: ${0##*/} <access_token> [gitlab_url] [group]"
    exit 64
  fi

  # Discover group_id for the group_name we are interested in.
  declare group_id=""
  group_id=$(groups "search=$group_name")
  if [[ ! "$group_id" =~ ^[0-9]+$ ]] ; then
    log_error "group_id '$group_id' returned for group name search" \
        "'$group_name' is not a single integar as expected; try refining" \
        "search terms."
    exit 2
  fi
  log_info "Found group ID $group_id matches group name $group_name."

  # Create a temporary directory to write public SSH keys into.
  declare tmpdir="$(mktemp -d)"
  trap "rm -Rf '$tmpdir'" EXIT

  # Download the public SSH keys for everyone who is a memer of this group.
  declare -i total_members=0
  declare user_id="" username=""
  while read -r user_id username ; do
    total_members+=1
    log_info "[$total_members] Saving SSH public keys to" \
      "${tmpdir%/}/$username for $username (UID $user_id) ..."
    ssh_keys "$user_id" > "${tmpdir%/}/$username" || true
  done < <(group_members "$group_id")
  log_info "Found total of $total_members group members."

  # Create a tarball of all the public SSH keys we saved.
  log_info "Creating tarball archive $PWD/ssh_keys.tar.gz ..."
  tar -C "$(dirname "$tmpdir")" \
    -zcf "$PWD/ssh_keys.tar.gz" \
    "$(basename "$tmpdir")"
}

main "$@"