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 +59 -0
- data/EXAMPLES.md +17 -0
- data/lib/sshkit/backends/abstract.rb +14 -7
- data/lib/sshkit/backends/netssh.rb +1 -1
- data/lib/sshkit/command.rb +79 -57
- data/lib/sshkit/version.rb +1 -1
- data/test/functional/backends/test_netssh.rb +2 -2
- data/test/unit/test_command.rb +13 -3
- metadata +2 -2
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(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
data/lib/sshkit/command.rb
CHANGED
@@ -22,9 +22,9 @@ module SSHKit
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
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? ? "
|
91
|
-
message += (stderr.strip.empty? ? "
|
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
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
202
|
+
def default_options
|
203
|
+
{
|
204
|
+
raise_on_non_zero_exit: true,
|
205
|
+
run_in_background: false
|
206
|
+
}
|
207
|
+
end
|
186
208
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
|
data/lib/sshkit/version.rb
CHANGED
@@ -62,13 +62,13 @@ module SSHKit
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
def
|
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
|
data/test/unit/test_command.rb
CHANGED
@@ -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 \"
|
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 \"
|
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 "
|
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.
|
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-
|
13
|
+
date: 2013-01-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: net-ssh
|