Merge branch '42547-upload-store-mount-point' into 'master'
Store uploader context in uploads Closes #42547 See merge request gitlab-org/gitlab-ce!16779
This commit is contained in:
commit
0e15a5b805
15 changed files with 120 additions and 30 deletions
|
@ -70,7 +70,7 @@ module UploadsActions
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_uploader_from_params
|
def build_uploader_from_params
|
||||||
uploader = uploader_class.new(model, params[:secret])
|
uploader = uploader_class.new(model, secret: params[:secret])
|
||||||
uploader.retrieve_from_store!(params[:filename])
|
uploader.retrieve_from_store!(params[:filename])
|
||||||
uploader
|
uploader
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,7 @@ class Upload < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_uploader
|
def build_uploader
|
||||||
uploader_class.new(model).tap do |uploader|
|
uploader_class.new(model, mount_point, **uploader_context).tap do |uploader|
|
||||||
uploader.upload = self
|
uploader.upload = self
|
||||||
uploader.retrieve_from_store!(identifier)
|
uploader.retrieve_from_store!(identifier)
|
||||||
end
|
end
|
||||||
|
@ -40,6 +40,13 @@ class Upload < ActiveRecord::Base
|
||||||
File.exist?(absolute_path)
|
File.exist?(absolute_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def uploader_context
|
||||||
|
{
|
||||||
|
identifier: identifier,
|
||||||
|
secret: secret
|
||||||
|
}.compact
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def checksummable?
|
def checksummable?
|
||||||
|
@ -62,11 +69,15 @@ class Upload < ActiveRecord::Base
|
||||||
!path.start_with?('/')
|
!path.start_with?('/')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def uploader_class
|
||||||
|
Object.const_get(uploader)
|
||||||
|
end
|
||||||
|
|
||||||
def identifier
|
def identifier
|
||||||
File.basename(path)
|
File.basename(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def uploader_class
|
def mount_point
|
||||||
Object.const_get(uploader)
|
super&.to_sym
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,11 +49,11 @@ class FileMover
|
||||||
end
|
end
|
||||||
|
|
||||||
def uploader
|
def uploader
|
||||||
@uploader ||= PersonalFileUploader.new(model, secret)
|
@uploader ||= PersonalFileUploader.new(model, secret: secret)
|
||||||
end
|
end
|
||||||
|
|
||||||
def temp_file_uploader
|
def temp_file_uploader
|
||||||
@temp_file_uploader ||= PersonalFileUploader.new(nil, secret)
|
@temp_file_uploader ||= PersonalFileUploader.new(nil, secret: secret)
|
||||||
end
|
end
|
||||||
|
|
||||||
def revert
|
def revert
|
||||||
|
|
|
@ -62,9 +62,11 @@ class FileUploader < GitlabUploader
|
||||||
|
|
||||||
attr_accessor :model
|
attr_accessor :model
|
||||||
|
|
||||||
def initialize(model, secret = nil)
|
def initialize(model, mounted_as = nil, **uploader_context)
|
||||||
|
super(model, nil, **uploader_context)
|
||||||
|
|
||||||
@model = model
|
@model = model
|
||||||
@secret = secret
|
apply_context!(uploader_context)
|
||||||
end
|
end
|
||||||
|
|
||||||
def base_dir
|
def base_dir
|
||||||
|
@ -107,15 +109,17 @@ class FileUploader < GitlabUploader
|
||||||
self.file.filename
|
self.file.filename
|
||||||
end
|
end
|
||||||
|
|
||||||
# the upload does not hold the secret, but holds the path
|
|
||||||
# which contains the secret: extract it
|
|
||||||
def upload=(value)
|
def upload=(value)
|
||||||
|
super
|
||||||
|
|
||||||
|
return unless value
|
||||||
|
return if apply_context!(value.uploader_context)
|
||||||
|
|
||||||
|
# fallback to the regex based extraction
|
||||||
if matches = DYNAMIC_PATH_PATTERN.match(value.path)
|
if matches = DYNAMIC_PATH_PATTERN.match(value.path)
|
||||||
@secret = matches[:secret]
|
@secret = matches[:secret]
|
||||||
@identifier = matches[:identifier]
|
@identifier = matches[:identifier]
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def secret
|
def secret
|
||||||
|
@ -124,6 +128,18 @@ class FileUploader < GitlabUploader
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def apply_context!(uploader_context)
|
||||||
|
@secret, @identifier = uploader_context.values_at(:secret, :identifier)
|
||||||
|
|
||||||
|
!!(@secret && @identifier)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_upload
|
||||||
|
super.tap do |upload|
|
||||||
|
upload.secret = secret
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def markdown_name
|
def markdown_name
|
||||||
(image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
|
(image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
|
||||||
|
|
||||||
delegate :base_dir, :file_storage?, to: :class
|
delegate :base_dir, :file_storage?, to: :class
|
||||||
|
|
||||||
|
def initialize(model, mounted_as = nil, **uploader_context)
|
||||||
|
super(model, mounted_as)
|
||||||
|
end
|
||||||
|
|
||||||
def file_cache_storage?
|
def file_cache_storage?
|
||||||
cache_storage.is_a?(CarrierWave::Storage::File)
|
cache_storage.is_a?(CarrierWave::Storage::File)
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,7 +24,7 @@ module RecordsUploads
|
||||||
uploads.where(path: upload_path).delete_all
|
uploads.where(path: upload_path).delete_all
|
||||||
upload.destroy! if upload
|
upload.destroy! if upload
|
||||||
|
|
||||||
self.upload = build_upload_from_uploader(self)
|
self.upload = build_upload
|
||||||
upload.save!
|
upload.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -39,12 +39,13 @@ module RecordsUploads
|
||||||
Upload.order(id: :desc).where(uploader: self.class.to_s)
|
Upload.order(id: :desc).where(uploader: self.class.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_upload_from_uploader(uploader)
|
def build_upload
|
||||||
Upload.new(
|
Upload.new(
|
||||||
size: uploader.file.size,
|
uploader: self.class.to_s,
|
||||||
path: uploader.upload_path,
|
size: file.size,
|
||||||
model: uploader.model,
|
path: upload_path,
|
||||||
uploader: uploader.class.to_s
|
model: model,
|
||||||
|
mount_point: mounted_as
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
5
changelogs/unreleased/42547-upload-store-mount-point.yml
Normal file
5
changelogs/unreleased/42547-upload-store-mount-point.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Added uploader metadata to the uploads.
|
||||||
|
merge_request: 16779
|
||||||
|
author:
|
||||||
|
type: added
|
14
db/migrate/20180129193323_add_uploads_builder_context.rb
Normal file
14
db/migrate/20180129193323_add_uploads_builder_context.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||||
|
# for more information on how to write migrations for GitLab.
|
||||||
|
|
||||||
|
class AddUploadsBuilderContext < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
# Set this constant to true if this migration requires downtime.
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :uploads, :mount_point, :string
|
||||||
|
add_column :uploads, :secret, :string
|
||||||
|
end
|
||||||
|
end
|
|
@ -1751,6 +1751,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do
|
||||||
t.string "model_type"
|
t.string "model_type"
|
||||||
t.string "uploader", null: false
|
t.string "uploader", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
|
t.string "mount_point"
|
||||||
|
t.string "secret"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
|
add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
|
||||||
|
|
|
@ -46,7 +46,7 @@ module Gitlab
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_file(project, secret, file)
|
def find_file(project, secret, file)
|
||||||
uploader = FileUploader.new(project, secret)
|
uploader = FileUploader.new(project, secret: secret)
|
||||||
uploader.retrieve_from_store!(file)
|
uploader.retrieve_from_store!(file)
|
||||||
uploader.file
|
uploader.file
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,36 +3,40 @@ FactoryBot.define do
|
||||||
model { build(:project) }
|
model { build(:project) }
|
||||||
size 100.kilobytes
|
size 100.kilobytes
|
||||||
uploader "AvatarUploader"
|
uploader "AvatarUploader"
|
||||||
|
mount_point :avatar
|
||||||
|
secret nil
|
||||||
|
|
||||||
# we should build a mount agnostic upload by default
|
# we should build a mount agnostic upload by default
|
||||||
transient do
|
transient do
|
||||||
mounted_as :avatar
|
filename 'myfile.jpg'
|
||||||
secret SecureRandom.hex
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# this needs to comply with RecordsUpload::Concern#upload_path
|
# this needs to comply with RecordsUpload::Concern#upload_path
|
||||||
path { File.join("uploads/-/system", model.class.to_s.underscore, mounted_as.to_s, 'avatar.jpg') }
|
path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') }
|
||||||
|
|
||||||
trait :personal_snippet_upload do
|
trait :personal_snippet_upload do
|
||||||
model { build(:personal_snippet) }
|
|
||||||
path { File.join(secret, 'myfile.jpg') }
|
|
||||||
uploader "PersonalFileUploader"
|
uploader "PersonalFileUploader"
|
||||||
|
path { File.join(secret, filename) }
|
||||||
|
model { build(:personal_snippet) }
|
||||||
|
secret SecureRandom.hex
|
||||||
end
|
end
|
||||||
|
|
||||||
trait :issuable_upload do
|
trait :issuable_upload do
|
||||||
path { File.join(secret, 'myfile.jpg') }
|
|
||||||
uploader "FileUploader"
|
uploader "FileUploader"
|
||||||
|
path { File.join(secret, filename) }
|
||||||
|
secret SecureRandom.hex
|
||||||
end
|
end
|
||||||
|
|
||||||
trait :namespace_upload do
|
trait :namespace_upload do
|
||||||
model { build(:group) }
|
model { build(:group) }
|
||||||
path { File.join(secret, 'myfile.jpg') }
|
path { File.join(secret, filename) }
|
||||||
uploader "NamespaceFileUploader"
|
uploader "NamespaceFileUploader"
|
||||||
|
secret SecureRandom.hex
|
||||||
end
|
end
|
||||||
|
|
||||||
trait :attachment_upload do
|
trait :attachment_upload do
|
||||||
transient do
|
transient do
|
||||||
mounted_as :attachment
|
mount_point :attachment
|
||||||
end
|
end
|
||||||
|
|
||||||
model { build(:note) }
|
model { build(:note) }
|
||||||
|
|
|
@ -2,8 +2,8 @@ require 'spec_helper'
|
||||||
|
|
||||||
describe FileSizeValidator do
|
describe FileSizeValidator do
|
||||||
let(:validator) { described_class.new(options) }
|
let(:validator) { described_class.new(options) }
|
||||||
let(:attachment) { AttachmentUploader.new }
|
|
||||||
let(:note) { create(:note) }
|
let(:note) { create(:note) }
|
||||||
|
let(:attachment) { AttachmentUploader.new(note) }
|
||||||
|
|
||||||
describe 'options uses an integer' do
|
describe 'options uses an integer' do
|
||||||
let(:options) { { maximum: 10, attributes: { attachment: attachment } } }
|
let(:options) { { maximum: 10, attributes: { attachment: attachment } } }
|
||||||
|
|
|
@ -103,4 +103,10 @@ describe Upload do
|
||||||
expect(upload).not_to exist
|
expect(upload).not_to exist
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#uploader_context" do
|
||||||
|
subject { create(:upload, :issuable_upload, secret: 'secret', filename: 'file.txt') }
|
||||||
|
|
||||||
|
it { expect(subject.uploader_context).to match(a_hash_including(secret: 'secret', identifier: 'file.txt')) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,7 +40,7 @@ describe FileUploader do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'initialize' do
|
describe 'initialize' do
|
||||||
let(:uploader) { described_class.new(double, 'secret') }
|
let(:uploader) { described_class.new(double, secret: 'secret') }
|
||||||
|
|
||||||
it 'accepts a secret parameter' do
|
it 'accepts a secret parameter' do
|
||||||
expect(described_class).not_to receive(:generate_secret)
|
expect(described_class).not_to receive(:generate_secret)
|
||||||
|
@ -54,4 +54,31 @@ describe FileUploader do
|
||||||
expect(uploader.secret).to eq('secret')
|
expect(uploader.secret).to eq('secret')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#upload=' do
|
||||||
|
let(:secret) { SecureRandom.hex }
|
||||||
|
let(:upload) { create(:upload, :issuable_upload, secret: secret, filename: 'file.txt') }
|
||||||
|
|
||||||
|
it 'handles nil' do
|
||||||
|
expect(uploader).not_to receive(:apply_context!)
|
||||||
|
|
||||||
|
uploader.upload = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'extract the uploader context from it' do
|
||||||
|
expect(uploader).to receive(:apply_context!).with(a_hash_including(secret: secret, identifier: 'file.txt'))
|
||||||
|
|
||||||
|
uploader.upload = upload
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'uploader_context is empty' do
|
||||||
|
it 'fallbacks to regex based extraction' do
|
||||||
|
expect(upload).to receive(:uploader_context).and_return({})
|
||||||
|
|
||||||
|
uploader.upload = upload
|
||||||
|
expect(uploader.secret).to eq(secret)
|
||||||
|
expect(uploader.instance_variable_get(:@identifier)).to eq('file.txt')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ require 'carrierwave/storage/fog'
|
||||||
describe GitlabUploader do
|
describe GitlabUploader do
|
||||||
let(:uploader_class) { Class.new(described_class) }
|
let(:uploader_class) { Class.new(described_class) }
|
||||||
|
|
||||||
subject { uploader_class.new }
|
subject { uploader_class.new(double) }
|
||||||
|
|
||||||
describe '#file_storage?' do
|
describe '#file_storage?' do
|
||||||
context 'when file storage is used' do
|
context 'when file storage is used' do
|
||||||
|
|
Loading…
Reference in a new issue