Last active
March 28, 2025 11:40
-
-
Save pyt0xic/fa24cbfbb462e36e7d447d762ce95194 to your computer and use it in GitHub Desktop.
This script creates a tunnel to a PostgreSQL database using socat from within a kube pod
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # This script creates a tunnel to a PostgreSQL database using socat from within a kube pod. | |
| # Will download socat binary if not found in the system. | |
| # Configuration variables | |
| REMOTE_HOST="$DB_HOSTNAME" | |
| LOCAL_PORT=5432 | |
| REMOTE_PORT=5432 | |
| SOCAT_PATH="$(command -v socat || echo '/tmp/socat')" | |
| SOCAT_URL="https://github.com/3ndG4me/socat/releases/download/v1.7.3.3/socatx64.bin" | |
| LONGOPTS="host:,local:,remote:,socat-path:,socat-url:,help" | |
| OPTIONS="H:l:r:p:u:h" | |
| set -o errexit -o pipefail -o noclobber -o nounset | |
| # Function to display usage | |
| print_usage() { | |
| echo "Usage: $0 [OPTIONS]" | |
| echo "Create a tunnel to a PostgreSQL database using socat." | |
| echo | |
| echo "Options:" | |
| echo " -H, --host HOST Database hostname (required if DB_HOSTNAME not set)" | |
| echo " -l, --local PORT Local port to listen on (default: 5432)" | |
| echo " -r, --remote PORT Remote port to connect to (default: 5432)" | |
| echo " -p, --socat-path PATH Path to socat binary (default: \$(command -v socat || echo '/tmp/socat'))" | |
| echo " -u, --socat-url URL Download URL for socat (default: https://github.com/3ndG4me/socat/releases/download/v1.7.3.3/socatx64.bin)" | |
| echo " -h, --help Display this help and exit" | |
| echo | |
| echo "Environment variables:" | |
| echo " DB_HOSTNAME Database hostname (used if --host not specified)" | |
| } | |
| # Function for logging | |
| log_message() { | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | |
| } | |
| cleanup() { | |
| log_message "Cleaning up" | |
| # Remove $SOCAT_PATH if it is a temporary file and exists | |
| [ -f "$SOCAT_PATH" ] && [ "$SOCAT_PATH" = "/tmp/socat" ] && rm -f "$SOCAT_PATH" | |
| } | |
| # Error handling function | |
| handle_error() { | |
| log_message "ERROR: $1" | |
| # Clean up if needed | |
| cleanup | |
| # print_usage | |
| exit 1 | |
| } | |
| # Parse args using new getopt from util-linux. | |
| parse_args() { | |
| # ignore errexit with `&& true` | |
| getopt --test >/dev/null && true | |
| if [[ $? -ne 4 ]]; then | |
| die 'Cannot parse args, "getopt --test" failed in this environment.' | |
| fi | |
| # -temporarily store output to be able to check for errors | |
| # -activate quoting/enhanced mode (e.g. by writing out “--options”) | |
| # -pass arguments only via -- "$@" to separate them correctly | |
| # -if getopt fails, it complains itself to stdout | |
| PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") || exit 2 | |
| # read getopt’s output this way to handle the quoting right: | |
| eval set -- "$PARSED" | |
| # Extract options and their arguments into variables. | |
| while true; do | |
| case "$1" in | |
| -H | --host) | |
| REMOTE_HOST="$2" | |
| shift 2 | |
| ;; | |
| -l | --local) | |
| LOCAL_PORT="$2" | |
| shift 2 | |
| ;; | |
| -r | --remote) | |
| REMOTE_PORT="$2" | |
| shift 2 | |
| ;; | |
| -p | --socat-path) | |
| SOCAT_PATH="$2" | |
| shift 2 | |
| ;; | |
| -u | --socat-url) | |
| SOCAT_URL="$2" | |
| shift 2 | |
| ;; | |
| -h | --help) | |
| print_usage | |
| exit 0 | |
| ;; | |
| --) | |
| shift | |
| break | |
| ;; | |
| *) | |
| handle_error "Unknown option: $1" | |
| ;; | |
| esac | |
| done | |
| } | |
| download_socat() { | |
| log_message "Downloading socat binary from $SOCAT_URL" | |
| curl -L "$SOCAT_URL" -o "$SOCAT_PATH" || handle_error "Failed to download socat" | |
| log_message "Making socat binary executable" | |
| chmod +x "$SOCAT_PATH" || handle_error "Failed to make socat executable" | |
| log_message "Using socat binary at $SOCAT_PATH" | |
| } | |
| ensure_socat_installed() { | |
| if [ -x "$SOCAT_PATH" ]; then | |
| log_message "Using socat binary at $SOCAT_PATH" | |
| else | |
| download_socat | |
| fi | |
| } | |
| print_kubectl_port_forward_command() { | |
| log_message "To forward the local port to a Kubernetes pod, run the following command:" | |
| echo -e "\t\tkubectl port-forward $(hostname) 54320:$LOCAL_PORT\n" | |
| } | |
| print_psql_command() { | |
| log_message "To connect to the database, run the following command:" | |
| echo -e "\t\tPGPASSWORD=$DB_PASSWORD psql -h localhost -p 54320 -U $DB_USERNAME $DB_DATABASE\n" | |
| } | |
| create_tunnel() { | |
| if [ -z "$REMOTE_HOST" ]; then | |
| handle_error "Database hostname not specified" | |
| fi | |
| log_message "Creating tunnel from localhost:$LOCAL_PORT to $REMOTE_HOST:$REMOTE_PORT" | |
| print_kubectl_port_forward_command | |
| print_psql_command | |
| trap 'echo "Tunnel closed"; [ "$SOCAT_PATH" = "/tmp/socat" ] && rm -f "$SOCAT_PATH"; exit 0' INT TERM | |
| "$SOCAT_PATH" tcp-listen:"$LOCAL_PORT",fork,reuseaddr tcp-connect:"$REMOTE_HOST":"$REMOTE_PORT" || handle_error "Tunnel failed" | |
| } | |
| parse_args "$@" | |
| ensure_socat_installed && create_tunnel |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment