mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
new attribute interpreter for commands (#7)
This commit is contained in:
@@ -17,12 +17,15 @@ commands:
|
||||
# Unlike others commands' templates, you have access to FilesPaths instead of FilePath: it gathers all PDF files which should be merged.
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
|
||||
conversions:
|
||||
|
||||
# The command template: you have access to FilePath and ResultFilePath variables.
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
# The binary which will call the command.
|
||||
interpreter: "/bin/sh -c"
|
||||
# Duration in seconds after which the command will be killed if it has not finished.
|
||||
timeout: 30
|
||||
# Files with the following extensions will be converted by the current command.
|
||||
@@ -30,12 +33,14 @@ commands:
|
||||
- ".md"
|
||||
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -8,18 +8,22 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -5,22 +5,27 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -5,18 +5,22 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -5,18 +5,22 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 0
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -5,18 +5,22 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 0
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 0
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 0
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 0
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -5,18 +5,22 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -5,18 +5,22 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -5,18 +5,22 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -5,18 +5,22 @@ logs:
|
||||
commands:
|
||||
merge:
|
||||
template: "pdftk {{ range $filePath := FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
conversions:
|
||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".md"
|
||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".html"
|
||||
- ".htm"
|
||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
||||
interpreter: "/bin/sh -c"
|
||||
timeout: 30
|
||||
extensions:
|
||||
- ".doc"
|
||||
|
||||
@@ -8,6 +8,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -29,6 +30,8 @@ type (
|
||||
Command struct {
|
||||
// Template is the data-driven template of the command.
|
||||
Template *template.Template
|
||||
// The binary which will call the command.
|
||||
Interpreter []string
|
||||
// Timeout is the duration in seconds after which the command's process will be killed
|
||||
// if it does not finish before.
|
||||
Timeout int
|
||||
@@ -123,15 +126,29 @@ func GetLogsFormatter() logrus.Formatter {
|
||||
return config.logsFormatter
|
||||
}
|
||||
|
||||
type interpreterEmptyError struct {
|
||||
command string
|
||||
}
|
||||
|
||||
const interpreterEmptyErrorMessage = "the interepreter for command %s should not be empty"
|
||||
|
||||
func (e *interpreterEmptyError) Error() string {
|
||||
return fmt.Sprintf(interpreterEmptyErrorMessage, e.command)
|
||||
}
|
||||
|
||||
// NewCommand instantiates a Command. If the given command string
|
||||
// is not a valid template, throws an error.
|
||||
func NewCommand(command string, timeout int) (*Command, error) {
|
||||
func NewCommand(command string, interpreter string, timeout int) (*Command, error) {
|
||||
t, err := template.New(command).Parse(command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Command{t, timeout}, nil
|
||||
if interpreter == "" {
|
||||
return nil, &interpreterEmptyError{command}
|
||||
}
|
||||
|
||||
return &Command{t, strings.Fields(interpreter), timeout}, nil
|
||||
}
|
||||
|
||||
type fileExtensionAlreadyUsedError struct {
|
||||
@@ -140,7 +157,7 @@ type fileExtensionAlreadyUsedError struct {
|
||||
existingCommand *Command
|
||||
}
|
||||
|
||||
const fileExtensionAlreadyUsedErrorMessage = "file extension '%s' from command '%s' is already used by command '%s'"
|
||||
const fileExtensionAlreadyUsedErrorMessage = "file extension %s from command %s is already used by command %s"
|
||||
|
||||
func (e *fileExtensionAlreadyUsedError) Error() string {
|
||||
return fmt.Sprintf(fileExtensionAlreadyUsedErrorMessage, e.extension, e.command.Template.Name(), e.existingCommand.Template.Name())
|
||||
@@ -167,7 +184,7 @@ type noCommandFoundForFileExtensionError struct {
|
||||
extension string
|
||||
}
|
||||
|
||||
const noCommandFoundForFileExtensionErrorMessage = "no command found for file extension '%s'"
|
||||
const noCommandFoundForFileExtensionErrorMessage = "no command found for file extension %s"
|
||||
|
||||
func (e *noCommandFoundForFileExtensionError) Error() string {
|
||||
return fmt.Sprintf(noCommandFoundForFileExtensionErrorMessage, e.extension)
|
||||
|
||||
@@ -99,26 +99,39 @@ func TestGetLogsFormatter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpreterEmptyError(t *testing.T) {
|
||||
err := &interpreterEmptyError{"echo hello world"}
|
||||
expected := fmt.Sprintf(interpreterEmptyErrorMessage, err.command)
|
||||
if err.Error() != expected {
|
||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCommand(t *testing.T) {
|
||||
var cmd string
|
||||
|
||||
// case 1: uses a wrong command template.
|
||||
cmd = "pdftk {{ range $filePath := FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
if _, err := NewCommand(cmd, 0); err == nil {
|
||||
if _, err := NewCommand(cmd, "/bin/sh -c", 0); err == nil {
|
||||
t.Errorf("Command should not have been instantiated by using '%s' as command template", cmd)
|
||||
}
|
||||
|
||||
// case 2: uses a correct command template.
|
||||
cmd = "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
||||
if _, err := NewCommand(cmd, 0); err != nil {
|
||||
if _, err := NewCommand(cmd, "/bin/sh -c", 0); err != nil {
|
||||
t.Errorf("Command should have been instantiated by using '%s' as command template", cmd)
|
||||
}
|
||||
|
||||
// case 3: uses an empty interpreter.
|
||||
if _, err := NewCommand(cmd, "", 0); err == nil {
|
||||
t.Error("Command should not have been instantiated by using an empty interpreter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileExtensionAlreadyUsedError(t *testing.T) {
|
||||
ext := ".pdf"
|
||||
cmd1, _ := NewCommand("echo", 0)
|
||||
cmd2, _ := NewCommand("echo", 0)
|
||||
cmd1, _ := NewCommand("echo", "/bin/sh -c", 0)
|
||||
cmd2, _ := NewCommand("echo", "/bin/sh -c", 0)
|
||||
err := &fileExtensionAlreadyUsedError{ext, cmd1, cmd2}
|
||||
expected := fmt.Sprintf(fileExtensionAlreadyUsedErrorMessage, err.extension, err.command.Template.Name(), err.existingCommand.Template.Name())
|
||||
|
||||
@@ -129,7 +142,7 @@ func TestFileExtensionAlreadyUsedError(t *testing.T) {
|
||||
|
||||
func TestWithCommand(t *testing.T) {
|
||||
ext := ".pdf"
|
||||
cmd, _ := NewCommand("echo", 0)
|
||||
cmd, _ := NewCommand("echo", "/bin/sh -c", 0)
|
||||
|
||||
// case 1: uses a command with a file extension not already referenced.
|
||||
if err := WithCommand(ext, cmd); err != nil {
|
||||
@@ -154,7 +167,7 @@ func TestNoCommandFoundForFileExtensionError(t *testing.T) {
|
||||
func TestGetCommand(t *testing.T) {
|
||||
Reset()
|
||||
ext := ".pdf"
|
||||
cmd, _ := NewCommand("echo", 0)
|
||||
cmd, _ := NewCommand("echo", "/bin/sh -c", 0)
|
||||
WithCommand(ext, cmd)
|
||||
|
||||
// case 1: uses a file extension which has a command associated.
|
||||
|
||||
@@ -24,7 +24,7 @@ func ParseFile(configurationFilePath string) error {
|
||||
}
|
||||
|
||||
// handles merge command first...
|
||||
cmd, err := NewCommand(fileConfig.Commands.Merge.Template, fileConfig.Commands.Merge.Timeout)
|
||||
cmd, err := NewCommand(fileConfig.Commands.Merge.Template, fileConfig.Commands.Merge.Interpreter, fileConfig.Commands.Merge.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func ParseFile(configurationFilePath string) error {
|
||||
|
||||
// ...then conversion commands!
|
||||
for _, command := range fileConfig.Commands.Conversions {
|
||||
cmd, err := NewCommand(command.Template, command.Timeout)
|
||||
cmd, err := NewCommand(command.Template, command.Interpreter, command.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,15 +64,17 @@ type (
|
||||
|
||||
// mergeCommand gathers all data regarding the... merge command.
|
||||
mergeCommand struct {
|
||||
Template string `yaml:"template"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
Template string `yaml:"template"`
|
||||
Interpreter string `yaml:"interpreter"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
}
|
||||
|
||||
// conversionCommand gathers all data regarding a conversion command.
|
||||
conversionCommand struct {
|
||||
Template string `yaml:"template"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
Extensions []string `yaml:"extensions"`
|
||||
Template string `yaml:"template"`
|
||||
Interpreter string `yaml:"interpreter"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
Extensions []string `yaml:"extensions"`
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ type commandTimeoutError struct {
|
||||
timeout int
|
||||
}
|
||||
|
||||
const commandTimeoutErrorMessage = "the command '%s' has reached the %d second(s) timeout"
|
||||
const commandTimeoutErrorMessage = "the command %s has reached the %d second(s) timeout"
|
||||
|
||||
func (e *commandTimeoutError) Error() string {
|
||||
return fmt.Sprintf(commandTimeoutErrorMessage, e.command, e.timeout)
|
||||
@@ -32,11 +32,13 @@ func (e *commandTimeoutError) Error() string {
|
||||
|
||||
// run runs the given command. If timeout is reached or
|
||||
// something bad happened, returns an error.
|
||||
func (r *runner) run(command string, timeout int) error {
|
||||
func (r *runner) run(command string, interpreter []string, timeout int) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
cmd := exec.Command("/bin/sh", "-c", command)
|
||||
binary := interpreter[0]
|
||||
parameters := append(interpreter[1:], command)
|
||||
cmd := exec.Command(binary, parameters...)
|
||||
logger.Debugf("executing command %s", cmd.Args)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
@@ -85,7 +87,7 @@ func Unconv(workingDir string, file *gfile.File) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = forest.run(data.String(), cmd.Timeout)
|
||||
err = forest.run(data.String(), cmd.Interpreter, cmd.Timeout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -114,7 +116,7 @@ func Merge(workingDir string, filesPaths []string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = forest.run(data.String(), cmd.Timeout)
|
||||
err = forest.run(data.String(), cmd.Interpreter, cmd.Timeout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/app/config"
|
||||
@@ -42,19 +43,19 @@ func TestRun(t *testing.T) {
|
||||
|
||||
// case 1: uses a simple command.
|
||||
cmd = "echo Hello world"
|
||||
if err := forest.run(cmd, 30); err != nil {
|
||||
if err := forest.run(cmd, strings.Fields("/bin/sh -c"), 30); err != nil {
|
||||
t.Errorf("Command '%s' should have worked", cmd)
|
||||
}
|
||||
|
||||
// case 2: uses a simple command but with an unsuitable timeout.
|
||||
cmd = "sleep 5"
|
||||
if err := forest.run(cmd, 0); err == nil {
|
||||
if err := forest.run(cmd, strings.Fields("/bin/sh -c"), 0); err == nil {
|
||||
t.Errorf("Command '%s' should not have worked", cmd)
|
||||
}
|
||||
|
||||
// case 3: uses a broken command.
|
||||
cmd = "helloworld"
|
||||
if err := forest.run(cmd, 30); err == nil {
|
||||
if err := forest.run(cmd, strings.Fields("/bin/sh -c"), 30); err == nil {
|
||||
t.Errorf("Command '%s' should not have worked", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user