diff --git a/.gitignore b/.gitignore index ce0b402..9bcd667 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea -config.js -node_modules \ No newline at end of file +config.py +*.pyc \ No newline at end of file diff --git a/README.md b/README.md index aaf2e75..f95db64 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,36 @@ git clone https://github.com/FELDSAM-INC/ansible-one-inv.git ```bash cd ansible-one-inv -npm install -cp config.sample.js config.js -nano config.js # configure +pip install pyone +cp config.sample.py config.py +nano config.py # configure cd - ``` +**Sample config:** +``` +# OpenNebula XML-RPC API connection details +ONE = { + 'address': 'https://opennebula:2633/RPC2', + 'username': 'user', + 'password': 'pass' +} + +# Whether to use VM name or IP (first one found) as hostname +USE_VM_NAME = False + +# VM USER_TEMPLATE var to use as hostname +# Fallback: +# 1. check USE_VM_NAME and if True return use VM name +# 2. use VM IP (first one found) +HOSTNAME_USER_TEMPLATE_VAR = '' + +# Skip VMs by labels +SKIP_LABELS = [ + 'SomeLabel' +] +``` + ### Test ``` @@ -40,9 +64,18 @@ inventory = ./ansible-one-inv/one-inv Now you can use dynamic inventory in your playbooks. Hosts will be grouped by OpenNebula Labels to Ansible Host Groups. +## Notes + +### Host groups + +This script defines host groups by labels assigned to VMs in OpenNebula. +There is one special group `All`, which contains all VMs. +One VM can be in more groups if have more labels. + ### Custom SSH ports -I added support for define custom ssh port in vm user template. Just add variable `SSH_PORT` and ansible use it. +There is support to define custom ssh port in VM USER_TEMPLATE. +Just add variable `SSH_PORT` and Ansible use it. ## Ansible diff --git a/config.sample.js b/config.sample.js deleted file mode 100644 index db8c9e9..0000000 --- a/config.sample.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - address: 'http://opennebula:2633/RPC2', - user: 'oneadmin', - token: 'someStrongPass', - useVmName: false, - hostnameUserTemplateVar: "", - skipLabels: ["SomeLabel"] -}; diff --git a/config.sample.py b/config.sample.py new file mode 100644 index 0000000..ad07638 --- /dev/null +++ b/config.sample.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# -------------------------------------------------------------------------- # +# Copyright 2020, FeldHost™ (feldhost.net) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# -------------------------------------------------------------------------- # + +# OpenNebula XML-RPC API connection details +ONE = { + 'address': 'https://opennebula:2633/RPC2', + 'username': 'user', + 'password': 'pass' +} + +# Whether to use VM name or IP (first one found) as hostname +USE_VM_NAME = False + +# VM USER_TEMPLATE var to use as hostname +# Fallback: +# 1. check USE_VM_NAME and if True return use VM name +# 2. use VM IP (first one found) +HOSTNAME_USER_TEMPLATE_VAR = '' + +# Skip VMs by labels +SKIP_LABELS = [ + 'SomeLabel' +] diff --git a/functions.py b/functions.py new file mode 100644 index 0000000..224b250 --- /dev/null +++ b/functions.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# -------------------------------------------------------------------------- # +# Copyright 2020, FeldHost™ (feldhost.net) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# -------------------------------------------------------------------------- # + +import config + + +def get_hostname(vm): + if not config.USE_VM_NAME and config.HOSTNAME_USER_TEMPLATE_VAR != '' and vm.USER_TEMPLATE.get(config.HOSTNAME_USER_TEMPLATE_VAR): + return vm.USER_TEMPLATE.get(config.HOSTNAME_USER_TEMPLATE_VAR) + + if not config.USE_VM_NAME: + return get_vm_ip(vm) + + return vm.NAME + + +def get_vm_ip(vm): + nic = vm.TEMPLATE.get('NIC') + + if isinstance(nic, dict): + nic = [nic] + + for net in nic: + return net['IP'] + + return False + + +def get_ssh_port(vm): + if vm.USER_TEMPLATE.get('SSH_PORT'): + return vm.USER_TEMPLATE.get('SSH_PORT') + + return False diff --git a/one-inv b/one-inv index 14f4cd7..0708d7e 100755 --- a/one-inv +++ b/one-inv @@ -1,150 +1,104 @@ -#!/usr/local/bin/node - -const program = require("commander"); -const opennebula = require("opennebula"); -const config = require("./config"); - -// define program -program - .version('1.0.0') - .option('--list', 'prints all groups') - .option('--host [hostname]', 'prints host vars') - .parse(process.argv); - -// connect to one -var one = new opennebula(config.user+':'+config.token, config.address); - -if(program.list) { - getAll(function(err, groups, hosts) { - if(err) { - console.error(err); - process.exit(1); - } - - groups["_meta"] = {}; - groups["_meta"]["hostvars"] = hosts; - - console.log(JSON.stringify(groups, null, 2)); - }) -} - -if(program.host) { - getAll(function(err, groups, hosts) { - if(err) { - console.error(err); - process.exit(1); - } - - // check if host exists - if(typeof hosts[program.host] === "undefined") { - console.error("Host with name %s does't exists!", program.host); - process.exit(1); - } - - console.log(JSON.stringify(hosts[program.host], null, 2)); - }) -} - -if(!program.list && !program.host) { - program.help(); -} - -function getAll(callback) { - var groups = {}; - var hosts = {}; - - // get all VMs - one.getVMs(function (err, data) { - if (err) { - return callback(err); - } - - if(Object.keys(data).length === 0 || typeof data[0] === "undefined") { - return callback("No VMs found!"); - } - - for (var key in data) { - var vm = data[key]; - var labels = vm.USER_TEMPLATE.LABELS; - - if(labels) { - var exLabels = labels.split(","); - - for (var key2 in exLabels) { - var label = exLabels[key2]; - - // skip labels - if (config.skipLabels.indexOf(label) !== -1) { - continue; - } - - // create new group - if (typeof groups[label] === "undefined") { - groups[label] = []; - } - - var hostname = getHostName(vm); - - // add host to group - groups[label].push(hostname); - - // save host and its user template vars - hosts[hostname] = vm.USER_TEMPLATE; - - // set custom ssh port from VM USER_TEMPLATE var SSH_PORT - sshPort = getSshPort(vm); - if (sshPort) { - hosts[hostname]["ansible_port"] = sshPort; - } - } - } - } - - if(Object.keys(groups).length === 0) { - return callback("No VMs with labels found (except skiped one)!"); - } - - callback(null, groups, hosts); - }); -} - -function getHostName(vm) { - // get hostname key - if (!config.useVmName && config.hostnameUserTemplateVar !== "" && typeof vm.USER_TEMPLATE[config.hostnameUserTemplateVar] !== "undefined") { - return vm.USER_TEMPLATE[config.hostnameUserTemplateVar]; - } - - // not use vm name but hostname user template var is not defined or empty - // so get IP - if (!config.useVmName) { - return getVMIp(vm); - } - - // user vm name - return vm.NAME; -} - -function getSshPort(vm) { - if (vm.USER_TEMPLATE["SSH_PORT"] !== "undefined") { - return vm.USER_TEMPLATE["SSH_PORT"]; - } - - return false; -} - -function getVMIp(vm) { - var nic = vm.TEMPLATE.NIC; - - if(typeof nic.NETWORK_ID !== "undefined") { - nic = Array(nic); - } - - for(k in nic) { - var net = nic[k]; - - // return first - return net.IP; - } - - return false; -} +#!/usr/bin/env python + +# -*- coding: utf-8 -*- +# -------------------------------------------------------------------------- # +# Copyright 2020, FeldHost™ (feldhost.net) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# -------------------------------------------------------------------------- # + +import sys +import argparse +import json +import pyone +import functions +import config + +# ---------------------------- +# Define parser +# ---------------------------- +parser = argparse.ArgumentParser(description='OpenNebula Ansible Dynamic Inventory') + +parser.add_argument('--list', help='Get list of all groups', action='store_true') +parser.add_argument('--host', help='Get specific host vars') + +# ------------------------------------- +# Print help if no args passed +# ------------------------------------- +if len(sys.argv[1:]) == 0: + parser.print_help() + parser.exit() + +# ------------------------------------- +# Parse args and proceed with execution +# ------------------------------------- +args = parser.parse_args() + +# ----------------------- +# Connect to OpenNebula +# ----------------------- +one = pyone.OneServer(config.ONE['address'], session='%s:%s' % (config.ONE['username'], config.ONE['password'])) + +# Init vars +groups = {} +groups['All'] = [] +hosts = {} + +# get hosts (VMs) +vm_pool = one.vmpool.infoextended(-2, -1, -1, 3) + +# iterate over hosts +for vm in vm_pool.VM: + labels = [] + if vm.USER_TEMPLATE.get('LABELS'): + labels = vm.USER_TEMPLATE.get('LABELS').split(',') + + # get hostname + hostname = functions.get_hostname(vm) + + for label in labels: + # skips hosts by excluded labels + if label in config.SKIP_LABELS: + continue + + # create new group if doesn't exist + if not groups.get(label): + groups[label] = [] + + # add host to group + groups[label].append(hostname) + + # add host to default group + groups['All'].append(hostname) + + # save host vars + hosts[hostname] = vm.USER_TEMPLATE + + # custom ssh port + ssh_port = functions.get_ssh_port(vm) + if ssh_port: + hosts[hostname]['ansible_port'] = ssh_port + +# prepare final groups data +groups['_meta'] = {} +groups['_meta']['hostvars'] = hosts + +if args.list: + print(json.dumps(groups, sort_keys=False, indent=4, separators=(',', ': '))) + +if args.host: + if not args.host in hosts: + print('Host with name %s does\'t exists!' % args.host) + exit(1) + + print(json.dumps(hosts[args.host], sort_keys=False, indent=4, separators=(',', ': '))) diff --git a/package.json b/package.json deleted file mode 100644 index 2dff8be..0000000 --- a/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "ansible-one-inv", - "version": "1.0.0", - "description": "Ansible OpenNebula Dynamic Inventory", - "main": "one-inv", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [ - "one", - "ansible", - "dynamic", - "inventory" - ], - "author": "Kristian Feldsam (http://www.feldsam.cz)", - "license": "MIT", - "dependencies": { - "commander": "^2.9.0", - "opennebula": "^1.0.8" - } -}