fix: LibreOffice newer versions stability (#697)

This commit is contained in:
Julien Neuhart
2023-10-23 17:05:10 +02:00
committed by GitHub
parent 9cc8e16d64
commit 258876d13f
59 changed files with 870 additions and 1383 deletions

View File

@@ -19,11 +19,12 @@ import (
"github.com/chromedp/cdproto/page"
"github.com/chromedp/cdproto/runtime"
"github.com/chromedp/chromedp"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
flag "github.com/spf13/pflag"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
)
func init() {
@@ -68,8 +69,11 @@ var (
// Chromium is a module which provides both an API and routes for converting
// HTML document to PDF.
type Chromium struct {
binPath string
engine gotenberg.PDFEngine
binPath string
engine gotenberg.PDFEngine
fs *gotenberg.FileSystem
logger *zap.Logger
failedStartsThreshold int
userAgent string
incognito bool
@@ -311,6 +315,20 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
mod.binPath = binPath
// Logger.
loggerProvider, err := ctx.Module(new(gotenberg.LoggerProvider))
if err != nil {
return fmt.Errorf("get logger provider: %w", err)
}
logger, err := loggerProvider.(gotenberg.LoggerProvider).Logger(mod)
if err != nil {
return fmt.Errorf("get logger: %w", err)
}
mod.logger = logger
// PDF engine.
provider, err := ctx.Module(new(gotenberg.PDFEngineProvider))
if err != nil {
return fmt.Errorf("get PDF engine provider: %w", err)
@@ -323,6 +341,9 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
mod.engine = engine
// File system.
mod.fs = gotenberg.NewFileSystem()
return nil
}
@@ -412,7 +433,7 @@ func (mod Chromium) Routes() ([]api.Route, error) {
// the end of the conversion.
func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath string, options Options) error {
debug := debugLogger{logger: logger.Named("browser")}
userProfileDirPath := gotenberg.NewDirPath()
userProfileDirPath := mod.fs.NewDirPath()
args := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.CombinedOutput(debug),
@@ -799,7 +820,6 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
evaluate := chromedp.Evaluate(expression, &ok)
err := evaluate.Do(ctx)
if err != nil {
return fmt.Errorf("evaluate: %v: %w", err, ErrInvalidEvaluationExpression)
}
@@ -886,7 +906,7 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
}
}()
file, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY, 0600)
file, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
return fmt.Errorf("open output path: %w", err)
}
@@ -921,13 +941,20 @@ func (mod Chromium) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
activeInstancesCountMu.Unlock()
// Always remove the user profile directory created by Chromium.
go func() {
logger.Debug(fmt.Sprintf("remove user profile directory '%s'", userProfileDirPath))
defer func() {
go func() {
// FIXME: Chromium seems to recreate the user profile directory
// right after its deletion if we do not wait a certain amount
// of time.
time.Sleep(10 * time.Second)
err := os.RemoveAll(userProfileDirPath)
if err != nil {
logger.Error(fmt.Sprintf("remove user profile directory: %s", err))
}
err := os.RemoveAll(userProfileDirPath)
if err != nil {
logger.Error(fmt.Sprintf("remove Chromium's user profile directory: %s", err))
}
logger.Debug(fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath))
}()
}()
if err != nil {

View File

@@ -10,18 +10,11 @@ import (
"time"
"github.com/alexliesenfeld/health"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"go.uber.org/zap"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
)
type ProtoModule struct {
descriptor func() gotenberg.ModuleDescriptor
}
func (mod ProtoModule) Descriptor() gotenberg.ModuleDescriptor {
return mod.descriptor()
}
type ProtoAPI struct {
pdf func(_ context.Context, _ *zap.Logger, _, _ string, _ Options) error
}
@@ -30,28 +23,6 @@ func (mod ProtoAPI) PDF(ctx context.Context, logger *zap.Logger, URL, outputPath
return mod.pdf(ctx, logger, URL, outputPath, options)
}
type ProtoPDFEngineProvider struct {
ProtoModule
pdfEngine func() (gotenberg.PDFEngine, error)
}
func (mod ProtoPDFEngineProvider) PDFEngine() (gotenberg.PDFEngine, error) {
return mod.pdfEngine()
}
type ProtoPDFEngine struct {
merge func(_ context.Context, _ *zap.Logger, _ []string, _ string) error
convert func(_ context.Context, _ *zap.Logger, _, _, _ string) error
}
func (mod ProtoPDFEngine) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
return mod.merge(ctx, logger, inputPaths, outputPath)
}
func (mod ProtoPDFEngine) Convert(ctx context.Context, logger *zap.Logger, format, inputPath, outputPath string) error {
return mod.convert(ctx, logger, format, inputPath, outputPath)
}
func TestDefaultOptions(t *testing.T) {
actual := DefaultOptions()
notExpect := Options{}
@@ -73,11 +44,13 @@ func TestChromium_Descriptor(t *testing.T) {
}
func TestChromium_Provision(t *testing.T) {
for i, tc := range []struct {
for _, tc := range []struct {
scenario string
ctx *gotenberg.Context
expectErr bool
}{
{
scenario: "no logger provider",
ctx: func() *gotenberg.Context {
return gotenberg.NewContext(
gotenberg.ParsedFlags{
@@ -89,12 +62,16 @@ func TestChromium_Provision(t *testing.T) {
expectErr: true,
},
{
scenario: "no logger from logger provider",
ctx: func() *gotenberg.Context {
mod := struct{ ProtoPDFEngineProvider }{}
mod.descriptor = func() gotenberg.ModuleDescriptor {
mod := struct {
gotenberg.ModuleMock
gotenberg.LoggerProviderMock
}{}
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
}
mod.pdfEngine = func() (gotenberg.PDFEngine, error) {
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
return nil, errors.New("foo")
}
@@ -110,13 +87,75 @@ func TestChromium_Provision(t *testing.T) {
expectErr: true,
},
{
scenario: "no PDF engine provider",
ctx: func() *gotenberg.Context {
mod := struct{ ProtoPDFEngineProvider }{}
mod.descriptor = func() gotenberg.ModuleDescriptor {
mod := struct {
gotenberg.ModuleMock
gotenberg.LoggerProviderMock
}{}
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
}
mod.pdfEngine = func() (gotenberg.PDFEngine, error) {
return struct{ ProtoPDFEngine }{}, nil
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
return zap.NewNop(), nil
}
return gotenberg.NewContext(
gotenberg.ParsedFlags{
FlagSet: new(Chromium).Descriptor().FlagSet,
},
[]gotenberg.ModuleDescriptor{
mod.Descriptor(),
},
)
}(),
expectErr: true,
},
{
scenario: "no PDF engine from PDF engine provider",
ctx: func() *gotenberg.Context {
mod := struct {
gotenberg.ModuleMock
gotenberg.LoggerProviderMock
gotenberg.PDFEngineProviderMock
}{}
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
}
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
return zap.NewNop(), nil
}
mod.PDFEngineMock = func() (gotenberg.PDFEngine, error) {
return nil, errors.New("foo")
}
return gotenberg.NewContext(
gotenberg.ParsedFlags{
FlagSet: new(Chromium).Descriptor().FlagSet,
},
[]gotenberg.ModuleDescriptor{
mod.Descriptor(),
},
)
}(),
expectErr: true,
},
{
scenario: "provision success",
ctx: func() *gotenberg.Context {
mod := struct {
gotenberg.ModuleMock
gotenberg.LoggerProviderMock
gotenberg.PDFEngineProviderMock
}{}
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
}
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
return zap.NewNop(), nil
}
mod.PDFEngineMock = func() (gotenberg.PDFEngine, error) {
return gotenberg.PDFEngineMock{}, nil
}
return gotenberg.NewContext(
@@ -134,11 +173,11 @@ func TestChromium_Provision(t *testing.T) {
err := mod.Provision(tc.ctx)
if tc.expectErr && err == nil {
t.Errorf("test %d: expected error but got: %v", i, err)
t.Errorf("test %s: expected error but got: %v", tc.scenario, err)
}
if !tc.expectErr && err != nil {
t.Errorf("test %d: expected no error but got: %v", i, err)
t.Errorf("test %s: expected no error but got: %v", tc.scenario, err)
}
}
}
@@ -643,16 +682,18 @@ func TestChromium_PDF(t *testing.T) {
mod.allowList = tc.allowList
mod.denyList = tc.denyList
mod.disableJavaScript = tc.disableJavaScript
mod.fs = gotenberg.NewFileSystem()
outputDir, err := gotenberg.MkdirAll()
ctxFs := gotenberg.NewFileSystem()
outputDir, err := ctxFs.MkdirAll()
if err != nil {
t.Fatalf("test %s: expected error but got: %v", tc.name, err)
}
defer func() {
err := os.RemoveAll(outputDir)
err := os.RemoveAll(ctxFs.WorkingDirPath())
if err != nil {
t.Fatalf("test %s: expected no error but got: %v", tc.name, err)
t.Fatalf("test %s: expected no error while cleaning up but got: %v", tc.name, err)
}
}()
@@ -678,9 +719,5 @@ func TestChromium_PDF(t *testing.T) {
// Interface guards.
var (
_ gotenberg.Module = (*ProtoModule)(nil)
_ API = (*ProtoAPI)(nil)
_ gotenberg.PDFEngineProvider = (*ProtoPDFEngineProvider)(nil)
_ gotenberg.Module = (*ProtoPDFEngineProvider)(nil)
_ gotenberg.PDFEngine = (*ProtoPDFEngine)(nil)
_ API = (*ProtoAPI)(nil)
)

View File

@@ -43,7 +43,6 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
if allow {
req := fetch.ContinueRequest(e.RequestID)
err := req.Do(executorCtx)
if err != nil {
logger.Error(fmt.Sprintf("continue request: %s", err))
}
@@ -53,7 +52,6 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied)
err := req.Do(executorCtx)
if err != nil {
logger.Error(fmt.Sprintf("fail request: %s", err))
}

View File

@@ -12,12 +12,13 @@ import (
"strings"
"time"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
"github.com/labstack/echo/v4"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday/v2"
"go.uber.org/multierr"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
)
// FormDataChromiumPDFOptions creates Options form the form data. Fallback to
@@ -163,7 +164,6 @@ func convertURLRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
return nil
}).
Validate()
if err != nil {
return fmt.Errorf("validate form data: %w", err)
}
@@ -197,7 +197,6 @@ func convertHTMLRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
MandatoryPath("index.html", &inputPath).
String("pdfFormat", &PDFformat, "").
Validate()
if err != nil {
return fmt.Errorf("validate form data: %w", err)
}
@@ -236,7 +235,6 @@ func convertMarkdownRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
MandatoryPaths([]string{".md"}, &markdownPaths).
String("pdfFormat", &PDFformat, "").
Validate()
if err != nil {
return fmt.Errorf("validate form data: %w", err)
}
@@ -284,7 +282,6 @@ func convertMarkdownRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
return template.HTML(sanitized), nil
},
}).ParseFiles(inputPath)
if err != nil {
return fmt.Errorf("parse template file: %w", err)
}
@@ -308,7 +305,7 @@ func convertMarkdownRoute(chromium API, engine gotenberg.PDFEngine) api.Route {
inputPath = ctx.GeneratePath(".html")
err = os.WriteFile(inputPath, buffer.Bytes(), 0600)
err = os.WriteFile(inputPath, buffer.Bytes(), 0o600)
if err != nil {
return fmt.Errorf("write template result: %w", err)
}

View File

@@ -8,10 +8,11 @@ import (
"reflect"
"testing"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
"github.com/labstack/echo/v4"
"go.uber.org/zap"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
)
func TestFormDataChromiumPDFOptions(t *testing.T) {
@@ -588,8 +589,7 @@ func TestConvertMarkdownHandler(t *testing.T) {
} {
func() {
if tc.outputDir != "" {
err := os.MkdirAll(tc.outputDir, 0755)
err := os.MkdirAll(tc.outputDir, 0o755)
if err != nil {
t.Fatalf("test %d: expected error but got: %v", i, err)
}
@@ -784,8 +784,8 @@ func TestConvertURL(t *testing.T) {
return chromiumAPI
}(),
engine: func() gotenberg.PDFEngine {
return &ProtoPDFEngine{
convert: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
return &gotenberg.PDFEngineMock{
ConvertMock: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
return gotenberg.ErrPDFFormatNotAvailable
},
}
@@ -807,8 +807,8 @@ func TestConvertURL(t *testing.T) {
return chromiumAPI
}(),
engine: func() gotenberg.PDFEngine {
return &ProtoPDFEngine{
convert: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
return &gotenberg.PDFEngineMock{
ConvertMock: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
return errors.New("foo")
},
}
@@ -828,8 +828,8 @@ func TestConvertURL(t *testing.T) {
return chromiumAPI
}(),
engine: func() gotenberg.PDFEngine {
return &ProtoPDFEngine{
convert: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
return &gotenberg.PDFEngineMock{
ConvertMock: func(_ context.Context, _ *zap.Logger, _, _, _ string) error {
return nil
},
}