rendr

rendr is a scaffolding tool which allows generating entire projects (or anything else) from blueprints, using standard templating engines and simple customization via parameters. It is generic enough to apply to a wide variety of applications and tech stacks, but powerful and flexible enough to provide value, fast. The tool itself is really a generic template renderer. It's up to you, the template creator, to decide what to put in your template.

Use cases

Here are just a few possible use cases:

  • Enable rapid spin-up of new projects, complete with CI/CD pipelines, code quality gates, security analysis, and more
  • Ship "Hello, World!" projects immediately to production, enabling instant iteration on features
  • Include CI/CD standards baked into projects from the start, easily kept up to date
  • Simplify repeated patterns like creating new microservices, libraries, or submodules on an existing project

Contributing

Feedback and pull requests are welcome! Let us know if you have issues using the tool, or see use cases that are not yet supported. We'd love to expand its usefulness!

Installation

Homebrew

To install the latest release:

brew install jamf/tap/rendr

Cargo (from source)

Again, the latest release can be installed with:

cargo install rendr

Binaries for Linux and macOS

Alternatively, you can download the CLI binary directly from the Releases page and put it on your system path.

Rendering Blueprints

See the options

View the available commands with rendr --help:

❯ rendr --help
A project scaffolding tool

USAGE:
    rendr [SUBCOMMAND]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    create              Creates a project from a blueprint
    create-blueprint    Creates a new blueprint scaffold
    help                Prints this message or the help of the given subcommand(s)
    info                Displays blueprint info for an existing project

To see usage for a subcommand, use rendr [command] --help:

❯ rendr create --help
rendr-create
Creates a project from a blueprint

USAGE:
    rendr create [FLAGS] [OPTIONS] --blueprint <blueprint> --dir <dir>

FLAGS:
        --debug          Enables debug logging
        --git-init       Initializes a Git repository in the rendered project
    -h, --help           Prints help information
        --no-git-init    Skips initializing Git repository in the rendered project
    -V, --version        Prints version information
    -w, --watch          After generating the project, watch the blueprint files for changes and regenerate the project
                         on change

OPTIONS:
    -b, --blueprint <blueprint>    The location of the blueprint (a Git repo or a local directory)
    -d, --dir <dir>                The output directory name
    -n, --name <name>              The name of the project
    -p, --password <password>      The password for Git authentication (insecure - use the GIT_PASS env var instead)
    -k, --ssh-key <ssh-key>        The path to the private SSH key for Git auth
    -u, --user <user>              The user for Git authentication
    -v, --value <value>...         Custom value provided the blueprint (flag may be repeated)

Render a project

Use rendr create to render a project from a blueprint. The basic usage looks like this:

rendr create --blueprint https://github.com/your/template --dir my-project

Provide custom values

By default, if you don't provide any values when running rendr create, you will be prompted for each required value. To provide values non-interactively, use the -v flag. This flag is repeated once for each value.

rendr create -b https://github.com/your/template -d my-project -v name:foo -v version:1.0.0

Important! A note about scripts

Blueprints can contain scripts that execute as part of the rendering process. Blueprint creators can use this mechanism to provide custom functionality when the blueprint is rendered, like creating a repository or configuring a CI pipeline. However, be careful to only use templates from trusted sources, as these scripts execute with the same privileges as the user that invoked them. A malicious template script could modify files on your system, send your personal data somewhere, install malware, etc.

Upgrading to a new blueprint version

If a new version is released of the blueprint used in your project, your project can be easily upgraded using the rendr upgrade command. Blueprint authors can include new files, or make custom upgrades to existing files in a project. The upgrade logic is entirely up to the blueprint author.

This is a powerful mechanism that allows maintaining common code, configuration and best practices across multiple codebases.

Use rendr upgrade --help for more details on usage.

Blueprint developer mode

If you are developing a blueprint, you will likely want to edit your templates and then render them to see the output, make more edits, render them again, in a continuous loop. To make this easier, use the --watch (-w) flag for rendr create.

rendr create --blueprint ../my-blueprint --dir foo --watch
Output directory: "foo". Creating your new scaffold...
Success. Enjoy!
Watching for blueprint changes...

The next time you make changes to the files in the my-blueprint directory, the entire blueprint will be re-rendered automatically. Use Ctrl-C to cancel the watch command.

Creating blueprints

Blueprints consist of template files, scripts, and metadata.

  • Template files live in the template directory, and are rendered by the templating engine
  • Metadata is provided in a metadata.yaml file in the root of the blueprint directory. It lists specific values that can be provided to the template, among other things.
  • Scripts live in a scripts directory in the blueprint directory. This is the place to customize the generated files or automate followup actions (like creating a remote repository or pipeline).

With these basic features, blueprints are already highly customizable! If you have other use cases that are not supported, feel free to let us know in the issues!

Template

Each blueprint has a template directory at its root level. This directory contains all the template files and directories that will be rendered into the final project.

Template engine

The template engine used by rendr is Mustache, "The logic-less template engine". See the link for the (extremely simple) Mustache manual and demos to get started.

To get started even without reading the manual, it's enough to know that Mustache uses "tags", which are indicated by double "mustaches", like {{ name }} or {{ my_custom_url }}. When given a context of key/value pairs, Mustache replaces the tags with the values from the context. For example, given this template:

Hello {{ name }}!

and this context:

name:foo

will produce the following text:

Hello foo!

Pretty simple.

