awesome_spawn 1.0.0 → 1.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.
Potentially problematic release.
This version of awesome_spawn might be problematic. Click here for more details.
- data/README.md +1 -0
- data/lib/awesome_spawn.rb +59 -22
- data/lib/awesome_spawn/version.rb +1 -1
- data/spec/awesome_spawn_spec.rb +54 -11
- metadata +2 -8
data/README.md
CHANGED
@@ -11,6 +11,7 @@ AwesomeSpawn is a module that provides some useful features over Ruby's Kernel.s
|
|
11
11
|
Some additional features include...
|
12
12
|
|
13
13
|
- Parameter passing as a Hash or associative Array sanitizing them to prevent command line injection.
|
14
|
+
- Ability to pass the contents of stdin as a String.
|
14
15
|
- Results returned as an object giving access to the output stream, error stream, and exit status.
|
15
16
|
- Optionally raising an exception when exit status is not 0.
|
16
17
|
|
data/lib/awesome_spawn.rb
CHANGED
@@ -31,26 +31,35 @@ module AwesomeSpawn
|
|
31
31
|
# result.exit_status # => 1
|
32
32
|
#
|
33
33
|
# @example With parameters sanitized
|
34
|
-
# result = AwesomeSpawn.run('echo', :params => {
|
34
|
+
# result = AwesomeSpawn.run('echo', :params => {:out => "; rm /some/file"})
|
35
35
|
# # => #<AwesomeSpawn::CommandResult:0x007ff64baa6650 @exit_status=0>
|
36
36
|
# result.command_line
|
37
37
|
# # => "echo --out \\;\\ rm\\ /some/file"
|
38
38
|
#
|
39
|
+
# @example With data to be passed on stdin
|
40
|
+
# result = AwesomeSpawn.run('cat', :in_data => "line1\nline2")
|
41
|
+
# => #<AwesomeSpawn::CommandResult:0x007fff05b0ab10 @exit_status=0>
|
42
|
+
# result.output
|
43
|
+
# => "line1\nline2"
|
44
|
+
#
|
39
45
|
# @param [String] command The command to run
|
40
|
-
# @param [Hash] options The options for running the command
|
46
|
+
# @param [Hash] options The options for running the command. Also accepts any
|
47
|
+
# option that can be passed to Kernel.spawn, except `:out` and `:err`.
|
41
48
|
# @option options [Hash,Array] :params The command line parameters. See
|
42
|
-
# {#build_command_line} for how to specify params.
|
43
|
-
#
|
44
|
-
#
|
49
|
+
# {#build_command_line} for how to specify params.
|
50
|
+
# @option options [String] :in_data Data to be passed on stdin. If this option
|
51
|
+
# is specified you cannot specify `:in`.
|
45
52
|
#
|
46
53
|
# @raise [NoSuchFileError] if the `command` is not found
|
47
54
|
# @return [CommandResult] the output stream, error stream, and exit status
|
48
55
|
# @see http://ruby-doc.org/core/Kernel.html#method-i-spawn Kernel.spawn
|
49
56
|
def run(command, options = {})
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
57
|
+
raise ArgumentError, "options cannot contain :out" if options.include?(:out)
|
58
|
+
raise ArgumentError, "options cannot contain :err" if options.include?(:err)
|
59
|
+
raise ArgumentError, "options cannot contain :in if :in_data is specified" if options.include?(:in) && options.include?(:in_data)
|
60
|
+
options = options.dup
|
61
|
+
params = options.delete(:params)
|
62
|
+
in_data = options.delete(:in_data)
|
54
63
|
|
55
64
|
output = ""
|
56
65
|
error = ""
|
@@ -58,7 +67,7 @@ module AwesomeSpawn
|
|
58
67
|
command_line = build_command_line(command, params)
|
59
68
|
|
60
69
|
begin
|
61
|
-
output, error = launch(command_line,
|
70
|
+
output, error = launch(command_line, in_data, options)
|
62
71
|
status = exitstatus
|
63
72
|
ensure
|
64
73
|
output ||= ""
|
@@ -99,11 +108,15 @@ module AwesomeSpawn
|
|
99
108
|
# @param [String] command The command to run
|
100
109
|
# @param [Hash,Array] params Optional command line parameters. They can
|
101
110
|
# be passed as a Hash or associative Array. The values are sanitized to
|
102
|
-
# prevent command line injection.
|
111
|
+
# prevent command line injection. Keys as symbols are prefixed with `--`,
|
112
|
+
# and `_` is replaced with `-`.
|
103
113
|
#
|
114
|
+
# - `{:key => "value"}` generates `--key value`
|
104
115
|
# - `{"--key" => "value"}` generates `--key value`
|
116
|
+
# - `{:key= => "value"}` generates `--key=value`
|
105
117
|
# - `{"--key=" => "value"}` generates `--key=value`
|
106
|
-
# - `{
|
118
|
+
# - `{:key_name => "value"}` generates `--key-name value`
|
119
|
+
# - `{:key => nil}` generates `--key`
|
107
120
|
# - `{"-f" => ["file1", "file2"]}` generates `-f file1 file2`
|
108
121
|
# - `{nil => ["file1", "file2"]}` generates `file1 file2`
|
109
122
|
#
|
@@ -118,12 +131,22 @@ module AwesomeSpawn
|
|
118
131
|
def sanitize(params)
|
119
132
|
return [] if params.nil? || params.empty?
|
120
133
|
params.collect do |k, v|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
134
|
+
[sanitize_key(k), sanitize_value(v)]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def sanitize_key(key)
|
139
|
+
case key
|
140
|
+
when Symbol then "--#{key.to_s.tr("_", "-")}"
|
141
|
+
else key
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def sanitize_value(value)
|
146
|
+
case value
|
147
|
+
when Array then value.collect { |i| i.to_s.shellescape }
|
148
|
+
when NilClass then value
|
149
|
+
else value.to_s.shellescape
|
127
150
|
end
|
128
151
|
end
|
129
152
|
|
@@ -139,20 +162,34 @@ module AwesomeSpawn
|
|
139
162
|
# http://stackoverflow.com/questions/13829830/ruby-process-spawn-stdout-pipe-buffer-size-limit/13846146#13846146
|
140
163
|
THREAD_SYNC_KEY = "#{self.name}-exitstatus"
|
141
164
|
|
142
|
-
def launch(command, spawn_options = {})
|
165
|
+
def launch(command, in_data, spawn_options = {})
|
143
166
|
out_r, out_w = IO.pipe
|
144
167
|
err_r, err_w = IO.pipe
|
145
|
-
|
146
|
-
|
168
|
+
in_r, in_w = IO.pipe if in_data
|
169
|
+
|
170
|
+
spawn_options[:out] = out_w
|
171
|
+
spawn_options[:err] = err_w
|
172
|
+
spawn_options[:in] = in_r if in_data
|
173
|
+
|
174
|
+
pid = Kernel.spawn(command, spawn_options)
|
175
|
+
|
176
|
+
write_to_input(in_w, in_data) if in_data
|
177
|
+
wait_for_process(pid, out_w, err_w, in_r)
|
147
178
|
wait_for_pipes(out_r, err_r)
|
148
179
|
end
|
149
180
|
|
150
|
-
def
|
181
|
+
def write_to_input(in_w, in_data)
|
182
|
+
in_w.write(in_data)
|
183
|
+
in_w.close
|
184
|
+
end
|
185
|
+
|
186
|
+
def wait_for_process(pid, out_w, err_w, in_r)
|
151
187
|
self.exitstatus = :not_done
|
152
188
|
Thread.new(Thread.current) do |parent_thread|
|
153
189
|
_, status = Process.wait2(pid)
|
154
190
|
out_w.close
|
155
191
|
err_w.close
|
192
|
+
in_r.close if in_r
|
156
193
|
parent_thread[THREAD_SYNC_KEY] = status.exitstatus
|
157
194
|
end
|
158
195
|
end
|
data/spec/awesome_spawn_spec.rb
CHANGED
@@ -6,11 +6,13 @@ describe AwesomeSpawn do
|
|
6
6
|
|
7
7
|
let(:params) do
|
8
8
|
{
|
9
|
-
"--user"
|
10
|
-
"--pass"
|
11
|
-
"--db"
|
12
|
-
"--desc="
|
13
|
-
|
9
|
+
"--user" => "bob",
|
10
|
+
"--pass" => "P@$sw0^& |<>/-+*d%",
|
11
|
+
"--db" => nil,
|
12
|
+
"--desc=" => "Some Description",
|
13
|
+
:symkey => nil,
|
14
|
+
:symkey_dash => nil,
|
15
|
+
nil => ["pkg1", "some pkg"]
|
14
16
|
}
|
15
17
|
end
|
16
18
|
|
@@ -19,21 +21,28 @@ describe AwesomeSpawn do
|
|
19
21
|
end
|
20
22
|
|
21
23
|
shared_examples_for "run" do
|
22
|
-
context "
|
24
|
+
context "options" do
|
23
25
|
before do
|
24
26
|
subject.stub(:exitstatus => 0)
|
25
27
|
end
|
26
28
|
|
27
|
-
it "won't
|
29
|
+
it ":params won't be modified" do
|
28
30
|
orig_params = params.dup
|
29
31
|
subject.stub(:launch)
|
30
32
|
subject.send(run_method, "true", :params => params)
|
31
33
|
expect(orig_params).to eq(params)
|
32
34
|
end
|
33
35
|
|
34
|
-
it "
|
35
|
-
subject.
|
36
|
-
|
36
|
+
it ":in_data cannot be passed with :in" do
|
37
|
+
expect { subject.send(run_method, "true", :in_data => "XXXXX", :in => "/dev/null") } .to raise_error(ArgumentError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it ":out is not supported" do
|
41
|
+
expect { subject.send(run_method, "true", :out => "/dev/null") }.to raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it ":err is not supported" do
|
45
|
+
expect { subject.send(run_method, "true", :err => "/dev/null") }.to raise_error(ArgumentError)
|
37
46
|
end
|
38
47
|
end
|
39
48
|
|
@@ -66,6 +75,20 @@ describe AwesomeSpawn do
|
|
66
75
|
expect { subject.send(run_method, "XXXXX --user=bob") }.to raise_error(Errno::ENOENT, "No such file or directory - XXXXX")
|
67
76
|
end
|
68
77
|
|
78
|
+
context "with option" do
|
79
|
+
it ":chdir" do
|
80
|
+
result = subject.send(run_method, "pwd", :chdir => "..")
|
81
|
+
expect(result.exit_status).to eq(0)
|
82
|
+
expect(result.output.chomp).to eq(File.expand_path("..", Dir.pwd))
|
83
|
+
end
|
84
|
+
|
85
|
+
it ":in_data" do
|
86
|
+
result = subject.send(run_method, "cat", :in_data => "line1\nline2")
|
87
|
+
expect(result.exit_status).to eq(0)
|
88
|
+
expect(result.output).to eq("line1\nline2")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
69
92
|
context "#exit_status" do
|
70
93
|
it "command ok exit ok" do
|
71
94
|
expect(subject.send(run_method, "true").exit_status).to eq(0)
|
@@ -113,7 +136,27 @@ describe AwesomeSpawn do
|
|
113
136
|
context ".build_command_line" do
|
114
137
|
it "sanitizes crazy params" do
|
115
138
|
cl = subject.build_command_line("true", modified_params)
|
116
|
-
expect(cl).to eq "true --user bob --pass P@\\$sw0\\^\\&\\ \\|\\<\\>/-\\+\\*d\\% --db --desc=Some\\ Description pkg1 some\\ pkg --pool 123 --pool 456"
|
139
|
+
expect(cl).to eq "true --user bob --pass P@\\$sw0\\^\\&\\ \\|\\<\\>/-\\+\\*d\\% --db --desc=Some\\ Description --symkey --symkey-dash pkg1 some\\ pkg --pool 123 --pool 456"
|
140
|
+
end
|
141
|
+
|
142
|
+
it "handles Symbol keys" do
|
143
|
+
cl = subject.build_command_line("true", :abc => "def")
|
144
|
+
expect(cl).to eq "true --abc def"
|
145
|
+
end
|
146
|
+
|
147
|
+
it "handles Symbol keys with tailing '='" do
|
148
|
+
cl = subject.build_command_line("true", :abc= => "def")
|
149
|
+
expect(cl).to eq "true --abc=def"
|
150
|
+
end
|
151
|
+
|
152
|
+
it "handles Symbol keys with underscore" do
|
153
|
+
cl = subject.build_command_line("true", :abc_def => "ghi")
|
154
|
+
expect(cl).to eq "true --abc-def ghi"
|
155
|
+
end
|
156
|
+
|
157
|
+
it "handles Symbol keys with underscore and tailing '='" do
|
158
|
+
cl = subject.build_command_line("true", :abc_def= => "ghi")
|
159
|
+
expect(cl).to eq "true --abc-def=ghi"
|
117
160
|
end
|
118
161
|
|
119
162
|
it "sanitizes Fixnum array param value" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: awesome_spawn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2014-
|
15
|
+
date: 2014-02-03 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bundler
|
@@ -151,18 +151,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
151
151
|
- - ! '>='
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: '0'
|
154
|
-
segments:
|
155
|
-
- 0
|
156
|
-
hash: -1223152174146401530
|
157
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
155
|
none: false
|
159
156
|
requirements:
|
160
157
|
- - ! '>='
|
161
158
|
- !ruby/object:Gem::Version
|
162
159
|
version: '0'
|
163
|
-
segments:
|
164
|
-
- 0
|
165
|
-
hash: -1223152174146401530
|
166
160
|
requirements: []
|
167
161
|
rubyforge_project:
|
168
162
|
rubygems_version: 1.8.23
|