Skip to content

Instantly share code, notes, and snippets.

@andyneff
Last active March 14, 2019 21:17
Show Gist options
  • Save andyneff/26830a64793ea18f6363e5578dc5664f to your computer and use it in GitHub Desktop.
Save andyneff/26830a64793ea18f6363e5578dc5664f to your computer and use it in GitHub Desktop.
Like a virtual env for docker
#!/usr/bin/env bash
function _de_cleanup()
{
for f in ~/.ssh/docker_*; do
if ! \fuser "${f}" >& /dev/null; then
\rm "${f}"
fi
done
}
#**
# .. function:: de_activate
#
# Docker environment activate - connected to remote docker server using ssh tunneling. This does **not** employ the *insecure* method of exposing the docker daemon to an open TCP port.
#
# :Arguments: ``$1``... - Arguments to ``ssh`` command, machine name and options
#
# :Parameters: [``DE_OLDER``] - Set to 1 if using ssh older than 6.7. When you use this flag, the server needs to have ``socat`` installed.
#
# .. rubric:: Example
#
# .. code-block:: bash
#
# de_activate username@server -p 1234
#
# .. seealso::
# :func:`de_reverse_activate`
#**
function de_activate()
{
local docker_host
local ssh_args
if [ "$#" == "0" ]; then
echo "usage: $0 <ssh flags>"
echo " Set DE_OLDER to 1 when using openssh version before 6.7"
return 1
fi
_de_ssh_args=("${@}")
_OLD_DOCKER_HOST="${DOCKER_HOST-}"
ssh_args=(-o ControlPath=~/.ssh/%C -o ControlMaster=auto -o ControlPersist=yes)
_de_cleanup
if [ "${DE_OLDER-}" = "1" ]; then
ssh -n -L 2375:localhost:2375 "${@}" \
'socat TCP-LISTEN:2375,fork,bind=localhost UNIX-CONNECT:/var/run/docker.sock&
pid=$!
trap "kill $pid" 0
while printf \\0; do
sleep 5
done' &
docker_host="tcp://localhost:2375"
else
# Requires opensshd 6.7 or newer... THANKS CentOS! :(
_de_socket="$(mktemp -u -d ~/.ssh/docker_XXXXXXXX)"
ssh_args+=(-fTN -L "${_de_socket}":/var/run/docker.sock "${@}")
docker_host="unix://${_de_socket}"
if ssh -O check "${ssh_args[@]}" >&/dev/null; then
# I can't tell when a command fails. Oh well
ssh -o ServerAliveInterval=60 "${ssh_args[@]}"&
else
ssh -o ServerAliveInterval=60 "${ssh_args[@]}" || return $?
fi
fi
export DOCKER_HOST="${docker_host}"
#This will not always be correct, but oh well
VIRTUAL_ENV="de $1"
echo "VIRTUAL_ENV=\"${VIRTUAL_ENV}\""
echo "export DOCKER_HOST=\"${DOCKER_HOST}\""
function de_deactivate()
{
ssh -o ControlPath=~/.ssh/de_%C -O exit "${_de_ssh_args[@]}" # || return $?
if [ "${_OLD_DOCKER_HOST-}" == "" ]; then
unset DOCKER_HOST
else
export DOCKER_HOST="${_OLD_DOCKER_HOST}"
fi
# Newer way leaves a stray socket behind... which if you close without
# deactivating will result in a "socket" leak
if [ "${_de_socket+set}" == "set" ]; then
\rm "${_de_socket}"
unset _de_socket
fi
_de_cleanup
unset _de_ssh_args VIRTUAL_ENV
unset -f de_deactivate
}
}
#**
# .. function:: de_reverse_activate
#
# Not as good as :func:`de_activate`, but needed on systems where you can't directly ssh into a docker enabled user or for some reason on Synology this is needed too.
#
# :Arguments: [``$1``] - Name of computer connecting back on. Optional if 2... is only one argument, and defaults to $(hostname)
# ``$2``... - Arguments to ``ssh`` command, machine name and options
#
# :Parameters: [``CHANGE_USER``] - The ssh-ed user needs to ``sudo`` to another user, add that command using the ``CHANGE_USER`` environment variable. Typically something like ``sudo su - root -c``. The ``-c`` is needed as the return ``ssh`` command is passed in as one argument to the ``su`` command.
# .. rubric:: Example
#
# .. code-block:: bash
#
# de_reverse_activate my_username@my_hostname username@server -p 1234
# # or
# CHANGE_USER="sudo su - root -c" de_reverse_activate my_username@my_hostname username@server -p 1234
#
# .. seealso::
# :func:`de_reverse_activate`
#**
# Old way won't handle spaces in home directory, etc...
# function de_reverse_activate()
# {
# if [ "$#" = "1" ]; then
# de_reverse_activate $(hostname) "$1"
# fi
# local phone_home="${1}"
# shift 1
# local de_socket="$(mktemp -u -d ~/.ssh/docker_XXXXXXXX)"
# if [ -n "${CHANGE_USER+set}" ]; then
# ssh -t "${@}" ${CHANGE_USER} "'ssh -t -R \"${de_socket}:/var/run/docker.sock\" ${phone_home} env \"DOCKER_HOST=unix://${de_socket}\" bash'"
# else
# ssh -t "${@}" ssh -t -R "${de_socket}:/var/run/docker.sock" ${phone_home} env "DOCKER_HOST=unix://${de_socket}" bash
# fi
# }
function de_reverse_activate()
{
if [ "$#" = "1" ]; then
de_reverse_activate $(hostname) "$1"
fi
local phone_home="${1}"
local args=()
shift 1
local de_socket="$(mktemp -u -d ~/".ssh/docker_XXXXXXXX")"
args[0]="$(print_command ssh -t "${@}")"
args[1]="ssh -t -R '${de_socket}:/var/run/docker.sock' ${phone_home}"
args[2]="env 'DOCKER_HOST=unix://${de_socket}' 'DISPLAY=${DISPLAY}' bash -c"
args[3]="cd '$(pwd)'; exec bash"
if [ -n "${CHANGE_USER+set}" ]; then
if [ "${CHANGE_SINGLE-}" = "1" ]; then
args=("${args[0]}" "${CHANGE_USER}" "${args[@]:1}")
else
args[1]="${CHANGE_USER} ${args[1]}"
fi
fi
eval "$(quotemire "${args[@]}")"
}
#**
# .. function:: print_command
#
# :Arguments: ``$1``... - List of command + arguments to be echoed
# :Output: *stdout* - A quote escaped version of the command + arguments, ready to be ``eval``ed
#
# Accurately echoes out a properly escaped single string representation of a command + arguments.
#
# .. rubric:: Example
#
# .. code-block:: bash
#
# print_command this is a t\ e\ \ s\'\"t
#
# # Results in
# this is a 't e s'"'"'"t'
#
# # Typical usage
# eval "$(print_command "${stuff[@]}")"
# or
# bash -c "$(print_command "${stuff[@]}")"
#**
function print_command()
{
while [ "$#" -gt 0 ]; do
# if [[ ${1} =~ ^[a-zA-Z0-9_.:/=-]*$ ]]; then
# https://unix.stackexchange.com/a/357932/123413
if [[ ${1} =~ ^[a-zA-Z0-9_.:/=@%^,+-]*$ ]]; then
printf -- "${1}"
else
printf "'${1//\'/\'\"\'\"\'}'"
fi
shift 1
[ "$#" -gt 0 ] && printf " "
done
printf "\n"
}
#**
# .. function:: quotemire
#
# :Arguments: ``$1``... - List of commands in the chain
# :Output: *stdout* - The final conglomeration of all the arguments together, ready to be eval'ed
#
# When you have to execute a command that calls another command, that calls another command, where to put the quotes can get out of hand after the 3rd or 4th iteration.
#
# .. rubric:: Problem Example
#
# .. code-block:: bash
#
# ssh server "su - ben -c 'bash -c \"ls -la '\''/tmp/foo bar'\''\"'"
#
# :func:`quotemire` allows you to instead handle this as this chain of commands as an array of strings, and then it programatically combines it into one long command
#
# .. rubric:: Example
#
# .. code-block:: bash
# args=()
# args[0]="ssh server"
# args[1]="su - ben -c"
# args[2]="bash -c"
# args[3]="ls -la"
# args[4]="/tmp/foo bar"
#
# # Or if you want to combine args[3] and args[4]
# # args[3]="ls -la '/tmp/foo bar'"
#
# eval "$(quotemire "${args[@]}")"
# # or
# bash -c "$(quotemire "${args[@]}")"
#
# This chain quoting is only an issue for commands like ``su -c``, ``bash -c`` and ``ssh`` (because ssh does not handle command arguments correctly). However other commands like ``env``, ``sudo``, etc... do not need this quotation, so should not be made separate arguments:
#
# .. rubric:: Mixed Example
#
# args=()
# args[0]="$(print_command ssh -t "${@}")"
# args[1]="ssh -t -R '${de_socket}:/var/run/docker.sock' ${phone_home}"
# args[2]="env 'DOCKER_HOST=unix://${de_socket}' 'DISPLAY=${DISPLAY}' bash -c"
# args[3]="cd '$(pwd)'; exec bash"
#
# In this example, the ``env`` and ``bash`` are on the same argument, because env does handle multiple arguments for a command correctly
#**
function quotemire()
{
local args
if [ "$#" -gt 0 ]; then
args="${!#}"
fi
local x
for (( x=$#-2; x>=0; x-- )); do
args="${@:$x+1:1} $(print_command "${args}")"
done
echo "${args}"
}
#**
# .. function:: dcp2v
#
# Docker copy files to a docker volume
#
# :Arguments: ``$1`` - Volume name
# ``$2``... - Filenames
# .. seealso::
# :func:`dcpfv`
#**
function dcp2v()
{
local volume="$1"
shift 1
tar zc "${@}" | docker run -i --rm -v "${volume}":/cp -w /cp alpine tar zx
}
#**
# .. function:: dcpfv
#
# Copy files from a docker volume
#
# :Arguments: ``$1`` - Volume name
# ``$2``... - Filenames
#
# .. note::
#
# Does not handle spaces correctly. See :func:`dcpfv2`
#
# .. seealso::
# :func:`dcpfv2`
#**
function dcpfv()
{
local volume="$1"
shift 1
local args=("${@}")
docker run --rm -e IFS=t \
-v "${volume}":/cp:ro -w /cp \
debian bash -c "$(declare -p args);"' eval tar zc "${args[@]}"' | tar zx
}
#**
# .. function:: dcpfv2
#
# Version of :func:`dcpfv` that supports spaces
#
# :Arguments: ``$1`` - Volume name
# ``$2``... - Filenames
# .. seealso::
# :func:`dcpfv`
#**
function dcpfv2()
{
local volume="$1"
shift 1
local args=("${@}")
docker run --rm -e IFS=$'\t' \
-v "${volume}":/cp:ro -w /cp \
debian bash -c "$(declare -p args);"' tar zc "${args[@]}"' | tar zx
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment