vagrant_cloud 1.1.0 → 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 +5 -5
- data/LICENSE +1 -0
- data/README.md +147 -32
- data/lib/vagrant_cloud.rb +19 -8
- data/lib/vagrant_cloud/account.rb +92 -89
- data/lib/vagrant_cloud/box.rb +111 -72
- data/lib/vagrant_cloud/box/provider.rb +173 -0
- data/lib/vagrant_cloud/box/version.rb +161 -0
- data/lib/vagrant_cloud/client.rb +464 -0
- 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 +129 -0
- data/lib/vagrant_cloud/version.rb +1 -95
- metadata +33 -36
- data/bin/vagrant_cloud +0 -5
- data/lib/vagrant_cloud/cli.rb +0 -65
- data/lib/vagrant_cloud/provider.rb +0 -74
data/lib/vagrant_cloud/box.rb
CHANGED
@@ -1,97 +1,136 @@
|
|
1
1
|
module VagrantCloud
|
2
|
-
class Box
|
3
|
-
|
4
|
-
|
5
|
-
attr_accessor :data
|
2
|
+
class Box < Data::Mutable
|
3
|
+
autoload :Provider, "vagrant_cloud/box/provider"
|
4
|
+
autoload :Version, "vagrant_cloud/box/version"
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@name = name
|
13
|
-
@data = data
|
14
|
-
end
|
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
|
15
11
|
|
16
|
-
|
17
|
-
def description
|
18
|
-
data['description_markdown'].to_s
|
19
|
-
end
|
12
|
+
attr_mutable :short_description, :description, :private, :versions
|
20
13
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
14
|
+
# Create a new instance
|
15
|
+
#
|
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!
|
24
32
|
end
|
25
33
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
34
|
+
# Delete this box
|
35
|
+
#
|
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
|
29
43
|
end
|
30
44
|
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
45
|
+
# Add a new version of this box
|
46
|
+
#
|
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
|
35
57
|
end
|
36
58
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
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
|
40
73
|
end
|
41
74
|
|
42
|
-
# @
|
43
|
-
def
|
44
|
-
|
75
|
+
# @return [Boolean] box exists remotely
|
76
|
+
def exist?
|
77
|
+
!!created_at
|
45
78
|
end
|
46
79
|
|
47
|
-
|
48
|
-
|
80
|
+
# @return [Array<Version>]
|
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
|
91
|
+
end
|
92
|
+
plain_versions
|
49
93
|
end
|
94
|
+
alias_method :plain_versions, :versions
|
95
|
+
alias_method :versions, :versions_on_demand
|
50
96
|
|
51
|
-
#
|
52
|
-
#
|
53
|
-
# @return [
|
54
|
-
def
|
55
|
-
|
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
|
56
104
|
end
|
57
105
|
|
58
|
-
|
59
|
-
# @param [String] description
|
60
|
-
# @return [Version]
|
61
|
-
def create_version(name, description = nil)
|
62
|
-
params = { version: name }
|
63
|
-
params[:description] = description if description
|
64
|
-
data = account.request('post', "/box/#{account.username}/#{self.name}/versions", version: params)
|
65
|
-
get_version(data['number'], data)
|
66
|
-
end
|
106
|
+
protected
|
67
107
|
|
68
|
-
#
|
69
|
-
#
|
70
|
-
# @return [
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
108
|
+
# Save the box
|
109
|
+
#
|
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)
|
121
|
+
else
|
122
|
+
result = organization.account.client.box_create(**req_args)
|
76
123
|
end
|
77
|
-
|
124
|
+
clean(data: result, ignores: [:current_version, :versions])
|
125
|
+
self
|
78
126
|
end
|
79
127
|
|
80
|
-
#
|
81
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
128
|
+
# Save the versions if any require saving
|
129
|
+
#
|
130
|
+
# @return [self]
|
131
|
+
def save_versions
|
132
|
+
versions.map(&:save)
|
133
|
+
self
|
86
134
|
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
# Vagrant Cloud returns keys different from what you set for some params.
|
91
|
-
# Values in this map should be strings.
|
92
|
-
ATTR_MAP = {
|
93
|
-
is_private: 'private',
|
94
|
-
description: 'description_markdown'
|
95
|
-
}.freeze
|
96
135
|
end
|
97
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
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module VagrantCloud
|
2
|
+
class Box
|
3
|
+
class Version < Data::Mutable
|
4
|
+
attr_reader :box
|
5
|
+
attr_required :version
|
6
|
+
attr_optional :status, :description_html, :description_markdown,
|
7
|
+
:created_at, :updated_at, :number, :providers, :description
|
8
|
+
|
9
|
+
attr_mutable :description
|
10
|
+
|
11
|
+
def initialize(box:, **opts)
|
12
|
+
if !box.is_a?(Box)
|
13
|
+
raise TypeError, "Expecting type `#{Box.name}` but received `#{box.class.name}`"
|
14
|
+
end
|
15
|
+
@box = box
|
16
|
+
opts[:providers] = Array(opts[:providers]).map do |provider|
|
17
|
+
if provider.is_a?(Provider)
|
18
|
+
provider
|
19
|
+
else
|
20
|
+
Provider.load(version: self, **provider)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
super(opts)
|
24
|
+
clean!
|
25
|
+
end
|
26
|
+
|
27
|
+
# Delete this version
|
28
|
+
#
|
29
|
+
# @return [nil]
|
30
|
+
# @note This will delete the version, and all providers
|
31
|
+
def delete
|
32
|
+
if exist?
|
33
|
+
box.organization.account.client.box_version_delete(
|
34
|
+
username: box.username,
|
35
|
+
name: box.name,
|
36
|
+
version: version
|
37
|
+
)
|
38
|
+
# Remove self from box
|
39
|
+
box.versions.delete(self)
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Release this version
|
45
|
+
#
|
46
|
+
# @return [self]
|
47
|
+
def release
|
48
|
+
if released?
|
49
|
+
raise Error::BoxError::VersionStatusChangeError,
|
50
|
+
"Version #{version} is already released for box #{box.tag}"
|
51
|
+
end
|
52
|
+
if !exist?
|
53
|
+
raise Error::BoxError::VersionStatusChangeError,
|
54
|
+
"Version #{version} for box #{box.tag} must be saved before release"
|
55
|
+
end
|
56
|
+
result = box.organization.account.client.box_version_release(
|
57
|
+
username: box.username,
|
58
|
+
name: box.name,
|
59
|
+
version: version
|
60
|
+
)
|
61
|
+
clean(data: result, only: :status)
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Revoke this version
|
66
|
+
#
|
67
|
+
# @return [self]
|
68
|
+
def revoke
|
69
|
+
if !released?
|
70
|
+
raise Error::BoxError::VersionStatusChangeError,
|
71
|
+
"Version #{version} is not yet released for box #{box.tag}"
|
72
|
+
end
|
73
|
+
result = box.organization.account.client.box_version_revoke(
|
74
|
+
username: box.username,
|
75
|
+
name: box.name,
|
76
|
+
version: version
|
77
|
+
)
|
78
|
+
clean(data: result, only: :status)
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Boolean]
|
83
|
+
def released?
|
84
|
+
status == "active"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Add a new provider for this version
|
88
|
+
#
|
89
|
+
# @param [String] pname Name of provider
|
90
|
+
# @return [Provider]
|
91
|
+
def add_provider(pname)
|
92
|
+
if providers.any? { |p| p.name == pname }
|
93
|
+
raise Error::BoxError::VersionProviderExistsError,
|
94
|
+
"Provider #{pname} already exists for box #{box.tag} version #{version}"
|
95
|
+
end
|
96
|
+
pv = Provider.new(version: self, name: pname)
|
97
|
+
clean(data: {providers: providers + [pv]})
|
98
|
+
pv
|
99
|
+
end
|
100
|
+
|
101
|
+
# Check if this instance is dirty
|
102
|
+
#
|
103
|
+
# @param [Boolean] deep Check nested instances
|
104
|
+
# @return [Boolean] instance is dirty
|
105
|
+
def dirty?(key=nil, deep: false)
|
106
|
+
if key
|
107
|
+
super(key)
|
108
|
+
else
|
109
|
+
d = super() || !exist?
|
110
|
+
if deep && !d
|
111
|
+
d = providers.any? { |p| p.dirty?(deep: true) }
|
112
|
+
end
|
113
|
+
d
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Boolean] version exists remotely
|
118
|
+
def exist?
|
119
|
+
!!created_at
|
120
|
+
end
|
121
|
+
|
122
|
+
# Save the version if any changes have been made
|
123
|
+
#
|
124
|
+
# @return [self]
|
125
|
+
def save
|
126
|
+
save_version if dirty?
|
127
|
+
save_providers if dirty?(deep: true)
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
# Save the version
|
134
|
+
#
|
135
|
+
# @return [self]
|
136
|
+
def save_version
|
137
|
+
params = {
|
138
|
+
username: box.username,
|
139
|
+
name: box.name,
|
140
|
+
version: version,
|
141
|
+
description: description
|
142
|
+
}
|
143
|
+
if exist?
|
144
|
+
result = box.organization.account.client.box_version_update(**params)
|
145
|
+
else
|
146
|
+
result = box.organization.account.client.box_version_create(**params)
|
147
|
+
end
|
148
|
+
clean(data: result, ignores: :providers)
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
# Save the providers if any require saving
|
153
|
+
#
|
154
|
+
# @return [self]
|
155
|
+
def save_providers
|
156
|
+
Array(providers).map(&:save)
|
157
|
+
self
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|