shexy 0.2 → 0.3

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.
@@ -0,0 +1,9 @@
1
+ # 0.3
2
+
3
+ * Bug fixes
4
+ * Shexy should be thread safe now
5
+ * Added some more methods (see README)
6
+ - Shexy.permit_root_login
7
+ - Shexy.copy_ssh_pubkey
8
+ - Shexy.batch
9
+
data/README.md CHANGED
@@ -26,10 +26,48 @@ and added them to the SSH agent (ssh-add ~/.ssh/my-priv-key):
26
26
  end
27
27
  Shexy.exe 'echo hello' # no need to add host/user again
28
28
 
29
+
30
+ Specify key to use:
31
+
32
+ Shexy.key = '~/.ssh/id_rsa'
33
+ out, err, exit_code = Shexy.exe 'test@test-host', 'ls -la'
34
+
29
35
  Copying files (local -> remote):
30
36
 
31
37
  Shexy.copy_to 'test@test-host', '/home/rubiojr/my-uber-file', '/tmp/'
32
38
 
39
+ Batch mode:
40
+
41
+ Shexy.batch do
42
+ script <<-EOH
43
+ echo > /tmp/foo
44
+ ls -la
45
+ sed -i s/foo/bar/ /etc/foo
46
+ EOH
47
+ end
48
+
49
+ Use sudo:
50
+
51
+ Shexy.use_sudo
52
+ # sudo will be used to run the commands
53
+ Shexy.exe 'test@foobar.com', 'echo | cat' # => sudo echo | sudo cat
54
+
55
+ Query the server OS:
56
+
57
+ Shexy.distro # => :ubuntu, :redhat, :centos, :fedora, :debian
58
+ Shexy.distro_release # => 12.04, 5.8, etc.
59
+
60
+ More helpers:
61
+
62
+ Shexy.user = 'rubiojr'
63
+ Shexy.password = 'secret'
64
+ Shexy.host = 'foo.com'
65
+ # Copy the key to /home/rubiojr/.ssh/authorized_keys in remote server
66
+ Shexy.copy_ssh_pubkey '~/.ssh/id_rsa.pub'
67
+ # Set PermitRootLogin to without-password in /etc/ssh/sshd_config
68
+ # requires sudo in remote server (unless using root user).
69
+ Shexy.permit_root_login 'without-password'
70
+
33
71
  ## Caution
34
72
 
35
73
  I was bored writing net-ssh boilerplate, so I created this highly
@@ -30,40 +30,76 @@ require 'net/scp'
30
30
  #
31
31
  # Shexy.copy_to 'test@test-host', '/home/rubiojr/my-uber-file', '/tmp/'
32
32
  #
33
+
33
34
  module Shexy
35
+
36
+ VERSION = '0.3'
34
37
 
35
- VERSION = '0.2'
38
+ [:user, :password, :key, :cmd, :host].each do |n|
39
+ instance_eval %{
40
+ def #{n}; Thread.current[:shexy_#{n}]; end
41
+ def #{n}=(v); Thread.current[:shexy_#{n}] = v; end
42
+ }
43
+ end
36
44
 
37
- @flags = {}
45
+ def self.flags=(f);Thread.current[:shexy_flags] = f;end
46
+ def self.flags; Thread.current[:shexy_flags] ||= {} ; end
47
+ def self.sudo?;Thread.current[:shexy_use_sudo]; end
48
+ def self.use_sudo(v = true); Thread.current[:shexy_use_sudo] = v; end
38
49
 
39
- def self.password=(password); flags[:password] = password; end
40
- def self.password; flags[:password]; end
41
- def self.key=(key); flags[:keys] = [File.expand_path(key)]; end
42
- def self.key; flags[:keys]; end
43
- def self.use_sudo(v=true); @sudo = v; end
44
- def self.sudo?; @sudo ||= false; end
50
+ def self.wait_for_ssh(timeout = 60)
51
+ Timeout.timeout(timeout) do
52
+ begin
53
+ sleep(1) until tcp_test_ssh(host) do
54
+ end
55
+ rescue Errno::ECONNRESET
56
+ # safe to ignore, we need to retry all the time.
57
+ end
58
+ end
59
+ true
60
+ rescue Exception => e
61
+ $stderr.puts e.message
62
+ false
63
+ end
45
64
 
46
- class << self
47
- attr_accessor :host, :user, :flags
48
- attr_reader :cmd
65
+ def self.tcp_test_ssh
66
+ tcp_socket = TCPSocket.new(host, 22)
67
+ readable = IO.select([tcp_socket], nil, nil, 5)
68
+ if readable
69
+ yield
70
+ true
71
+ else
72
+ false
73
+ end
74
+ rescue Errno::ETIMEDOUT, Errno::EPERM
75
+ false
76
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH
77
+ sleep 2
78
+ false
79
+ ensure
80
+ tcp_socket && tcp_socket.close
49
81
  end
50
82
 
51
83
  def self.exe(*args)
52
84
  args.flatten!
53
85
  if args.size > 1
54
- @host = args[0]
55
- if @host =~ /@/
56
- @user, @host = @host.split '@'
57
- end
58
- @cmd = args[1]
86
+ self.host = args[0]
87
+ self.user, self.host = self.host.split '@' if self.host =~ /@/
88
+ self.cmd = args[1]
59
89
  else
60
- @cmd = args[0]
90
+ self.cmd = args[0]
61
91
  end
