Source code for linchpin.utils.dataparser

#!/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)