summaryrefslogtreecommitdiff
path: root/app/uploaders/object_store_uploader.rb
blob: 9b9f47d5943aaf5a85d6bf74e5d0fb066d476547 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
require 'fog/aws'
require 'carrierwave/storage/fog'

class ObjectStoreUploader < GitlabUploader
  before :store, :set_default_local_store
  before :store, :verify_license!

  LOCAL_STORE = 1
  REMOTE_STORE = 2

  class << self
    def storage_options(options)
      @storage_options = options
    end

    def object_store_options
      @storage_options&.object_store
    end

    def object_store_enabled?
      object_store_options&.enabled
    end

    def background_upload_enabled?
      object_store_options&.background_upload
    end

    def object_store_credentials
      @object_store_credentials ||= object_store_options&.connection&.to_hash&.deep_symbolize_keys
    end

    def object_store_directory
      object_store_options&.remote_directory
    end

    def local_store_path
      raise NotImplementedError
    end
  end

  attr_reader :subject, :field

  def initialize(subject, field)
    @subject = subject
    @field = field
  end

  def object_store
    subject.public_send(:"#{field}_store")
  end

  def object_store=(value)
    @storage = nil
    subject.public_send(:"#{field}_store=", value)
  end

  def store_dir
    if file_storage?
      default_local_path
    else
      default_path
    end
  end

  def use_file
    if file_storage?
      return yield path
    end

    begin
      cache_stored_file!
      yield cache_path
    ensure
      cache_storage.delete_dir!(cache_path(nil))
    end
  end

  def filename
    super || file&.filename
  end

  def migrate!(new_store)
    raise 'Undefined new store' unless new_store

    return unless object_store != new_store
    return unless file

    old_file = file
    old_store = object_store

    # for moving remote file we need to first store it locally
    cache_stored_file! unless file_storage?

    # change storage
    self.object_store = new_store

    with_callbacks(:store, file) do
      storage.store!(file).tap do |new_file|
        # since we change storage store the new storage
        # in case of failure delete new file
        begin
          subject.save!
        rescue => e
          new_file.delete
          self.object_store = old_store
          raise e
        end

        old_file.delete
      end
    end
  end

  def schedule_migration_to_object_storage(new_file)
    if self.class.object_store_enabled? && licensed? && file_storage?
      ObjectStorageUploadWorker.perform_async(self.class.name, subject.class.name, field, subject.id)
    end
  end

  def fog_directory
    self.class.object_store_options.remote_directory
  end

  def fog_credentials
    self.class.object_store_options.connection
  end

  def fog_public
    false
  end

  def move_to_store
    file.try(:storage) == storage
  end

  def move_to_cache
    file.try(:storage) == cache_storage
  end

  # We block storing artifacts on Object Storage, not receiving
  def verify_license!(new_file)
    return if file_storage?

    raise 'Object Storage feature is missing' unless licensed?
  end

  def exists?
    file.try(:exists?)
  end

  def cache_dir
    File.join(self.class.local_store_path, 'tmp/cache')
  end

  # Override this if you don't want to save local files by default to the Rails.root directory
  def work_dir
    # Default path set by CarrierWave:
    # https://github.com/carrierwaveuploader/carrierwave/blob/v1.1.0/lib/carrierwave/uploader/cache.rb#L182
    # CarrierWave.tmp_path
    File.join(self.class.local_store_path, 'tmp/work')
  end

  def licensed?
    License.feature_available?(:object_storage)
  end

  private

  def set_default_local_store(new_file)
    self.object_store = LOCAL_STORE unless self.object_store
  end

  def default_local_path
    File.join(self.class.local_store_path, default_path)
  end

  def default_path
    raise NotImplementedError
  end

  def storage
    @storage ||=
      if object_store == REMOTE_STORE
        remote_storage
      else
        local_storage
      end
  end

  def remote_storage
    raise 'Object Storage is not enabled' unless self.class.object_store_enabled?

    CarrierWave::Storage::Fog.new(self)
  end

  def local_storage
    CarrierWave::Storage::File.new(self)
  end
end