derelict_m 0.6.2a
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 +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
|