213 lines
7.1 KiB
Plaintext
213 lines
7.1 KiB
Plaintext
|
|
#!/bin/sh
|
||
|
|
|
||
|
|
set -eu
|
||
|
|
|
||
|
|
VERSION="0.0.1-20250509"
|
||
|
|
PROXIFIER_DIR="${HOME}/.freeleaps/proxifier"
|
||
|
|
|
||
|
|
help() {
|
||
|
|
echo "Freeleaps Cluster Proxifier (Version: ${VERSION})"
|
||
|
|
echo ""
|
||
|
|
echo "This script helps you to forward Kubernetes service ports to your local machine."
|
||
|
|
echo "It maintains the forwarding state and provides commands to manage port forwarding."
|
||
|
|
echo ""
|
||
|
|
echo "Usage: freeleaps-cluster-proxifier <sub-command>"
|
||
|
|
echo ""
|
||
|
|
echo "Sub Commands:"
|
||
|
|
echo " forward,-f,--forward <namespace>/<service> -p <local-port>:<service-port> Forward a service port to local"
|
||
|
|
echo " stop,-s,--stop <namespace>/<service> Stop forwarding a service"
|
||
|
|
echo " list,-l,--list List all forwarded services"
|
||
|
|
echo " list-available,-la,--list-available List all available services"
|
||
|
|
echo " help,-h,--help Show this help message"
|
||
|
|
}
|
||
|
|
|
||
|
|
ensure_proxifier_dir() {
|
||
|
|
if [ ! -d "${PROXIFIER_DIR}" ]; then
|
||
|
|
mkdir -p "${PROXIFIER_DIR}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
get_process_file() {
|
||
|
|
namespace="$1"
|
||
|
|
service="$2"
|
||
|
|
echo "${PROXIFIER_DIR}/${namespace}-${service}.pid"
|
||
|
|
}
|
||
|
|
|
||
|
|
forward_port() {
|
||
|
|
if [ $# -lt 1 ]; then
|
||
|
|
echo "[ERROR] Invalid number of arguments for forward command"
|
||
|
|
echo "[TIP] Usage: freeleaps-cluster-proxifier forward <namespace>/<service> -p <local-port>:<service-port>"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Parse namespace/service
|
||
|
|
IFS='/' read -r namespace service <<EOF
|
||
|
|
$1
|
||
|
|
EOF
|
||
|
|
|
||
|
|
if [ -z "${namespace}" ] || [ -z "${service}" ]; then
|
||
|
|
echo "[ERROR] Invalid format. Use namespace/service"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Parse port mapping
|
||
|
|
if [ "$2" != "-p" ] || [ -z "$3" ]; then
|
||
|
|
echo "[ERROR] Invalid port format. Use -p <local-port>:<service-port>"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
ports="$3"
|
||
|
|
|
||
|
|
# Validate service exists and user has permissions
|
||
|
|
if ! kubectl get svc "${service}" -n "${namespace}" >/dev/null 2>&1; then
|
||
|
|
if kubectl get namespace "${namespace}" >/dev/null 2>&1; then
|
||
|
|
echo "[ERROR] Either the service '${service}' doesn't exist in namespace '${namespace}' or you don't have permission to access it"
|
||
|
|
echo "[TIP] Please contact your cluster administrator to request access to this service"
|
||
|
|
else
|
||
|
|
echo "[ERROR] Namespace '${namespace}' doesn't exist or you don't have permission to access it"
|
||
|
|
echo "[TIP] Please contact your cluster administrator to request access to this namespace"
|
||
|
|
fi
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
process_file=$(get_process_file "${namespace}" "${service}")
|
||
|
|
|
||
|
|
if [ -f "${process_file}" ]; then
|
||
|
|
echo "[ERROR] Service ${service} in namespace ${namespace} is already being forwarded"
|
||
|
|
echo "[TIP] Use 'freeleaps-cluster-proxifier list' to see active forwards"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
ensure_proxifier_dir
|
||
|
|
|
||
|
|
echo "[FORWARD] Starting port forward for ${service} in namespace ${namespace}..."
|
||
|
|
kubectl port-forward -n "${namespace}" "svc/${service}" "${ports}" > /dev/null 2>&1 &
|
||
|
|
pid=$!
|
||
|
|
|
||
|
|
# Store PID and port mapping
|
||
|
|
echo "${pid}:${ports}" > "${process_file}"
|
||
|
|
|
||
|
|
echo "[FORWARD] Port forward started successfully"
|
||
|
|
echo "[INFO] Service ${service}.${namespace} is now mapping with ${ports}"
|
||
|
|
}
|
||
|
|
|
||
|
|
stop_forward() {
|
||
|
|
if [ $# -ne 1 ]; then
|
||
|
|
echo "[ERROR] Invalid number of arguments for stop command"
|
||
|
|
echo "[TIP] Usage: freeleaps-cluster-proxifier stop <namespace>/<service>"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Parse namespace/service
|
||
|
|
IFS='/' read -r namespace service <<EOF
|
||
|
|
$1
|
||
|
|
EOF
|
||
|
|
|
||
|
|
if [ -z "${namespace}" ] || [ -z "${service}" ]; then
|
||
|
|
echo "[ERROR] Invalid format. Use namespace/service"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
process_file=$(get_process_file "${namespace}" "${service}")
|
||
|
|
|
||
|
|
if [ ! -f "${process_file}" ]; then
|
||
|
|
echo "[ERROR] No active forward found for service ${service} in namespace ${namespace}"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
pid=$(cat "${process_file}" | cut -d: -f1)
|
||
|
|
if kill "${pid}" >/dev/null 2>&1; then
|
||
|
|
rm "${process_file}"
|
||
|
|
echo "[STOP] Stopped forwarding service ${service} in namespace ${namespace}"
|
||
|
|
else
|
||
|
|
echo "[WARNING] Process not found, cleaning up state file"
|
||
|
|
rm "${process_file}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
list_forwards() {
|
||
|
|
ensure_proxifier_dir
|
||
|
|
|
||
|
|
echo "Belows are all active port forwards:"
|
||
|
|
printf "%-30s %-60s %-15s %-10s\n" "Namespace" "Service" "Port Mapping" "PID"
|
||
|
|
|
||
|
|
for file in "${PROXIFIER_DIR}"/*.pid; do
|
||
|
|
if [ -f "${file}" ]; then
|
||
|
|
name=$(basename "${file}" .pid)
|
||
|
|
namespace=$(echo "${name}" | cut -d'-' -f1)
|
||
|
|
service=$(echo "${name}" | cut -d'-' -f2-)
|
||
|
|
data=$(cat "${file}")
|
||
|
|
pid=$(echo "${data}" | cut -d: -f1)
|
||
|
|
ports=$(echo "${data}" | cut -d: -f2-)
|
||
|
|
|
||
|
|
# Check if process is still running
|
||
|
|
if kill -0 "${pid}" >/dev/null 2>&1; then
|
||
|
|
printf "%-30s %-60s %-15s %-10s\n" "${namespace}" "${service}" "${ports}" "${pid}"
|
||
|
|
else
|
||
|
|
echo "[WARNING] Cleaning up stale forward for ${service} in namespace ${namespace}"
|
||
|
|
rm "${file}"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
list_available_services() {
|
||
|
|
echo "Belows are all available services that you can forward:"
|
||
|
|
printf "%-30s %-60s %-10s\n" "Namespace" "Service" "Ports"
|
||
|
|
|
||
|
|
# Get all namespaces user has access to
|
||
|
|
kubectl get namespaces -o name | cut -d'/' -f2 | while read -r ns; do
|
||
|
|
# Get services in each namespace
|
||
|
|
if kubectl auth can-i get services -n "${ns}" >/dev/null 2>&1; then
|
||
|
|
kubectl get services -n "${ns}" \
|
||
|
|
--no-headers \
|
||
|
|
-o custom-columns="Namespace:.metadata.namespace,Service:.metadata.name,Ports:.spec.ports[*].port" | \
|
||
|
|
while read -r line; do
|
||
|
|
# Only show if user has permission to port-forward
|
||
|
|
svc_name=$(echo "${line}" | awk '{print $2}')
|
||
|
|
if kubectl auth can-i get services/"${svc_name}" -n "${ns}" >/dev/null 2>&1; then
|
||
|
|
namespace=$(echo "${line}" | awk '{print $1}')
|
||
|
|
service=$(echo "${line}" | awk '{print $2}')
|
||
|
|
ports=$(echo "${line}" | awk '{print $3}')
|
||
|
|
printf "%-30s %-60s %-10s\n" "${namespace}" "${service}" "${ports}"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
main() {
|
||
|
|
if [ $# -lt 1 ]; then
|
||
|
|
echo "[ERROR] No sub-command provided"
|
||
|
|
echo "[TIP] Run 'freeleaps-cluster-proxifier -h' to see available sub-commands"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
subcommand="$1"
|
||
|
|
shift
|
||
|
|
|
||
|
|
case "${subcommand}" in
|
||
|
|
forward|-f|--forward)
|
||
|
|
forward_port "$@"
|
||
|
|
;;
|
||
|
|
stop|-s|--stop)
|
||
|
|
stop_forward "$@"
|
||
|
|
;;
|
||
|
|
list|-l|--list)
|
||
|
|
list_forwards
|
||
|
|
;;
|
||
|
|
list-available|-la|--list-available)
|
||
|
|
list_available_services
|
||
|
|
;;
|
||
|
|
help|-h|--help)
|
||
|
|
help
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
echo "[ERROR] Invalid sub-command: ${subcommand}"
|
||
|
|
help
|
||
|
|
exit 1
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
|
||
|
|
main "$@"
|