#!/usr/bin/env python
import yaml
import json
import subprocess
import yamlordereddictloader
# CentOS 6 EPEL provides an alternate Jinja2 package
try:
from jinja2 import BaseLoader
from jinja2 import Environment
except ImportError:
import sys
sys.path.insert(0, '/usr/lib/python2.6/site-packages/Jinja2-2.6-py2.6.egg')
from jinja2 import BaseLoader
from jinja2 import Environment
from linchpin.exceptions import LinchpinError
from linchpin.exceptions import ValidationError
[docs]class DataParser(object):
def __init__(self):
self._mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
[docs] def process(self, file_w_path, data=None):
""" Processes the PinFile and any data (if a template)
using Jinja2. Returns json of PinFile, topology, layout,
and hooks.
:param file_w_path:
Full path to the provided file to process
:param data:
A JSON representation of data mapped to a Jinja2 template in
file_w_path
"""
if not data:
data = '{}'
with open(file_w_path, 'r') as stream:
file_data = stream.read()
if data.startswith('@'):
with open(data[1:], 'r') as strm:
data = strm.read()
try:
file_data = self.render(file_data, data, ordered=False)
return self.parse_json_yaml(file_data)
except TypeError:
error_txt = "Error attempting to parse PinFile data file."
error_txt += "\nTemplate-data files require a prepended '@'"
error_txt += " (eg. '@/path/to/template-data.yml')"
error_txt += "\nPerhaps the path to the PinFile or"
error_txt += " template-data is missing or the incorrect path?."
raise ValidationError(error_txt)
return self.load_pinfile(file_w_path)
[docs] def render(self, template, context, ordered=True):
"""
Performs the rendering of template and context data using
Jinja2.
:param template:
Full path to the Jinja2 template
:param context:
A dictionary of variables to be rendered againt the template
"""
c = self.parse_json_yaml(context, ordered=ordered)
t = Environment(loader=BaseLoader).from_string(template)
return t.render(c)
[docs] def parse_json_yaml(self, data, ordered=True):
""" parses yaml file into json object """
d = None
try:
if ordered:
data = yaml.load(data, Loader=yamlordereddictloader.Loader)
else:
data = yaml.load(data)
except Exception as e:
raise LinchpinError('YAML parsing error: {}'.format(e))
if isinstance(data, dict):
return data
return d
[docs] def run_script(self, script):
sp = None
try:
sp = subprocess.Popen(script,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except OSError as e:
raise ValidationError("problem running {0} ({1})".format(script, e))
(stdout, stderr) = sp.communicate()
if sp.returncode != 0:
raise ValidationError("Script {0}"
" had execution error".format(script))
return stdout
[docs] def load_pinfile(self, pinfile):
# try to convert the data into json
pf = None
try:
with open(pinfile, 'r') as stream:
pf_data = stream.read()
pf = self.parse_json_yaml(pf_data)
# ordered hooks gives parsing errors
# since hooks are already in lists we need not maintain order
pf['hooks'] = self.parse_json_yaml(pf_data,
ordered=False).get('hooks',
{})
except ValidationError as e:
pass
if not pf:
# assume not json, so dynamic script
# once executed, verify it is json
try:
pf = json.loads(self.run_script(pinfile))
except Exception as e:
raise LinchpinError(e)
return pf
[docs] def write_json(self, provision_data, pf_outfile):
with open(pf_outfile, 'w') as outfile:
json.dump(provision_data, outfile, indent=4)