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
ea56e1bd
Commit
ea56e1bd
authored
Sep 24, 2020
by
Matthias Käppler
Committed by
Jacob Vosmaer
Sep 24, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Image scaler binary with pure golang lib
parent
07bbad76
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
150 additions
and
84 deletions
+150
-84
.gitignore
.gitignore
+1
-0
.gitlab-ci.yml
.gitlab-ci.yml
+1
-1
Makefile
Makefile
+6
-2
README.md
README.md
+0
-22
changelogs/unreleased/mk-golang-only-image-scaler.yml
changelogs/unreleased/mk-golang-only-image-scaler.yml
+5
-0
cmd/gitlab-resize-image/main.go
cmd/gitlab-resize-image/main.go
+45
-0
go.mod
go.mod
+1
-0
go.sum
go.sum
+4
-0
internal/imageresizer/image_resizer.go
internal/imageresizer/image_resizer.go
+52
-42
internal/imageresizer/image_resizer_test.go
internal/imageresizer/image_resizer_test.go
+33
-15
internal/testhelper/testhelper.go
internal/testhelper/testhelper.go
+1
-1
main_test.go
main_test.go
+1
-1
No files found.
.gitignore
View file @
ea56e1bd
...
@@ -2,6 +2,7 @@ testdata/data
...
@@ -2,6 +2,7 @@ testdata/data
testdata/scratch
testdata/scratch
testdata/public
testdata/public
/gitlab-workhorse
/gitlab-workhorse
/gitlab-resize-image
/gitlab-zip-cat
/gitlab-zip-cat
/gitlab-zip-metadata
/gitlab-zip-metadata
/_build
/_build
...
...
.gitlab-ci.yml
View file @
ea56e1bd
...
@@ -38,7 +38,7 @@ changelog:
...
@@ -38,7 +38,7 @@ changelog:
GITALY_ADDRESS
:
"
tcp://gitaly:8075"
GITALY_ADDRESS
:
"
tcp://gitaly:8075"
script
:
script
:
-
go version
-
go version
-
apt-get update && apt-get -y install libimage-exiftool-perl
graphicsmagick
-
apt-get update && apt-get -y install libimage-exiftool-perl
-
make test
-
make test
test using go 1.13
:
test using go 1.13
:
...
...
Makefile
View file @
ea56e1bd
...
@@ -11,7 +11,7 @@ VERSION_STRING := v$(shell cat VERSION)
...
@@ -11,7 +11,7 @@ VERSION_STRING := v$(shell cat VERSION)
endif
endif
BUILD_TIME
:=
$(
shell
date
-u
+%Y%m%d.%H%M%S
)
BUILD_TIME
:=
$(
shell
date
-u
+%Y%m%d.%H%M%S
)
GOBUILD
:=
go build
-ldflags
"-X main.Version=
$(VERSION_STRING)
-X main.BuildTime=
$(BUILD_TIME)
"
GOBUILD
:=
go build
-ldflags
"-X main.Version=
$(VERSION_STRING)
-X main.BuildTime=
$(BUILD_TIME)
"
EXE_ALL
:=
gitlab-zip-cat gitlab-zip-metadata gitlab-workhorse
EXE_ALL
:=
gitlab-
resize-image gitlab-
zip-cat gitlab-zip-metadata gitlab-workhorse
INSTALL
:=
install
INSTALL
:=
install
BUILD_TAGS
:=
tracer_static tracer_static_jaeger continuous_profiler_stackdriver
BUILD_TAGS
:=
tracer_static tracer_static_jaeger continuous_profiler_stackdriver
...
@@ -40,6 +40,10 @@ $(TARGET_SETUP):
...
@@ -40,6 +40,10 @@ $(TARGET_SETUP):
mkdir
-p
"
$(TARGET_DIR)
"
mkdir
-p
"
$(TARGET_DIR)
"
touch
"
$(TARGET_SETUP)
"
touch
"
$(TARGET_SETUP)
"
gitlab-resize-image
:
$(TARGET_SETUP) $(shell find cmd/gitlab-resize-image/ -name '*.go')
$(
call
message,Building
$@
)
$(GOBUILD)
-tags
"
$(BUILD_TAGS)
"
-o
$(BUILD_DIR)
/
$@
$(PKG)
/cmd/
$@
gitlab-zip-cat
:
$(TARGET_SETUP) $(shell find cmd/gitlab-zip-cat/ -name '*.go')
gitlab-zip-cat
:
$(TARGET_SETUP) $(shell find cmd/gitlab-zip-cat/ -name '*.go')
$(
call
message,Building
$@
)
$(
call
message,Building
$@
)
$(GOBUILD)
-tags
"
$(BUILD_TAGS)
"
-o
$(BUILD_DIR)
/
$@
$(PKG)
/cmd/
$@
$(GOBUILD)
-tags
"
$(BUILD_TAGS)
"
-o
$(BUILD_DIR)
/
$@
$(PKG)
/cmd/
$@
...
@@ -53,7 +57,7 @@ gitlab-workhorse: $(TARGET_SETUP) $(shell find . -name '*.go' | grep -v '^\./_')
...
@@ -53,7 +57,7 @@ gitlab-workhorse: $(TARGET_SETUP) $(shell find . -name '*.go' | grep -v '^\./_')
$(GOBUILD)
-tags
"
$(BUILD_TAGS)
"
-o
$(BUILD_DIR)
/
$@
$(PKG)
$(GOBUILD)
-tags
"
$(BUILD_TAGS)
"
-o
$(BUILD_DIR)
/
$@
$(PKG)
.PHONY
:
install
.PHONY
:
install
install
:
gitlab-workhorse gitlab-zip-cat gitlab-zip-metadata
install
:
gitlab-workhorse gitlab-
resize-image gitlab-
zip-cat gitlab-zip-metadata
$(
call
message,
$@
)
$(
call
message,
$@
)
mkdir
-p
$(DESTDIR)$(PREFIX)
/bin/
mkdir
-p
$(DESTDIR)$(PREFIX)
/bin/
cd
$(BUILD_DIR)
&&
$(INSTALL)
gitlab-workhorse gitlab-zip-cat gitlab-zip-metadata
$(DESTDIR)$(PREFIX)
/bin/
cd
$(BUILD_DIR)
&&
$(INSTALL)
gitlab-workhorse gitlab-zip-cat gitlab-zip-metadata
$(DESTDIR)$(PREFIX)
/bin/
...
...
README.md
View file @
ea56e1bd
...
@@ -212,28 +212,6 @@ images. If you installed GitLab:
...
@@ -212,28 +212,6 @@ images. If you installed GitLab:
sudo yum install perl-Image-ExifTool
sudo yum install perl-Image-ExifTool
```
```
### GraphicsMagick (**experimental**)
Workhorse has an experimental feature that allows us to rescale images on-the-fly.
If you do not run Workhorse in a container where the
`gm`
tool is already installed,
you will have to install it on your host machine instead:
#### macOS
```
sh
brew
install
graphicsmagick
```
#### Debian/Ubuntu
```
sh
sudo
apt-get
install
graphicsmagick
```
For installation on other platforms, please consult http://www.graphicsmagick.org/README.html.
Note that Omnibus containers already come with
`gm`
installed.
## Error tracking
## Error tracking
GitLab-Workhorse supports remote error tracking with
GitLab-Workhorse supports remote error tracking with
...
...
changelogs/unreleased/mk-golang-only-image-scaler.yml
0 → 100644
View file @
ea56e1bd
---
title
:
Switch image scaler to a Go-only solution
merge_request
:
603
author
:
type
:
changed
cmd/gitlab-resize-image/main.go
0 → 100644
View file @
ea56e1bd
package
main
import
(
"fmt"
"image"
"mime"
"os"
"strconv"
"github.com/disintegration/imaging"
)
func
main
()
{
if
err
:=
_main
();
err
!=
nil
{
fmt
.
Fprintf
(
os
.
Stderr
,
"%s: fatal: %v
\n
"
,
os
.
Args
[
0
],
err
)
os
.
Exit
(
1
)
}
}
func
_main
()
error
{
widthParam
:=
os
.
Getenv
(
"GL_RESIZE_IMAGE_WIDTH"
)
requestedWidth
,
err
:=
strconv
.
Atoi
(
widthParam
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"GL_RESIZE_IMAGE_WIDTH: %w"
,
err
)
}
contentType
:=
os
.
Getenv
(
"GL_RESIZE_IMAGE_CONTENT_TYPE"
)
if
contentType
==
""
{
return
fmt
.
Errorf
(
"GL_RESIZE_IMAGE_CONTENT_TYPE is empty"
)
}
src
,
extension
,
err
:=
image
.
Decode
(
os
.
Stdin
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"decode: %w"
,
err
)
}
if
detectedType
:=
mime
.
TypeByExtension
(
"."
+
extension
);
detectedType
!=
contentType
{
return
fmt
.
Errorf
(
"MIME types do not match; requested: %s; actual: %s"
,
contentType
,
detectedType
)
}
format
,
err
:=
imaging
.
FormatFromExtension
(
extension
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"find imaging format: %w"
,
err
)
}
image
:=
imaging
.
Resize
(
src
,
requestedWidth
,
0
,
imaging
.
Lanczos
)
return
imaging
.
Encode
(
os
.
Stdout
,
image
,
format
)
}
go.mod
View file @
ea56e1bd
...
@@ -9,6 +9,7 @@ require (
...
@@ -9,6 +9,7 @@ require (
github.com/alecthomas/chroma v0.7.3
github.com/alecthomas/chroma v0.7.3
github.com/aws/aws-sdk-go v1.31.13
github.com/aws/aws-sdk-go v1.31.13
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/disintegration/imaging v1.6.2
github.com/getsentry/raven-go v0.1.2
github.com/getsentry/raven-go v0.1.2
github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721
github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721
github.com/golang/protobuf v1.4.2
github.com/golang/protobuf v1.4.2
...
...
go.sum
View file @
ea56e1bd
...
@@ -155,6 +155,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
...
@@ -155,6 +155,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
...
@@ -536,6 +538,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
...
@@ -536,6 +538,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 h1:00BeQWmeaGazuOrq8Q5K5d3/cHaGuFrZzpaHBXfrsUA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 h1:00BeQWmeaGazuOrq8Q5K5d3/cHaGuFrZzpaHBXfrsUA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
...
...
internal/imageresizer/image_resizer.go
View file @
ea56e1bd
...
@@ -8,6 +8,7 @@ import (
...
@@ -8,6 +8,7 @@ import (
"net/http"
"net/http"
"os"
"os"
"os/exec"
"os/exec"
"strconv"
"strings"
"strings"
"sync/atomic"
"sync/atomic"
"syscall"
"syscall"
...
@@ -40,7 +41,10 @@ type processCounter struct {
...
@@ -40,7 +41,10 @@ type processCounter struct {
var
numScalerProcs
processCounter
var
numScalerProcs
processCounter
const
maxImageScalerProcs
=
100
const
(
maxImageScalerProcs
=
100
maxAllowedFileSizeBytes
=
250
*
1000
// 250kB
)
// Images might be located remotely in object storage, in which case we need to stream
// Images might be located remotely in object storage, in which case we need to stream
// it via http(s)
// it via http(s)
...
@@ -88,7 +92,7 @@ func init() {
...
@@ -88,7 +92,7 @@ func init() {
prometheus
.
MustRegister
(
imageResizeCompleted
)
prometheus
.
MustRegister
(
imageResizeCompleted
)
}
}
// This Injecter forks into
graphicsmagick
to resize an image identified by path or URL
// 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
// and streams the resized image back to the client
func
(
r
*
resizer
)
Inject
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
,
paramsData
string
)
{
func
(
r
*
resizer
)
Inject
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
,
paramsData
string
)
{
start
:=
time
.
Now
()
start
:=
time
.
Now
()
...
@@ -101,7 +105,7 @@ func (r *resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
...
@@ -101,7 +105,7 @@ func (r *resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
return
return
}
}
sourceImageReader
,
file
s
ize
,
err
:=
openSourceImage
(
params
.
Location
)
sourceImageReader
,
file
S
ize
,
err
:=
openSourceImage
(
params
.
Location
)
if
err
!=
nil
{
if
err
!=
nil
{
// This means we cannot even read the input image; fail fast.
// This means we cannot even read the input image; fail fast.
helper
.
Fail500
(
w
,
req
,
fmt
.
Errorf
(
"ImageResizer: Failed opening image data stream: %v"
,
err
))
helper
.
Fail500
(
w
,
req
,
fmt
.
Errorf
(
"ImageResizer: Failed opening image data stream: %v"
,
err
))
...
@@ -115,13 +119,13 @@ func (r *resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
...
@@ -115,13 +119,13 @@ func (r *resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
"duration_s"
:
time
.
Since
(
start
)
.
Seconds
(),
"duration_s"
:
time
.
Since
(
start
)
.
Seconds
(),
"target_width"
:
params
.
Width
,
"target_width"
:
params
.
Width
,
"content_type"
:
params
.
ContentType
,
"content_type"
:
params
.
ContentType
,
"original_filesize"
:
file
s
ize
,
"original_filesize"
:
file
S
ize
,
}
}
}
}
// We first attempt to rescale the image; if this should fail for any reason,
we
// We first attempt to rescale the image; if this should fail for any reason,
imageReader
//
simply fail over to rendering out the original image
unchanged.
//
will point to the original image, i.e. we render it
unchanged.
imageReader
,
resizeCmd
,
err
:=
tryResizeImage
(
req
,
sourceImageReader
,
logger
.
Writer
(),
params
)
imageReader
,
resizeCmd
,
err
:=
tryResizeImage
(
req
,
sourceImageReader
,
logger
.
Writer
(),
params
,
fileSize
)
if
err
!=
nil
{
if
err
!=
nil
{
// something failed, but we can still write out the original image, do don't return early
// something failed, but we can still write out the original image, do don't return early
helper
.
LogErrorWithFields
(
req
,
err
,
*
logFields
(
0
))
helper
.
LogErrorWithFields
(
req
,
err
,
*
logFields
(
0
))
...
@@ -130,24 +134,39 @@ func (r *resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
...
@@ -130,24 +134,39 @@ func (r *resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
imageResizeCompleted
.
Inc
()
imageResizeCompleted
.
Inc
()
w
.
Header
()
.
Del
(
"Content-Length"
)
w
.
Header
()
.
Del
(
"Content-Length"
)
bytesWritten
,
err
:=
io
.
Copy
(
w
,
imageReader
)
bytesWritten
,
err
:=
serveImage
(
imageReader
,
w
,
resizeCmd
)
if
err
!=
nil
{
if
err
!=
nil
{
handleFailedCommand
(
w
,
req
,
bytesWritten
,
err
,
logFields
(
bytesWritten
))
handleFailedCommand
(
w
,
req
,
bytesWritten
,
err
,
logFields
(
bytesWritten
))
}
else
if
err
=
resizeCmd
.
Wait
();
err
!=
nil
{
return
handleFailedCommand
(
w
,
req
,
bytesWritten
,
err
,
logFields
(
bytesWritten
))
}
else
{
logger
.
WithFields
(
*
logFields
(
bytesWritten
))
.
Printf
(
"ImageResizer: Success"
)
}
}
logger
.
WithFields
(
*
logFields
(
bytesWritten
))
.
Printf
(
"ImageResizer: Success"
)
}
}
func
handleFailedCommand
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
,
bytesWritten
int64
,
err
error
,
logFields
*
log
.
Fields
)
{
// Streams image data from the given reader to the given writer and returns the number of bytes written.
// Errors are either served to the caller or merely logged, depending on whether any image data had
// already been transmitted or not.
func
serveImage
(
r
io
.
Reader
,
w
io
.
Writer
,
resizeCmd
*
exec
.
Cmd
)
(
int64
,
error
)
{
bytesWritten
,
err
:=
io
.
Copy
(
w
,
r
)
if
err
!=
nil
{
if
err
!=
nil
{
if
bytesWritten
<=
0
{
return
bytesWritten
,
err
helper
.
Fail500
(
w
,
req
,
err
)
}
}
else
{
helper
.
LogErrorWithFields
(
req
,
err
,
*
logFields
)
if
resizeCmd
!=
nil
{
if
err
=
resizeCmd
.
Wait
();
err
!=
nil
{
return
bytesWritten
,
err
}
}
}
}
return
bytesWritten
,
nil
}
func
handleFailedCommand
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
,
bytesWritten
int64
,
err
error
,
logFields
*
log
.
Fields
)
{
if
bytesWritten
<=
0
{
helper
.
Fail500
(
w
,
req
,
err
)
}
else
{
helper
.
LogErrorWithFields
(
req
,
err
,
*
logFields
)
}
}
}
func
(
r
*
resizer
)
unpackParameters
(
paramsData
string
)
(
*
resizeParams
,
error
)
{
func
(
r
*
resizer
)
unpackParameters
(
paramsData
string
)
(
*
resizeParams
,
error
)
{
...
@@ -161,14 +180,18 @@ func (r *resizer) unpackParameters(paramsData string) (*resizeParams, error) {
...
@@ -161,14 +180,18 @@ func (r *resizer) unpackParameters(paramsData string) (*resizeParams, error) {
}
}
if
params
.
ContentType
==
""
{
if
params
.
ContentType
==
""
{
return
nil
,
fmt
.
Errorf
(
"ImageResizer:
Image MIME t
ype must be set"
)
return
nil
,
fmt
.
Errorf
(
"ImageResizer:
ContentT
ype must be set"
)
}
}
return
&
params
,
nil
return
&
params
,
nil
}
}
// Attempts to rescale the given image data, or in case of errors, falls back to the original image.
// 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
)
(
io
.
Reader
,
*
exec
.
Cmd
,
error
)
{
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
)
}
if
!
numScalerProcs
.
tryIncrement
()
{
if
!
numScalerProcs
.
tryIncrement
()
{
return
r
,
nil
,
fmt
.
Errorf
(
"ImageResizer: too many running scaler processes"
)
return
r
,
nil
,
fmt
.
Errorf
(
"ImageResizer: too many running scaler processes"
)
}
}
...
@@ -179,35 +202,22 @@ func tryResizeImage(req *http.Request, r io.Reader, errorWriter io.Writer, param
...
@@ -179,35 +202,22 @@ func tryResizeImage(req *http.Request, r io.Reader, errorWriter io.Writer, param
numScalerProcs
.
decrement
()
numScalerProcs
.
decrement
()
}()
}()
width
:=
params
.
Width
resizeCmd
,
resizedImageReader
,
err
:=
startResizeImageCommand
(
ctx
,
r
,
errorWriter
,
params
)
gmFileSpec
:=
determineFilePrefix
(
params
.
ContentType
)
if
gmFileSpec
==
""
{
return
r
,
nil
,
fmt
.
Errorf
(
"ImageResizer: unexpected MIME type: %s"
,
params
.
ContentType
)
}
resizeCmd
,
resizedImageReader
,
err
:=
startResizeImageCommand
(
ctx
,
r
,
errorWriter
,
width
,
gmFileSpec
)
if
err
!=
nil
{
if
err
!=
nil
{
return
r
,
nil
,
fmt
.
Errorf
(
"ImageResizer: failed forking into
graphicsmagick"
)
return
r
,
nil
,
fmt
.
Errorf
(
"ImageResizer: failed forking into
scaler process: %w"
,
err
)
}
}
return
resizedImageReader
,
resizeCmd
,
nil
return
resizedImageReader
,
resizeCmd
,
nil
}
}
func
determineFilePrefix
(
contentType
string
)
string
{
func
startResizeImageCommand
(
ctx
context
.
Context
,
imageReader
io
.
Reader
,
errorWriter
io
.
Writer
,
params
*
resizeParams
)
(
*
exec
.
Cmd
,
io
.
ReadCloser
,
error
)
{
switch
contentType
{
cmd
:=
exec
.
CommandContext
(
ctx
,
"gitlab-resize-image"
)
case
"image/png"
:
return
"png:"
case
"image/jpeg"
:
return
"jpg:"
default
:
return
""
}
}
func
startResizeImageCommand
(
ctx
context
.
Context
,
imageReader
io
.
Reader
,
errorWriter
io
.
Writer
,
width
uint
,
gmFileSpec
string
)
(
*
exec
.
Cmd
,
io
.
ReadCloser
,
error
)
{
cmd
:=
exec
.
CommandContext
(
ctx
,
"gm"
,
"convert"
,
"-resize"
,
fmt
.
Sprintf
(
"%dx"
,
width
),
gmFileSpec
+
"-"
,
"-"
)
cmd
.
Stdin
=
imageReader
cmd
.
Stdin
=
imageReader
cmd
.
Stderr
=
errorWriter
cmd
.
Stderr
=
errorWriter
cmd
.
SysProcAttr
=
&
syscall
.
SysProcAttr
{
Setpgid
:
true
}
cmd
.
SysProcAttr
=
&
syscall
.
SysProcAttr
{
Setpgid
:
true
}
cmd
.
Env
=
[]
string
{
"GL_RESIZE_IMAGE_WIDTH="
+
strconv
.
Itoa
(
int
(
params
.
Width
)),
"GL_RESIZE_IMAGE_CONTENT_TYPE="
+
params
.
ContentType
,
}
stdout
,
err
:=
cmd
.
StdoutPipe
()
stdout
,
err
:=
cmd
.
StdoutPipe
()
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -227,13 +237,13 @@ func isURL(location string) bool {
...
@@ -227,13 +237,13 @@ func isURL(location string) bool {
func
openSourceImage
(
location
string
)
(
io
.
ReadCloser
,
int64
,
error
)
{
func
openSourceImage
(
location
string
)
(
io
.
ReadCloser
,
int64
,
error
)
{
if
isURL
(
location
)
{
if
isURL
(
location
)
{
return
openFromU
rl
(
location
)
return
openFromU
RL
(
location
)
}
}
return
openFromFile
(
location
)
return
openFromFile
(
location
)
}
}
func
openFromU
rl
(
location
string
)
(
io
.
ReadCloser
,
int64
,
error
)
{
func
openFromU
RL
(
location
string
)
(
io
.
ReadCloser
,
int64
,
error
)
{
res
,
err
:=
httpClient
.
Get
(
location
)
res
,
err
:=
httpClient
.
Get
(
location
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
0
,
err
return
nil
,
0
,
err
...
...
internal/imageresizer/image_resizer_test.go
View file @
ea56e1bd
package
imageresizer
package
imageresizer
import
(
import
(
"bytes"
"encoding/base64"
"encoding/base64"
"encoding/json"
"encoding/json"
"net/http"
"net/http"
"os"
"os"
"testing"
"testing"
"gitlab.com/gitlab-org/labkit/log"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/testhelper"
)
)
var
r
=
resizer
{}
var
r
=
resizer
{}
func
TestMain
(
m
*
testing
.
M
)
{
if
err
:=
testhelper
.
BuildExecutables
();
err
!=
nil
{
log
.
WithError
(
err
)
.
Fatal
()
}
os
.
Exit
(
m
.
Run
())
}
func
TestUnpackParametersReturnsParamsInstanceForValidInput
(
t
*
testing
.
T
)
{
func
TestUnpackParametersReturnsParamsInstanceForValidInput
(
t
*
testing
.
T
)
{
inParams
:=
resizeParams
{
Location
:
"/path/to/img"
,
Width
:
64
,
ContentType
:
"image/png"
}
inParams
:=
resizeParams
{
Location
:
"/path/to/img"
,
Width
:
64
,
ContentType
:
"image/png"
}
...
@@ -37,19 +50,13 @@ func TestUnpackParametersReturnsErrorWhenContentTypeBlank(t *testing.T) {
...
@@ -37,19 +50,13 @@ func TestUnpackParametersReturnsErrorWhenContentTypeBlank(t *testing.T) {
require
.
Error
(
t
,
err
,
"expected error when ContentType is blank"
)
require
.
Error
(
t
,
err
,
"expected error when ContentType is blank"
)
}
}
func
TestDetermineFilePrefixFromMimeType
(
t
*
testing
.
T
)
{
require
.
Equal
(
t
,
"png:"
,
determineFilePrefix
(
"image/png"
))
require
.
Equal
(
t
,
"jpg:"
,
determineFilePrefix
(
"image/jpeg"
))
require
.
Equal
(
t
,
""
,
determineFilePrefix
(
"unsupported"
))
}
func
TestTryResizeImageSuccess
(
t
*
testing
.
T
)
{
func
TestTryResizeImageSuccess
(
t
*
testing
.
T
)
{
inParams
:=
resizeParams
{
Location
:
"/path/to/img"
,
Width
:
64
,
ContentType
:
"image/png"
}
inParams
:=
resizeParams
{
Location
:
"/path/to/img"
,
Width
:
64
,
ContentType
:
"image/png"
}
inFile
:=
testImage
(
t
)
inFile
:=
testImage
(
t
)
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
reader
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
)
reader
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
maxAllowedFileSizeBytes
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
cmd
)
require
.
NotNil
(
t
,
cmd
)
...
@@ -57,29 +64,40 @@ func TestTryResizeImageSuccess(t *testing.T) {
...
@@ -57,29 +64,40 @@ func TestTryResizeImageSuccess(t *testing.T) {
require
.
NotEqual
(
t
,
inFile
,
reader
)
require
.
NotEqual
(
t
,
inFile
,
reader
)
}
}
func
TestTryResizeImage
FailsOverToOriginalImageWhenContentTypeNotSupported
(
t
*
testing
.
T
)
{
func
TestTryResizeImage
SkipsResizeWhenSourceImageTooLarge
(
t
*
testing
.
T
)
{
inParams
:=
resizeParams
{
Location
:
"/path/to/img"
,
Width
:
64
,
ContentType
:
"
not supported
"
}
inParams
:=
resizeParams
{
Location
:
"/path/to/img"
,
Width
:
64
,
ContentType
:
"
image/png
"
}
inFile
:=
testImage
(
t
)
inFile
:=
testImage
(
t
)
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
reader
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
)
reader
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
maxAllowedFileSizeBytes
+
1
)
require
.
Error
(
t
,
err
)
require
.
Error
(
t
,
err
)
require
.
Nil
(
t
,
cmd
)
require
.
Nil
(
t
,
cmd
)
require
.
Equal
(
t
,
inFile
,
reader
)
require
.
Equal
(
t
,
inFile
,
reader
,
"Expected output streams to match"
)
}
}
func
Test
GraphicsMagick
FailsWhenContentTypeNotMatchingFileContents
(
t
*
testing
.
T
)
{
func
Test
TryResizeImage
FailsWhenContentTypeNotMatchingFileContents
(
t
*
testing
.
T
)
{
inParams
:=
resizeParams
{
Location
:
"/path/to/img"
,
Width
:
64
,
ContentType
:
"image/jpeg"
}
inParams
:=
resizeParams
{
Location
:
"/path/to/img"
,
Width
:
64
,
ContentType
:
"image/jpeg"
}
inFile
:=
testImage
(
t
)
// this is
PNG file; gm
should fail fast in this case
inFile
:=
testImage
(
t
)
// this is
a PNG file; the image scaler
should fail fast in this case
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
req
,
err
:=
http
.
NewRequest
(
"GET"
,
"/foo"
,
nil
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
_
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
)
_
,
cmd
,
err
:=
tryResizeImage
(
req
,
inFile
,
os
.
Stderr
,
&
inParams
,
maxAllowedFileSizeBytes
)
require
.
NoError
(
t
,
err
)
require
.
Error
(
t
,
cmd
.
Wait
(),
"Expected to fail due to content-type mismatch"
)
}
func
TestServeImage
(
t
*
testing
.
T
)
{
inFile
:=
testImage
(
t
)
var
writer
bytes
.
Buffer
bytesWritten
,
err
:=
serveImage
(
inFile
,
&
writer
,
nil
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Error
(
t
,
cmd
.
Wait
())
require
.
Greater
(
t
,
bytesWritten
,
int64
(
0
))
require
.
Equal
(
t
,
int64
(
len
(
writer
.
Bytes
())),
bytesWritten
)
}
}
// The Rails applications sends a Base64 encoded JSON string carrying
// The Rails applications sends a Base64 encoded JSON string carrying
...
...
internal/testhelper/testhelper.go
View file @
ea56e1bd
...
@@ -74,7 +74,7 @@ func TestServerWithHandler(url *regexp.Regexp, handler http.HandlerFunc) *httpte
...
@@ -74,7 +74,7 @@ func TestServerWithHandler(url *regexp.Regexp, handler http.HandlerFunc) *httpte
}))
}))
}
}
var
workhorseExecutables
=
[]
string
{
"gitlab-workhorse"
,
"gitlab-zip-cat"
,
"gitlab-zip-metadata"
}
var
workhorseExecutables
=
[]
string
{
"gitlab-workhorse"
,
"gitlab-zip-cat"
,
"gitlab-zip-metadata"
,
"gitlab-resize-image"
}
func
BuildExecutables
()
error
{
func
BuildExecutables
()
error
{
rootDir
:=
RootDir
()
rootDir
:=
RootDir
()
...
...
main_test.go
View file @
ea56e1bd
...
@@ -377,7 +377,7 @@ func TestImageResizing(t *testing.T) {
...
@@ -377,7 +377,7 @@ func TestImageResizing(t *testing.T) {
resp
,
body
,
err
:=
doSendDataRequest
(
resourcePath
,
"send-scaled-img"
,
jsonParams
)
resp
,
body
,
err
:=
doSendDataRequest
(
resourcePath
,
"send-scaled-img"
,
jsonParams
)
require
.
NoError
(
t
,
err
,
"send resize request"
)
require
.
NoError
(
t
,
err
,
"send resize request"
)
require
.
Equal
(
t
,
200
,
resp
.
StatusCode
,
"GET %q:
status code"
,
resourcePath
)
require
.
Equal
(
t
,
200
,
resp
.
StatusCode
,
"GET %q:
body: %s"
,
resourcePath
,
body
)
img
,
err
:=
png
.
Decode
(
bytes
.
NewReader
(
body
))
img
,
err
:=
png
.
Decode
(
bytes
.
NewReader
(
body
))
require
.
NoError
(
t
,
err
,
"decode resized image"
)
require
.
NoError
(
t
,
err
,
"decode resized image"
)
...
...
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