symphony-ssh 0.1.0
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.
- checksums.yaml +7 -0
- data/README.rdoc +113 -0
- data/lib/symphony/tasks/ssh.rb +166 -0
- data/lib/symphony/tasks/sshscript.rb +183 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ecf234436b7966c1062b9d13dbfe4c71015c29fe
|
4
|
+
data.tar.gz: 89636ab1155f42de42d0bc9f74e45f6b51299e18
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a6b1681a726bbb2a554f2429c5dd11f3695e149c1341991088ad4deac99f0edd7728fcaca02480362cce98218dbe7b1e13dbfc1bcc01ef468df8199f3d9746df
|
7
|
+
data.tar.gz: bcc024b3faed110b348714ee2925d13544448da998f5a9d145ff1d017ed69c8b244047fe18019c6234aa8143c05aa3db1d5803e9430da9724cbd3808bf6d1df6
|
data/README.rdoc
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
= symphony-ssh
|
3
|
+
|
4
|
+
== Description
|
5
|
+
|
6
|
+
This is a small collection of base classes used for interacting with
|
7
|
+
remote machines over ssh. With them, you can use AMQP (via Symphony) to
|
8
|
+
run batch commands, execute templates as scripts, and perform any
|
9
|
+
batch/remoting stuff you can think of without the need of a separate
|
10
|
+
client agent.
|
11
|
+
|
12
|
+
These classes assume you have a user that can connect and login to
|
13
|
+
remote machines using a password-less ssh keypair. They are not meant
|
14
|
+
to be used directly. Subclass them!
|
15
|
+
|
16
|
+
See the rdoc for additional information and examples.
|
17
|
+
|
18
|
+
|
19
|
+
== Options
|
20
|
+
|
21
|
+
Symphony-ssh uses
|
22
|
+
Configurability[https://rubygems.org/gems/configurability] to determine
|
23
|
+
behavior. The configuration is a YAML[http://www.yaml.org/] file.
|
24
|
+
|
25
|
+
symphony_ssh:
|
26
|
+
path: /usr/bin/ssh
|
27
|
+
user: root
|
28
|
+
key: /path/to/a/private_key.rsa
|
29
|
+
opts:
|
30
|
+
- -e
|
31
|
+
- none
|
32
|
+
- -T
|
33
|
+
- -x
|
34
|
+
- -o
|
35
|
+
- CheckHostIP=no'
|
36
|
+
- -o
|
37
|
+
- BatchMode=yes'
|
38
|
+
- -o
|
39
|
+
- StrictHostKeyChecking=no
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
=== path
|
44
|
+
|
45
|
+
The absolute path to the ssh binary.
|
46
|
+
|
47
|
+
=== user
|
48
|
+
|
49
|
+
The default user to connect to remote hosts with. This can be
|
50
|
+
changes per connection in the AMQP payload.
|
51
|
+
|
52
|
+
=== key
|
53
|
+
|
54
|
+
An absolute path to a password-less ssh private key.
|
55
|
+
|
56
|
+
=== opts
|
57
|
+
|
58
|
+
SSH client options, passed to the ssh binary on the command line. Note
|
59
|
+
that the defaults have been tested fairly extensively, these are just
|
60
|
+
exposed if you have very specific needs and you know what you're doing.
|
61
|
+
|
62
|
+
|
63
|
+
== Installation
|
64
|
+
|
65
|
+
gem install symphony-ssh
|
66
|
+
|
67
|
+
|
68
|
+
== Contributing
|
69
|
+
|
70
|
+
You can check out the current development source with Mercurial via its
|
71
|
+
{project page}[http://bitbucket.org/mahlon/symphony-ssh].
|
72
|
+
|
73
|
+
After checking out the source, run:
|
74
|
+
|
75
|
+
$ rake
|
76
|
+
|
77
|
+
This task will run the tests/specs and generate the API documentation.
|
78
|
+
|
79
|
+
If you use {rvm}[http://rvm.io/], entering the project directory will
|
80
|
+
install any required development dependencies.
|
81
|
+
|
82
|
+
|
83
|
+
== License
|
84
|
+
|
85
|
+
Copyright (c) 2014, Mahlon E. Smith and Michael Granger
|
86
|
+
All rights reserved.
|
87
|
+
|
88
|
+
Redistribution and use in source and binary forms, with or without
|
89
|
+
modification, are permitted provided that the following conditions are met:
|
90
|
+
|
91
|
+
* Redistributions of source code must retain the above copyright notice,
|
92
|
+
this list of conditions and the following disclaimer.
|
93
|
+
|
94
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
95
|
+
this list of conditions and the following disclaimer in the documentation
|
96
|
+
and/or other materials provided with the distribution.
|
97
|
+
|
98
|
+
* Neither the name of the author/s, nor the names of the project's
|
99
|
+
contributors may be used to endorse or promote products derived from this
|
100
|
+
software without specific prior written permission.
|
101
|
+
|
102
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
103
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
104
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
105
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
106
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
107
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
108
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
109
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
110
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
111
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
112
|
+
|
113
|
+
|
@@ -0,0 +1,166 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
|
4
|
+
require 'shellwords'
|
5
|
+
require 'symphony/task' unless defined?( Symphony::Task )
|
6
|
+
|
7
|
+
|
8
|
+
### A base class for connecting to remote hosts, running arbitrary
|
9
|
+
### commands, and collecting output.
|
10
|
+
###
|
11
|
+
### This isn't designed to be used directly. To use this in your
|
12
|
+
### environment, you'll want to subclass it, add the behaviors
|
13
|
+
### that make sense for you, then super() back to the parent in the
|
14
|
+
### #work method.
|
15
|
+
###
|
16
|
+
### It expects the payload to contain the following keys:
|
17
|
+
###
|
18
|
+
### host: (required) The hostname to connect to
|
19
|
+
### command: (required) The command to run on the remote host
|
20
|
+
### port: (optional) The port to connect to (defaults to 22)
|
21
|
+
### opts: (optional) Explicit SSH client options
|
22
|
+
### user: (optional) The user to connect as (defaults to root)
|
23
|
+
### key: (optional) The path to an SSH private key
|
24
|
+
###
|
25
|
+
###
|
26
|
+
### Additionally, this class responds to the 'symphony_ssh' configurability
|
27
|
+
### key. Currently, you can set the 'path' argument, which is the
|
28
|
+
### full path to the local ssh binary (defaults to '/usr/bin/ssh') and
|
29
|
+
### override the default ssh user, key, and client opts.
|
30
|
+
###
|
31
|
+
### Textual output of the command is stored in the @output instance variable.
|
32
|
+
###
|
33
|
+
###
|
34
|
+
### require 'symphony'
|
35
|
+
### require 'symphony/tasks/ssh'
|
36
|
+
###
|
37
|
+
### class YourTask < Symphony::Task::SSH
|
38
|
+
### timeout 5
|
39
|
+
### subscribe_to 'ssh.command'
|
40
|
+
###
|
41
|
+
### def work( payload, metadata )
|
42
|
+
### status = super
|
43
|
+
### puts "Remote host said: %s" % [ @output ]
|
44
|
+
### return status.success?
|
45
|
+
### end
|
46
|
+
### end
|
47
|
+
###
|
48
|
+
class Symphony::Task::SSH < Symphony::Task
|
49
|
+
extend Configurability
|
50
|
+
config_key :symphony_ssh
|
51
|
+
|
52
|
+
# SSH default options.
|
53
|
+
#
|
54
|
+
CONFIG_DEFAULTS = {
|
55
|
+
:path => '/usr/bin/ssh',
|
56
|
+
:opts => [
|
57
|
+
'-e', 'none',
|
58
|
+
'-T',
|
59
|
+
'-x',
|
60
|
+
'-q',
|
61
|
+
'-o', 'CheckHostIP=no',
|
62
|
+
'-o', 'BatchMode=yes',
|
63
|
+
'-o', 'StrictHostKeyChecking=no'
|
64
|
+
],
|
65
|
+
:user => 'root',
|
66
|
+
:key => nil
|
67
|
+
}
|
68
|
+
|
69
|
+
# SSH "informative" stdout output that should be cleaned from the
|
70
|
+
# command output.
|
71
|
+
SSH_CLEANUP = %r/Warning: no access to tty|Thus no job control in this shell/
|
72
|
+
|
73
|
+
class << self
|
74
|
+
# The full path to the ssh binary.
|
75
|
+
attr_reader :path
|
76
|
+
|
77
|
+
# A default set of ssh client options when connecting
|
78
|
+
# to remote hosts.
|
79
|
+
attr_reader :opts
|
80
|
+
|
81
|
+
# The default user to use when connecting. If unset, 'root' is used.
|
82
|
+
attr_reader :user
|
83
|
+
|
84
|
+
# An absolute path to a password-free ssh private key.
|
85
|
+
attr_reader :key
|
86
|
+
end
|
87
|
+
|
88
|
+
### Configurability API.
|
89
|
+
###
|
90
|
+
def self::configure( config=nil )
|
91
|
+
config = self.defaults.merge( config || {} )
|
92
|
+
@path = config.delete( :path )
|
93
|
+
@opts = config.delete( :opts )
|
94
|
+
@user = config.delete( :user )
|
95
|
+
@key = config.delete( :key )
|
96
|
+
super
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
### Perform the ssh connection, passing the command to the pipe
|
101
|
+
### and retreiving any output from the remote end.
|
102
|
+
###
|
103
|
+
def work( payload, metadata )
|
104
|
+
command = payload[ 'command' ]
|
105
|
+
raise ArgumentError, "Missing required option 'command'" unless command
|
106
|
+
raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ]
|
107
|
+
|
108
|
+
exitcode = self.open_connection( payload, metadata ) do |reader, writer|
|
109
|
+
self.log.debug "Writing command #{command}..."
|
110
|
+
writer.puts( command )
|
111
|
+
self.log.debug " closing child's writer."
|
112
|
+
writer.close
|
113
|
+
self.log.debug " reading from child."
|
114
|
+
reader.read
|
115
|
+
end
|
116
|
+
|
117
|
+
self.log.debug "SSH exited: %d" % [ exitcode ]
|
118
|
+
return exitcode
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
#########
|
123
|
+
protected
|
124
|
+
#########
|
125
|
+
|
126
|
+
### Call ssh and yield the remote IO objects to the caller,
|
127
|
+
### cleaning up afterwards.
|
128
|
+
###
|
129
|
+
def open_connection( payload, metadata=nil )
|
130
|
+
raise LocalJumpError, "no block given" unless block_given?
|
131
|
+
@output = ''
|
132
|
+
|
133
|
+
port = payload[ 'port' ] || 22
|
134
|
+
opts = payload[ 'opts' ] || Symphony::Task::SSH.opts
|
135
|
+
user = payload[ 'user' ] || Symphony::Task::SSH.user
|
136
|
+
key = payload[ 'key' ] || Symphony::Task::SSH.key
|
137
|
+
|
138
|
+
cmd = []
|
139
|
+
cmd << Symphony::Task::SSH.path
|
140
|
+
cmd += Symphony::Task::SSH.opts
|
141
|
+
|
142
|
+
cmd << '-p' << port.to_s
|
143
|
+
cmd << '-i' << key if key
|
144
|
+
cmd << '-l' << user
|
145
|
+
cmd << payload[ 'host' ]
|
146
|
+
cmd.flatten!
|
147
|
+
self.log.debug "Running SSH command with: %p" % [ Shellwords.shelljoin(cmd) ]
|
148
|
+
|
149
|
+
parent_reader, child_writer = IO.pipe
|
150
|
+
child_reader, parent_writer = IO.pipe
|
151
|
+
|
152
|
+
pid = spawn( *cmd, :out => child_writer, :in => child_reader, :close_others => true )
|
153
|
+
child_writer.close
|
154
|
+
child_reader.close
|
155
|
+
|
156
|
+
self.log.debug "Yielding back to the run block."
|
157
|
+
@output = yield( parent_reader, parent_writer )
|
158
|
+
@output = @output.split("\n").reject{|l| l =~ SSH_CLEANUP }.join
|
159
|
+
self.log.debug " run block done."
|
160
|
+
|
161
|
+
pid, status = Process.waitpid2( pid )
|
162
|
+
return status
|
163
|
+
end
|
164
|
+
|
165
|
+
end # Symphony::Task::SSH
|
166
|
+
|
@@ -0,0 +1,183 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
|
4
|
+
require 'net/ssh'
|
5
|
+
require 'net/sftp'
|
6
|
+
require 'tmpdir'
|
7
|
+
require 'inversion'
|
8
|
+
require 'symphony'
|
9
|
+
require 'symphony/task'
|
10
|
+
|
11
|
+
|
12
|
+
### A base class for connecting to a remote host, then uploading and
|
13
|
+
### executing an Inversion templated script.
|
14
|
+
###
|
15
|
+
### This isn't designed to be used directly. To use this in your
|
16
|
+
### environment, you'll want to subclass it, add the behaviors
|
17
|
+
### that make sense for you, then super() back to the parent in the
|
18
|
+
### #work method.
|
19
|
+
###
|
20
|
+
### It expects the payload to contain the following keys:
|
21
|
+
###
|
22
|
+
### host: (required) The hostname to connect to
|
23
|
+
### template: (required) A path to the Inversion templated script
|
24
|
+
### port: (optional) The port to connect to (defaults to 22)
|
25
|
+
### user: (optional) The user to connect as (defaults to root)
|
26
|
+
### key: (optional) The path to an SSH private key
|
27
|
+
### attributes: (optional) Additional data to attach to the template
|
28
|
+
### nocleanup: (optional) Leave the remote script after execution? (default to false)
|
29
|
+
###
|
30
|
+
###
|
31
|
+
### Additionally, this class responds to the 'symphony_ssh' configurability
|
32
|
+
### key. Currently, you can override the default ssh user and private key.
|
33
|
+
###
|
34
|
+
### Textual output of the command is stored in the @output instance variable.
|
35
|
+
###
|
36
|
+
###
|
37
|
+
### require 'symphony'
|
38
|
+
### require 'symphony/tasks/sshscript'
|
39
|
+
###
|
40
|
+
### class YourTask < Symphony::Task::SSHScript
|
41
|
+
### timeout 30
|
42
|
+
### subscribe_to 'ssh.script.*'
|
43
|
+
###
|
44
|
+
### def work( payload, metadata )
|
45
|
+
### status = super
|
46
|
+
### puts "Remote script said: %s" % [ @output ]
|
47
|
+
### return status.success?
|
48
|
+
### end
|
49
|
+
### end
|
50
|
+
###
|
51
|
+
class Symphony::Task::SSHScript < Symphony::Task
|
52
|
+
extend Configurability
|
53
|
+
config_key :symphony_ssh
|
54
|
+
|
55
|
+
# Template config
|
56
|
+
#
|
57
|
+
TEMPLATE_OPTS = {
|
58
|
+
:ignore_unknown_tags => false,
|
59
|
+
:on_render_error => :propagate,
|
60
|
+
:strip_tag_lines => true
|
61
|
+
}
|
62
|
+
|
63
|
+
# The defaults to use when connecting via SSH
|
64
|
+
#
|
65
|
+
DEFAULT_SSH_OPTIONS = {
|
66
|
+
:auth_methods => [ 'publickey' ],
|
67
|
+
:compression => true,
|
68
|
+
:config => false,
|
69
|
+
:keys_only => true,
|
70
|
+
:paranoid => false,
|
71
|
+
:global_known_hosts_file => '/dev/null',
|
72
|
+
:user_known_hosts_file => '/dev/null'
|
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 = self.defaults.merge( config || {} )
|
94
|
+
@user = config.delete( :user )
|
95
|
+
@key = config.delete( :key )
|
96
|
+
super
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
### Perform the ssh connection, render the template, send it, and
|
101
|
+
### execute it.
|
102
|
+
###
|
103
|
+
def work( payload, metadata )
|
104
|
+
template = payload[ 'template' ]
|
105
|
+
attributes = payload[ 'attributes' ] || {}
|
106
|
+
port = payload[ 'port' ] || 22
|
107
|
+
user = payload[ 'user' ] || Symphony::Task::SSHScript.user
|
108
|
+
key = payload[ 'key' ] || Symphony::Task::SSHScript.key
|
109
|
+
nocleanup = payload[ 'nocleanup' ]
|
110
|
+
|
111
|
+
raise ArgumentError, "Missing required option 'command'" unless template
|
112
|
+
raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ]
|
113
|
+
|
114
|
+
remote_filename = self.make_remote_filename( template )
|
115
|
+
source = self.generate_script( template, attributes )
|
116
|
+
|
117
|
+
ssh_options = DEFAULT_SSH_OPTIONS.merge( :port => port, :keys => [key] )
|
118
|
+
ssh_options.merge!(
|
119
|
+
:logger => Loggability[ Net::SSH ],
|
120
|
+
:verbose => :debug
|
121
|
+
) if payload[ 'debug' ]
|
122
|
+
|
123
|
+
Net::SSH.start( payload['host'], user, ssh_options ) do |conn|
|
124
|
+
self.log.debug "Uploading script (%d bytes) to %s:%s." %
|
125
|
+
[ source.bytesize, payload['host'], remote_filename ]
|
126
|
+
self.upload_script( conn, source, remote_filename )
|
127
|
+
self.log.debug " done with the upload."
|
128
|
+
|
129
|
+
self.run_script( conn, remote_filename, nocleanup )
|
130
|
+
self.log.debug "Output was:\n#{@output}"
|
131
|
+
end
|
132
|
+
|
133
|
+
return true
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
#########
|
138
|
+
protected
|
139
|
+
#########
|
140
|
+
|
141
|
+
### Generate a unique filename for the script on the remote host,
|
142
|
+
### based on +template+ name.
|
143
|
+
###
|
144
|
+
def make_remote_filename( template )
|
145
|
+
basename = File.basename( template, File.extname(template) )
|
146
|
+
tmpname = Dir::Tmpname.make_tmpname( basename, rand(10000) )
|
147
|
+
|
148
|
+
return "/tmp/#{tmpname}"
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
### Generate a script by loading the script +template+, populating it with
|
153
|
+
### +attributes+, and returning the rendered output.
|
154
|
+
###
|
155
|
+
def generate_script( template, attributes )
|
156
|
+
tmpl = Inversion::Template.load( template, TEMPLATE_OPTS )
|
157
|
+
tmpl.attributes.merge!( attributes )
|
158
|
+
tmpl.task = self
|
159
|
+
|
160
|
+
return tmpl.render
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
### Upload the templated +source+ via the ssh +conn+ to an
|
165
|
+
### executable file named +remote_filename+.
|
166
|
+
###
|
167
|
+
def upload_script( conn, source, remote_filename )
|
168
|
+
conn.sftp.file.open( remote_filename, "w", 0755 ) do |fh|
|
169
|
+
fh.print( source )
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
### Run the +remote_filename+ via the ssh +conn+. The script
|
175
|
+
### will be deleted automatically unless +nocleanup+ is true.
|
176
|
+
###
|
177
|
+
def run_script( conn, remote_filename, nocleanup=false )
|
178
|
+
@output = conn.exec!( remote_filename )
|
179
|
+
conn.exec!( "rm #{remote_filename}" ) unless nocleanup
|
180
|
+
end
|
181
|
+
|
182
|
+
end # Symphony::Task::SSHScript
|
183
|
+
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: symphony-ssh
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mahlon E. Smith <mahlon@martini.nu>
|
8
|
+
- Michael Granger <ged@faeriemud.org>
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: symphony
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ~>
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0.6'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0.6'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: net-ssh
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.9'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.9'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: net-sftp
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '2.1'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '2.1'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '3.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: simplecov
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.8'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ~>
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.8'
|
84
|
+
description: |
|
85
|
+
A small collection of base classes used for interacting with remote
|
86
|
+
machines over ssh. With them, you can use AMQP (via Symphony) to
|
87
|
+
run batch commands, execute templates as scripts, and perform any
|
88
|
+
batch/remoting stuff you can think of without the need of separate
|
89
|
+
client agents.
|
90
|
+
email: mahlon@martini.nu
|
91
|
+
executables: []
|
92
|
+
extensions: []
|
93
|
+
extra_rdoc_files: []
|
94
|
+
files:
|
95
|
+
- README.rdoc
|
96
|
+
- lib/symphony/tasks/ssh.rb
|
97
|
+
- lib/symphony/tasks/sshscript.rb
|
98
|
+
homepage: http://projects.martini.nu/ruby-modules
|
99
|
+
licenses:
|
100
|
+
- BSD
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.0.0
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: 2.0.3
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.2.2
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Base classes for using Symphony with ssh.
|
122
|
+
test_files: []
|