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, :architecture, :default_architecture attr_mutable :url, :checksum, :checksum_type, :architecture, :default_architecture 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, architecture: architecture ) 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, architecture: architecture, } 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, architecture: architecture, default_architecture: default_architecture, url: url } if exist? # If the provider already exists, use the original architecture # value for locating the existing record and use the current # architecture for the new_architecture value so it can be updated # properly req_args[:architecture] = data[:architecture] req_args[:new_architecture] = architecture 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