vagrant_cloud 2.0.0 → 3.0.1

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.
@@ -0,0 +1,293 @@
1
+ module VagrantCloud
2
+ # Generic data class which provides simple attribute
3
+ # data storage using a Hash like interface
4
+ class Data
5
+ # Custom nil class which is used for signifying
6
+ # a nil value that was not set by the user. This
7
+ # makes it easy to filter out values which are
8
+ # unset vs. those that are set to nil.
9
+ class NilClass < BasicObject
10
+ include ::Singleton
11
+ def nil?; true; end
12
+ def ==(v); v.nil? || super(v); end
13
+ def ===(v); equal?(v); end
14
+ def equal?(v); v.nil? || super(v); end
15
+ def to_i; 0; end
16
+ def to_f; 0.0; end
17
+ def to_a; []; end
18
+ def to_h; {}; end
19
+ def to_s; ""; end
20
+ def &(_); false; end
21
+ def |(_); false; end
22
+ def ^(_); false; end
23
+ def !; true; end
24
+ def inspect; 'nil'; end
25
+ end
26
+
27
+ # Easy to use constant to access general
28
+ # use instance of our custom nil class
29
+ Nil = NilClass.instance
30
+
31
+ # Create a new instance
32
+ #
33
+ # @return [Data]
34
+ def initialize(**opts)
35
+ @data = opts
36
+ end
37
+
38
+ # Fetch value from data
39
+ #
40
+ # @param [String, Symbol] k Name of value
41
+ # @return [Object]
42
+ def [](k)
43
+ @data.key?(k.to_sym) ? @data[k.to_sym] : Nil
44
+ end
45
+
46
+ # @return [String]
47
+ def inspect
48
+ "<#{self.class.name}:#{sprintf("%#x", object_id)}>"
49
+ end
50
+
51
+ protected def data; @data; end
52
+
53
+ # Immutable data class. This class adds extra functionality to the Data
54
+ # class like providing attribute methods which can be defined using the
55
+ # `attr_required` and `attr_optional` methods. Once an instance is created
56
+ # the data is immutable. For example:
57
+ #
58
+ # class MyData < Immutable
59
+ # attr_required :name
60
+ # attr_optional :version
61
+ # end
62
+ #
63
+ # When creating a new instance, a name parameter _must_ be provided,
64
+ # but a version parameter is optional, so both are valid:
65
+ #
66
+ # instance = MyData.new(name: "testing", version: "new-version")
67
+ #
68
+ # and
69
+ #
70
+ # instance = MyData.new(name: "testing")
71
+ #
72
+ # but only providing the version is invalid:
73
+ #
74
+ # instance = MyData.new(version: "new-version") # -> Exception
75
+ class Immutable < Data
76
+ @@lock = Mutex.new
77
+
78
+ # Define attributes which are required
79
+ def self.attr_required(*args)
80
+ return @required || [] if args.empty?
81
+ sync do
82
+ @required ||= []
83
+ if !args.empty?
84
+ # Create any accessor methods which do not yet exist
85
+ args = args.map(&:to_sym) - @required
86
+ args.each do |argument_name|
87
+ if !method_defined?(argument_name)
88
+ define_method(argument_name) {
89
+ send(:[], argument_name.to_sym)
90
+ }
91
+ end
92
+ end
93
+ @required += args
94
+ end
95
+ @required
96
+ end
97
+ end
98
+
99
+ # Define attributes which are optional
100
+ def self.attr_optional(*args)
101
+ return @optional || [] if args.empty?
102
+ sync do
103
+ @optional ||= []
104
+ if !args.empty?
105
+ # Create any accessor method which do not yet exist
106
+ args = args.map(&:to_sym) - @optional
107
+ args.each do |argument_name|
108
+ if !method_defined?(argument_name)
109
+ define_method(argument_name) {
110
+ send(:[], argument_name.to_sym)
111
+ }
112
+ end
113
+ end
114
+ @optional += args
115
+ end
116
+ @optional
117
+ end
118
+ end
119
+
120
+ # If inherited, set attribute information
121
+ def self.inherited(klass)
122
+ klass.attr_required(*attr_required)
123
+ klass.attr_optional(*attr_optional)
124
+ klass.class_variable_set(:@@lock, Mutex.new)
125
+ end
126
+
127
+ # Synchronize action
128
+ def self.sync
129
+ @@lock.synchronize do
130
+ yield
131
+ end
132
+ end
133
+
134
+ # Create a new instance
135
+ #
136
+ # @return [Immutable]
137
+ def initialize(**opts)
138
+ super()
139
+ self.class.attr_required.each do |attr|
140
+ if !opts.key?(attr)
141
+ raise ArgumentError, "Missing required parameter `#{attr}`"
142
+ end
143
+ data[attr.to_sym] = opts[attr].dup
144
+ end
145
+ self.class.attr_optional.each do |attr|
146
+ if opts.key?(attr)
147
+ data[attr.to_sym] = opts[attr].dup
148
+ end
149
+ end
150
+ extras = opts.keys - (self.class.attr_required + self.class.attr_optional)
151
+ if !extras.empty?
152
+ raise ArgumentError, "Unknown parameters provided: #{extras.join(",")}"
153
+ end
154
+ freezer(@data)
155
+ end
156
+
157
+ # @return [String]
158
+ def inspect
159
+ vars = (self.class.attr_required + self.class.attr_optional).map do |k|
160
+ val = self.send(:[], k)
161
+ next if val.nil? || val.to_s.empty?
162
+ "#{k}=#{val.inspect}"
163
+ end.compact.join(", ")
164
+ "<#{self.class.name}:#{sprintf("%#x", object_id)} #{vars}>"
165
+ end
166
+
167
+ protected
168
+
169
+ # Freeze the given object and all nested
170
+ # objects that can be found
171
+ #
172
+ # @return [Object]
173
+ def freezer(obj)
174
+ if obj.is_a?(Enumerable)
175
+ obj.each do |item|
176
+ freezer(item)
177
+ item.freeze
178
+ end
179
+ end
180
+ obj.freeze
181
+ end
182
+ end
183
+
184
+ # Mutable data class
185
+ class Mutable < Immutable
186
+ # Define attributes which are mutable
187
+ def self.attr_mutable(*args)
188
+ sync do
189
+ args.each do |attr|
190
+ if !attr_required.include?(attr.to_sym) && !attr_optional.include?(attr.to_sym)
191
+ raise ArgumentError, "Unknown attribute name provided `#{attr}`"
192
+ end
193
+ define_method("#{attr}=") { |v| dirty[attr.to_sym] = v }
194
+ end
195
+ end
196
+ end
197
+
198
+ # Load data and create a new instance
199
+ #
200
+ # @param [Hash] options Value to initialize instance
201
+ # @return [Mutable]
202
+ def self.load(options={})
203
+ opts = {}.tap do |o|
204
+ (attr_required + attr_optional +
205
+ self.instance_method(:initialize).parameters.find_all { |i|
206
+ i.first == :key || i.first == :keyreq
207
+ }.map(&:last)).each do |k|
208
+ o[k.to_sym] = options[k.to_sym]
209
+ end
210
+ end
211
+ self.new(**opts)
212
+ end
213
+
214
+ # Create a new instance
215
+ #
216
+ # @return [Mutable]
217
+ def initialize(**opts)
218
+ super
219
+ @dirty = {}
220
+ end
221
+
222
+ # Fetch value from data
223
+ #
224
+ # @param [String, Symbol] k Name of value
225
+ # @return [Object]
226
+ def [](k)
227
+ if dirty?(k)
228
+ @dirty[k.to_sym]
229
+ else
230
+ super
231
+ end
232
+ end
233
+
234
+ # Check if instance is dirty or specific
235
+ # attribute if key is provided
236
+ #
237
+ # @param [Symbol] key Key to check
238
+ # @return [Boolean] instance is dirty
239
+ def dirty?(key=nil, **opts)
240
+ if key.nil?
241
+ !@dirty.empty?
242
+ else
243
+ @dirty.key?(key.to_sym)
244
+ end
245
+ end
246
+
247
+ # Load given data and ignore any fields
248
+ # that are provided. Flush dirty state.
249
+ #
250
+ # @param [Hash] data Attribute data to load
251
+ # @param [Array<Symbol>] ignores Fields to skip
252
+ # @param [Array<Symbol>] only Fields to update
253
+ # @return [self]
254
+ def clean(data:, ignores: [], only: [])
255
+ raise TypeError, "Expected type `Hash` but received `#{data.inspect}`" if
256
+ !data.is_a?(Hash)
257
+ new_data = @data.dup
258
+ ignores = Array(ignores).map(&:to_sym)
259
+ only = Array(only).map(&:to_sym)
260
+ data.each do |k, v|
261
+ k = k.to_sym
262
+ next if ignores.include?(k)
263
+ next if !only.empty? && !only.include?(k)
264
+ if self.respond_to?(k)
265
+ new_data[k] = v
266
+ @dirty.delete(k)
267
+ end
268
+ end
269
+ @data = freezer(new_data)
270
+ self
271
+ end
272
+
273
+ # Merge values from dirty cache into data
274
+ #
275
+ # @return [self]
276
+ def clean!
277
+ @data = freezer(@data.merge(@dirty))
278
+ @dirty.clear
279
+ self
280
+ end
281
+
282
+ # @return [self] disable freezing
283
+ def freeze
284
+ self
285
+ end
286
+
287
+ # @return [Hash] updated attributes
288
+ protected def dirty; @dirty; end
289
+ end
290
+ end
291
+
292
+ Nil = Data::Nil
293
+ end
@@ -0,0 +1,48 @@
1
+ module VagrantCloud
2
+ class Error < StandardError
3
+ class ClientError < Error
4
+ class RequestError < ClientError
5
+ attr_accessor :error_arr
6
+ attr_accessor :error_code
7
+
8
+ def initialize(msg, http_body, http_code)
9
+ message = msg
10
+
11
+ begin
12
+ errors = JSON.parse(http_body)
13
+ if errors.is_a?(Hash)
14
+ vagrant_cloud_msg = errors['errors']
15
+ if vagrant_cloud_msg.is_a?(Array)
16
+ message = msg + ' - ' + vagrant_cloud_msg.map(&:to_s).join(', ').to_s
17
+ elsif !vagrant_cloud_msg.to_s.empty?
18
+ message = msg + ' - ' + vagrant_cloud_msg.to_s
19
+ end
20
+ end
21
+ rescue JSON::ParserError => err
22
+ vagrant_cloud_msg = err.message
23
+ end
24
+
25
+ @error_arr = vagrant_cloud_msg
26
+ @error_code = http_code.to_i
27
+ super(message)
28
+ end
29
+ end
30
+
31
+ class ConnectionLockedError < ClientError; end
32
+ end
33
+
34
+ class BoxError < Error
35
+ class InvalidVersionError < BoxError
36
+ def initialize(version_number)
37
+ message = 'Invalid version given: ' + version_number
38
+ super(message)
39
+ end
40
+ end
41
+ class BoxExistsError < BoxError; end
42
+ class ProviderNotFoundError < BoxError; end
43
+ class VersionExistsError < BoxError; end
44
+ class VersionStatusChangeError < BoxError; end
45
+ class VersionProviderExistsError < BoxError; end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,7 @@
1
+ module VagrantCloud
2
+ module Instrumentor
3
+ autoload :Collection, "vagrant_cloud/instrumentor/collection"
4
+ autoload :Core, "vagrant_cloud/instrumentor/core"
5
+ autoload :Logger, "vagrant_cloud/instrumentor/logger"
6
+ end
7
+ end
@@ -0,0 +1,123 @@
1
+ module VagrantCloud
2
+ module Instrumentor
3
+ class Collection < Core
4
+ # @return [Set<Instrumentor::Core>]
5
+ attr_reader :instrumentors
6
+ # @return [Set<Callable>]
7
+ attr_reader :subscriptions
8
+
9
+ # Create a new instance
10
+ #
11
+ # @param [Array<Core>] instrumentors Instrumentors to add to collection
12
+ def initialize(instrumentors: [])
13
+ @lock = Mutex.new
14
+ @subscriptions = Set.new
15
+ @instrumentors = Set.new
16
+ # Add our default
17
+ @instrumentors << Logger.new
18
+
19
+ Array(instrumentors).each do |i|
20
+ if !i.is_a?(Core) && !i.respond_to?(:instrument)
21
+ raise TypeError, "Instrumentors must implement `#instrument`"
22
+ end
23
+ @instrumentors << i
24
+ end
25
+ @instrumentors.freeze
26
+ end
27
+
28
+ # Add a new instrumentor
29
+ #
30
+ # @param [Core] instrumentor New instrumentor to add
31
+ # @return [self]
32
+ def add(instrumentor)
33
+ @lock.synchronize do
34
+ if !instrumentor.is_a?(Core) && !instrumentor.respond_to?(:instrument)
35
+ raise TypeError, "Instrumentors must implement `#instrument`"
36
+ end
37
+
38
+ @instrumentors = (instrumentors + [instrumentor]).freeze
39
+ end
40
+ self
41
+ end
42
+
43
+ # Remove instrumentor
44
+ #
45
+ # @param [Core] instrumentor Remove instrumentor from collection
46
+ # @return [self]
47
+ def remove(instrumentor)
48
+ @lock.synchronize do
49
+ @instrumentors = instrumentors.dup.tap{|i| i.delete(instrumentor)}.freeze
50
+ end
51
+ self
52
+ end
53
+
54
+ # Add a subscription for events
55
+ #
56
+ # @param [Regexp, String] event Event to match
57
+ def subscribe(event, callable=nil, &block)
58
+ if callable && block
59
+ raise ArgumentError, "Callable argument or block expected, not both"
60
+ end
61
+ c = callable || block
62
+ if !c.respond_to?(:call)
63
+ raise TypeError, "Callable action is required for subscription"
64
+ end
65
+ entry = [event, c]
66
+ @lock.synchronize do
67
+ @subscriptions = (@subscriptions + [entry]).freeze
68
+ end
69
+ self
70
+ end
71
+
72
+ def unsubscribe(callable)
73
+ @lock.synchronize do
74
+ subscriptions = @subscriptions.dup
75
+ subscriptions.delete_if { |entry| entry.last == callable }
76
+ @subscriptions = subscriptions.freeze
77
+ end
78
+ self
79
+ end
80
+
81
+ # Call all instrumentors in collection with given parameters
82
+ def instrument(name, params = {})
83
+ # Log the start time
84
+ timing = {start_time: Time.now}
85
+
86
+ # Run the action
87
+ result = yield if block_given?
88
+
89
+ # Log the completion time and calculate duration
90
+ timing[:complete_time] = Time.now
91
+ timing[:duration] = timing[:complete_time] - timing[:start_time]
92
+
93
+ # Insert timing into params
94
+ params[:timing] = timing
95
+
96
+ # Call any instrumentors we know about
97
+ @lock.synchronize do
98
+ # Call our instrumentors first
99
+ instrumentors.each do |i|
100
+ i.instrument(name, params)
101
+ end
102
+ # Now call any matching subscriptions
103
+ subscriptions.each do |event, callable|
104
+ if event.is_a?(Regexp)
105
+ next if !event.match(name)
106
+ else
107
+ next if event != name
108
+ end
109
+ args = [name, params]
110
+
111
+ if callable.arity > -1
112
+ args = args[0, callable.arity]
113
+ end
114
+
115
+ callable.call(*args)
116
+ end
117
+ end
118
+
119
+ result
120
+ end
121
+ end
122
+ end
123
+ end