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