mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 08:32:16 +01:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3297e41289 | ||
|
|
4e1b347797 | ||
|
|
4602332dbb |
@@ -9,7 +9,7 @@ ARG GOLANG_VERSION
|
||||
FROM golang:$GOLANG_VERSION AS binary-stage
|
||||
|
||||
ARG GOTENBERG_VERSION
|
||||
ENV CGO_ENABLED 0
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
# Define the working directory outside of $GOPATH (we're using go modules).
|
||||
WORKDIR /home
|
||||
@@ -191,12 +191,12 @@ COPY build/fonts.conf /etc/fonts/conf.d/100-gotenberg.conf
|
||||
COPY --from=binary-stage /home/gotenberg /usr/bin/
|
||||
|
||||
# Environment variables required by modules or else.
|
||||
ENV CHROMIUM_BIN_PATH /usr/bin/chromium
|
||||
ENV LIBREOFFICE_BIN_PATH /usr/lib/libreoffice/program/soffice.bin
|
||||
ENV UNOCONVERTER_BIN_PATH /usr/bin/unoconverter
|
||||
ENV PDFTK_BIN_PATH /usr/bin/pdftk
|
||||
ENV QPDF_BIN_PATH /usr/bin/qpdf
|
||||
ENV EXIFTOOL_BIN_PATH /usr/bin/exiftool
|
||||
ENV CHROMIUM_BIN_PATH=/usr/bin/chromium
|
||||
ENV LIBREOFFICE_BIN_PATH=/usr/lib/libreoffice/program/soffice.bin
|
||||
ENV UNOCONVERTER_BIN_PATH=/usr/bin/unoconverter
|
||||
ENV PDFTK_BIN_PATH=/usr/bin/pdftk
|
||||
ENV QPDF_BIN_PATH=/usr/bin/qpdf
|
||||
ENV EXIFTOOL_BIN_PATH=/usr/bin/exiftool
|
||||
|
||||
USER gotenberg
|
||||
WORKDIR /home/gotenberg
|
||||
|
||||
4
go.mod
4
go.mod
@@ -6,7 +6,7 @@ require (
|
||||
github.com/alexliesenfeld/health v0.8.0
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/barasher/go-exiftool v1.10.0
|
||||
github.com/chromedp/cdproto v0.0.0-20240709201219-e202069cc16b
|
||||
github.com/chromedp/cdproto v0.0.0-20240721024200-dac8efcb39ce
|
||||
github.com/chromedp/chromedp v0.9.5
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -55,7 +55,7 @@ require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -12,8 +12,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/cdproto v0.0.0-20240709201219-e202069cc16b h1:U1h0qXjQvrOWOjagZmtDkxg/A4QKkWJyGWoQ3sXt6Vg=
|
||||
github.com/chromedp/cdproto v0.0.0-20240709201219-e202069cc16b/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/cdproto v0.0.0-20240721024200-dac8efcb39ce h1:pvzUsAunw3R7swXkLT6vqv81Awhnds43mbZHAzhn2pQ=
|
||||
github.com/chromedp/cdproto v0.0.0-20240721024200-dac8efcb39ce/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
|
||||
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
@@ -84,8 +84,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
|
||||
@@ -28,6 +28,11 @@ var (
|
||||
// ErrMalformedPageRanges happens if the page ranges option cannot be
|
||||
// interpreted by LibreOffice.
|
||||
ErrMalformedPageRanges = errors.New("page ranges are malformed")
|
||||
|
||||
// ErrCoreDumped happens randomly; sometime a conversion will work as
|
||||
// expected, and some other time the same conversion will fail.
|
||||
// See https://github.com/gotenberg/gotenberg/issues/639.
|
||||
ErrCoreDumped = errors.New("core dumped")
|
||||
)
|
||||
|
||||
// Api is a module which provides a [Uno] to interact with LibreOffice.
|
||||
@@ -365,9 +370,20 @@ func (a *Api) LibreOffice() (Uno, error) {
|
||||
|
||||
// Pdf converts a document to PDF.
|
||||
func (a *Api) Pdf(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options Options) error {
|
||||
return a.supervisor.Run(ctx, logger, func() error {
|
||||
err := a.supervisor.Run(ctx, logger, func() error {
|
||||
return a.libreOffice.pdf(ctx, logger, inputPath, outputPath, options)
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// See https://github.com/gotenberg/gotenberg/issues/639.
|
||||
if errors.Is(err, ErrCoreDumped) {
|
||||
return a.Pdf(ctx, logger, inputPath, outputPath, options)
|
||||
}
|
||||
|
||||
return fmt.Errorf("supervisor run task: %w", err)
|
||||
}
|
||||
|
||||
// Extensions returns the file extensions available for conversions.
|
||||
|
||||
@@ -433,6 +433,13 @@ func TestApi_Pdf(t *testing.T) {
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "ErrCoreDumped",
|
||||
libreOffice: &libreOfficeMock{pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return ErrCoreDumped
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
a := new(Api)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -348,6 +349,12 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP
|
||||
return ErrMalformedPageRanges
|
||||
}
|
||||
|
||||
// We may want to retry in case of a core dumped event.
|
||||
// See https://github.com/gotenberg/gotenberg/issues/639.
|
||||
if strings.Contains(err.Error(), "core dumped") {
|
||||
return ErrCoreDumped
|
||||
}
|
||||
|
||||
// Possible errors:
|
||||
// 1. LibreOffice failed for some reason.
|
||||
// 2. Context done.
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
@@ -33,12 +34,21 @@ func (provider *ProviderMock) LibreOffice() (Uno, error) {
|
||||
|
||||
// libreOfficeMock is a mock for the [libreOffice] interface.
|
||||
type libreOfficeMock struct {
|
||||
errCoreDumpedCount int
|
||||
|
||||
gotenberg.ProcessMock
|
||||
pdfMock func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options Options) error
|
||||
}
|
||||
|
||||
func (b *libreOfficeMock) pdf(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options Options) error {
|
||||
return b.pdfMock(ctx, logger, inputPath, outputPath, options)
|
||||
err := b.pdfMock(ctx, logger, inputPath, outputPath, options)
|
||||
if errors.Is(err, ErrCoreDumped) {
|
||||
b.errCoreDumpedCount += 1
|
||||
}
|
||||
if b.errCoreDumpedCount > 1 {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
|
||||
@@ -42,14 +42,53 @@ func TestProviderMock(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLibreOfficeMock(t *testing.T) {
|
||||
mock := &libreOfficeMock{
|
||||
pdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options Options) error {
|
||||
return nil
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
mock *libreOfficeMock
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "success",
|
||||
mock: &libreOfficeMock{
|
||||
pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
{
|
||||
scenario: "ErrCoreDumped (first call)",
|
||||
mock: &libreOfficeMock{
|
||||
pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return ErrCoreDumped
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "ErrCoreDumped (second call)",
|
||||
mock: func() *libreOfficeMock {
|
||||
m := &libreOfficeMock{
|
||||
pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return ErrCoreDumped
|
||||
},
|
||||
}
|
||||
m.pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||
return m
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.mock.pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||
|
||||
err := mock.pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from libreOfficeMock.pdf, but got: %v", err)
|
||||
if !tc.expectError && err != nil {
|
||||
t.Fatalf("expected no error from libreOfficeMock.pdf but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectError && err == nil {
|
||||
t.Fatal("expected error from libreOfficeMock.pdf but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ ARG DOCKER_REPOSITORY
|
||||
ARG GOTENBERG_VERSION
|
||||
ARG GOLANGCI_LINT_VERSION
|
||||
|
||||
FROM golang:$GOLANG_VERSION-bookworm as golang
|
||||
FROM golang:$GOLANG_VERSION-bookworm AS golang
|
||||
|
||||
# We're extending the Gotenberg's Docker image because our code relies on external
|
||||
# dependencies like Google Chrome, LibreOffice, etc.
|
||||
@@ -11,9 +11,9 @@ FROM $DOCKER_REPOSITORY/gotenberg:$GOTENBERG_VERSION
|
||||
|
||||
USER root
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
|
||||
ENV CGO_ENABLED 1
|
||||
ENV GOPATH=/go
|
||||
ENV PATH=$GOPATH/bin:/usr/local/go/bin:$PATH
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
COPY --from=golang /usr/local/go /usr/local/go
|
||||
|
||||
|
||||
Reference in New Issue
Block a user