vagrant_cloud 2.0.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,47 @@
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
+ end
46
+ end
47
+ 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