Skip to main content

Render parametrized Jinja2 templates at the CLI

Project description

Pythons Build Status Scrutinizer Code Quality Code Coverage Code Intelligence Status

inji

Inji renders static jinja2 templates.

Templates may be parametrized in which case inji can be given one or more YAML vars files to source parameters used in the templates.

Useful in CI/CD scenarios where DRY configuration is necessary and templating/parametrization is a usable pattern.

Installation

python3 -m pip install inji   #  or use pip3/pip, requires python >= 3.6 (may work on 3.5)

Usage

Render a Jinja2 template
$ system=$(< /etc/hostname)
$ startime=$(date +%FT%T%z)

$ echo '
node : {{ node }}
time : {{ time }}
' | inji -k node="$system" -k time="$startime"

Or from a file

$ inji --template=jello-star-motd.j2 -k ... > /etc/motd
Render a template passing vars in a JSON object

JSON allows you to pass configuration in complex/multi-dimensional objects.

$ echo '
node : {{ node.name }}
time : {{ node.time }}
' > template.j2

$ inji -t template.j2 -j '{
  "node":{
    "name":"'$(</etc/hostname)'", // Note the "interpolation" of shell commands
    "time":"'$(date)'"            // here with the quoting.
  }
}'
Render a template passing vars from a YAML file
inji --template=motd.j2 --vars-file=production.yaml

