Class: ActiveStorage::Service::GCSService

Inherits:
ActiveStorage::Service show all
Defined in:
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.

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.



16
17
18
19
# File 'lib/active_storage/service/gcs_service.rb', line 16

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



139
140
141
142
143
144
145
# File '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

#delete(key) ⇒ Object



66
67
68
69
70
71
72
# File '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) ⇒ Object



74
75
76
77
78
79
80
81
82
# File '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) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
# File '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) ⇒ Object



58
59
60
61
62
63
64
# File '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) ⇒ Boolean

Returns:

  • (Boolean)


84
85
86
87
88
89
90
# File '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: {}) ⇒ Object



128
129
130
131
132
133
134
135
136
137
# File '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

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



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

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



21
22
23
24
25
26
27
28
29
30
31
32
# File '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: {}) ⇒ Object



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
125
126
# File '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