Skip to content

Commit

Permalink
Merge pull request #2 from cernops/improvement
Browse files Browse the repository at this point in the history
Improvements
  • Loading branch information
danifr committed Feb 25, 2016
2 parents bbc5e61 + 187cd7f commit 30a8688
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 89 deletions.
207 changes: 124 additions & 83 deletions rundeck-puppetdb-nodes-plugin/contents/rundeck_puppetdb_nodes.py
Original file line number Diff line number Diff line change
@@ -1,111 +1,152 @@
# Daniel Fernandez Rodriguez <[email protected]>

from argparse import ArgumentParser
from collections import defaultdict
from requests_kerberos import HTTPKerberosAuth

import json
import requests
import subprocess
import logging
from collections import defaultdict
import sys


class PuppetDBNodes(object):

def __init__(self, args):
for k, v in args.items():
setattr(self, k, v)


def negociate_krb_ticket(self, keytab_path, username):
kinit = '/usr/bin/kinit'
kinit_args = [kinit, '-kt', keytab_path, username]
kinit = subprocess.Popen(kinit_args)
kinit.wait()


def destroy_krb_ticket(self):
subprocess.call(["kdestroy"])


def get_facts_puppetdb(self, apiurl, facts, hostgroup):
url ='%s/facts' % apiurl
query_base = '["and",["or",%s],["in", "certname", ["extract", "certname", ["select-facts", ["and", ["=", "name", "hostgroup"], ["~", "value", "%s"]]]]]]'
query_facts = ','.join(['["=","name","%s"]' % fact for fact in facts])
query = query_base % (query_facts, hostgroup)

headers = {'Content-Type': 'application/json','Accept': 'application/json, version=2'}
payload = {'query': query}

logging.info("Getting facts from '%s', query: '%s'" % (url, query))
r = requests.get(url, params=payload, headers=headers, auth=HTTPKerberosAuth())

if r.status_code == requests.codes.ok:
logging.info("Request code: '%s'" % r.status_code)
return json.loads(r.text)
else:
logging.error("The request failed with code '%s'" % r.status_code)
return None


def print_puppetdb_nodes(self, apiurl, hostgroup, factlist):
'''
Queries PuppetDB and prints out the nodes information in a supported format for Rundeck
.
'''
logging.info(type(factlist))
factlist.extend(["operatingsystem", "operatingsystemrelease", "hostgroup"])
raw_data = self.get_facts_puppetdb(apiurl, factlist, hostgroup)
data = defaultdict(lambda: {})

if raw_data != None:
for entry in raw_data:
data[entry['certname']] = dict(data[entry['certname']].items() + [(entry['name'], entry['value'])])

logging.info("Printing node list using standard output...")
for node in data.keys():
print ('%s:'%node)
print (" "*4 + "hostname: " + node)
print (" "*4 + "username: root")
for fact in factlist:
if data[node].has_key(fact):
print (" "*4 + fact + ": " + data[node][fact] )
logging.info("Node list printed successfully")

else:
logging.error("Fact list empty. Check PuppetDB connection params")


def store_puppetdb_nodes(self, apiurl, hostgroup, factlist, filename):
'''
Instead of querying PuppetDB every time, saves the list of nodes on a local file
so Rundeck can access it localy.
'''
factlist.extend(["operatingsystem", "operatingsystemrelease", "hostgroup"])
raw_data = self.get_facts_puppetdb(apiurl, factlist, hostgroup)
data = defaultdict(lambda: {})

if raw_data != None:
for entry in raw_data:
data[entry['certname']] = dict(data[entry['certname']].items() + [(entry['name'], entry['value'])])

logging.info("Saving node list in '%s'..." % filename)
with open(filename, 'w') as file:
for node in data.keys():
file.write('%s:\n'%node)
file.write(" "*4 + "hostname: " + node + '\n')
file.write(" "*4 + "username: root" + '\n')
for fact in factlist:
if data[node].has_key(fact):
file.write(" "*4 + fact + ": " + data[node][fact] + '\n')
logging.info("Node list saved successfully")
else:
logging.error("Fact list empty. Check PuppetDB connection params")


