Skip to content

Commit

Permalink
Merge branch 'devel'
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Fernandez Rodriguez authored and Daniel Fernandez Rodriguez committed Dec 17, 2015
2 parents 762c56b + 94fe6f1 commit bbc5e61
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rundeck-puppetdb-nodes-plugin.zip
deploy.sh
99 changes: 97 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,97 @@
# rundeck-puppetdb-nodes
Rundeck plugin to retrieve node definitions from PuppetDB
Rundeck PuppetDB Nodes Plugin
=============================================

Description
-----------
This is a Resource Model Source plugin for [Rundeck][] Rundeck version 2.0 (or higher) that retrieves node definitions
from PuppetDB.

Developed in Python, it uses [python-requests][] library and Kerberos authentication to connect to the PuppetDB API.

[RunDeck]: http://rundeck.org
[python-requests]: http://docs.python-requests.org/en/latest/

Either the puppetdb API url, the foreman hostgroup, username, and the kerberos keytab path can be specified via plugin parameters on the project's configuration page.

Parameters
----------
`PuppetDB` - PuppetDB API URL following this format: `https://<SERVER>:<PORT>/<API VERSION>`

> `https://my.puppet.db:2525/v3`
`Foreman Hostgroup` - Specify a Foreman hosgroup to filter the query

> `cloud_workflows` or `cloud_`
`Kerberos user` - User to connect to PuppetDB

`Kerberos keytab` - Path to user's keytab to authenticate

![alt tag](images/config.png)

Requirements
------------
* The plugin requires Rundeck version 2.0 or higher.
* python-requests v1.1.0-4
* python-requests-kerberos v0.5 ([important!!](https://bugzilla.redhat.com/show_bug.cgi?id=1169296))

Installation
------------
Download the latest .ZIP from the [releases page](https://github.com/cernops/rundeck-puppetdb-nodes/releases) and copy it to `/var/lib/rundeck/libext/`. Restart the Rundeck service to be sure it gets the lastest changes.

Next time you log in, you will see a new Resource Model Source called **PuppetDB Source** on the project's configuration page.

Plugin Output example
---------------------
```
scheduler-02.mydomain.com:
hostname: scheduler-02.mydomain.com
username: root
tags: hostgroup=workflows/scheduler/server
osName: SLC
scheduler-01.mydomain.com:
hostname: scheduler-01.mydomain.com
username: root
tags: hostgroup=workflows/scheduler/server
osName: SLC
loadbalancer-01.mydomain.com:
hostname: loadbalancer-01.mydomain.com
username: root
tags: hostgroup=workflows/ha
osName: SLC
server-02.mydomain.com:
hostname: server-02.mydomain.com
username: root
tags: hostgroup=workflows/server/production
osName: SLC
loadbalancer-02.mydomain.com:
hostname: loadbalancer-02.mydomain.com
username: root
tags: hostgroup=workflows/ha
osName: SLC
server-qa-01.mydomain.com:
hostname: server-qa-01.mydomain.com
username: root
tags: hostgroup=workflows/server/qa
osName: SLC
loadbalancer-qa-01.mydomain.com:
hostname: loadbalancer-qa-01.mydomain.com
username: root
tags: hostgroup=workflows/ha
osName: SLC
server-qa-03.mydomain.com:
hostname: server-qa-03.mydomain.com
username: root
tags: hostgroup=workflows/server
osName: CentOS
server-datastore.mydomain.com:
hostname: server-datastore.mydomain.com
username: root
tags: hostgroup=workflows/datastore
osName: CentOS
server-qa-02.mydomain.com:
hostname: server-qa-02.mydomain.com
username: root
tags: hostgroup=workflows/server
osName: SLC
```
Binary file added images/config.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 111 additions & 0 deletions rundeck-puppetdb-nodes-plugin/contents/rundeck_puppetdb_nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Daniel Fernandez Rodriguez <[email protected]>

from argparse import ArgumentParser
from requests_kerberos import HTTPKerberosAuth

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

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.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='?')

args = parser.parse_args()

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)

if __name__ == "__main__":
main()
44 changes: 44 additions & 0 deletions rundeck-puppetdb-nodes-plugin/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#yaml plugin metadata

name: PuppetDB source
version: 1.0
rundeckPluginVersion: 1.0
author: Daniel Fernandez ([email protected])
date: 2015/02/12
providers:
- name: rundeck-puppetdb-nodes
service: ResourceModelSource
title: PuppetDB source
description: Queries PuppetDB to retrieve node definitions from a specific Foreman hostgroup
resource-format: resourceyaml
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}"
config:
- name: apiurl
type: String
title: PuppetDB
description: "PuppetDB API URL (https://<SERVER>:<PORT>/<API VERSION>). Ex: https://my.puppet.db:2525/v3"
required: true
- name: hostgroup
type: String
title: Hostgroup
description: "Foreman Hostgroup"
required: true
- name: username
type: String
title: Username
description: "User to connect to PuppetDB"
required: true
- name: keytab
type: String
title: Keytab
description: "Path to user's keytab to authenticate"
required: true
- name: factlist
type: String
title: Fact list
description: "List of facts to retrieve for every node"
required: true
default: "is_virtual"

0 comments on commit bbc5e61

Please sign in to comment.