aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--acid.adoc3
-rw-r--r--acid.go27
-rw-r--r--acid.yaml.example6
-rw-r--r--acid_test.go32
5 files changed, 64 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index 1fd3dc3..455c22d 100644
--- a/Makefile
+++ b/Makefile
@@ -9,5 +9,7 @@ acid: acid.go
go build -ldflags "-X 'main.projectVersion=$(version)'" -o $@
acid.1: acid.adoc
asciidoctor -b manpage -a release-version=$(version) -o $@ acid.adoc
+test: all
+ go test
clean:
rm -f $(outputs)
diff --git a/acid.adoc b/acid.adoc
index 4c56562..6775d52 100644
--- a/acid.adoc
+++ b/acid.adoc
@@ -71,6 +71,9 @@ which has the following fields:
*CloneURL*::
Gitea link for cloning the repository over HTTP.
+The special *quote* template function quotes fields for safe usage
+in *sh*(1) command arguments.
+
Runners
-------
Runners receive the following additional environment variables:
diff --git a/acid.go b/acid.go
index b029c2a..2c59c38 100644
--- a/acid.go
+++ b/acid.go
@@ -93,10 +93,30 @@ func parseConfig(path string) error {
}
var err error
- gNotifyScript, err = ttemplate.New("notify").Parse(gConfig.Notify)
+ gNotifyScript, err =
+ ttemplate.New("notify").Funcs(shellFuncs).Parse(gConfig.Notify)
return err
}
+var shellFuncs = ttemplate.FuncMap{
+ "quote": func(word string) string {
+ // History expansion is annoying, don't let it cut us.
+ if strings.IndexRune(word, '!') >= 0 {
+ return "'" + strings.ReplaceAll(word, "'", `'"'"'`) + "'"
+ }
+
+ const special = "$`\"\\"
+ quoted := []rune{'"'}
+ for _, r := range word {
+ if strings.IndexRune(special, r) >= 0 {
+ quoted = append(quoted, '\\')
+ }
+ quoted = append(quoted, r)
+ }
+ return string(append(quoted, '"'))
+ },
+}
+
// --- Utilities ---------------------------------------------------------------
func giteaSign(b []byte) string {
@@ -910,8 +930,9 @@ func executorRunTask(ctx context.Context, task Task) error {
// - we might have to clone submodules as well.
// Otherwise, we could download a source archive from Gitea,
// and use SFTP to upload it to the runner.
- tmplScript, err := ttemplate.New("script").Parse(rt.Runner.Setup + "\n" +
- rt.ProjectRunner.Setup + "\n" + rt.ProjectRunner.Build)
+ tmplScript, err := ttemplate.New("script").Funcs(shellFuncs).
+ Parse(rt.Runner.Setup + "\n" +
+ rt.ProjectRunner.Setup + "\n" + rt.ProjectRunner.Build)
if err != nil {
return fmt.Errorf("script: %w", err)
}
diff --git a/acid.yaml.example b/acid.yaml.example
index a80cc4c..b34abf0 100644
--- a/acid.yaml.example
+++ b/acid.yaml.example
@@ -44,9 +44,9 @@ runners:
setup: |
set -ex
sudo pacman -Syu --noconfirm git
- git clone --recursive '{{.CloneURL}}' '{{.Repo}}'
- cd '{{.Repo}}'
- git -c advice.detachedHead=false checkout '{{.Hash}}'
+ git clone --recursive {{quote .CloneURL}} {{quote .Repo}}
+ cd {{quote .Repo}}
+ git -c advice.detachedHead=false checkout {{quote .Hash}}
# Configuration for individual Gitea repositories.
projects:
diff --git a/acid_test.go b/acid_test.go
new file mode 100644
index 0000000..f23d892
--- /dev/null
+++ b/acid_test.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "bytes"
+ "testing"
+ ttemplate "text/template"
+)
+
+func TestTemplateQuote(t *testing.T) {
+ // Ideally, we should back-parse it using sh syntax.
+ // This is an unnecessarily fragile test.
+ for _, test := range []struct {
+ input, output string
+ }{
+ {`!!`, `'!!'`},
+ {``, `""`},
+ {`${var}`, `"\${var}"`},
+ {"`cat`", "\"\\`cat\\`\""},
+ {`"魚\"`, `"\"魚\\\""`},
+ } {
+ var b bytes.Buffer
+ err := ttemplate.Must(ttemplate.New("test").
+ Funcs(shellFuncs).Parse("{{quote .}}")).Execute(&b, test.input)
+ if err != nil {
+ t.Errorf("template execution error: %s\n", err)
+ }
+ if b.String() != test.output {
+ t.Errorf("%q should be quoted os %q, not %q\n",
+ test.input, test.output, b.String())
+ }
+ }
+}