def run(self):
self.negociate_krb_ticket(self.keytab, self.username)
if self.store:
self.store_puppetdb_nodes(self.apiurl, self.hostgroup, self.factlist, self.file)
else:
self.print_puppetdb_nodes(self.apiurl, self.hostgroup, self.factlist)

def negociateKRBticket(keytab, username):
kinit = '/usr/bin/kinit'
kinit_args = [kinit, '-kt', keytab, username]
kinit = subprocess.Popen(kinit_args)
kinit.wait()

def destroyKRBticket():
subprocess.call(["kdestroy"])

def getFactsPuppetDB(apiurl, facts, hostgroup):
url ='%s/facts' % apiurl
query_base = '["and",["or",%s],["in", "certname", ["extract", "certname", ["select-facts", ["and", ["=", "name", "hostgroup"], ["~", "value", "%s"]]]]]]'
query_facts = ','.join(['["=","name","%s"]' % fact for fact in facts])
query = query_base % (query_facts, hostgroup)
headers = {'Content-Type': 'application/json','Accept': 'application/json, version=2'}
payload = {'query': query}
logging.info("Getting facts from '%s', query: '%s'" % (url, query))
r = requests.get(url, params=payload, headers=headers, verify=False, auth=HTTPKerberosAuth())
if r.status_code == requests.codes.ok:
logging.info("Request code: '%s'" % r.status_code)
return json.loads(r.text)
logging.error("The request failed with code '%s'" % r.status_code)
return None

def printNodesList(apiurl, hostgroup, factlist):
'''
Prints the nodes information in a supported format for Rundeck.
'''
facts = factlist.replace(',','').split()
facts.extend(["operatingsystem", "operatingsystemrelease", "hostgroup"])
raw_data = getFactsPuppetDB(apiurl, facts, hostgroup)
data = defaultdict(lambda: {})
if raw_data != None:
for entry in raw_data:
data[entry['certname']] = dict(data[entry['certname']].items() + [(entry['name'], entry['value'])])

logging.info("Printing node list using standard output...")
for node in data.keys():
print ('%s:'%node)
print (" "*4 + "hostname: " + node)
print (" "*4 + "username: root")
for fact in facts:
if data[node].has_key(fact):
print (" "*4 + fact + ": " + data[node][fact] )
logging.info("Node list printed successfully")
else:
logging.error("Fact list empty. Check PuppetDB connection params")

def storeNodesList(apiurl, hostgroup, path):
'''
Saves the node list in a local file so rundeck can access it localy.
'''
operatingsystemfacts = getFactPuppetDB(apiurl, "operatingsystem", hostgroup)
hostgroupfacts = getFactPuppetDB(apiurl, "hostgroup", hostgroup)
logging.info("Saving node list in '%s'..." % path)
if not (operatingsystemfacts == None or hostgroupfacts == None):
with open("%s/nodes.yaml" % path, 'w') as file:
for operatingsystem in operatingsystemfacts:
global counter
counter = counter + 1
file.write (operatingsystem['certname'] + ':\n')
file.write (" "*4+"hostname: "+ operatingsystem['certname']+"\n")
file.write (" "*4+"username: root\n")
file.write(" "*4+"tags: ")
for hostgroup in hostgroupfacts:
if (operatingsystem['certname'] == hostgroup['certname']):
file.write(" hostgroup="+ hostgroup['value']+"\n")
file.write (" "*4+"osName: "+ operatingsystem['value']+"\n")
logging.info("Node list saved successfully")
else:
logging.error("Fact list empty. Check PuppetDB connection params")

