Add script to covert nic configs to use OS::Heat::Value

This adds a new script to convert jinja rendered nic config
templates to use OS::Heat::Value.

There is lots of duplication of code as tht is not installed
as a python package and can also be extended later to handle
multiple files if required.

Change-Id: I06ef54f70ffcd4fc1e501bf9c8395bbede1c6dda
This commit is contained in:
Rabi Mishra 2020-09-19 12:58:15 +05:30
parent 02f80c05d0
commit eb8691c239
2 changed files with 230 additions and 2 deletions

220
tools/convert_nic_config.py Executable file
View File

@ -0,0 +1,220 @@
#!/usr/bin/env python
# Licensed 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.
import argparse
import collections
import datetime
import os
import re
import shutil
import six
import sys
import yaml
def parse_opts(argv):
parser = argparse.ArgumentParser(
description='Convert to new NIC config templates with '
'OS::Heat::Value resources.')
parser.add_argument('-t', '--template', metavar='TEMPLATE_FILE',
help=("Existing NIC config template to conver."),
required=True)
parser.add_argument('--discard-comments', metavar='DISCARD_COMMENTS',
help="Discard comments from the template. (The "
"scripts functions to keep YAML file comments in "
"place, does not work in all scenarios.)",
default=False)
opts = parser.parse_args(argv[1:])
return opts
def to_commented_yaml(filename):
"""Convert comments into 'comments<num>: ...' YAML"""
out_str = ''
last_non_comment_spaces = ''
with open(filename, 'r') as f:
comment_count = 0
for line in f:
# skip blank line
if line.isspace():
continue
char_count = 0
spaces = ''
for char in line:
char_count += 1
if char == ' ':
spaces += ' '
next
elif char == '#':
comment_count += 1
comment = line[char_count:-1]
last_non_comment_spaces = spaces
out_str += "%scomment%i_%i: '%s'\n" % (
last_non_comment_spaces, comment_count, len(spaces),
comment)
break
else:
last_non_comment_spaces = spaces
out_str += line
# inline comments check
m = re.match(".*:.*#(.*)", line)
if m:
comment_count += 1
out_str += "%s inline_comment%i: '%s'\n" % (
last_non_comment_spaces, comment_count, m.group(1))
break
with open(filename, 'w') as f:
f.write(out_str)
return out_str
def to_normal_yaml(filename):
"""Convert back to normal #commented YAML"""
with open(filename, 'r') as f:
data = f.read()
out_str = ''
next_line_break = False
for line in data.split('\n'):
# get_input not supported by run-os-net-config.sh script
line = line.replace('get_input: ', '')
# Normal comments
m = re.match(" +comment[0-9]+_([0-9]+): '(.*)'.*", line)
# Inline comments
i = re.match(" +inline_comment[0-9]+: '(.*)'.*", line)
if m:
if next_line_break:
out_str += '\n'
next_line_break = False
for x in range(0, int(m.group(1))):
out_str += " "
out_str += "#%s\n" % m.group(2)
elif i:
out_str += " #%s\n" % i.group(1)
next_line_break = False
else:
if next_line_break:
out_str += '\n'
out_str += line
next_line_break = True
if next_line_break:
out_str += '\n'
with open(filename, 'w') as f:
f.write(out_str)
return out_str
class TemplateDumper(yaml.SafeDumper):
def represent_ordered_dict(self, data):
return self.represent_dict(data.items())
def description_presenter(self, data):
if not len(data) > 80:
return self.represent_scalar('tag:yaml.org,2002:str', data)
return self.represent_scalar('tag:yaml.org,2002:str', data, style='>')
class TemplateLoader(yaml.SafeLoader):
def construct_mapping(self, node):
self.flatten_mapping(node)
return collections.OrderedDict(self.construct_pairs(node))
TemplateDumper.add_representer(six.text_type,
TemplateDumper.description_presenter)
TemplateDumper.add_representer(six.binary_type,
TemplateDumper.description_presenter)
TemplateDumper.add_representer(collections.OrderedDict,
TemplateDumper.represent_ordered_dict)
TemplateLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
TemplateLoader.construct_mapping)
def write_template(template, filename=None):
with open(filename, 'w') as f:
yaml.dump(template, f, TemplateDumper, width=120,
default_flow_style=False)
def validate_template(template):
if not os.path.exists(template):
raise RuntimeError('Template not provided.')
if not os.path.isfile(template):
raise RuntimeError('Template %s is not a file.')
pass
def backup_template(template):
extension = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
backup_filename = os.path.realpath(template) + '.' + extension
if os.path.exists(backup_filename):
raise RuntimeError('Backupe file: %s already exists. Aborting!'
% backup_filename)
shutil.copyfile(template, backup_filename)
print('The original template was saved as: %s' % backup_filename)
def needs_conversion():
with open(OPTS.template, 'r') as f:
template = yaml.load(f.read(), Loader=TemplateLoader)
net_config_res = template['resources'].get('OsNetConfigImpl')
if (net_config_res and net_config_res[
'type'] == 'OS::Heat::SoftwareConfig'):
backup_template(OPTS.template)
if not OPTS.discard_comments:
# Convert comments '# ...' into 'comments<num>: ...'
# is not lost when loading the data.
to_commented_yaml(OPTS.template)
return True
return False
def convert_to_heat_value_resource():
if needs_conversion():
with open(OPTS.template, 'r') as f:
template = yaml.load(f.read(), Loader=TemplateLoader)
net_config_res = template['resources']['OsNetConfigImpl']
net_config_res_props = net_config_res['properties']
# set the type to OS::Heat::Value
net_config_res['type'] = 'OS::Heat::Value'
del net_config_res_props['group']
old_config = net_config_res_props['config']
new_config = old_config['str_replace']['params']['$network_config']
net_config_res_props['config'] = new_config
outputs = template['outputs']
del outputs['OS::stack_id']
outputs['config'] = {}
outputs['config']['value'] = 'get_attr[OsNetConfigImpl, value]'
write_template(template, filename=OPTS.template)
if not OPTS.discard_comments:
# Convert previously converted comments, 'comments<num>: ...'
# YAML back to normal #commented YAML
to_normal_yaml(OPTS.template)
print('The update template was saved as: %s' % OPTS.template)
else:
print('Template does not need conversion: %s' % OPTS.template)
OPTS = parse_opts(sys.argv)
convert_to_heat_value_resource()

