derelict_m 0.6.2a

Sign up to get free protection for your applications and to get access to all the features.
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