62
- Net::SSH.start(host, user, flags) do |sh|
92
+ self.flags[:password] = self.password if self.password
93
+ self.flags[:keys] = [self.key] if self.key
94
+ Net::SSH.start(self.host, self.user, self.flags) do |sh|
63
95
  sh.open_channel do |ch|
64
96
  if sudo?
65
97
  ch.request_pty
66
- @cmd = "sudo #{@cmd}"
98
+ if cmd =~ /(&&|\|\||&|\|)/
99
+ self.cmd = cmd.gsub(/(&&|\|\||&|\|)/, $1 + 'sudo')
100
+ end
101
+ self.cmd = "sudo #{cmd}"
102
+ puts "SHEXY: #{cmd}" if $DEBUG
67
103
  end
68
104
  ch.exec cmd do
69
105
  # FIXME: I don't think it's a good idea
@@ -95,6 +131,25 @@ module Shexy
95
131
  end
96
132
  end
97
133
 
134
+ def self.batch(&block)
135
+ require 'tempfile'
136
+ out, err = nil,nil
137
+ def self.script(script)
138
+ f = Tempfile.new 'shexy'
139
+ begin
140
+ f.puts script
141
+ f.flush
142
+ copy_to f.path, "#{f.path}.remote"
143
+ out, err = exe "/bin/bash #{f.path}.remote"
144
+ ensure
145
+ f.close
146
+ f.unlink
147
+ end
148
+ return out, err
149
+ end
150
+ instance_eval &block
151
+ end
152
+
98
153
  #
99
154
  # Shexy.copy_to 'root@foobar.com', 'source_file', 'dest_file'
100
155
  #
@@ -113,9 +168,9 @@ module Shexy
113
168
 
114
169
  if args.size > 2
115
170
  # First arg assumed to be foo@host.net
116
- @host = args[0]
117
- if @host =~ /@/
118
- @user, @host = @host.split '@'
171
+ self.host = args[0]
172
+ if self.host =~ /@/
173
+ self.user, self.host = self.host.split '@'
119
174
  end
120
175
  from = args[1]
121
176
  to = args[2]
@@ -125,6 +180,7 @@ module Shexy
125
180
  from = args[0]
126
181
  to = args[1]
127
182
  end
183
+ self.flags[:password] = self.password if self.password
128
184
  from = File.expand_path from
129
185
  Net::SCP.start(host, user, flags) do |scp|
130
186
  scp.upload! from, to, opts
@@ -165,4 +221,38 @@ module Shexy
165
221
  issue
166
222
  end
167
223
 
224
+ def self.copy_ssh_pubkey(path, dest_dir = '~/.ssh')
225
+ path = File.expand_path path
226
+ raise ArgumentError.new("Invalid key file") unless File.exist?(path)
227
+ key = File.read path
228
+ batch do
229
+ script <<-EOH
230
+ mkdir -p #{dest_dir} && chmod 700 #{dest_dir}
231
+ echo '#{key}' >> #{dest_dir}/authorized_keys
232
+ EOH
233
+ end
234
+ end
235
+
236
+ #
237
+ # Set PermitRootLogin to value in /etc/ssh/sshd_config
238
+ # value accepted: yes,now, without-password
239
+ #
240
+ def self.permit_root_login(value)
241
+ value = value.to_s
242
+ unless value =~ /^(yes|no|without-password)$/
243
+ raise ArgumentError.new "Argument should be yes|no|without-password"
244
+ end
245
+ using_sudo = Shexy.sudo?
246
+ Shexy.use_sudo
247
+ out, err = batch do
248
+ script <<-EOH
249
+ sed -i 's/^#\?PermitRootLogin.*$/PermitRootLogin\ #{value}/' /etc/ssh/sshd_config
250
+ test -f /etc/init.d/ssh && /etc/init.d/ssh restart
251
+ test -f /etc/init.d/sshd && /etc/init.d/sshd restart
252
+ EOH
253
+ end
254
+ Shexy.use_sudo(false) unless using_sudo
255
+ return out, err
256
+ end
257
+
168
258
  end
@@ -0,0 +1,2 @@
1
+ $: << File.join(File.dirname(__FILE__), '../lib')
2
+ require 'shexy'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shexy
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.2'
4
+ version: '0.3'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-17 00:00:00.000000000 Z
12
+ date: 2012-10-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-ssh
@@ -132,13 +132,13 @@ extra_rdoc_files:
132
132
  - README.md
133
133
  files:
134
134
  - .document
135
+ - CHANGELOG.md
135
136
  - Gemfile
136
137
  - LICENSE.txt
137
138
  - README.md
138
139
  - Rakefile
139
140
  - lib/shexy.rb
140
- - test/helper.rb
141
- - test/test_shexy.rb
141
+ - spec/spec_helper.rb
142
142
  homepage: http://github.com/rubiojr/shexy
143
143
  licenses:
144
144
  - MIT
@@ -154,7 +154,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
154
154
  version: '0'
155
155
  segments:
156
156
  - 0
157
- hash: 2098135952916495063
157
+ hash: 130145655460833176
158
158
  required_rubygems_version: !ruby/object:Gem::Requirement
159
159
  none: false
160
160
  requirements:
@@ -1,18 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
- require 'test/unit'
11
- require 'shoulda'
12
-
13
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
- $LOAD_PATH.unshift(File.dirname(__FILE__))
15
- require 'shexy'
16
-
17
- class Test::Unit::TestCase
18
- end
@@ -1,7 +0,0 @@
1
- require 'helper'
2
-
3
- class TestShexy < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
7
- end