def puppetdb_nodes_main(apiurl, hostgroup, keytab, username, factlist):
negociateKRBticket(keytab, username)
#storeNodesList(apiurl, hostgroup, path)
printNodesList(apiurl, hostgroup, factlist)
destroyKRBticket()

def main():
parser = ArgumentParser(description="Get rundeck nodes from PuppetDB")
parser = ArgumentParser(description="Populate Rundeck list of nodes from PuppetDB")
parser.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true")
parser.add_argument("-d", "--debug", help="increase output to debug messages", action="store_true")

parser.add_argument("--apiurl", help="PuppetDB API url (https://<SERVER>:<PORT>/<API VERSION>)", required=True)
parser.add_argument("--hostgroup", help="Foreman hostgroup", required=True)
parser.add_argument("--keytab", help="Keytab", required=True)
parser.add_argument("--username", help="Username to connect to PuppetDB", required=True)
parser.add_argument("--factlist", help="List of facts to retrieve for every node", required=True)
parser.add_argument("--path", help="Path where the node list will be stored", nargs='?')
parser.add_argument("--factlist", nargs='*', default=[], help="List of facts to retrieve for every node")
parser.add_argument("--file", default="/tmp/nodes.yaml", help="File path where the node list info will be stored")

behaviour = parser.add_mutually_exclusive_group()
behaviour.add_argument('--store', action='store_true')
behaviour.add_argument('--print', action='store_false')

args = parser.parse_args()

#trick to get the factlist as an object list when called it from Rundeck
if len(args.factlist) == 1:
args.factlist = args.factlist[0].split()

if args.verbose:
logging.basicConfig(level=logging.INFO)
elif args.debug:
logging.basicConfig(level=logging.DEBUG)

puppetdb_nodes_main(args.apiurl, args.hostgroup, args.keytab, args.username, args.factlist)
plugin = PuppetDBNodes(args.__dict__)
plugin.run()


if __name__ == "__main__":
main()
try:
main()
except Exception, e:
logging.error(e)
sys.exit(-1)
25 changes: 19 additions & 6 deletions rundeck-puppetdb-nodes-plugin/plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#yaml plugin metadata

name: PuppetDB source
version: 1.0
version: 1.1
rundeckPluginVersion: 1.0
author: Daniel Fernandez ([email protected])
date: 2015/02/12
date: 2015/10/21
providers:
- name: rundeck-puppetdb-nodes
service: ResourceModelSource
Expand All @@ -14,12 +14,13 @@ providers:
plugin-type: script
script-interpreter: /usr/bin/python
script-file: rundeck_puppetdb_nodes.py
script-args: "-v --apiurl ${config.apiurl} --hostgroup ${config.hostgroup} --username ${config.username} --keytab ${config.keytab} --factlist ${config.factlist}"
script-args: "-v --apiurl ${config.apiurl} --hostgroup ${config.hostgroup} --username ${config.username} --keytab ${config.keytab} --file ${config.file} --${config.mode} --factlist ${config.factlist}"
config:
- name: apiurl
type: String
title: PuppetDB
description: "PuppetDB API URL (https://<SERVER>:<PORT>/<API VERSION>). Ex: https://my.puppet.db:2525/v3"
description: "PuppetDB API URL. Ex: https://my.puppet.db:2525/v3"
default: "https://judy.cern.ch:9081/v3"
required: true
- name: hostgroup
type: String
Expand All @@ -39,6 +40,18 @@ providers:
- name: factlist
type: String
title: Fact list
description: "List of facts to retrieve for every node"
description: "Space-separated list of facts to retrieve for every node"
default: "hostname ipaddress"
required: false
- name: mode
type: Select
title: Mode
description: "Prints out/stores on file the list of nodes"
default: print
values: print,store
required: true
default: "is_virtual"
- name: file
type: String
title: "Output file"
description: "Save list of nodes to file (only if 'store' mode selected)"
default: "/var/rundeck/projects/${project.name}/etc/resources.yaml"

0 comments on commit 30a8688

Please sign in to comment.