vaniacer/sshto

Add Optional Tags to Change Behavior for Specific Host

Opened this issue · 6 comments

I'm looking for a way to add optional tags to a host and have sshto act on those tags.

Specifically, in my environment I have certain hosts that are live and hosts used for testing, as most people probably do. One of the fast ways I differentiate this is by making the background color of the production hosts different from non-production hosts, and I use a kitty browser kitten to do this.

What I'm now trying to do is add a tag to the end of a host description in an sshconfig file, say Host prod_serv #Production Server#PROD and then have sshto run a different ssh command when it sees that PROD tag. Where I'm running into trouble is I can't quite figure out how to make sshto see the tag or created variable when go_to_target() is called. Below is what I have so far, and any suggestions or advice you can provide is greatly appreciated. I couldn't find a way to highlight the specific lines I added, so the edited portions in the second code block are preceded with four underscores (____). Please be forewarned I'm no programmer so it would not surprise me to learn I'm approaching this all wrong.

Edit: I suppose I should have included what behavior I'm seeing now. It does not identify the $prod variable in the go_to_target function and so always executes the else statement. In the past I've just put all production servers in the same group and used the $group_name in the if/else statement, but I'd like to get away from having to keep unrelated production servers in the same group.

#------------------------{ SSH to target server }-----------------------------------------------------------------------
go_to_target(){ clear; 
  if [[ $prod == *"PROD"* ]]; then
   kitty +kitten ssh $SSH_OPT $target || pause;
  else
   ssh $SSH_OPT $target || pause;
  fi
}

.
.
.
.

