sshkit 0.0.16 → 0.0.18

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.
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