24 ### port: (optional) The port to connect to (defaults to 22) |
25 ### port: (optional) The port to connect to (defaults to 22) |
25 ### user: (optional) The user to connect as (defaults to root) |
26 ### user: (optional) The user to connect as (defaults to root) |
26 ### key: (optional) The path to an SSH private key |
27 ### key: (optional) The path to an SSH private key |
27 ### attributes: (optional) Additional data to attach to the template |
28 ### attributes: (optional) Additional data to attach to the template |
28 ### nocleanup: (optional) Leave the remote script after execution? (default to false) |
29 ### nocleanup: (optional) Leave the remote script after execution? (default to false) |
|
30 ### tempdir: (optional) The destination temp directory. (defaults to /tmp) |
29 ### |
31 ### |
30 ### |
32 ### |
31 ### Additionally, this class responds to the 'symphony_ssh' configurability |
33 ### Additionally, this class responds to the 'symphony.ssh' configurability |
32 ### key. Currently, you can override the default ssh user and private key. |
34 ### key. Currently, you can override the default ssh user and private key. |
33 ### |
35 ### |
34 ### Textual output of the command is stored in the @output instance variable. |
36 ### Textual output of the command is stored in the @output instance variable. |
35 ### |
37 ### |
36 ### |
38 ### |
47 ### return status.success? |
49 ### return status.success? |
48 ### end |
50 ### end |
49 ### end |
51 ### end |
50 ### |
52 ### |
51 class Symphony::Task::SSHScript < Symphony::Task |
53 class Symphony::Task::SSHScript < Symphony::Task |
52 extend Configurability |
|
53 config_key :symphony_ssh |
|
54 |
54 |
55 # Template config |
55 # Template config |
56 # |
56 # |
57 TEMPLATE_OPTS = { |
57 TEMPLATE_OPTS = { |
58 :ignore_unknown_tags => false, |
58 ignore_unknown_tags: false, |
59 :on_render_error => :propagate, |
59 on_render_error: :propagate, |
60 :strip_tag_lines => true |
60 strip_tag_lines: true |
61 } |
61 } |
62 |
62 |
63 # The defaults to use when connecting via SSH |
63 # The defaults to use when connecting via SSH |
64 # |
64 # |
65 DEFAULT_SSH_OPTIONS = { |
65 DEFAULT_SSH_OPTIONS = { |
66 :auth_methods => [ 'publickey' ], |
66 auth_methods: [ 'publickey' ], |
67 :compression => true, |
67 compression: true, |
68 :config => false, |
68 config: false, |
69 :keys_only => true, |
69 keys_only: true, |
70 :paranoid => false, |
70 verify_host_key: :never, |
71 :global_known_hosts_file => '/dev/null', |
71 global_known_hosts_file: '/dev/null', |
72 :user_known_hosts_file => '/dev/null' |
72 user_known_hosts_file: '/dev/null' |
73 } |
73 } |
74 |
|
75 # SSH default options. |
|
76 # |
|
77 CONFIG_DEFAULTS = { |
|
78 :user => 'root', |
|
79 :key => nil |
|
80 } |
|
81 |
|
82 class << self |
|
83 # The default user to use when connecting. If unset, 'root' is used. |
|
84 attr_reader :user |
|
85 |
|
86 # An absolute path to a password-free ssh private key. |
|
87 attr_reader :key |
|
88 end |
|
89 |
|
90 ### Configurability API. |
|
91 ### |
|
92 def self::configure( config=nil ) |
|
93 config = Symphony::Task::SSHScript.defaults.merge( config || {} ) |
|
94 @user = config.delete( :user ) |
|
95 @key = config.delete( :key ) |
|
96 super |
|
97 end |
|
98 |
74 |
99 |
75 |
100 ### Perform the ssh connection, render the template, send it, and |
76 ### Perform the ssh connection, render the template, send it, and |
101 ### execute it. |
77 ### execute it. |
102 ### |
78 ### |
103 def work( payload, metadata ) |
79 def work( payload, metadata ) |
104 template = payload[ 'template' ] |
80 template = payload[ 'template' ] |
105 attributes = payload[ 'attributes' ] || {} |
81 attributes = payload[ 'attributes' ] || {} |
106 port = payload[ 'port' ] || 22 |
82 port = payload[ 'port' ] || 22 |
107 user = payload[ 'user' ] || Symphony::Task::SSHScript.user |
83 user = payload[ 'user' ] || Symphony::Task::SSH.user |
108 key = payload[ 'key' ] || Symphony::Task::SSHScript.key |
84 key = payload[ 'key' ] || Symphony::Task::SSH.key |
109 nocleanup = payload[ 'nocleanup' ] |
85 nocleanup = payload[ 'nocleanup' ] |
|
86 tempdir = payload[ 'tempdir' ] || '/tmp' |
110 |
87 |
111 raise ArgumentError, "Missing required option 'template'" unless template |
88 raise ArgumentError, "Missing required option 'template'" unless template |
112 raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ] |
89 raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ] |
113 |
90 |
114 remote_filename = self.make_remote_filename( template ) |
91 remote_filename = self.make_remote_filename( template, tempdir ) |
115 source = self.generate_script( template, attributes ) |
92 source = self.generate_script( template, attributes ) |
116 |
93 |
117 ssh_options = DEFAULT_SSH_OPTIONS.merge( :port => port, :keys => [key] ) |
94 ssh_options = DEFAULT_SSH_OPTIONS.merge( port: port, keys: Array(key) ) |
118 ssh_options.merge!( |
95 ssh_options.merge!( |
119 :logger => Loggability[ Net::SSH ], |
96 logger: Loggability[ Net::SSH ], |
120 :verbose => :debug |
97 verbose: :debug |
121 ) if payload[ 'debug' ] |
98 ) if payload[ 'debug' ] |
122 |
99 |
123 Net::SSH.start( payload['host'], user, ssh_options ) do |conn| |
100 Net::SSH.start( payload['host'], user, ssh_options ) do |conn| |
124 self.log.debug "Uploading script (%d bytes) to %s:%s." % |
101 self.log.debug "Uploading script (%d bytes) to %s:%s." % |
125 [ source.bytesize, payload['host'], remote_filename ] |
102 [ source.bytesize, payload['host'], remote_filename ] |
139 ######### |
116 ######### |
140 |
117 |
141 ### Generate a unique filename for the script on the remote host, |
118 ### Generate a unique filename for the script on the remote host, |
142 ### based on +template+ name. |
119 ### based on +template+ name. |
143 ### |
120 ### |
144 def make_remote_filename( template ) |
121 def make_remote_filename( template, tempdir="/tmp" ) |
145 basename = File.basename( template, File.extname(template) ) |
122 basename = File.basename( template, File.extname(template) ) |
146 tmpname = Dir::Tmpname.make_tmpname( basename, rand(10000) ) |
123 tmpname = "%s/%s-%s" % [ |
|
124 tempdir, |
|
125 basename, |
|
126 SecureRandom.hex( 6 ) |
|
127 ] |
147 |
128 |
148 return "/tmp/#{tmpname}" |
129 return tmpname |
149 end |
130 end |
150 |
131 |
151 |
132 |
152 ### Generate a script by loading the script +template+, populating it with |
133 ### Generate a script by loading the script +template+, populating it with |
153 ### +attributes+, and returning the rendered output. |
134 ### +attributes+, and returning the rendered output. |