command-runner 0.9.1 → 0.9.2
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.
- checksums.yaml +4 -4
- data/README.md +3 -4
- data/lib/command/runner/backends/backticks.rb +1 -1
- data/lib/command/runner/backends/fake.rb +12 -1
- data/lib/command/runner/backends/posix_spawn.rb +9 -3
- data/lib/command/runner/backends/spawn.rb +6 -2
- data/lib/command/runner/backends/ssh.rb +11 -3
- data/lib/command/runner/backends/unsafe_fake.rb +19 -0
- data/lib/command/runner/backends.rb +1 -0
- data/lib/command/runner/version.rb +1 -1
- data/lib/command/runner.rb +33 -19
- data/spec/backticks_spec.rb +5 -5
- data/spec/interpolation_spec.rb +19 -0
- data/spec/messenger_spec.rb +18 -15
- data/spec/spawn_spec.rb +13 -7
- data/spec/spec_helper.rb +4 -0
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b5636d13fb538ac969b67e6eb4fdc7ad5ddaa27
|
4
|
+
data.tar.gz: fa207be281ffb8b56279bbe7d50e76c1f52ef225
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 037f29024cedc32f6b469288a2c7229f7d74d9035df02894b9b8685834f867eab990fc3c9f69a9ba305ae38eb33a7a141b284dbb46396603fde613083aaddaf3
|
7
|
+
data.tar.gz: 5804940205201d61ab9ff50164e75dc9e5b18c415d46a38747345b9a990e52c3d731f3e0754eb1f60253df69373cde1a514f9644f09605c6d34d7d61f6fbf3fc
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Command Runner
|
2
|
-
[](https://travis-ci.org/medcat/command-runner) [](https://codeclimate.com/github/medcat/command-runner)
|
3
3
|
|
4
4
|
Runs commands.
|
5
5
|
|
@@ -46,7 +46,7 @@ It can also use different methods to run commands...
|
|
46
46
|
|
47
47
|
```Ruby
|
48
48
|
line = Command::Runner.new("echo", "something")
|
49
|
-
line.
|
49
|
+
line.backend = Command::Runner::Backends::Spawn.new
|
50
50
|
line.pass
|
51
51
|
```
|
52
52
|
|
@@ -80,7 +80,6 @@ It works on
|
|
80
80
|
- 2.1.0
|
81
81
|
- 2.0.0
|
82
82
|
- 1.9.3
|
83
|
-
- 1.8.7
|
84
83
|
- JRuby (2.0 Mode)
|
85
84
|
- JRuby (1.9 Mode)
|
86
85
|
|
@@ -88,5 +87,5 @@ It works on
|
|
88
87
|
unless the travis build fails.
|
89
88
|
|
90
89
|
|
91
|
-
[](https://bitdeli.com/free "Bitdeli Badge")
|
92
91
|
|
@@ -11,8 +11,10 @@ module Command
|
|
11
11
|
# Returns whether or not this backend is avialable on this
|
12
12
|
# platform.
|
13
13
|
#
|
14
|
+
# @param force_unsafe [Boolean] if the backend needs to be
|
15
|
+
# able to handle unsafe execution, then this will be true.
|
14
16
|
# @abstract
|
15
|
-
def self.available?
|
17
|
+
def self.available?(force_unsafe = false)
|
16
18
|
true
|
17
19
|
end
|
18
20
|
|
@@ -27,6 +29,15 @@ module Command
|
|
27
29
|
false
|
28
30
|
end
|
29
31
|
|
32
|
+
# Whether or not it can handle unsafe execution. This is in
|
33
|
+
# case the developer wants to force unsafe execution on a
|
34
|
+
# safe backend.
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
def self.unsafe_execution?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
30
41
|
# Initialize the fake backend.
|
31
42
|
def initialize
|
32
43
|
@ran = []
|
@@ -13,10 +13,10 @@ module Command
|
|
13
13
|
#
|
14
14
|
# @see Fake.available?
|
15
15
|
# @return [Boolean]
|
16
|
-
def self.available?
|
16
|
+
def self.available?(_ = false)
|
17
17
|
@_available ||= begin
|
18
18
|
require 'posix/spawn'
|
19
|
-
|
19
|
+
super
|
20
20
|
rescue LoadError => e
|
21
21
|
false
|
22
22
|
end
|
@@ -27,7 +27,13 @@ module Command
|
|
27
27
|
# @see Spawn#spawn
|
28
28
|
# @return [Numeric]
|
29
29
|
def spawn(env, command, arguments, options)
|
30
|
-
|
30
|
+
if options.delete(:unsafe)
|
31
|
+
POSIX::Spawn.spawn(env,
|
32
|
+
"#{command} #{arguments.join(' ')}", options)
|
33
|
+
else
|
34
|
+
POSIX::Spawn.spawn(env, command,
|
35
|
+
*[arguments, options].flatten)
|
36
|
+
end
|
31
37
|
end
|
32
38
|
|
33
39
|
end
|
@@ -11,7 +11,7 @@ module Command
|
|
11
11
|
# that platform.
|
12
12
|
#
|
13
13
|
# @return [Boolean]
|
14
|
-
def self.available?
|
14
|
+
def self.available?(_ = false)
|
15
15
|
Process.respond_to?(:spawn) && !(RUBY_PLATFORM == "java" && RUBY_VERSION =~ /\A1\.9/)
|
16
16
|
end
|
17
17
|
|
@@ -81,7 +81,11 @@ module Command
|
|
81
81
|
# @see Process.spawn
|
82
82
|
# @return [Numeric] the process id
|
83
83
|
def spawn(env, command, arguments, options)
|
84
|
-
|
84
|
+
if options.delete(:unsafe)
|
85
|
+
Process.spawn(env, "#{command} #{arguments.join(' ')}", options)
|
86
|
+
else
|
87
|
+
Process.spawn(env, command, *arguments, options)
|
88
|
+
end
|
85
89
|
end
|
86
90
|
|
87
91
|
# Waits for the given process, and returns the process id and the
|
@@ -10,10 +10,14 @@ module Command
|
|
10
10
|
class SSH < Fake
|
11
11
|
|
12
12
|
# (see Fake.available?)
|
13
|
-
def self.available?
|
13
|
+
def self.available?(force_unsafe = false)
|
14
14
|
begin
|
15
15
|
require 'net/ssh'
|
16
|
-
|
16
|
+
if force_unsafe
|
17
|
+
false
|
18
|
+
else
|
19
|
+
true
|
20
|
+
end
|
17
21
|
rescue LoadError
|
18
22
|
false
|
19
23
|
end
|
@@ -23,6 +27,10 @@ module Command
|
|
23
27
|
true
|
24
28
|
end
|
25
29
|
|
30
|
+
def self.unsafe_execution?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
26
34
|
# Initializes the backend.
|
27
35
|
#
|
28
36
|
# @param host [String] the host to connect to.
|
@@ -41,7 +49,7 @@ module Command
|
|
41
49
|
|
42
50
|
|
43
51
|
ch.exec "#{command} " \
|
44
|
-
"#{arguments.join(" ")
|
52
|
+
"#{arguments.join(" ")}" do |sch, success|
|
45
53
|
raise Errno::ENOENT unless success
|
46
54
|
|
47
55
|
env.each do |k, v|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Command
|
2
|
+
class Runner
|
3
|
+
module Backends
|
4
|
+
class UnsafeFake < Fake
|
5
|
+
|
6
|
+
# A backend is considered unsafe when the arguments are
|
7
|
+
# exposed directly to the shell. This is a vulnerability, so
|
8
|
+
# we mark the class as unsafe and when we're about to pass
|
9
|
+
# the arguments to the backend, escape the safe
|
10
|
+
# interpolations.
|
11
|
+
#
|
12
|
+
# @return [Boolean]
|
13
|
+
def self.unsafe?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -11,6 +11,7 @@ module Command
|
|
11
11
|
autoload :Spawn, "command/runner/backends/spawn"
|
12
12
|
autoload :Backticks, "command/runner/backends/backticks"
|
13
13
|
autoload :PosixSpawn, "command/runner/backends/posix_spawn"
|
14
|
+
autoload :UnsafeFake, "command/runner/backends/unsafe_fake"
|
14
15
|
|
15
16
|
end
|
16
17
|
|
data/lib/command/runner.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'shellwords'
|
2
2
|
require 'future'
|
3
|
+
require 'hashie'
|
3
4
|
|
4
5
|
require 'command/runner/version'
|
5
6
|
require 'command/runner/message'
|
@@ -24,11 +25,11 @@ module Command
|
|
24
25
|
#
|
25
26
|
# @return [#call] a backend to use.
|
26
27
|
def best_backend(force_unsafe = false)
|
27
|
-
if Backends::PosixSpawn.available?
|
28
|
+
if Backends::PosixSpawn.available?(force_unsafe)
|
28
29
|
Backends::PosixSpawn.new
|
29
|
-
elsif Backends::Spawn.available?
|
30
|
+
elsif Backends::Spawn.available?(force_unsafe)
|
30
31
|
Backends::Spawn.new
|
31
|
-
elsif Backends::Backticks.available?
|
32
|
+
elsif Backends::Backticks.available?(force_unsafe)
|
32
33
|
Backends::Backticks.new
|
33
34
|
else
|
34
35
|
Backends::Fake.new
|
@@ -98,7 +99,9 @@ module Command
|
|
98
99
|
# @return [Message, Object] message if no block was given, the
|
99
100
|
# return value of the block otherwise.
|
100
101
|
def pass!(interops = {}, options = {}, &block)
|
101
|
-
|
102
|
+
options[:unsafe] = @unsafe
|
103
|
+
env = options.delete(:env) || {}
|
104
|
+
backend.call(*contents(interops), env, options, &block)
|
102
105
|
|
103
106
|
rescue Errno::ENOENT
|
104
107
|
raise NoCommandError, @command
|
@@ -135,7 +138,7 @@ module Command
|
|
135
138
|
#
|
136
139
|
# @return [void]
|
137
140
|
def force_unsafe!
|
138
|
-
@
|
141
|
+
@unsafe = true
|
139
142
|
end
|
140
143
|
|
141
144
|
# The command line being run by the runner. Interpolates the
|
@@ -159,24 +162,35 @@ module Command
|
|
159
162
|
# @param interops [Hash] the interpolations to make.
|
160
163
|
# @return [Array<String>] the interpolated string.
|
161
164
|
def interpolate(string, interops = {})
|
162
|
-
interops =
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
if
|
168
|
-
|
169
|
-
|
165
|
+
interops = Hashie::Mash.new(interops)
|
166
|
+
args = string.shellsplit
|
167
|
+
|
168
|
+
args.map do |arg|
|
169
|
+
arg.gsub(/(\{{1,2})([0-9a-zA-Z_\-.]+)(\}{1,2})/) do |m|
|
170
|
+
if $1.length != $3.length
|
171
|
+
next m
|
172
|
+
end
|
173
|
+
|
174
|
+
ops = interops
|
175
|
+
parts = $2.split('.')
|
176
|
+
parts.each do |part|
|
177
|
+
if ops.key?(part)
|
178
|
+
ops = ops[part]
|
170
179
|
else
|
171
|
-
|
180
|
+
ops = nil
|
181
|
+
break
|
172
182
|
end
|
183
|
+
end
|
184
|
+
|
185
|
+
if ops == nil
|
186
|
+
m
|
187
|
+
elsif $1.length == 1
|
188
|
+
escape(ops)
|
173
189
|
else
|
174
|
-
|
190
|
+
ops
|
175
191
|
end
|
176
|
-
else
|
177
|
-
part
|
178
192
|
end
|
179
|
-
end
|
193
|
+
end
|
180
194
|
end
|
181
195
|
|
182
196
|
private
|
@@ -187,7 +201,7 @@ module Command
|
|
187
201
|
# @param string [String] the string to escape.
|
188
202
|
# @return [String] the escaped string.
|
189
203
|
def escape(string)
|
190
|
-
if backend.unsafe?
|
204
|
+
if backend.unsafe? || @unsafe
|
191
205
|
Shellwords.escape(string)
|
192
206
|
else
|
193
207
|
string
|
data/spec/backticks_spec.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
describe Command::Runner::Backends::Backticks do
|
2
2
|
|
3
3
|
it "is available" do
|
4
|
-
Command::Runner::Backends::Backticks.
|
4
|
+
expect(Command::Runner::Backends::Backticks).to be_available
|
5
5
|
end
|
6
6
|
|
7
|
-
|
7
|
+
it("is unsafe") { expect(described_class).to be_unsafe }
|
8
8
|
|
9
9
|
it "returns a message" do
|
10
10
|
value = subject.call("echo", ["hello"])
|
11
|
-
value.
|
12
|
-
value.
|
11
|
+
expect(value).to be_instance_of Command::Runner::Message
|
12
|
+
expect(value).to be_executed
|
13
13
|
end
|
14
14
|
|
15
15
|
it "gives the correct time" do
|
16
|
-
subject.call("sleep", ["0.5"]).time.
|
16
|
+
expect(subject.call("sleep", ["0.5"]).time).to be_within(0.1).of(0.5)
|
17
17
|
end
|
18
18
|
|
19
19
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
describe Command::Runner do
|
2
|
+
|
3
|
+
before :each do
|
4
|
+
Command::Runner.backend = Command::Runner::Backends::Fake.new
|
5
|
+
end
|
6
|
+
|
7
|
+
subject { Command::Runner.new("gcc", "{env.flags} -o {output} {input} -l{lib}") }
|
8
|
+
let(:data) do
|
9
|
+
{ env: { flags: "-g3" },
|
10
|
+
output: "test",
|
11
|
+
input: "test.c",
|
12
|
+
lib: "m" }
|
13
|
+
end
|
14
|
+
|
15
|
+
it "interpolates correctly" do
|
16
|
+
value = subject.contents(data).last
|
17
|
+
expect(value).to eq ["-g3", "-o", "test", "test.c", "-lm"]
|
18
|
+
end
|
19
|
+
end
|
data/spec/messenger_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
describe Command::Runner do
|
2
2
|
|
3
3
|
before :each do
|
4
|
-
Command::Runner.backend = Command::Runner::Backends::
|
4
|
+
Command::Runner.backend = Command::Runner::Backends::UnsafeFake.new
|
5
5
|
end
|
6
6
|
|
7
7
|
subject do
|
@@ -21,13 +21,13 @@ describe Command::Runner do
|
|
21
21
|
it "escapes bad values" do
|
22
22
|
expect(
|
23
23
|
subject.contents(:interpolation => "`bad value`")
|
24
|
-
).to eq ["echo", ["some", "
|
24
|
+
).to eq ["echo", ["some", "\\`bad\\ value\\`"]]
|
25
25
|
end
|
26
26
|
|
27
27
|
it "doesn't interpolate interpolation values" do
|
28
28
|
expect(
|
29
29
|
subject.contents(:interpolation => "{other}", :other => "hi")
|
30
|
-
).to eq ["echo", ["some", "{other}"]]
|
30
|
+
).to eq ["echo", ["some", "\\{other\\}"]]
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -44,7 +44,7 @@ describe Command::Runner do
|
|
44
44
|
it "doesn't escape bad values" do
|
45
45
|
expect(
|
46
46
|
subject.contents(:interpolation => "`bad value`")
|
47
|
-
).to eq ["echo", ["some", "`bad
|
47
|
+
).to eq ["echo", ["some", "`bad value`"]]
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -61,16 +61,17 @@ describe Command::Runner do
|
|
61
61
|
|
62
62
|
context "when selecting backends" do
|
63
63
|
it "selects the best backend" do
|
64
|
-
Command::Runner::Backends::PosixSpawn
|
65
|
-
|
66
|
-
Command::Runner
|
67
|
-
|
68
|
-
Command::Runner
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
Command::Runner.best_backend
|
64
|
+
allow(Command::Runner::Backends::PosixSpawn).
|
65
|
+
to receive(:available?).and_return(false)
|
66
|
+
allow(Command::Runner::Backends::Spawn).
|
67
|
+
to receive(:available?).and_return(true)
|
68
|
+
expect(Command::Runner.best_backend).
|
69
|
+
to be_instance_of Command::Runner::Backends::Spawn
|
70
|
+
|
71
|
+
allow(Command::Runner::Backends::PosixSpawn).
|
72
|
+
to receive(:available?).and_return(true)
|
73
|
+
expect(Command::Runner.best_backend).
|
74
|
+
to be_instance_of Command::Runner::Backends::PosixSpawn
|
74
75
|
end
|
75
76
|
end
|
76
77
|
|
@@ -82,7 +83,9 @@ describe Command::Runner do
|
|
82
83
|
subject.backend = Command::Runner::Backends::Backticks.new
|
83
84
|
end
|
84
85
|
|
85
|
-
|
86
|
+
it "should result in no command" do
|
87
|
+
expect(subject.pass).to be_no_command
|
88
|
+
end
|
86
89
|
|
87
90
|
it "calls the block given" do
|
88
91
|
subject.pass do |message|
|
data/spec/spawn_spec.rb
CHANGED
@@ -2,16 +2,16 @@ describe Command::Runner::Backends::Spawn do
|
|
2
2
|
|
3
3
|
next unless Process.respond_to?(:spawn) && !(RUBY_PLATFORM == "java" && RUBY_VERSION =~ /\A1\.9/)
|
4
4
|
|
5
|
-
|
5
|
+
it("is safe") { expect(described_class).to_not be_unsafe }
|
6
6
|
|
7
7
|
it "is available" do
|
8
|
-
Command::Runner::Backends::Spawn.
|
8
|
+
expect(Command::Runner::Backends::Spawn).to be_available
|
9
9
|
end
|
10
10
|
|
11
11
|
it "returns a message" do
|
12
12
|
value = subject.call("echo", ["hello"])
|
13
|
-
value.
|
14
|
-
value.
|
13
|
+
expect(value).to be_instance_of Command::Runner::Message
|
14
|
+
expect(value).to be_executed
|
15
15
|
end
|
16
16
|
|
17
17
|
it "doesn't block" do
|
@@ -19,8 +19,8 @@ describe Command::Runner::Backends::Spawn do
|
|
19
19
|
value = subject.call("sleep", ["0.5"])
|
20
20
|
end_time = Time.now
|
21
21
|
|
22
|
-
(end_time - start_time).
|
23
|
-
value.time.
|
22
|
+
expect(end_time - start_time).to be_within((1.0/100)).of(0)
|
23
|
+
expect(value.time).to be_within((3.0/100)).of(0.5)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "doesn't expose arguments to the shell" do
|
@@ -29,8 +29,14 @@ describe Command::Runner::Backends::Spawn do
|
|
29
29
|
expect(value.stdout).to eq "`uname -a`\n"
|
30
30
|
end
|
31
31
|
|
32
|
+
it "can be unsafe" do
|
33
|
+
value = subject.call("echo", ["`uname -a`"], {}, unsafe: true)
|
34
|
+
|
35
|
+
expect(value.stdout).to_not eq "`uname -a`\n"
|
36
|
+
end
|
37
|
+
|
32
38
|
it "can not be available" do
|
33
|
-
Command::Runner::Backends::Spawn.
|
39
|
+
allow(Command::Runner::Backends::Spawn).to receive(:available?).and_return(false)
|
34
40
|
|
35
41
|
expect {
|
36
42
|
Command::Runner::Backends::Spawn.new
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: command-runner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Rodi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: promise
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hashie
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.3'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rspec
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,13 +97,16 @@ files:
|
|
83
97
|
- lib/command/runner/backends/posix_spawn.rb
|
84
98
|
- lib/command/runner/backends/spawn.rb
|
85
99
|
- lib/command/runner/backends/ssh.rb
|
100
|
+
- lib/command/runner/backends/unsafe_fake.rb
|
86
101
|
- lib/command/runner/exceptions.rb
|
87
102
|
- lib/command/runner/message.rb
|
88
103
|
- lib/command/runner/version.rb
|
89
104
|
- spec/backticks_spec.rb
|
105
|
+
- spec/interpolation_spec.rb
|
90
106
|
- spec/messenger_spec.rb
|
91
107
|
- spec/spawn_spec.rb
|
92
|
-
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
homepage: http://github.com/medcat/command-runner
|
93
110
|
licenses:
|
94
111
|
- MIT
|
95
112
|
metadata: {}
|