derelict_m 0.6.2a
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.cane +2 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +18 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +99 -0
- data/Rakefile +22 -0
- data/derelict.gemspec +63 -0
- data/lib/derelict.rb +74 -0
- data/lib/derelict/box.rb +29 -0
- data/lib/derelict/box/manager.rb +111 -0
- data/lib/derelict/box/not_found.rb +16 -0
- data/lib/derelict/connection.rb +84 -0
- data/lib/derelict/connection/invalid.rb +14 -0
- data/lib/derelict/connection/not_found.rb +13 -0
- data/lib/derelict/exception.rb +6 -0
- data/lib/derelict/exception/optional_reason.rb +32 -0
- data/lib/derelict/executer.rb +237 -0
- data/lib/derelict/instance.rb +147 -0
- data/lib/derelict/instance/command_failed.rb +30 -0
- data/lib/derelict/instance/invalid.rb +14 -0
- data/lib/derelict/instance/missing_binary.rb +13 -0
- data/lib/derelict/instance/non_directory.rb +13 -0
- data/lib/derelict/instance/not_found.rb +13 -0
- data/lib/derelict/parser.rb +27 -0
- data/lib/derelict/parser/box_list.rb +53 -0
- data/lib/derelict/parser/box_list/invalid_format.rb +16 -0
- data/lib/derelict/parser/plugin_list.rb +63 -0
- data/lib/derelict/parser/plugin_list/invalid_format.rb +16 -0
- data/lib/derelict/parser/plugin_list/needs_reinstall.rb +22 -0
- data/lib/derelict/parser/status.rb +90 -0
- data/lib/derelict/parser/status/invalid_format.rb +16 -0
- data/lib/derelict/parser/version.rb +28 -0
- data/lib/derelict/parser/version/invalid_format.rb +16 -0
- data/lib/derelict/plugin.rb +29 -0
- data/lib/derelict/plugin/manager.rb +107 -0
- data/lib/derelict/plugin/not_found.rb +14 -0
- data/lib/derelict/utils.rb +11 -0
- data/lib/derelict/utils/logger.rb +59 -0
- data/lib/derelict/utils/logger/array_outputter.rb +43 -0
- data/lib/derelict/utils/logger/invalid_type.rb +15 -0
- data/lib/derelict/utils/logger/raw_formatter.rb +12 -0
- data/lib/derelict/version.rb +3 -0
- data/lib/derelict/virtual_machine.rb +190 -0
- data/lib/derelict/virtual_machine/invalid.rb +14 -0
- data/lib/derelict/virtual_machine/not_found.rb +18 -0
- data/spec/coverage_helper.rb +19 -0
- data/spec/derelict/box/manager_spec.rb +171 -0
- data/spec/derelict/box/not_found_spec.rb +13 -0
- data/spec/derelict/box_spec.rb +37 -0
- data/spec/derelict/connection/invalid_spec.rb +16 -0
- data/spec/derelict/connection/not_found_spec.rb +13 -0
- data/spec/derelict/connection_spec.rb +107 -0
- data/spec/derelict/exception/optional_reason_spec.rb +41 -0
- data/spec/derelict/exception_spec.rb +11 -0
- data/spec/derelict/executer_spec.rb +129 -0
- data/spec/derelict/instance/command_failed_spec.rb +40 -0
- data/spec/derelict/instance/invalid_spec.rb +16 -0
- data/spec/derelict/instance/missing_binary_spec.rb +13 -0
- data/spec/derelict/instance/non_directory_spec.rb +13 -0
- data/spec/derelict/instance/not_found_spec.rb +13 -0
- data/spec/derelict/instance_spec.rb +258 -0
- data/spec/derelict/parser/box_list/invalid_format_spec.rb +16 -0
- data/spec/derelict/parser/box_list_spec.rb +64 -0
- data/spec/derelict/parser/plugin_list/invalid_format_spec.rb +16 -0
- data/spec/derelict/parser/plugin_list/needs_reinstall_spec.rb +13 -0
- data/spec/derelict/parser/plugin_list_spec.rb +82 -0
- data/spec/derelict/parser/status/invalid_format_spec.rb +16 -0
- data/spec/derelict/parser/status_spec.rb +214 -0
- data/spec/derelict/parser/version/invalid_format_spec.rb +16 -0
- data/spec/derelict/parser/version_spec.rb +42 -0
- data/spec/derelict/parser_spec.rb +24 -0
- data/spec/derelict/plugin/manager_spec.rb +208 -0
- data/spec/derelict/plugin/not_found_spec.rb +13 -0
- data/spec/derelict/plugin_spec.rb +37 -0
- data/spec/derelict/utils/logger/array_outputter_spec.rb +40 -0
- data/spec/derelict/utils/logger/invalid_type_spec.rb +13 -0
- data/spec/derelict/utils/logger/raw_formatter_spec.rb +17 -0
- data/spec/derelict/utils/logger_spec.rb +35 -0
- data/spec/derelict/virtual_machine/invalid_spec.rb +16 -0
- data/spec/derelict/virtual_machine/not_found_spec.rb +34 -0
- data/spec/derelict/virtual_machine_spec.rb +356 -0
- data/spec/derelict_spec.rb +50 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/log_context.rb +36 -0
- metadata +332 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module Derelict
|
2
|
+
class Box
|
3
|
+
# A box that isn't currently installed has been retrieved
|
4
|
+
class NotFound < Derelict::Exception
|
5
|
+
# Initializes a new instance, for a particular box name/provider
|
6
|
+
#
|
7
|
+
# * box_name: The name of the box that this exception relates
|
8
|
+
# to
|
9
|
+
# * provider: The provider of the box that this exception
|
10
|
+
# relates to
|
11
|
+
def initialize(box_name, provider)
|
12
|
+
super "Box '#{box_name}' for provider '#{provider}' missing"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Derelict
|
2
|
+
# Connects a Derelict::Instance to its use in a particular directory
|
3
|
+
class Connection
|
4
|
+
autoload :Invalid, "derelict/connection/invalid"
|
5
|
+
autoload :NotFound, "derelict/connection/not_found"
|
6
|
+
|
7
|
+
# Include "logger" method to get a logger for this class
|
8
|
+
include Utils::Logger
|
9
|
+
|
10
|
+
attr_reader :instance
|
11
|
+
attr_reader :path
|
12
|
+
|
13
|
+
# Initializes a Connection for use in a particular directory
|
14
|
+
#
|
15
|
+
# * instance: The Derelict::Instance to use to control Vagrant
|
16
|
+
# * path: The project path, which contains the Vagrantfile
|
17
|
+
def initialize(instance, path)
|
18
|
+
@instance = instance
|
19
|
+
@path = path
|
20
|
+
logger.debug "Successfully initialized #{description}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Validates the data used for this connection
|
24
|
+
#
|
25
|
+
# Raises exceptions on failure:
|
26
|
+
#
|
27
|
+
# * +Derelict::Connection::NotFound+ if the path is not found
|
28
|
+
def validate!
|
29
|
+
logger.debug "Starting validation for #{description}"
|
30
|
+
raise NotFound.new path unless File.exists? path
|
31
|
+
logger.info "Successfully validated #{description}"
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Executes a Vagrant subcommand using this connection
|
36
|
+
#
|
37
|
+
# * subcommand: Vagrant subcommand to run (:up, :status, etc.)
|
38
|
+
# * arguments: Arguments to pass to the subcommand (optional)
|
39
|
+
# * block: Passed through to @instance#execute
|
40
|
+
def execute(subcommand, *arguments, &block)
|
41
|
+
log_execute subcommand, *arguments
|
42
|
+
Dir.chdir path do
|
43
|
+
instance.execute subcommand.to_sym, *arguments, &block
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Executes a Vagrant subcommand, raising an exception on failure
|
48
|
+
#
|
49
|
+
# * subcommand: Vagrant subcommand to run (:up, :status, etc.)
|
50
|
+
# * arguments: Arguments to pass to the subcommand (optional)
|
51
|
+
# * block: Passed through to Derelict::Executer.execute
|
52
|
+
#
|
53
|
+
# Raises +Derelict::Instance::CommandFailed+ if the command fails.
|
54
|
+
def execute!(subcommand, *arguments, &block)
|
55
|
+
log_execute subcommand, *arguments
|
56
|
+
Dir.chdir path do
|
57
|
+
instance.execute! subcommand.to_sym, *arguments, &block
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Retrieves a Derelict::VirtualMachine for a particular VM
|
62
|
+
#
|
63
|
+
# * name: The name of the virtual machine to retrieve
|
64
|
+
def vm(name)
|
65
|
+
logger.debug "Retrieving VM '#{name}' from #{description}"
|
66
|
+
Derelict::VirtualMachine.new(self, name).validate!
|
67
|
+
end
|
68
|
+
|
69
|
+
# Provides a description of this Connection
|
70
|
+
#
|
71
|
+
# Mainly used for log messages.
|
72
|
+
def description
|
73
|
+
"Derelict::Connection at '#{path}' using #{instance.description}"
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
# Handles the logging that should occur for a call to #execute(!)
|
78
|
+
def log_execute(subcommand, *arguments)
|
79
|
+
logger.debug do
|
80
|
+
"Executing #{subcommand} #{arguments.inspect} on #{description}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Derelict
|
2
|
+
class Connection
|
3
|
+
# Represents an invalid connection, which Derelict can't use
|
4
|
+
class Invalid < Derelict::Exception
|
5
|
+
include Derelict::Exception::OptionalReason
|
6
|
+
|
7
|
+
private
|
8
|
+
# Retrieves the default error message
|
9
|
+
def default_message
|
10
|
+
"Invalid Derelict connection"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Derelict
|
2
|
+
class Connection
|
3
|
+
# The Vagrantfile for the connection was not found
|
4
|
+
class NotFound < Invalid
|
5
|
+
# Initializes a new instance of this exception for a given path
|
6
|
+
#
|
7
|
+
# * path: The requested path of the instance
|
8
|
+
def initialize(path)
|
9
|
+
super "Vagrantfile not found for #{path}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Derelict
|
2
|
+
class Exception
|
3
|
+
# An exception that has a message and optional additional reason
|
4
|
+
#
|
5
|
+
# The reason can be passed to the constructor (if desired). When a
|
6
|
+
# reason is passed, it's appended to the default message. If no
|
7
|
+
# reason is passed, the default message is used.
|
8
|
+
module OptionalReason
|
9
|
+
# Initializes a new instance of this exception, with a reason
|
10
|
+
#
|
11
|
+
# * reason: Optional reason to add to the default error message
|
12
|
+
# (optional, the default message will be used if no
|
13
|
+
# reason is provided)
|
14
|
+
def initialize(reason = nil)
|
15
|
+
if reason.nil?
|
16
|
+
super default_message
|
17
|
+
else
|
18
|
+
super "#{default_message}: #{reason}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
# Retrieves the default error message
|
24
|
+
#
|
25
|
+
# This needs to be overridden in child classes in order to
|
26
|
+
# customize the default error message.
|
27
|
+
def default_message
|
28
|
+
raise NotImplementedError.new "#default_message not defined"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,237 @@
|
|
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
|
+
# Include "logger" method to get a logger for this class
|
8
|
+
include Utils::Logger
|
9
|
+
|
10
|
+
attr_reader :stdout, :stderr, :pid
|
11
|
+
|
12
|
+
# Executes <tt>command</tt> and returns after execution
|
13
|
+
#
|
14
|
+
# * command: A string containing the command to run
|
15
|
+
# * options: A hash of options, with the following (symbol) keys:
|
16
|
+
# * :mode: Controls how the process' output is given to
|
17
|
+
# the block, one of :chars (pass each character
|
18
|
+
# one by one, retrieved with getc), or :lines
|
19
|
+
# (pass only whole lines, retrieved with gets).
|
20
|
+
# (optional, defaults to :lines)
|
21
|
+
# * :no_buffer: If true, the process' stdout and stderr won't
|
22
|
+
# be collected in the stdout and stderr
|
23
|
+
# properties, and will only be passed to the
|
24
|
+
# block (optional, defaults to false)
|
25
|
+
# * block: Gets passed stdout and stderr every time the process
|
26
|
+
# outputs to each stream (first parameter is stdout,
|
27
|
+
# second parameter is stderr; only one will contain
|
28
|
+
# data, the other will be nil)
|
29
|
+
def self.execute(command, options = {}, &block)
|
30
|
+
self.new(options).execute(command, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Initializes an Executer instance with particular options
|
35
|
+
#
|
36
|
+
# * options: A hash of options, with the following (symbol) keys:
|
37
|
+
# * :mode: Controls how the process' output is given to
|
38
|
+
# the block, one of :chars (pass each character
|
39
|
+
# one by one, retrieved with getc), or :lines
|
40
|
+
# (pass only whole lines, retrieved with gets).
|
41
|
+
# (optional, defaults to :lines)
|
42
|
+
# * :no_buffer: If true, the process' stdout and stderr won't
|
43
|
+
# be collected in the stdout and stderr
|
44
|
+
# properties, and will only be passed to the
|
45
|
+
# block (optional, defaults to false)
|
46
|
+
def initialize(options = {})
|
47
|
+
@options = {:mode => :lines, :no_buffer => false}.merge(options)
|
48
|
+
|
49
|
+
logger.info "Initializing with options: #{@options.inspect}"
|
50
|
+
|
51
|
+
if @options[:mode] == :chars
|
52
|
+
@reader = proc {|s| s.getc }
|
53
|
+
else
|
54
|
+
@reader = proc {|s| s.gets }
|
55
|
+
end
|
56
|
+
|
57
|
+
@mutex = Mutex.new
|
58
|
+
reset
|
59
|
+
end
|
60
|
+
|
61
|
+
# Executes <tt>command</tt> and returns after execution
|
62
|
+
#
|
63
|
+
# * command: A string containing the command to run
|
64
|
+
# * block: Gets passed stdout and stderr every time the process
|
65
|
+
# outputs to each stream (first parameter is stdout,
|
66
|
+
# second parameter is stderr; only one will contain
|
67
|
+
# data, the other will be nil)
|
68
|
+
def execute(command, &block)
|
69
|
+
logger.info "Executing command '#{command}'"
|
70
|
+
reset
|
71
|
+
pid, stdin, stdout_stream, stderr_stream = Open4::popen4(command)
|
72
|
+
@pid = pid
|
73
|
+
stdin.close rescue nil
|
74
|
+
|
75
|
+
save_exit_status(pid)
|
76
|
+
forward_signals_to(pid) do
|
77
|
+
handle_streams stdout_stream, stderr_stream, &block
|
78
|
+
end
|
79
|
+
|
80
|
+
self
|
81
|
+
ensure
|
82
|
+
logger.debug "Closing stdout and stderr streams for process"
|
83
|
+
stdout.close rescue nil
|
84
|
+
stderr.close rescue nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Determines whether the last command was successful or not
|
88
|
+
#
|
89
|
+
# If the command's exit status was zero, this will return true.
|
90
|
+
# If the command's exit status is anything else, this will return
|
91
|
+
# false. If a command is currently running, this will return nil.
|
92
|
+
def success?
|
93
|
+
@success
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return the real exit status of the last command as it was returned to
|
97
|
+
# the system. This is useful if your doing things like running commands
|
98
|
+
# inside a vm over ssh and want to obtain the exit status from the command
|
99
|
+
# itself. If a command is currently running, this will return nil.
|
100
|
+
def status
|
101
|
+
@status
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
# Clears the variables relating to a particular command execution
|
106
|
+
#
|
107
|
+
# This is done when first initialising, and just before a command
|
108
|
+
# is run, to get rid of the previous command's data.
|
109
|
+
def reset
|
110
|
+
logger.debug "Resetting executer state"
|
111
|
+
@stdout = ''
|
112
|
+
@stderr = ''
|
113
|
+
@success = nil
|
114
|
+
@status = nil
|
115
|
+
@pid = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
# Waits for the exit status of a process (in a thread) saving it
|
119
|
+
#
|
120
|
+
# This will set the @status instance variable to true if the exit
|
121
|
+
# status was 0, or false if the exit status was anything else.
|
122
|
+
def save_exit_status(pid)
|
123
|
+
logger.debug "Spawning thread to monitor process ID #{pid}"
|
124
|
+
@success = nil
|
125
|
+
Thread.start do
|
126
|
+
logger.debug "Thread started, waiting for PID #{pid}"
|
127
|
+
@status = Process.waitpid2(pid).last.exitstatus
|
128
|
+
logger.debug "Process exited with status #{@status}"
|
129
|
+
@success = (@status == 0)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Forward signals to a process while running the given block
|
134
|
+
#
|
135
|
+
# * pid: The process ID to forward signals to
|
136
|
+
# * signals: The names of the signals to handle (optional,
|
137
|
+
# defaults to SIGINT only)
|
138
|
+
def forward_signals_to(pid, signals = %w[INT])
|
139
|
+
# Set up signal handlers
|
140
|
+
logger.debug "Setting up signal handlers for #{signals.inspect}"
|
141
|
+
signal_count = 0
|
142
|
+
signals.each do |signal|
|
143
|
+
Signal.trap(signal) do
|
144
|
+
Process.kill signal, pid rescue nil
|
145
|
+
|
146
|
+
# If this is the second time we've received and forwarded
|
147
|
+
# on the signal, make sure next time we just give up.
|
148
|
+
reset_handlers_for signals if ++signal_count >= 2
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Run the block now that the signals are being forwarded
|
153
|
+
yield
|
154
|
+
ensure
|
155
|
+
# Always good to make sure we clean up after ourselves
|
156
|
+
reset_handlers_for signals
|
157
|
+
end
|
158
|
+
|
159
|
+
# Resets the handlers for particular signals to the default
|
160
|
+
#
|
161
|
+
# * signals: An array of signal names to reset the handlers for
|
162
|
+
def reset_handlers_for(signals)
|
163
|
+
logger.debug "Resetting signal handlers for #{signals.inspect}"
|
164
|
+
signals.each {|signal| Signal.trap signal, "DEFAULT" }
|
165
|
+
end
|
166
|
+
|
167
|
+
# Manages reading from the stdout and stderr streams
|
168
|
+
#
|
169
|
+
# * stdout_stream: The process' stdout stream
|
170
|
+
# * stderr_stream: The process' stderr stream
|
171
|
+
# * block: The block to pass output to (optional)
|
172
|
+
def handle_streams(stdout_stream, stderr_stream, &block)
|
173
|
+
logger.debug "Monitoring stdout/stderr streams for output"
|
174
|
+
streams = [stdout_stream, stderr_stream]
|
175
|
+
until streams.empty?
|
176
|
+
# Find which streams are ready for reading, timeout 0.1s
|
177
|
+
selected, = select(streams, nil, nil, 0.1)
|
178
|
+
|
179
|
+
# Try again if none were ready
|
180
|
+
next if selected.nil? or selected.empty?
|
181
|
+
|
182
|
+
selected.each do |stream|
|
183
|
+
if stream.eof?
|
184
|
+
logger.debug "Stream reached end-of-file"
|
185
|
+
if @success.nil?
|
186
|
+
logger.debug "Process hasn't finished, keeping stream"
|
187
|
+
else
|
188
|
+
logger.debug "Removing stream"
|
189
|
+
streams.delete(stream)
|
190
|
+
end
|
191
|
+
next
|
192
|
+
end
|
193
|
+
|
194
|
+
while data = @reader.call(stream)
|
195
|
+
data = ((@options[:mode] == :chars) ? data.chr : data)
|
196
|
+
stream_name = (stream == stdout_stream) ? :stdout : :stderr
|
197
|
+
output data, stream_name, &block unless block.nil?
|
198
|
+
buffer data, stream_name unless @options[:no_buffer]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Outputs data to the block
|
205
|
+
#
|
206
|
+
# * data: The data that needs to be passed to the block
|
207
|
+
# * stream_name: The stream data came from (:stdout or :stderr)
|
208
|
+
# * block: The block to pass the data to
|
209
|
+
def output(data, stream_name = :stdout, &block)
|
210
|
+
# Pass the output to the block
|
211
|
+
if block.arity == 2
|
212
|
+
args = [nil, nil]
|
213
|
+
if stream_name == :stdout
|
214
|
+
args[0] = data
|
215
|
+
else
|
216
|
+
args[1] = data
|
217
|
+
end
|
218
|
+
block.call(*args)
|
219
|
+
else
|
220
|
+
yield data if stream_name == :stdout
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Buffers data for a stream into this object to retrieve it later
|
225
|
+
#
|
226
|
+
# * data: The data that should be added to the buffer
|
227
|
+
# * stream_name: Which stream the data came from (:stdout or
|
228
|
+
# :stderr)
|
229
|
+
def buffer(data, stream_name)
|
230
|
+
if stream_name == :stdout
|
231
|
+
@stdout += data
|
232
|
+
else
|
233
|
+
@stderr += data
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Derelict
|
2
|
+
# Represents a Vagrant instance installed via the Installer package
|
3
|
+
class Instance
|
4
|
+
autoload :CommandFailed, "derelict/instance/command_failed"
|
5
|
+
autoload :Invalid, "derelict/instance/invalid"
|
6
|
+
autoload :MissingBinary, "derelict/instance/missing_binary"
|
7
|
+
autoload :NonDirectory, "derelict/instance/non_directory"
|
8
|
+
autoload :NotFound, "derelict/instance/not_found"
|
9
|
+
|
10
|
+
# Include "memoize" class method to memoize methods
|
11
|
+
extend Memoist
|
12
|
+
|
13
|
+
# Include "logger" method to get a logger for this class
|
14
|
+
include Utils::Logger
|
15
|
+
|
16
|
+
# The default path to the Vagrant installation folder
|
17
|
+
DEFAULT_PATH = "/Applications/Vagrant"
|
18
|
+
|
19
|
+
attr_reader :path
|
20
|
+
|
21
|
+
# Initialize an instance for a particular directory
|
22
|
+
#
|
23
|
+
# * path: The path to the Vagrant installation folder (optional,
|
24
|
+
# defaults to DEFAULT_PATH)
|
25
|
+
def initialize(path = DEFAULT_PATH)
|
26
|
+
@path = path
|
27
|
+
logger.debug "Successfully initialized #{description}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Validates the data used for this instance
|
31
|
+
#
|
32
|
+
# Raises exceptions on failure:
|
33
|
+
#
|
34
|
+
# * +Derelict::Instance::NotFound+ if the instance is not found
|
35
|
+
# * +Derelict::Instance::NonDirectory+ if the path is a file,
|
36
|
+
# instead of a directory as expected
|
37
|
+
# * +Derelict::Instance::MissingBinary+ if the "vagrant" binary
|
38
|
+
# isn't in the expected location or is not executable
|
39
|
+
def validate!
|
40
|
+
logger.debug "Starting validation for #{description}"
|
41
|
+
raise NotFound.new path unless File.exists? path
|
42
|
+
raise NonDirectory.new path unless File.directory? path
|
43
|
+
raise MissingBinary.new vagrant unless File.exists? vagrant
|
44
|
+
raise MissingBinary.new vagrant unless File.executable? vagrant
|
45
|
+
logger.info "Successfully validated #{description}"
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# Determines the version of this Vagrant instance
|
50
|
+
def version
|
51
|
+
logger.info "Determining Vagrant version for #{description}"
|
52
|
+
output = execute!("--version").stdout
|
53
|
+
Derelict::Parser::Version.new(output).version
|
54
|
+
end
|
55
|
+
memoize :version
|
56
|
+
|
57
|
+
# Executes a Vagrant subcommand using this instance
|
58
|
+
#
|
59
|
+
# * subcommand: Vagrant subcommand to run (:up, :status, etc.)
|
60
|
+
# * arguments: Arguments to pass to the subcommand (optional)
|
61
|
+
# * options: If the last argument is a Hash, it will be used
|
62
|
+
# as a hash of options. A list of valid options is
|
63
|
+
# below. Any options provided that aren't in the
|
64
|
+
# list of valid options will get passed through to
|
65
|
+
# Derelict::Executer.execute.
|
66
|
+
# Valid option keys:
|
67
|
+
# * sudo: Whether to run the command as root, or not
|
68
|
+
# (defaults to false)
|
69
|
+
# * block: Passed through to Derelict::Executer.execute
|
70
|
+
def execute(subcommand, *arguments, &block)
|
71
|
+
options = arguments.last.is_a?(Hash) ? arguments.pop : Hash.new
|
72
|
+
command = command(subcommand, *arguments)
|
73
|
+
command = "sudo -- #{command}" if options.delete(:sudo)
|
74
|
+
logger.debug "Executing #{command} using #{description}"
|
75
|
+
Executer.execute command, options, &block
|
76
|
+
end
|
77
|
+
|
78
|
+
# Executes a Vagrant subcommand, raising an exception on failure
|
79
|
+
#
|
80
|
+
# * subcommand: Vagrant subcommand to run (:up, :status, etc.)
|
81
|
+
# * arguments: Arguments to pass to the subcommand (optional)
|
82
|
+
# * block: Passed through to Derelict::Executer.execute
|
83
|
+
#
|
84
|
+
# Raises +Derelict::Instance::CommandFailed+ if the command fails.
|
85
|
+
def execute!(subcommand, *arguments, &block)
|
86
|
+
execute(subcommand, *arguments, &block).tap do |result|
|
87
|
+
unless result.success?
|
88
|
+
command = command(subcommand, *arguments)
|
89
|
+
exception = CommandFailed.new command, result
|
90
|
+
logger.warn "Command #{command} failed: #{exception.message}"
|
91
|
+
raise exception
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Initializes a Connection for use in a particular directory
|
97
|
+
#
|
98
|
+
# * instance: The Derelict::Instance to use to control Vagrant
|
99
|
+
# * path: The project path, which contains the Vagrantfile
|
100
|
+
def connect(path)
|
101
|
+
logger.info "Creating connection for '#{path}' by #{description}"
|
102
|
+
Derelict::Connection.new(self, path).validate!
|
103
|
+
end
|
104
|
+
|
105
|
+
# Initializes a plugin manager for use with this instance
|
106
|
+
def plugins
|
107
|
+
logger.info "Creating plugin manager for #{description}"
|
108
|
+
Derelict::Plugin::Manager.new(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Initializes a box manager for use with this instance
|
112
|
+
def boxes
|
113
|
+
logger.info "Creating box manager for #{description}"
|
114
|
+
Derelict::Box::Manager.new(self)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Provides a description of this Instance
|
118
|
+
#
|
119
|
+
# Mainly used for log messages.
|
120
|
+
def description
|
121
|
+
"Derelict::Instance at '#{path}'"
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
# Retrieves the path to the vagrant binary for this instance
|
126
|
+
def vagrant
|
127
|
+
File.join(@path, "bin", "vagrant").tap do |vagrant|
|
128
|
+
logger.debug "Vagrant binary for #{description} is '#{vagrant}'"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
memoize :vagrant
|
132
|
+
|
133
|
+
|
134
|
+
# Constructs the command to execute a Vagrant subcommand
|
135
|
+
#
|
136
|
+
# * subcommand: Vagrant subcommand to run (:up, :status, etc.)
|
137
|
+
# * arguments: Arguments to pass to the subcommand (optional)
|
138
|
+
def command(subcommand, *arguments)
|
139
|
+
args = [vagrant, subcommand.to_s, arguments].flatten
|
140
|
+
args.map {|a| Shellwords.escape a }.join(' ').tap do |command|
|
141
|
+
logger.debug "Generated command '#{command}' from " +
|
142
|
+
"subcommand '#{subcommand.to_s}' with arguments " +
|
143
|
+
arguments.inspect
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|