sshotgun 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +164 -99
- data/lib/sshotgun.rb +104 -60
- metadata +5 -3
    
        data/README.rdoc
    CHANGED
    
    | @@ -10,8 +10,12 @@ The author of SSHotgun is Vick Perry (vick.perry @nospam@ gmail.com) | |
| 10 10 |  | 
| 11 11 | 
             
            SShotgun is a utility library for writing Unix/Linux server management and
         | 
| 12 12 | 
             
            provisioning scripts in Ruby. I use it for remotely managing machines in server
         | 
| 13 | 
            -
            clusters/farms.   | 
| 14 | 
            -
             | 
| 13 | 
            +
            clusters/farms.  
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            SShotgun calls your locally installed SSH client.  Before you do anything with
         | 
| 16 | 
            +
            SSHotgun, insure that your SSH public keys are installed correctly, the
         | 
| 17 | 
            +
            ssh-agent is running, that you've entered your passphrase via ssh-add and you
         | 
| 18 | 
            +
            can successfully log into the remote servers without getting a password prompt.
         | 
| 15 19 |  | 
| 16 20 | 
             
            == FEATURES:
         | 
| 17 21 |  | 
| @@ -202,26 +206,26 @@ script). See the advanced example for details. | |
| 202 206 | 
             
            Below is an advanced example that illustrates additional capabilities.
         | 
| 203 207 |  | 
| 204 208 | 
             
              #!/usr/bin/env ruby
         | 
| 205 | 
            -
             | 
| 209 | 
            +
             | 
| 206 210 | 
             
              # Required libraries
         | 
| 207 211 | 
             
              require "highline/import"
         | 
| 208 212 | 
             
              require 'sshotgun'
         | 
| 209 | 
            -
             | 
| 213 | 
            +
             | 
| 210 214 | 
             
              # Create array to contain hosts for processing
         | 
| 211 215 | 
             
              hostlist = Array::new()
         | 
| 212 216 | 
             
              host = HostDefinition.new("someserver.junk", "test", "ubuntu_server_7.10", "test vm")
         | 
| 213 217 | 
             
              hostlist << host
         | 
| 214 | 
            -
             | 
| 215 | 
            -
              # Import  | 
| 218 | 
            +
             | 
| 219 | 
            +
              # Import an external (shared) list of host definitions
         | 
| 216 220 | 
             
              require 'listofhosts'
         | 
| 217 221 | 
             
              hostlist = hostlist + ListOfHosts.getHostlist
         | 
| 218 | 
            -
             | 
| 222 | 
            +
             | 
| 219 223 | 
             
              class MyProcessingClass < BaseProcessingClass
         | 
| 220 | 
            -
             | 
| 224 | 
            +
             | 
| 221 225 | 
             
                # You can add your own Ruby instance variables. These are useful if you
         | 
| 222 226 | 
             
                # want to share data across your own custom methods.
         | 
| 223 227 | 
             
                attr_accessor :myInstanceVariable
         | 
| 224 | 
            -
             | 
| 228 | 
            +
             | 
| 225 229 | 
             
                # You MUST define a doProcessing method. Note that to make the doProcessing
         | 
| 226 230 | 
             
                # method less cluttered, you can also create your own methods that are
         | 
| 227 231 | 
             
                # called from within the doProcessing method. You can use your own accessor
         | 
| @@ -230,27 +234,81 @@ Below is an advanced example that illustrates additional capabilities. | |
| 230 234 | 
             
                def doProcessing
         | 
| 231 235 | 
             
                  # Log the beginning of this method. This marker is useful for debugging problems.
         | 
| 232 236 | 
             
                  log "[" + @hostdef.hostname + "] info: " + "Processing started"
         | 
| 233 | 
            -
             | 
| 234 | 
            -
                  #  | 
| 235 | 
            -
                  # to  | 
| 236 | 
            -
                  #  | 
| 237 | 
            -
                   | 
| 238 | 
            -
             | 
| 237 | 
            +
             | 
| 238 | 
            +
                  # Sometimes a remote server is unavailable. I like to include a check at
         | 
| 239 | 
            +
                  # top of the doProcessing method to stop processing for that server. This
         | 
| 240 | 
            +
                  # snippet works because if a server is down, unreachable or non-responsive
         | 
| 241 | 
            +
                  # then the id lookup for yourself will fail - when this happens call
         | 
| 242 | 
            +
                  # "return" to drop out of this method.
         | 
| 243 | 
            +
                  # Ruby's ENV contains the current user (you)
         | 
| 244 | 
            +
                  currentUser = ENV["USER"].to_s
         | 
| 245 | 
            +
                  runstatus = run "id " + currentUser
         | 
| 246 | 
            +
                  if runstatus.exitstatus != 0
         | 
| 247 | 
            +
                    log "[" + @hostdef.hostname + "] info: " + "Server not available or you cannot log in. Stopping processing for this server"
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                    # Set the status field for this host to indicate an abort. This status
         | 
| 250 | 
            +
                    # will be displayed upon termination of the script
         | 
| 251 | 
            +
                    @hostdef.status = "aborted"
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                    # return will end the processing of this host
         | 
| 254 | 
            +
                    return
         | 
| 255 | 
            +
                  else
         | 
| 256 | 
            +
                    # set status to indicate that processing for this host has started
         | 
| 257 | 
            +
                    @hostdef.status = "started"
         | 
| 258 | 
            +
                  end
         | 
| 259 | 
            +
             | 
| 260 | 
            +
                  # Compound commands in many shells may be created via the ";" semicolon
         | 
| 261 | 
            +
                  # separator.
         | 
| 262 | 
            +
                  run "ls -alF; printenv"
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                  # Escape any quotes (single or double) that are included within the command
         | 
| 265 | 
            +
                  # string. Surround the shell command string with single quotes or %q() in
         | 
| 266 | 
            +
                  # the sshotgun script so that Ruby won't interpolate the escaped characters
         | 
| 267 | 
            +
                  # before passing them to the your local ssh client.
         | 
| 268 | 
            +
                  run 'ls -aF | grep -i \".bash*\"'
         | 
