sshkit 0.0.16 → 0.0.18

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -3,6 +3,65 @@
3
3
  This file is written in reverse chronological order, newer releases will
4
4
  appear at the top.
5
5
 
6
+ ## 0.0.18
7
+
8
+ * Enable `as()` to take either a string/symbol as previously, but also now
9
+ accepts a hash of `{user: ..., group: ...}`. In case that your host system
10
+ supports the command `sg` (`man 1 sg`) to switch your effective group ID
11
+ then one can work on files as a team group user.
12
+
13
+ on host do |host|
14
+ as user: :peter, group: griffin do
15
+ execute :touch, 'somefile'
16
+ end
17
+ end
18
+
19
+ will result in a file with the following permissions:
20
+
21
+ -rw-r--r-- 1 peter griffin 0 Jan 27 08:12 somefile
22
+
23
+ This should make it much easier to share deploy scripts between team
24
+ members.
25
+
26
+ **Note:** `sg` has some very strict user and group password requirements
27
+ (the user may not have a password (`passwd username -l` to lock an account
28
+ that already has a password), and the group may not have a password.)
29
+
30
+ Additionally, and unsurprisingly *the user must also be a member of the
31
+ group.*
32
+
33
+ `sg` was chosen over `newgrp` as it's easier to embed in a one-liner
34
+ command, `newgrp` could be used with a heredoc, but my research suggested
35
+ that it might be better to use sg, as it better represents my intention, a
36
+ temporary switch to a different effective group.
37
+
38
+ * Fixed a bug with environmental variables and umasking introduced in 0.0.14.
39
+ Since that version the environmental variables were being presented to the
40
+ umask command's subshell, and not to intended command's subshell.
41
+
42
+ incorrect: `ENV=var umask 002 && env`
43
+ correct: `umask 002 && ENV=var env`
44
+
45
+ * Changed the exception handler, if a command returns with a non-zero exit
46
+ status then the output will be prefixed with the command name and which
47
+ channel any output was written to, for example:
48
+
49
+ Command.new("echo ping; false")
50
+ => echo stdout: ping
51
+ echo stderr: Nothing written
52
+
53
+ In this contrived example that's more or less useless, however with badly
54
+ behaved commands that write errors to stdout, and don't include their name
55
+ in the program output, it can help a lot with debugging.
56
+
57
+ ## 0.0.17
58
+
59
+ * Fixed a bug introduced in 0.0.16 where the capture() helper returned
60
+ the name of the command that had been run, not it's output.
61
+
62
+ * Classify the pre-directory switch, and pre-user switch command guards
63
+ as having a DEBUG log level to exclude them from the logs.
64
+
6
65
  ## 0.0.16
7
66
 
8
67
  * Fixed a bug introduced in 0.0.15 where the capture() helper returned
data/EXAMPLES.md CHANGED
@@ -50,6 +50,23 @@ This will output:
50
50
  **Note:** This example is a bit misleading, as the `www-data` user doesn't
51
51
  have a shell defined, one cannot switch to that user.
52
52
 
53
+ ## Run a command with a different effective group ID
54
+
55
+
56
+ on hosts do |host|
57
+ as user: 'www-data', group: 'project-group' do
58
+ in '/var/log' do
59
+ execute :touch, 'somefile'
60
+ execute :ls, '-l'
61
+ end
62
+ end
63
+
64
+ One will see that the created file is owned by the user `www-data` and the
65
+ group `project-group`.
66
+
67
+ When combined with the `umask` configuration option, it is easy to share
68
+ scripts for deployment between team members without sharing logins.
69
+
53
70
  ## Stack directory nestings
54
71
 
55
72
  on hosts do
@@ -43,7 +43,7 @@ module SSHKit
43
43
 
44
44
  def within(directory, &block)
45
45
  (@pwd ||= []).push directory.to_s
46
- execute <<-EOTEST
46
+ execute <<-EOTEST, verbosity: Logger::DEBUG
47
47
  if test ! -d #{File.join(@pwd)}
48
48
  then echo "Directory does not exist '#{File.join(@pwd)}'" 1>&2
49
49
  false
@@ -63,24 +63,31 @@ module SSHKit
63
63
  remove_instance_variable(:@_env)
64
64
  end
65
65
 
66
- def as(user, &block)
67
- @user = user
68
- execute <<-EOTEST
69
- if ! sudo su #{user} -c whoami > /dev/null
70
- then echo "You cannot switch to user '#{user}' using sudo, please check the sudoers file" 1>&2
66
+ def as(who, &block)
67
+ if who.is_a? Hash
68
+ @user = who[:user] || who["user"]
69
+ @group = who[:group] || who["group"]
70
+ else
71
+ @user = who
72
+ @group = nil
73
+ end
74
+ execute <<-EOTEST, verbosity: Logger::DEBUG
75
+ if ! sudo su #{@user} -c whoami > /dev/null
76
+ then echo "You cannot switch to user '#{@user}' using sudo, please check the sudoers file" 1>&2
71
77
  false
72
78
  fi
73
79
  EOTEST
74
80
  yield
