Skip to Content Skip to Search

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.

Namespace
Methods
B
C
D
E
H
I
N
U

Class Public methods

new(public: false, **config)

# File activestorage/lib/active_storage/service/gcs_service.rb, line 16
def initialize(public: false, **config)
  @public = public
  @config = config
end

Instance Public methods

bucket()

# File activestorage/lib/active_storage/service/gcs_service.rb, line 147
def bucket
  @bucket ||= client.bucket(@config.fetch(:bucket), skip_lookup: true)
end

client()

# File activestorage/lib/active_storage/service/gcs_service.rb, line 151
def client
  @client ||= Google::Cloud::Storage.new(**@config.except(:bucket, :cache_control, :iam, :gsa_email))
end

compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})

# 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.metadata = custom_metadata
  end
end

delete(key)

# 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)

# 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)

# 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)

# 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

exist?(key)

# 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

headers_for_direct_upload(key, checksum:, filename: nil, disposition: nil, custom_metadata: {}, **)

# 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, **custom_metadata_headers(custom_metadata) }
  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.authorization = Google::Auth::ImpersonatedServiceAccountCredentials.new(options)
# File activestorage/lib/active_storage/service/gcs_service.rb, line 162
def iam_client
  @iam_client ||= Google::Apis::IamcredentialsV1::IAMCredentialsService.new.tap do |client|
    client.authorization ||= Google::Auth.get_application_default(["https://www.googleapis.com/auth/iam"])
  rescue
    nil
  end
end

update_metadata(key, content_type:, disposition: nil, filename: nil, custom_metadata: {})

# File activestorage/lib/active_storage/service/gcs_service.rb, line 48
def update_metadata(key, content_type:, disposition: nil, filename: nil, custom_metadata: {})
  instrument :update_metadata, 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.metadata = custom_metadata
    end
  end
end

upload(key, io, checksum: nil, content_type: nil, disposition: nil, filename: nil, custom_metadata: {})

# 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: custom_metadata)
  rescue Google::Cloud::InvalidArgumentError
    raise ActiveStorage::IntegrityError
  end
end

url_for_direct_upload(key, expires_in:, checksum:, custom_metadata: {}, **)

# 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!(custom_metadata_headers(custom_metadata))

    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