Skip to content

Instantly share code, notes, and snippets.

@folex
Last active July 7, 2021 12:05
Show Gist options
  • Save folex/d12b49b7ad7b18aae817 to your computer and use it in GitHub Desktop.
Save folex/d12b49b7ad7b18aae817 to your computer and use it in GitHub Desktop.
Opens ssh connections to aws or digitalocean host which starts with $1. Save as `awslist` or `dolist` for AWS and DO respectively.
#!/bin/bash
name=""
REGION="eu-central-1"
LISTONLY=false
SHOULDFORWARD=false
OSNAME=`uname`
TITLE="Name PublicIP PrivateIP InstanceType InstanceId PlacementGroup Purpose"
case $(basename $0) in
awslist)
DIGITALOCEAN=false
AWS=true
USER=ubuntu
;;
dolist)
DIGITALOCEAN=true
AWS=false
USER=root
;;
esac
while getopts "n:L:r:f:o:lhpxDAu:" opt; do
case $opt in
#SSH port forwarding
L)
SHOULDFORWARD=true
if [[ "$OPTARG" =~ .*:.*:.* ]]; then
echo "OPTARG is $OPTARG"
IFS=\: read -a fields <<<"$OPTARG"
STARTPORT="${fields[0]}"
FORWARDHOST="${fields[1]}"
FORWARDPORT="${fields[2]}"
else
STARTPORT="$OPTARG"
FORWARDPORT="$OPTARG"
FORWARDHOST="localhost"
fi
;;
n) MAXCOUNT="$OPTARG" ;;
l) LISTONLY=true ;;
r) REGION="$OPTARG" ;;
h)
echo ' USAGE:
-nNUMBER number of instances to use
-LPORT:HOST:REMOTEPORT ssh style port forwarding (PORT incremented for multiple instances)
-l list only
-rREGION specify region (AWS only)
-h this help
-fFILENAME specify file with public ips
-oARGS additional ssh args, passed to ssh with -o
-p use Purpose tag for instance filtering (AWS only)
-x enables regex (AWS only)
-D query instances from DigitalOcean (doctl)
-A query instances from AWS (awscli)
-u SSH user
NOTE:
Script filename `awslist` will default to `-A -u ubuntu`.
Script filename `dolist` will default to `-D -u root`.
'
exit 0
;;
f) SOURCEFILE="$OPTARG" ;;
o) SSH_ARGS="$OPTARG" ;;
p) FILTER_BY_PURPOSE=true ;;
x) REGEX=true ;;
D) DIGITALOCEAN=true; AWS=false ;;
A) AWS=true; DIGITALOCEAN=false ;;
u) USER="$OPTARG" ;;
:) echo "Option -$OPTARG requires an argument"; exit 1 ;;
esac
done
shift $(expr $OPTIND - 1 )
name="$1"
function fetchAndFilter() {
# field we're selecting (filtering) on
FIELD="$1"
if [ -n "$2" ]; then
# query, string we're looking for
Q="\"$2\""
# P is the predicate we use for filtering
# 'test' is like 'contains' but with regex
[ "$REGEX" = "true" ] && P="test" || P="contains"
SELECT="select (opt($FIELD) | $P($Q)) |"
fi
aws ec2 describe-instances --region $REGION | jq -r "
def tag(f): .Tags | from_entries | f;
def get(f): . | f // empty;
def opt(f): . | f | tostring | select (length > 0) // \"-\";
flatten | .[].Instances[] | $SELECT
\"\(tag(.Name)) \(get(.PublicIpAddress)) \(.PrivateIpAddress) \(.InstanceType) \(.InstanceId) \(opt(.Placement | .GroupName)) \(tag(.Purpose))\"
"
}
function loadFromAws() {
if [[ -z `which aws` ]]; then
echo "aws cli tools should be installed! https://aws.amazon.com/cli/"
exit 0
fi
if [[ "$FILTER_BY_PURPOSE" == "true" ]]; then
nodes=$(fetchAndFilter "tag(.Purpose)" "$name")
else
nodes=$(fetchAndFilter "tag(.Name)" "$name")
fi
length=$(echo "$nodes" | grep . | wc -l)
}
function loadFromFile() {
length=$(wc -l $SOURCEFILE | awk '{print $1}')
nodes=$(cat $SOURCEFILE | awk '{
print $1 "|at|" $1
}')
}
function loadFromDigitalOcean() {
if [[ -z `which doctl` ]]; then
echo "doctl should be installed! https://github.com/digitalocean/doctl"
exit 0
fi
TITLE="Name PublicIPv4 VCPUs ID Region Tags"
nodes="$(doctl compute droplet list --format ${TITLE// /,} --no-header)"
if [[ -n $name ]]; then
nodes=$(echo "$nodes" | grep $name)
fi
length=$(echo "$nodes" | grep . | wc -l)
}
if [[ "$DIGITALOCEAN" == true ]]; then
loadFromDigitalOcean
else
if [[ ! -z $SOURCEFILE ]]; then
echo "Loading from file"
loadFromFile
else
loadFromAws
fi
fi
# yeah, single brackets, double brackets, I know
if [[ $length -gt 0 ]]; then
nodes=$(echo "$nodes" | sort)
if [[ -n $MAXCOUNT ]]; then
nodes=$(echo "$nodes" | head -n $MAXCOUNT)
length=$MAXCOUNT
fi
echo -e $TITLE"\n""$nodes" | column -t
if [[ "$LISTONLY" != true ]]; then
echo "Creating tmux session for $length nodes"
session=$(tmux new -dPs $name 2>/dev/null)
if [[ -z "${session// }" ]]; then
session=$(tmux new -dP)
fi
echo "$nodes" | tr "|" " " | \
awk \
-v SESSION=$session \
-v SHOULDFORWARD=$SHOULDFORWARD \
-v STARTPORT=$STARTPORT \
-v FORWARDHOST=$FORWARDHOST \
-v FORWARDPORT=$FORWARDPORT \
-v SSH_ARGS="$SSH_ARGS" \
-v OSNAME=$OSNAME \
-v USER=$USER '{
ip = $2
name = $1
sshcommand="\"ssh -o StrictHostKeyChecking=no " USER "@" ip
if (SHOULDFORWARD == "true") {
resultport=STARTPORT + NR - 1
sshcommand=sshcommand " -L" resultport ":" FORWARDHOST ":" FORWARDPORT
}
if (length(SSH_ARGS) > 0) {
sshcommand=sshcommand " -o " SSH_ARGS
}
sshcommand=sshcommand "\""
command="tmux split-window -t " SESSION " -v " sshcommand
print(command)
system(command)
title_command="tmux send-keys -t " SESSION " printf Space \\\"\\\\033]2\\;%s\\\\033\\\\\\\\\\\" Space \\\" " name "\\\" Enter"
system(title_command)
system("tmux select-layout -t " SESSION " tiled")
}'
tmux kill-pane -t $session"0.0"
tmux select-layout -t $session tiled
tmux attach
fi
else
echo "No matches"
fi
# ~/.tmux.conf
set -g mouse on
bind ` setw synchronize-panes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment