derelict 0.3.4.travis.108 → 0.3.4.travis.115
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +2 -2
- data/derelict.gemspec +1 -1
- data/lib/derelict/connection.rb +1 -1
- data/lib/derelict/executer.rb +173 -0
- data/lib/derelict/instance/command_failed.rb +6 -4
- data/lib/derelict/instance.rb +4 -4
- data/lib/derelict.rb +2 -1
- data/spec/derelict/executer_spec.rb +99 -0
- data/spec/derelict/instance_spec.rb +3 -3
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NjFhN2Q1ZGVhMThhODA1NjZlMGFjNzI3MWY0Mzg5Njg0M2EwM2QzZQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Y2JkYjNiNjRmNGFjMzNmNTM1ODFmMTQ5ODczNWU0YzYwMjI4NmQzOQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZWNiOGVlYmQ4YjBkN2ZkOTFhMmJiNTgxNmIyYTc3ZWI3NjlkYjIxZTNhZGMx
|
10
|
+
NmUxZDQ5YWNiYmZhNjVlM2NjMTUxNjgwMDFlMjQzNzk5ODFkMzViNWNkNDA4
|
11
|
+
ODE3OTljYTkyZDIyMDVjODM1NzhjMDU3OWRiZGM0ZWQ4YWE1YjY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZmZmNWI1MzRlNTgzZDZiNmUxZmMwNGFmZDQ2N2ZiY2UwYmUzMGYxZGUzNTkw
|
14
|
+
MmQ1NzdjZDVmM2U3NGU2MWQxYjg2NzM2MWE5NDZhZjhjZjZhNTBkNWI5N2Vk
|
15
|
+
NjM2ZWU1ODUzMjYyZmM1NGZkYmMyOGZlYjUzZGRjNWM0OGQ5MjM=
|
data/README.md
CHANGED
@@ -64,7 +64,7 @@ instance = Derelict.instance("/path/to/vagrant")
|
|
64
64
|
instance = Derelict.instance # Defaults to /Applications/Vagrant
|
65
65
|
|
66
66
|
# Issue commands to the instance directly (not usually necessary)
|
67
|
-
result = instance.execute('--version') #
|
67
|
+
result = instance.execute('--version') # Derelict::Executer object
|
68
68
|
print "success" if result.success? # if Vagrant's exit status was 0
|
69
69
|
print result.stdout # "Vagrant 1.3.3\n"
|
70
70
|
|
@@ -73,7 +73,7 @@ connection = instance.connect("/path/to/project")
|
|
73
73
|
|
74
74
|
# Issue commands to the connection directly (runs from the project dir)
|
75
75
|
result = connection.execute(:up) # runs "vagrant up" in project dir
|
76
|
-
result.success? #
|
76
|
+
result.success? # Derelict::Executer object again
|
77
77
|
|
78
78
|
# Retrieve a particular VM from a connection (multi-machine support)
|
79
79
|
vm = connection.vm(:web) # "vm" is a Derelict::VirtualMachine
|
data/derelict.gemspec
CHANGED
data/lib/derelict/connection.rb
CHANGED
@@ -48,7 +48,7 @@ module Derelict
|
|
48
48
|
#
|
49
49
|
# * subcommand: Vagrant subcommand to run (:up, :status, etc.)
|
50
50
|
# * arguments: Arguments to pass to the subcommand (optional)
|
51
|
-
# * block: Passed through to
|
51
|
+
# * block: Passed through to Derelict::Executer.execute
|
52
52
|
#
|
53
53
|
# Raises +Derelict::Instance::CommandFailed+ if the command fails.
|
54
54
|
def execute!(subcommand, *arguments, &block)
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module Derelict
|
2
|
+
# Executes an external (shell) command "safely"
|
3
|
+
#
|
4
|
+
# The safety involved is mainly ensuring that the command is
|
5
|
+
# gracefully terminated if this process is about to terminate.
|
6
|
+
class Executer
|
7
|
+
attr_reader :stdout, :stderr, :success
|
8
|
+
|
9
|
+
# Executes <tt>command</tt> and returns after execution
|
10
|
+
#
|
11
|
+
# * command: A string containing the command to run
|
12
|
+
# * options: A hash of options, with the following (symbol) keys:
|
13
|
+
# * :mode: Controls how the process' output is given to
|
14
|
+
# the block, one of :chars (pass each character
|
15
|
+
# one by one, retrieved with getc), or :lines
|
16
|
+
# (pass only whole lines, retrieved with gets).
|
17
|
+
# (optional, defaults to :lines)
|
18
|
+
# * :no_buffer: If true, the process' stdout and stderr won't
|
19
|
+
# be collected in the stdout and stderr
|
20
|
+
# properties, and will only be passed to the
|
21
|
+
# block (optional, defaults to false)
|
22
|
+
# * block: Gets passed stdout and stderr every time the process
|
23
|
+
# outputs to each stream (first parameter is stdout,
|
24
|
+
# second parameter is stderr; only one will contain
|
25
|
+
# data, the other will be nil)
|
26
|
+
def self.execute(command, options = {}, &block)
|
27
|
+
self.new(options).execute(command, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Initializes an Executer instance with particular options
|
32
|
+
#
|
33
|
+
# * options: A hash of options, with the following (symbol) keys:
|
34
|
+
# * :mode: Controls how the process' output is given to
|
35
|
+
# the block, one of :chars (pass each character
|
36
|
+
# one by one, retrieved with getc), or :lines
|
37
|
+
# (pass only whole lines, retrieved with gets).
|
38
|
+
# (optional, defaults to :lines)
|
39
|
+
# * :no_buffer: If true, the process' stdout and stderr won't
|
40
|
+
# be collected in the stdout and stderr
|
41
|
+
# properties, and will only be passed to the
|
42
|
+
# block (optional, defaults to false)
|
43
|
+
def initialize(options = {})
|
44
|
+
@options = {:mode => :lines, :no_buffer => false}.merge(options)
|
45
|
+
|
46
|
+
if @options[:mode] == :chars
|
47
|
+
@reader = proc {|s| s.getc }
|
48
|
+
else
|
49
|
+
@reader = proc {|s| s.gets }
|
50
|
+
end
|
51
|
+
|
52
|
+
@mutex = Mutex.new
|
53
|
+
reset
|
54
|
+
end
|
55
|
+
|
56
|
+
# Executes <tt>command</tt> and returns after execution
|
57
|
+
#
|
58
|
+
# * command: A string containing the command to run
|
59
|
+
# * block: Gets passed stdout and stderr every time the process
|
60
|
+
# outputs to each stream (first parameter is stdout,
|
61
|
+
# second parameter is stderr; only one will contain
|
62
|
+
# data, the other will be nil)
|
63
|
+
def execute(command, &block)
|
64
|
+
reset
|
65
|
+
pid, stdin, stdout, stderr = Open4::popen4(command)
|
66
|
+
|
67
|
+
save_exit_status(pid)
|
68
|
+
forward_signals_to(pid) { handle_streams stdout, stderr, &block }
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
private
|
74
|
+
# Clears the variables relating to a particular command execution
|
75
|
+
#
|
76
|
+
# This is done when first initialising, and just before a command
|
77
|
+
# is run, to get rid of the previous command's data.
|
78
|
+
def reset
|
79
|
+
@stdout = ''
|
80
|
+
@stderr = ''
|
81
|
+
@success = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# Waits for the exit status of a process (in a thread) saving it
|
85
|
+
#
|
86
|
+
# This will set the @status instance variable to true if the exit
|
87
|
+
# status was 0, or false if the exit status was anything else.
|
88
|
+
def save_exit_status(pid)
|
89
|
+
Thread.start do
|
90
|
+
@success = nil
|
91
|
+
@success = (Process.waitpid2(pid).last.exitstatus == 0)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Forward signals to a process while running the given block
|
96
|
+
#
|
97
|
+
# * pid: The process ID to forward signals to
|
98
|
+
# * signals: The names of the signals to handle (optional,
|
99
|
+
# defaults to SIGINT only)
|
100
|
+
def forward_signals_to(pid, signals = %w[INT])
|
101
|
+
# Set up signal handlers
|
102
|
+
signals.each do |signal|
|
103
|
+
Signal.trap(signal) { Process.kill signal, pid }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Run the block now that the signals are being forwarded
|
107
|
+
yield
|
108
|
+
|
109
|
+
# Reset signal handlers
|
110
|
+
signals.each do |signal|
|
111
|
+
Signal.trap signal, "DEFAULT"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Manages reading from the stdout and stderr streams
|
116
|
+
#
|
117
|
+
# * stdout: The process' stdout stream
|
118
|
+
# * stderr: The process' stderr stream
|
119
|
+
# * block: The block to pass any read data to (optional)
|
120
|
+
def handle_streams(stdout, stderr, &block)
|
121
|
+
streams = [stdout, stderr]
|
122
|
+
until streams.empty?
|
123
|
+
# Find which streams are ready for reading, timeout 0.1s
|
124
|
+
selected, = select(streams, nil, nil, 0.1)
|
125
|
+
|
126
|
+
# Try again if none were ready
|
127
|
+
next if selected.nil? or selected.empty?
|
128
|
+
|
129
|
+
selected.each do |stream|
|
130
|
+
if stream.eof?
|
131
|
+
streams.delete(stream) unless @success.nil?
|
132
|
+
next
|
133
|
+
end
|
134
|
+
|
135
|
+
while data = @reader.call(stream)
|
136
|
+
data = ((@options[:mode] == :chars) ? data.chr : data)
|
137
|
+
stream_name = (stream == stdout) ? :stdout : :stderr
|
138
|
+
output data, stream_name, &block unless block.nil?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Outputs data to the block
|
145
|
+
#
|
146
|
+
# * data: The data that needs to be passed to the block
|
147
|
+
# * stream_name: The stream data came from (:stdout or :stderr)
|
148
|
+
# * block: The block to pass the data to
|
149
|
+
def output(data, stream_name = :stdout, &block)
|
150
|
+
# Pass the output to the block
|
151
|
+
if block.arity == 2
|
152
|
+
args = [nil, nil]
|
153
|
+
if stream_name == :stdout
|
154
|
+
args[0] = data
|
155
|
+
else
|
156
|
+
args[1] = data
|
157
|
+
end
|
158
|
+
block.call(*args)
|
159
|
+
else
|
160
|
+
yield data if stream_name == :stdout
|
161
|
+
end
|
162
|
+
|
163
|
+
# Add to the buffers
|
164
|
+
unless @options[:no_buffer]
|
165
|
+
if stream_name == :stdout
|
166
|
+
@stdout += data
|
167
|
+
else
|
168
|
+
@stderr += data
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -2,11 +2,13 @@ module Derelict
|
|
2
2
|
class Instance
|
3
3
|
# Represents an invalid instance, which can't be used with Derelict
|
4
4
|
class CommandFailed < Derelict::Exception
|
5
|
-
# Initializes a new instance of this exception,
|
5
|
+
# Initializes a new instance of this exception, for a command
|
6
6
|
#
|
7
|
-
# *
|
8
|
-
#
|
9
|
-
#
|
7
|
+
# * command: The name of the command which failed (optional,
|
8
|
+
# provides extra detail in the message)
|
9
|
+
# * result: The result (Derelict::Executer) for the command
|
10
|
+
# which failed (optional, provides extra detail in
|
11
|
+
# the message)
|
10
12
|
def initialize(command = nil, result = nil)
|
11
13
|
super [default_message, describe(command, result)].join
|
12
14
|
end
|
data/lib/derelict/instance.rb
CHANGED
@@ -62,24 +62,24 @@ module Derelict
|
|
62
62
|
# as a hash of options. A list of valid options is
|
63
63
|
# below. Any options provided that aren't in the
|
64
64
|
# list of valid options will get passed through to
|
65
|
-
#
|
65
|
+
# Derelict::Executer.execute.
|
66
66
|
# Valid option keys:
|
67
67
|
# * sudo: Whether to run the command as root, or not
|
68
68
|
# (defaults to false)
|
69
|
-
# * block: Passed through to
|
69
|
+
# * block: Passed through to Derelict::Executer.execute
|
70
70
|
def execute(subcommand, *arguments, &block)
|
71
71
|
options = arguments.last.is_a?(Hash) ? arguments.pop : Hash.new
|
72
72
|
command = command(subcommand, *arguments)
|
73
73
|
command = "sudo -- #{command}" if options.delete(:sudo)
|
74
74
|
logger.debug "Executing #{command} using #{description}"
|
75
|
-
|
75
|
+
Executer.execute command, options, &block
|
76
76
|
end
|
77
77
|
|
78
78
|
# Executes a Vagrant subcommand, raising an exception on failure
|
79
79
|
#
|
80
80
|
# * subcommand: Vagrant subcommand to run (:up, :status, etc.)
|
81
81
|
# * arguments: Arguments to pass to the subcommand (optional)
|
82
|
-
# * block: Passed through to
|
82
|
+
# * block: Passed through to Derelict::Executer.execute
|
83
83
|
#
|
84
84
|
# Raises +Derelict::Instance::CommandFailed+ if the command fails.
|
85
85
|
def execute!(subcommand, *arguments, &block)
|
data/lib/derelict.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require "derelict/version"
|
2
2
|
require "log4r"
|
3
3
|
require "memoist"
|
4
|
+
require "open4"
|
4
5
|
require "set"
|
5
|
-
require "shell/executer"
|
6
6
|
require "shellwords"
|
7
7
|
|
8
8
|
Log4r::Logger["root"] # creates the level constants (INFO, etc).
|
@@ -12,6 +12,7 @@ module Derelict
|
|
12
12
|
autoload :Box, "derelict/box"
|
13
13
|
autoload :Connection, "derelict/connection"
|
14
14
|
autoload :Exception, "derelict/exception"
|
15
|
+
autoload :Executer, "derelict/executer"
|
15
16
|
autoload :Instance, "derelict/instance"
|
16
17
|
autoload :Parser, "derelict/parser"
|
17
18
|
autoload :Plugin, "derelict/plugin"
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Derelict::Executer do
|
4
|
+
let(:executer) { described_class.new }
|
5
|
+
let(:command) { double("command") }
|
6
|
+
let(:options) { Hash.new }
|
7
|
+
let(:block) { Proc.new do end }
|
8
|
+
let(:result) { double("result") }
|
9
|
+
|
10
|
+
describe ".execute" do
|
11
|
+
before do
|
12
|
+
expect(described_class).to receive(:new).with(options).and_return(executer)
|
13
|
+
expect(executer).to receive(:execute).with(command, &block).and_return(result)
|
14
|
+
end
|
15
|
+
|
16
|
+
subject { described_class.execute command, options, &block }
|
17
|
+
it { should be result }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#initialize" do
|
21
|
+
subject { described_class.new options }
|
22
|
+
it { should be_a described_class }
|
23
|
+
its(:stdout) { should eq "" }
|
24
|
+
its(:stderr) { should eq "" }
|
25
|
+
its(:success) { should be_nil }
|
26
|
+
|
27
|
+
context "with :chars mode specified" do
|
28
|
+
let(:options) { {:mode => :chars} }
|
29
|
+
it { should be_a described_class }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# The way #execute is tested is much slower than a unit test
|
34
|
+
# (obviously), but I ran into some difficulty implementing a proper
|
35
|
+
# unit test for this method.
|
36
|
+
#
|
37
|
+
# TODO: rewrite as a unit test
|
38
|
+
describe "#execute" do
|
39
|
+
subject { executer.execute command, &block }
|
40
|
+
|
41
|
+
context "without a block" do
|
42
|
+
let(:command) { "echo 'test 1'" }
|
43
|
+
its(:stdout) { should eq "test 1\n" }
|
44
|
+
its(:stderr) { should eq "" }
|
45
|
+
its(:success) { should be_true }
|
46
|
+
|
47
|
+
context "with non-existent command" do
|
48
|
+
let(:command) { "not_actually_a_command" }
|
49
|
+
it "should raise ENOENT" do
|
50
|
+
expect { subject }.to raise_error(Errno::ENOENT)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with unsuccessful command" do
|
55
|
+
let(:command) { "false" }
|
56
|
+
its(:success) { should be_false }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with a block" do
|
61
|
+
context "with one argument" do
|
62
|
+
let(:command) { "echo 'test 2'" }
|
63
|
+
let(:block) do
|
64
|
+
@buffer = ""
|
65
|
+
Proc.new do |stdout|
|
66
|
+
@buffer << stdout
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
its(:stdout) { should eq "test 2\n" }
|
71
|
+
its(:stderr) { should eq "" }
|
72
|
+
its(:success) { should be_true }
|
73
|
+
specify "the block should get called" do
|
74
|
+
subject; expect(@buffer).to eq executer.stdout
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "with two arguments" do
|
79
|
+
let(:command) { "echo 'test 3'; echo 'test 4' 1>&2" }
|
80
|
+
let(:block) do
|
81
|
+
@stdout = ""; @stderr = ""
|
82
|
+
Proc.new do |stdout, stderr|
|
83
|
+
@stdout << stdout unless stdout.nil?
|
84
|
+
@stderr << stderr unless stderr.nil?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
its(:stdout) { should eq "test 3\n" }
|
89
|
+
its(:stderr) { should eq "test 4\n" }
|
90
|
+
its(:success) { should be_true }
|
91
|
+
specify "the block should get called" do
|
92
|
+
subject
|
93
|
+
expect(@stdout).to eq executer.stdout
|
94
|
+
expect(@stderr).to eq executer.stderr
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -165,10 +165,10 @@ describe Derelict::Instance do
|
|
165
165
|
]}
|
166
166
|
end
|
167
167
|
|
168
|
-
context "with mock
|
168
|
+
context "with mock Executer" do
|
169
169
|
let(:expected_command) { "/foo/bar/bin/vagrant test arg\\ 1" }
|
170
170
|
before do
|
171
|
-
expect(
|
171
|
+
expect(Derelict::Executer).to receive(:execute).with(expected_command, options).and_return(result)
|
172
172
|
end
|
173
173
|
|
174
174
|
let(:options) { Hash.new }
|
@@ -207,7 +207,7 @@ describe Derelict::Instance do
|
|
207
207
|
|
208
208
|
context "with :sudo option enabled" do
|
209
209
|
let(:options_argument) { {:sudo => true} }
|
210
|
-
let(:options) { Hash.new } # Don't pass sudo opt to
|
210
|
+
let(:options) { Hash.new } # Don't pass sudo opt to Executer.execute
|
211
211
|
let(:expected_command) { "sudo -- /foo/bar/bin/vagrant test arg\\ 1" }
|
212
212
|
subject { instance.execute :test, "arg 1", options_argument }
|
213
213
|
its(:stdout) { should eq "stdout\n" }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: derelict
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.4.travis.
|
4
|
+
version: 0.3.4.travis.115
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brad Feehan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: log4r
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: open4
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ! '>='
|
@@ -198,6 +198,7 @@ files:
|
|
198
198
|
- lib/derelict/connection/not_found.rb
|
199
199
|
- lib/derelict/exception.rb
|
200
200
|
- lib/derelict/exception/optional_reason.rb
|
201
|
+
- lib/derelict/executer.rb
|
201
202
|
- lib/derelict/instance.rb
|
202
203
|
- lib/derelict/instance/command_failed.rb
|
203
204
|
- lib/derelict/instance/invalid.rb
|
@@ -234,6 +235,7 @@ files:
|
|
234
235
|
- spec/derelict/connection_spec.rb
|
235
236
|
- spec/derelict/exception/optional_reason_spec.rb
|
236
237
|
- spec/derelict/exception_spec.rb
|
238
|
+
- spec/derelict/executer_spec.rb
|
237
239
|
- spec/derelict/instance/command_failed_spec.rb
|
238
240
|
- spec/derelict/instance/invalid_spec.rb
|
239
241
|
- spec/derelict/instance/missing_binary_spec.rb
|
@@ -296,6 +298,7 @@ test_files:
|
|
296
298
|
- spec/derelict/connection_spec.rb
|
297
299
|
- spec/derelict/exception/optional_reason_spec.rb
|
298
300
|
- spec/derelict/exception_spec.rb
|
301
|
+
- spec/derelict/executer_spec.rb
|
299
302
|
- spec/derelict/instance/command_failed_spec.rb
|
300
303
|
- spec/derelict/instance/invalid_spec.rb
|
301
304
|
- spec/derelict/instance/missing_binary_spec.rb
|