derelict 0.0.1 → 0.1.0

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 (63) hide show
  1. checksums.yaml +5 -13
  2. data/.cane +2 -0
  3. data/.coveralls.yml +1 -0
  4. data/.travis.yml +13 -0
  5. data/README.md +55 -9
  6. data/Rakefile +21 -0
  7. data/derelict.gemspec +25 -1
  8. data/lib/derelict/connection/invalid.rb +14 -0
  9. data/lib/derelict/connection/not_found.rb +13 -0
  10. data/lib/derelict/connection.rb +84 -0
  11. data/lib/derelict/exception/optional_reason.rb +32 -0
  12. data/lib/derelict/exception.rb +3 -2
  13. data/lib/derelict/instance/command_failed.rb +28 -0
  14. data/lib/derelict/instance/invalid.rb +11 -11
  15. data/lib/derelict/instance/missing_binary.rb +13 -0
  16. data/lib/derelict/instance/non_directory.rb +10 -8
  17. data/lib/derelict/instance/not_found.rb +10 -8
  18. data/lib/derelict/instance.rb +105 -33
  19. data/lib/derelict/parser/status/invalid_format.rb +16 -0
  20. data/lib/derelict/parser/status.rb +89 -0
  21. data/lib/derelict/parser/version/invalid_format.rb +16 -0
  22. data/lib/derelict/parser/version.rb +28 -0
  23. data/lib/derelict/parser.rb +25 -0
  24. data/lib/derelict/utils/logger/array_outputter.rb +43 -0
  25. data/lib/derelict/utils/logger/invalid_type.rb +15 -0
  26. data/lib/derelict/utils/logger/raw_formatter.rb +12 -0
  27. data/lib/derelict/utils/logger.rb +51 -0
  28. data/lib/derelict/utils.rb +11 -0
  29. data/lib/derelict/version.rb +2 -2
  30. data/lib/derelict/virtual_machine/invalid.rb +14 -0
  31. data/lib/derelict/virtual_machine/not_found.rb +18 -0
  32. data/lib/derelict/virtual_machine.rb +154 -0
  33. data/lib/derelict.rb +61 -14
  34. data/spec/coverage_helper.rb +16 -0
  35. data/spec/derelict/connection/invalid_spec.rb +16 -0
  36. data/spec/derelict/connection/not_found_spec.rb +13 -0
  37. data/spec/derelict/connection_spec.rb +107 -0
  38. data/spec/derelict/exception/optional_reason_spec.rb +41 -0
  39. data/spec/derelict/exception_spec.rb +11 -0
  40. data/spec/derelict/instance/command_failed_spec.rb +40 -0
  41. data/spec/derelict/instance/invalid_spec.rb +16 -0
  42. data/spec/derelict/instance/missing_binary_spec.rb +13 -0
  43. data/spec/derelict/instance/non_directory_spec.rb +13 -0
  44. data/spec/derelict/instance/not_found_spec.rb +13 -0
  45. data/spec/derelict/instance_spec.rb +226 -0
  46. data/spec/derelict/parser/status/invalid_format_spec.rb +16 -0
  47. data/spec/derelict/parser/status_spec.rb +214 -0
  48. data/spec/derelict/parser/version/invalid_format_spec.rb +16 -0
  49. data/spec/derelict/parser/version_spec.rb +31 -0
  50. data/spec/derelict/parser_spec.rb +24 -0
  51. data/spec/derelict/utils/logger/array_outputter_spec.rb +40 -0
  52. data/spec/derelict/utils/logger/invalid_type_spec.rb +13 -0
  53. data/spec/derelict/utils/logger/raw_formatter_spec.rb +17 -0
  54. data/spec/derelict/utils/logger_spec.rb +35 -0
  55. data/spec/derelict/virtual_machine/invalid_spec.rb +16 -0
  56. data/spec/derelict/virtual_machine/not_found_spec.rb +34 -0
  57. data/spec/derelict/virtual_machine_spec.rb +295 -0
  58. data/spec/derelict_spec.rb +50 -0
  59. data/spec/spec_helper.rb +28 -3
  60. data/spec/support/log_context.rb +36 -0
  61. metadata +175 -22
  62. data/lib/derelict/instance/already_active.rb +0 -9
  63. data/spec/system_spec.spec +0 -10
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YzA4ZGRlMzg1YWNjOGYzZmI2MzhlZDVlMTNiYjI5MDhmYzAxZjNmOA==
5
- data.tar.gz: !binary |-
6
- N2RmNTAwOTcwM2VkYTFmNGZmZDQ4NzI2NDhlYTgxNTZlODVjNzU3Mw==
2
+ SHA1:
3
+ metadata.gz: 4fa06a401e855d7793e04db862ea95dab0af609b
4
+ data.tar.gz: b0f47b9b5f6a5c4613cea9e73f422c62c68b3b50
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- YTM4NDgzOWUzYjQ0NzI4ODQ5OWFmMzE3MzU4NzFmNjhmMGU3NDM2OGNkZGZm
10
- NzAxMmQ0ZGQxNzMyZjljZTc2MWU3MGVkOGU0OTcwMzdhMjBhMmZlZGEzZWQy
11
- ZGU2NjAwNDhiNTNlNGFhNTVjMWExNjEyYjAyYjdhMjJiNTQ5MGE=
12
- data.tar.gz: !binary |-
13
- MmFkMmJkMjk1ZWRlNTY2N2IxYTc5ZTU5NWFhZjMxZTQ4MDI4YjQ1MDRlMzg3
14
- N2Q5ZDhjZmQxOTYyZDEzNmFiYzE1ZDY5MDhmYzg4N2I0NTY2MzIxYWM1NjEw
15
- MDMxNmUwNjRhYTgzM2QyODRkNDljNmM5ZDY2NDA5Mjk5YTMzZGU=
6
+ metadata.gz: 022c6640fd37eaf97cfa45a1d8d5ff8fed190487d032614ba138beace12b52dd94419018b006444153289fe440740a24c17f070b1ccb6a89e4bf87aa889b8228
7
+ data.tar.gz: a0e5c3a9c89be92bedb6310299634e4bf9d8cf3bd00396ff7a6b457adcc3647497400821190e3e103c8e000dc258b0c14abc675404e9a9a71b43aed783d048de
data/.cane ADDED
@@ -0,0 +1,2 @@
1
+ --style-exclude spec/**/*.rb
2
+ --gte coverage/.last_run.json,100
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
6
+ deploy:
7
+ provider: rubygems
8
+ api_key:
9
+ secure: lm8VbzXJIylbk3bHC5KBU7x0zI5zDHgOaYUkrc4z8R2gjdW9RLAoX8dwJ0N2FEX24zMf3s1fPT4dg70YD2S7FnVy3zan25BP2C2FKGkQ5kqRzV+gJCSD0Hwk8Yj5Breh1RF3xutV12asLq/+rltei7Fu6BnE2pQDk9/gGSCxILs=
10
+ gem: derelict
11
+ on:
12
+ repo: bradfeehan/derelict
13
+ ruby: 2.0.0
data/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Derelict
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/derelict.png)](http://badge.fury.io/rb/derelict)
4
+ [![Build Status](https://travis-ci.org/bradfeehan/derelict.png?branch=master)](https://travis-ci.org/bradfeehan/derelict)
5
+ [![Coverage Status](https://coveralls.io/repos/bradfeehan/derelict/badge.png)](https://coveralls.io/r/bradfeehan/derelict)
6
+ [![Code Climate](https://codeclimate.com/github/bradfeehan/derelict.png)](https://codeclimate.com/github/bradfeehan/derelict)
7
+ [![Dependency Status](https://gemnasium.com/bradfeehan/derelict.png)](https://gemnasium.com/bradfeehan/derelict)
8
+
3
9
  Provides a Ruby API to control [Vagrant][1] where Vagrant is installed
4
10
  via the Installer package on Mac OS X.
5
11
 
@@ -13,15 +19,9 @@ Currently a work-in-progress.
13
19
  Vagrant was historically available as a [gem][2], naturally providing a
14
20
  Ruby API to control Vagrant in other Ruby libraries and applications.
15
21
  However, [since version 1.1.0][3], [Vagrant is distributed exclusively
16
- using an Installer package][4]. To control Vagrant when it's installed
17
- this way, other Ruby libraries and applications typically need to
18
- invoke the Vagrant binary, which requires forking a new process and
19
- parsing its output using string manipulation.
20
-
21
- Derelict is a Ruby gem that can control an instance of Vagrant that's
22
- installed with the Installer package, which avoids calling the Vagrant
23
- binary by interfacing directly with the Ruby source files in the
24
- Vagrant installation.
22
+ using an Installer package][4]. Derelict is a Ruby library that wraps
23
+ the Vagrant binary, shelling out and parsing the results of each
24
+ command.
25
25
 
26
26
  [2]: <https://rubygems.org>
27
27
  [3]: <https://groups.google.com/forum/#!msg/vagrant-up/kX_wvn7wcds/luwNur4kgDEJ>
@@ -43,6 +43,52 @@ Or install it yourself as:
43
43
  $ gem install derelict
44
44
 
45
45
 
46
+ ## Usage
47
+
48
+ Some examples of common operations using Derelict:
49
+
50
+ ```ruby
51
+ require "derelict"
52
+
53
+ # Determine if there's a "default" VM defined in /path/to/project
54
+ Derelict.instance.connect("/path/to/project").vm(:default).exists?
55
+ ```
56
+
57
+ ### Advanced
58
+
59
+ ```ruby
60
+ require "derelict"
61
+
62
+ # Create an instance (represents a Vagrant installation)
63
+ instance = Derelict.instance("/path/to/vagrant")
64
+ instance = Derelict.instance # Defaults to /Applications/Vagrant
65
+
66
+ # Issue commands to the instance directly (not usually necessary)
67
+ result = instance.execute('--version') # Shell::Executer object
68
+ print "success" if result.success? # if Vagrant's exit status was 0
69
+ print result.stdout # "Vagrant 1.3.3\n"
70
+
71
+ # Connect to a Vagrant project (containing a Vagrantfile)
72
+ connection = instance.connect("/path/to/project")
73
+
74
+ # Issue commands to the connection directly (runs from the project dir)
75
+ result = connection.execute(:up) # runs "vagrant up" in project dir
76
+ result.success? # it's a Shell::Executer object again
77
+
78
+ # Retrieve a particular VM from a connection (multi-machine support)
79
+ vm = connection.vm(:web) # "vm" is a Derelict::VirtualMachine
80
+ vm.exists? # does the connection define a "web" VM?
81
+ vm.state # current VM state (:running, :not_created...)
82
+ vm.running? # whether the VM is currently running or not
83
+ vm.up! # runs "vagrant up" for this VM only
84
+ vm.halt! # runs "vagrant halt" for this VM only
85
+ vm.destroy! # runs "vagrant destroy --force" for this VM
86
+ vm.reload! # runs "vagrant reload" for this VM only
87
+ vm.suspend! # runs "vagrant suspend" for this VM only
88
+ vm.resume! # runs "vagrant resume" for this VM only
89
+ ```
90
+
91
+
46
92
  ## Contributing
47
93
 
48
94
  1. Fork it
data/Rakefile CHANGED
@@ -1 +1,22 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ # Define "spec" task using RSpec's built-in Rake task
5
+ RSpec::Core::RakeTask.new :spec do |spec|
6
+ spec.verbose = false
7
+ end
8
+
9
+ version_major = RbConfig::CONFIG["MAJOR"].to_i
10
+ version_minor = RbConfig::CONFIG["MINOR"].to_i
11
+ if version_major >= 1 and version_minor >= 9
12
+ require "cane/rake_task"
13
+
14
+ # Define "quality" task using Cane's built-in Rake task
15
+ Cane::RakeTask.new :quality do |quality|
16
+ quality.canefile = File.join File.dirname(__FILE__), ".cane"
17
+ end
18
+
19
+ task :default => [:spec, :quality]
20
+ else
21
+ task :default => :spec
22
+ end
data/derelict.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  "applications typically need to invoke the Vagrant binary, which ",
21
21
  "requires forking a new process and parsing its output using ",
22
22
  "string manipulation.",
23
- ].join
23
+ ].join,
24
24
  spec.summary =
25
25
  "Ruby API for Vagrant installed via Installer package on Mac OS X."
26
26
  spec.homepage = "https://github.com/bradfeehan/derelict"
@@ -31,7 +31,31 @@ Gem::Specification.new do |spec|
31
31
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
32
32
  spec.require_paths = ["lib"]
33
33
 
34
+ spec.add_runtime_dependency "log4r"
35
+ spec.add_runtime_dependency "memoist"
36
+ spec.add_runtime_dependency "shell-executer"
37
+
38
+
39
+ version_major = RbConfig::CONFIG["MAJOR"].to_i
40
+ version_minor = RbConfig::CONFIG["MINOR"].to_i
41
+ cane_supported = (version_major >= 1 and version_minor >= 9)
42
+
34
43
  spec.add_development_dependency "bundler", "~> 1.3"
44
+ spec.add_development_dependency "cane" if cane_supported
45
+ spec.add_development_dependency "coveralls"
35
46
  spec.add_development_dependency "rake"
36
47
  spec.add_development_dependency "rspec"
48
+ spec.add_development_dependency "simplecov"
49
+ spec.add_development_dependency "its"
50
+
51
+ # When running on Travis CI, any passing builds will be deployed
52
+ # (i.e. pushed to RubyGems). This changes the version number so that
53
+ # these deployments are marked as pre-release versions (which will
54
+ # occur if the version number has a letter in it, so we add
55
+ # "travis" followed by the job number to the version string).
56
+ # So version 1.2.3 will be marked as (e.g.) "1.2.4.travis.567".
57
+ if ENV["TRAVIS"]
58
+ build = ENV["TRAVIS_JOB_NUMBER"].split(".").first
59
+ spec.version = "#{spec.version.to_s.succ}.travis.#{build}"
60
+ end
37
61
  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,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 Shell.execute (shell-executer)
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,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
@@ -1,5 +1,6 @@
1
- class Derelict
2
- # The base class for exceptions thrown by Derelict
1
+ module Derelict
2
+ # Base class for any exceptions thrown by Derelict
3
3
  class Exception < ::Exception
4
+ autoload :OptionalReason, "derelict/exception/optional_reason"
4
5
  end
5
6
  end
@@ -0,0 +1,28 @@
1
+ module Derelict
2
+ class Instance
3
+ # Represents an invalid instance, which can't be used with Derelict
4
+ class CommandFailed < Derelict::Exception
5
+ # Initializes a new instance of this exception, with a reason
6
+ #
7
+ # * reason: The result (Shell::Executer) for the command that
8
+ # failed (optional, provides extra detail in the
9
+ # message)
10
+ def initialize(command = nil, result = nil)
11
+ super [default_message, describe(command, result)].join
12
+ end
13
+
14
+ private
15
+ # Retrieves the default error message
16
+ def default_message
17
+ "Error executing Vagrant command"
18
+ end
19
+
20
+ def describe(command = nil, result = nil)
21
+ [
22
+ command.nil? ? "" : " '#{command}'",
23
+ result.nil? ? "" : ", STDERR output:\n#{result.stderr}",
24
+ ].join
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,14 +1,14 @@
1
- class Derelict
2
- # The requested Vagrant instance can't be used with Derelict
3
- class Instance::Invalid < Exception
4
- # Initialize with a default message (with optional detailed reason)
5
- #
6
- # reason: An additional reason to add to the message (optional)
7
- def initialize(reason = nil)
8
- super [
9
- "Derelict can't use the specified Vagrant instance",
10
- reason.nil? ? '' : ": #{reason}"
11
- ].join
1
+ module Derelict
2
+ class Instance
3
+ # Represents an invalid instance, which can't be used with Derelict
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 instance"
11
+ end
12
12
  end
13
13
  end
14
14
  end
@@ -0,0 +1,13 @@
1
+ module Derelict
2
+ class Instance
3
+ # The "vagrant" binary was missing from the instance
4
+ class MissingBinary < Invalid
5
+ # Initializes a new instance of this exception for a given file
6
+ #
7
+ # * file: The expected location of the binary
8
+ def initialize(file)
9
+ super "'vagrant' binary not found at #{file}"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,11 +1,13 @@
1
- class Derelict::Instance
2
- # The requested Vagrant instance path is not a directory
3
- class NonDirectory < Invalid
4
- # Creates an instance of this exception for a particular path
5
- #
6
- # path: The path that this exception relates to
7
- def initialize(path)
8
- super "not a directory: #{path}"
1
+ module Derelict
2
+ class Instance
3
+ # The path used for the instance was a file, not a directory
4
+ class NonDirectory < 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 "expected directory, found file: #{path}"
10
+ end
9
11
  end
10
12
  end
11
13
  end
@@ -1,11 +1,13 @@
1
- class Derelict::Instance
2
- # The requested Vagrant instance directory doesn't exist
3
- class NotFound < Invalid
4
- # Creates an instance of this exception for a particular path
5
- #
6
- # path: The path that this exception relates to
7
- def initialize(path)
8
- super "directory not found: #{path}"
1
+ module Derelict
2
+ class Instance
3
+ # The path used for the instance 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 "directory doesn't exist: #{path}"
10
+ end
9
11
  end
10
12
  end
11
13
  end
@@ -1,53 +1,125 @@
1
- class Derelict
2
- # Represents a connection to an package-installed instance of Vagrant
1
+ module Derelict
2
+ # Represents a Vagrant instance installed via the Installer package
3
3
  class Instance
4
- autoload :AlreadyActive, "derelict/instance/already_active"
4
+ autoload :CommandFailed, "derelict/instance/command_failed"
5
+ autoload :Invalid, "derelict/instance/invalid"
6
+ autoload :MissingBinary, "derelict/instance/missing_binary"
5
7
  autoload :NonDirectory, "derelict/instance/non_directory"
6
8
  autoload :NotFound, "derelict/instance/not_found"
7
9
 
8
- attr_reader :namespace
10
+ # Include "memoize" class method to memoize methods
11
+ extend Memoist
9
12
 
10
- # Initializes a new instance
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
11
22
  #
12
- # path: The path to the location of the Vagrant instance
13
- def initialize(path)
14
- raise NotFound.new unless File.exists? path
15
- raise NonDirectory.new unless File.directory? path
16
- @path = path.chomp "/"
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}"
17
28
  end
18
29
 
19
- # Loads the Vagrant module into an instance variable
20
- def activate!
21
- raise AlreadyActive.new if Derelict.active?
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
22
48
 
23
- gemspec_files.each {|gemspec_file|
24
- Gem::Specification.load(gemspec_file).add_self_to_load_path
25
- }
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
26
56
 
27
- require vagrant_entrypoint
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
+ # * block: Passed through to Shell.execute (shell-executer)
62
+ def execute(subcommand, *arguments, &block)
63
+ command = command(subcommand, *arguments)
64
+ logger.debug "Executing #{command} using #{description}"
65
+ Shell.execute command, &block
28
66
  end
29
67
 
30
- # # Pass through any method calls to the Vagrant module
31
- # def method_missing(method, *args)
32
- # @module.Vagrant.send method, args
33
- # end
68
+ # Executes a Vagrant subcommand, raising an exception on failure
69
+ #
70
+ # * subcommand: Vagrant subcommand to run (:up, :status, etc.)
71
+ # * arguments: Arguments to pass to the subcommand (optional)
72
+ # * block: Passed through to Shell.execute (shell-executer)
73
+ #
74
+ # Raises +Derelict::Instance::CommandFailed+ if the command fails.
75
+ def execute!(subcommand, *arguments, &block)
76
+ execute(subcommand, *arguments, &block).tap do |result|
77
+ unless result.success?
78
+ command = command(subcommand, *arguments)
79
+ exception = CommandFailed.new command
80
+ logger.warn "Command #{command} failed: #{exception.message}"
81
+ raise exception, result
82
+ end
83
+ end
84
+ end
85
+
86
+ # Initializes a Connection for use in a particular directory
87
+ #
88
+ # * instance: The Derelict::Instance to use to control Vagrant
89
+ # * path: The project path, which contains the Vagrantfile
90
+ def connect(path)
91
+ logger.info "Creating connection for '#{path}' by #{description}"
92
+ Derelict::Connection.new(self, path).validate!
93
+ end
94
+
95
+ # Provides a description of this Instance
96
+ #
97
+ # Mainly used for log messages.
98
+ def description
99
+ "Derelict::Instance at '#{path}'"
100
+ end
34
101
 
35
102
  private
36
- # Retrieves the path to the directory containing embedded gems
37
- def gems_path
38
- File.join @path, "embedded", "gems"
103
+ # Retrieves the path to the vagrant binary for this instance
104
+ def vagrant
105
+ File.join(@path, "bin", "vagrant").tap do |vagrant|
106
+ logger.debug "Vagrant binary for #{description} is '#{vagrant}'"
107
+ end
39
108
  end
109
+ memoize :vagrant
40
110
 
41
- # Creates a pattern to match all embedded gemspec files
42
- def gemspec_files
43
- pattern = File.join gems_path, "specifications", "*.gemspec"
44
- Dir.glob(pattern).sort
45
- end
46
111
 
47
- # Retrieves the absolute path of the main vagrant.rb in the gem
48
- def vagrant_entrypoint
49
- file = File.join gems_path, *%w[gems vagrant-* lib vagrant.rb]
50
- Dir.glob(file).sort.last
112
+ # Constructs the command to execute a Vagrant subcommand
113
+ #
114
+ # * subcommand: Vagrant subcommand to run (:up, :status, etc.)
115
+ # * arguments: Arguments to pass to the subcommand (optional)
116
+ def command(subcommand, *arguments)
117
+ args = [vagrant, subcommand.to_s, arguments].flatten
118
+ args.map {|a| Shellwords.escape a }.join(' ').tap do |command|
119
+ logger.debug "Generated command '#{command}' from " +
120
+ "subcommand '#{subcommand.to_s}' with arguments " +
121
+ arguments.inspect
122
+ end
51
123
  end
52
124
  end
53
125
  end
@@ -0,0 +1,16 @@
1
+ module Derelict
2
+ class Parser
3
+ class Status
4
+ # The status wasn't in the expected format and couldn't be parsed
5
+ class InvalidFormat < Derelict::Exception
6
+ include Derelict::Exception::OptionalReason
7
+
8
+ private
9
+ # Retrieves the default error message
10
+ def default_message
11
+ "Output from 'vagrant status' was in an unexpected format"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end