derelict 0.3.4.travis.108 → 0.3.4.travis.115
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 +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
|