Skip to content

Instantly share code, notes, and snippets.

@remie
Last active December 2, 2020 18:03
Show Gist options
  • Save remie/2a40516af604518233ae to your computer and use it in GitHub Desktop.
Save remie/2a40516af604518233ae to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
###########################################################################################
# Dynamic inventory for Ansible with DNS
# Author Remie Bolte (https://nl.linkedin.com/in/remiebolte)
#
# This NodeJS script generates a dynamic inventory based on DNS TXT records.
#
# If you use the “--inventory” switch when calling Ansible, you can provide the
# path to a directory which includes inventory files. Ansible will automtically
# run any executable script in that directory to see if provides the JSON output
# describing a dynamic inventory.
#
# Using the following NodeJS script, you can read the information from your TXT
# record and provide the JSON output that Ansible requires.
#
# This script will query the DNS server for the value of the TXT record associated
# with “host._ansible.yourdomain.com”.
#
# It expects to retrieve a string-based value of:
# “hostname=web-host-01.yourdomain.com;sections=webserver,boston”
#
# The second TXT record might be:
# “hostname=db-host-01.yourdomain.com;sections=database,atlanta”
#
# The script will parse these TXT records and output the following JSON:
# { “webserver”: { “hosts”: [ “web-host-01.yourdomain.com” ] }, “boston”: { “hosts”: [ “web-host-01.yourdomain.com” ] }, “database”: { “hosts”: [ “db-host-01.yourdomain.com” ] }, “atlanta”: { “hosts”: [ “db-host-01.yourdomain.com” ] }
#
# Now you only need to create a script that updates your DNS when provisiong or
# decommissioning a server, by either adding or removing the TXT record for that
# specific host.
#
# TODO:
# - Add host variables
# - Add encryption/decription of TXT record metadata
#
# Copyright (c) 2016 Remie Bolte
# This snippet is license with the MIT open source license
# https://opensource.org/licenses/MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in the
# Software without restriction, including without limitation the rights to use, copy,
# modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so, subject to the
# following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies
# or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
###########################################################################################
// ------------------------------------------------------------------------------------------ Dependencies
var dns = require('dns');
var _ = require('lodash');
var program = require('commander');
// ------------------------------------------------------------------------------------------ Variables
// Replace 'yourdomain.com' in this variable with your DNS domain name
var domain = 'host._ansible.yourdomain.com';
// ------------------------------------------------------------------------------------------ Command-line parser
program
.option('-l, --list', 'JSON list of sections/hosts')
.option('-h, --host [hostname]', 'retrieve variables for specified host (not supported)')
.parse(process.argv);
generateDynamicInventoryFromDNS(domain, function(inventory) {
if(program.list) {
// Return list of hosts
return console.log(JSON.stringify(inventory));
} else if (program.host) {
// Host variables are currently not supported, returning empty object
return console.log({});
} else {
// Wait... what?
program.outputHelp();
}
});
// ------------------------------------------------------------------------------------------ Private functions
function generateDynamicInventoryFromDNS(domain, callback) {
// Retrieve TXT records from DNS
dns.resolveTxt(domain, function(error, records) {
// Variable that will contain the inventory
var inventory = {};
// Iterate over TXT records
records.forEach(function(record) {
// Find each key/value pair in TXT record
var data = record[0].split(';');
// temporary object to store key/value pairs
var store = Array();
_.each(data, function(items) {
// Parse key/value pair and store them in temporary object
var item = items.split("=");
var key=item[0];
var value=item[1];
store[key] = value;
});
// Check if this host has sections or is ungrouped
if(_.has(store, 'sections')) {
// Iterate over the sections this host is assigned to
_.each(store['sections'].split(','), function(section) {
// Initialize section if it does not already exist
if(!_.has(inventory, section)) {
inventory[section] = {}
inventory[section]['hosts'] = Array();
};
// Add host to inventory (unless it exists)
if(!_.includes(inventory[section]['hosts'], store['hostname'])) {
inventory[section]['hosts'].push(store['hostname']);
}
});
} else {
// Host is ungrouped
// Initialize ungrouped section if it does not already exist
if(!_.has(inventory, 'ungrouped')) {
inventory['ungrouped'] = {};
inventory['ungrouped']['hosts'] = Array();
}
// Add host to inventory (unless it exists)
if(!_.includes(inventory['ungrouped']['hosts'], store['hostname'])) {
inventory['ungrouped']['hosts'].push(store['hostname']);
}
};
});
callback(inventory);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment