lib/google/cloud/storage/file.rb in google-cloud-storage-0.23.1 vs lib/google/cloud/storage/file.rb in google-cloud-storage-0.23.2

- old
+ new

@@ -19,10 +19,12 @@ require "google/cloud/storage/file/verifier" module Google module Cloud module Storage + GOOGLEAPIS_URL = "https://storage.googleapis.com".freeze + ## # # File # # Represents a File # ([Object](https://cloud.google.com/storage/docs/json_api/v1/objects)) @@ -264,10 +266,39 @@ return nil unless @gapi.customer_encryption Base64.decode64 @gapi.customer_encryption.key_sha256 end ## + # The file's storage class. This defines how the file is stored and + # determines the SLA and the cost of storage. For more information, see + # [Storage + # Classes](https://cloud.google.com/storage/docs/storage-classes) and + # [Per-Object Storage + # Class](https://cloud.google.com/storage/docs/per-object-storage-class). + def storage_class + @gapi.storage_class + end + + ## + # Updates how the file is stored and determines the SLA and the cost of + # storage. Values include `:multi_regional`, `:regional`, `:nearline`, + # `:coldline`, `:standard`, and `:dra` (Durable Reduced Availability), + # as well as the strings returned by {File#storage_class} or + # {Bucket#storage_class}. For more information, see [Storage + # Classes](https://cloud.google.com/storage/docs/storage-classes) and + # [Per-Object Storage + # Class](https://cloud.google.com/storage/docs/per-object-storage-class). + # The default value is the default storage class for the bucket. See + # {Bucket#storage_class}. + # @param [Symbol, String] storage_class Storage class of the file. + def storage_class= storage_class + resp = service.update_file_storage_class \ + bucket, name, storage_class_for(storage_class) + @gapi = resp.resource + end + + ## # Updates the file with changes made in the given block in a single # PATCH request. The following attributes may be set: {#cache_control=}, # {#content_disposition=}, {#content_encoding=}, {#content_language=}, # {#content_type=}, and {#metadata=}. The {#metadata} hash accessible in # the block is completely mutable and will be included in the request. @@ -459,10 +490,67 @@ dest_bucket, dest_path, options File.from_gapi gapi, service end ## + # [Rewrites](https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite) + # the file to the same {#bucket} and {#name} with a new + # [customer-supplied encryption + # key](https://cloud.google.com/storage/docs/encryption#customer-supplied). + # + # If a new key is provided to this method, the new key must be used to + # subsequently download or copy the file. You must securely manage your + # keys and ensure that they are not lost. Also, please note that file + # metadata is not encrypted, with the exception of the CRC32C checksum + # and MD5 hash. The names of files and buckets are also not encrypted, + # and you can read or update the metadata of an encrypted file without + # providing the encryption key. + # + # @see https://cloud.google.com/storage/docs/encryption + # + # @param [String, nil] encryption_key Optional. The last + # customer-supplied, AES-256 encryption key used to encrypt the file, + # if one was used. + # @param [String, nil] new_encryption_key Optional. The new + # customer-supplied, AES-256 encryption key with which to encrypt the + # file. If `nil`, the rewritten file will be encrypted using the + # default server-side encryption, not customer-supplied encryption + # keys. + # + # @return [Google::Cloud::Storage::File] + # + # @example The file will be rewritten with a new encryption key: + # require "google/cloud/storage" + # + # storage = Google::Cloud::Storage.new + # bucket = storage.bucket "my-bucket" + # + # # Old key was stored securely for later use. + # old_key = "y\x03\"\x0E\xB6\xD3\x9B\x0E\xAB*\x19\xFAv\xDEY\xBEI..." + # + # file = bucket.file "path/to/my-file.ext", encryption_key: old_key + # + # # Key generation shown for example purposes only. Write your own. + # cipher = OpenSSL::Cipher.new "aes-256-cfb" + # cipher.encrypt + # new_key = cipher.random_key + # + # file.rotate encryption_key: old_key, new_encryption_key: new_key + # + def rotate encryption_key: nil, new_encryption_key: nil + ensure_service! + options = { source_key: encryption_key, + destination_key: new_encryption_key } + gapi = service.rewrite_file bucket, name, bucket, name, options + until gapi.done + options[:token] = gapi.rewrite_token + gapi = service.rewrite_file bucket, name, bucket, name, options + end + File.from_gapi gapi.resource, service + end + + ## # Permanently deletes the file. # # @return [Boolean] Returns `true` if the file was deleted. # # @example @@ -721,10 +809,22 @@ Verifier.verify_md5! self, file if verify_md5 Verifier.verify_crc32c! self, file if verify_crc32c file end + def storage_class_for str + return nil if str.nil? + { "durable_reduced_availability" => "DURABLE_REDUCED_AVAILABILITY", + "dra" => "DURABLE_REDUCED_AVAILABILITY", + "durable" => "DURABLE_REDUCED_AVAILABILITY", + "nearline" => "NEARLINE", + "coldline" => "COLDLINE", + "multi_regional" => "MULTI_REGIONAL", + "regional" => "REGIONAL", + "standard" => "STANDARD" }[str.to_s.downcase] || str.to_s + end + ## # @private Create a signed_url for a file. class Signer def initialize bucket, path, service @bucket = bucket @@ -747,11 +847,11 @@ end ## # The external url to the file. def ext_url - "https://storage.googleapis.com#{ext_path}" + "#{GOOGLEAPIS_URL}#{ext_path}" end def apply_option_defaults options adjusted_expires = (Time.now.utc + (options[:expires] || 300)).to_i options[:expires] = adjusted_expires @@ -773,34 +873,61 @@ def determine_issuer options = {} options[:issuer] || options[:client_email] || @service.credentials.issuer end + def post_object options + options = apply_option_defaults options + + fields = { + key: ext_path.sub("/", "") + } + + p = options[:policy] || {} + fail "Policy must be given in a Hash" unless p.is_a? Hash + + i = determine_issuer options + s = determine_signing_key options + + fail SignedUrlUnavailable unless i && s + + policy_str = p.to_json + policy = Base64.strict_encode64(policy_str).delete("\n") + + signature = generate_signature s, policy + + fields[:GoogleAccessId] = i + fields[:signature] = signature + fields[:policy] = policy + + Google::Cloud::Storage::PostObject.new GOOGLEAPIS_URL, fields + end + def signed_url options options = apply_option_defaults options i = determine_issuer options s = determine_signing_key options fail SignedUrlUnavailable unless i && s - sig = generate_signature s, options + sig = generate_signature s, signature_str(options) generate_signed_url i, sig, options[:expires] end - def generate_signature signing_key, options = {} + def generate_signature signing_key, secret unless signing_key.respond_to? :sign signing_key = OpenSSL::PKey::RSA.new signing_key end - signing_key.sign OpenSSL::Digest::SHA256.new, signature_str(options) + signature = signing_key.sign OpenSSL::Digest::SHA256.new, secret + Base64.strict_encode64(signature).delete("\n") end def generate_signed_url issuer, signed_string, expires - signature = Base64.strict_encode64(signed_string).delete("\n") "#{ext_url}?GoogleAccessId=#{CGI.escape issuer}" \ "&Expires=#{expires}" \ - "&Signature=#{CGI.escape signature}" + "&Signature=#{CGI.escape signed_string}" end def format_extension_headers headers return "" if headers.nil? fail "Headers must be given in a Hash" unless headers.is_a? Hash @@ -813,12 +940,18 @@ end ## # Yielded to a block to accumulate changes for a patch request. class Updater < File + # @private Do not allow storage_class to be set in an update call. + # It cannot be set with PATCH. + undef :storage_class= + + # @private attr_reader :updates + ## - # Create an Updater object. + # @private Create an Updater object. def initialize gapi @updates = [] @gapi = gapi end