rendr handles passing the context to the templating engine for you. The way to provide values to the rendering context is by using rendr's "values", which are defined in the metadata.yaml and provided by the user via prompts or the --value flag when rendering the blueprint. See the metadata format and the command line usage for more details there.

Sample template directory

Here's a concrete example of creating templates in rendr. We have a template directory with just two files:

template
├── main.go
└── README.md

The README.md has this for contents:

# Project {{ name }}

Welcome to your new project!

Please run `go run main.go` to run the app at http://localhost:{{ port }}.

You will notice that the template has two tags, name and port. These must be defined in the metadata.yaml file like this:

...
values:
- name: name
  description: The name of the project
  required: true
- name: port
  description: The port where the app runs
  default: "8000"

These values are then provided by the user rendering the blueprint like this:

rendr create --dir foo --blueprint <url> --value name:foo --value port:3000

This will result in all the files in the template directory being rendered by the template engine and copied into the specified new project directory (in this case named foo):

foo
├── main.go
└── README.md

The contents of the rendered README.md look like this:

# Project foo

Welcome to your new project!

Please run `go run main.go` to run the app at http://localhost:3000.

Any files or directories you like can go in the template directory, and they will rendered into the generated project directory.

Excluding files

Sometimes your template will contain files that you don't want to render with Mustache, and want them to be copied over to the rendered project without modification. Some examples of this would be binary files like images, or third-party files that are included in the project. These files can be excluded by using the exclusions: list in metadata.yaml. See the Metadata docs for details.

Dynamic file or directory names

Sometimes you want your rendered files or directories to have custom names based on the values the user supplied. This can't be done using the Mustache templating engine directly. The way to accomplish this is to rename the files or directories in the post-render.sh script. See Scripts for more details.

Additional use cases

Between the Mustache templating and customization via pre- and post-render scripts, rendr provides immense flexibility to generate projects for nearly any tech stack. If you have specific use cases that you find are not supported, please open a issue over at the GitHub Issues, describe your problem and what functionality is missing, and we will consider it.

Support

Still need help or more examples? Open an issue at rendr's GitHub Issues and describe your problem, and we'd be happy to help!

Metadata

Each blueprint has a metadata.yaml file at its root level. This file defines things like the blueprint name, version and description, as well as values which the user can provide when rendering the blueprint.

Here is a simple example:

name: foo
version: 1
author: thecodesmith
description: A simple microservice blueprint
values:
- name: name
  description: The name of your project
  required: true
- name: port
  description: The port where the service listens
  default: "8000"
exclusions:
- "images/*"

Let's break it down:

ParameterDescription
nameThe blueprint name
versionThe blueprint version
authorThe blueprint author
descriptionThe blueprint description
valuesA list of values that will be provided to the template rendering
exclusionsA list of glob patterns to exclude from rendering
upgradesAn optional list of upgrade scripts

There can be any number of items in the values list. The structure of each item looks like this:

ParameterDescription
nameThe value key name
descriptionThe description of this value; becomes the interactive prompt text
requiredWhether the value must be provided by the user (true or false)
defaultThe default value if one is not provided by the user

The required field defaults to false, and can be omitted.

The default field can also be omitted. If there is no default, the user will be prompted for this value (if not provided with the -v flag).

The upgrades field can have any number of upgrade scripts in the list. The structure of each upgrade script is like this:

Parameter | Description version | The blueprint version where this script will be run (format: integer) script | The name of the script file in the blueprint scripts directory executable | The executable (program) to use to run the script

Scripts

Blueprints can optionally contain scripts. This is a powerful mechanism that enables blueprint creators to customize how the blueprint is rendered.

Scripts live in the scripts directory at the root level of the blueprint.

Currently only pre-render.sh and post-render.sh scripts are supported. This can be expanded in the future if there are other integration points where scripts are useful.

The pre-render script

If a script named pre-render.sh exists in the blueprint's scripts directory, it will be run immediately after the generated project directory is created, and before any template files are rendered.

The values used to render the template are also provided to the script as environment variables. So, if a user specifies --value foo:42, there will be a variable named foo that can be accessed in the script like this:

#!/bin/sh

echo "The value of foo is $foo!"

This script will print:

The value of foo is 42!

NOTE: The script must be executable to be run. Make it executable like this:

chmod +x scripts/pre-render.sh

The post-render script

If there is a script named post-render.sh in the blueprint's scripts directory, it will be run automatically after the template files are rendered by the templating engine.

Use the post-render script to customize files and directories, or to create remote repositories, CI/CD pipelines, etc.

For more details, see the notes above on the pre-render script regarding environment variables and executable file permissions, which apply here just the same.

Development

This chapter includes information pertaining to rendr contributors.

Local iteration

You'll need Rust and Cargo. You can get the whole toolchain here!

After checking out this repository, cd into the directory. You can use the standard Cargo commands. Below you'll find the most important ones.

Compile the whole project

This should get you the usual dev build.

cargo build

This one is the same, but it produces an optimized release build. It takes more time.

cargo build --release

Tests

Run the unit tests and documentation tests.

cargo test

Docs

Build the reference manual and open it in your browser.

cargo doc --open

Run the application

Run the application.

cargo run

Get the helpfile.

cargo run -- -h

Get the help text for the create subcommand.

cargo run -- create -h

Initialize a project from the Go microservice blueprint.

cargo run -- create --blueprint https://github.com/jamf/rendr-sample-blueprint-go-microservice.git --dir my-project -v name:foo