Skip to content

Commit

Permalink
adds skip_templating feature (gruntwork-io#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
rwittrick committed Jun 10, 2024
1 parent 9fd73bc commit 0243d20
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 11 deletions.
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Learn more about boilerplate in the following sections:
1. [Dependencies](#dependencies)
1. [Hooks](#hooks)
1. [Skip Files](#skip-files)
1. [Skip Templating](#skip-files)
1. [Templates](#templates)
1. [Validations](#validations)
1. [Variable Ordering](#variable-ordering)
Expand Down Expand Up @@ -312,6 +313,9 @@ skip_files:
- path: subfolder/README.md
if: {{ not .ShowLogo }}

skip_templating:
- path: subfolder/helm-charts/*

engines:
- path: subfolder/foo.json.jsonnet
template_engine: jsonnet
Expand Down Expand Up @@ -392,6 +396,16 @@ values `"true"` or `"false"`.

See the [Skip Files](#skip-files) section for more info.

**Skip Templating**: Use *skip_templating* to specify files in the template folder that should not be parsed by the templating engine but will be copied to the `output-folder` with the same path and file name . The `path` field
is the relative path from the template folder root (where the `boilerplate.yml` file is defined) of the file that should
be copied without rendering. You can conditionally skip the templating of the file using the `if` field, which should either be a YAML boolean value
(`true` or `false`), or Go templating syntax (which can use the boilerplate variables) that evaluates to the string
values `"true"` or `"false"`.

See the [Skip Files](#skip-files) section for more info on the configuration as the syntax is the same.

If a file is listed in both `skip_files` and `skip_templating`, the file will be skipped entirely.

**Engines**: Use *engines* to specify files in the template folder that should be rendered with an alternative
templating engine. The `path` field is the relative path from the template folder root (where the `boilerplate.yml` file
is defined) of the file that should use the alternative template engine.
Expand Down Expand Up @@ -586,11 +600,11 @@ Note the following:

#### Skip Files

You can specify files that should be excluded from the rendered output using the `skip_files` section in
`boilerplate.yml`. This is most useful when you have templates that need to conditionally exclude files from the
rendered folder list.
You can specify files that should be excluded from the rendered output using the `skip_files`, or copied without rendering using the `skip_templating` sections in
`boilerplate.yml`. `skip_files` is most useful when you have templates that need to conditionally exclude files from the
rendered folder list. `skip_templating` is useful when you want to copy files without rendering them, such as files that contain syntax similar to the go templating syntax - such as helm charts.

The `skip_files` section is a list of objects with the fields `path`, `not_path`, and `if`, where one of `path` or
The `skip_files` and `skip_templating` sections are a list of objects with the fields `path`, `not_path`, and `if`, where one of `path` or
`not_path` is required. When `path` is set, all files that match the `path` attribute will be skipped, while when
`not_path` is set, all files that DO NOT match the `not_path` attribute are skipped (in other words, only paths that
match `not_path` are kept).
Expand All @@ -608,6 +622,9 @@ Consider the following boilerplate template folder:
.
├── boilerplate.yml
├── BOILERPLATE_README.md
├── helm-charts
├── Chart.yaml
└── values.yaml
└── docs
├── README_WITH_ENCRYPTION.md
└── README_WITHOUT_ENCRYPTION.md
Expand Down Expand Up @@ -638,11 +655,25 @@ This will:
- Skip rendering `docs/README_WITH_ENCRYPTION.md` if `UseEncryption` is set to `false`.
- If `DocsOnly` is set to `true`, only render the `docs` folder.

Also suppose that you wanted to conditionally select which helm chart file to simly be copied, while rendering the variables. You can use `skip_templating`
to implement this:

```yaml
skip_templating:
- path: "*/Chart.yaml"
```

This will:

- Copy `helm-charts/Chart.yaml` to the `output-folder` without processing the file.

For a more concise specification, you can use glob syntax in the `path` to match multiple paths in one entry:

```yaml
skip_files:
- path: "docs/**/*"
skip_templating:
- path: "helm-charts/*"
```


Expand Down
24 changes: 23 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type BoilerplateConfig struct {
Hooks variables.Hooks
Partials []string
SkipFiles []variables.SkipFile
SkipTemplating []variables.SkipFile
Engines []variables.Engine
}

Expand Down Expand Up @@ -76,11 +77,17 @@ func (config *BoilerplateConfig) UnmarshalYAML(unmarshal func(interface{}) error
return err
}

skipFiles, err := variables.UnmarshalSkipFilesFromBoilerplateConfigYaml(fields)
skipFiles, err := variables.UnmarshalSkipFilesFromBoilerplateConfigYaml(fields, "skip_files")
if err != nil {
return err
}

skipTemplating, err := variables.UnmarshalSkipFilesFromBoilerplateConfigYaml(fields, "skip_templating")
if err != nil {
return err
}


engines, err := variables.UnmarshalEnginesFromBoilerplateConfigYaml(fields)
if err != nil {
return err
Expand All @@ -93,6 +100,7 @@ func (config *BoilerplateConfig) UnmarshalYAML(unmarshal func(interface{}) error
Hooks: hooks,
Partials: partials,
SkipFiles: skipFiles,
SkipTemplating: skipTemplating,
Engines: engines,
}
return nil
Expand Down Expand Up @@ -154,6 +162,20 @@ func (config *BoilerplateConfig) MarshalYAML() (interface{}, error) {
}
configYml["skip_files"] = skipFilesYml
}
if len(config.SkipTemplating) > 0 {
// Due to go type system, we can only pass through []interface{}, even though []SkipTemplating is technically
// polymorphic to that type. So we reconstruct the list using the right type before passing it in to the marshal
// function.
interfaceList := []interface{}{}
for _, skipTemplate := range config.SkipTemplating {
interfaceList = append(interfaceList, skipTemplate)
}
skipTemplatingYml, err := util.MarshalListOfObjectsToYAML(interfaceList)
if err != nil {
return nil, err
}
configYml["skip_templating"] = skipTemplatingYml
}
if len(config.Engines) > 0 {
// Due to go type system, we can only pass through []interface{}, even though []Engine is technically
// polymorphic to that type. So we reconstruct the list using the right type before passing it in to the marshal
Expand Down
28 changes: 28 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,34 @@ func TestParseBoilerplateConfigWithSkipFiles(t *testing.T) {
assert.Equal(t, expected, actual)
}

// YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation
const configWithSkipTemplating = `skip_templating:
- path: "docs/README_ALWAYS_SKIP.md"
- path: "docs/README_MAYBE_SKIP.md"
if: "{{ .MaybeSkip }}"
- not_path: "docs/**/*"
if: "{{ .DocsOnly }}"
`

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

actual, err := ParseBoilerplateConfig([]byte(configWithSkipTemplating))
require.NoError(t, err)

expected := &BoilerplateConfig{
Variables: []variables.Variable{},
Dependencies: []variables.Dependency{},
Hooks: variables.Hooks{},
SkipTemplating: []variables.SkipFile{
{Path: "docs/README_ALWAYS_SKIP.md", If: ""},
{Path: "docs/README_MAYBE_SKIP.md", If: "{{ .MaybeSkip }}"},
{NotPath: "docs/**/*", If: "{{ .DocsOnly }}"},
},
}
assert.Equal(t, expected, actual)
}

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
val: {{ VarVal }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
val: {{ VarVal }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
variables:
- name: VarVal
type: string
default: "val"

skip_templating:
- path: "**/d.yml"
14 changes: 12 additions & 2 deletions templates/template_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,11 +458,15 @@ func processTemplateFolder(
) error {
util.Logger.Printf("Processing templates in %s and outputting generated files to %s", opts.TemplateFolder, opts.OutputFolder)

// Process and render skip files and engines before walking so we only do the rendering operation once.
// Process and render skip files, templates and engines before walking so we only do the rendering operation once.
processedSkipFiles, err := processSkipFiles(config.SkipFiles, opts, variables)
if err != nil {
return err
}
processedSkipTemplating, err := processSkipFiles(config.SkipTemplating, opts, variables)
if err != nil {
return err
}
processedEngines, err := processEngines(config.Engines, opts, variables)
if err != nil {
return err
Expand All @@ -477,7 +481,8 @@ func processTemplateFolder(
return createOutputDir(path, opts, variables)
} else {
engine := determineTemplateEngine(processedEngines, path)
return processFile(path, opts, variables, partials, engine)
skipTemplating := shouldSkipPath(path, opts, processedSkipTemplating)
return processFile(path, opts, variables, partials, skipTemplating, engine)
}
})
}
Expand All @@ -489,8 +494,13 @@ func processFile(
opts *options.BoilerplateOptions,
variables map[string]interface{},
partials []string,
skipTemplating bool,
engine variables.TemplateEngineType,
) error {
if skipTemplating {
util.Logger.Printf("Skipping templating of %s", path)
return copyFile(path, opts, variables)
}
isText, err := util.IsTextFile(path)
if err != nil {
return err
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
val: {{ VarVal }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
val: val
Empty file.
8 changes: 4 additions & 4 deletions variables/skip_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package variables

import "github.com/gruntwork-io/boilerplate/errors"

// A single skip_file entry, which is a file that (conditionally) should be excluded from the rendered output.
// A single skip_file or skip_templating entry, which is a file that (conditionally) should be excluded from the rendered output.
type SkipFile struct {
Path string
NotPath string
Expand All @@ -27,15 +27,15 @@ func (skipFile SkipFile) MarshalYAML() (interface{}, error) {

// Given a list of key:value pairs read from a Boilerplate YAML config file of the format:
//
// skip_files:
// (skip_files|skip_templating):
// - path: <PATH>
// if: <SKIPIF>
// - path: <PATH>
// - not_path: <PATH>
//
// convert to a list of SkipFile structs.
func UnmarshalSkipFilesFromBoilerplateConfigYaml(fields map[string]interface{}) ([]SkipFile, error) {
rawSkipFiles, err := unmarshalListOfFields(fields, "skip_files")
func UnmarshalSkipFilesFromBoilerplateConfigYaml(fields map[string]interface{}, fieldName string) ([]SkipFile, error) {
rawSkipFiles, err := unmarshalListOfFields(fields, fieldName)
if err != nil || rawSkipFiles == nil {
return nil, err
}
Expand Down

0 comments on commit 0243d20

Please sign in to comment.