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,29 @@
1
+ module Derelict
2
+ # Represents an individual Vagrant plugin at a particular version
3
+ class Plugin
4
+ autoload :Manager, "derelict/plugin/manager"
5
+ autoload :NotFound, "derelict/plugin/not_found"
6
+
7
+ attr_reader :name, :version
8
+
9
+ # Initializes a plugin with a particular name and version
10
+ #
11
+ # * name: The name of the plugin represented by this object
12
+ # * version: The version of the plugin represented by this object
13
+ def initialize(name, version)
14
+ @name = name
15
+ @version = version
16
+ end
17
+
18
+ # Ensure equivalent Plugins are equal to this one
19
+ def ==(other)
20
+ other.name == name and other.version == version
21
+ end
22
+ alias_method :eql?, :==
23
+
24
+ # Make equivalent Plugins hash to the same value
25
+ def hash
26
+ name.hash ^ version.hash
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,107 @@
1
+ module Derelict
2
+ class Plugin
3
+ # A class that handles managing plugins for a Vagrant instance
4
+ class Manager
5
+ # Include "memoize" class method to memoize methods
6
+ extend Memoist
7
+
8
+ # Include "logger" method to get a logger for this class
9
+ include Utils::Logger
10
+
11
+ attr_reader :instance
12
+
13
+ # Initializes a Manager for use with a particular instance
14
+ #
15
+ # * instance: The Derelict::Instance which will have its
16
+ # plugins managed by this Manager
17
+ def initialize(instance)
18
+ @instance = instance
19
+ logger.debug "Successfully initialized #{description}"
20
+ end
21
+
22
+ # Retrieves the Set of currently installed plugins
23
+ def list
24
+ logger.info "Retrieving Vagrant plugin list for #{description}"
25
+ output = instance.execute!(:plugin, "list").stdout
26
+ Derelict::Parser::PluginList.new(output).plugins
27
+ end
28
+ memoize :list
29
+
30
+ # Determines whether a particular plugin is installed
31
+ #
32
+ # * plugin_name: Name of the plugin to look for (as a string)
33
+ def installed?(plugin_name, version = nil)
34
+ fetch(plugin_name).version == version or version.nil?
35
+ rescue Plugin::NotFound
36
+ false
37
+ end
38
+
39
+ # Installs a plugin (optionally a particular version)
40
+ #
41
+ # If no version is specified, the latest stable version is used
42
+ # by Vagrant.
43
+ #
44
+ # * plugin_name: Name of the plugin to install (as a string)
45
+ # * options: Hash of options, valid keys:
46
+ # * version: Particular version to install (optional,
47
+ # latest version will be installed if omitted)
48
+ # * log: Whether to log the output (optional, defaults
49
+ # to false)
50
+ def install(plugin_name, options = {})
51
+ options = {:log => false, :version => nil}.merge(options)
52
+ logger.info "Installing plugin '#{plugin_name}' using #{description}"
53
+
54
+ version = options[:version]
55
+ command = [:plugin, "install", plugin_name]
56
+ command.concat ["--plugin-version", version] unless version.nil?
57
+
58
+ log_block = options[:log] ? shell_log_block : nil
59
+ instance.execute!(*command, &log_block).tap do
60
+ flush_cache # flush memoized method return values
61
+ end
62
+ end
63
+
64
+ # Uninstalls a particular Vagrant plugin
65
+ #
66
+ # * plugin_name: Name of the plugin to uninstall (as a string)
67
+ def uninstall(plugin_name, options = {})
68
+ options = {:log => false}.merge(options)
69
+ logger.info "Uninstalling plugin '#{plugin_name}' using #{description}"
70
+
71
+ log_block = options[:log] ? shell_log_block : nil
72
+ instance.execute!(:plugin, "uninstall", plugin_name, &log_block).tap do
73
+ flush_cache # flush memoized method return values
74
+ end
75
+ end
76
+
77
+ # Updates a particular Vagrant plugin
78
+ #
79
+ # * plugin_name: Name of the plugin to update (as a string)
80
+ def update(plugin_name, options = {})
81
+ options = {:log => false}.merge(options)
82
+ logger.info "Updating plugin '#{plugin_name}' using #{description}"
83
+
84
+ log_block = options[:log] ? shell_log_block : nil
85
+ instance.execute!(:plugin, "update", plugin_name, &log_block).tap do
86
+ flush_cache # flush memoized method return values
87
+ end
88
+ end
89
+
90
+ # Retrieves a plugin with a particular name
91
+ #
92
+ # * plugin_name: Name of the plugin to look for (as a string)
93
+ def fetch(plugin_name)
94
+ list.find {|plugin| plugin.name == plugin_name}.tap do |plugin|
95
+ raise Plugin::NotFound.new plugin_name if plugin.nil?
96
+ end
97
+ end
98
+
99
+ # Provides a description of this Connection
100
+ #
101
+ # Mainly used for log messages.
102
+ def description
103
+ "Derelict::Plugin::Manager for #{instance.description}"
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,14 @@
1
+ module Derelict
2
+ class Plugin
3
+ # A plugin that isn't currently installed has been retrieved
4
+ class NotFound < Derelict::Exception
5
+ # Initializes a new instance of this exception, for a plugin name
6
+ #
7
+ # * plugin_name: The name of the plugin that this exception
8
+ # relates to
9
+ def initialize(plugin_name)
10
+ super "Plugin '#{plugin_name}' is not currently installed"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module Derelict
2
+ # A namespaced collection of utilities for general purpose use
3
+ #
4
+ # Derelict::Utils contains all the individual sub-modules inside it.
5
+ module Utils
6
+ autoload :Logger, "derelict/utils/logger"
7
+
8
+ # Include sub-modules here
9
+ include Derelict::Utils::Logger
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ module Derelict
2
+ module Utils
3
+ # Provides a method to retrieve a logger
4
+ module Logger
5
+ autoload :ArrayOutputter, "derelict/utils/logger/array_outputter"
6
+ autoload :InvalidType, "derelict/utils/logger/invalid_type"
7
+ autoload :RawFormatter, "derelict/utils/logger/raw_formatter"
8
+
9
+ # Retrieves the logger for this class
10
+ def logger(options = {})
11
+ options = {:type => :internal}.merge(options)
12
+
13
+ case options[:type].to_sym
14
+ when :external
15
+ external_logger
16
+ when :internal
17
+ find_or_create_logger(logger_name)
18
+ else raise InvalidType.new options[:type]
19
+ end
20
+ end
21
+
22
+ private
23
+ # A block that can be passed to #execute to log the output
24
+ def shell_log_block
25
+ Proc.new do |stdout, stderr|
26
+ # Only stdout or stderr is populated, the other will be nil
27
+ logger(:type => :external).info(stdout || stderr)
28
+ end
29
+ end
30
+
31
+ # Finds or creates a Logger with a particular fullname
32
+ def find_or_create_logger(fullname)
33
+ Log4r::Logger[fullname.to_s] || Log4r::Logger.new(fullname.to_s)
34
+ end
35
+
36
+ # Gets the "external" logger, used to print to stdout
37
+ def external_logger
38
+ @@external ||= find_or_create_logger("external").tap do |external|
39
+ logger.debug "Created external logger instance"
40
+ external.add(Log4r::Outputter.stdout.tap do |outputter|
41
+ outputter.formatter = RawFormatter.new
42
+ end)
43
+ end
44
+ end
45
+
46
+ # Retrieves the name of the logger for this class
47
+ #
48
+ # By default, the name of the logger is just the lowercase
49
+ # version of the class name.
50
+ def logger_name
51
+ if self.is_a? Module
52
+ self.name.downcase
53
+ else
54
+ self.class.name.downcase
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,43 @@
1
+ module Derelict
2
+ module Utils
3
+ module Logger
4
+ # A Log4r Outputter which stores all logs in an array
5
+ #
6
+ # Logs are stored in the internal array by #write. Logs can be
7
+ # cleared using #flush, which returns the flushed logs too.
8
+ class ArrayOutputter < Log4r::Outputter
9
+ # Include "memoize" class method to memoize methods
10
+ extend Memoist
11
+
12
+ # Force the outputter to receive and store all levels of messages
13
+ def level
14
+ Log4r::ALL
15
+ end
16
+
17
+ # The internal array of messages
18
+ def messages
19
+ []
20
+ end
21
+ memoize :messages
22
+
23
+ # Clear internal log messages array and return the erased data
24
+ def flush
25
+ messages.dup.tap { messages.clear }
26
+ end
27
+
28
+ private
29
+
30
+ # Write a message to the internal array
31
+ #
32
+ # This is an abstract method in the parent class, and handles
33
+ # persisting the log data. In this class, it saves the message
34
+ # into an internal array to be retrieved later.
35
+ #
36
+ # * message: The log message to be persisted
37
+ def write(message)
38
+ messages << message
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ module Derelict
2
+ module Utils
3
+ module Logger
4
+ # The "type" option when requesting the logger was invalid
5
+ class InvalidType < ::Derelict::Exception
6
+ # Initializes a new instance of this exception for a type
7
+ #
8
+ # * type: The (invalid) requested type
9
+ def initialize(type)
10
+ super "Invalid logger type '#{type}'"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module Derelict
2
+ module Utils
3
+ module Logger
4
+ # A Formatter that passes the log message through untouched
5
+ class RawFormatter < Log4r::Formatter
6
+ def format(event)
7
+ event.data
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Derelict
2
+ VERSION = "0.6.2a"
3
+ end
@@ -0,0 +1,190 @@
1
+ module Derelict
2
+ # A Vagrant virtual machine in a particular Derelict connection
3
+ class VirtualMachine
4
+ autoload :Invalid, "derelict/virtual_machine/invalid"
5
+ autoload :NotFound, "derelict/virtual_machine/not_found"
6
+
7
+ # Include "memoize" class method to memoize methods
8
+ extend Memoist
9
+
10
+ # Include "logger" method to get a logger for this class
11
+ include Utils::Logger
12
+
13
+ COMMANDS = [
14
+ :up,
15
+ :halt,
16
+ :destroy,
17
+ :reload,
18
+ :suspend,
19
+ :resume,
20
+ ]
21
+
22
+ attr_reader :connection
23
+ attr_reader :name
24
+
25
+ # Initializes a new VirtualMachine for a connection and name
26
+ #
27
+ # * connection: The +Derelict::Connection+ to use to manipulate
28
+ # the VirtualMachine instance
29
+ # * name: The name of the virtual machine, used when
30
+ # communicating with the connection)
31
+ def initialize(connection, name)
32
+ @connection = connection
33
+ @name = name
34
+ logger.debug "Successfully initialized #{description}"
35
+ end
36
+
37
+ # Validates the data used for this connection
38
+ #
39
+ # Raises exceptions on failure:
40
+ #
41
+ # * +Derelict::VirtualMachine::NotFound+ if the connection
42
+ # doesn't know about a virtual machine with the requested
43
+ # name
44
+ def validate!
45
+ logger.debug "Starting validation for #{description}"
46
+ raise NotFound.new name, connection unless exists?
47
+ logger.info "Successfully validated #{description}"
48
+ self
49
+ end
50
+
51
+ # Determines whether this Vagrant virtual machine exists
52
+ #
53
+ # Returns +true+ if the connection reports a virtual machine with
54
+ # the requested name, otherwise returns +false+.
55
+ def exists?
56
+ status.exists? name
57
+ end
58
+ memoize :exists?
59
+
60
+ # Gets the current state of this Vagrant virtual machine
61
+ #
62
+ # The state is returned as a symbol, e.g. :running.
63
+ def state
64
+ status.state name
65
+ end
66
+ memoize :state
67
+
68
+ # Determines whether this virtual machine is currently running
69
+ def running?
70
+ (state == :running)
71
+ end
72
+ memoize :running?
73
+
74
+ # Add methods for each command
75
+ #
76
+ # A method is defined for each of the symbols in COMMANDS. The
77
+ # method name will be the symbol with an added bang (!). For
78
+ # example, #up!, #halt!, etc.
79
+ #
80
+ # Each method takes an options hash as an argument. For example:
81
+ #
82
+ # vm.up! :log => true
83
+ #
84
+ # This example will run the "up" command with logging enabled. The
85
+ # option keys can optionally include any of the following symbols:
86
+ #
87
+ # * log: Should the log output be printed? (defaults to false)
88
+ COMMANDS.each do |command|
89
+ define_method :"#{command}!" do |*opts|
90
+ # Ideally this block would have one argument with a default
91
+ # value of an empty Hash. Unfortunately, setting a default
92
+ # value for the arguments to a block is only supported in Ruby
93
+ # 1.9+. The splatted arguments thing is a way to allow zero or
94
+ # one argument, but it actually allows any number of arguments.
95
+ # So we need to emulate the error Ruby would throw if you give
96
+ # the wrong number of arguments.
97
+ if opts.length > 1
98
+ message = "wrong number of arguments (#{opts.length} for 0-1)"
99
+ raise ArgumentError.new message
100
+ end
101
+
102
+ # Set defaults for the opts hash
103
+ opts = {:color => false, :log => false}.merge(opts.first || {})
104
+
105
+ # Log message if there's one for this command
106
+ log_message_for(command).tap {|m| logger.info m unless m.nil? }
107
+
108
+ # Execute the command!
109
+ execute! command, opts
110
+ end
111
+ end
112
+
113
+ # Retrieves the (parsed) status from the connection
114
+ def status
115
+ logger.info "Retrieving Vagrant status for #{description}"
116
+ output = connection.execute!(:status).stdout
117
+ Derelict::Parser::Status.new(output)
118
+ end
119
+ memoize :status
120
+
121
+ # Provides a description of this Connection
122
+ #
123
+ # Mainly used for log messages.
124
+ def description
125
+ "Derelict::VirtualMachine '#{name}' from #{connection.description}"
126
+ end
127
+
128
+ private
129
+ # Executes a command on the connection for this VM
130
+ #
131
+ # * command: The command to execute (as a symbol)
132
+ # * options: A Hash of options, with the following optional keys:
133
+ # * log: Logs the output of the command if true
134
+ # (defaults to false)
135
+ # * log_mode: Controls how commands are logged (one of
136
+ # either :chars or :lines, defaults to :lines)
137
+ # * color: Uses color in the log output (defaults to
138
+ # false, only relevant if log is true)
139
+ # * provider: The Vagrant provider to use, one of
140
+ # "virtualbox" or "vmware_fusion" (defaults to
141
+ # "virtualbox")
142
+ def execute!(command, options)
143
+ # Build up the arguments to pass to connection.execute!
144
+ arguments = [command, name, *arguments_for(command)]
145
+ arguments << "--color" if options[:color]
146
+ if options[:provider]
147
+ arguments << "--provider"
148
+ arguments << options[:provider]
149
+ end
150
+
151
+ if options[:log_mode]
152
+ arguments << {:mode => options[:log_mode]}
153
+ end
154
+
155
+ # Set up the block to use when executing -- if logging is
156
+ # enabled, use a block that logs the output; otherwise no block.
157
+ block = options[:log] ? shell_log_block : nil
158
+
159
+ # Execute the command
160
+ connection.execute! *arguments, &block
161
+ end
162
+
163
+ # Retrieves the arguments for a particular action
164
+ #
165
+ # * action: The symbol representing the action (one of :up,
166
+ # :halt, :destroy, :reload, :suspend, :resume)
167
+ def arguments_for(action)
168
+ case action
169
+ when :destroy then ['--force']
170
+ else []
171
+ end
172
+ end
173
+
174
+ # Retrieves the correct log message for a particular action
175
+ #
176
+ # * action: The symbol representing the action (one of :up,
177
+ # :halt, :destroy, :reload, :suspend, :resume)
178
+ def log_message_for(action)
179
+ case action
180
+ when :up then "Bringing up #{description}"
181
+ when :halt then "Halting #{description}"
182
+ when :destroy then "Destroying #{description}"
183
+ when :reload then "Reloading #{description}"
184
+ when :suspend then "Suspending #{description}"
185
+ when :resume then "Resuming #{description}"
186
+ else nil
187
+ end
188
+ end
189
+ end
190
+ end