|
Packit Service |
a04d08 |
# This file is part of cloud-init. See LICENSE file for license information.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
from errno import EACCES
|
|
Packit Service |
a04d08 |
import os
|
|
Packit Service |
a04d08 |
import re
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
from jinja2.exceptions import UndefinedError as JUndefinedError
|
|
Packit Service |
a04d08 |
except ImportError:
|
|
Packit Service |
a04d08 |
# No jinja2 dependency
|
|
Packit Service |
a04d08 |
JUndefinedError = Exception
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
from cloudinit import handlers
|
|
Packit Service |
a04d08 |
from cloudinit import log as logging
|
|
Packit Service |
a04d08 |
from cloudinit.sources import INSTANCE_JSON_FILE
|
|
Packit Service |
a04d08 |
from cloudinit.templater import render_string, MISSING_JINJA_PREFIX
|
|
Packit Service |
a04d08 |
from cloudinit.util import b64d, load_file, load_json, json_dumps
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
from cloudinit.settings import PER_ALWAYS
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
LOG = logging.getLogger(__name__)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class JinjaTemplatePartHandler(handlers.Handler):
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
prefixes = ['## template: jinja']
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __init__(self, paths, **_kwargs):
|
|
Packit Service |
a04d08 |
handlers.Handler.__init__(self, PER_ALWAYS, version=3)
|
|
Packit Service |
a04d08 |
self.paths = paths
|
|
Packit Service |
a04d08 |
self.sub_handlers = {}
|
|
Packit Service |
a04d08 |
for handler in _kwargs.get('sub_handlers', []):
|
|
Packit Service |
a04d08 |
for ctype in handler.list_types():
|
|
Packit Service |
a04d08 |
self.sub_handlers[ctype] = handler
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def handle_part(self, data, ctype, filename, payload, frequency, headers):
|
|
Packit Service |
a04d08 |
if ctype in handlers.CONTENT_SIGNALS:
|
|
Packit Service |
a04d08 |
return
|
|
Packit Service |
a04d08 |
jinja_json_file = os.path.join(self.paths.run_dir, INSTANCE_JSON_FILE)
|
|
Packit Service |
a04d08 |
rendered_payload = render_jinja_payload_from_file(
|
|
Packit Service |
a04d08 |
payload, filename, jinja_json_file)
|
|
Packit Service |
a04d08 |
if not rendered_payload:
|
|
Packit Service |
a04d08 |
return
|
|
Packit Service |
a04d08 |
subtype = handlers.type_from_starts_with(rendered_payload)
|
|
Packit Service |
a04d08 |
sub_handler = self.sub_handlers.get(subtype)
|
|
Packit Service |
a04d08 |
if not sub_handler:
|
|
Packit Service |
a04d08 |
LOG.warning(
|
|
Packit Service |
a04d08 |
'Ignoring jinja template for %s. Could not find supported'
|
|
Packit Service |
a04d08 |
' sub-handler for type %s', filename, subtype)
|
|
Packit Service |
a04d08 |
return
|
|
Packit Service |
a04d08 |
if sub_handler.handler_version == 3:
|
|
Packit Service |
a04d08 |
sub_handler.handle_part(
|
|
Packit Service |
a04d08 |
data, ctype, filename, rendered_payload, frequency, headers)
|
|
Packit Service |
a04d08 |
elif sub_handler.handler_version == 2:
|
|
Packit Service |
a04d08 |
sub_handler.handle_part(
|
|
Packit Service |
a04d08 |
data, ctype, filename, rendered_payload, frequency)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def render_jinja_payload_from_file(
|
|
Packit Service |
a04d08 |
payload, payload_fn, instance_data_file, debug=False):
|
|
Packit Service |
a04d08 |
"""Render a jinja template payload sourcing variables from jinja_vars_path.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param payload: String of jinja template content. Should begin with
|
|
Packit Service |
a04d08 |
## template: jinja\n.
|
|
Packit Service |
a04d08 |
@param payload_fn: String representing the filename from which the payload
|
|
Packit Service |
a04d08 |
was read used in error reporting. Generally in part-handling this is
|
|
Packit Service |
a04d08 |
'part-##'.
|
|
Packit Service |
a04d08 |
@param instance_data_file: A path to a json file containing variables that
|
|
Packit Service |
a04d08 |
will be used as jinja template variables.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@return: A string of jinja-rendered content with the jinja header removed.
|
|
Packit Service |
a04d08 |
Returns None on error.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
instance_data = {}
|
|
Packit Service |
a04d08 |
rendered_payload = None
|
|
Packit Service |
a04d08 |
if not os.path.exists(instance_data_file):
|
|
Packit Service |
a04d08 |
raise RuntimeError(
|
|
Packit Service |
a04d08 |
'Cannot render jinja template vars. Instance data not yet'
|
|
Packit Service |
a04d08 |
' present at %s' % instance_data_file)
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
instance_data = load_json(load_file(instance_data_file))
|
|
Packit Service |
a04d08 |
except (IOError, OSError) as e:
|
|
Packit Service |
a04d08 |
if e.errno == EACCES:
|
|
Packit Service |
a04d08 |
raise RuntimeError(
|
|
Packit Service |
a04d08 |
'Cannot render jinja template vars. No read permission on'
|
|
Packit Service |
9bfd13 |
" '%s'. Try sudo" % instance_data_file
|
|
Packit Service |
9bfd13 |
) from e
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
rendered_payload = render_jinja_payload(
|
|
Packit Service |
a04d08 |
payload, payload_fn, instance_data, debug)
|
|
Packit Service |
a04d08 |
if not rendered_payload:
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
return rendered_payload
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def render_jinja_payload(payload, payload_fn, instance_data, debug=False):
|
|
Packit Service |
a04d08 |
instance_jinja_vars = convert_jinja_instance_data(
|
|
Packit Service |
a04d08 |
instance_data,
|
|
Packit Service |
a04d08 |
decode_paths=instance_data.get('base64-encoded-keys', []))
|
|
Packit Service |
a04d08 |
if debug:
|
|
Packit Service |
a04d08 |
LOG.debug('Converted jinja variables\n%s',
|
|
Packit Service |
a04d08 |
json_dumps(instance_jinja_vars))
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
rendered_payload = render_string(payload, instance_jinja_vars)
|
|
Packit Service |
a04d08 |
except (TypeError, JUndefinedError) as e:
|
|
Packit Service |
a04d08 |
LOG.warning(
|
|
Packit Service |
a04d08 |
'Ignoring jinja template for %s: %s', payload_fn, str(e))
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
warnings = [
|
|
Packit Service |
a04d08 |
"'%s'" % var.replace(MISSING_JINJA_PREFIX, '')
|
|
Packit Service |
a04d08 |
for var in re.findall(
|
|
Packit Service |
a04d08 |
r'%s[^\s]+' % MISSING_JINJA_PREFIX, rendered_payload)]
|
|
Packit Service |
a04d08 |
if warnings:
|
|
Packit Service |
a04d08 |
LOG.warning(
|
|
Packit Service |
a04d08 |
"Could not render jinja template variables in file '%s': %s",
|
|
Packit Service |
a04d08 |
payload_fn, ', '.join(warnings))
|
|
Packit Service |
a04d08 |
return rendered_payload
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def convert_jinja_instance_data(data, prefix='', sep='/', decode_paths=()):
|
|
Packit Service |
a04d08 |
"""Process instance-data.json dict for use in jinja templates.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Replace hyphens with underscores for jinja templates and decode any
|
|
Packit Service |
a04d08 |
base64_encoded_keys.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
result = {}
|
|
Packit Service |
a04d08 |
decode_paths = [path.replace('-', '_') for path in decode_paths]
|
|
Packit Service |
a04d08 |
for key, value in sorted(data.items()):
|
|
Packit Service |
a04d08 |
if '-' in key:
|
|
Packit Service |
a04d08 |
# Standardize keys for use in #cloud-config/shell templates
|
|
Packit Service |
a04d08 |
key = key.replace('-', '_')
|
|
Packit Service |
a04d08 |
key_path = '{0}{1}{2}'.format(prefix, sep, key) if prefix else key
|
|
Packit Service |
a04d08 |
if key_path in decode_paths:
|
|
Packit Service |
a04d08 |
value = b64d(value)
|
|
Packit Service |
a04d08 |
if isinstance(value, dict):
|
|
Packit Service |
a04d08 |
result[key] = convert_jinja_instance_data(
|
|
Packit Service |
a04d08 |
value, key_path, sep=sep, decode_paths=decode_paths)
|
|
Packit Service |
a04d08 |
if re.match(r'v\d+', key):
|
|
Packit Service |
a04d08 |
# Copy values to top-level aliases
|
|
Packit Service |
a04d08 |
for subkey, subvalue in result[key].items():
|
|
Packit Service |
a04d08 |
result[subkey] = subvalue
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
result[key] = value
|
|
Packit Service |
a04d08 |
return result
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# vi: ts=4 expandtab
|