Back to Blog
Open-Source
Devops
Tools
Go
Cli

Introducing Boilerplate

Yevgeniy "Jim" Brikman
Yevgeniy
Brikman
,
Co-Founder
March 6, 2024

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.txt files.

Basic usage

The basic idea behind Boilerplate is that you create a template folder that contains:

  • A boilerplate.yml file 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:

  1. dependencies: create more complicated templates by composing together simpler templates.
  2. 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 the modules folder. You’ll reuse the tofu-module template from the previous section to create the module.
  • examples: An example usage of your module goes into the examples folder. 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 the examples folder.

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:

  1. Run tofu init.
  2. Run tofu apply.
  3. Check that the output variable example_output is equal to the value “Hello, World”.
  4. Run tofu destroy at 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!