-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from cernops/improvement
Improvements
- Loading branch information
Showing
2 changed files
with
143 additions
and
89 deletions.
There are no files selected for viewing
207 changes: 124 additions & 83 deletions
207
rundeck-puppetdb-nodes-plugin/contents/rundeck_puppetdb_nodes.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
@@ -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 | ||
|
@@ -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" |