diff --git a/internal/humanitec/convert.go b/internal/humanitec/convert.go index f5a4f64..53c55f7 100644 --- a/internal/humanitec/convert.go +++ b/internal/humanitec/convert.go @@ -136,7 +136,9 @@ func convertFileMountSpec(f *score.FileMountSpec, context *templatesContext, bas return "", nil, err } - if !f.NoExpand { + if f.NoExpand { + content = context.Escape(content) + } else { content = context.Substitute(content) } diff --git a/internal/humanitec/convert_test.go b/internal/humanitec/convert_test.go index 13245b5..e76d82e 100644 --- a/internal/humanitec/convert_test.go +++ b/internal/humanitec/convert_test.go @@ -389,11 +389,11 @@ func TestScoreConvert(t *testing.T) { }, "/etc/backend/config.yml": map[string]interface{}{ "mode": "666", - "value": "DEBUG: ${resources.env.DEBUG}", + "value": "DEBUG: $\\{resources.env.DEBUG}", }, "/etc/backend/config.txt": map[string]interface{}{ "mode": "666", - "value": "Mounted\nFile\nContent\n${resources.env.DEBUG}", + "value": "Mounted\nFile\nContent\n$\\{resources.env.DEBUG}", }, }, "volume_mounts": map[string]interface{}{ diff --git a/internal/humanitec/templates.go b/internal/humanitec/templates.go index ceee7a3..9a654d6 100644 --- a/internal/humanitec/templates.go +++ b/internal/humanitec/templates.go @@ -87,7 +87,27 @@ func (ctx *templatesContext) Substitute(src string) string { return ctx.mapVar(matches[2]) }) +} + +// Escape replaces all matching '${...}' templates in a source string with '$\{...}' +func (ctx *templatesContext) Escape(src string) string { + return placeholderRegEx.ReplaceAllStringFunc(src, func(str string) string { + // WORKAROUND: ReplaceAllStringFunc(..) does not provide match details + // https://github.com/golang/go/issues/5690 + var matches = placeholderRegEx.FindStringSubmatch(str) + // SANITY CHECK + if len(matches) != 3 { + log.Printf("Error: could not find a proper match in previously captured string fragment") + return src + } + + if strings.HasPrefix(matches[1], "{") { + return fmt.Sprintf("$\\%s", matches[1]) + } + + return matches[0] + }) } // MapVar replaces objects and properties references with corresponding values diff --git a/internal/humanitec/templates_test.go b/internal/humanitec/templates_test.go index e0ba23a..685904e 100644 --- a/internal/humanitec/templates_test.go +++ b/internal/humanitec/templates_test.go @@ -64,6 +64,50 @@ func TestMapVar(t *testing.T) { assert.Equal(t, "${nil.db.name}", ctx.mapVar("nil.db.name")) } +func TestEscape(t *testing.T) { + var meta = score.WorkloadMeta{ + Name: "test-name", + } + + var resources = score.ResourcesSpecs{ + "env": score.ResourceSpec{ + Type: "environment", + }, + "db": score.ResourceSpec{ + Type: "postgres", + }, + "dns": score.ResourceSpec{ + Type: "dns", + }, + "service-a": score.ResourceSpec{ + Type: "service", + }, + } + + var ext = extensions.HumanitecResourcesSpecs{ + "dns": {Scope: "shared"}, + } + + ctx, err := buildContext(meta, resources, ext) + assert.NoError(t, err) + + assert.Equal(t, "", ctx.Escape("")) + assert.Equal(t, "abc", ctx.Escape("abc")) + assert.Equal(t, "abc $$ abc", ctx.Escape("abc $$ abc")) + assert.Equal(t, "$abc", ctx.Escape("$abc")) + assert.Equal(t, "$${abc}", ctx.Escape("$${abc}")) + + assert.Equal(t, "The name is '$\\{metadata.name}'", ctx.Escape("The name is '${metadata.name}'")) + assert.Equal(t, "The name is '$\\{metadata.nil}'", ctx.Escape("The name is '${metadata.nil}'")) + + assert.Equal(t, "resources.env.DEBUG", ctx.Escape("resources.env.DEBUG")) + + assert.Equal(t, "$\\{resources.db}", ctx.Escape("${resources.db}")) + assert.Equal(t, + "postgresql://$\\{resources.db.user}:$\\{resources.db.password}@$\\{resources.db.host}:$\\{resources.db.port}/$\\{resources.db.name}", + ctx.Escape("postgresql://${resources.db.user}:${resources.db.password}@${resources.db.host}:${resources.db.port}/${resources.db.name}")) +} + func TestSubstitute(t *testing.T) { var meta = score.WorkloadMeta{ Name: "test-name",