5a0d21c4c7
Because we don't need to use yaml.FullLoader, use yaml.SafeLoder
so that we can keep compability with PyYAML < 5.1 .
Change-Id: I51e700f5ad3364c5c8a26aa79b44cd8da1ec469d
(cherry picked from commit ade6c3b37e
)
295 lines
9.8 KiB
Python
Executable File
295 lines
9.8 KiB
Python
Executable File
#!/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 traceback
|
|
import yaml
|
|
|
|
|
|
def parse_opts(argv):
|
|
parser = argparse.ArgumentParser(
|
|
description='Convert an old style NIC config file into the new format '
|
|
'using run-os-net-config.sh')
|
|
parser.add_argument('--script-dir', metavar='<script directory>',
|
|
help="Relative path to run-os-net-config.sh",
|
|
default="network/scripts/run-os-net-config.sh")
|
|
parser.add_argument('files', nargs="+", metavar='<file>',
|
|
help='List of one or more NIC config files to convert')
|
|
parser.add_argument('--yes',
|
|
action='store_true',
|
|
help=("Use --yes to skip the confirmation "
|
|
"to overwrite the original config file "),
|
|
)
|
|
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 == '#':
|
|
last_non_comment_spaces = spaces
|
|
comment_count += 1
|
|
comment = line[char_count:-1]
|
|
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 description(six.text_type):
|
|
pass
|
|
|
|
|
|
# FIXME: Some of this duplicates code from build_endpoint_map.py, we should
|
|
# refactor to share the common code
|
|
class TemplateDumper(yaml.SafeDumper):
|
|
|
|
def represent_ordered_dict(self, data):
|
|
return self.represent_dict(data.items())
|
|
|
|
def description_presenter(self, data):
|
|
if '\n' in data:
|
|
style = '>'
|
|
else:
|
|
style = ''
|
|
return self.represent_scalar(
|
|
yaml.resolver.BaseResolver.DEFAULT_SCALAR_TAG, data, style=style)
|
|
|
|
|
|
# We load mappings into OrderedDict to preserve their order
|
|
class TemplateLoader(yaml.SafeLoader):
|
|
def construct_mapping(self, node):
|
|
self.flatten_mapping(node)
|
|
return collections.OrderedDict(self.construct_pairs(node))
|
|
|
|
|
|
TemplateDumper.add_representer(description,
|
|
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 convert(filename, script_path):
|
|
print('Converting %s' % filename)
|
|
try:
|
|
with open(filename, 'r') as f:
|
|
tpl = yaml.load(f.read(), Loader=TemplateLoader)
|
|
except Exception:
|
|
print(traceback.format_exc())
|
|
return 0
|
|
|
|
for r in (tpl.get('resources', {})).items():
|
|
if (r[1].get('type') == 'OS::Heat::StructuredConfig' and
|
|
r[1].get('properties', {}).get('group') == 'os-apply-config' and
|
|
r[1].get('properties', {}).get('config', {}).get('os_net_config')):
|
|
new_r = collections.OrderedDict()
|
|
new_r['type'] = 'OS::Heat::SoftwareConfig'
|
|
new_r['properties'] = collections.OrderedDict()
|
|
new_r['properties']['group'] = 'script'
|
|
old_net_config = r[1].get(
|
|
'properties', {}).get('config', {}).get('os_net_config')
|
|
new_config = {'str_replace': collections.OrderedDict()}
|
|
new_config['str_replace']['template'] = {'get_file': script_path}
|
|
new_config['str_replace']['params'] = \
|
|
{'$network_config': old_net_config}
|
|
new_r['properties']['config'] = new_config
|
|
tpl['resources'][r[0]] = new_r
|
|
else:
|
|
print("No match %s" % r[0])
|
|
return 0
|
|
|
|
# Preserve typical HOT template key ordering
|
|
od_result = collections.OrderedDict()
|
|
# Need to bump the HOT version so str_replace supports serializing to json
|
|
od_result['heat_template_version'] = "rocky"
|
|
if tpl.get('description'):
|
|
od_result['description'] = description(tpl['description'])
|
|
od_result['parameters'] = tpl['parameters']
|
|
od_result['resources'] = tpl['resources']
|
|
od_result['outputs'] = tpl['outputs']
|
|
|
|
write_template(od_result, filename)
|
|
|
|
return 1
|
|
|
|
|
|
def check_old_style(filename):
|
|
|
|
with open(filename, 'r') as f:
|
|
tpl = yaml.load(f.read(), Loader=yaml.SafeLoader)
|
|
|
|
if isinstance(tpl.get('resources', {}), dict):
|
|
for r in (tpl.get('resources', {})).items():
|
|
if (r[1].get('type') == 'OS::Heat::StructuredConfig' and
|
|
r[1].get('properties', {}).get('group') == 'os-apply-config' and
|
|
r[1].get('properties', {}).get('config', {}).get('os_net_config')):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
opts = parse_opts(sys.argv)
|
|
exit_val = 0
|
|
num_converted = 0
|
|
|
|
for base_path in opts.files:
|
|
if os.path.isfile(base_path) and base_path.endswith('.yaml'):
|
|
|
|
if check_old_style(base_path):
|
|
|
|
# Check for script in the user entered (or default) location or in
|
|
# path relative to NIC config files
|
|
script_paths = [opts.script_dir]
|
|
script_paths.append('../../scripts/run-os-net-config.sh')
|
|
script_paths.append('../network/scripts/run-os-net-config.sh')
|
|
script_paths.append('/usr/share/openstack-tripleo-heat-templates/'
|
|
'network/scripts/run-os-net-config.sh')
|
|
|
|
script_path = None
|
|
for p in script_paths:
|
|
if os.path.isfile(os.path.join(os.path.dirname(base_path), p)):
|
|
script_path = p
|
|
break
|
|
if script_path is None:
|
|
print("Error couldn't find run-os-net-config.sh relative "
|
|
"to filename")
|
|
sys.exit(1)
|
|
|
|
print("Using script at %s" % script_path)
|
|
|
|
extension = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
|
backup_filename = os.path.realpath(base_path) + '.' + extension
|
|
|
|
print('The yaml file will be overwritten and the original saved '
|
|
'as %s' % backup_filename)
|
|
if not (opts.yes or
|
|
input("Overwrite %s? [y/n] " % base_path).lower() == 'y'):
|
|
print("Skipping file %s" % base_path)
|
|
continue
|
|
|
|
if os.path.exists(backup_filename):
|
|
print("Backup file already exists, skipping file %s" %
|
|
base_path)
|
|
continue
|
|
|
|
shutil.copyfile(base_path, backup_filename)
|
|
|
|
to_commented_yaml(base_path)
|
|
num_converted += convert(base_path, script_path)
|
|
to_normal_yaml(base_path)
|
|
else:
|
|
print('File %s is not using old style NIC configuration' %
|
|
base_path)
|
|
|
|
else:
|
|
print('Unexpected argument %s' % base_path)
|
|
|
|
|
|
if num_converted == 0:
|
|
exit_val = 1
|
|
sys.exit(exit_val)
|