Class: ActiveStorage::Service::GCSService
| Relationships & Source Files | |
| Namespace Children | |
|
Exceptions:
| |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
|
|
|
Instance Chain:
self,
::ActiveStorage::Service
|
|
| Inherits: |
ActiveStorage::Service
|
| Defined in: | activestorage/lib/active_storage/service/gcs_service.rb |
Overview
Active Storage GCS Service
Wraps the Google Cloud Storage as an Active Storage service. See ::ActiveStorage::Service for the generic API documentation that applies to all services.
Class Method Summary
- .new(public: false, **config) ⇒ GCSService constructor
::ActiveStorage::Service - Inherited
| .configure | Configure an Active Storage service by name from a set of configurations, typically loaded from a YAML file. |
| .build | Override in subclasses that stitch together multiple services and hence need to build additional services using the configurator. |
::ActiveSupport::Autoload - Extended
Instance Attribute Summary
Instance Method Summary
- #compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
- #delete(key)
- #delete_prefixed(prefix)
- #download(key, &block)
- #download_chunk(key, range)
- #exist?(key) ⇒ Boolean
- #headers_for_direct_upload(key, checksum:, filename: nil, disposition: nil, custom_metadata: {})
-
#iam_client
Returns the IAM client used for direct uploads and signed URLs.
- #update_metadata(key, content_type:, disposition: nil, filename: nil, custom_metadata: {})
- #upload(key, io, checksum: nil, content_type: nil, disposition: nil, filename: nil, custom_metadata: {})
- #url_for_direct_upload(key, expires_in:, checksum:, custom_metadata: {})
- #bucket private
- #client private
- #custom_metadata_headers(metadata) private
- #email_from_metadata_server private
- #file_for(key, skip_lookup: true) private
- #issuer private
- #private_url(key, expires_in:, filename:, content_type:, disposition:) private
- #public_url(key) private
- #signer private
-
#stream(key)
private
Reads the file for the given key in chunks, yielding each to the block.
::ActiveStorage::Service - Inherited
| #compose | Concatenate multiple files into a single “composed” file. |
| #delete | Delete the file at the |
| #delete_prefixed | Delete files at keys starting with the |
| #download | Return the content of the file at the |
| #download_chunk | Return the partial content in the byte |
| #exist? | Return |
| #headers_for_direct_upload | Returns a |
| #open, | |
| #update_metadata | Update metadata for the file identified by |
| #upload | Upload the |
| #url | Returns the URL for the file at the |
| #url_for_direct_upload | Returns a signed, temporary URL that a direct upload file can be PUT to on the |
| #content_disposition_with, #custom_metadata_headers, #instrument, #private_url, #public_url, #service_name, #inspect | |
Constructor Details
.new(public: false, **config) ⇒ GCSService
Instance Attribute Details
#config (readonly, private)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 185
attr_reader :config
Instance Method Details
#bucket (private)
[ GitHub ]#client (private)
[ GitHub ]#compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 139
def compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {}) bucket.compose(source_keys, destination_key).update do |file| file.content_type = content_type file.content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename file. = end end
#custom_metadata_headers(metadata) (private)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 238
def () .transform_keys { |key| "x-goog-meta-#{key}" } end
#delete(key)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 66
def delete(key) instrument :delete, key: key do file_for(key).delete rescue Google::Cloud::NotFoundError # Ignore files already deleted end end
#delete_prefixed(prefix)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 74
def delete_prefixed(prefix) instrument :delete_prefixed, prefix: prefix do bucket.files(prefix: prefix).all do |file| file.delete rescue Google::Cloud::NotFoundError # Ignore concurrently-deleted files end end end
#download(key, &block)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 34
def download(key, &block) if block_given? instrument :streaming_download, key: key do stream(key, &block) end else instrument :download, key: key do file_for(key).download.string rescue Google::Cloud::NotFoundError raise ActiveStorage::FileNotFoundError end end end
#download_chunk(key, range)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 58
def download_chunk(key, range) instrument :download_chunk, key: key, range: range do file_for(key).download(range: range).string rescue Google::Cloud::NotFoundError raise ActiveStorage::FileNotFoundError end end
#email_from_metadata_server (private)
# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 218
def env = Google::Cloud.env raise MetadataServerNotFoundError if !env. email = env.("instance", "service-accounts/default/email") email.presence or raise MetadataServerError end
#exist?(key) ⇒ Boolean
# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 84
def exist?(key) instrument :exist, key: key do |payload| answer = file_for(key).exists? payload[:exist] = answer answer end end
#file_for(key, skip_lookup: true) (private)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 187
def file_for(key, skip_lookup: true) bucket.file(key, skip_lookup: skip_lookup) end
#headers_for_direct_upload(key, checksum:, filename: nil, disposition: nil, custom_metadata: {})
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 128
def headers_for_direct_upload(key, checksum:, filename: nil, disposition: nil, custom_metadata: {}, **) content_disposition = content_disposition_with(type: disposition, filename: filename) if filename headers = { "Content-MD5" => checksum, "Content-Disposition" => content_disposition, **() } if @config[:cache_control].present? headers["Cache-Control"] = @config[:cache_control] end headers end
#iam_client
Returns the IAM client used for direct uploads and signed URLs. By default, the authorization for the IAM client is set to Application Default Credentials, fetched once at instantiation time, then refreshed automatically when expired. This can be set to a different value to use other authorization methods.
ActiveStorage::Blob.service.iam_client. = Google::Auth::ImpersonatedServiceAccountCredentials.new()
#issuer (private)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 214
def issuer @issuer ||= @config[:gsa_email].presence || end
#private_url(key, expires_in:, filename:, content_type:, disposition:) (private)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 163
def private_url(key, expires_in:, filename:, content_type:, disposition:, **) args = { expires: expires_in, query: { "response-content-disposition" => content_disposition_with(type: disposition, filename: filename), "response-content-type" => content_type } } if @config[:iam] args[:issuer] = issuer args[:signer] = signer end file_for(key).signed_url(**args) end
#public_url(key) (private)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 180
def public_url(key, **) file_for(key).public_url end
#signer (private)
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 226
def signer # https://googleapis.dev/ruby/google-cloud-storage/latest/Google/Cloud/Storage/Project.html#signed_url-instance_method lambda do |string_to_sign| request = Google::Apis::IamcredentialsV1::SignBlobRequest.new( payload: string_to_sign ) resource = "projects/-/serviceAccounts/#{issuer}" response = iam_client.sign_service_account_blob(resource, request) response.signed_blob end end
#stream(key) (private)
Reads the file for the given key in chunks, yielding each to the block.
# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 192
def stream(key) file = file_for(key, skip_lookup: false) chunk_size = 5.megabytes offset = 0 raise ActiveStorage::FileNotFoundError unless file.present? while offset < file.size yield file.download(range: offset..(offset + chunk_size - 1)).string offset += chunk_size end end
#update_metadata(key, content_type:, disposition: nil, filename: nil, custom_metadata: {})
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 48
def (key, content_type:, disposition: nil, filename: nil, custom_metadata: {}) instrument :, key: key, content_type: content_type, disposition: disposition do file_for(key).update do |file| file.content_type = content_type file.content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename file. = end end end
#upload(key, io, checksum: nil, content_type: nil, disposition: nil, filename: nil, custom_metadata: {})
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 21
def upload(key, io, checksum: nil, content_type: nil, disposition: nil, filename: nil, custom_metadata: {}) instrument :upload, key: key, checksum: checksum do # GCS's signed URLs don't include params such as response-content-type response-content_disposition # in the signature, which means an attacker can modify them and bypass our effort to force these to # binary and attachment when the file's content type requires it. The only way to force them is to # store them as object's metadata. content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename bucket.create_file(io, key, md5: checksum, cache_control: @config[:cache_control], content_type: content_type, content_disposition: content_disposition, metadata: ) rescue Google::Cloud::InvalidArgumentError raise ActiveStorage::IntegrityError end end
#url_for_direct_upload(key, expires_in:, checksum:, custom_metadata: {})
[ GitHub ]# File 'activestorage/lib/active_storage/service/gcs_service.rb', line 92
def url_for_direct_upload(key, expires_in:, checksum:, custom_metadata: {}, **) instrument :url, key: key do |payload| headers = {} version = :v2 if @config[:cache_control].present? headers["Cache-Control"] = @config[:cache_control] # v2 signing doesn't support non `x-goog-` headers. Only switch to v4 signing # if necessary for back-compat; v4 limits the expiration of the URL to 7 days # whereas v2 has no limit version = :v4 end headers.merge!(()) args = { content_md5: checksum, expires: expires_in, headers: headers, method: "PUT", version: version, } if @config[:iam] args[:issuer] = issuer args[:signer] = signer end generated_url = bucket.signed_url(key, **args) payload[:url] = generated_url generated_url end end