#!/usr/bin/env bash set -uo pipefail DEFAULT_MIN_PORT=8000 DEFAULT_MAX_PORT=64000 usage() { cat < 8000 $(basename "$0") next --from 8200 --to 8300 # Find next available port in range $(basename "$0") check 8008 # Check if port 8008 is available $(basename "$0") check 9000 --from 9000 # Check if port 9000 is available in range $(basename "$0") list # List all ports in use EOF } VERBOSE=0 COMMAND="" MIN_PORT=$DEFAULT_MIN_PORT MAX_PORT=$DEFAULT_MAX_PORT CHECK_PORT="" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage exit 0 ;; -v) VERBOSE=1 shift ;; --from) MIN_PORT="$2" shift 2 ;; --to) MAX_PORT="$2" shift 2 ;; next|check|list) COMMAND="$1" shift ;; -*) echo "Error: Unknown option: $1" >&2 usage >&2 exit 1 ;; *) if [[ "$1" =~ ^[0-9]+$ ]]; then if [[ "$COMMAND" == "check" ]] || [[ -z "$COMMAND" ]]; then CHECK_PORT="$1" COMMAND="check" else echo "Error: Unexpected argument: $1" >&2 usage >&2 exit 1 fi else echo "Error: Unexpected argument: $1" >&2 usage >&2 exit 1 fi shift ;; esac done if [[ -z "$COMMAND" ]]; then echo "Error: No command specified" >&2 usage >&2 exit 1 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" K8S_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" collect_ports() { PORTS=() while IFS= read -r file || [[ -n "$file" ]]; do if [[ ! -f "$file" ]]; then continue fi if ! grep -q "LoadBalancer" "$file" 2>/dev/null; then continue fi lb_port=$(yq -r '.. | objects | select(.type == "LoadBalancer") | select(.port != null) | .port // empty' "$file" 2>/dev/null || true) if [[ -n "$lb_port" ]] && [[ "$lb_port" != "null" ]] && [[ "$lb_port" =~ ^[0-9]+$ ]]; then PORTS+=("$lb_port") [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found .port $lb_port in $file" fi service_port_http=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .servicePortHttp // empty' "$file" 2>/dev/null || true) if [[ -n "$service_port_http" ]] && [[ "$service_port_http" != "null" ]] && [[ "$service_port_http" =~ ^[0-9]+$ ]]; then PORTS+=("$service_port_http") [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found servicePortHttp $service_port_http in $file" fi service_port_https=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .servicePortHttps // empty' "$file" 2>/dev/null || true) if [[ -n "$service_port_https" ]] && [[ "$service_port_https" != "null" ]] && [[ "$service_port_https" =~ ^[0-9]+$ ]]; then PORTS+=("$service_port_https") [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found servicePortHttps $service_port_https in $file" fi service_dns_port=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .dns.port // empty' "$file" 2>/dev/null || true) if [[ -n "$service_dns_port" ]] && [[ "$service_dns_port" != "null" ]] && [[ "$service_dns_port" =~ ^[0-9]+$ ]]; then PORTS+=("$service_dns_port") [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found dns.port $service_dns_port in $file" fi done < <(find "$K8S_DIR/k8s" -name "*.yaml" -type f 2>/dev/null) || true if command -v kubectl &>/dev/null && kubectl cluster-info &>/dev/null 2>&1; then [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Querying kubectl for LoadBalancer services..." while IFS= read -r line || [[ -n "$line" ]]; do ports_str=$(echo "$line" | awk '{print $6}') svc_name=$(echo "$line" | awk '{print $2}') while IFS=',' read -ra port_entries || [[ -n "${port_entries[*]}" ]]; do for entry in "${port_entries[@]}"; do port=$(echo "$entry" | cut -d: -f1 | cut -d/ -f1) if [[ -n "$port" ]] && [[ "$port" =~ ^[0-9]+$ ]]; then PORTS+=("$port") [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found kubectl port $port for $svc_name" fi done done <<< "$ports_str" done < <(kubectl get svc -A --no-headers 2>/dev/null | grep LoadBalancer || true) fi } is_port_in_use() { local port="$1" [[ " ${PORTS[*]} " =~ ${port} ]] } run_command() { collect_ports if [[ ${#PORTS[@]} -eq 0 ]]; then [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] No ports found in use" else [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Ports in use: ${PORTS[*]}" fi case "$COMMAND" in next) next_port=$MIN_PORT while [[ $next_port -le $MAX_PORT ]]; do if ! is_port_in_use "$next_port"; then echo "$next_port" return 0 fi next_port=$((next_port + 1)) done echo "Error: No available port found between $MIN_PORT and $MAX_PORT" >&2 return 1 ;; check) if [[ -z "$CHECK_PORT" ]]; then echo "Error: PORT is required for check command" >&2 return 1 fi if ! [[ "$CHECK_PORT" =~ ^[0-9]+$ ]]; then echo "Error: PORT must be a number" >&2 return 1 fi if [[ "$CHECK_PORT" -lt 1 ]] || [[ "$CHECK_PORT" -gt 65535 ]]; then echo "Error: PORT must be between 1 and 65535" >&2 return 1 fi if [[ "$CHECK_PORT" -lt "$MIN_PORT" ]] || [[ "$CHECK_PORT" -gt "$MAX_PORT" ]]; then echo "Error: PORT $CHECK_PORT is outside the check range ($MIN_PORT - $MAX_PORT)" >&2 return 1 fi if is_port_in_use "$CHECK_PORT"; then echo "PORT $CHECK_PORT IS IN USE" return 1 else echo "PORT $CHECK_PORT IS AVAILABLE" return 0 fi ;; list) if [[ ${#PORTS[@]} -eq 0 ]]; then echo "No ports in use" else printf '%s\n' "${PORTS[@]}" | sort -n -u fi ;; esac } run_command