proposal
- When you create a program to load,να γίνει υποκατηγορίαfrom
Attachment loader
- Add your loader totablein this document
- do not addbin to store the new object
- to applyimmediate loading
- Whether you need to process your transfers is up to youwhere to do
Accompanying information
- CarrierWave Uploader
- GitLab changes to CarrierWave
Where should you store your files?
The CarrierWave uploader determines where the files are stored. When you create a new Uploader class, you decide where to store the files for your new functionality.
First, ask yourself if you need a new Uploader class. The same Uploader class can be used for different attachment points or different models.
If you really want or need your own Uploader class, you should create onesubcategory ofAttachment loader
You then inherit the storage location and directory schema from this class. The directory format is:
document.Participation(Model.class.tone, install_as.to_s, Model.ID card.to_s)
If you look at the GitLab repository, you will see that many users have their own repository. For item storage, this means loaders have their own containers. right now wethey do not encourageNew containers are added for the following reasons:
- Using a new container increases development time because you have to addGDK,GitLab for everyoneIcompressed natural gas.
- Using the new containers required changes to the GitLab.com infrastructure, which slowed down the release of new features
- Using a new container slows down the adoption of new features for self-managed GitLab installations: users can't start using your new features until your local GitLab admin configures the new container.
By using your existing barrel, you avoid all that extra work and friction. thisGitlab.config.uploads
storage location, what is itAttachment loader
Use, make sure it is set.
Implement live streaming support
Below we describe how to achieve thisimmediate loadingsupport.
Using direct streaming isn't always necessary, but it's usually a good idea. Unless your function handles infrequent and small loads, you probably want to apply loads directly. Project avatars are an example of a feature with small and infrequent uploads: these avatars rarely change and their application imposes a strict size limit.
If your operation handles transfers that are neither frequent nor small, not implementing direct transfer support means you're taking on technical debt. At least, you should make surecapableAdd live streaming support later.
To support live streaming, you need two things:
- Pre-authorization endpoint in Rails
- Main routing rules
Workhorse does not know where to store your uploads. To find out, submit a pre-approval request. He also does not know if and where to apply for pre-approval. For this you need routing rules.
A note for those of us who remember,Workhorse was a separate project: It is no longer necessary to split these two steps into separate merge requests. In fact, it may be easier to do both in one merge request.
Add Workhorse routing rules
Routing rules are defined inworkhorse/internal/upstream/routes.go.They include:
- HTTP verb (usually "POST" or "PUT")
- path regular expression
- Transport type: Multipart MIME or "full request body"
- Alternatively, you can also assign HTTP headers, such as
Content type
example:
vessel.ruta("tan", way project api+"package/nuget/"., mimeMultipartUploader),
You should add a test to the routing ruleTry expedited shipping
existsmain/upload_test.go.
You should also manually verify that Workhorse issues a pre-approval request when you submit a request to load a new feature. You can verify this by looking at the Rails access log. This is necessary because if you get the routing rules wrong, you won't have a hard failure: you'll just end up using a less efficient default route.
Add an endpoint before authorization
We distinguish three cases: Rails controllers, Grape API endpoints, and GraphQL resources.
First the bad news: direct transfers in GraphQL are not currently supported. This is because Workhorse does not parse GraphQL queries. See alsoIssue #280819.Consider accepting the file upload via Grape.
For the Grape preauthorization endpoint, look for existing example implementations/approve
Route. An example ispostal:id/upload/authorization
endpoint.This example uses a FileUloader, which means that the upload is stored in the storage location (bucket) of this Uploader class.
For Rails endpoints you can useThe main authorization problem.
loading process
Some features require us to manage uploads, such as extracting metadata from uploaded files. There are several different ways to achieve this. The main options areWhereProcessing implementation, i.e. "who is the processor".
processor | Can it be loaded directly? | Can an HTTP request be rejected? | to apply |
---|---|---|---|
Sidekiq | And | Yes | direct |
main force | And | And | complicated |
guide | Yes | And | Comfortably |
Handling in Rails looks attractive, but it usually causes scaling problems because you can't use transfers directly. You will then be forced to rebuild your function by doing so in Workhorse. So, if your operational requirements allow it, running on Sidekiq can be a good balance between complexity and scalability.
CarrierWave Uploader
GitLab uses a modified versionCarrierTransfer management. Below we describe how we use CarrierWave and how we modify it.
The basic idea of CarrierWave isUploaded by userclass. Loaders define where the files are stored and can optionally contain validation and processing logic. To use a loader, you must bind it to a text column in the ActiveRecord model. This is called "installation" and the column is namedplacement point
.For example:
class Work < application file install the uploader : avatar, Attachment loaderend
Now if you upload a file namedcivet cat.png
The idea is that inproject.avatar
column, CarrierWave storage arraycivet cat.png
, and the AttachmentUploader class contains configuration data and a directory schema. For example, if the project ID is 123, the actual file might be located at/var/opt/gitlab/gitlab-rails/uploads/-/system/project/avatar/123/tanuki.png
.Content/var/opt/gitlab/gitlab-rails/uploads/-/system/project/avatar/123/
It is up to the loader to use additional configuration options (/var/opt/gitlab/gitlab-rails/uploads
), Model Name (Work
), model ID (123
) and support point (avatar
).
The sender specifies a personal directory to store your uploads. this
placement point
A column in the model contains the file name.
you never visitplacement point
Direct because CarrierWave defines a getter and setter on your model that operates on the file handler object.
Optional loader behavior
In addition to specifying the storage directory to upload to, the CarrierWave Uploader can implement many other behaviors through callbacks. Not all of these behaviors are available in GitLab. Specifically, you can't use it right nowVersion
Carrier wave mechanism. Things you can do include:
- file name check
- Not compatible with live streaming:Perform pre-processing on the file contents, such as resizing the image
- Not compatible with live streaming:encryption at rest
CarrierWave's preprocessing behavior, such as image resizing or encryption, requires local access to the uploaded files. This forces you to load the edited files from Ruby. This is opposed to direct loading, which is approxYesLoading in Ruby. If you use direct loading with a loader that has preprocessing behavior, the preprocessing behavior will be silently skipped.
CarrierWave data storage mechanism
CarrierWave has 2 data storage mechanisms:
CarrierWave class | GitLab time | describe |
---|---|---|
CarrierWave::Save::File | Object Storage::Storage::Local | Local files, accessible through Rubystandard library |
CarrierWave::Storage::Fog | Object Storage::Storage::Remote | Files in the cloud, accessible viadental ornament |
GitLab uses both engines depending on the configuration.
A typical way to select a storage mechanism in CarrierWave is to useuploader.storage
class method. At GitLab, we don't do that. we coveredUploaded by #storage
instead of this. This allows us to modify the storage engine file by file.
CarrierWave file life cycle
The uploader is connected to two storage areas: normal storage and cache storage. Each has its own data storage mechanism. If a file is assigned a mount point setter (project.avatar = File.open('/tmp/tanuki.png')
) as a side effect, you have to passCrypt!
method. To save the file, you need to call it somehowStore!
method. This either happens insideActiveRecord callbackor callStore!
in the case of the loader.
You usually don't need to use itCrypt!
IStore!
But if you need to patch changes in GitLab CarrierWave, it's useful to know that it's there and always called. In particular, it is good to know the CarrierWave preprocessing behavior (procedure
etc) are implemented asbefore: cache
hooks, and in the case of direct loads, these hooks are ignored and will not work.
Direct transmission skips all CarrierWave
before: cache
scream.
GitLab changes to CarrierWave
GitLab uses a modified version of CarrierWave to enable many things.
Data transfer between storage machines
existsapp/uploader/object_storage.rbThere is code to migrate user data between local storage and object storage. This code exists because GitLab.com has long saved commits to local storage via NFS. This changed when we had to move transports to object storage as part of an infrastructure migration.
Zato CarrierWaveSave
It varies depending on the upload to GitLab and why we have database columns likeupload.store
theci_job_artifacts.file_store
.
Direct upload via Workhorse
Workhorse direct loading is a mechanism that allows us to accept large loads without consuming too much Ruby CPU time. Workhorse is written in Go, and goroutines have much fewer resources than Rubythreads.
See how live streaming works.
- Workhorse accepts user upload requests
- Workhorse uses Rails to pre-validate requests and obtain temporary load positions
- Workhorse stores uploaded files from user requests in a temporary upload location
- Workhorse propagates the request to Rails
- Rails performs a remote copy function to copy an uploaded file from a temporary location to a final location
- Rails deletes the temporary transfer
- Workhorse flushes the temporary transfer a second time to prevent Rails from timing out
usually,Crypt!
return an instanceCarrierWave::SanitizedFile
, MeStore!
afterwardUpload the file using Fog.
In the case of object storage, copying from a temporary location to a final location is accomplished with Rails tricking CarrierWave with GitLab-specific modifications. When CarrierWave triesCrypt!
upload, miRETURNONECarrierWave::Save::Fog::File
The file handle for the temporary file. During this periodStore!
Phase, CarrierWave and timecopythis file to the expected location.
surface
The Scalability::Frameworks team makes saving and loading objects easier to use and more powerful. If you add or change loaders, it would help us to update this form as well. This helps us understand where and how transmitters are being used.
Characteristic container elements
feature | loading technology | Uploaded by user | barrel structure |
---|---|---|---|
work artifact | immediate loading | main force | /tricks/ |
Production from the assembly line | Carrier | sidekiq | /tricks/ |
Live job traces | FOG | sidekiq | /artifacts/tmp/builds/ |
Task tracking file | Carrier | sidekiq | /tricks/ |
Autoscale cursor cache | Does not apply | gitlab-collier | /gitlab-com-[platform-]runners-cache/??? |
backup | Does not apply | s3cmd ,awscli , theground station | /gitlab-backup/??? |
The file system of the Git language | immediate loading | main force | /lfs-objects/ |
design management file | disk cache | railway controller | /lsf-object/ |
Project Management Thumbnail | Carrier | sidekiq | /uploads/design_management/action/image_v432x230/ |
General file upload | immediate loading | main force | /uploads/@hashed/[0:2]/[2:4]/ |
General file transfer - individual fragments | immediate loading | main force | /uploads/personal_nippet/ |
Global layout settings | disk cache | railway controller | /uploads/display/... |
him | disk cache | railway controller | /uploads/projects/themes/... |
slika avatara | immediate loading | main force | /uploads/[user,group,project]/avatar/ |
Introduction | immediate loading | main force | /uploads/import_export_upload/import_file/ |
Exit | Carrier | sidekiq | /uploads/import_export_upload/export_file/ |
GitLab migration | Carrier | sidekiq | /uploads/bulk_imports/??? |
MR difference | Carrier | sidekiq | /external-diffs/merge_request_diffs/mr- |
Package Management Resources (Non-NPM) | immediate loading | main force | /packet/ |
NPM Package Management Resources | Carrier | API for grapes | /packet/ |
Debian Package Management Resources | immediate loading | main force | /packet/ |
Rely on a proxy cache | sending dependency | main force | /dependency-proxy/ |
Terraform state file | Carrier | railway controller | /terraform/ |
Website content archive | Carrier | sidekiq | /gitlab-gprd-pages/ |
security file | Carrier | sidekiq | /ci-secure-files/ |
CarrierWave integration
document | Using CarrierWave | Classification |
---|---|---|
application/models/projekt.rb | including avatars | {cycle check}And |
application/models/project/tema.rb | including avatars | {cycle check}And |
application/models/group.rb | including avatars | {cycle check}And |
application/models/user.rb | including avatars | {cycle check}And |
app/models/terraform/state_version.rb | Enable FileStoreMounter | {cycle check}And |
app/models/ci/job_artifact.rb | Enable FileStoreMounter | {cycle check}And |
app/models/ci/pipeline_artifact.rb | Enable FileStoreMounter | {cycle check}And |
app/modeli/pages_deployment.rb | Enable FileStoreMounter | {cycle check}And |
application/models/lfs_object.rb | Enable FileStoreMounter | {cycle check}And |
application/models/dependency_proxy/blob.rb | Enable FileStoreMounter | {cycle check}And |
application/models/dependency_proxy/manifest.rb | Enable FileStoreMounter | {cycle check}And |
app/modeli/packages/composer/cache_file.rb | Enable FileStoreMounter | {cycle check}And |
application/models/package/package_file.rb | Enable FileStoreMounter | {cycle check}And |
app/modeli/concerns/packages/debian/component_file.rb | Enable FileStoreMounter | {cycle check}And |
ee/app/models/issuable_metric_image.rb | Enable FileStoreMounter | |
ee/app/modeli/vulnerabilities/remediation.rb | Enable FileStoreMounter | |
ee/app/modeli/vulnerabilities/export.rb | Enable FileStoreMounter | |
app/modeli/packages/debian/project_distribution.rb | Contains packages::Debian::Distribution | {cycle check}And |
application/models/packages/debian/group_distribution.rb | Contains packages::Debian::Distribution | {cycle check}And |
app/modeli/packages/debian/project_component_file.rb | Include package::Debian::ComponentFile | {cycle check}And |
app/modeli/packages/debian/group_component_file.rb | Include package::Debian::ComponentFile | {cycle check}And |
application/models/merge_request_diff.rb | mount_uploader :external_diff, ExternalDiffUploader | {cycle check}And |
app/models/note.rb | mount_uploader : attachment, attachment uploader | {cycle check}And |
application/models/layout.rb | mount_uploader :logo, AttachmentUploader | {cycle check}And |
application/models/layout.rb | mount_uploader : header_logo, attachment uploader | {cycle check}And |
application/models/layout.rb | mount_uploader :favicon, FaviconUloader | {cycle check}And |
application/models/projekt.rb | mount_uploader :bfg_object_map, attachment uploader | |
application/models/import_export_upload.rb | mount_uploader :import_file, ImportExportUploader | {cycle check}And |
application/models/import_export_upload.rb | mount_uploader :export_file, ImportExportUploader | {cycle check}And |
app/modeli/ci/deleted_object.rb | mount_uploader :file, DeletedObjectUploader | |
app/modeli/design_management/action.rb | mount_uploader :image_v432x230, DesignManagement::DesignV432x230Uploader | {cycle check}And |
app/modeli/concerns/packages/debian/distribution.rb | mount_uploader :signed_file, Paketi::Debian::DistributionReleaseFileUloader | {cycle check}And |
application/models/bulk_imports/export_upload.rb | mount_uploader :export_file, ExportUploader | {cycle check}And |
ee/app/models/user_permission_export_upload.rb | mount_uploader :file, AttachmentUploader | |
app/modeli/ci/secure_file.rb | Enable FileStoreMounter |