vars files must contain valid (YAML documents and can hold either simple scalars or collections. Your jinja templates can then reference parameters/variables inside these varsfiles depending on your context.

e.g.

A typical case is building multiple docker images - without the assistance of a templating tool, you may have to keep and maintain several Dockerfiles and corresponding build commands for each image - but imagine the yucky prospects of maintaining that kind of WET approach.

Instead, to DRY things up, consider a templated Dockerfile like this

$ cat Dockerfile.j2
FROM {{ distribution }}:{{ version }}    # These jinja2 vars are set by inji
                                         # from travis' environment variables

MAINTAINER http://my.org/PlatformOps

ENV container       docker
ENV distribution {{ distribution }}
ENV version      {{ version }}-{{ ref }} # `ref` is set at inji's CLI

{% if distribution == 'centos' %}        # Conditional execution
RUN yum -y update && yum clean all
{% endif %}

{% if distribution == 'debian' %}
RUN apt update -qq && apt upgrade -y
{% endif %}

{% if distribution == 'fedora' %}
RUN dnf -y update && dnf clean all
{% endif %}

RUN my-awesome-build-script {{ distribution }} {{ version }}

ENTRYPOINT ["/opt/bin/myserv"]

Then a Travis CI build job using inji would look like this.

$ cat .travis.yml
---
language: python
sudo: required
services:
  - docker

env:

  - distribution: centos
    version: 7

  - distribution: centos
    version: 8

  - distribution: debian
    version: stretch

  - distribution: debian
    version: buster

  - distribution: fedora
    version: 28

  - distribution: fedora
    version: 29

before_script:
  - pip install inji

script:
  - >
    inji --template Dockerfile.j2 --kv-config ref="$CI_COMMIT_REF_NAME" |
      docker build --pull --tag "myimage:$distribution-$version" -
  - docker push --all-tags "myimage"
...
Render a template using variables from multiple vars files
$ inji --template=nginx.conf.j2    \
      --vars-file=web-tier.yaml  \
      --vars-file=eu-west-1.yaml \
      --vars-file=prod.yaml  > /etc/nginx/sites-enabled/prod-eu-west-1.conf

Here, variables from files specified later on the command-line will override those from files specified before (prod.yaml supercedes eu-west-1.yaml, etc).

This is especially useful in managing layered configuration where different tiers of a deployment enforce/provide different parameters.

Using directory configuration overlays

An inevitable practice is using multiple smaller configuration files to avoid the growing pains of huge configuration files, to source configuration from different sources, improve churn, reduce friction, etc, etc, etc. Here, explicitly naming configuration files for inji to use becomes a new pain point.

With overlay directories, inji naively reads in all yaml files from a directory and compiles a combined configuration object before using that in rendering a template.

$ tree conf/
conf/
├── dev
│   ├── service-discovery.yaml
│   ├── load-balancer-ip.yaml
│   ├── modules.yaml
│   └── sites.yaml
├── prod
│   ├── service-discovery.yaml
│   ├── load-balancer-ip.yaml
│   ├── modules.yaml
│   └── sites.yaml
└── stage
    ├── service-discovery.yaml
    ├── load-balancer-ip.yaml
    ├── modules.yaml
    └── sites.yaml
3 directories, 9 files

$ inji  --template=nginx.conf.j2 \  # here $CI_ENV is be some variable your CI system
        --overlay="conf/$CI_ENV" \  # sets holding the name of the target deployment
        > nginx.conf                # e.g. dev, stage, prod

Parameter sourcing and precedence order

Parameters can be specified and sourced from multiple places. The order of parameters sourced and their precedence is 12-factor friendly and is done as set out here (from lowest-to-highest precedence).

  • Default configuration file (.inji.y*ml or inji.y*ml) in current directory.
  • Overlay directories - last file sorted alphabetically wins
  • Named configuration file - last one specified wins
  • Environmental variables - last one specified wins
  • CLI JSON strings - last one specified wins
  • CLI KV strings - last one specified wins
  • Template parameters - last one specified wins (Jinja2 order)

Fuller Example

This is a very contrived example showing how to orient a .gitlab-ci.yml towards business workflows - a multi-stage CI/CD deployment pipeline expedited by Gitlab.

Note the use of complex objects in the parameters.

$ cat .gitlab-ci.vars
---
project:
  name: snowmobile
  id:   https://gitlab.com/snowslope/snowmobile.git
  url:  https://snowmobile.example.com/

deployer:
  image: snowmobile-deployer:latest

# This serves as a more succinct business abstract

environments:

  - name: snowmobile-env_dev
    type: dev
    region: us-east-1
    ci_url:  https://snowmobile-dev.env.example.com/
    branches:
      - /^[0-9]+\-.*/  # Match feature branches that have
                       # a ticket number at the start of the branch name

  - name: snowmobile-env_stage
    type: stage
    region: eu-west-2
    ci_url:  https://snowmobile-stage.env.example.com/
    branches:
      - master         # Deploy to stage only after merge request is complete

  - name: snowmobile-env_prod
    type: production
    region: eu-west-1
    ci_url:  https://snowmobile.env.example.com/
    branches:
      - tags           # Only deploy tagged releases to production

...
$ cat .gitlab-ci.j2
---

# >>>>>>>>>>>>>
# >> WARNING >>   This file is autogenerated!!
# >> !!!!!!! >>   Edit .gitlab-ci.{j2, vars} instead and `make gitlab-ci-yml`
# >>>>>>>>>>>>>   All edits will be lost on the next update

# This template when rendered with parameters from the above varsfile
# produces the actual fuller .gitlab-ci.yml file

stages:
{% for env in environments %}
  - '{{ env.name }}:provision'
  - '{{ env.name }}:validate'
  - '{{ env.name }}:deploy'
  - '{{ env.name }}:test'
  - '{{ env.name }}:destroy'
{% endfor %}
  - 'docs:publish'

variables:
  project:             {{  project.name }}
  project_id:          '{{ project.id   }}'
  project_url:         {{  project.url  }}

{% for env in environments %}

# {{ env.type }} Run tenant provisioning, runner setup on shared runner
'provision:{{ env.name }}':
  stage: '{{ env.name }}:provision'
  environment:
    name: {{ env.type }}/$SITE/$CI_COMMIT_REF_NAME
    url:  {{ env.ci_url }}
  variables:
    SITE:                {{ env.name }}
    CI_ENVIRONMENT_TYPE: {{ env.type }}
    REGION:              {{ env.region }}
    CI_URL:              {{ env.ci_url }}
  image:  {{ deployer.image }}
  script:
    - snowmobile-ctl provision

  {% if env.branches -%}
  only: {{ env.branches }}
  {% endif %}

# {{ env.type }} Run deployment
'deploy:{{ env.name }}':
  stage: '{{ env.name }}:deploy'
  # ...
  script:
    - snowmobile-ctl deploy

# {{ env.type }} Run smoke tests
'test:{{ env.name }}':
  stage: '{{ env.name }}:test'
  # ...
  script:
    - snowmobile-ctl smoke-test

{% endfor %}

# vim:ft=yaml
...

To then update the .gitlab-ci.yml, run inji with the above.

$ inji -t .gitlab-ci.j2 \
       -v .gitlab-ci.vars > .gitlab-ci.yml

WARNING: Edits to the above files are not automatically reflected in .gitlab-ci.yml and some other mechanism using inji to render the latter needs to be run before Gitlab acts upon it. e.g. Using a git commit hook or gitattribute filter , etc.

e.g.

$ cat .githooks/pre-commit
#!/bin/sh

set -e

inji -t .gitlab-ci.j2 -v .gitlab-ci.vars > .gitlab-ci.yml

# NOTE: git diff --exit-code ... returns 1 if file has changed
if ! git diff --exit-code .gitlab-ci.yml; then
  git add .gitlab-ci.yml &&
    git commit --amend -C HEAD --no-verify
fi

Etymology

Why the name inji?

inji is named in keeping of the UNIX tradition of short (memorable?) command names. In this case, it is a 4-letter near-anagram of Jinja.

inji (/ɪndʒi:/) also happens to be the Dravidian word and ostensibly the source of the English word Ginger, of which jinja is a partial homophone.

TODO

Only potential ideas so far - No commitment is made.

  • Read config from JSON/TOML files?
  • Manage collections of templates e.g. *.j2
  • Dry-run syntax checking

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

inji-0.5.0.post36.tar.gz (17.8 kB view details)

Uploaded Source

Built Distribution

inji-0.5.0.post36-py3-none-any.whl (17.7 kB view details)

Uploaded Python 3

File details

Details for the file inji-0.5.0.post36.tar.gz.

File metadata

  • Download URL: inji-0.5.0.post36.tar.gz
  • Upload date:
  • Size: 17.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/49.6.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.8.5

File hashes

Hashes for inji-0.5.0.post36.tar.gz
Algorithm Hash digest
SHA256 95b9817dd271772ce288f416d90a5338b693fc6ef3deb64cca8361d371c21552
MD5 0fe9b7183794a13146c918aed2b733fb
BLAKE2b-256 53fb7c7b5295f6a6e94622236d1f19e2a999fd80885cb5063b28b588ce4abb9c

See more details on using hashes here.

File details

Details for the file inji-0.5.0.post36-py3-none-any.whl.

File metadata

  • Download URL: inji-0.5.0.post36-py3-none-any.whl
  • Upload date:
  • Size: 17.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/49.6.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.8.5

File hashes

Hashes for inji-0.5.0.post36-py3-none-any.whl
Algorithm Hash digest
SHA256 7fd78a98557f716b025d6b688dabcf079910481e6ed70aa08de079d062fdc564
MD5 80c6b96fe3f4169fc02c9a8451b736de
BLAKE2b-256 181586a9bf3d771fe81a4f1fb5d8767586384732a3f19dd5980b21269e6ed208

See more details on using hashes here.

Supported by

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