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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/.cane +2 -0
  3. data/.coveralls.yml +1 -0
  4. data/.gitignore +18 -0
  5. data/.travis.yml +16 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +99 -0
  9. data/Rakefile +22 -0
  10. data/derelict.gemspec +63 -0
  11. data/lib/derelict.rb +74 -0
  12. data/lib/derelict/box.rb +29 -0
  13. data/lib/derelict/box/manager.rb +111 -0
  14. data/lib/derelict/box/not_found.rb +16 -0
  15. data/lib/derelict/connection.rb +84 -0
  16. data/lib/derelict/connection/invalid.rb +14 -0
  17. data/lib/derelict/connection/not_found.rb +13 -0
  18. data/lib/derelict/exception.rb +6 -0
  19. data/lib/derelict/exception/optional_reason.rb +32 -0
  20. data/lib/derelict/executer.rb +237 -0
  21. data/lib/derelict/instance.rb +147 -0
  22. data/lib/derelict/instance/command_failed.rb +30 -0
  23. data/lib/derelict/instance/invalid.rb +14 -0
  24. data/lib/derelict/instance/missing_binary.rb +13 -0
  25. data/lib/derelict/instance/non_directory.rb +13 -0
  26. data/lib/derelict/instance/not_found.rb +13 -0
  27. data/lib/derelict/parser.rb +27 -0
  28. data/lib/derelict/parser/box_list.rb +53 -0
  29. data/lib/derelict/parser/box_list/invalid_format.rb +16 -0
  30. data/lib/derelict/parser/plugin_list.rb +63 -0
  31. data/lib/derelict/parser/plugin_list/invalid_format.rb +16 -0
  32. data/lib/derelict/parser/plugin_list/needs_reinstall.rb +22 -0
  33. data/lib/derelict/parser/status.rb +90 -0
  34. data/lib/derelict/parser/status/invalid_format.rb +16 -0
  35. data/lib/derelict/parser/version.rb +28 -0
  36. data/lib/derelict/parser/version/invalid_format.rb +16 -0
  37. data/lib/derelict/plugin.rb +29 -0
  38. data/lib/derelict/plugin/manager.rb +107 -0
  39. data/lib/derelict/plugin/not_found.rb +14 -0
  40. data/lib/derelict/utils.rb +11 -0
  41. data/lib/derelict/utils/logger.rb +59 -0
  42. data/lib/derelict/utils/logger/array_outputter.rb +43 -0
  43. data/lib/derelict/utils/logger/invalid_type.rb +15 -0
  44. data/lib/derelict/utils/logger/raw_formatter.rb +12 -0
  45. data/lib/derelict/version.rb +3 -0
  46. data/lib/derelict/virtual_machine.rb +190 -0
  47. data/lib/derelict/virtual_machine/invalid.rb +14 -0
  48. data/lib/derelict/virtual_machine/not_found.rb +18 -0
  49. data/spec/coverage_helper.rb +19 -0
  50. data/spec/derelict/box/manager_spec.rb +171 -0
  51. data/spec/derelict/box/not_found_spec.rb +13 -0
  52. data/spec/derelict/box_spec.rb +37 -0
  53. data/spec/derelict/connection/invalid_spec.rb +16 -0
  54. data/spec/derelict/connection/not_found_spec.rb +13 -0
  55. data/spec/derelict/connection_spec.rb +107 -0
  56. data/spec/derelict/exception/optional_reason_spec.rb +41 -0
  57. data/spec/derelict/exception_spec.rb +11 -0
  58. data/spec/derelict/executer_spec.rb +129 -0
  59. data/spec/derelict/instance/command_failed_spec.rb +40 -0
  60. data/spec/derelict/instance/invalid_spec.rb +16 -0
  61. data/spec/derelict/instance/missing_binary_spec.rb +13 -0
  62. data/spec/derelict/instance/non_directory_spec.rb +13 -0
  63. data/spec/derelict/instance/not_found_spec.rb +13 -0
  64. data/spec/derelict/instance_spec.rb +258 -0
  65. data/spec/derelict/parser/box_list/invalid_format_spec.rb +16 -0
  66. data/spec/derelict/parser/box_list_spec.rb +64 -0
  67. data/spec/derelict/parser/plugin_list/invalid_format_spec.rb +16 -0
  68. data/spec/derelict/parser/plugin_list/needs_reinstall_spec.rb +13 -0
  69. data/spec/derelict/parser/plugin_list_spec.rb +82 -0
  70. data/spec/derelict/parser/status/invalid_format_spec.rb +16 -0
  71. data/spec/derelict/parser/status_spec.rb +214 -0
  72. data/spec/derelict/parser/version/invalid_format_spec.rb +16 -0
  73. data/spec/derelict/parser/version_spec.rb +42 -0
  74. data/spec/derelict/parser_spec.rb +24 -0
  75. data/spec/derelict/plugin/manager_spec.rb +208 -0
  76. data/spec/derelict/plugin/not_found_spec.rb +13 -0
  77. data/spec/derelict/plugin_spec.rb +37 -0
  78. data/spec/derelict/utils/logger/array_outputter_spec.rb +40 -0
  79. data/spec/derelict/utils/logger/invalid_type_spec.rb +13 -0
  80. data/spec/derelict/utils/logger/raw_formatter_spec.rb +17 -0
  81. data/spec/derelict/utils/logger_spec.rb +35 -0
  82. data/spec/derelict/virtual_machine/invalid_spec.rb +16 -0
  83. data/spec/derelict/virtual_machine/not_found_spec.rb +34 -0
  84. data/spec/derelict/virtual_machine_spec.rb +356 -0
  85. data/spec/derelict_spec.rb +50 -0
  86. data/spec/spec_helper.rb +32 -0
  87. data/spec/support/log_context.rb +36 -0
  88. 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,6 @@
1
+ module Derelict
2
+ # Base class for any exceptions thrown by Derelict
3
+ class Exception < ::Exception
4
+ autoload :OptionalReason, "derelict/exception/optional_reason"
5
+ end
6
+ 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