Skip to main content

Convert config files into Ansible defaults and Jinja2 templates.

Project description

JinjaTurtle

JinjaTurtle logo

JinjaTurtle is a command-line tool that helps turn existing native configuration files into reusable configuration-management templates.

By default it generates:

  • a Jinja2 template; and
  • an Ansible defaults YAML file containing the variables used by that template.

JinjaTurtle does not try to replace configuration-management tools. Its job is to speed up the boring first pass: take a real config file, discover the values inside it, replace those values with variables, and write the corresponding variable data beside the template.

How it works

JinjaTurtle examines a source config file and keeps the original structure as much as possible.

For the default Jinja2/Ansible mode:

  1. The config file is parsed.
  2. Variable names are generated from the config keys and paths.
  3. Those variable names are prefixed with --role-name, which should usually match your Ansible role name.
  4. A Jinja2 template is generated with values replaced by {{ variable }} expressions.
  5. An Ansible defaults YAML file is generated with those variables and the original values.

By default, the generated variable data and template are printed to stdout. Use --defaults-output and --template-output to write them to files.

Jinja2 / Ansible example

Say you have a php.ini file and you are inside an Ansible role with defaults/ and templates/ directories:

jinjaturtle php.ini \
  --role-name php \
  --defaults-output defaults/main.yml \
  --template-output templates/php.ini.j2

Given a source value such as:

memory_limit = 256M

JinjaTurtle will produce a template value like:

memory_limit = {{ php_memory_limit }}

and defaults data like:

php_memory_limit: 256M

What sort of config files can it handle?

JinjaTurtle supports common structured and semi-structured config formats:

  • TOML
  • YAML
  • INI-style files
  • JSON
  • XML
  • Postfix main.cf
  • systemd unit files, such as *.service, *.socket, *.timer, and related unit types
  • OpenSSH-style config files, including ssh_config, sshd_config, and common *.conf snippets detected as SSH config

For ambiguous extensions such as *.conf, JinjaTurtle uses lightweight content sniffing. You can always force a handler with --format.

For YAML, XML, TOML, INI-style, and other supported structured files, JinjaTurtle will attempt to generate loops when a repeated structure looks homogeneous enough. If it is not confident, it falls back to flattened scalar variables.

Some very complex files will still need manual cleanup. The goal is to speed up conversion into Jinja2 templates, not to guarantee a perfect final module without review.

JSON, quoting, and type preservation

JinjaTurtle tries to preserve rendered config types.

For JSON, it uses JSON-aware expressions rather than plain string substitution. This avoids generating invalid JSON such as:

{"enabled": True}

when the correct rendered JSON should be:

{"enabled": true}

This uses Ansible-style JSON filters.

Can I convert multiple files at once?

Yes. Pass a directory instead of a single file and JinjaTurtle will convert the files it understands in that directory.

jinjaturtle ./config-dir \
  --role-name myrole \
  --defaults-output defaults/main.yml \
  --template-output templates/

Use --recursive to recurse into subdirectories.

In folder mode, variables for multiple files of the same type are grouped under an items-style structure in the generated YAML so that the resulting templates can be used with loops in Ansible.

For example:

- name: Render configs
  template:
    src: config.j2
    dest: "/somewhere/{{ item.id }}"
  loop: "{{ myrole_items }}"

How to install it

Ubuntu/Debian apt repository

sudo mkdir -p /usr/share/keyrings
curl -fsSL https://mig5.net/static/mig5.asc | sudo gpg --dearmor -o /usr/share/keyrings/mig5.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/mig5.gpg] https://apt.mig5.net $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/mig5.list
sudo apt update
sudo apt install jinjaturtle

Fedora

sudo rpm --import https://mig5.net/static/mig5.asc

sudo tee /etc/yum.repos.d/mig5.repo > /dev/null << 'EOF'
[mig5]
name=mig5 Repository
baseurl=https://rpm.mig5.net/$releasever/rpm/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mig5.net/static/mig5.asc
EOF

sudo dnf upgrade --refresh
sudo dnf install jinjaturtle

From PyPI

pip install jinjaturtle

From this git repository

Clone the repo and then run inside the clone:

poetry install

Full usage info

usage: jinjaturtle [-h] [-r ROLE_NAME] [--recursive]
                   [-f {ini,json,toml,yaml,xml,postfix,systemd,ssh}]
                   [-d DEFAULTS_OUTPUT] [-t TEMPLATE_OUTPUT]
                   config

