1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
module VagrantCloud
class Box
class Provider < Data::Mutable
# Result for upload requests to upload directly to the
# storage backend.
#
# @param [String] upload_url URL for uploading file asset
# @param [String] callback_url URL callback to PUT after successful upload
# @param [Proc] callback Callable proc to perform callback via configured client
DirectUpload = Struct.new(:upload_url, :callback_url, :callback, keyword_init: true)
attr_reader :version
attr_required :name
attr_optional :hosted, :created_at, :updated_at,
:checksum, :checksum_type, :original_url, :download_url,
:url
attr_mutable :url, :checksum, :checksum_type
def initialize(version:, **opts)
if !version.is_a?(Version)
raise TypeError, "Expecting type `#{Version.name}` but received `#{version.class.name}`"
end
@version = version
super(**opts)
end
# Delete this provider
#
# @return [nil]
def delete
if exist?
version.box.organization.account.client.box_version_provider_delete(
username: version.box.username,
name: version.box.name,
version: version.version,
provider: name
)
pv = version.providers.dup
pv.delete(self)
version.clean(data: {providers: pv})
end
nil
end
# Upload box file to be hosted on VagrantCloud. This
# method provides different behaviors based on the
# parameters passed. When the `direct` option is enabled
# the upload target will be directly to the backend
# storage. However, when the `direct` option is used the
# upload process becomes a two steps where a callback
# must be called after the upload is complete.
#
# If the path is provided, the file will be uploaded
# and the callback will be requested if the `direct`
# option is enabled.
#
# If a block is provided, the upload URL will be yielded
# to the block. If the `direct` option is set, the callback
# will be automatically requested after the block execution
# has completed.
#
# If no path or block is provided, the upload URL will
# be returned. If the `direct` option is set, the
# `DirectUpload` instance will be yielded and it is
# the caller's responsibility to issue the callback
#
# @param [String] path Path to asset
# @param [Boolean] direct Upload directly to backend storage
# @yieldparam [String] url URL to upload asset
# @return [self, Object, String, DirectUpload] self when path provided, result of yield when block provided, URL otherwise
# @note The callback request uses PUT request method
def upload(path: nil, direct: false)
if !exist?
raise Error::BoxError::ProviderNotFoundError,
"Provider #{name} not found for box #{version.box.tag} version #{version.version}"
end
if path && block_given?
raise ArgumentError,
"Only path or block may be provided, not both"
end
if path && !File.exist?(path)
raise Errno::ENOENT, path
end
req_args = {
username: version.box.username,
name: version.box.name,
version: version.version,
provider: name
}
if direct
r = version.box.organization.account.client.box_version_provider_upload_direct(**req_args)
else
r = version.box.organization.account.client.box_version_provider_upload(**req_args)
end
result = DirectUpload.new(
upload_url: r[:upload_path],
callback_url: r[:callback],
callback: proc {
if r[:callback]
version.box.organization.account.client.
request(method: :put, path: URI.parse(r[:callback]).path)
end
}
)
if block_given?
block_r = yield result.upload_url
result[:callback].call
block_r
elsif path
File.open(path, "rb") do |file|
chunks = lambda { file.read(Excon.defaults[:chunk_size]).to_s }
Excon.put(result.upload_url, request_block: chunks)
end
result[:callback].call
self
else
# When returning upload information for requester to complete,
# return upload URL when `direct` option is false, otherwise
# return the `DirectUpload` instance
direct ? result : result.upload_url
end
end
# @return [Boolean] provider exists remotely
def exist?
!!created_at
end
# Check if this instance is dirty
#
# @param [Boolean] deep Check nested instances
# @return [Boolean] instance is dirty
def dirty?(key=nil, **args)
if key
super(key)
else
super || !exist?
end
end
# Save the provider if any changes have been made
#
# @return [self]
def save
save_provider if dirty?
self
end
protected
# Save the provider
#
# @return [self]
def save_provider
req_args = {
username: version.box.username,
name: version.box.name,
version: version.version,
provider: name,
checksum: checksum,
checksum_type: checksum_type,
url: url
}
if exist?
result = version.box.organization.account.client.box_version_provider_update(**req_args)
else
result = version.box.organization.account.client.box_version_provider_create(**req_args)
end
clean(data: result)
self
end
end
end
end
|