blob: d4e7e6561afe4602b0b6c3b99ff04f7cf331fcff [file] [log] [blame]
#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""
Script which uses a Python "template" to generate a Hadoop-style XML
configuration file.
The "template" is a Python module which should export a global variable called
'CONFIG'. This variable should be a dictionary of keys/values. The values may
use the special syntax '${FOO}' to substitute an environment variable (as a
convenience over manually implementing the same).
If you have an existing XML configuration and want to see it in convenient
python form, you can use a snippet like the following from within the Python
REPL:
import xml.etree.ElementTree as ET
import pprint
def convert(path):
e = ET.parse(path)
c = dict([(property.findtext('name'), property.findtext('value'))
for property in e.getroot()])
pprint.pprint(c, stream=file(path + ".py", "w"))
"""
from __future__ import absolute_import, division, print_function
import imp
import os
import re
import sys
from textwrap import dedent
from xml.sax.saxutils import escape as xmlescape
ENV_VAR_RE = re.compile(r'\${(.+?)\}')
def _substitute_env_vars(s):
""" Substitute ${FOO} with the $FOO environment variable in 's' """
def lookup_func(match):
return os.environ[match.group(1)]
return ENV_VAR_RE.sub(lookup_func, s)
def dump_config(d, source_path, out):
"""
Dump a Hadoop-style XML configuration file.
'd': a dictionary of name/value pairs.
'source_path': the path where 'd' was parsed from.
'out': stream to write to
"""
header = """\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<!--
NOTE: THIS FILE IS AUTO-GENERATED FROM:
{source_path}
EDITS BY HAND WILL BE LOST!
-->
<configuration>""".format(source_path=os.path.abspath(source_path))
print(dedent(header), file=out)
for k, v in sorted(d.items()):
try:
k_new = _substitute_env_vars(k)
if isinstance(v, int):
v = str(v)
v_new = _substitute_env_vars(v)
except KeyError as e:
raise Exception("failed environment variable substitution for value {k}: {e}"
.format(k=k, e=e))
print("""\
<property>
<name>{name}</name>
<value>{value}</value>
</property>""".format(name=xmlescape(k_new), value=xmlescape(v_new)), file=out)
print("</configuration>", file=out)
def main():
if len(sys.argv) != 3:
print("usage: {prog} <template> <out>".format(prog=sys.argv[0]), file=sys.stderr)
sys.exit(1)
_, in_path, out_path = sys.argv
try:
mod = imp.load_source('template', in_path)
except: # noqa
print("Unable to load template: %s" % in_path, file=sys.stderr)
raise
conf = mod.__dict__.get('CONFIG')
if not isinstance(conf, dict):
raise Exception("module in '{path}' should define a dict named CONFIG"
.format(path=in_path))
tmp_path = out_path + ".tmp"
with open(tmp_path, "w") as out:
try:
dump_config(conf, in_path, out)
except: # noqa
os.unlink(tmp_path)
raise
os.rename(tmp_path, out_path)
if __name__ == "__main__":
main()