vagrant_cloud 2.0.3 → 3.0.0
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 +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
data/lib/vagrant_cloud/box.rb
CHANGED
@@ -1,190 +1,136 @@
|
|
1
1
|
module VagrantCloud
|
2
|
-
class Box
|
3
|
-
|
4
|
-
|
2
|
+
class Box < Data::Mutable
|
3
|
+
autoload :Provider, "vagrant_cloud/box/provider"
|
4
|
+
autoload :Version, "vagrant_cloud/box/version"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# @param [String] access_token
|
12
|
-
def initialize(account, name = nil, data = nil, short_description = nil, description = nil, access_token = nil, custom_server = nil)
|
13
|
-
@account = account
|
14
|
-
@name = name
|
15
|
-
@data = data
|
16
|
-
@description = description
|
17
|
-
@short_description = short_description
|
18
|
-
@client = Client.new(access_token, custom_server)
|
19
|
-
end
|
20
|
-
|
21
|
-
#--------------------
|
22
|
-
# Box API Helpers
|
23
|
-
#--------------------
|
6
|
+
attr_reader :organization
|
7
|
+
attr_required :name
|
8
|
+
attr_optional :created_at, :updated_at, :tag, :short_description,
|
9
|
+
:description_html, :description_markdown, :private, :downloads,
|
10
|
+
:current_version, :versions, :description, :username
|
24
11
|
|
25
|
-
|
26
|
-
# @return [Hash]
|
27
|
-
def data
|
28
|
-
@data ||= @client.request('get', box_path)
|
29
|
-
end
|
12
|
+
attr_mutable :short_description, :description, :private, :versions
|
30
13
|
|
31
|
-
#
|
14
|
+
# Create a new instance
|
32
15
|
#
|
33
|
-
# @
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
data
|
16
|
+
# @return [Box]
|
17
|
+
def initialize(organization:, **opts)
|
18
|
+
@organization = organization
|
19
|
+
@versions_loaded = false
|
20
|
+
opts[:username] = organization.username
|
21
|
+
super(opts)
|
22
|
+
if opts[:versions] && !opts[:versions].empty?
|
23
|
+
self.versions= Array(opts[:versions]).map do |version|
|
24
|
+
Box::Version.load(box: self, **version)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
if opts[:current_version]
|
28
|
+
clean(data: {current_version: Box::Version.
|
29
|
+
load(box: self, **opts[:current_version])})
|
30
|
+
end
|
31
|
+
clean!
|
50
32
|
end
|
51
33
|
|
52
|
-
#
|
53
|
-
# If org and box name is not supplied, it will default to
|
54
|
-
# reading the given Box object
|
34
|
+
# Delete this box
|
55
35
|
#
|
56
|
-
# @
|
57
|
-
# @
|
58
|
-
|
59
|
-
|
60
|
-
|
36
|
+
# @return [nil]
|
37
|
+
# @note This will delete the box, and all versions
|
38
|
+
def delete
|
39
|
+
if exist?
|
40
|
+
organization.account.client.box_delete(username: username, name: name)
|
41
|
+
end
|
42
|
+
nil
|
61
43
|
end
|
62
44
|
|
63
|
-
#
|
45
|
+
# Add a new version of this box
|
64
46
|
#
|
65
|
-
# @param [String]
|
66
|
-
# @
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
# @return [Hash]
|
76
|
-
def create(short_description = nil, description = nil, org = nil, box_name = nil, is_private = false)
|
77
|
-
update_data = !(org && box_name)
|
78
|
-
|
79
|
-
org ||= account.username
|
80
|
-
box_name ||= @name
|
81
|
-
short_description ||= @short_description
|
82
|
-
description ||= @description
|
83
|
-
|
84
|
-
params = {
|
85
|
-
name: box_name,
|
86
|
-
username: org,
|
87
|
-
is_private: is_private,
|
88
|
-
short_description: short_description,
|
89
|
-
description: description
|
90
|
-
}.delete_if { |_, v| v.nil? }
|
91
|
-
|
92
|
-
data = @client.request('post', '/boxes', box: params)
|
93
|
-
|
94
|
-
# Create was called on *this* object, so update
|
95
|
-
# objects data locally
|
96
|
-
@data = data if update_data
|
97
|
-
data
|
98
|
-
end
|
99
|
-
|
100
|
-
#--------------------
|
101
|
-
# Metadata Helpers
|
102
|
-
#--------------------
|
103
|
-
|
104
|
-
# @return [String]
|
105
|
-
def description
|
106
|
-
data['description_markdown'].to_s
|
47
|
+
# @param [String] version Version number
|
48
|
+
# @return [Version]
|
49
|
+
def add_version(version)
|
50
|
+
if versions.any? { |v| v.version == version }
|
51
|
+
raise Error::BoxError::VersionExistsError,
|
52
|
+
"Version #{version} already exists for box #{tag}"
|
53
|
+
end
|
54
|
+
v = Version.new(box: self, version: version)
|
55
|
+
clean(data: {versions: versions + [v]})
|
56
|
+
v
|
107
57
|
end
|
108
58
|
|
109
|
-
#
|
110
|
-
|
111
|
-
|
59
|
+
# Check if this instance is dirty
|
60
|
+
#
|
61
|
+
# @param [Boolean] deep Check nested instances
|
62
|
+
# @return [Boolean] instance is dirty
|
63
|
+
def dirty?(key=nil, deep: false)
|
64
|
+
if key
|
65
|
+
super(key)
|
66
|
+
else
|
67
|
+
d = super() || !exist?
|
68
|
+
if deep && !d
|
69
|
+
d = Array(plain_versions).any? { |v| v.dirty?(deep: true) }
|
70
|
+
end
|
71
|
+
d
|
72
|
+
end
|
112
73
|
end
|
113
74
|
|
114
|
-
# @return [
|
115
|
-
def
|
116
|
-
!!
|
75
|
+
# @return [Boolean] box exists remotely
|
76
|
+
def exist?
|
77
|
+
!!created_at
|
117
78
|
end
|
118
79
|
|
119
80
|
# @return [Array<Version>]
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
# @param [Hash] data
|
131
|
-
# @return [Version]
|
132
|
-
def get_version(number, data = nil)
|
133
|
-
VagrantCloud::Version.new(self, number, data, nil, @client.access_token, @client.url_base)
|
134
|
-
end
|
135
|
-
|
136
|
-
# @param [String] name
|
137
|
-
# @param [String] description
|
138
|
-
# @return [Version]
|
139
|
-
def create_version(name, description = nil)
|
140
|
-
params = { version: name }
|
141
|
-
params[:description] = description if description
|
142
|
-
data = @client.request('post', "#{box_path}/versions", version: params)
|
143
|
-
get_version(data['number'], data)
|
144
|
-
end
|
145
|
-
|
146
|
-
# @param [String] name
|
147
|
-
# @param [String] description
|
148
|
-
# @return [Version]
|
149
|
-
def ensure_version(name, description = nil)
|
150
|
-
version = versions.select { |v| v.version == name }.first
|
151
|
-
version ||= create_version(name, description)
|
152
|
-
if description && (description != version.description)
|
153
|
-
version.update(description)
|
81
|
+
# @note This is used to allow versions information to be loaded
|
82
|
+
# only when requested
|
83
|
+
def versions_on_demand
|
84
|
+
if !@versions_loaded
|
85
|
+
r = self.organization.account.client.box_get(username: username, name: name)
|
86
|
+
v = Array(r[:versions]).map do |version|
|
87
|
+
Box::Version.load(box: self, **version)
|
88
|
+
end
|
89
|
+
clean(data: {versions: v + Array(plain_versions)})
|
90
|
+
@versions_loaded = true
|
154
91
|
end
|
155
|
-
|
92
|
+
plain_versions
|
156
93
|
end
|
94
|
+
alias_method :plain_versions, :versions
|
95
|
+
alias_method :versions, :versions_on_demand
|
157
96
|
|
158
|
-
#
|
159
|
-
#
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
97
|
+
# Save the box if any changes have been made
|
98
|
+
#
|
99
|
+
# @return [self]
|
100
|
+
def save
|
101
|
+
save_versions if dirty?(deep: true)
|
102
|
+
save_box if dirty?
|
103
|
+
self
|
164
104
|
end
|
165
105
|
|
166
|
-
|
106
|
+
protected
|
167
107
|
|
168
|
-
#
|
169
|
-
# If no params are given, it constructs a path for *this* Box object,
|
170
|
-
# but if both params are given it will construct a path for a one-off request
|
108
|
+
# Save the box
|
171
109
|
#
|
172
|
-
# @
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
110
|
+
# @return [self]
|
111
|
+
def save_box
|
112
|
+
req_args = {
|
113
|
+
username: username,
|
114
|
+
name: name,
|
115
|
+
short_description: short_description,
|
116
|
+
description: description,
|
117
|
+
is_private: self.private
|
118
|
+
}
|
119
|
+
if exist?
|
120
|
+
result = organization.account.client.box_update(**req_args)
|
178
121
|
else
|
179
|
-
|
122
|
+
result = organization.account.client.box_create(**req_args)
|
180
123
|
end
|
124
|
+
clean(data: result, ignores: [:current_version, :versions])
|
125
|
+
self
|
181
126
|
end
|
182
127
|
|
183
|
-
#
|
184
|
-
#
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
128
|
+
# Save the versions if any require saving
|
129
|
+
#
|
130
|
+
# @return [self]
|
131
|
+
def save_versions
|
132
|
+
versions.map(&:save)
|
133
|
+
self
|
134
|
+
end
|
189
135
|
end
|
190
136
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module VagrantCloud
|
2
|
+
class Box
|
3
|
+
class Provider < Data::Mutable
|
4
|
+
|
5
|
+
# Result for upload requests to upload directly to the
|
6
|
+
# storage backend.
|
7
|
+
#
|
8
|
+
# @param [String] upload_url URL for uploading file asset
|
9
|
+
# @param [String] callback_url URL callback to PUT after successful upload
|
10
|
+
DirectUpload = Struct.new(:upload_url, :callback_url, keyword_init: true)
|
11
|
+
|
12
|
+
attr_reader :version
|
13
|
+
attr_required :name
|
14
|
+
attr_optional :hosted, :created_at, :updated_at,
|
15
|
+
:checksum, :checksum_type, :original_url, :download_url,
|
16
|
+
:url
|
17
|
+
|
18
|
+
attr_mutable :url, :checksum, :checksum_type
|
19
|
+
|
20
|
+
def initialize(version:, **opts)
|
21
|
+
if !version.is_a?(Version)
|
22
|
+
raise TypeError, "Expecting type `#{Version.name}` but received `#{version.class.name}`"
|
23
|
+
end
|
24
|
+
@version = version
|
25
|
+
super(**opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Delete this provider
|
29
|
+
#
|
30
|
+
# @return [nil]
|
31
|
+
def delete
|
32
|
+
if exist?
|
33
|
+
version.box.organization.account.client.box_version_provider_delete(
|
34
|
+
username: version.box.username,
|
35
|
+
name: version.box.name,
|
36
|
+
version: version.version,
|
37
|
+
provider: name
|
38
|
+
)
|
39
|
+
version.providers.delete(self)
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Upload box file to be hosted on VagrantCloud. This
|
45
|
+
# method provides different behaviors based on the
|
46
|
+
# parameters passed. When the `direct` option is enabled
|
47
|
+
# the upload target will be directly to the backend
|
48
|
+
# storage. However, when the `direct` option is used the
|
49
|
+
# upload process becomes a two steps where a callback
|
50
|
+
# must be called after the upload is complete.
|
51
|
+
#
|
52
|
+
# If the path is provided, the file will be uploaded
|
53
|
+
# and the callback will be requested if the `direct`
|
54
|
+
# option is enabled.
|
55
|
+
#
|
56
|
+
# If a block is provided, the upload URL will be yielded
|
57
|
+
# to the block. If the `direct` option is set, the callback
|
58
|
+
# will be automatically requested after the block execution
|
59
|
+
# has completed.
|
60
|
+
#
|
61
|
+
# If no path or block is provided, the upload URL will
|
62
|
+
# be returned. If the `direct` option is set, the
|
63
|
+
# `DirectUpload` instance will be yielded and it is
|
64
|
+
# the caller's responsibility to issue the callback
|
65
|
+
#
|
66
|
+
# @param [String] path Path to asset
|
67
|
+
# @param [Boolean] direct Upload directly to backend storage
|
68
|
+
# @yieldparam [String] url URL to upload asset
|
69
|
+
# @return [self, Object, String, DirectUpload] self when path provided, result of yield when block provided, URL otherwise
|
70
|
+
# @note The callback request uses PUT request method
|
71
|
+
def upload(path: nil, direct: false)
|
72
|
+
if !exist?
|
73
|
+
raise Error::BoxError::ProviderNotFoundError,
|
74
|
+
"Provider #{name} not found for box #{version.box.tag} version #{version.version}"
|
75
|
+
end
|
76
|
+
if path && block_given?
|
77
|
+
raise ArgumentError,
|
78
|
+
"Only path or block may be provided, not both"
|
79
|
+
end
|
80
|
+
if path && !File.exist?(path)
|
81
|
+
raise Errno::ENOENT, path
|
82
|
+
end
|
83
|
+
req_args = {
|
84
|
+
username: version.box.username,
|
85
|
+
name: version.box.name,
|
86
|
+
version: version.version,
|
87
|
+
provider: name
|
88
|
+
}
|
89
|
+
if direct
|
90
|
+
r = version.box.organization.account.client.box_version_provider_upload_direct(**req_args)
|
91
|
+
else
|
92
|
+
r = version.box.organization.account.client.box_version_provider_upload(**req_args)
|
93
|
+
end
|
94
|
+
result = DirectUpload.new(
|
95
|
+
upload_url: r[:upload_path],
|
96
|
+
callback_url: r[:callback]
|
97
|
+
)
|
98
|
+
if block_given?
|
99
|
+
block_r = yield result.upload_url
|
100
|
+
Excon.put(result.callback_url) if direct
|
101
|
+
block_r
|
102
|
+
elsif path
|
103
|
+
File.open(path, "rb") do |file|
|
104
|
+
chunks = lambda { file.read(Excon.defaults[:chunk_size]).to_s }
|
105
|
+
# When performing a direct upload, we must POST the request
|
106
|
+
# to the provided upload URL. If it's just a regular upload
|
107
|
+
# then we just PUT to the upload URL.
|
108
|
+
if direct
|
109
|
+
Excon.post(result.upload_url, request_block: chunks)
|
110
|
+
else
|
111
|
+
Excon.put(result.upload_url, request_block: chunks)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
Excon.put(result.callback_url) if direct
|
115
|
+
self
|
116
|
+
else
|
117
|
+
# When returning upload information for requester to complete,
|
118
|
+
# return upload URL when `direct` option is false, otherwise
|
119
|
+
# return the `DirectUpload` instance
|
120
|
+
direct ? result : result.upload_url
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# @return [Boolean] provider exists remotely
|
125
|
+
def exist?
|
126
|
+
!!created_at
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check if this instance is dirty
|
130
|
+
#
|
131
|
+
# @param [Boolean] deep Check nested instances
|
132
|
+
# @return [Boolean] instance is dirty
|
133
|
+
def dirty?(key=nil, **args)
|
134
|
+
if key
|
135
|
+
super(key)
|
136
|
+
else
|
137
|
+
super || !exist?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Save the provider if any changes have been made
|
142
|
+
#
|
143
|
+
# @return [self]
|
144
|
+
def save
|
145
|
+
save_provider if dirty?
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
# Save the provider
|
152
|
+
#
|
153
|
+
# @return [self]
|
154
|
+
def save_provider
|
155
|
+
req_args = {
|
156
|
+
username: version.box.username,
|
157
|
+
name: version.box.name,
|
158
|
+
version: version.version,
|
159
|
+
provider: name,
|
160
|
+
checksum: checksum,
|
161
|
+
checksum_type: checksum_type
|
162
|
+
}
|
163
|
+
if exist?
|
164
|
+
result = version.box.organization.account.client.box_version_provider_update(**req_args)
|
165
|
+
else
|
166
|
+
result = version.box.organization.account.client.box_version_provider_create(**req_args)
|
167
|
+
end
|
168
|
+
clean(data: result)
|
169
|
+
self
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|