Class: ActiveStorage::Service::GCSService

Inherits:
ActiveStorage::Service show all
Defined in:
lib/active_storage/service/gcs_service.rb

Overview

Wraps the Google Cloud Storage as an Active Storage service. See ActiveStorage::Service for the generic API documentation that applies to all services.

Defined Under Namespace

Classes: MetadataServerError, MetadataServerNotFoundError

Instance Attribute Summary

Attributes inherited from ActiveStorage::Service

#name

Instance Method Summary collapse

Methods inherited from ActiveStorage::Service

build, configure, #open, #public?, #url

Constructor Details

#initialize(public: false, **config) ⇒ GCSService

Returns a new instance of GCSService.



14
15
16
17
# File 'lib/active_storage/service/gcs_service.rb', line 14

def initialize(public: false, **config)
  @config = config
  @public = public
end

Instance Method Details

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



137
138
139
140
141
142
143
# File 'lib/active_storage/service/gcs_service.rb', line 137

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

#delete(key) ⇒ Object



64
65
66
67
68
69
70
# File 'lib/active_storage/service/gcs_service.rb', line 64

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) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/active_storage/service/gcs_service.rb', line 72

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) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/active_storage/service/gcs_service.rb', line 32

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) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/active_storage/service/gcs_service.rb', line 56

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) ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
85
86
87
88
# File 'lib/active_storage/service/gcs_service.rb', line 82

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: {}) ⇒ Object



126
127
128
129
130
131
132
133
134
135
# File 'lib/active_storage/service/gcs_service.rb', line 126

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

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



46
47
48
49
50
51
52
53
54
# File 'lib/active_storage/service/gcs_service.rb', line 46

def (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. = 
    end
  end
end

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



19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/active_storage/service/gcs_service.rb', line 19

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: {}) ⇒ Object



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
# File 'lib/active_storage/service/gcs_service.rb', line 90

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