Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
d6e2712f
Commit
d6e2712f
authored
Oct 12, 2020
by
Aleksei Lipniagov
Committed by
Jacob Vosmaer
Oct 12, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow configure Img Resizing params
parent
926eb55f
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
119 additions
and
29 deletions
+119
-29
changelogs/unreleased/298-set-img-resizing-const-via-env-vars.yml
...gs/unreleased/298-set-img-resizing-const-via-env-vars.yml
+5
-0
config.toml.example
config.toml.example
+4
-0
internal/config/config.go
internal/config/config.go
+18
-1
internal/config/config_test.go
internal/config/config_test.go
+36
-0
internal/imageresizer/image_resizer.go
internal/imageresizer/image_resizer.go
+19
-17
internal/imageresizer/image_resizer_test.go
internal/imageresizer/image_resizer_test.go
+27
-4
internal/upstream/routes.go
internal/upstream/routes.go
+4
-4
main.go
main.go
+2
-0
main_test.go
main_test.go
+4
-3
No files found.
changelogs/unreleased/298-set-img-resizing-const-via-env-vars.yml
0 → 100644
View file @
d6e2712f
---
title
:
Allow configure image resizing params
merge_request
:
615
author
:
type
:
changed
config.toml.example
View file @
d6e2712f
...
...
@@ -11,3 +11,7 @@ URL = "unix:/home/git/gitlab/redis/redis.socket"
[object_store.azurerm]
azure_storage_account_name = "YOUR ACCOUNT NAME"
azure_storage_access_key = "YOUR ACCOUNT KEY"
[image_resizer]
max_scaler_procs = 100
max_filesize = 250000
internal/config/config.go
View file @
d6e2712f
...
...
@@ -80,6 +80,11 @@ type RedisConfig struct {
MaxActive
*
int
}
type
ImageResizerConfig
struct
{
MaxScalerProcs
uint32
`toml:"max_scaler_procs"`
MaxFilesize
uint64
`toml:"max_filesize"`
}
type
Config
struct
{
Redis
*
RedisConfig
`toml:"redis"`
Backend
*
url
.
URL
`toml:"-"`
...
...
@@ -97,11 +102,23 @@ type Config struct {
ObjectStorageConfig
ObjectStorageConfig
`toml:"-"`
ObjectStorageCredentials
*
ObjectStorageCredentials
`toml:"object_storage"`
PropagateCorrelationID
bool
`toml:"-"`
ImageResizerConfig
*
ImageResizerConfig
`toml:"image_resizer"`
}
const
(
DefaultImageResizerMaxScalerProcs
=
100
DefaultImageResizerMaxFilesize
=
250
*
1000
// 250kB
)
var
DefaultImageResizerConfig
=
&
ImageResizerConfig
{
MaxScalerProcs
:
DefaultImageResizerMaxScalerProcs
,
MaxFilesize
:
DefaultImageResizerMaxFilesize
,
}
// LoadConfig from a file
func
LoadConfig
(
filename
string
)
(
*
Config
,
error
)
{
cfg
:=
&
Config
{}
cfg
:=
&
Config
{
ImageResizerConfig
:
DefaultImageResizerConfig
}
if
_
,
err
:=
toml
.
DecodeFile
(
filename
,
cfg
);
err
!=
nil
{
return
nil
,
err
}
...
...
internal/config/config_test.go
View file @
d6e2712f
...
...
@@ -80,6 +80,42 @@ func TestRegisterGoCloudURLOpeners(t *testing.T) {
require
.
Equal
(
t
,
[]
string
{
"azblob"
},
cfg
.
ObjectStorageConfig
.
URLMux
.
BucketSchemes
())
}
func
TestLoadDefaultConfig
(
t
*
testing
.
T
)
{
config
:=
``
tmpFile
,
cfg
:=
loadTempConfig
(
t
,
config
)
defer
os
.
Remove
(
tmpFile
.
Name
())
expected
:=
Config
{
ImageResizerConfig
:
&
ImageResizerConfig
{
MaxScalerProcs
:
DefaultImageResizerMaxScalerProcs
,
MaxFilesize
:
DefaultImageResizerMaxFilesize
,
},
}
require
.
Equal
(
t
,
expected
,
*
cfg
)
}
func
TestLoadImageResizerConfig
(
t
*
testing
.
T
)
{
config
:=
`
[image_resizer]
max_scaler_procs = 200
max_filesize = 350000
`
tmpFile
,
cfg
:=
loadTempConfig
(
t
,
config
)
defer
os
.
Remove
(
tmpFile
.
Name
())
require
.
NotNil
(
t
,
cfg
.
ImageResizerConfig
,
"Expected image resizer config"
)
expected
:=
ImageResizerConfig
{
MaxScalerProcs
:
200
,
MaxFilesize
:
350000
,
}
require
.
Equal
(
t
,
expected
,
*
cfg
.
ImageResizerConfig
)
}
func
loadTempConfig
(
t
*
testing
.
T
,
config
string
)
(
f
*
os
.
File
,
cfg
*
Config
)
{
tmpFile
,
err
:=
ioutil
.
TempFile
(
os
.
TempDir
(),
"test-"
)
require
.
NoError
(
t
,
err
)
...
...
internal/imageresizer/image_resizer.go
View file @
d6e2712f
...
...
@@ -14,6 +14,8 @@ import (
"syscall"
"time"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/config"
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/gitlab-org/labkit/correlation"
...
...
@@ -25,9 +27,10 @@ import (
"gitlab.com/gitlab-org/gitlab-workhorse/internal/senddata"
)
type
resizer
struct
{
senddata
.
Prefix
}
var
SendScaledImage
=
&
resizer
{
"send-scaled-img:"
}
type
Resizer
struct
{
config
.
Config
senddata
.
Prefix
}
type
resizeParams
struct
{
Location
string
...
...
@@ -41,11 +44,6 @@ type processCounter struct {
var
numScalerProcs
processCounter
const
(
maxImageScalerProcs
=
100
maxAllowedFileSizeBytes
=
250
*
1000
// 250kB
)
var
envInjector
=
tracing
.
NewEnvInjector
()
// Images might be located remotely in object storage, in which case we need to stream
...
...
@@ -123,9 +121,13 @@ func init() {
prometheus
.
MustRegister
(
imageResizeDurations
)
}
func
NewResizer
(
cfg
config
.
Config
)
*
Resizer
{
return
&
Resizer
{
cfg
,
"send-scaled-img:"
}
}
// This Injecter forks into a dedicated scaler process to resize an image identified by path or URL
// and streams the resized image back to the client
func
(
r
*
r
esizer
)
Inject
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
,
paramsData
string
)
{
func
(
r
*
R
esizer
)
Inject
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
,
paramsData
string
)
{
start
:=
time
.
Now
()
logger
:=
log
.
ContextLogger
(
req
.
Context
())
params
,
err
:=
r
.
unpackParameters
(
paramsData
)
...
...
@@ -156,7 +158,7 @@ func (r *resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
// We first attempt to rescale the image; if this should fail for any reason, imageReader
// will point to the original image, i.e. we render it unchanged.
imageReader
,
resizeCmd
,
err
:=
tryResizeImage
(
req
,
sourceImageReader
,
logger
.
Writer
(),
params
,
fileSize
)
imageReader
,
resizeCmd
,
err
:=
tryResizeImage
(
req
,
sourceImageReader
,
logger
.
Writer
(),
params
,
fileSize
,
r
.
Config
.
ImageResizerConfig
)
if
err
!=
nil
{
// something failed, but we can still write out the original image, do don't return early
helper
.
LogErrorWithFields
(
req
,
err
,
*
logFields
(
0
))
...
...
@@ -204,7 +206,7 @@ func handleFailedCommand(w http.ResponseWriter, req *http.Request, bytesWritten
}
}
func
(
r
*
r
esizer
)
unpackParameters
(
paramsData
string
)
(
*
resizeParams
,
error
)
{
func
(
r
*
R
esizer
)
unpackParameters
(
paramsData
string
)
(
*
resizeParams
,
error
)
{
var
params
resizeParams
if
err
:=
r
.
Unpack
(
&
params
,
paramsData
);
err
!=
nil
{
return
nil
,
err
...
...
@@ -222,12 +224,12 @@ func (r *resizer) unpackParameters(paramsData string) (*resizeParams, error) {
}
// Attempts to rescale the given image data, or in case of errors, falls back to the original image.
func
tryResizeImage
(
req
*
http
.
Request
,
r
io
.
Reader
,
errorWriter
io
.
Writer
,
params
*
resizeParams
,
fileSize
int64
)
(
io
.
Reader
,
*
exec
.
Cmd
,
error
)
{
if
fileSize
>
maxAllowedFileSizeBytes
{
return
r
,
nil
,
fmt
.
Errorf
(
"ImageResizer: %db exceeds maximum file size of %db"
,
fileSize
,
maxAllowedFileSizeBytes
)
func
tryResizeImage
(
req
*
http
.
Request
,
r
io
.
Reader
,
errorWriter
io
.
Writer
,
params
*
resizeParams
,
fileSize
int64
,
cfg
*
config
.
ImageResizerConfig
)
(
io
.
Reader
,
*
exec
.
Cmd
,
error
)
{
if
fileSize
>
int64
(
cfg
.
MaxFilesize
)
{
return
r
,
nil
,
fmt
.
Errorf
(
"ImageResizer: %db exceeds maximum file size of %db"
,
fileSize
,
cfg
.
MaxFilesize
)
}
if
!
numScalerProcs
.
tryIncrement
()
{
if
!
numScalerProcs
.
tryIncrement
(
int32
(
cfg
.
MaxScalerProcs
)
)
{
return
r
,
nil
,
fmt
.
Errorf
(
"ImageResizer: too many running scaler processes"
)
}
...
...
@@ -312,8 +314,8 @@ func openFromFile(location string) (io.ReadCloser, int64, error) {
// Only allow more scaling requests if we haven't yet reached the maximum
// allowed number of concurrent scaler processes
func
(
c
*
processCounter
)
tryIncrement
()
bool
{
if
p
:=
atomic
.
AddInt32
(
&
c
.
n
,
1
);
p
>
max
Image
ScalerProcs
{
func
(
c
*
processCounter
)
tryIncrement
(
maxScalerProcs
int32
)
bool
{
if
p
:=
atomic
.
AddInt32
(
&
c
.
n
,
1
);
p
>
maxScalerProcs
{
c
.
decrement
()
imageResizeConcurrencyLimitExceeds
.
Inc
()
...
...
internal/imageresizer/image_resizer_test.go
View file @
d6e2712f
...
...
@@ -8,6 +8,8 @@ import (
"os"
"testing"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/config"
"gitlab.com/gitlab-org/labkit/log"
"github.com/stretchr/testify/require"
...
...
@@ -15,7 +17,7 @@ import (
"gitlab.com/gitlab-org/gitlab-workhorse/internal/testhelper"
)
var
r
=
r
esizer
{}
var
r
=
R
esizer
{}
func
TestMain
(
m
*
testing
.
M
)
{
if
err
:=
testhelper
.
BuildExecutables
();
err
!=
nil
{
...
...
@@ -56,7 +58,14 @@ func TestTryResizeImageSuccess(t *testing.T) {
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
require
.
NoError
(
t
,
err
)
reader
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
maxAllowedFileSizeBytes
)
reader
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
int64
(
config
.
DefaultImageResizerConfig
.
MaxFilesize
),
config
.
DefaultImageResizerConfig
,
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
cmd
)
...
...
@@ -70,7 +79,14 @@ func TestTryResizeImageSkipsResizeWhenSourceImageTooLarge(t *testing.T) {
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
require
.
NoError
(
t
,
err
)
reader
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
maxAllowedFileSizeBytes
+
1
)
reader
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
int64
(
config
.
DefaultImageResizerConfig
.
MaxFilesize
)
+
1
,
config
.
DefaultImageResizerConfig
,
)
require
.
Error
(
t
,
err
)
require
.
Nil
(
t
,
cmd
)
...
...
@@ -83,7 +99,14 @@ func TestTryResizeImageFailsWhenContentTypeNotMatchingFileContents(t *testing.T)
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
require
.
NoError
(
t
,
err
)
_
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
maxAllowedFileSizeBytes
)
_
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
int64
(
config
.
DefaultImageResizerConfig
.
MaxFilesize
),
config
.
DefaultImageResizerConfig
,
)
require
.
NoError
(
t
,
err
)
require
.
Error
(
t
,
cmd
.
Wait
(),
"Expected to fail due to content-type mismatch"
)
...
...
internal/upstream/routes.go
View file @
d6e2712f
...
...
@@ -142,7 +142,7 @@ func (ro *routeEntry) isMatch(cleanedPath string, req *http.Request) bool {
return
ok
}
func
buildProxy
(
backend
*
url
.
URL
,
version
string
,
rt
http
.
RoundTripper
)
http
.
Handler
{
func
buildProxy
(
backend
*
url
.
URL
,
version
string
,
rt
http
.
RoundTripper
,
cfg
config
.
Config
)
http
.
Handler
{
proxier
:=
proxypkg
.
NewProxy
(
backend
,
version
,
rt
)
return
senddata
.
SendData
(
...
...
@@ -154,7 +154,7 @@ func buildProxy(backend *url.URL, version string, rt http.RoundTripper) http.Han
git
.
SendSnapshot
,
artifacts
.
SendEntry
,
sendurl
.
SendURL
,
imageresizer
.
SendScaledImage
,
imageresizer
.
NewResizer
(
cfg
)
,
)
}
...
...
@@ -170,11 +170,11 @@ func (u *upstream) configureRoutes() {
)
static
:=
&
staticpages
.
Static
{
DocumentRoot
:
u
.
DocumentRoot
}
proxy
:=
buildProxy
(
u
.
Backend
,
u
.
Version
,
u
.
RoundTripper
)
proxy
:=
buildProxy
(
u
.
Backend
,
u
.
Version
,
u
.
RoundTripper
,
u
.
Config
)
cableProxy
:=
proxypkg
.
NewProxy
(
u
.
CableBackend
,
u
.
Version
,
u
.
CableRoundTripper
)
signingTripper
:=
secret
.
NewRoundTripper
(
u
.
RoundTripper
,
u
.
Version
)
signingProxy
:=
buildProxy
(
u
.
Backend
,
u
.
Version
,
signingTripper
)
signingProxy
:=
buildProxy
(
u
.
Backend
,
u
.
Version
,
signingTripper
,
u
.
Config
)
preparers
:=
createUploadPreparers
(
u
.
Config
)
uploadPath
:=
path
.
Join
(
u
.
DocumentRoot
,
"uploads/tmp"
)
...
...
main.go
View file @
d6e2712f
...
...
@@ -157,6 +157,7 @@ func main() {
APIQueueTimeout
:
*
apiQueueTimeout
,
APICILongPollingDuration
:
*
apiCiLongPollingDuration
,
PropagateCorrelationID
:
*
propagateCorrelationID
,
ImageResizerConfig
:
config
.
DefaultImageResizerConfig
,
}
if
*
configFile
!=
""
{
...
...
@@ -167,6 +168,7 @@ func main() {
cfg
.
Redis
=
cfgFromFile
.
Redis
cfg
.
ObjectStorageCredentials
=
cfgFromFile
.
ObjectStorageCredentials
cfg
.
ImageResizerConfig
=
cfgFromFile
.
ImageResizerConfig
if
cfg
.
Redis
!=
nil
{
redis
.
Configure
(
cfg
.
Redis
,
redis
.
DefaultDialFunc
)
...
...
main_test.go
View file @
d6e2712f
...
...
@@ -645,9 +645,10 @@ func testAuthServer(t *testing.T, url *regexp.Regexp, params url.Values, code in
func
newUpstreamConfig
(
authBackend
string
)
*
config
.
Config
{
return
&
config
.
Config
{
Version
:
"123"
,
DocumentRoot
:
testDocumentRoot
,
Backend
:
helper
.
URLMustParse
(
authBackend
),
Version
:
"123"
,
DocumentRoot
:
testDocumentRoot
,
Backend
:
helper
.
URLMustParse
(
authBackend
),
ImageResizerConfig
:
config
.
DefaultImageResizerConfig
,
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment