15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""hive -- Hive Shell
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)This lets you ssh to a group of servers and control them as if they were one.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Each command you enter is sent to each host in parallel. The response of each
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)host is collected and printed. In normal synchronous mode Hive will wait for
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)each host to return the shell command line prompt. The shell prompt is used to
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)sync output.
10868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Example:
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    $ hive.py --sameuser --samepass host1.example.com host2.example.net
14868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    username: myusername
155e3f23d412006dc4db4e659864679f29341e113fTorne (Richard Coles)    password:
16868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    connecting to host1.example.com - OK
17eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    connecting to host2.example.net - OK
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    targetting hosts: 192.168.1.104 192.168.1.107
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CMD (? for help) > uptime
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    =======================================================================
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    host1.example.com
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    -----------------------------------------------------------------------
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    uptime
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    23:49:55 up 74 days,  5:14,  2 users,  load average: 0.15, 0.05, 0.01
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    =======================================================================
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    host2.example.net
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    -----------------------------------------------------------------------
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    uptime
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    23:53:02 up 1 day, 13:36,  2 users,  load average: 0.50, 0.40, 0.46
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    =======================================================================
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Other Usage Examples:
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)1. You will be asked for your username and password for each host.
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
36eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    hive.py host1 host2 host3 ... hostN
37eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch2. You will be asked once for your username and password.
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   This will be used for each host.
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hive.py --sameuser --samepass host1 host2 host3 ... hostN
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)3. Give a username and password on the command-line:
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hive.py user1:pass2@host1 user2:pass2@host2 ... userN:passN@hostN
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)You can use an extended host notation to specify username, password, and host
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)instead of entering auth information interactively. Where you would enter a
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)host name use this format:
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    username:password@host
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)This assumes that ':' is not part of the password. If your password contains a
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)':' then you can use '\\:' to indicate a ':' and '\\\\' to indicate a single
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)'\\'. Remember that this information will appear in the process listing. Anyone
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)on your machine can see this auth information. This is not secure.
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)This is a crude script that begs to be multithreaded. But it serves its
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)purpose.
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Noah Spurrier
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)$Id: hive.py 509 2008-01-05 21:27:47Z noah $
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# TODO add feature to support username:password@host combination
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# TODO add feature to log each host output in separate file
68c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
69c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import sys, os, re, optparse, traceback, types, time, getpass
70c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import pexpect, pxssh
71c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import readline, atexit
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#histfile = os.path.join(os.environ["HOME"], ".hive_history")
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#try:
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    readline.read_history_file(histfile)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#except IOError:
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    pass
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#atexit.register(readline.write_history_file, histfile)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CMD_HELP="""Hive commands are preceded by a colon : (just think of vi).
81c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
82c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles):target name1 name2 name3 ...
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    set list of hosts to target commands
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles):target all
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    reset list of hosts to target all hosts in the hive.
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles):to name command
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    send a command line to the named host. This is similar to :target, but
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sends only one command and does not change the list of targets for future
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    commands.
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles):sync
972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    set mode to wait for shell prompts after commands are run. This is the
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    default. When Hive first logs into a host it sets a special shell prompt
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pattern that it can later look for to synchronize output of the hosts. If
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    you 'su' to another user then it can upset the synchronization. If you need
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    to run something like 'su' then use the following pattern:
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CMD (? for help) > :async
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CMD (? for help) > sudo su - root
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CMD (? for help) > :prompt
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CMD (? for help) > :sync
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles):async
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    set mode to not expect command line prompts (see :sync). Afterwards
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    commands are send to target hosts, but their responses are not read back
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    until :sync is run. This is useful to run before commands that will not
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return with the special shell prompt pattern that Hive uses to synchronize.
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles):refresh
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    refresh the display. This shows the last few lines of output from all hosts.
119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    This is similar to resync, but does not expect the promt. This is useful
120a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for seeing what hosts are doing during long running commands.
121a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
122a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles):resync
123a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
124a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    This is similar to :sync, but it does not change the mode. It looks for the
125a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    prompt and thus consumes all input from all targetted hosts.
126a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
127a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles):prompt
128a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    force each host to reset command line prompt to the special pattern used to
130a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    synchronize all the hosts. This is useful if you 'su' to a different user
131a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    where Hive would not know the prompt to match.
132a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
133a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles):send my text
134a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
135a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    This will send the 'my text' wihtout a line feed to the targetted hosts.
136a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    This output of the hosts is not automatically synchronized.
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles):control X
139a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    This will send the given control character to the targetted hosts.
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    For example, ":control c" will send ASCII 3.
142a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
143a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles):exit
144a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    This will exit the hive shell.
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def login (args, cli_username=None, cli_password=None):
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # I have to keep a separate list of host names because Python dicts are not ordered.
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # I want to keep the same order as in the args list.
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    host_names = []
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hive_connect_info = {}
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hive = {}
156a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # build up the list of connection information (hostname, username, password, port)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for host_connect_string in args:
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        hcd = parse_host_connect_string (host_connect_string)
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        hostname = hcd['hostname']
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        port     = hcd['port']
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if port == '':
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            port = None
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if len(hcd['username']) > 0:
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            username = hcd['username']
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cli_username is not None:
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            username = cli_username
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            username = raw_input('%s username: ' % hostname)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if len(hcd['password']) > 0:
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            password = hcd['password']
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cli_password is not None:
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            password = cli_password
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            password = getpass.getpass('%s password: ' % hostname)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        host_names.append(hostname)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        hive_connect_info[hostname] = (hostname, username, password, port)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # build up the list of hive connections using the connection information.
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for hostname in host_names:
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print 'connecting to', hostname
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            fout = file("log_"+hostname, "w")
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            hive[hostname] = pxssh.pxssh()
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            hive[hostname].login(*hive_connect_info[hostname])
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print hive[hostname].before
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            hive[hostname].logfile = fout
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print '- OK'
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except Exception, e:
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print '- ERROR',
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print str(e)
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print 'Skipping', hostname
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            hive[hostname] = None
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return host_names, hive
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def main ():
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    global options, args, CMD_HELP
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if options.sameuser:
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cli_username = raw_input('username: ')
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cli_username = None
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if options.samepass:
204a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        cli_password = getpass.getpass('password: ')
205a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    else:
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cli_password = None
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    host_names, hive = login(args, cli_username, cli_password)
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    synchronous_mode = True
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    target_hostnames = host_names[:]
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'targetting hosts:', ' '.join(target_hostnames)
213868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    while True:
214868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)        cmd = raw_input('CMD (? for help) > ')
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cmd = cmd.strip()
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if cmd=='?' or cmd==':help' or cmd==':h':
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print CMD_HELP
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd==':refresh':
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            refresh (hive, target_hostnames, timeout=0.5)
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            for hostname in target_hostnames:
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                if hive[hostname] is None:
223868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                    print '/============================================================================='
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '| ' + hostname + ' is DEAD'
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '\\-----------------------------------------------------------------------------'
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                else:
227868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                    print '/============================================================================='
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '| ' + hostname
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '\\-----------------------------------------------------------------------------'
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print hive[hostname].before
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print '=============================================================================='
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd==':resync':
234868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            resync (hive, target_hostnames, timeout=0.5)
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            for hostname in target_hostnames:
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                if hive[hostname] is None:
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '/============================================================================='
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '| ' + hostname + ' is DEAD'
239868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                    print '\\-----------------------------------------------------------------------------'
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                else:
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '/============================================================================='
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '| ' + hostname
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print '\\-----------------------------------------------------------------------------'
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print hive[hostname].before
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print '=============================================================================='
246868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            continue
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd==':sync':
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            synchronous_mode = True
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            resync (hive, target_hostnames, timeout=0.5)
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd==':async':
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            synchronous_mode = False
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd==':prompt':
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            for hostname in target_hostnames:
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                try:
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    if hive[hostname] is not None:
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        hive[hostname].set_unique_prompt()
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                except Exception, e:
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print "Had trouble communicating with %s, so removing it from the target list." % hostname
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print str(e)
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    hive[hostname] = None
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd[:5] == ':send':
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            cmd, txt = cmd.split(None,1)
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            for hostname in target_hostnames:
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                try:
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    if hive[hostname] is not None:
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        hive[hostname].send(txt)
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                except Exception, e:
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print "Had trouble communicating with %s, so removing it from the target list." % hostname
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print str(e)
273a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    hive[hostname] = None
274a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            continue
275a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        elif cmd[:3] == ':to':
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            cmd, hostname, txt = cmd.split(None,2)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if hive[hostname] is None:
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print '/============================================================================='
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print '| ' + hostname + ' is DEAD'
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print '\\-----------------------------------------------------------------------------'
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                continue
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            try:
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                hive[hostname].sendline (txt)
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                hive[hostname].prompt(timeout=2)
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print '/============================================================================='
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print '| ' + hostname
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print '\\-----------------------------------------------------------------------------'
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print hive[hostname].before
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            except Exception, e:
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print "Had trouble communicating with %s, so removing it from the target list." % hostname
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print str(e)
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                hive[hostname] = None
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd[:7] == ':expect':
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            cmd, pattern = cmd.split(None,1)
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print 'looking for', pattern
297c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            try:
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                for hostname in target_hostnames:
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    if hive[hostname] is not None:
300868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                        hive[hostname].expect(pattern)
301868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                        print hive[hostname].before
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            except Exception, e:
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print "Had trouble communicating with %s, so removing it from the target list." % hostname
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print str(e)
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                hive[hostname] = None
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd[:7] == ':target':
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            target_hostnames = cmd.split()[1:]
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if len(target_hostnames) == 0 or target_hostnames[0] == all:
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                target_hostnames = host_names[:]
311868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            print 'targetting hosts:', ' '.join(target_hostnames)
312868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            continue
313868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)        elif cmd == ':exit' or cmd == ':q' or cmd == ':quit':
314868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            break
315868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)        elif cmd[:8] == ':control' or cmd[:5] == ':ctrl' :
316868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            cmd, c = cmd.split(None,1)
317868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            if ord(c)-96 < 0 or ord(c)-96 > 255:
318868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                print '/============================================================================='
319868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                print '| Invalid character. Must be [a-zA-Z], @, [, ], \\, ^, _, or ?'
320868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                print '\\-----------------------------------------------------------------------------'
321868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                continue
322868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            for hostname in target_hostnames:
323868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                try:
324868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                    if hive[hostname] is not None:
325868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                        hive[hostname].sendcontrol(c)
326868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                except Exception, e:
327868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                    print "Had trouble communicating with %s, so removing it from the target list." % hostname
328868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                    print str(e)
329868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                    hive[hostname] = None
330868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            continue
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cmd == ':esc':
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            for hostname in target_hostnames:
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                if hive[hostname] is not None:
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    hive[hostname].send(chr(27))
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            continue
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Run the command on all targets in parallel
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for hostname in target_hostnames:
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            try:
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                if hive[hostname] is not None:
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    hive[hostname].sendline (cmd)
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            except Exception, e:
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print "Had trouble communicating with %s, so removing it from the target list." % hostname
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                print str(e)
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                hive[hostname] = None
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # print the response for each targeted host.
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if synchronous_mode:
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            for hostname in target_hostnames:
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                try:
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    if hive[hostname] is None:
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        print '/============================================================================='
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        print '| ' + hostname + ' is DEAD'
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        print '\\-----------------------------------------------------------------------------'
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    else:
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        hive[hostname].prompt(timeout=2)
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        print '/============================================================================='
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        print '| ' + hostname
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        print '\\-----------------------------------------------------------------------------'
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        print hive[hostname].before
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                except Exception, e:
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print "Had trouble communicating with %s, so removing it from the target list." % hostname
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    print str(e)
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    hive[hostname] = None
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print '=============================================================================='
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def refresh (hive, hive_names, timeout=0.5):
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """This waits for the TIMEOUT on each host.
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO This is ideal for threading.
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for hostname in hive_names:
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        hive[hostname].expect([pexpect.TIMEOUT,pexpect.EOF],timeout=timeout)
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def resync (hive, hive_names, timeout=2, max_attempts=5):
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """This waits for the shell prompt for each host in an effort to try to get
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    them all to the same state. The timeout is set low so that hosts that are
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    already at the prompt will not slow things down too much. If a prompt match
384a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    is made for a hosts then keep asking until it stops matching. This is a
385a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    best effort to consume all input if it printed more than one prompt. It's
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    kind of kludgy. Note that this will always introduce a delay equal to the
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    timeout for each machine. So for 10 machines with a 2 second delay you will
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    get AT LEAST a 20 second delay if not more. """
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO This is ideal for threading.
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for hostname in hive_names:
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for attempts in xrange(0, max_attempts):
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if not hive[hostname].prompt(timeout=timeout):
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                break
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def parse_host_connect_string (hcs):
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """This parses a host connection string in the form
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    username:password@hostname:port. All fields are options expcet hostname. A
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dictionary is returned with all four keys. Keys that were not included are
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    set to empty strings ''. Note that if your password has the '@' character
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    then you must backslash escape it. """
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if '@' in hcs:
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
406    else:
407        p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
408    m = p.search (hcs)
409    d = m.groupdict()
410    d['password'] = d['password'].replace('\\@','@')
411    return d
412
413if __name__ == '__main__':
414    try:
415        start_time = time.time()
416        parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version='$Id: hive.py 509 2008-01-05 21:27:47Z noah $',conflict_handler="resolve")
417        parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output')
418        parser.add_option ('--samepass', action='store_true', default=False, help='Use same password for each login.')
419        parser.add_option ('--sameuser', action='store_true', default=False, help='Use same username for each login.')
420        (options, args) = parser.parse_args()
421        if len(args) < 1:
422            parser.error ('missing argument')
423        if options.verbose: print time.asctime()
424        main()
425        if options.verbose: print time.asctime()
426        if options.verbose: print 'TOTAL TIME IN MINUTES:',
427        if options.verbose: print (time.time() - start_time) / 60.0
428        sys.exit(0)
429    except KeyboardInterrupt, e: # Ctrl-C
430        raise e
431    except SystemExit, e: # sys.exit()
432        raise e
433    except Exception, e:
434        print 'ERROR, UNEXPECTED EXCEPTION'
435        print str(e)
436        traceback.print_exc()
437        os._exit(1)
438