new attribute interpreter for commands (#7)

This commit is contained in:
Julien Neuhart
2018-05-16 10:55:08 +02:00
committed by GitHub
parent faec55dd01
commit 5100693789
15 changed files with 102 additions and 25 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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)

View File

@@ -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.

View File

@@ -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"`
}
)

View File

@@ -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
}

View File

@@ -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)
}
}