Newer
Older
tsshbatch / tsshbatch.py
#!/usr/bin/env python
# tsshbatch.py - Non-Interactive ssh Connection
# Copyright (c) 2011 TundraWare Inc.
# Permission Hereby Granted For Unrestricted Personal Or Commercial Use
# For Updates See:  http://www.tundraware.com/Software/tsshbatch


#####
# Program Housekeeping
#####

PROGNAME = "tsshbatch.py"
BASENAME = PROGNAME.split(".py")[0]
PROGENV  = BASENAME.upper()
RCSID    = "$Id: tsshbatch.py,v 1.119 2011/12/28 17:54:42 tundra Exp $"
VERSION = RCSID.split()[2]


#####
# Suppress Deprecation Warnings 
# Required in some older environments where paramiko is behind the python libs
#####

import warnings
warnings.filterwarnings("ignore", "", DeprecationWarning)


#####
# Imports
#####

import getopt
import getpass
import os
import paramiko
import shlex
import socket
import sys


#####
# Constants And Literals
#####

FAILURE     = "FAILURE"
INDENTWIDTH = 8
OPTIONSLIST = "h:kn:p:"
PADWIDTH    = 30
SEPARATOR   = " --->  "
SUCCESS     = "SUCCESS"
SUDO        = 'sudo'
SUDOARGS    = '-S'
TRAILER     = ": "
USAGE       = "Usage:  tsshbatch.py [-k] [-n name] [-p pw] [-h 'host host ..' | serverlistfile] command arg arg arg \n" +\
    "          where,\n"                                                                 +\
    "\n"                                                                                 +\
    "                 -h '...'   List of targeted hosts passed as a single argument\n"   +\
    "                 -k         Turns on key-exchange authentication\n"                 +\
    "                 -n name    Specifies login name\n"                                 +\
    "                 -p pw      Specifies login password\n"
#####
# Error Messages
#####

eBADARG       =  "Invalid command line: %s!"
eBADFILE      =  "Cannot open '%s'!"
eNOCONNECT    =  "Cannot Connect! (Name/Address Bad?  Destination Unreachable?)"
eNOLOGIN      =  "Cannot Login! (Login/Password Bad?)"
eSUDOPW       =  "Must Specify Password When Using %s Commands!" % SUDO

#####
# Prompts
#####

pPASS = "Password: "
pUSER = "Username: "


#####
# Print Message(s) To stdout
#####

def PrintStdout(msg, TERMINATOR="\n"):
    sys.stdout.write(msg + TERMINATOR)

# End of 'PrintStdout()'


#####
# Display An Error Message And Exit
#####

def ErrorExit(msg):

    PrintStdout(msg)
    sys.exit()

# End Of 'ErrorExit()'


#####
# Process A Command On A Host
#####

def HostCommand(host, user, pw, command):

    ssh = paramiko.SSHClient()

    # Connect and run the command, reporting results as we go
    
    try: 
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        if KEYEXCHANGE:
            ssh.connect(host)
        else:
            ssh.connect(host, username=user, password=pw)

        # If this is a sudo run, force password to be read
        # from stdin thereby avoiding fiddling around with ptys.

        if command.startswith(SUDO):
            command = command.replace(SUDO, "%s %s" % (SUDO, SUDOARGS))

        # Run the command

        stdin, stdout, stderr = ssh.exec_command(command)

        # If doing a sudo command, send the password

        if command.startswith(SUDO):
            stdin.write("%s\n" % pw)
            stdin.flush()
            
        PrintReport([host, SUCCESS] + stdout.readlines() + stderr.readlines())
        
    # Catch authentication problems explicitly
        
    except paramiko.AuthenticationException:
        PrintReport([host, FAILURE, eNOLOGIN])
    
    # Everything else is some kind of connection problem

    except:
        PrintReport([host, FAILURE, eNOCONNECT])

    ssh.close()

# End of 'HostCommand()'

    
#####
# Print Report
#####

# Expects input as [host, success/failure message, result1, result2, ...]

def PrintReport(results):

    PrintStdout(SEPARATOR + results[0] +
                TRAILER +
                (PADWIDTH - len(results[0])) * " " +
                results[1])

    for r in results[2:]:                             # Command Results
        PrintStdout(INDENTWIDTH * " " + r.strip())

# End of 'PrintReport()'


# ---------------------- Program Entry Point ---------------------- #

#####
# Process Any Options User Set In The Environment Or On Command Line
#####

# Options that can be overriden by user

HOSTS        = ""        # List of hosts to target
KEYEXCHANGE  = False     # Do key exchange-based auth?
PWORD        = ""        # Password
UNAME        = ""        # Login name

# Handle any options set in the environment

OPTIONS = sys.argv[1:]
envopt = os.getenv(PROGENV)
if envopt:
    OPTIONS = shlex.split(envopt) + OPTIONS

# Combine them with those given on the command line
# This allows the command line to override defaults
# set in the environment

try:
    opts, args = getopt.getopt(OPTIONS, OPTIONSLIST)
except getopt.GetoptError, (errmsg, badarg):
    ErrorExit(eBADARG % errmsg)

# Make sure we have sufficient command line args
# to do something useful

if len(args) < 2:
    ErrorExit(USAGE)

for opt, val in opts:

    if opt == "-h":
        HOSTS = val.split()
        
    if opt == "-k":
        KEYEXCHANGE = True

    if opt == "-n":
        UNAME = val

    if opt == "-p":
        PWORD = val
    
#####
# Go Do The Requested Work
#####

# If we're not doing key exchange-based authentication, get
# user name & password.

# Only do this if they've not been set in the environment
# variable/command line

if not KEYEXCHANGE:

    if not UNAME:
        UNAME = raw_input(pUSER)

    if not PWORD:
        PWORD  = getpass.getpass(pPASS)


# Host list and command parsing
        
# Get the list of hosts if not specified on command line.
# The assumption is that the first argument is the file
# containing the list of targeted hosts and the remaining
# arguments form the command.

if not HOSTS:

    try:
        f = open(args[0])
        HOSTS = f.readlines()
        f.close()

    except:
        ErrorExit(eBADFILE % sys.argv[1])

    CMD = " ".join(args[1:])
    
# If hosts were passed on the command line, all the arguments
# are understood to form the command.
    
else:
    CMD = " ".join(args[0:])

# If user want 'sudo' execution, they MUST provide a password
# because key exchange-based authentication is not part of sudo

CMD = CMD.strip()
if CMD.startswith(SUDO) and not PWORD:
    ErrorExit(eSUDOPW)
    
# Iterate over the list of hosts, executing the command

for host in HOSTS:
    host = host.strip()
    if host:
     HostCommand(host, UNAME, PWORD, CMD)