An open source, cross-platform project generator / scaffolding tool
I’m happy to announce that we’ve we’ve open sourced a cross-platform project generator / scaffolding tool called Boilerplate, licensed under MPL 2.0: https://github.com/gruntwork-io/boilerplate.
We’ve used Boilerplate for years at Gruntwork to generate oft-repeated code, such as Terraform/OpenTofu projects, the Reference Architecture, and vending new AWS accounts and GitHub repos as part of DevOps Foundations. Boilerplate is similar to other project generator tools such as cookiecutter, yeoman, and copier, but with the advantages of being cross-platform (a single standalone binary), with support for typed input variables, validations, scripting, template composition, interactive and non-interactive modes, and more.
In this blog post, I’ll walk you through the following:
Example use cases
Boilerplate is useful whenever you find yourself repeatedly creating the same sort of file and folder structures over and over again, but need to be able to fill in some of the information dynamically, based on user input, such as:
- Generating a new Terraform/OpenTofu module that has all of your company’s patterns and conventions baked in: e.g., folder structure, file naming, documentation, automated testing, etc.
- Generating a new Java project with the standard directory layout.
- Generate a repo and folder structure for each new team or microservice as part of supporting developer self-service.
-
Generating oft-repeated files, such as legal disclaimers or license at the top of each source
file or in
LICENSE.txtfiles.
Basic usage
The basic idea behind Boilerplate is that you create a template folder that contains:
-
A
boilerplate.ymlfile that configures the template, such as the input variables to gather from the user. - Any number of other files and folders that generate the code you want, using Go templating syntax to fill in those input variables where necessary, do loops, do conditionals, and so on.
Let’s go through an example of creating a Boilerplate template to generate a basic OpenTofu module. This could be a good way to help standardize your company around common coding conventions for your modules, including:
-
File naming conventions: e.g.,
main.tf,outputs.tf,variables.tf. - Documentation: e.g.,
README.md. - License: e.g.,
LICENSE.txt.
Create a template folder called tofu-module and put a file called
boilerplate.yml into it with the following contents:
This boilerplate.yml declares three variables to gather from the user:
ModuleName, CopyrightInfo, TofuVersion.
Note how each input variable has an description, type, and
default value.
Next, create a main.tf with the following contents:
The preceding code is configuring the version of OpenTofu to use, but instead of hard-coding the
value, the code above uses
Go templating syntax ({{ ... }}) to fill in the value from the TofuVersion input variable declared earlier in
boilerplate.yml.
Next, create a README.md with the following contents:
This file has more Go templating syntax to fill in data based on the input variables you declared
earlier in boilerplate.yml, including ModuleName and
CopyrightInfo. Note the use of the title and
kebabcase functions: Boilerplate comes with all the
sprig helpers built-in, so this code is
making use of the
title helper and
kebabcase helper to
format the module name in title case or kebab case, respectively.
The code above also contains a simple if-statement, ensuring that the data in
.CopyrightInfo is only rendered if it’s non-empty. Go templating syntax lets
you use conditionals, loops, functions, etc.
You may want to add variables.tf, outputs.tf, and
LICENSE.txt files to your template as well. Have a look at the tofu-module example for the full code sample.
To generate a module from this template folder, run the following command:
The preceding command tells boilerplate to use the template folder
tofu-module and to put the rendered output into the
output folder /tmp/tofu-module. When you run this command,
Boilerplate will to prompt you for each input variable:
Boilerplate shows you the variable name, description, and allows you to interactively type in the
value for each variable. If you don’t want to do this interactively, you can run Boilerplate with
the --non-interactive flag, and instead set the input variables via one or more
--var flags. Alternatively, you can set all the values in a YAML file:
And pass the path of that YAML file to Boilerplate via the --var-file flag:
Boilerplate will go through all the files in your template folder and render them into the output folder:
If you open up the files in the output folder, you’ll see that Go templating syntax is now fully
rendered based on the input variables values you entered. For example, here’s the rendered
main.tf:
And here’s the rendered README.md:
More advanced usage
Now that you’ve seen basic project generation in action, let’s look at a couple of the more advanced features available in Boilerplate:
-
dependencies: create more complicated templates by composing together simpler templates. -
hooks: add scripting to your templates to perform actions that can’t be done with pure templating.
We’ll piggy back on top of the OpenTofu example in the previous section, but now, instead of generating just a single module, we’re going to generate our full recommended folder structure, which includes:
-
modules: The module itself goes into themodulesfolder. You’ll reuse thetofu-moduletemplate from the previous section to create the module. -
examples: An example usage of your module goes into theexamplesfolder. These examples are essentially executable documentation, both showing how to use your code, and giving you both a manual and automated test harness for testing that code. -
test: An automated test for the example code in theexamplesfolder.
Create a new template folder called tofu-example and put a new
boilerplate.yml file in it with the following contents:
This is very similar to the boilerplate.yml from the previous section,
declaring a few input variables at the top, but there’s something new on the bottom: a
hooks section that defines scripts to run, either before code generation (in a
before section) or after code generation (in an
after section). The preceding code uses the after section to
run tofu fmt to apply standard formatting to the generated code.
Add a main.tf file to the tofu-example folder with the
following contents:
This code will “instantiate” the module generated by the tofu-module template
in the previous section. You should also add an outputs.tf file to
tofu-example to proxy through the output variables from the module:
Have a look at tofu-example for the full code sample.
OK, that does it for the tofu-example template. Next, create one more template
folder called tofu-test, with the following boilerplate.yml:
The code above defines a few input variables and makes use of hooks to format
code using goimports. Next, create a file called
{{ .ModuleName | snakecase }}_test.go (yes, you can use Go templating syntax in
file and folder names!) and put the following contents in it:
Have a look at the tofu-test example for the full code sample.
This is Go code that uses the Terratest library to create an automated test for your code that does the following:
- Run
tofu init. - Run
tofu apply. -
Check that the output variable
example_outputis equal to the value “Hello, World”. - Run
tofu destroyat the end of the test.
OK, you now have three project templates. To put them together, create one final template folder
called tofu-module-full and put the following
boilerplate.yml in it:
Have a look at the tofu-module-full example for the full code sample.
The code above gathers a few input variables, as you’ve seen before, but also introduces something
new: dependencies. The dependencies section allows you to
specify other Boilerplate templates that should be rendered as part of rendering the current one.
This allows you to combine simpler templates together into more complicated ones.
The code above uses all three templates you created earlier, configuring a different
output-folder for each of these dependencies: the
tofu-module template will be rendered into the
modules folder, the tofu-example template will be rendered
into the examples folder, and the tofu-test template will be
rendered into the test folder.
Note that, by default, any input variables in the top-level template are automatically passed down
to the templates declared in dependencies, so, for example, the
ModuleName, CopyrightInfo, and
TofuVersion gathered in tofu-module-full automatically get
passed down to tofu-module. However, you can set or override variables by
adding a variables section to each dependency, which is how the code above
configures the proper values for ModuleSource and
ExamplePath for tofu-example and
tofu-test, respectively.
Run Boilerplate to render your tofu-module-full template:
Here are the files and folders that get generated:
You now have a fully working and testable OpenTofu module! Jump into
examples/vpc to manually test and see if it is working:
Looks good. Next, jump into the test/vpc folder to run an automated test to
check it’s working. On the very first run, you need to initialize the Go module and download
dependencies by running go mod init and then go mod tidy:
And now you can run the automated tests with go test:
And there you go! A fully working OpenTofu module, example, and test, all automatically generated for you.
Give it a shot!
Of course, you can use Boilerplate for generating any type of code, and not just Terraform/OpenTofu. Now that Boilerplate is open source, give it a shot, and see what you can come up with:
https://github.com/gruntwork-io/boilerplate
Let us know how it works for you. Better yet, PRs are very welcome to make it even better!



- No-nonsense DevOps insights
- Expert guidance
- Latest trends on IaC, automation, and DevOps
- Real-world best practices