| 269 | 
            +
                  run %q(ls -aF | grep -i \".bash*\")   
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                  # Run the command under sudo on the remote machine. If you run runSudo
         | 
| 272 | 
            +
                  # commands then the SSHotgun.sudoPassword must be set - uncomment the user
         | 
| 273 | 
            +
                  # prompt that sets the sudoPassword at the bottom of this script. See the
         | 
| 274 | 
            +
                  # gotcha below when running compound commands via sudo.
         | 
| 275 | 
            +
                  #runSudo "somecommand"
         | 
| 276 | 
            +
                  
         | 
| 277 | 
            +
                  # A gotcha with sudo in unix is that is that security is tightened by
         | 
| 278 | 
            +
                  # restricting the process environment. This means that compound commands
         | 
| 279 | 
            +
                  # that use built-in shell functions such as "cd" or "pushd" will fail.  To
         | 
| 280 | 
            +
                  # overcome this, first spawn a new shell, then pass it the commands to run.
         | 
| 281 | 
            +
                  # Note that because the command string contains escaped characters, you
         | 
| 282 | 
            +
                  # must enclose the entire command string in single quotes.
         | 
| 283 | 
            +
                  #runSudo 'bash -c \"hostname;pushd /tmp;hostname;ls -alf;popd\"'
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                  # THIS WON'T WORK - SUDO can't find pushd and popd unless you spawn a shell first
         | 
| 286 | 
            +
                  #runSudo "hostname;pushd /tmp;hostname;ls -alf;popd"  
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                  # On the remote server, the run and runSudo commands are executed in a
         | 
| 289 | 
            +
                  # non-interactive shell. This means that many of the environment variables
         | 
| 290 | 
            +
                  # that are set when you log in interactively will be missing. For debugging
         | 
| 291 | 
            +
                  # purposes, call 'printenv' to view the environment variables.
         | 
| 292 | 
            +
                  #run "printenv"
         | 
| 293 | 
            +
                
         | 
| 294 | 
            +
                  # A useful technique is set environment variables via a compound command.
         | 
| 295 | 
            +
                  #run "export http_proxy=aaa.bbb.com/8080/;wget www.mysite.junk/mypage"
         | 
| 296 | 
            +
             | 
| 239 297 | 
             
                  # The runstatus object is returned from all run and runSudo calls.
         | 
| 240 298 | 
             
                  # runstatus contains information about exit status, stdout and stderr from
         | 
| 241 299 | 
             
                  # the remote server.
         | 
| 242 | 
            -
                  runstatus = run "ls -aF"
         | 
| 243 | 
            -
             | 
| 300 | 
            +
                  runstatus = run "ls -aF .bash*"
         | 
| 301 | 
            +
             | 
| 244 302 | 
             
                  # Use the log method to write to the log (the console). Don't use the Ruby
         | 
| 245 303 | 
             
                  # 'puts' method to write to the console because it isn't threadsafe and may
         | 
| 246 304 | 
             
                  # mix and garble simultaneous output by multiple threads.
         | 
| 247 305 | 
             
                  log "[" + @hostdef.hostname + "] info: " + "The exit status of the run command is " + runstatus.exitstatus.to_s
         | 
| 248 | 
            -
             | 
| 306 | 
            +
             | 
| 249 307 | 
             
                  # Remote stderr and stdout are captured and stored in the stdoutstr and
         | 
| 250 308 | 
             
                  # stderrstr variables. Learn about Ruby's regular expressions for examining
         | 
| 251 309 | 
             
                  # the output from a remote command.
         | 
| 252 310 | 
             
                  runstatus = run "id rumplestiltskin"
         | 
| 253 | 
            -
             | 
| 311 | 
            +
             | 
| 254 312 | 
             
                  # Check for "No such user" contained in stdoutstr. That's what is displayed
         | 
| 255 313 | 
             
                  # when the unix "id" command can't find the specified user. Note that the
         | 
| 256 314 | 
             
                  # the exit status of a failed 'id' command is not equal to zero either (see
         | 
| @@ -258,18 +316,18 @@ Below is an advanced example that illustrates additional capabilities. | |
| 258 316 | 
             
                  if /No such user/ =~ runstatus.stdoutstr
         | 
| 259 317 | 
             
                    log "I failed to find user rumplestiltskin on this server"
         | 
| 260 318 | 
             
                  end
         | 
| 261 | 
            -
             | 
| 319 | 
            +
             | 
| 262 320 | 
             
                  # In some cases a remote command fails and you must stop processing for
         | 
| 263 321 | 
             
                  # that one server. Stop the execution of the doProcessing method by calling
         | 
| 264 322 | 
             
                  # Ruby's "return" statement. Don't call Ruby's "exit" statement because
         | 
| 265 323 | 
             
                  # that will halt all running threads.
         | 
| 266 | 
            -
             | 
| 267 | 
            -
             | 
| 268 | 
            -
               | 
| 269 | 
            -
               | 
| 270 | 
            -
             | 
| 271 | 
            -
             | 
| 272 | 
            -
             | 
| 324 | 
            +
                  #runstatus = run "id rumplestiltskin"
         | 
| 325 | 
            +
                  #if runstatus.exitstatus > 0
         | 
| 326 | 
            +
                  #  log "The exit code was not 0, therefore I failed to find user rumplestiltskin"
         | 
| 327 | 
            +
                  #  return  # stop processing for this host
         | 
| 328 | 
            +
                  #end
         | 
| 329 | 
            +
                  #log "You'll never get here...unless you have a user named rumplestiltskin"
         | 
| 330 | 
            +
             | 
| 273 331 | 
             
                  # The @hostdef instance variable contains the host definition for the
         | 
| 274 332 | 
             
                  # current host being processed. If you set category, os and desc to
         | 
| 275 333 | 
             
                  # meaningful information then you can do fancier branching logic in your
         | 
| @@ -280,97 +338,99 @@ Below is an advanced example that illustrates additional capabilities. | |
| 280 338 | 
             
                  log "The current host's category is: " + @hostdef.category
         | 
| 281 339 | 
             
                  log "The current host's os is: " + @hostdef.os
         | 
| 282 340 | 
             
                  log "The current host's description is: " + @hostdef.desc
         | 
| 283 | 
            -
             | 
| 341 | 
            +
                  log "The current host's status is: " + @hostdef.status
         | 
| 342 | 
            +
             | 
| 284 343 | 
             
                  # Target a specified host. This is a Ruby string comparison.
         | 
| 285 344 | 
             
                  if @hostdef.hostname == "192.168.1.224"
         | 
| 286 345 | 
             
                    log "[" + @hostdef.hostname + "] info: " + "Special processing for this host"
         | 
| 287 346 | 
             
                  end
         | 
| 288 | 
            -
             | 
| 289 | 
            -
                  # If necessary, escape any quotes within the command.  that must be passed
         | 
| 290 | 
            -
                  # through to the remote server. This isn't a great example but you'll need
         | 
| 291 | 
            -
                  # to do this for tricky shell scripting.
         | 
| 292 | 
            -
                  run "ls -aF | grep -i \".bash\""
         | 
| 293 | 
            -
              
         | 
| 294 | 
            -
                  # If you are running any runSudo commands then the SSHotgun.sudoPassword
         | 
| 295 | 
            -
                  # must be set - uncomment the user prompt and setting of the sudoPassword
         | 
| 296 | 
            -
                  # at the bottom of this script. Don't set up your servers to permit a
         | 
| 297 | 
            -
                  # password-less sudo - it is a security risk. Watch out, the "runSudo"
         | 
| 298 | 
            -
                  # command and Ruby in general, are case sensitive.
         | 
| 299 | 
            -
                  # runSudo <command>
         | 
| 300 | 
            -
              #    runSudo "touch /usr/local/bin/mytest.txt"
         | 
| 301 | 
            -
              #    run "ls -alF /usr/local/bin/"
         | 
| 302 | 
            -
              #    runSudo "rm /usr/local/bin/mytest.txt"
         | 
| 303 | 
            -
              
         | 
| 347 | 
            +
             | 
| 304 348 | 
             
                  # Run a command locally (on your local machine). The runstatus variable is
         | 
| 305 349 | 
             
                  # returned if you need the data - same as run or runSudo.
         | 
| 306 350 | 
             
                  # runLocal <command> 
         | 
| 307 | 
            -
             | 
| 308 | 
            -
             | 
| 351 | 
            +
                  #runLocal "ls -alF"
         | 
| 352 | 
            +
             | 
| 309 353 | 
             
                  # Run a sudo command locally. The runstatus variable is returned if you
         | 
| 310 354 | 
             
                  # need data from it. Set a sudoPassword if you are calling runLocalSudo.
         | 
| 311 355 | 
             
                  # runLocalSudo <command> 
         | 
| 312 | 
            -
             | 
| 313 | 
            -
             | 
| 356 | 
            +
                  #runLocalSudo "ls -alF"
         | 
| 357 | 
            +
             | 
| 314 358 | 
             
                  # Create a remote file from a string parameter.
         | 
| 315 359 | 
             
                  # createRemoteFile <content string>, <remotefile>
         | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 360 | 
            +
                  #createRemoteFile "this is your content here", "/home/vickp/testfile.txt"
         | 
| 361 | 
            +
             | 
| 318 362 | 
             
                  # Create a remote file and set mode
         | 
| 319 363 | 
             
                  # createRemoteFile <content string>, <remotefile>, <mode string>
         | 
| 320 | 
            -
             | 
| 364 | 
            +
                  #createRemoteFile "this is your content here", "/home/vickp/testfile.sh", "0755"
         | 
| 321 365 |  | 
| 322 366 | 
             
                  # Copy a remote file to the local machine. Note that you should vary the
         | 
| 323 367 | 
             
                  # name of the incoming local file else it will be overwritten when each
         | 
| 324 368 | 
             
                  # host's file is copied.
         | 
| 325 369 | 
             
                  # copyRemoteFileToLocal <remotefile>, <localfile>
         | 
| 326 | 
            -
             | 
| 370 | 
            +
                  #copyRemoteFileToLocal "/home/vickp/testfile.txt", "/home/vickp/temp/" + @hostdef.hostname + "_testfile.txt" 
         | 
| 327 371 |  | 
| 328 372 | 
             
                  # Copy a remote file to the local machine and set the mode of local file.
         | 
| 329 373 | 
             
                  # copyRemoteFileToLocal <remotefile>, <localfile>, <mode string>
         | 
| 330 | 
            -
             | 
| 374 | 
            +
                  #copyRemoteFileToLocal "/home/vickp/testfile.txt", "/home/vickp/temp/" + @hostdef.hostname + "_testfile.txt", "0644"
         | 
| 331 375 |  | 
| 332 376 | 
             
                  # Copy a local file to the remote machine.
         | 
| 333 377 | 
             
                  # copyRemoteFileToLocal <localfile>, <remotefile>
         | 
| 334 | 
            -
             | 
| 378 | 
            +
                  #copyLocalFileToRemote "/home/vickp/temp/testfile.txt", "/home/vickp/testfile2.txt"
         | 
| 335 379 |  | 
| 336 380 | 
             
                  # Copy a local file to the remote machine and set the mode of remote file.
         | 
| 337 381 | 
             
                  # copyRemoteFileToLocal <localfile>, <remotefile>, <mode string>
         | 
| 338 | 
            -
             | 
| 339 | 
            -
             | 
| 382 | 
            +
                  #copyLocalFileToRemote "/home/vickp/temp/testfile.txt", "/home/vickp/testfile2.txt", "0644"
         | 
| 383 | 
            +
             | 
| 340 384 | 
             
                  # Other helpful SSHotgun and Ruby tips below
         | 
| 341 385 | 
             
                  # ===========================================
         | 
| 342 | 
            -
             | 
| 386 | 
            +
             | 
| 343 387 | 
             
                  # Ruby allows you to concatenate multiple command lines
         | 
| 344 388 | 
             
                  # Note use of shell command separator ';'
         | 
| 345 | 
            -
             | 
| 346 | 
            -
             | 
| 347 | 
            -
             | 
| 348 | 
            -
             | 
| 349 | 
            -
             | 
| 389 | 
            +
                  cmd = "hostname;"
         | 
| 390 | 
            +
                  cmd << "ls -alF | grep -i .bash;"
         | 
| 391 | 
            +
                  cmd << "ls -alF | grep -i .profile"
         | 
| 392 | 
            +
                  run cmd
         | 
| 393 | 
            +
             | 
| 350 394 | 
             
                  # Delete a file on the remote server.
         | 
| 351 395 | 
             
                  # Note use of -f with rm command
         | 
| 352 | 
            -
             | 
| 353 | 
            -
             | 
| 396 | 
            +
                  #run "rm -f /path/to/remote/file"
         | 
| 397 | 
            +
             | 
| 398 | 
            +
                  # Determine if a directory exists on the remote server
         | 
| 399 | 
            +
                  runstatus = run 'test -d \"/tmp\"'
         | 
| 400 | 
            +
                  if runstatus.exitstatus > 0
         | 
| 401 | 
            +
                    log "[" + @hostdef.hostname + "] info: " + "The /tmp directory does NOT exist"
         | 
| 402 | 
            +
                  else
         | 
| 403 | 
            +
                    log "[" + @hostdef.hostname + "] info: " + "The /tmp directory exists"
         | 
| 404 | 
            +
                  end
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                  # Determine if a file exists on the remote server
         | 
| 407 | 
            +
                  runstatus = run 'test -e \".profile\"'
         | 
| 408 | 
            +
                  if runstatus.exitstatus > 0
         | 
| 409 | 
            +
                    log "[" + @hostdef.hostname + "] info: " + ".profile does NOT exist"
         | 
| 410 | 
            +
                  else
         | 
| 411 | 
            +
                    log "[" + @hostdef.hostname + "] info: " + ".profile exists"
         | 
| 412 | 
            +
                  end
         | 
| 413 | 
            +
             | 
| 354 414 | 
             
                  # Call your own methods to unclutter and better organize your doProcess
         | 
| 355 415 | 
             
                  # method code. In most of my SSHotgun scripts the doProcess method is
         | 
| 356 416 | 
             
                  # fairly sparse - most of the work is done is various custom methods that
         | 
| 357 417 | 
             
                  # are called within the doProcess method
         | 
| 358 | 
            -
             | 
| 359 | 
            -
             | 
| 360 | 
            -
                  #  | 
| 361 | 
            -
                   | 
| 418 | 
            +
                  #myOwnMethod
         | 
| 419 | 
            +
             | 
| 420 | 
            +
                  # The SSHotgun instance variables are visible in this method. 
         | 
| 421 | 
            +
                  log "[" + @hostdef.hostname + "] info: " + "The script started at: " + @sshotgun.startTime.to_s
         | 
| 422 | 
            +
             | 
| 423 | 
            +
                  # Sometimes I either install unsigned debian packages of my own
         | 
| 424 | 
            +
                  # or need to force the installation of files (overwrite) that
         | 
| 362 425 | 
             
                  # belong to another package. Aptitude doesn't yet support forcing yes for
         | 
| 363 426 | 
             
                  # this sort of thing but apt-get does...
         | 
| 364 | 
            -
             | 
| 365 | 
            -
             | 
| 366 | 
            -
                  # Ruby's ENV contains the current user (you)
         | 
| 367 | 
            -
                  currentUser = ENV["USER"].to_s
         | 
| 368 | 
            -
              
         | 
| 427 | 
            +
                  #runSudo "apt-get install -y --force-yes myUntrustedPackage -o DPkg::options::='--force-overwrite'"
         | 
| 428 | 
            +
             | 
| 369 429 | 
             
                  # For double quoted strings, Ruby will do inline substitution for a
         | 
| 370 430 | 
             
                  # variable enbedded within "#{myvariablehere}". This is useful when you
         | 
| 371 431 | 
             
                  # want a variable substituted inside of double quotes
         | 
| 372 | 
            -
             | 
| 373 | 
            -
             | 
| 432 | 
            +
                  #createRemoteFile "deb http://debrepo/global ./", "/home/#{currentUser}/sources.list", "0644"
         | 
| 433 | 
            +
             | 
| 374 434 | 
             
                  # Create a global variable if you need to prompt for additional information
         | 
| 375 435 | 
             
                  # (such as userid, date range, etc) and want to use that information in
         | 
| 376 436 | 
             
                  # your doProcessing method. Global variables begin with '$'. See bottom of
         | 
| @@ -382,27 +442,27 @@ Below is an advanced example that illustrates additional capabilities. | |
| 382 442 | 
             
                  else
         | 
| 383 443 | 
             
                    log "[" + @hostdef.hostname + "] info: " + "DID NOT find user " + $userid
         | 
| 384 444 | 
             
                  end
         | 
| 385 | 
            -
             | 
| 386 | 
            -
                  #  | 
| 387 | 
            -
                  # by using single quotes or the %q() delimiter to enclose the escaped
         | 
| 388 | 
            -
                  # string.  This means that Ruby will not attempt to resolve the escaped
         | 
| 389 | 
            -
                  # string but merely pass it on to the ssh client.
         | 
| 390 | 
            -
              #    run %q("mycommand \"some\ string\ with\ escapes\" more")
         | 
| 391 | 
            -
              
         | 
| 392 | 
            -
                  # Example of how to update a debian/ubuntu sources.list package repository file
         | 
| 445 | 
            +
             | 
| 446 | 
            +
                  # See example of how to update a debian/ubuntu sources.list package repository file
         | 
| 393 447 | 
             
                  # Also see use of case statement in the method body.
         | 
| 394 | 
            -
             | 
| 395 | 
            -
             | 
| 448 | 
            +
                  #installSourcesListAndUpdate
         | 
| 449 | 
            +
             | 
| 450 | 
            +
                  # You can set the exit code of this script
         | 
| 451 | 
            +
                  #@sshotgun.exitCode = 3
         | 
| 452 | 
            +
             | 
| 396 453 | 
             
                  # Log the end of this method.
         | 
| 397 454 | 
             
                  log "[" + @hostdef.hostname + "] info: " + "Processing completed"
         | 
| 455 | 
            +
             | 
| 456 | 
            +
                  # Set the status field for this host to indicate successful processing.
         | 
| 457 | 
            +
                  @hostdef.status = "finished"
         | 
| 398 458 | 
             
                end
         | 
| 399 459 | 
             
              end
         | 
| 400 | 
            -
             | 
| 460 | 
            +
             | 
| 401 461 | 
             
              # Your own custom methods can be called from within the doProcess method
         | 
| 402 462 | 
             
              def myOwnMethod
         | 
| 403 463 | 
             
                log "[" + @hostdef.hostname + "] info: " + "Calling my own method"
         | 
| 404 464 | 
             
              end
         | 
| 405 | 
            -
             | 
| 465 | 
            +
             | 
| 406 466 | 
             
              # Update a debian/ubuntu /etc/apt/sources.list
         | 
| 407 467 | 
             
              def installSourcesListAndUpdate
         | 
| 408 468 | 
             
                # WARNING: This only works if you fill in the category field in your hostdefs.
         | 
| @@ -432,25 +492,25 @@ Below is an advanced example that illustrates additional capabilities. | |
| 432 492 | 
             
                createRemoteFile s, "/home/#{$currentUser}/sources.list", "0644"
         | 
| 433 493 | 
             
                runSudo "cp /home/#{$currentUser}/sources.list /etc/apt/sources.list"
         | 
| 434 494 | 
             
                run "rm /home/#{$currentUser}/sources.list"
         | 
| 435 | 
            -
             | 
| 495 | 
            +
             | 
| 436 496 | 
             
                # after changing sources.list, update package manager to refresh package list
         | 
| 437 497 | 
             
                runSudo "aptitude update"
         | 
| 438 498 | 
             
              end
         | 
| 439 | 
            -
             | 
| 499 | 
            +
             | 
| 440 500 | 
             
              # Create an instance of SSHotgun and pass it the list of hosts and your custom
         | 
| 441 501 | 
             
              # processing class.
         | 
| 442 502 | 
             
              sshotgun = SSHotgun.new(hostlist, MyProcessingClass)
         | 
| 443 | 
            -
             | 
| 503 | 
            +
             | 
| 444 504 | 
             
              # If you are calling a sudo command then prompt the user for the sudo password.
         | 
| 445 505 | 
             
              # I recommend that you NEVER HARDCODE A PASSWORD IN A SCRIPT!  Configure the
         | 
| 446 506 | 
             
              # ask command to hide the password as the user types it.  Note that the you
         | 
| 447 507 | 
             
              # must have the highline gem package installed for the "ask" command
         | 
| 448 508 | 
             
              #sshotgun.sudopassword = ask(">>> Enter your sudo password:  ") { |q| q.echo = "*" } # or q.echo = false
         | 
| 449 | 
            -
             | 
| 509 | 
            +
             | 
| 450 510 | 
             
              # If you need to forward all calls via a gateway ssh server, set it here.  Once
         | 
| 451 511 | 
             
              # set, all ssh connections go through the gateway machine
         | 
| 452 512 | 
             
              #sshotgun.gatewayhost = "someserver.somedomain.com"
         | 
| 453 | 
            -
             | 
| 513 | 
            +
             | 
| 454 514 | 
             
              # You can also use the 'ask' command to prompt the user for any other
         | 
| 455 515 | 
             
              # information. Configure the ask command to display the string as the user
         | 
| 456 516 | 
             
              # types it. Be sure to test password-less login from your gateway server to
         | 
| @@ -458,26 +518,30 @@ Below is an advanced example that illustrates additional capabilities. | |
| 458 518 | 
             
              # host.
         | 
| 459 519 | 
             
              #gatewayhost = ask(">>> Enter the hostname of the gateway server:  ") { |q| q.echo = true }
         | 
| 460 520 | 
             
              #sshotgun.gatewayhost = gatewayhost
         | 
| 461 | 
            -
              
         | 
| 521 | 
            +
              #sshotgun.gatewayhost = "mygateway.hostname.junk"
         | 
| 522 | 
            +
             | 
| 462 523 | 
             
              # Create and use a global variable to get additional data into your doProcessing method.
         | 
| 463 524 | 
             
              $userid = ask(">>> Enter the userid to find:  ") { |q| q.echo = true }
         | 
| 464 | 
            -
             | 
| 525 | 
            +
             | 
| 465 526 | 
             
              # Number of simultaneous processing threads (hosts) to run. In terms of load to
         | 
| 466 527 | 
             
              # your local machine, each processing thread equates to about one ssh client
         | 
| 467 528 | 
             
              # running. Default is 50
         | 
| 468 529 | 
             
              # sshotgun.maxThreads = 30    
         | 
| 469 | 
            -
             | 
| 530 | 
            +
             | 
| 470 531 | 
             
              # A processing thread will be killed after this amount of time. Default is 30 minutes.
         | 
| 471 532 | 
             
              # sshotgun.threadTimeoutInSeconds = 900
         | 
| 472 | 
            -
             | 
| 533 | 
            +
             | 
| 473 534 | 
             
              # Period for displaying status information about processing threads that
         | 
| 474 535 | 
             
              # are still running. Default is 30
         | 
| 475 536 | 
             
              # sshotgun.monitorPollIntervalInSeconds = 20
         | 
| 476 | 
            -
             | 
| 537 | 
            +
             | 
| 477 538 | 
             
              # Any output is also written to a file logger. You can disable the file logging
         | 
| 478 539 | 
             
              # by setting isFileLogging to false.
         | 
| 479 540 | 
             
              # sshotgun.isFileLogging = false.
         | 
| 480 | 
            -
             | 
| 541 | 
            +
             | 
| 542 | 
            +
              # Turn on debug mode
         | 
| 543 | 
            +
              # sshotgun.isDebug = true
         | 
| 544 | 
            +
             | 
| 481 545 | 
             
              # What should the behavior be when a command returns a non-zero exit status? A
         | 
| 482 546 | 
             
              # non-zero exit status usually indicates failure of the command. Some sshotgun
         | 
| 483 547 | 
             
              # users wish to immediately stop the processing for that server and stop it's
         | 
| @@ -487,10 +551,11 @@ Below is an advanced example that illustrates additional capabilities. | |
| 487 551 | 
             
              # you never have the opportunity to check the exit status in your processing
         | 
| 488 552 | 
             
              # method code. The default is false (don't stop on non-zero exit).
         | 
| 489 553 | 
             
              # sshotgun.isStopOnNonZeroExit = true
         | 
| 490 | 
            -
             | 
| 554 | 
            +
             | 
| 491 555 | 
             
              # Start processing. The start call should be the very last line in a SSHotgun
         | 
| 492 556 | 
             
              # script.
         | 
| 493 557 | 
             
              sshotgun.start
         | 
| 558 | 
            +
             | 
| 494 559 |  | 
| 495 560 | 
             
            == LICENSE:
         | 
| 496 561 |  | 
    
        data/lib/sshotgun.rb
    CHANGED
    
    | @@ -3,6 +3,19 @@ | |
| 3 3 | 
             
            #Copyright:: Copyright (c) 2008, Vick Perry
         | 
| 4 4 | 
             
            #License::   Simplified BSD License.
         | 
| 5 5 | 
             
            #
         | 
| 6 | 
            +
            # vickp 9/24/2008, Added HostDefinition.status freeform text field. Upon
         | 
| 7 | 
            +
            # termination of the script, the status field is displayed for each host.
         | 
| 8 | 
            +
            # Typically set by the script programmer to indicate state of processing. E.g.
         | 
| 9 | 
            +
            # "started", "aborted", "finished", etc. Removed original status flags.
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            # vickp 9/23/2008, Added SSHotgun.exitCode (fixnum). So that programmer can set
         | 
| 12 | 
            +
            # the script's exit code. Exit code is returned to calling process upon termination.
         | 
| 13 | 
            +
            #
         | 
| 14 | 
            +
            # vickp 9/23/2008, Added check for unescaped quotes in command strings.  Added
         | 
| 15 | 
            +
            # isDebug flag. Updated documents with clarification about escaping quotes in
         | 
| 16 | 
            +
            # command strings and tips about how to deal with a restricted unix environment
         | 
| 17 | 
            +
            # when using sudo.
         | 
| 18 | 
            +
            #
         | 
| 6 19 | 
             
            require 'open4'
         | 
| 7 20 | 
             
            require 'logger'
         | 
| 8 21 |  | 
| @@ -21,24 +34,15 @@ class HostDefinition | |
| 21 34 | 
             
              # desc is a one sentence string describing main purpose of the host (optional)
         | 
| 22 35 | 
             
              attr_accessor :desc
         | 
| 23 36 |  | 
| 24 | 
            -
              #  | 
| 25 | 
            -
              attr_accessor : | 
| 26 | 
            -
             | 
| 27 | 
            -
              # For internal use. True if processing was finished for this server. This means that doProcess exited
         | 
| 28 | 
            -
              # normally with no exceptions. 
         | 
| 29 | 
            -
              attr_accessor :finishedProcess
         | 
| 30 | 
            -
             | 
| 31 | 
            -
              # For internal use. True if one or more commands on a server returned with a non-zero exit status
         | 
| 32 | 
            -
              attr_accessor :hadNonZeroExitStatus
         | 
| 37 | 
            +
              # status is freeform text field that the script programmer can set. Generally set to "started", "finished", "aborted", etc.
         | 
| 38 | 
            +
              attr_accessor :status
         | 
| 33 39 |  | 
| 34 40 | 
             
              def initialize(hostname, category = "", os = "", desc = "")
         | 
| 35 41 | 
             
                @hostname = hostname
         | 
| 36 42 | 
             
                @category = category
         | 
| 37 43 | 
             
                @os = os
         | 
| 38 44 | 
             
                @desc = desc
         | 
| 39 | 
            -
                @ | 
| 40 | 
            -
                @finishedProcess = false
         | 
| 41 | 
            -
                @hadNonZeroExitStatus = false
         | 
| 45 | 
            +
                @status = ""
         | 
| 42 46 | 
             
              end
         | 
| 43 47 | 
             
            end
         | 
| 44 48 |  | 
| @@ -92,12 +96,19 @@ class BaseProcessingClass | |
| 92 96 | 
             
              #
         | 
| 93 97 | 
             
              def run(cmd)
         | 
| 94 98 | 
             
                log "[" + @hostdef.hostname + "] run: " + cmd
         | 
| 95 | 
            -
                if  | 
| 96 | 
            -
                   | 
| 99 | 
            +
                if !hasUnescapedQuotes(cmd)
         | 
| 100 | 
            +
                  if @sshotgun.gatewayhost
         | 
| 101 | 
            +
                    cmdline = "ssh -A -t -x " + @sshotgun.gatewayhost + " 'ssh -A -x " +  @hostdef.hostname + " \"" + cmd + "\"'"
         | 
| 102 | 
            +
                  else
         | 
| 103 | 
            +
                    cmdline = "ssh -A -t -x " +  @hostdef.hostname + " \"" + cmd + "\""
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                  runLocal cmdline, false
         | 
| 97 106 | 
             
                else
         | 
| 98 | 
            -
                   | 
| 107 | 
            +
                  log "[_sshotgun_] fatal: Command string cannot contain unescaped quotes. Surround command string"
         | 
| 108 | 
            +
                  log "[_sshotgun_] fatal: with single quotes or %q() so that Ruby does not interpolate string first."
         | 
| 109 | 
            +
                  @sshotgun.exitCode = 1
         | 
| 110 | 
            +
                  @sshotgun.showRunStatsAndExit
         | 
| 99 111 | 
             
                end
         | 
| 100 | 
            -
                runLocal cmdline, false
         | 
| 101 112 | 
             
              end
         | 
| 102 113 |  | 
| 103 114 | 
             
              # Sudo a command on a remote server
         | 
| @@ -108,12 +119,19 @@ class BaseProcessingClass | |
| 108 119 | 
             
              #
         | 
| 109 120 | 
             
              def runSudo(cmd)
         | 
| 110 121 | 
             
                log "[" + @hostdef.hostname + "] runSudo: " + cmd
         | 
| 111 | 
            -
                if  | 
| 112 | 
            -
                   | 
| 122 | 
            +
                if !hasUnescapedQuotes(cmd)
         | 
| 123 | 
            +
                  if @sshotgun.gatewayhost
         | 
| 124 | 
            +
                    cmdline = "ssh -A -t -x " + @sshotgun.gatewayhost + " 'ssh -A -x " + @hostdef.hostname + " sudo -S \"" + cmd + "\"'"
         | 
| 125 | 
            +
                  else
         | 
| 126 | 
            +
                    cmdline = "ssh -A -t -x " + @hostdef.hostname + " sudo -S \"" + cmd + "\""
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
                  runLocal cmdline, true
         | 
| 113 129 | 
             
                else
         | 
| 114 | 
            -
                   | 
| 130 | 
            +
                  log "[_sshotgun_] fatal: Command string cannot contain unescaped quotes. Surround command string"
         | 
| 131 | 
            +
                  log "[_sshotgun_] fatal: with single quotes or %q() so that Ruby does not interpolate string first."
         | 
| 132 | 
            +
                  @sshotgun.exitCode = 1
         | 
| 133 | 
            +
                  @sshotgun.showRunStatsAndExit
         | 
| 115 134 | 
             
                end
         | 
| 116 | 
            -
                runLocal cmdline, true
         | 
| 117 135 | 
             
              end
         | 
| 118 136 |  | 
| 119 137 | 
             
              # Copy a remote file to local
         | 
| @@ -180,7 +198,10 @@ class BaseProcessingClass | |
| 180 198 | 
             
              # isSudo is the flag to launch as a sudo command
         | 
| 181 199 | 
             
              #
         | 
| 182 200 | 
             
              def runLocal (cmdline, isSudo=false)
         | 
| 183 | 
            -
                 | 
| 201 | 
            +
                if @sshotgun.isDebug
         | 
| 202 | 
            +
                  # display & log the actual command line sent to ssh
         | 
| 203 | 
            +
                  log "[" + @hostdef.hostname + "] debug: raw command line to local ssh client: " + cmdline
         | 
| 204 | 
            +
                end
         | 
| 184 205 | 
             
                currentThread = Thread.current
         | 
| 185 206 | 
             
                runstatus = RunStatus.new
         | 
| 186 207 | 
             
                retval = Open4::popen4( cmdline ) do |pid, stdin, stdout, stderr|
         | 
| @@ -188,6 +209,8 @@ class BaseProcessingClass | |
| 188 209 | 
             
                  if isSudo
         | 
| 189 210 | 
             
                    if @sshotgun.sudopassword
         | 
| 190 211 | 
             
                      stdin.puts @sshotgun.sudopassword
         | 
| 212 | 
            +
                    else
         | 
| 213 | 
            +
                      log "[" + @hostdef.hostname + "] info: sudo invoked but sudo password is blank"
         | 
| 191 214 | 
             
                    end
         | 
| 192 215 | 
             
                  end
         | 
| 193 216 | 
             
                  runstatus.stdoutstr = stdout.read.strip
         | 
| @@ -202,10 +225,6 @@ class BaseProcessingClass | |
| 202 225 | 
             
                runstatus.exitstatus = retval.exitstatus
         | 
| 203 226 |  | 
| 204 227 | 
             
                if runstatus.exitstatus != 0
         | 
| 205 | 
            -
                  # set flag to indicate a non-zero exit status
         | 
| 206 | 
            -
                  # this flag is needed by the status report at the end of the script run
         | 
| 207 | 
            -
                  @hostdef.hadNonZeroExitStatus = true
         | 
| 208 | 
            -
                  
         | 
| 209 228 | 
             
                  # am I supposed to stop thread?
         | 
| 210 229 | 
             
                  if @sshotgun.isStopOnNonZeroExit
         | 
| 211 230 | 
             
                    log "[" + @hostdef.hostname + "] stderr: Non-zero exit status - Stop processing for this server (" + runstatus.exitstatus.to_s + ")"
         | 
| @@ -235,6 +254,30 @@ class BaseProcessingClass | |
| 235 254 | 
             
              def log(s)
         | 
| 236 255 | 
             
                @sshotgun.log s
         | 
| 237 256 | 
             
              end
         | 
| 257 | 
            +
             | 
| 258 | 
            +
              # Does the string contain unescaped quotes? This is a safety measure.  If
         | 
| 259 | 
            +
              # there are unescaped quotes (double or single) contained in a command
         | 
| 260 | 
            +
              # string, it may be interpolated differently than as is expected. For example
         | 
| 261 | 
            +
              # a compound command may execute the first part on a local or gateway server
         | 
| 262 | 
            +
              # and the last part on the remote server. 
         | 
| 263 | 
            +
              #
         | 
| 264 | 
            +
              # s is the string to check
         | 
| 265 | 
            +
              #
         | 
| 266 | 
            +
              def hasUnescapedQuotes(s)
         | 
| 267 | 
            +
                retval = false
         | 
| 268 | 
            +
                previousChar = ""
         | 
| 269 | 
            +
                sArray = s.split(//)
         | 
| 270 | 
            +
                sArray.each do |currentChar|
         | 
| 271 | 
            +
                  #log currentChar
         | 
| 272 | 
            +
                  if (currentChar == "\"" || currentChar == "\'") && previousChar != "\\"
         | 
| 273 | 
            +
                    retval = true
         | 
| 274 | 
            +
                    break
         | 
| 275 | 
            +
                  end
         | 
| 276 | 
            +
                  previousChar = currentChar
         | 
| 277 | 
            +
                end
         | 
| 278 | 
            +
                return retval
         | 
| 279 | 
            +
              end
         | 
| 280 | 
            +
             | 
| 238 281 | 
             
            end
         | 
| 239 282 |  | 
| 240 283 | 
             
            # This is the main class of SSHotgun. This class stores the configuration and launches the processing threads.
         | 
| @@ -270,15 +313,24 @@ class SSHotgun | |
| 270 313 | 
             
              # For internal use. Mutex for launching processing threads
         | 
| 271 314 | 
             
              attr_reader :threadStartMutex
         | 
| 272 315 |  | 
| 316 | 
            +
              # For internal use. Mutex for exiting
         | 
| 317 | 
            +
              attr_reader :threadEndMutex
         | 
| 318 | 
            +
             | 
| 273 319 | 
             
              # File logger. Set your own if desired. (optional)
         | 
| 274 320 | 
             
              attr_accessor :filelogger
         | 
| 275 321 |  | 
| 276 322 | 
             
              # Turn on/off file logging. Default is on. (optional)
         | 
| 277 323 | 
             
              attr_accessor :isFileLogging
         | 
| 278 324 |  | 
| 325 | 
            +
              # Turn on/off debugging. Default is off. (optional)
         | 
| 326 | 
            +
              attr_accessor :isDebug
         | 
| 327 | 
            +
             | 
| 279 328 | 
             
              # For internal use. Script start time
         | 
| 280 329 | 
             
              attr_reader :startTime
         | 
| 281 330 |  | 
| 331 | 
            +
              # For internal use. sshotgun version
         | 
| 332 | 
            +
              attr_reader :versionStr
         | 
| 333 | 
            +
             | 
| 282 334 | 
             
              # Turn on/off behavior where processing for a server is stopped if any
         | 
| 283 335 | 
             
              # command (run, runSudo, etc) returns with a non-zero exit code. Default is
         | 
| 284 336 | 
             
              # off (don't stop). Note that if isStopOnNonZeroExit is true then the
         | 
| @@ -286,11 +338,16 @@ class SSHotgun | |
| 286 338 | 
             
              # check the exit status in your processing code.
         | 
| 287 339 | 
             
              attr_accessor :isStopOnNonZeroExit
         | 
| 288 340 |  | 
| 341 | 
            +
              # Exit code for this script, default is zero. Settable by the script programmer.
         | 
| 342 | 
            +
              attr_accessor :exitCode
         | 
| 343 | 
            +
             | 
| 289 344 | 
             
              def initialize(hostlist, processingclass)
         | 
| 345 | 
            +
                @versionStr = "1.0.5"
         | 
| 290 346 | 
             
                @hostlist = hostlist
         | 
| 291 347 | 
             
                @processingclass = processingclass
         | 
| 292 348 | 
             
                @logmutex = Mutex.new
         | 
| 293 349 | 
             
                @threadStartMutex = Mutex.new
         | 
| 350 | 
            +
                @threadEndMutex = Mutex.new
         | 
| 294 351 | 
             
                @maxThreads = 30  # allow NN processing threads to run at any time
         | 
| 295 352 | 
             
                @threadTimeoutInSeconds = 1800  # timeout after 30 minutes
         | 
| 296 353 | 
             
                @monitorPollIntervalInSeconds = 30 # period for displaying thread monitor
         | 
| @@ -298,7 +355,9 @@ class SSHotgun | |
| 298 355 | 
             
                logfilename = File.basename($0) + ".#{timestr}.log"
         | 
| 299 356 | 
             
                @filelogger = Logger.new(logfilename)
         | 
| 300 357 | 
             
                @isFileLogging = true
         | 
| 358 | 
            +
                @isDebug = false
         | 
| 301 359 | 
             
                @isStopOnNonZeroExit = false
         | 
| 360 | 
            +
                @exitCode = 0
         | 
| 302 361 | 
             
              end
         | 
| 303 362 |  | 
| 304 363 | 
             
              # Start the processing. In most cases, this call will be the last line in
         | 
| @@ -308,10 +367,11 @@ class SSHotgun | |
| 308 367 | 
             
                # trap ctrl-c, show stats and exit
         | 
| 309 368 | 
             
                trap("INT") {
         | 
| 310 369 | 
             
                  log "[_sshotgun_] info: Script terminated early by ctrl-c"
         | 
| 311 | 
            -
                   | 
| 312 | 
            -
                  exit
         | 
| 370 | 
            +
                  showRunStatsAndExit
         | 
| 313 371 | 
             
                }
         | 
| 372 | 
            +
             | 
| 314 373 | 
             
                @startTime = Time.now
         | 
| 374 | 
            +
                log "[_sshotgun_] info: SSHotgun version = " + @versionStr
         | 
| 315 375 | 
             
                log "[_sshotgun_] info: Started script " + $0 + " at " + @startTime.to_s
         | 
| 316 376 |  | 
| 317 377 | 
             
                # Display configuration info
         | 
| @@ -363,10 +423,8 @@ class SSHotgun | |
| 363 423 | 
             
                        aThread["hostname"] = hostdef.hostname  # store hostname time in threadlocal variable
         | 
| 364 424 | 
             
                        log "[_sshotgun_] info: Started thread for host = " + hostdef.hostname + " at " + aThread["threadstarttime"].to_s
         | 
| 365 425 | 
             
                        begin 
         | 
| 366 | 
            -
                          hostdef.startedProcess = true
         | 
| 367 426 | 
             
                          o = @processingclass.new(self, hostdef)
         | 
| 368 427 | 
             
                          o.doProcessing
         | 
| 369 | 
            -
                          hostdef.finishedProcess = true
         | 
| 370 428 | 
             
                        rescue => ex
         | 
| 371 429 | 
             
                          log "[_sshotgun_] stderr: Thread blew up for host = " + hostdef.hostname + ". ex = " + ex.inspect + "\n" + ex.backtrace.join("\n")
         | 
| 372 430 | 
             
                        end
         | 
| @@ -424,42 +482,27 @@ class SSHotgun | |
| 424 482 |  | 
| 425 483 | 
             
                # ended normally, show stats
         | 
| 426 484 | 
             
                log "[_sshotgun_] info: Script terminated normally"
         | 
| 427 | 
            -
                 | 
| 485 | 
            +
                showRunStatsAndExit
         | 
| 428 486 | 
             
              end
         | 
| 429 487 |  | 
| 430 | 
            -
              # Display  | 
| 431 | 
            -
              def  | 
| 432 | 
            -
                 | 
| 433 | 
            -
             | 
| 434 | 
            -
             | 
| 435 | 
            -
             | 
| 436 | 
            -
             | 
| 437 | 
            -
             | 
| 438 | 
            -
             | 
| 439 | 
            -
                   | 
| 440 | 
            -
             | 
| 441 | 
            -
                     | 
| 442 | 
            -
                  else
         | 
| 443 | 
            -
                    # started processing
         | 
| 444 | 
            -
                    if hostdef.hadNonZeroExitStatus
         | 
| 445 | 
            -
                      # one or more exit status were non-zero
         | 
| 446 | 
            -
                      hostarr << (hostdef.hostname + "(*)")
         | 
| 447 | 
            -
                    else
         | 
| 448 | 
            -
                      if hostdef.finishedProcess
         | 
| 449 | 
            -
                        # finished with no errors during processing
         | 
| 450 | 
            -
                        hostarr << hostdef.hostname
         | 
| 451 | 
            -
                      else
         | 
| 452 | 
            -
                        # Incomplete processing.  This usually means that the method threw
         | 
| 453 | 
            -
                        # an exception
         | 
| 454 | 
            -
                        hostarr << (hostdef.hostname + "(i)")
         | 
| 455 | 
            -
                      end
         | 
| 456 | 
            -
                    end
         | 
| 488 | 
            +
              # Display status and exit. May be called by pressing ctrl-c, fatal error or normal termination
         | 
| 489 | 
            +
              def showRunStatsAndExit
         | 
| 490 | 
            +
                @threadEndMutex.synchronize {
         | 
| 491 | 
            +
                  # Display script ending message
         | 
| 492 | 
            +
                  stopTime = Time.now
         | 
| 493 | 
            +
                  log "[_sshotgun_] info: Ended script " + $0 + " at " + stopTime.to_s
         | 
| 494 | 
            +
                  log "[_sshotgun_] info: Elapsed script " + $0 + " time " + (stopTime - @startTime).to_s + "s"
         | 
| 495 | 
            +
                  log "[_sshotgun_] info: Script exit code is = " + @exitCode.to_s
         | 
| 496 | 
            +
             | 
| 497 | 
            +
                  # Display status of each host
         | 
| 498 | 
            +
                  @hostlist.each do |hostdef| 
         | 
| 499 | 
            +
                    log "[_sshotgun_] status: " + hostdef.hostname + " = " + hostdef.status
         | 
| 457 500 | 
             
                  end
         | 
| 458 | 
            -
             | 
| 459 | 
            -
                 | 
| 460 | 
            -
                log "[_sshotgun_] info: Hosts summary = " + hostarr.join(", ")
         | 
| 501 | 
            +
                  exit @exitCode
         | 
| 502 | 
            +
                }
         | 
| 461 503 | 
             
              end
         | 
| 462 504 |  | 
| 505 | 
            +
             | 
| 463 506 | 
             
              # Log a string to the console and to the file logger
         | 
| 464 507 | 
             
              #
         | 
| 465 508 | 
             
              # s is the string to log
         | 
| @@ -472,4 +515,5 @@ class SSHotgun | |
| 472 515 | 
             
                  end
         | 
| 473 516 | 
             
                }
         | 
| 474 517 | 
             
              end
         | 
| 518 | 
            +
              
         | 
| 475 519 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: sshotgun
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              version: 1.0. | 
| 4 | 
            +
              version: 1.0.5
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors: 
         | 
| 7 7 | 
             
            - Vick Perry
         | 
| @@ -9,11 +9,12 @@ autorequire: | |
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 11 |  | 
| 12 | 
            -
            date: 2008-09- | 
| 12 | 
            +
            date: 2008-09-26 00:00:00 -07:00
         | 
| 13 13 | 
             
            default_executable: 
         | 
| 14 14 | 
             
            dependencies: 
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| 16 16 | 
             
              name: open4
         | 
| 17 | 
            +
              type: :runtime
         | 
| 17 18 | 
             
              version_requirement: 
         | 
| 18 19 | 
             
              version_requirements: !ruby/object:Gem::Requirement 
         | 
| 19 20 | 
             
                requirements: 
         | 
| @@ -23,6 +24,7 @@ dependencies: | |
| 23 24 | 
             
                version: 
         | 
| 24 25 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| 25 26 | 
             
              name: highline
         | 
| 27 | 
            +
              type: :runtime
         | 
| 26 28 | 
             
              version_requirement: 
         | 
| 27 29 | 
             
              version_requirements: !ruby/object:Gem::Requirement 
         | 
| 28 30 | 
             
                requirements: 
         | 
| @@ -68,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 68 70 | 
             
            requirements: []
         | 
| 69 71 |  | 
| 70 72 | 
             
            rubyforge_project: sshotgun
         | 
| 71 | 
            -
            rubygems_version: 1. | 
| 73 | 
            +
            rubygems_version: 1.2.0
         | 
| 72 74 | 
             
            signing_key: 
         | 
| 73 75 | 
             
            specification_version: 2
         | 
| 74 76 | 
             
            summary: A lib for writing server management scripts in Ruby. Useful for managing lots of servers.
         |