View File

@ -60,6 +60,8 @@ def parse_opts(argv):
return opts
# FIXME: This duplicates code from tools/convert_nic_config.py, we should
# refactor to share the common code
def to_commented_yaml(filename):
"""Convert comments into 'comments<num>: ...' YAML"""
@ -104,6 +106,8 @@ def to_commented_yaml(filename):
return out_str
# FIXME: This duplicates code from tools/convert_nic_config.py, we should
# refactor to share the common code
def to_normal_yaml(filename):
"""Convert back to normal #commented YAML"""
@ -156,7 +160,7 @@ class TemplateDumper(yaml.SafeDumper):
return self.represent_scalar('tag:yaml.org,2002:str', data, style='>')
# FIXME: This duplicates code from tools/yaml-nic-config-2-script.py, we should
# FIXME: This duplicates code from tools/convert_nic_config.py, we should
# refactor to share the common code
# We load mappings into OrderedDict to preserve their order
class TemplateLoader(yaml.SafeLoader):
@ -176,7 +180,7 @@ TemplateLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
TemplateLoader.construct_mapping)
# FIXME: This duplicates code from tools/yaml-nic-config-2-script.py, we should
# FIXME: This duplicates code from tools/convert_nic_config.py, we should
# refactor to share the common code
def write_template(template, filename=None):
with open(filename, 'w') as f:
@ -223,6 +227,8 @@ def process_templates_and_get_reference_parameters():
return reference_params
# FIXME: This duplicates code from tools/convert_nic_config.py, we should
# refactor to share the common code
def validate_template():
if not os.path.exists(OPTS.template):
raise RuntimeError('Template not provided.')
@ -231,6 +237,8 @@ def validate_template():
pass
# FIXME: This duplicates code from tools/convert_nic_config.py, we should
# refactor to share the common code
def backup_template():
extension = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
backup_filename = os.path.realpath(OPTS.template) + '.' + extension