New upstream version 4.0.0+debian

parent 4373aef6
......@@ -2,6 +2,11 @@
Formerly known as 'gitlab-git-http-server'.
v 4.0.0
- Handle Object Store upload in upload.HandleFileUploads !238
- More consistent API naming. ObjectStore -> RemoteObject !240
v3.8.0
- Add structured logging !236
......
#!/bin/sh
git grep 'context.\(Background\|TODO\)' | \
grep -v -e '^[^:]*_test\.go:' -e '^vendor/' -e '^_support/' | \
grep -v -e '^[^:]*_test\.go:' -e '^vendor/' -e '^_support/' -e '^cmd/[^:]*/main.go' | \
grep -e '^[^:]*\.go' | \
awk '{
print "Found disallowed use of context.Background or TODO"
......
......@@ -2,16 +2,11 @@ package main
import (
"archive/zip"
"context"
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/jfbus/httprs"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/zipartifacts"
......@@ -23,67 +18,6 @@ var Version = "unknown"
var printVersion = flag.Bool("version", false, "Print version and exit")
var httpClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 10 * time.Second,
}).DialContext,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 10 * time.Second,
ResponseHeaderTimeout: 30 * time.Second,
},
}
func isURL(path string) bool {
return strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://")
}
func openHTTPArchive(archivePath string) (*zip.Reader, func()) {
scrubbedArchivePath := helper.ScrubURLParams(archivePath)
resp, err := httpClient.Get(archivePath)
if err != nil {
fatalError(fmt.Errorf("HTTP GET %q: %v", scrubbedArchivePath, err))
} else if resp.StatusCode == http.StatusNotFound {
notFoundError(fmt.Errorf("HTTP GET %q: not found", scrubbedArchivePath))
} else if resp.StatusCode != http.StatusOK {
fatalError(fmt.Errorf("HTTP GET %q: %d: %v", scrubbedArchivePath, resp.StatusCode, resp.Status))
}
rs := httprs.NewHttpReadSeeker(resp, httpClient)
archive, err := zip.NewReader(rs, resp.ContentLength)
if err != nil {
notFoundError(fmt.Errorf("open %q: %v", scrubbedArchivePath, err))
}
return archive, func() {
resp.Body.Close()
rs.Close()
}
}
func openFileArchive(archivePath string) (*zip.Reader, func()) {
archive, err := zip.OpenReader(archivePath)
if err != nil {
notFoundError(fmt.Errorf("open %q: %v", archivePath, err))
}
return &archive.Reader, func() {
archive.Close()
}
}
func openArchive(archivePath string) (*zip.Reader, func()) {
if isURL(archivePath) {
return openHTTPArchive(archivePath)
}
return openFileArchive(archivePath)
}
func main() {
flag.Parse()
......@@ -110,8 +44,17 @@ func main() {
fatalError(fmt.Errorf("decode entry %q: %v", encodedFileName, err))
}
archive, cleanFn := openArchive(archivePath)
defer cleanFn()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
archive, err := zipartifacts.OpenArchive(ctx, archivePath)
if err != nil {
oaError := fmt.Errorf("OpenArchive: %v", err)
if err == zipartifacts.ErrArchiveNotFound {
notFoundError(oaError)
}
fatalError(oaError)
}
file := findFileInZip(fileName, archive)
if file == nil {
......
package main
import (
"context"
"flag"
"fmt"
"os"
......@@ -27,11 +28,24 @@ func main() {
fmt.Fprintf(os.Stderr, "Usage: %s FILE.ZIP\n", progName)
os.Exit(1)
}
if err := zipartifacts.GenerateZipMetadataFromFile(os.Args[1], os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", progName, err)
if err == os.ErrInvalid {
os.Exit(zipartifacts.StatusNotZip)
}
os.Exit(1)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
archive, err := zipartifacts.OpenArchive(ctx, os.Args[1])
if err != nil {
fatalError(err)
}
if err := zipartifacts.GenerateZipMetadata(os.Stdout, archive); err != nil {
fatalError(err)
}
}
func fatalError(err error) {
fmt.Fprintf(os.Stderr, "%s: %v\n", progName, err)
if err == zipartifacts.ErrNotAZip {
os.Exit(zipartifacts.StatusNotZip)
}
os.Exit(1)
}
......@@ -67,16 +67,15 @@ func NewAPI(myURL *url.URL, version string, roundTripper *badgateway.RoundTrippe
type HandleFunc func(http.ResponseWriter, *http.Request, *Response)
type RemoteObjectStore struct {
// GetURL is not used in gitlab-workhorse. We pass it back to gitlab-rails
// later for symmetry with the 'store upload in tempfile' approach.
type RemoteObject struct {
// GetURL is an S3 GetObject URL
GetURL string
// DeleteURL is a presigned S3 RemoveObject URL
DeleteURL string
// StoreURL is the temporary presigned S3 PutObject URL to which upload the first found file
StoreURL string
// ObjectID is a unique identifier of object storage upload
ObjectID string
// ID is a unique identifier of object storage upload
ID string
// Timeout is a number that represents timeout in seconds for sending data to StoreURL
Timeout int
}
......@@ -105,9 +104,9 @@ type Response struct {
// TmpPath is the path where we should store temporary files
// This is set by authorization middleware
TempPath string
// ObjectStore is provided by the GitLab Rails application
// RemoteObject is provided by the GitLab Rails application
// and defines a way to store object on remote storage
ObjectStore RemoteObjectStore
RemoteObject RemoteObject
// Archive is the path where the artifacts archive is stored
Archive string `json:"archive"`
// Entry is a filename inside the archive point to file that needs to be extracted
......
package artifacts
import (
"context"
"fmt"
"mime/multipart"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/filestore"
)
func (a *artifactsUploadProcessor) storeFile(ctx context.Context, formName, fileName string, writer *multipart.Writer) error {
if !a.opts.IsRemote() {
return nil
}
if a.stored {
return nil
}
fh, err := filestore.SaveFileFromDisk(ctx, fileName, a.opts)
if err != nil {
return fmt.Errorf("Uploading to object store failed. %s", err)
}
for field, value := range fh.GitLabFinalizeFields(formName) {
writer.WriteField(field, value)
}
// Allow to upload only once using given credentials
a.stored = true
return nil
}
......@@ -48,6 +48,13 @@ func createTestMultipartForm(t *testing.T, data []byte) (bytes.Buffer, string) {
return buffer, writer.FormDataContentType()
}
func testUploadArtifactsFromTestZip(t *testing.T, ts *httptest.Server) *httptest.ResponseRecorder {
archiveData, _ := createTestZipArchive(t)
contentBuffer, contentType := createTestMultipartForm(t, archiveData)
return testUploadArtifacts(contentType, &contentBuffer, t, ts)
}
func TestUploadHandlerSendingToExternalStorage(t *testing.T) {
tempPath, err := ioutil.TempDir("", "uploads")
if err != nil {
......@@ -74,8 +81,8 @@ func TestUploadHandlerSendingToExternalStorage(t *testing.T) {
responseProcessorCalled := 0
responseProcessor := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "store-id", r.FormValue("file.object_id"))
assert.NotEmpty(t, r.FormValue("file.store_url"))
assert.Equal(t, "store-id", r.FormValue("file.remote_id"))
assert.NotEmpty(t, r.FormValue("file.remote_url"))
w.WriteHeader(200)
responseProcessorCalled++
}
......@@ -85,9 +92,9 @@ func TestUploadHandlerSendingToExternalStorage(t *testing.T) {
authResponse := api.Response{
TempPath: tempPath,
ObjectStore: api.RemoteObjectStore{
RemoteObject: api.RemoteObject{
StoreURL: storeServer.URL + "/url/put",
ObjectID: "store-id",
ID: "store-id",
GetURL: storeServer.URL + "/store-id",
},
}
......@@ -114,19 +121,16 @@ func TestUploadHandlerSendingToExternalStorageAndStorageServerUnreachable(t *tes
authResponse := api.Response{
TempPath: tempPath,
ObjectStore: api.RemoteObjectStore{
RemoteObject: api.RemoteObject{
StoreURL: "http://localhost:12323/invalid/url",
ObjectID: "store-id",
ID: "store-id",
},
}
ts := testArtifactsUploadServer(t, authResponse, responseProcessor)
defer ts.Close()
archiveData, _ := createTestZipArchive(t)
contentBuffer, contentType := createTestMultipartForm(t, archiveData)
response := testUploadArtifacts(contentType, &contentBuffer, t, ts)
response := testUploadArtifactsFromTestZip(t, ts)
testhelper.AssertResponseCode(t, response, 500)
}
......@@ -143,19 +147,16 @@ func TestUploadHandlerSendingToExternalStorageAndInvalidURLIsUsed(t *testing.T)
authResponse := api.Response{
TempPath: tempPath,
ObjectStore: api.RemoteObjectStore{
RemoteObject: api.RemoteObject{
StoreURL: "htt:////invalid-url",
ObjectID: "store-id",
ID: "store-id",
},
}
ts := testArtifactsUploadServer(t, authResponse, responseProcessor)
defer ts.Close()
archiveData, _ := createTestZipArchive(t)
contentBuffer, contentType := createTestMultipartForm(t, archiveData)
response := testUploadArtifacts(contentType, &contentBuffer, t, ts)
response := testUploadArtifactsFromTestZip(t, ts)
testhelper.AssertResponseCode(t, response, 500)
}
......@@ -184,19 +185,16 @@ func TestUploadHandlerSendingToExternalStorageAndItReturnsAnError(t *testing.T)
authResponse := api.Response{
TempPath: tempPath,
ObjectStore: api.RemoteObjectStore{
RemoteObject: api.RemoteObject{
StoreURL: storeServer.URL + "/url/put",
ObjectID: "store-id",
ID: "store-id",
},
}
ts := testArtifactsUploadServer(t, authResponse, responseProcessor)
defer ts.Close()
archiveData, _ := createTestZipArchive(t)
contentBuffer, contentType := createTestMultipartForm(t, archiveData)
response := testUploadArtifacts(contentType, &contentBuffer, t, ts)
response := testUploadArtifactsFromTestZip(t, ts)
testhelper.AssertResponseCode(t, response, 500)
assert.Equal(t, 1, putCalledTimes, "upload should be called only once")
}
......@@ -227,9 +225,9 @@ func TestUploadHandlerSendingToExternalStorageAndSupportRequestTimeout(t *testin
authResponse := api.Response{
TempPath: tempPath,
ObjectStore: api.RemoteObjectStore{
RemoteObject: api.RemoteObject{
StoreURL: storeServer.URL + "/url/put",
ObjectID: "store-id",
ID: "store-id",
Timeout: 1,
},
}
......@@ -237,10 +235,7 @@ func TestUploadHandlerSendingToExternalStorageAndSupportRequestTimeout(t *testin
ts := testArtifactsUploadServer(t, authResponse, responseProcessor)
defer ts.Close()
archiveData, _ := createTestZipArchive(t)
contentBuffer, contentType := createTestMultipartForm(t, archiveData)
response := testUploadArtifacts(contentType, &contentBuffer, t, ts)
response := testUploadArtifactsFromTestZip(t, ts)
testhelper.AssertResponseCode(t, response, 500)
assert.Equal(t, 1, putCalledTimes, "upload should be called only once")
}
......@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
......@@ -24,28 +23,55 @@ type artifactsUploadProcessor struct {
stored bool
}
func (a *artifactsUploadProcessor) generateMetadataFromZip(fileName string, metadataFile io.Writer) (bool, error) {
// Generate metadata and save to file
zipMd := exec.Command("gitlab-zip-metadata", fileName)
func (a *artifactsUploadProcessor) generateMetadataFromZip(ctx context.Context, file *filestore.FileHandler) (*filestore.FileHandler, error) {
metaReader, metaWriter := io.Pipe()
defer metaWriter.Close()
metaOpts := &filestore.SaveFileOpts{
LocalTempPath: a.opts.LocalTempPath,
TempFilePrefix: "metadata.gz",
}
fileName := file.LocalPath
if fileName == "" {
fileName = file.RemoteURL
}
zipMd := exec.CommandContext(ctx, "gitlab-zip-metadata", fileName)
zipMd.Stderr = os.Stderr
zipMd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
zipMd.Stdout = metadataFile
zipMd.Stdout = metaWriter
if err := zipMd.Start(); err != nil {
return false, err
return nil, err
}
defer helper.CleanUpProcessGroup(zipMd)
type saveResult struct {
error
*filestore.FileHandler
}
done := make(chan saveResult)
go func() {
var result saveResult
result.FileHandler, result.error = filestore.SaveFileFromReader(ctx, metaReader, -1, metaOpts)
done <- result
}()
if err := zipMd.Wait(); err != nil {
if st, ok := helper.ExitStatus(err); ok && st == zipartifacts.StatusNotZip {
return false, nil
return nil, nil
}
return false, err
return nil, err
}
return true, nil
metaWriter.Close()
result := <-done
return result.FileHandler, result.error
}
func (a *artifactsUploadProcessor) ProcessFile(ctx context.Context, formName, fileName string, writer *multipart.Writer) error {
func (a *artifactsUploadProcessor) ProcessFile(ctx context.Context, formName string, file *filestore.FileHandler, writer *multipart.Writer) error {
// ProcessFile for artifacts requires file form-data field name to eq `file`
if formName != "file" {
......@@ -55,28 +81,22 @@ func (a *artifactsUploadProcessor) ProcessFile(ctx context.Context, formName, fi
return fmt.Errorf("Artifacts request contains more than one file!")
}
// Create temporary file for metadata and store it's path
tempFile, err := ioutil.TempFile(a.opts.LocalTempPath, "metadata_")
if err != nil {
return err
}
defer tempFile.Close()
a.metadataFile = tempFile.Name()
generatedMetadata, err := a.generateMetadataFromZip(fileName, tempFile)
if err != nil {
return fmt.Errorf("generateMetadataFromZip: %v", err)
}
select {
case <-ctx.Done():
return fmt.Errorf("ProcessFile: context done")
if generatedMetadata {
// Pass metadata file path to Rails
writer.WriteField("metadata.path", a.metadataFile)
writer.WriteField("metadata.name", "metadata.gz")
}
default:
// TODO: can we rely on disk for shipping metadata? Not if we split workhorse and rails in 2 different PODs
metadata, err := a.generateMetadataFromZip(ctx, file)
if err != nil {
return fmt.Errorf("generateMetadataFromZip: %v", err)
}
if err := a.storeFile(ctx, formName, fileName, writer); err != nil {
return fmt.Errorf("storeFile: %v", err)
if metadata != nil {
for k, v := range metadata.GitLabFinalizeFields("metadata") {
writer.WriteField(k, v)
}
}
}
return nil
}
......@@ -93,12 +113,6 @@ func (a *artifactsUploadProcessor) Name() string {
return "artifacts"
}
func (a *artifactsUploadProcessor) Cleanup() {
if a.metadataFile != "" {
os.Remove(a.metadataFile)
}
}
func UploadArtifacts(myAPI *api.API, h http.Handler) http.Handler {
return myAPI.PreAuthorizeHandler(func(w http.ResponseWriter, r *http.Request, a *api.Response) {
if a.TempPath == "" {
......@@ -107,8 +121,7 @@ func UploadArtifacts(myAPI *api.API, h http.Handler) http.Handler {
}
mg := &artifactsUploadProcessor{opts: filestore.GetOpts(a)}
defer mg.Cleanup()
upload.HandleFileUploads(w, r, h, a.TempPath, mg)
upload.HandleFileUploads(w, r, h, a, mg)
}, "/authorize")
}
......@@ -65,10 +65,10 @@ func (fh *FileHandler) GitLabFinalizeFields(prefix string) map[string]string {
data[key("path")] = fh.LocalPath
}
if fh.RemoteURL != "" {
data[key("store_url")] = fh.RemoteURL
data[key("remote_url")] = fh.RemoteURL
}
if fh.RemoteID != "" {
data[key("object_id")] = fh.RemoteID
data[key("remote_id")] = fh.RemoteID
}
data[key("size")] = strconv.FormatInt(fh.Size, 10)
for hashName, hash := range fh.hashes {
......@@ -98,12 +98,6 @@ func SaveFileFromReader(ctx context.Context, reader io.Reader, size int64, opts
}()
if opts.IsRemote() {
// Unknown ContentLength must be implemented in order to achieve Artifact Uploading
if size == -1 && !opts.isGoogleCloudStorage() {
// TODO add support for artifact upload to S3-compatible object storage
return nil, errors.New("Not implemented")
}
object, err = objectstore.NewObject(ctx, opts.PresignedPut, opts.PresignedDelete, opts.Timeout, size)
if err != nil {
return nil, err
......
......@@ -213,8 +213,8 @@ func TestSaveFile(t *testing.T) {
assert.Equal(fh.Name, fields["file.name"])
assert.Equal(fh.LocalPath, fields["file.path"])
assert.Equal(fh.RemoteURL, fields["file.store_url"])
assert.Equal(fh.RemoteID, fields["file.object_id"])
assert.Equal(fh.RemoteURL, fields["file.remote_url"])
assert.Equal(fh.RemoteID, fields["file.remote_id"])
assert.Equal(strconv.FormatInt(test.ObjectSize, 10), fields["file.size"])
assert.Equal(test.ObjectMD5, fields["file.md5"])
assert.Equal(test.ObjectSHA1, fields["file.sha1"])
......
......@@ -51,17 +51,17 @@ func (s *SaveFileOpts) isGoogleCloudStorage() bool {
// GetOpts converts GitLab api.Response to a proper SaveFileOpts
func GetOpts(apiResponse *api.Response) *SaveFileOpts {
timeout := time.Duration(apiResponse.ObjectStore.Timeout) * time.Second
timeout := time.Duration(apiResponse.RemoteObject.Timeout) * time.Second
if timeout == 0 {
timeout = objectstore.DefaultObjectStoreTimeout
}
return &SaveFileOpts{
LocalTempPath: apiResponse.TempPath,
RemoteID: apiResponse.ObjectStore.ObjectID,
RemoteURL: apiResponse.ObjectStore.GetURL,
PresignedPut: apiResponse.ObjectStore.StoreURL,
PresignedDelete: apiResponse.ObjectStore.DeleteURL,
RemoteID: apiResponse.RemoteObject.ID,
RemoteURL: apiResponse.RemoteObject.GetURL,
PresignedPut: apiResponse.RemoteObject.StoreURL,
PresignedDelete: apiResponse.RemoteObject.DeleteURL,
Timeout: timeout,
}
}
......@@ -62,9 +62,9 @@ func TestGetOpts(t *testing.T) {
assert := assert.New(t)
apiResponse := &api.Response{
TempPath: "/tmp",
ObjectStore: api.RemoteObjectStore{
RemoteObject: api.RemoteObject{
Timeout: 10,
ObjectID: "id",
ID: "id",
GetURL: "http://get",
StoreURL: "http://store",
DeleteURL: "http://delete",
......@@ -74,11 +74,11 @@ func TestGetOpts(t *testing.T) {
opts := filestore.GetOpts(apiResponse)
assert.Equal(apiResponse.TempPath, opts.LocalTempPath)
assert.Equal(time.Duration(apiResponse.ObjectStore.Timeout)*time.Second, opts.Timeout)
assert.Equal(apiResponse.ObjectStore.ObjectID, opts.RemoteID)
assert.Equal(apiResponse.ObjectStore.GetURL, opts.RemoteURL)
assert.Equal(apiResponse.ObjectStore.StoreURL, opts.PresignedPut)
assert.Equal(apiResponse.ObjectStore.DeleteURL, opts.PresignedDelete)
assert.Equal(time.Duration(apiResponse.RemoteObject.Timeout)*time.Second, opts.Timeout)
assert.Equal(apiResponse.RemoteObject.ID, opts.RemoteID)
assert.Equal(apiResponse.RemoteObject.GetURL, opts.RemoteURL)
assert.Equal(apiResponse.RemoteObject.StoreURL, opts.PresignedPut)
assert.Equal(apiResponse.RemoteObject.DeleteURL, opts.PresignedDelete)
}
func TestGetOptsDefaultTimeout(t *testing.T) {
......
......@@ -42,7 +42,6 @@ func IsGoogleCloudStorage(u *url.URL) bool {
return strings.ToLower(u.Host) == "storage.googleapis.com"
}
type MissingContentLengthError error
type StatusCodeError error
// Object represents an object on a S3 compatible Object Store service.
......@@ -79,14 +78,7 @@ func NewObject(ctx context.Context, putURL, deleteURL string, timeout time.Durat
objectStorageUploadRequestsRequestFailed.Inc()
return nil, fmt.Errorf("PUT %q: %v", helper.ScrubURLParams(o.PutURL), err)
}
if size == -1 {
if !IsGoogleCloudStorage(req.URL) {
objectStorageUploadRequestsRequestFailed.Inc()
return nil, MissingContentLengthError(fmt.Errorf("Unknown Content-Length not allowed on %s", req.URL.Host))
}
} else {
req.ContentLength = size
}
req.ContentLength = size
req.Header.Set("Content-Type", "application/octet-stream")
if timeout == 0 {
......
......@@ -97,13 +97,3 @@ func TestObjectUpload404(t *testing.T) {
assert.True(isStatusCodeError, "Should fail with StatusCodeError")
assert.Contains(err.Error(), "404")
}
func TestUnknownSizeUpload(t *testing.T) {
assert := assert.New(t)
object, err := objectstore.NewObject(context.Background(), "http://example.com/bucket/object", "", 0, -1)
assert.Error(err)
_, isMissingContentLengthError := err.(objectstore.MissingContentLengthError)
assert.True(isMissingContentLengthError, "Should fail with MissingContentLengthError")
assert.Nil(object)
}
......@@ -6,6 +6,8 @@ import (
"mime/multipart"
"net/http"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/api"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/filestore"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/secret"
jwt "github.com/dgrijalva/jwt-go"
......@@ -24,17 +26,19 @@ type MultipartClaims struct {
}
func Accelerate(tempDir string, h http.Handler) http.Handler {
// TODO: for Object Store this will need a authorize call
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
localOnlyPreAuth := &api.Response{TempPath: tempDir}
s := &savedFileTracker{request: r}
HandleFileUploads(w, r, h, tempDir, s)
HandleFileUploads(w, r, h, localOnlyPreAuth, s)
})
}
func (s *savedFileTracker) ProcessFile(_ context.Context, fieldName, fileName string, _ *multipart.Writer) error {
func (s *savedFileTracker) ProcessFile(_ context.Context, fieldName string, file *filestore.FileHandler, _ *multipart.Writer) error {
if s.rewrittenFields == nil {
s.rewrittenFields = make(map[string]string)
}
s.rewrittenFields[fieldName] = fileName
s.rewrittenFields[fieldName] = file.LocalPath
return nil
}
......
......@@ -10,6 +10,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/api"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/filestore"
)
......@@ -41,9 +42,9 @@ var (
)
type rewriter struct {
writer *multipart.Writer
tempPath string
filter MultipartFormProcessor
writer *multipart.Writer
preauth *api.Response
filter MultipartFormProcessor
}
func init() {
......@@ -52,7 +53,7 @@ func init() {
prometheus.MustRegister(multipartFiles)
}
func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, tempPath string, filter MultipartFormProcessor) error {
func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, preauth *api.Response, filter MultipartFormProcessor) error {
// Create multipart reader
reader, err := r.MultipartReader()
if err != nil {
......@@ -66,9 +67,9 @@ func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, te
multipartUploadRequests.WithLabelValues(filter.Name()).Inc()
rew := &rewriter{
writer: writer,
tempPath: tempPath,
filter: filter,
writer: writer,
preauth: preauth,
filter: filter,
}
for {
......@@ -88,7 +89,6 @@ func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, te
// Copy form field
if p.FileName() != "" {
err = rew.handleFilePart(r.Context(), name, p)
} else {
err = rew.copyPart(r.Context(), name, p)
}
......@@ -110,10 +110,8 @@ func (rew *rewriter) handleFilePart(ctx context.Context, name string, p *multipa
return fmt.Errorf("illegal filename: %q", filename)
}
opts := &filestore.SaveFileOpts{