75
81
  ensure
76
82
  remove_instance_variable(:@user)
83
+ remove_instance_variable(:@group)
77
84
  end
78
85
 
79
86
  private
80
87
 
81
88
  def command(*args)
82
89
  options = args.extract_options!
83
- SSHKit::Command.new(*[*args, options.merge({in: @pwd.nil? ? nil : File.join(@pwd), env: @env, host: @host, user: @user})])
90
+ SSHKit::Command.new(*[*args, options.merge({in: @pwd.nil? ? nil : File.join(@pwd), env: @env, host: @host, user: @user, group: @group})])
84
91
  end
85
92
 
86
93
  def connection
@@ -34,7 +34,7 @@ module SSHKit
34
34
 
35
35
  def capture(*args)
36
36
  options = args.extract_options!.merge(verbosity: Logger::DEBUG)
37
- _execute(*[*args, options])
37
+ _execute(*[*args, options]).stdout.strip
38
38
  end
39
39
 
40
40
  def configure
@@ -22,9 +22,9 @@ module SSHKit
22
22
 
23
23
  private
24
24
 
25
- def map(command)
26
- SSHKit.config.command_map[command.to_sym]
27
- end
25
+ def map(command)
26
+ SSHKit.config.command_map[command.to_sym]
27
+ end
28
28
 
29
29
  end
30
30
 
@@ -87,8 +87,8 @@ module SSHKit
87
87
  @exit_status = new_exit_status
88
88
  if options[:raise_on_non_zero_exit] && exit_status > 0
89
89
  message = ""
90
- message += (stdout.strip.empty? ? "No messages written to stdout\n" : stdout.strip)
91
- message += (stderr.strip.empty? ? "No messages written to stderr\n" : stderr.strip)
90
+ message += "#{command} stdout: " + (stdout.strip.empty? ? "Nothing written" : stdout.strip) + "\n"
91
+ message += "#{command} stderr: " + (stderr.strip.empty? ? "Nothing written" : stderr.strip) + "\n"
92
92
  raise Failed, message
93
93
  end
94
94
  end
@@ -132,69 +132,91 @@ module SSHKit
132
132
  end
133
133
  end
134
134
 
