#!/bin/bash
# Coding assistance from Claude Code


VERBOSE=false
USE_DEV=false
SHOW_HELP=false
DRYRUN=false
CLUSTER_OPT="prod"
CLUSTER_OPT_SET=false

declare -A PRINCIPAL
declare -A ENDPOINT

PRINCIPAL[stage]="service/remctl-central-dev"
PRINCIPAL[prod]="service/remctl-central-prod"
PRINCIPAL[openvox]="service/remctl-central-openvox"
ENDPOINT[stage]="remctl-central-dev.stanford.edu"
ENDPOINT[prod]="remctl-central-prod.stanford.edu"
ENDPOINT[openvox]="remctl-central-openvox.stanford.edu"

############################################################
exit_with_error () {
    echo "error: $1"
    exit 1
}

progress () {
    if [[ "$VERBOSE" == "true" ]]; then
        echo "progress: $1"
    fi
}

show_help () {
cat <<EOF
Usage: kremctl [-v] [-n] [--cluster CLUSTER] [--dev] COMMAND SUBCOMMAND NAMESPACE -- [any other options to pass to SUBCOMMAND]

  --cluster CLUSTER   remctl-central cluster to use (prod, stage, openvox); defaults to prod
  --dev               (deprecated) use the stage cluster; use --cluster=stage instead
  -n, --dryrun        show the remctl command that would be run but do not run it
  -v, --verbose       run in verbose mode
  -h, --help          show this help summary and exit
EOF
}

show_help_and_exit_with_error () {
    echo "error: $1"
    show_help
    exit 1
}
############################################################


### Parse command line options
### From
### https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
###
### See also https://gist.github.com/bobpaul/ecd74cdf7681516703f20726431eaceb
###

# More safety, by turning some bugs into errors.
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !'s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo "I am sorry, 'getopt --test' failed in this environment."
    exit 1
fi

OPTIONS=-dvhn
LONGOPTS=dev,verbose,help,dryrun,cluster:

# -regarding ! and PIPESTATUS see above
# -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
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt's output this way to handle the quoting right:
eval set -- "${PARSED}"

progress "Getopt parsed arguments: ${PARSED}"
progress "Effective arguments: $*"
progress "Num args: $#"

declare -a EXTRA_ARGS
declare -a POSITIONAL
dashes=0

while [[ $# -gt 0 ]]; do
    progress "parsing arg: $1"

    if [[ $dashes -eq 1 ]]; then
        EXTRA_ARGS+=("'$1'")
    else
        case "$1" in
            -v|--verbose)
                VERBOSE=true
                ;;
            -d|--dev)
                USE_DEV=true
                ;;
            --cluster)
                CLUSTER_OPT="$2"
                CLUSTER_OPT_SET=true
                shift
                ;;
            -n|--dryrun)
                DRYRUN=true
                ;;
            -h|--help)
                SHOW_HELP=true
                ;;
            --)
                dashes=1
                ;;
            *)
                POSITIONAL+=("$1")
                ;;
        esac
    fi

    shift
done

# To avoid issues with older versions of bash that treat empty arrays as
# unbound variables we use the construction
# ${POSITIONAL[@]+"${POSITIONAL[@]}"}. Taken from Stackoverflow:
# https://stackoverflow.com/a/61551944/268847

set -- ${POSITIONAL[@]+"${POSITIONAL[@]}"}

### End of parse command line options

progress "end of parse command line options"

if [[ "$SHOW_HELP" == "true" ]]; then
    show_help
    exit
fi

if [[ "$CLUSTER_OPT_SET" == "true" && "$USE_DEV" == "true" ]]; then
    exit_with_error "--cluster and --dev cannot be used together"
fi

if [[ "$CLUSTER_OPT_SET" == "true" ]]; then
    CLUSTER="$CLUSTER_OPT"
elif [[ "$USE_DEV" == "true" ]]; then
    echo "warning: the -d/--dev option is deprecated and will be removed in the next version; use --cluster=stage instead" >&2
    CLUSTER="stage"
else
    CLUSTER="$CLUSTER_OPT"
fi

if [[ -z "${PRINCIPAL[$CLUSTER]+set}" ]]; then
    KNOWN_CLUSTERS=$(echo "${!PRINCIPAL[@]}" | tr ' ' '\n' | sort | paste -sd ' ')
    exit_with_error "unknown cluster: $CLUSTER (recognized clusters: $KNOWN_CLUSTERS)"
fi

THE_PRINCIPAL="${PRINCIPAL[$CLUSTER]}"
THE_ENDPOINT="${ENDPOINT[$CLUSTER]}"

# POSITIONAL had _better_ contain the three necessary components: COMMAND,
# SUBCOMMAND, and NAMESPACE. If POSITIONAL is empty treat this as a help.
# If it is not empty and contains fewer than three elements, this is an
# error.

if [[ -z ${POSITIONAL[@]+"${POSITIONAL[@]}"} ]]; then
    show_help
    exit 0
fi

if (( "${#POSITIONAL[@]}" < "3" )); then
   show_help_and_exit_with_error "missing one of COMMAND, SUBCOMMAND, or NAMESPACE"
fi

COMMAND="${POSITIONAL[0]}"
SUBCOMMAND="${POSITIONAL[1]}"
NAMESPACE="${POSITIONAL[2]}"

# The next three args need to be COMMAND, SUBCOMMAND, and NAMESPACE

# Process EXTRA_ARGS

# Join all the extra arguments. Note we use the same construction as with
# POSITIONAL array to avoid unbound variable errors in old Bashes.
EXTRA_ARGS_FMTED=${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}

