Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Commit

Permalink
#20 rocker/template: shell helper
Browse files Browse the repository at this point in the history
  • Loading branch information
Yuriy Bogdanov committed Sep 19, 2015
1 parent a94896c commit 949e3a4
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/rocker/template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ This template will yield:
ENV={"USER":"johnsnow","DOCKER_MACHINE_NAME":"dev","PATH":"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin",...}
```

### {{ shell *string* }} or {{ *string* | shell }}
Escapes given string so it can be substituted to a shell command.

Example:
```Dockerfile
RUN echo {{ "hello\nworld" | shell }}
```

This template will yield:
```Dockerfile
RUN $'hello\nworld'
```

### {{ dump *anything* }}
Pretty-prints any variable. Useful for debugging.

Expand Down
52 changes: 52 additions & 0 deletions src/rocker/template/shellarg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*-
* Copyright 2015 Grammarly, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package template

import (
"regexp"
"strings"
)

var (
complexShellArgRegex = regexp.MustCompile("(?i:[^a-z\\d_\\/:=-])")
leadingSingleQuotesRegex = regexp.MustCompile("^(?:'')+")
)

// EscapeShellarg escapes any string so it can be safely passed to a shell
func EscapeShellarg(value string) string {
// Nothing to escape, return as is
if !complexShellArgRegex.MatchString(value) {
return value
}

// escape all single quotes
value = "'" + strings.Replace(value, "'", "'\\''", -1) + "'"

// remove duplicated single quotes at the beginning
value = leadingSingleQuotesRegex.ReplaceAllString(value, "")

// remove non-escaped single-quote if there are enclosed between 2 escaped
value = strings.Replace(value, "\\'''", "\\'", -1)

// if the string contains new lines, then use bash $'string' representation
// to have the newline escape character
if strings.Contains(value, "\n") {
value = "$" + strings.Replace(value, "\n", "\\n", -1)
}

return value
}
51 changes: 51 additions & 0 deletions src/rocker/template/shellarg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*-
* Copyright 2015 Grammarly, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package template

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestEscapeShellarg_Basic(t *testing.T) {
t.Parallel()
assert.Equal(t, "Testing", EscapeShellarg("Testing"))
assert.Equal(t, "'Testing;'", EscapeShellarg("Testing;"))
}

func TestEscapeShellarg_Advanced(t *testing.T) {
t.Parallel()

assertions := map[string]string{
"hello\\nworld": "'hello\\nworld'",
"hello:world": "hello:world",
"--hello=world": "--hello=world",
"hello\\tworld": "'hello\\tworld'",
"hello\nworld": "$'hello\\nworld'",
"\thello\nworld'": "$'\thello\\nworld'\\'",
"hello world": "'hello world'",
"hello\\\\'": "'hello\\\\'\\'",
"'\\\\'world": "\\''\\\\'\\''world'",
"world\\": "'world\\'",
"'single'": "\\''single'\\'",
}

for k, v := range assertions {
assert.Equal(t, v, EscapeShellarg(k))
}
}
1 change: 1 addition & 0 deletions src/rocker/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func ProcessConfigTemplate(name string, reader io.Reader, vars Vars, funcs map[s
"dump": dump,
"assert": assertFn,
"json": jsonFn,
"shell": EscapeShellarg,
}
for k, f := range funcs {
funcMap[k] = f
Expand Down
4 changes: 4 additions & 0 deletions src/rocker/template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ func TestProcessConfigTemplate_Json(t *testing.T) {
assert.Equal(t, "key: {\"foo\":\"bar\"}", processTemplate(t, "key: {{ .data | json }}"))
}

func TestProcessConfigTemplate_Shellarg(t *testing.T) {
assert.Equal(t, "echo 'hello world'", processTemplate(t, "echo {{ \"hello world\" | shell }}"))
}

func processTemplate(t *testing.T, tpl string) string {
result, err := ProcessConfigTemplate("test", strings.NewReader(tpl), configTemplateVars, map[string]interface{}{})
if err != nil {
Expand Down

0 comments on commit 949e3a4

Please sign in to comment.