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:
Sean McGivern 2018-02-05 15:28:09 +00:00
commit 0e15a5b805
15 changed files with 120 additions and 30 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,5 @@
---
title: Added uploader metadata to the uploads.
merge_request: 16779
author:
type: added

View 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

View file

@ -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

View file

@ -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

View file

@ -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) }

View file

@ -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 } } }

View file

@ -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

View file

@ -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

View file

@ -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