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.
- checksums.yaml +5 -5
- data/README.md +145 -39
- data/lib/vagrant_cloud.rb +20 -10
- data/lib/vagrant_cloud/account.rb +86 -164
- data/lib/vagrant_cloud/box.rb +115 -154
- data/lib/vagrant_cloud/box/provider.rb +175 -0
- data/lib/vagrant_cloud/box/version.rb +163 -0
- data/lib/vagrant_cloud/client.rb +449 -39
- data/lib/vagrant_cloud/data.rb +293 -0
- data/lib/vagrant_cloud/error.rb +48 -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 +64 -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 -186
- metadata +26 -34
- data/bin/vagrant_cloud +0 -5
- data/lib/vagrant_cloud/errors.rb +0 -25
- data/lib/vagrant_cloud/provider.rb +0 -149
@@ -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,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
|