You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
551 lines
16 KiB
551 lines
16 KiB
# -*- tcl -*- |
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt |
|
# module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm |
|
# |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# (C) 2009 Jose F. Nieves |
|
# |
|
# @@ Meta Begin |
|
# Application punk::sshrun 999999.0a1.0 |
|
# Meta platform tcl |
|
# Meta license ISC |
|
# @@ Meta End |
|
|
|
# Copyright (c) 2009 Jose F. Nieves <nieves@ltp.uprrp.edu> |
|
# |
|
# Permission to use, copy, modify, and distribute this software for any |
|
# purpose with or without fee is hereby granted, provided that the above |
|
# copyright notice and this permission notice appear in all copies. |
|
# |
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# doctools header |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
#*** !doctools |
|
#[manpage_begin punkshell_module_punk::sshrun 0 999999.0a1.0] |
|
#[copyright "2009"] |
|
#[titledesc {Tcl procedures to execute tcl scripts in remote hosts}] [comment {-- Name section and table of contents description --}] |
|
#[moddesc {punk::sshrun tclssh clone}] [comment {-- Description at end of page heading --}] |
|
#[require punk::sshrun] |
|
#[keywords module ssh] |
|
#[description] |
|
#[para] This is a clone of tclssh by Jose F. Nieves |
|
#[para] The original repo is at: https://bitbucket.org/noaaport/tclssh/src/master/ |
|
#[para] This version is namespaced under punk::sshrun specifically for the Punk shell project - and may lag the original project or diverge. |
|
#[para] You are encouraged to use the original Tclssh source from the above URL for your own projects |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
#*** !doctools |
|
#[section Overview] |
|
#[para] overview of punk::sshrun |
|
#[para] SYNOPSIS |
|
#[para] package require punk::sshrun |
|
#[para] - |
|
#[para] punk::sshrun::connect [lb]-t <tclsh_name>[rb] [lb]-- <ssh_options>[rb] [lb]<user>@[rb]<host> |
|
#[para] Defaults: -t tclsh |
|
#[subsection Concepts] |
|
#[para] - |
|
|
|
####### original wiki documentation |
|
## Overview |
|
## Tclssh is a package of pure Tcl functions for executing Tcl scripts in a remote host. It is used by Nbsp and the WRFPak to offload some long-time running jobs in parallel to other computers. |
|
## |
|
## The remote host must be accessible via ssh keys without a password; that is, for example, the contents of your ./ssh/id_rsa.pub must be added to the .ssh/authorized_keys file in the remote host. |
|
## |
|
## The directory |
|
## |
|
## <prefix>/share/doc/examples |
|
## has several examples that can be used for testing and documentation. |
|
## |
|
## Example |
|
## The best way to explain how to use is by looking at one of them, ex-1.tcl. |
|
## |
|
## #!/usr/bin/tclsh |
|
## |
|
## package require ssh |
|
## |
|
## # |
|
## # Collect in the string "script" the script that will be sent to the |
|
## # remote (slave) host. |
|
## # |
|
## set script { |
|
## puts "I am a remote host named [info hostname] executing a script |
|
## that my master sent me. I am going to sent him back the results of some |
|
## commands. Here they are: |
|
## |
|
## The date is: [exec date] |
|
## My info is: [exec uname -a] |
|
## |
|
## Now I need to inform my master that I have finished. |
|
## |
|
## DONE 0" |
|
## }; |
|
## |
|
## set slave "is.1-loop.net"; # This is the remote host |
|
## |
|
## # |
|
## # If the tcl shell in the remote host is just "tclsh" (e.g. a linux system) |
|
## # the -t option (and its argment) can be omitted; otherwise it can be |
|
## # used to specify the name of the remote tcl shell. |
|
## # |
|
## ::ssh::connect -t tclsh8.6 -- $slave |
|
## ::ssh::push $slave $script |
|
## ::ssh::send $slave; |
|
## while {[::ssh::pop_line $slave line] >= 0} { |
|
## if {[regexp {^DONE\s+(\d+)} $line match code]} { |
|
## break; |
|
## } |
|
## puts $line; |
|
## } |
|
## ::ssh::disconnect $slave; |
|
## |
|
## puts "Slave finished with code: $code"; |
|
## Below is the output of executing ex-1.tcl from a machine named elite: |
|
## |
|
## [nieves@elite examples]$ ./ex-1.tcl |
|
## I am a remote host named is.1-loop.net executing a script |
|
## that my master sent me. I am going to sent him back the results of some |
|
## commands. Here they are: |
|
## |
|
## The date is: Sun Dec 9 17:35:43 AST 2012 |
|
## My info is: Linux is 2.6.32-308.8.2.el5.028stab101.1 #1 SMP Sun Jun 24 20:25:35 MSD 2012 i686 GNU/Linux |
|
## |
|
## Now I need to inform my master that I have finished. |
|
|
|
## Slave finished with code: 0 |
|
## [nieves@elite examples]$ |
|
## Several other examples in the |
|
## |
|
## <prefix>/share/doc/examples |
|
## directory illustrate more capabilities of this package. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
## Requirements |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
#*** !doctools |
|
#[subsection dependencies] |
|
#[para] packages used by punk::sshrun |
|
#[list_begin itemized] |
|
|
|
package require Tcl 8.6- |
|
package require cmdline |
|
#*** !doctools |
|
#[item] [package {Tcl 8.6}] |
|
#[item] [package {cmdline}] |
|
|
|
# #package require frobz |
|
# #*** !doctools |
|
# #[item] [package {frobz}] |
|
|
|
#*** !doctools |
|
#[list_end] |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
#*** !doctools |
|
#[section API] |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# oo::class namespace |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
namespace eval punk::sshrun::class { |
|
#*** !doctools |
|
#[subsection {Namespace punk::sshrun::class}] |
|
#[para] class definitions |
|
if {[info commands [namespace current]::interface_sample1] eq ""} { |
|
#*** !doctools |
|
#[list_begin enumerated] |
|
|
|
# oo::class create interface_sample1 { |
|
# #*** !doctools |
|
# #[enum] CLASS [class interface_sample1] |
|
# #[list_begin definitions] |
|
|
|
# method test {arg1} { |
|
# #*** !doctools |
|
# #[call class::interface_sample1 [method test] [arg arg1]] |
|
# #[para] test method |
|
# puts "test: $arg1" |
|
# } |
|
|
|
# #*** !doctools |
|
# #[list_end] [comment {-- end definitions interface_sample1}] |
|
# } |
|
|
|
#*** !doctools |
|
#[list_end] [comment {--- end class enumeration ---}] |
|
} |
|
} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# Base namespace |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
namespace eval punk::sshrun { |
|
namespace export * |
|
|
|
variable ssh; |
|
array set ssh {}; |
|
|
|
#*** !doctools |
|
#[subsection {Namespace punk::sshrun}] |
|
#[para] Core API functions for punk::sshrun |
|
#[list_begin definitions] |
|
|
|
|
|
proc connect {args} { |
|
#*** !doctools |
|
#[call connect [arg args]] |
|
#[para] Must be called first. |
|
#[para] This proc opens an io channel to the tclsh in the remote host (via ssh) that is kept in an internal variable for subsequent use. |
|
#[para] The file handle can be retrieved if desired through the command: get_filehandle {host} |
|
variable ssh; |
|
|
|
set usage {connect [-t <tclsh_name>] [-- <ssh_options>] |
|
[<user>@]<host>}; |
|
set optlist {{t.arg "tclsh"}}; |
|
|
|
array set option [::cmdline::getoptions args $optlist $usage]; |
|
set cmd [concat "|ssh" $args $option(t) 2>@ stdout]; |
|
set F [open $cmd r+]; |
|
|
|
set host [lindex $args end]; |
|
if {[regexp {(.*)@(.*)} $host match s1 s2]} { |
|
set user $s1; |
|
set host $s2; |
|
} |
|
|
|
# These are the only internal variables (apart from the "user" variables). |
|
set ssh($host,F) $F; |
|
set ssh($host,script) [list]; |
|
} |
|
|
|
proc disconnect {host} { |
|
#*** !doctools |
|
# [call disconnect [arg host]] |
|
# [para] Must be called last. Closes the filehandle opened by connect. |
|
variable ssh; |
|
|
|
system::_verify_connection $host; |
|
set status [catch { |
|
close $ssh($host,F); |
|
} errmsg]; |
|
|
|
unset ssh($host,F); |
|
unset ssh($host,script); |
|
|
|
if {$status != 0} { |
|
return -code error $errmsg; |
|
} |
|
} |
|
|
|
proc push {host script} { |
|
#*** !doctools |
|
# [call push [arg host] [arg script]] |
|
# [para] <script> can be any tcl code. |
|
# [para] For example, if the remote host is named "diablo" |
|
# [example { |
|
# ssh::push "diablo" "exec date" |
|
# ssh::push "diablo" "exec uname -a" |
|
# }] |
|
# [para] The commands are note executed immediately. Instead, the "push" proc simply accumulates them in a list that is sent to the host when the "send" procedure is executed. |
|
# [para] Each push proc inserts the newline '\n' character after each <script> |
|
# [para] In the above example. Internally, each <script> is a member of a list, and when the "send" proc is invoked the entire script is constructed as a "join <list> \n |
|
|
|
variable ssh; |
|
system::_verify_connection $host; |
|
lappend ssh($host,script) $script; |
|
} |
|
|
|
proc send {host} { |
|
#*** !doctools |
|
# [call send [arg host]] |
|
# [para]This proc does the equivalent of a |
|
# [example { |
|
# puts <filehandle> [join <script_list> \n] |
|
# flush <filehandle> |
|
# }] |
|
variable ssh; |
|
system::_verify_connection $host; |
|
|
|
set status [catch { |
|
puts $ssh($host,F) [join $ssh($host,script) "\n"]; |
|
flush $ssh($host,F); |
|
} errmsg]; |
|
|
|
set ssh($host,script) [list]; |
|
|
|
if {$status != 0} { |
|
return -code error $errmsg; |
|
} |
|
} |
|
proc send_exit {host} { |
|
#*** !doctools |
|
# [call send_exit [arg host]] |
|
# [para] This proc is similar to the above, but it "pushes" an exit command at the end of the script. The proc does the equivalent of |
|
# [example { |
|
# ssh::push <host> "exit" |
|
# ssh::send <host> |
|
# }] |
|
# [para] The net effect if this is that the remote host's tclsh will exit, so that the filehandle receives an eof and we can use |
|
# [example { |
|
# [read <filehandle>] |
|
# }] |
|
# [para]to read the entire output at once (see the pop proc below) |
|
|
|
push $host "exit"; |
|
send $host; |
|
} |
|
|
|
proc pop_line {host line_varname} { |
|
#*** !doctools |
|
# [call pop_line [arg host] [arg line_varname]] |
|
# [para]After executing a "send", this can be used to read one line of output. The proc does the equivalent of |
|
# [example { |
|
# [gets <filehandle> line] |
|
# }] |
|
upvar $line_varname line; |
|
variable ssh; |
|
|
|
system::_verify_connection $host; |
|
set r [gets $ssh($host,F) line]; |
|
return $r; |
|
} |
|
|
|
proc pop_all {host output_varname} { |
|
#*** !doctools |
|
# [call pop_all [arg host] [arg output_varname]] |
|
# [para]This proc does the equivalent of |
|
# [example { |
|
# while {[pop_line $host line] >=0} { |
|
# puts $line; |
|
# } |
|
# }] |
|
# [para] but all the output is returned as one string in output_varname. |
|
# [para]It should be used only when we know that the remote host's tclsh will exit, so that the above code will detect the eof and exit |
|
# [para](see the send_exit proc above) |
|
# [para]The function returns the number of lines read (0 if nothing is read before encoutering eof) |
|
# |
|
upvar $output_varname output; |
|
variable ssh; |
|
|
|
system::_verify_connection $host; |
|
|
|
set r 0; |
|
set output_list [list]; |
|
while {[pop_line $host line] >= 0} { |
|
incr r; |
|
lappend output_list $line; |
|
} |
|
set output [join $output_list "\n"]; |
|
|
|
return $r; |
|
} |
|
proc pop_read {host numbytes output_varname} { |
|
#*** !doctools |
|
# [call pop_read [arg host] [arg numbytes] [arg output_varname]] |
|
# [para] Returns: numbytes read. If numbytes is not positive, then read is called without the numbytes argument. |
|
upvar $output_varname output; |
|
variable ssh; |
|
|
|
system::_verify_connection $host; |
|
|
|
if {$numbytes <= 0} { |
|
set output [read $ssh($host,F)]; |
|
} else { |
|
set output [read $ssh($host,F) $numbytes]; |
|
} |
|
|
|
return [string length $output]; |
|
} |
|
|
|
proc hfileevent {host readable_writable script} { |
|
#*** !doctools |
|
# [call hfileevent [arg host] [arg readable_writable] [arg script]] |
|
# [para] Equivalent to: |
|
# [example { |
|
# fileevent <filehandle> $readable_writable $script |
|
# }] |
|
variable ssh; |
|
system::_verify_connection $host; |
|
chan event $ssh($host,F) $readable_writable $script; |
|
} |
|
|
|
proc hfconfigure {host args} { |
|
#*** !doctools |
|
# [call hconfigure [arg host] [arg args]] |
|
variable ssh; |
|
system::_verify_connection $host; |
|
eval chan configure $ssh($host,F) $args; |
|
} |
|
|
|
proc rexec {host script output_varname} { |
|
#*** !doctools |
|
# [call rexec [arg host] [arg script] [arg output_varname]] |
|
# [para] shortcut for: |
|
# [example { |
|
# ssh::rexec_nopop $host $script |
|
# ssh::pop_all $host outputvar |
|
# }] |
|
upvar $output_varname output; |
|
rexec_nopop $host $script; |
|
pop_all $host output; |
|
} |
|
|
|
proc rexec_nopop {host script} { |
|
push $host $script; |
|
send_exit $host; |
|
} |
|
|
|
# |
|
# Utility |
|
# |
|
proc set_var {host var val} { |
|
variable ssh; |
|
# These functions can be used even when there is no valid connection |
|
# and therefore we do not call _verify_connection here. |
|
set ssh($host,user,$var) $val; |
|
} |
|
|
|
proc get_var {host var} { |
|
variable ssh; |
|
if {[info exists ssh($host,user,$var)] == 0} { |
|
return -code error "$var not defined"; |
|
} |
|
return $ssh($host,user,$var); |
|
} |
|
|
|
proc incr_var {host var {step 1}} { |
|
variable ssh; |
|
if {[info exists ssh($host,user,$var)] == 0} { |
|
return -code error "$var not defined"; |
|
} |
|
puts "incr ssh($host,user,$var) by $step"; |
|
incr ssh($host,user,$var) $step; |
|
} |
|
|
|
proc set_lvar {var val} { |
|
set host "localhost"; |
|
set_var $host $var $val; |
|
} |
|
|
|
proc get_lvar {var} { |
|
set host "localhost"; |
|
set status [catch { |
|
set r [get_var $host $var]; |
|
} errmsg]; |
|
|
|
if {$status != 0} { |
|
return -code error $errmsg; |
|
} |
|
return $r; |
|
} |
|
|
|
proc incr_lvar {var {step 1}} { |
|
set host "localhost"; |
|
incr_var $host $var $step; |
|
} |
|
|
|
# |
|
# low level |
|
# |
|
proc get_filehandle {host} { |
|
#*** !doctools |
|
# [call get_filehandle [arg host]] |
|
variable ssh; |
|
system::_verify_connection $host; |
|
|
|
return $ssh($host,F); |
|
} |
|
|
|
#*** !doctools |
|
#[list_end] [comment {--- end definitions namespace punk::sshrun ---}] |
|
} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# Secondary API namespace |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
namespace eval punk::sshrun::lib { |
|
namespace export * |
|
namespace path [namespace parent] |
|
#*** !doctools |
|
#[subsection {Namespace punk::sshrun::lib}] |
|
#[para] Secondary functions that are part of the API |
|
#[list_begin definitions] |
|
|
|
#proc utility1 {p1 args} { |
|
# #*** !doctools |
|
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] |
|
# #[para]Description of utility1 |
|
# return 1 |
|
#} |
|
|
|
|
|
|
|
#*** !doctools |
|
#[list_end] [comment {--- end definitions namespace punk::sshrun::lib ---}] |
|
} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
#*** !doctools |
|
#[section Internal] |
|
namespace eval punk::sshrun::system { |
|
#*** !doctools |
|
#[subsection {Namespace punk::sshrun::system}] |
|
#[para] Internal functions that are not part of the API |
|
|
|
# |
|
# private |
|
# |
|
proc _verify_connection {host} { |
|
upvar ::punk::sshrun::ssh ssh |
|
#variable ssh; |
|
if {[info exists ssh($host,F)]} { |
|
return 1; |
|
} |
|
return -code error "There is no connection to $host."; |
|
} |
|
|
|
|
|
} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
## Ready |
|
package provide punk::sshrun [namespace eval punk::sshrun { |
|
variable pkg punk::sshrun |
|
variable version |
|
set version 999999.0a1.0 |
|
}] |
|
return |
|
|
|
#*** !doctools |
|
#[manpage_end] |
|
|
|
|