#-------------{ Create the list of hosts. Get hosts and descriptions from ~/.ssh/config* }------------------------------
desclength=20
declare    -A      hostnames
____declare    -A      hostnames_prod
while read -r name hostname desc; do
    case    ${name,,} in
        'group_name') name="{ $desc }"
                      name_length=${#name}
                      name_left=$[(40-name_length)/2]
                      name_right=$[40-(name_left+name_length)]
                      printf -v tmp "%${name_left}s_NAME_%${name_right}s"
                      tmp=${tmp// /-}  name=${tmp//_NAME_/$name}
                      content+=( "$desc" );  desc='_LINE_';;
                '#'*) continue;;
    esac
    ((${#desc}>desclength)) && desclength=${#desc}
    hostnames["$name"]=$hostname #Create host-hostname pairs in hostnames array
    ____hostnames_prod["$name"]=$prod
    fullist+=("$name" "$desc")   #Add Host and Description to the list
done < <(gawk '
BEGIN{IGNORECASE=1}
/Host /{
    strt=1
    host=$2
    desc=gensub(/^.*Host .* #(.*)/, "\\1", "g", $0)
    ____prod=gensub(/(.*)(#PROD$)/, "PROD", "g", desc)
    desc=gensub(/(.*)#.*/,          "\\1", "g", desc)
    # desc=gensub(/(.*)#PROD$/, "\\1", "g", desc)
    next
}
strt && host == "dummy"{
    hostname=$2
    print "group_name", "dummy", desc
    strt=0
}
strt && /HostName /{
    hostname=$2
    print host, hostname, prod, desc
    strt=0
}'  $CONFILES)

descline=$(line - $desclength)
list=( "${fullist[@]}" )

Hi, interesting, let me see...

The problem is that by design this description '#some description#PROD' will be cut to just 'some description'.
Guest the easiest way to add your PROD tags is to add them into description itself, and if you don't want them to be shown in sshto dialog cut them in the process. Example:

...
Host prod_server1 #some description _PROD_
...

Add this array:
____declare -A hostnames_prod

And add this check:

...
while read -r name hostname desc; do
    case    ${name,,} in
        'group_name') name="{ $desc }"
                      name_length=${#name}
                      name_left=$[(40-name_length)/2]
                      name_right=$[40-(name_left+name_length)]
                      printf -v tmp "%${name_left}s_NAME_%${name_right}s"
                      tmp=${tmp// /-}  name=${tmp//_NAME_/$name}
                      content+=( "$desc" );  desc='_LINE_';;
                '#'*) continue;;
    esac

    # add this 
    [[ $desc =~ _PROD_ ]] && {
        hostnames_prod[$name]=1
        desc=${desc//_PROD_}
    }

    ((${#desc}>desclength)) && desclength=${#desc}
    hostnames["$name"]=$hostname #Create host-hostname pairs in hostnames array
    fullist+=("$name" "$desc")   #Add Host and Description to the list
done < <(gawk '
...

Then in go_to_target:

go_to_target(){ clear; 
  if [[ $hostnames_prod[$target] ]]; then
   kitty +kitten ssh $SSH_OPT $target || pause;
  else
   ssh $SSH_OPT $target || pause;
  fi
}

Hope this will help.

The problem is that by design this description '#some description#PROD' will be cut to just 'some description'. Guest the easiest way to add your PROD tags is to add them into description itself, and if you don't want them to be shown in sshto dialog cut them in the process. Example:

...
Host prod_server1 #some description _PROD_
...

Add this array: ____declare -A hostnames_prod

And add this check:

...
while read -r name hostname desc; do
    case    ${name,,} in
        'group_name') name="{ $desc }"
                      name_length=${#name}
                      name_left=$[(40-name_length)/2]
                      name_right=$[40-(name_left+name_length)]
                      printf -v tmp "%${name_left}s_NAME_%${name_right}s"
                      tmp=${tmp// /-}  name=${tmp//_NAME_/$name}
                      content+=( "$desc" );  desc='_LINE_';;
                '#'*) continue;;
    esac

    # add this 
    [[ $desc =~ _PROD_ ]] && {
        hostnames_prod[$name]=1
        desc=${desc//_PROD_}
    }

    ((${#desc}>desclength)) && desclength=${#desc}
    hostnames["$name"]=$hostname #Create host-hostname pairs in hostnames array
    fullist+=("$name" "$desc")   #Add Host and Description to the list
done < <(gawk '
...

Then in go_to_target:

go_to_target(){ clear; 
  if [[ $hostnames_prod[$target] ]]; then
   kitty +kitten ssh $SSH_OPT $target || pause;
  else
   ssh $SSH_OPT $target || pause;
  fi
}

Hope this will help.

Thanks for the suggestion! I tried using your provided code but it results in all hosts connecting using the kitten instead of just those with _PROD_ in the description, like the flag is always set.

In the various ways I've tried this I keep running into the problem that if the flag isn't visible in the dialog then I can't act on it, or it doesn't properly attach to the host as the list is built.

Ah, yes missed {} it should be like this:

 go_to_target(){ clear; 
  if [[ ${hostnames_prod[$target]} ]]; then
   kitty +kitten ssh $SSH_OPT $target || pause;
  else
   ssh $SSH_OPT $target || pause;
  fi
}

Ah, yes missed {} it should be like this:

 go_to_target(){ clear; 
  if [[ ${hostnames_prod[$target]} ]]; then
   kitty +kitten ssh $SSH_OPT $target || pause;
  else
   ssh $SSH_OPT $target || pause;
  fi
}

That did it! I was so close, but I was missing the below portion in my attempts. :

    # add this 
    [[ $desc =~ _PROD_ ]] && {
        hostnames_prod[$name]=1
        desc=${desc//_PROD_}
    }

Instead of handling it this way I was trying to save the entire description as another variable or just the #PROD# portion, but I was clearly doing something wrong.

Thank you for your help with this!

I actually had an idea to define more than one flag so that each is handled differently, mainly different terminal appearance settings depending on what the flag is, but I'm sure there are multiple uses for something like that. I'm not sure what the best approach to that would be, maybe to define the flags at the top of the script with the other configurables and then modify what you provided to look for any of those flags, or pattern match the flag pattern. If I come up with anything that's not too janky I'll share it here. Maybe somebody else will find it useful.

Thanks again for your help!!

I just wanted to leave this here, this is what I ultimately ended up with.

I'm using a file to store flags and commands in a comma-separated list. This allows me to look for the flag that's set and run the corresponding command. In my use case the command is just a piece of the whole ssh command, specifically a kitten for the Kitty browser that changes the terminal's theme for the duration of the SSH session. It could easily be expanded to provide the entire command, though.

_PROD_,--kitten=color_scheme=Modus\ Operandi
_SWIT_,--kitten=color_scheme=Grass

Then, I made the following changes to the sshto script (changed lines are preceded by 4 underscores):

  EDITOR=nvim   # SSH confile editor.
  LSEXIT=false   # Perform ls on exit true|false.
___CMDS=~/.ssh/flag-cmd-maps
    home=$PWD   # Destination folder on local  server to download\upload files and sshfs mount.
sshfsopt=       # Sshfs options if needed

Line 12, sets the location of the flag-command maps.



sshto_script[0]=~              # Path to sshto script
sshto_script[1]=.sshto_script  # Sshto script filename
sshto_script[2]="${sshto_script[0]}/${sshto_script[1]}" # Sshto script full
____mapfile -t command_map < $CMDS
#------------------------{ Add some tabs }------------------------------------------------------------------------------
tabbed(){ target=$target gnome-terminal --title=$target --tab -qe "${1/_target_/$target}"; } # Terminal command for tabs

Line 22, creates the mapping for the script.




#------------------------{ SSH to target server }-----------------------------------------------------------------------
# Iterate over the command mappings array to find the matching command
go_to_target(){

cmd_match=false

for cmd_mapping in "${command_map[@]}"; do
  
  # Split tag/command pairings
  IFS=',' read -r flag command <<< "$cmd_mapping"
  
  # Execute matched command if tag matches flag
  if [ "$flag" = "${hostnames_flag[$target]}" ]; then
    cmd_match=true; clear;
    eval "kitty +kitten ssh ${command} $SSH_OPT $target || pause"
  fi
done

if [ "$cmd_match" = false ]; then
  clear;
  ssh $SSH_OPT $target || pause;
fi
}

#------------------------{ Add aliases }--------------------------------------------------------------------------------

Lines 288-311. This entire section replaces the existing go_to_target function, and looks for the flag in the map table and if found runs the corresponding command. If not found it runs the normal ssh command.



    while read -r name hostname desc; do
        case    ${name,,} in
            'group_name') name="{ $desc }"
                          name_length=${#name}
                          name_left=$[(40-name_length)/2]
                          name_right=$[40-(name_left+name_length)]
                          printf -v tmp "%${name_left}s_NAME_%${name_right}s"
                          tmp=${tmp// /-}  name=${tmp//_NAME_/$name}
                          content+=( "$desc" );  desc='_LINE_';;
                    '#'*) continue;;
        esac

____        # Check SSH Flag
____        if [[ $desc =~ _(.{4})_ ]]; then
____          hostnames_flag[$name]="_${BASH_REMATCH[1]}_"
____          desc=${desc//_${BASH_REMATCH[1]}_}
____        fi

        ((${#desc}>desclength)) && desclength=${#desc}
        hostnames["$name"]=$hostname #Create host:hostname pairs in hostnames array
        fullist+=("$name" "$desc")   #Add Host and Description to the list
    done < <(gawk '

Lines 706-710. This is just where it searches the host line for the flag and associates it with the host.



Host production1 #Production Server 1 _PROD_
HostName production.server.com
User root
# PreferredAuthentications=password
# PubkeyAuthentication=no

Host chicago-fw #Chicago Cisco ASA#
HostName 1.2.3.4
User admin
PermitLocalCommand=yes

Host switch1 #Local Switch _SWIT_
	HostName 10.6.10.5
	User root

This is a short snippet of one of my hosts files. The first host is a production server, with a flag of _PROD_, and gets a specific color scheme by way of the first command in the command mappings. The second host is just a typical host, nothing special, and the script will connect to it by the regular ssh command. The last host is a local switch, with flag _SWIT_, and gets a different color scheme from the _PROD_ flag.



This should be expandable to as many flag/command pairings as desired, without requiring further modifications to the sshto script. Right now it only takes commands in the form of one underscore, four characters, then another underscore. This could be modified easily enough, though, by changing the regular expression used to match in the last code snippet. It does not match correctly if there's a second pound symbol before the first underscore, though, and I haven't figured out why. The Host line has to be entered as Host productionserver1 #Production Server 1 _PROD_ and not Host productionserver1 #Production Server 1# _PROD_. I'm sure this is easy enough to fix, for somebody smarter than me.

I welcome any feedback and suggestions, and I don't know that this is useful on a large enough scale to warrant importing it to the script by default, but maybe this will help somebody else one day.