135
- def to_s(expanded=false)
136
- return command.to_s if command.match /\s/
137
- String.new.tap do |cs|
138
- if options[:in]
139
- cs << sprintf("cd %s && ", options[:in])
140
- end
141
- unless SSHKit.config.default_env.empty?
142
- if options[:env].is_a? Hash
143
- options[:env] = SSHKit.config.default_env.merge(options[:env])
144
- end
145
- end
146
- if options[:env]
147
- cs << '( '
148
- options[:env].each do |k,v|
149
- cs << k.to_s.upcase
150
- cs << "="
151
- cs << v.to_s
152
- cs << ' '
135
+ def should_map?
136
+ !command.match /\s/
137
+ end
138
+
139
+ def within(&block)
140
+ return yield unless options[:in]
141
+ "cd #{options[:in]} && %s" % yield
142
+ end
143
+
144
+ def environment_hash
145
+ (SSHKit.config.default_env || {}).merge(options[:env] || {})
146
+ end
147
+
148
+ def envivonment_string
149
+ environment_hash.collect do |key,value|
150
+ "#{key.to_s.upcase}=#{value}"
151
+ end.join(' ')
152
+ end
153
+
154
+ def with(&block)
155
+ return yield unless environment_hash.any?
156
+ "( #{envivonment_string} %s )" % yield
157
+ end
158
+
159
+ def user(&block)
160
+ return yield unless options[:user]
161
+ "sudo su #{options[:user]} -c \"%s\"" % %Q{#{yield}}
162
+ end
163
+
164
+ def in_background(&block)
165
+ return yield unless options[:run_in_background]
166
+ "nohup %s > /dev/null &" % yield
167
+ end
168
+
169
+ def umask(&block)
170
+ return yield unless SSHKit.config.umask
171
+ "umask #{SSHKit.config.umask} && %s" % yield
172
+ end
173
+
174
+ def group(&block)
175
+ return yield unless options[:group]
176
+ "sg #{options[:group]} -c \\\"%s\\\"" % %Q{#{yield}}
177
+ # We could also use the so-called heredoc format perhaps:
178
+ #"newgrp #{options[:group]} <<EOC \\\"%s\\\" EOC" % %Q{#{yield}}
179
+ end
180
+
181
+ def to_s
182
+
183
+ return command.to_s unless should_map?
184
+
185
+ within do
186
+ umask do
187
+ with do
188
+ user do
189
+ in_background do
190
+ group do
191
+ [SSHKit.config.command_map[command.to_sym], *Array(args)].join(' ')
192
+ end
193
+ end
194
+ end
153
195
  end
154
196
  end
155
- if options[:user]
156
- cs << "sudo su #{options[:user]} -c \""
157
- end
158
- if options[:run_in_background]
159
- cs << 'nohup '
160
- end
161
- if umask = SSHKit.config.umask
162
- cs << "umask #{umask} && "
163
- end
164
- cs << SSHKit.config.command_map[command.to_sym]
165
- if args.any?
166
- cs << ' '
167
- cs << args.join(' ')
168
- end
169
- if options[:run_in_background]
170
- cs << ' > /dev/null &'
171
- end
172
- if options[:user]
173
- cs << "\""
174
- end
175
- if options[:env]
176
- cs << ' )'
177
- end
178
197
  end
179
198
  end
180
199
 
181
200
  private
182
201
 
183
- def default_options
184
- { raise_on_non_zero_exit: true, run_in_background: false }
185
- end
202
+ def default_options
203
+ {
204
+ raise_on_non_zero_exit: true,
205
+ run_in_background: false
206
+ }
207
+ end
186
208
 
187
- def sanitize_command!
188
- command.to_s.strip!
189
- if command.to_s.match("\n")
190
- @command = String.new.tap do |cs|
191
- command.to_s.lines.each do |line|
192
- cs << line.strip
193
- cs << '; ' unless line == command.to_s.lines.to_a.last
194
- end
209
+ def sanitize_command!
210
+ command.to_s.strip!
211
+ if command.to_s.match("\n")
212
+ @command = String.new.tap do |cs|
213
+ command.to_s.lines.each do |line|
214
+ cs << line.strip
215
+ cs << '; ' unless line == command.to_s.lines.to_a.last
195
216
  end
196
217
  end
197
218
  end
219
+ end
198
220
 
199
221
  end
200
222
 
@@ -1,3 +1,3 @@
1
1
  module SSHKit
2
- VERSION = "0.0.16"
2
+ VERSION = "0.0.18"
3
3
  end
@@ -62,13 +62,13 @@ module SSHKit
62
62
  end
63
63
  end
64
64
 
65
- def test_execute_raises_on_non_zero_exit_status_and_captures_stderr
65
+ def test_execute_raises_on_non_zero_exit_status_and_captures_stdout_and_stderr
66
66
  err = assert_raises SSHKit::Command::Failed do
67
67
  Netssh.new(a_host) do |host|
68
68
  execute :echo, "'Test capturing stderr' 1>&2; false"
69
69
  end.run
70
70
  end
71
- assert_equal "Test capturing stderr", err.message
71
+ assert_equal "echo stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
72
72
  end
73
73
 
74
74
  def test_test_does_not_raise_on_non_zero_exit_status
@@ -81,6 +81,16 @@ module SSHKit
81
81
  assert_equal "sudo su anotheruser -c \"/usr/bin/env whoami\"", String(c)
82
82
  end
83
83
 
84
+ def test_working_as_a_given_group
85
+ c = Command.new(:whoami, group: :devvers)
86
+ assert_equal "sg devvers -c \\\"/usr/bin/env whoami\\\"", String(c)
87
+ end
88
+
89
+ def test_working_as_a_given_user_and_group
90
+ c = Command.new(:whoami, user: :anotheruser, group: :devvers)
91
+ assert_equal "sudo su anotheruser -c \"sg devvers -c \\\"/usr/bin/env whoami\\\"\"", String(c)
92
+ end
93
+
84
94
  def test_backgrounding_a_task
85
95
  c = Command.new(:sleep, 15, run_in_background: true)
86
96
  assert_equal "nohup /usr/bin/env sleep 15 > /dev/null &", String(c)
@@ -111,13 +121,13 @@ module SSHKit
111
121
  def test_umask_with_working_directory_and_user
112
122
  SSHKit.config.umask = '007'
113
123
  c = Command.new(:touch, 'somefile', in: '/var', user: 'alice')
114
- assert_equal "cd /var && sudo su alice -c \"umask 007 && /usr/bin/env touch somefile\"", String(c)
124
+ assert_equal "cd /var && umask 007 && sudo su alice -c \"/usr/bin/env touch somefile\"", String(c)
115
125
  end
116
126
 
117
127
  def test_umask_with_env_and_working_directory_and_user
118
128
  SSHKit.config.umask = '007'
119
129
  c = Command.new(:touch, 'somefile', user: 'bob', env: {a: 'b'}, in: '/var')
120
- assert_equal "cd /var && ( A=b sudo su bob -c \"umask 007 && /usr/bin/env touch somefile\" )", String(c)
130
+ assert_equal "cd /var && umask 007 && ( A=b sudo su bob -c \"/usr/bin/env touch somefile\" )", String(c)
121
131
  end
122
132
 
123
133
  def test_verbosity_defaults_to_logger_info
@@ -195,7 +205,7 @@ module SSHKit
195
205
  error = assert_raises SSHKit::Command::Failed do
196
206
  Command.new(:whoami).exit_status = 1
197
207
  end
198
- assert_equal "No messages written to stdout\nNo messages written to stderr\n", error.message
208
+ assert_equal "whoami stdout: Nothing written\nwhoami stderr: Nothing written\n", error.message
199
209
  end
200
210
 
201
211
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sshkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.16
4
+ version: 0.0.18
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-01-26 00:00:00.000000000 Z
13
+ date: 2013-01-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: net-ssh