Infrastructure App Development
An infrastructure app is a Jinja template that generates one or more Fix Inventory commands which are then executed by Fix Inventory one after the other.
Check out the Fix Inventory Infrastructure Apps git repository for a list of apps.
Using fixinventoryappbundler
(pip install fixinventoryappbundler
) you can bundle and dry-run (using fixinventoryapprunner
) your app locally.
fixinventoryappbundler
takes one or more Fix Inventory app directories and bundles them into a single JSON file. This file can be hosted on any http server and used as an app index URL.
Development Workflow​
-
Create a new directory for app development:
$ mkdir fixinventory-app-development
$ cd fixinventory-app-development -
Create and activate a new Python virtual environment:
$ python3 -m venv fixinventory-apps-venv
$ source fixinventory-apps-venv/bin/activate -
Install
fixinventoryappbundler
:$ pip install fixinventoryappbundler
-
Check out the
fixinventory-apps
GitHub repository:$ git clone https://github.com/someengineering/fixinventory-apps.git
-
Add or modify an app in the
fixinventory-apps
directory.tipYou can perform a dry run of the
cleanup-untagged
app for sample infrastructure app output:$ fixinventoryapprunner --path fixinventory-apps/cleanup-untagged/
-
Bundle all apps into a single
index.json
file:$ fixinventoryappbundler --path fixinventory-apps/ --discover > index.json
-
From within Fix Inventory Shell, install an app using the
app install
command:Installing an app from a custom index URL> app install <app_name> --index-url <file://...>
noteThe
--index-url
argument can be used to specify a custom app index URL.By default, Fix Inventory uses the official app index URL.
For local development, the index URL can point to a local JSON file using
file://...
.
Extra Functions and Variables​
A Fix Inventory Infrastructure App has access to a couple of extras that are not part of the standard Jinja library:
search()
- Search the Fix Inventory Graph for resources. Returns a generator that yields resources (and edges if requested).parse_duration()
- Parse a duration string (e.g.2d4h
or7 weeks
) into a timedelta object that can be compared.config
- The app configuration (if any).args
- The command line arguments passed to the app (if any).stdin
- A generator representing the standard input passed to the app (if any).
Fix Inventory Infrastructure Apps also have access to the Expression Statement and Loop Controls Jinja extensions.
Directory Structure​
An infrastructure app is a directory that contains the following files:
README.md
- A markdown file that describes the app.app.yaml
- A YAML file that contains the app manifest.app.jinja2
- A Jinja template that generates Fix Inventory commands.app.svg
- A vector graphics icon for the app.
App Manifest​
The app manifest is a YAML file that contains the following fields:
name
- The name of the app.description
- A short description of the app.version
- The version of the app.language
- The language of the app. Currently onlyjinja2
is supported.license
- The license of the app.authors
- A list of authors.url
- The URL of the app.categories
- A list of categories the app belongs to.default_config
- The default configuration of the app.config_schema
- The configuration schema of the app.args_schema
- The command line arguments schema of the app.
Example Apps​
Execute a Static Search​
search /metadata.expires < "@NOW@" | clean "Resource is expired"
name: cleanup-expired
description: "This app cleans up resources that have expired."
version: "1.0.0"
language: jinja2
license: "Apache 2.0"
authors: ["someengineering"]
url: "https://fixinventory.org/"
categories: ["cleanup"]
default_config:
config_schema:
args_schema:
Clean Up Abandoned AWS CloudWatch Instance Alarms​
{%- set config = config["cleanup_aws_alarms"] %}
{%- for cloud, accounts in config["clouds_and_accounts"].items() %}
{%- for account in accounts %}
search is(aws_cloudwatch_alarm) and /ancestors.account.reported.id == "{{account}}" and /ancestors.cloud.reported.id == "{{cloud}}" and cloudwatch_dimensions[*].name = InstanceId with (empty, <-- is(aws_ec2_instance)) | clean "Abandoned CloudWatch Instance Alarm"
{%- endfor %}
{%- endfor %}
name: cleanup-aws-alarms
description: "This plugin marks all abandoned AWS CloudWatch Instance Alarms for cleanup."
version: "1.0.0"
language: jinja2
license: "Apache 2.0"
authors: ["someengineering"]
url: "https://fixinventory.org/"
categories: ["cleanup"]
default_config:
cleanup_aws_alarms:
clouds_and_accounts:
aws:
- '1234567'
- '567890'
config_schema:
- fqn: cleanup_aws_alarms
bases: []
properties:
- name: clouds_and_accounts
kind: dictionary[string, string[]]
required: false
description: Clouds and accounts to cleanup AWS alarms in.
args_schema:
Send a Message to a Discord Channel​
{%- set config = config["discord"] %}
{%- set message = [] %}
{%- for resource in stdin %}
{%- set reported = resource.get("reported", {}) %}
{%- set ancestors = resource.get("ancestors", {}) %}
{%- set account = ancestors.get("account", {}).get("reported", {}).get("name") %}
{%- set kind = reported.get("kind") %}
{%- set id = reported.get("id") %}
{%- set name = reported.get("id") %}
{%- if id == name %}
{%- set dname = id %}
{%- else %}
{%- set dname = name ~ ' (' ~ id ~ ')' %}
{%- endif %}
{%- set kdname = account ~ ' - ' ~ kind ~ ' ' ~ dname %}
{%- do message.append(kdname) %}
{%- endfor %}
{%- set message = message | join(" | ") %}
{%- set discord_data = {"embeds": [{"type": "rich", "title": args.title, "description": message, "footer": {"text": "Message created by Fix Inventory"}}]} %}
json {{ discord_data | tojson }} | http POST {{ config["webhook"] }}
name: discord
description: "Discord client for Fix Inventory"
version: "1.0.0"
language: jinja2
license: "Apache 2.0"
authors: ["someengineering"]
url: "https://fixinventory.org/"
categories: ["tools"]
default_config:
discord:
webhook: 'https://discordapp.com/api/webhooks/1234567890/abcdefghijklmnopqrstuvwxyz'
config_schema:
- fqn: discord
bases: []
properties:
- name: webhook
kind: string
required: true
description: Discord Webhook URL.
args_schema:
title:
help: "Title of the message to send to Discord."
type: str
required: true
message:
help: "Message to send to Discord."
type: str
required: true
num:
help: "Message to send to Discord."
type: int
default: 10