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.
- checksums.yaml +4 -4
- data/README.md +145 -39
- data/lib/vagrant_cloud.rb +20 -10
- data/lib/vagrant_cloud/account.rb +86 -169
- data/lib/vagrant_cloud/box.rb +105 -159
- data/lib/vagrant_cloud/box/provider.rb +173 -0
- data/lib/vagrant_cloud/box/version.rb +161 -0
- data/lib/vagrant_cloud/client.rb +436 -46
- data/lib/vagrant_cloud/data.rb +293 -0
- data/lib/vagrant_cloud/error.rb +47 -0
- data/lib/vagrant_cloud/instrumentor.rb +7 -0
- data/lib/vagrant_cloud/instrumentor/collection.rb +123 -0
- data/lib/vagrant_cloud/instrumentor/core.rb +9 -0
- data/lib/vagrant_cloud/instrumentor/logger.rb +97 -0
- data/lib/vagrant_cloud/logger.rb +60 -0
- data/lib/vagrant_cloud/organization.rb +62 -0
- data/lib/vagrant_cloud/response.rb +7 -0
- data/lib/vagrant_cloud/response/create_token.rb +7 -0
- data/lib/vagrant_cloud/response/request_2fa.rb +7 -0
- data/lib/vagrant_cloud/response/search.rb +65 -0
- data/lib/vagrant_cloud/search.rb +113 -15
- data/lib/vagrant_cloud/version.rb +1 -200
- metadata +31 -25
- data/bin/vagrant_cloud +0 -6
- data/lib/vagrant_cloud/errors.rb +0 -35
- data/lib/vagrant_cloud/provider.rb +0 -155
@@ -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,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
|