progress "VERBOSE:      $VERBOSE"
progress "USE_DEV:      $USE_DEV"
progress "CLUSTER:      $CLUSTER"
progress "ENDPOINT:     $THE_ENDPOINT"
progress "PRINCIPAL:    $THE_PRINCIPAL"
progress "COMMAND:      $COMMAND"
progress "SUBCOMMAND:   $SUBCOMMAND"
progress "NAMESPACE:    $NAMESPACE"
progress "EXTRA_ARGS:   <$EXTRA_ARGS_FMTED>"


cmd="remctl -s $THE_PRINCIPAL $THE_ENDPOINT $COMMAND $SUBCOMMAND $NAMESPACE"

if [[ $EXTRA_ARGS_FMTED != "" ]]; then
    cmd="$cmd $EXTRA_ARGS_FMTED"
fi

# We use "bash -c" rather than calling remctl directly because if we don't the
# $EXTRA_ARGS_FMTED string quoting is misinterpreted.

if [[ "$DRYRUN" == "true" ]]; then
    echo "equivalent remctl command: $cmd"
else
    progress "about to call '$cmd'"
    bash -c "$cmd"
fi

exit 0

# Documentation.  Use a hack to hide this from the shell.  Because of the
# above exit line, this should never be executed.
DOCS=<<__END_OF_DOCS__

=head1 NAME

kremctl - Kubernetes/remctl proxy client wrapper

=head1 SYNOPSIS

kremctl [B<-v>] [B<--cluster> I<CLUSTER>] [B<-n>] I<COMMAND> I<SUBCOMMAND> I<NAMESPACE> [-- I<other_args_and_options>]

The deprecated B<--dev> option is also still accepted; see the OPTIONS
section.

kremctl --help

=head1 DESCRIPTION

The remctl-central Kubernetes proxy service allows the use of remctl to
execute commands on Kubernetes pods. To do this, one typically has to add
extra options to the B<remctl> command as well as remember the
remctl-central endpoint name. The B<kremctl> wrapper script makes this
easier by setting these options for you.

For more information on the remctl-central Kubernetes proxy service
see http://goto.stanford.edu/remctl-central

The command

    kremctl COMMAND SUBCOMMAND NAMESPACE -- [other options]

is equivalent to

    remctl -s service/remctl-central-prod remctl-central-prod.stanford.edu \
    COMMAND SUBCOMMAND NAMESPACE [other options]

Using the B<--dev> option directs the resulting B<remctl> command to the non-production
remctl-central service, that is,

    kremctl --dev COMMAND SUBCOMMAND NAMESPACE -- [other options]

is equivalent to

    remctl -s service/remctl-central-dev remctl-central-dev.stanford.edu \
    COMMAND SUBCOMMAND NAMESPACE other options

The B<--dev> option is deprecated and will be removed in the next
version; use B<--cluster=stage> instead.

To direct the resulting B<remctl> command against a specific
remctl-central cluster use the B<--cluster> option. The recognized
clusters are B<prod> (the default), B<stage>, and B<openvox>. For example,

    kremctl --cluster=openvox COMMAND SUBCOMMAND NAMESPACE -- [other options]

is equivalent to

    remctl -s service/remctl-central-openvox remctl-central-openvox.stanford.edu \
    COMMAND SUBCOMMAND NAMESPACE other options

The B<--cluster> option cannot be combined with the B<--dev> option.

IMPORTANT: Unlike the usual remctl command the B<kremctl> command MUST
have both the SUBCOMMAND and the NAMESPACE parameters; leaving off either
will result in an error.

To display the remctl command that will be run without actually
running the command, use the B<-n> option.

=head1 EXAMPLES

To sign the Puppet client for the node myserver.stanford.edu on the
production KPuppet instance:

    kremctl --cluster=openvox openvox-ca-prod sign openvox-prod -- myserver.stanford.edu

To see all the remctl commands supported by the remctl-central service:

    kremctl --cluster=stage rhelp show-config remctl-central-dev   # non-production remctl-central
    kremctl                 rhelp show-config remctl-central-prod  # production remctl-central

To run a command against the openvox cluster:

    kremctl --cluster=openvox rhelp show-config remctl-central-openvox

To see the equivalent remctl command that would be run without actually running the
command use the B<-n> option:

    kremctl -n --cluster=openvox ovenvox-ca-dev sign openvox-dev -- myserver.stanford.edu

results in the output

    equivalent remctl command: remctl -s service/remctl-central-openvox remctl-central-openvox.stanford.edu ovenvox-ca-dev sign openvox-dev 'myserver.stanford.edu'

=head1 OPTIONS

=over 4

=item B<-d|--dev>

By default B<kremctl> directs the remctl call against the production
remctl-central service. Use the B<-d> or B<--dev> option to change this
to direct the call against the non-production remctl-central service.

This option is deprecated and will be removed in the next version; use
B<--cluster=stage> instead.

=item B<--cluster> I<CLUSTER>

Direct the remctl call against the named remctl-central cluster. The
recognized clusters are B<prod>, B<stage>, and B<openvox>. If neither
B<--cluster> nor B<--dev> is given, the B<prod> cluster is used. This
option cannot be combined with the B<--dev> option.

Note that for historical reasons the B<stage> cluster uses the
B<service/remctl-central-dev> principal and the
B<remctl-central-dev.stanford.edu> endpoint. The deprecated B<--dev>
option is equivalent to B<--cluster=stage>.

=item B<-n|--dryrun>

Show the remctl command that would be run but do not actually
run the remctl command.

=item B<-v|--verbose>

Run in verbose mode.

=item B<-h|--help>

Show a help summary and exit.

=back

=head1 AUTHOR

Adam Lewenberg <adamhl@stanford.edu>

=head1 SEE ALSO

remctl(1)

=cut

__END_OF_DOCS__
