Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote templates support #59

Merged
merged 8 commits into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions _docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ Template](https://golang.org/pkg/text/template) syntax:

Copy an image into the `website-boilerplate` folder and call it `logo.png`.

Finally, run `boilerplate`, setting the `--template-folder` to `website-boilerplate` and `--output-folder` to the path
Finally, run `boilerplate`, setting the `--template-url` to `website-boilerplate` and `--output-folder` to the path
where you want the generated code to go:

```
boilerplate --template-folder /home/ubuntu/website-boilerplate --output-folder /home/ubuntu/website-output
boilerplate --template-url /home/ubuntu/website-boilerplate --output-folder /home/ubuntu/website-output

Title

Expand All @@ -58,7 +58,7 @@ Generating /home/ubuntu/website-output/index.html
Copying /home/ubuntu/website-output/logo.png
```

Boilerplate copies any files from the `--template-folder` into the `--output-folder`, passing them through the
Boilerplate copies any files from the `--template-url` into the `--output-folder`, passing them through the
[Go Template](https://golang.org/pkg/text/template) engine along the way. After running the command above, the
`website-output` folder will contain a `logo.png` (unchanged from the original) and an `index.html` with the
following contents:
Expand All @@ -71,7 +71,7 @@ You can also run Boilerplate non-interactively, which is great for automation:

```
boilerplate \
--template-folder /home/ubuntu/website-boilerplate \
--template-url /home/ubuntu/website-boilerplate \
--output-folder /home/ubuntu/website-output \
--non-interactive \
--var Title="Boilerplate Example" \
Expand Down Expand Up @@ -112,11 +112,11 @@ You can find older versions on the [Releases Page](https://github.com/gruntwork-

When you run Boilerplate, it performs the following steps:

1. Read the `boilerplate.yml` file in the folder specified by the `--template-folder` option to find all defined
1. Read the `boilerplate.yml` file in the folder specified by the `--template-url` option to find all defined
varaibles.
1. Gather values for the variables from any `--var` and `--var-file` options that were passed in and prompting the user
for the rest (unless the `--non-interactive` flag is specified).
1. Copy each file from `--template-folder` to `--output-folder`, running each non-binary file through the Go
1. Copy each file from `--template-url` to `--output-folder`, running each non-binary file through the Go
Template engine with the map of variables as the data structure.

Learn more about boilerplate in the following sections:
Expand All @@ -133,7 +133,9 @@ Learn more about boilerplate in the following sections:

The `boilerplate` binary supports the following options:

* `--template-folder FOLDER` (required): Generate the project from the templates in `FOLDER`.
* `--template-url URL` (required): Generate the project from the templates in `URL`. This can be a local path, or a
[go-getter](https://github.com/hashicorp/go-getter) compatible URL for remote templates (e.g.,
`[email protected]:gruntwork-io/boilerplate.git//examples/include?ref=master`).
* `--output-folder` (required): Create the output files and folders in `FOLDER`.
* `--non-interactive` (optional): Do not prompt for input variables. All variables must be set via `--var` and
`--var-file` options instead.
Expand All @@ -155,21 +157,28 @@ Some examples:
Generate a project in ~/output from the templates in ~/templates:

```
boilerplate --template-folder ~/templates --output-folder ~/output
boilerplate --template-url ~/templates --output-folder ~/output
```

Generate a project in ~/output from the templates in ~/templates, using variables passed in via the command line:

```
boilerplate --template-folder ~/templates --output-folder ~/output --var "Title=Boilerplate" --var "ShowLogo=false"
boilerplate --template-url ~/templates --output-folder ~/output --var "Title=Boilerplate" --var "ShowLogo=false"
```

Generate a project in ~/output from the templates in ~/templates, using variables read from a file:

```
boilerplate --template-folder ~/templates --output-folder ~/output --var-file vars.yml
boilerplate --template-url ~/templates --output-folder ~/output --var-file vars.yml
```

Generate a project in ~/output from the templates in this repo's `include` example dir, using variables read from a file:

```
boilerplate --template-url "[email protected]:gruntwork-io/boilerplate.git//examples/include?ref=master" --output-folder ~/output --var-file vars.yml
```


#### The boilerplate.yml file

The `boilerplate.yml` file is used to configure `boilerplate`. The file is optional. If you don't specify it, you can
Expand All @@ -191,7 +200,7 @@ variables:

dependencies:
- name: <DEPENDENCY_NAME>
template-folder: <FOLDER>
template-url: <FOLDER>
output-folder: <FOLDER>
skip: <CONDITION>
dont-inherit-variables: <BOOLEAN>
Expand Down Expand Up @@ -244,7 +253,7 @@ See the [Variables](#variables) section for more info.
executing the current one. Each dependency may contain the following keys:

* `name` (Required): A unique name for the dependency.
* `template-folder` (Required): Run `boilerplate` on the templates in this folder. This path is relative to the
* `template-url` (Required): Run `boilerplate` on the templates in this folder. This path is relative to the
current template.
* `output-folder` (Required): Create the output files and folders in this folder. This path is relative to the output
folder of the current template.
Expand Down Expand Up @@ -350,7 +359,7 @@ Note the following:
prompt you for each of those variables separately from the root ones. You can also use the
`<DEPENDENCY_NAME>.<VARIABLE_NAME>` syntax as the name of the variable with the `-var` flag and inside of a var file
to provide a value for a variable in a dependency.
* Interpolation: You may use interpolation in the `template-folder` and `output-folder` parameters of your
* Interpolation: You may use interpolation in the `template-url` and `output-folder` parameters of your
dependencies. This allows you to use specify the paths to your template and output folders dynamically.
* Conditional dependencies: You can enable or disable a dependency using the `skip` parameter, which supports Go
templating syntax and boilerplate variables. If the `skip` parameter evaluates to the string `true`, the
Expand All @@ -366,7 +375,7 @@ Note the following:

dependencies:
- name: conditional-dependency-example
template-folder: ../foo
template-url: ../foo
output-folder: foo
# Skip this dependency if both .Foo and .Bar are set to true
skip: "{{"{{"}} and .Foo .Bar {{"}}"}}"
Expand Down Expand Up @@ -402,7 +411,7 @@ Note the following:
- {{"{{"}} .SomeVariable {{"}}"}}
- {{"{{"}} .AnotherVariable {{"}}"}}
```
* Boilerplate runs your `command` with the working directory set to the `--template-folder` option.
* Boilerplate runs your `command` with the working directory set to the `--template-url` option.
* `skip` (Optional): Skip this hook if this condition, which can use Go templating syntax and
boilerplate variables, evaluates to the string `true`. This is useful to conditionally enable or disable
dependencies.
Expand All @@ -411,7 +420,7 @@ Note the following:
#### Templates

Boilerplate puts all the variables into a Go map where the key is the name of the variable and the value is what
the user provided. It then starts copying files from the `--template-folder` into the `--output-folder`, passing each
the user provided. It then starts copying files from the `--template-url` into the `--output-folder`, passing each
non-binary file through the [Go Template](https://golang.org/pkg/text/template) engine, with the variable map as a
data structure.

Expand Down Expand Up @@ -481,7 +490,8 @@ Boilerplate also includes several custom helpers that you can access that enhanc
so you can use paths relative to the file from which you are calling the `shell` helper. Any argument you pass of the
form `ENV:KEY=VALUE` will be set as an environment variable for the command rather than an argument. For another way
to execute commands, see [hooks](#hooks).
* `templateFolder`: Return the value of the `--template-folder` command-line option. Useful for building relative paths.
* `templateFolder`: Return the value of the template working dir. This is the value of the `--template-url` command-line
option if local template, or the download dir if remote template. Useful for building relative paths.
* `outputFolder`: Return the value of the `--output-folder` command-line option. Useful for building relative paths.
* `envWithDefault NAME DEFAULT`: Render the value of environment variable `NAME`. If that environment variable is empty or not
defined, render `DEFAULT` instead.
Expand Down
2 changes: 1 addition & 1 deletion _docs/boilerplate.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
variables:
- name: Version
- name: Version
17 changes: 10 additions & 7 deletions cli/boilerplate_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@ A tool for generating files and folders (\"boilerplate\") from a set of template

Generate a project in ~/output from the templates in ~/templates:

boilerplate --template-folder ~/templates --output-folder ~/output
boilerplate --template-url ~/templates --output-folder ~/output

Generate a project in ~/output from the templates in ~/templates, using variables passed in via the command line:

boilerplate --template-folder ~/templates --output-folder ~/output --var "Title=Boilerplate" --var "ShowLogo=false"
boilerplate --template-url ~/templates --output-folder ~/output --var "Title=Boilerplate" --var "ShowLogo=false"

Generate a project in ~/output from the templates in ~/templates, using variables read from a file:

boilerplate --template-folder ~/templates --output-folder ~/output --var-file vars.yml
boilerplate --template-url ~/templates --output-folder ~/output --var-file vars.yml

Generate a project in ~/output from the templates in this repo's include example dir, using variables read from a file:

boilerplate --template-url "[email protected]:gruntwork-io/boilerplate.git//examples/include?ref=master" --output-folder ~/output --var-file vars.yml


Options:
Expand All @@ -47,8 +51,8 @@ func CreateBoilerplateCli(version string) *cli.App {

app.Flags = []cli.Flag{
cli.StringFlag{
Name: options.OptTemplateFolder,
Usage: "Generate the project from the templates in `FOLDER`.",
Name: options.OptTemplateUrl,
Usage: "Generate the project from the templates in `URL`. This can be a local path, or a go-getter compatible URL for remote templates (e.g., `[email protected]:gruntwork-io/boilerplate.git//examples/include?ref=master`).",
},
cli.StringFlag{
Name: options.OptOutputFolder,
Expand Down Expand Up @@ -91,8 +95,7 @@ func CreateBoilerplateCli(version string) *cli.App {
// When you run the CLI, this is the action function that gets called
func runApp(cliContext *cli.Context) error {
if !cliContext.Args().Present() && cliContext.NumFlags() == 0 {
cli.ShowAppHelp(cliContext)
return nil
return cli.ShowAppHelp(cliContext)
}

opts, err := options.ParseOptions(cliContext)
Expand Down
38 changes: 19 additions & 19 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ func TestParseBoilerplateConfigAllTypes(t *testing.T) {
// YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation
const CONFIG_ONE_DEPENDENCY = `dependencies:
- name: dep1
template-folder: /template/folder1
template-url: /template/folder1
output-folder: /output/folder1
`

Expand All @@ -311,7 +311,7 @@ func TestParseBoilerplateConfigOneDependency(t *testing.T) {
expected := &BoilerplateConfig{
Variables: []variables.Variable{},
Dependencies: []variables.Dependency{
{Name: "dep1", TemplateFolder: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}},
{Name: "dep1", TemplateUrl: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}},
},
Hooks: variables.Hooks{},
}
Expand All @@ -323,11 +323,11 @@ func TestParseBoilerplateConfigOneDependency(t *testing.T) {
// YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation
const CONFIG_MULTIPLE_DEPENDENCIES = `dependencies:
- name: dep1
template-folder: /template/folder1
template-url: /template/folder1
output-folder: /output/folder1

- name: dep2
template-folder: /template/folder2
template-url: /template/folder2
output-folder: /output/folder2
dont-inherit-variables: true
variables:
Expand All @@ -336,7 +336,7 @@ const CONFIG_MULTIPLE_DEPENDENCIES = `dependencies:
default: foo

- name: dep3
template-folder: /template/folder3
template-url: /template/folder3
output-folder: /output/folder3
skip: "{{ and .Foo .Bar }}"
`
Expand All @@ -350,14 +350,14 @@ func TestParseBoilerplateConfigMultipleDependencies(t *testing.T) {
Dependencies: []variables.Dependency{
{
Name: "dep1",
TemplateFolder: "/template/folder1",
TemplateUrl: "/template/folder1",
OutputFolder: "/output/folder1",
DontInheritVariables: false,
Variables: []variables.Variable{},
},
{
Name: "dep2",
TemplateFolder: "/template/folder2",
TemplateUrl: "/template/folder2",
OutputFolder: "/output/folder2",
DontInheritVariables: true,
Variables: []variables.Variable{
Expand All @@ -366,7 +366,7 @@ func TestParseBoilerplateConfigMultipleDependencies(t *testing.T) {
},
{
Name: "dep3",
TemplateFolder: "/template/folder3",
TemplateUrl: "/template/folder3",
OutputFolder: "/output/folder3",
DontInheritVariables: false,
Variables: []variables.Variable{},
Expand All @@ -382,7 +382,7 @@ func TestParseBoilerplateConfigMultipleDependencies(t *testing.T) {

// YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation
const CONFIG_DEPENDENCY_MISSING_NAME = `dependencies:
- template-folder: /template/folder1
- template-url: /template/folder1
output-folder: /output/folder1
`

Expand All @@ -401,19 +401,19 @@ const CONFIG_DEPENDENCY_MISSING_TEMPLATE_FOLDER = `dependencies:
output-folder: /output/folder1
`

func TestParseBoilerplateConfigDependencyMissingTemplateFolder(t *testing.T) {
func TestParseBoilerplateConfigDependencyMissingTemplateUrl(t *testing.T) {
t.Parallel()

_, err := ParseBoilerplateConfig([]byte(CONFIG_DEPENDENCY_MISSING_TEMPLATE_FOLDER))

assert.NotNil(t, err)
assert.True(t, errors.IsError(err, variables.RequiredFieldMissing("template-folder")), "Expected a RequiredFieldMissing error but got %s", reflect.TypeOf(errors.Unwrap(err)))
assert.True(t, errors.IsError(err, variables.RequiredFieldMissing("template-url")), "Expected a RequiredFieldMissing error but got %s", reflect.TypeOf(errors.Unwrap(err)))
}

// YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation
const CONFIG_DEPENDENCY_MISSING_VARIABLE_NAME = `dependencies:
- name: dep1
template-folder: /template/folder1
template-url: /template/folder1
output-folder: /output/folder1
variables:
- description: Enter foo
Expand All @@ -432,11 +432,11 @@ func TestParseBoilerplateConfigDependencyMissingVariableName(t *testing.T) {
// YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation
const CONFIG_DEPENDENCY_MISSING_OUTPUT_FOLDER = `dependencies:
- name: dep1
template-folder: /template/folder1
template-url: /template/folder1
output-folder: /output/folder1

- name: dep2
template-folder: /template/folder2
template-url: /template/folder2
`

func TestParseBoilerplateConfigDependencyMissingOutputFolder(t *testing.T) {
Expand All @@ -451,15 +451,15 @@ func TestParseBoilerplateConfigDependencyMissingOutputFolder(t *testing.T) {
// YAML is whitespace sensitive, so we need to be careful that we don't introduce unnecessary indentation
const CONFIG_DEPENDENCY_DUPLICATE_NAMES = `dependencies:
- name: dep1
template-folder: /template/folder1
template-url: /template/folder1
output-folder: /output/folder1

- name: dep2
template-folder: /template/folder2
template-url: /template/folder2
output-folder: /output/folder2

- name: dep1
template-folder: /template/folder3
template-url: /template/folder3
output-folder: /output/folder3
`

Expand Down Expand Up @@ -619,8 +619,8 @@ func TestLoadBoilerplateConfigFullConfig(t *testing.T) {
variables.NewStringVariable("baz").WithDescription("example description").WithDefault("default"),
},
Dependencies: []variables.Dependency{
{Name: "dep1", TemplateFolder: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}},
{Name: "dep2", TemplateFolder: "/template/folder2", OutputFolder: "/output/folder2", DontInheritVariables: true, Variables: []variables.Variable{
{Name: "dep1", TemplateUrl: "/template/folder1", OutputFolder: "/output/folder1", DontInheritVariables: false, Variables: []variables.Variable{}},
{Name: "dep2", TemplateUrl: "/template/folder2", OutputFolder: "/output/folder2", DontInheritVariables: true, Variables: []variables.Variable{
variables.NewStringVariable("baz").WithDescription("example description").WithDefault("other-default"),
variables.NewStringVariable("abc").WithDescription("example description").WithDefault("default"),
}},
Expand Down
6 changes: 3 additions & 3 deletions examples/dependencies-dynamic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

{{ .Description }}. It specifies both the
[docs](/examples/docs) and [website](/examples/website) examples as dependencies to show how one boilerplate template
can pull in another, and that you can use interpolation in the `template-folder` and `output-folder` parameters of
dependencies to dynamically specify where to read the template and where to write the output. It also defines all the
variables needed for both of those dependencies at the top level to show how variable inheritance works.
can pull in another, and that you can use interpolation in the `template-url` and `output-folder` parameters of
dependencies to dynamically specify where to read the template and where to write the output. It also defines all the
variables needed for both of those dependencies at the top level to show how variable inheritance works.
10 changes: 5 additions & 5 deletions examples/dependencies-dynamic/boilerplate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ variables:
default: true

- name: WebsiteTemplateFolder
description: The path to the website templates folder. This is used to check that interpolations work in the template-folder parameter of dependencies.
description: The path to the website templates folder. This is used to check that interpolations work in the template-url parameter of dependencies.

- name: WebsiteOutputFolder
description: The path to the website output folder. This is used to check that interpolations work in the output-folder parameter of dependencies.
Expand All @@ -39,26 +39,26 @@ variables:

dependencies:
- name: docs
template-folder: ../docs
template-url: ../docs
output-folder: ./docs
skip: "{{ .SkipAllDependencies }}"
variables:
- name: Title
description: Enter the title of the docs page

- name: website
template-folder: "{{ .WebsiteTemplateFolder }}"
template-url: "{{ .WebsiteTemplateFolder }}"
output-folder: "{{ .WebsiteOutputFolder }}"
skip: "{{ or .SkipAllDependencies .SkipFirstWebsiteDependency }}"
variables:
- name: Title
description: Enter the title of the website

- name: skip-website
template-folder: "{{ .WebsiteTemplateFolder }}"
template-url: "{{ .WebsiteTemplateFolder }}"
output-folder: "{{ .WebsiteOutputFolder }}"
skip: "{{ or .SkipAllDependencies .SkipSecondWebsiteDependency }}"
variables:
- name: Title
description: Enter the title of the website
default: This website dependency should be skipped
default: This website dependency should be skipped
Loading