Convert a config file into an Ansible defaults file and Jinja2 template.

positional arguments:
  config                Path to a config file OR a folder containing supported
                        config files. Supported: .toml, .yaml/.yml, .json,
                        .ini/.cfg/.conf, .xml, ssh_config/sshd_config

options:
  -h, --help            show this help message and exit
  -r, --role-name ROLE_NAME
                        Role name / variable prefix. In Jinja2 mode this is
                        usually the Ansible role name. Defaults to jinjaturtle.
  --recursive           When CONFIG is a folder, recurse into subfolders.
  -f, --format {ini,json,toml,yaml,xml,postfix,systemd,ssh}
                        Force config format instead of auto-detecting from
                        filename.
  -d, --defaults-output DEFAULTS_OUTPUT
                        Path to write the generated variable YAML. If omitted,
                        it is printed to stdout.
  -t, --template-output TEMPLATE_OUTPUT
                        Path to write the generated config template. If omitted,
                        it is printed to stdout.

Additional supported formats

JinjaTurtle also templates some common bespoke config formats:

  • Postfix main.cf (main.cf) → --format postfix
  • systemd unit files (*.service, *.socket, etc.) → --format systemd
  • OpenSSH config (ssh_config, sshd_config, and detected snippets) → --format ssh

For ambiguous extensions like *.conf, JinjaTurtle uses lightweight content sniffing. You can always force a specific handler with --format.

Security model

JinjaTurtle is frequently pointed at config files that were harvested from real systems, where some content may be influenced by an untrusted party (a hostname, a login banner, a GECOS comment, a "Managed by ..." note). It is therefore designed so that source content cannot turn into executable template code.

Two guarantees matter:

  1. Values are data, never code. Every config value is replaced with a {{ variable }} placeholder in the template, and the original value is stored separately in the defaults data. When the template is later rendered, the placeholder prints the value as a literal string; Jinja2 does not recursively render the contents of a variable, so a payload sitting inside a value is inert.

  2. Verbatim text is neutralised. To preserve formatting, JinjaTurtle copies comments, blank lines, headers and any unrecognised lines from the source into the template. Any template metacharacters in that copied text ({{ }}, {% %}, {# #} ) are escaped so they render as the literal characters the author wrote, rather than executing.

Consumer responsibilities

The value guarantee above relies on the downstream renderer being single-pass, which is the normal case:

  • Ansible: rendering a template with template:/ansible.builtin.template is single-pass. For defence in depth, treat the generated defaults as untrusted input — Ansible already does not re-template variable contents by default. If you build your own var structures from this data and pass them through additional templating, mark untrusted values with the !unsafe tag so they are never re-evaluated.

In short: render JinjaTurtle output exactly once. Do not feed it back through another templating pass.

IMPORTANT: Always review both the original config files, then the resulting templates generated by JinjaTurtle, before integrating them into your config management system!

Found a bug, have a suggestion?

You can e-mail me; see pyproject.toml for details. You can also contact me on the Fediverse:

https://goto.mig5.net/@mig5

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jinjaturtle-0.7.0.tar.gz (70.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jinjaturtle-0.7.0-py3-none-any.whl (81.8 kB view details)

Uploaded Python 3

File details

Details for the file jinjaturtle-0.7.0.tar.gz.

File metadata

  • Download URL: jinjaturtle-0.7.0.tar.gz
  • Upload date:
  • Size: 70.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.13.5 Linux/6.18.35-1.qubes.fc41.x86_64

File hashes

Hashes for jinjaturtle-0.7.0.tar.gz
Algorithm Hash digest
SHA256 6f27587d64785b8f73bf02faa79a918c300e612e61b882e81fe2644b66124836
MD5 f332c77c535aa15a9741d91afa4c1c8f
BLAKE2b-256 779f84a045bb86a6566f72cf9495a37551508084caf8466b7db4ebf34a5652f8

See more details on using hashes here.

File details

Details for the file jinjaturtle-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: jinjaturtle-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 81.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.13.5 Linux/6.18.35-1.qubes.fc41.x86_64

File hashes

Hashes for jinjaturtle-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a04a3e81d8126c0f47ad362b671aefeaf73684b7041429725598fed18a5387c8
MD5 28af42ef1eb0d6284431201a4b1a537b
BLAKE2b-256 ab263a0bf745ce845c305a26c3aecd5fa5f37ebb1ebf829886f07920b16271e4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page