browbeat/ansible/gather/roles/common/files/openstack-config-parser.py
Joe Talerico 8c668d51aa Gather script for containers
Currently our gather playbook assumes all configuration files will be in
/etc. This assumption isn't true with container deployments. Currently
the configuration files are located in
/var/lib/config-data/<service>/etc/<service>/.

The Gather script needs to support both container and non-container
deployments. This patchset updates the config parser python script
to check if a service is in the running containers list and then
determine it's appropriate path, grab all of multiple config files
in that path, then parse and drop them off for ansible to use.

This method automagically supports all possible mixes of containerized
uncontainerized services and will always grab the correct config even
if that changes build to build or run to run.

It's also easily extensible for many possible config locations or
different container types by adding another condition or additional
search paths.

Change-Id: I95a3059c7fc263733ac64aa894c6dbf11e2a909f
Closes-bug: #1701264
2017-07-13 10:02:13 -04:00

155 lines
5.4 KiB
Python

#!/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 sys
import os
import subprocess
# usage: openstack-config-parser.py [service] [output file]
def run_cmd(cmd):
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
output_dict = {}
output_dict['stdout'] = stdout.strip()
output_dict['stderr'] = stderr.strip()
output_dict['rc'] = process.returncode
return output_dict
def strip_chars(line):
forbidden_chars = ['#', '\n', '"', '\\', ' ', '<', '>']
for char in forbidden_chars:
line = line.replace(char, '')
return line
def parse_config(serviceName, fileName):
# a dict containing key/value pairs, last value is what is stored.
values = {}
with open(fileName) as config:
section = None
for line in config:
pair = strip_chars(line)
pair = pair.split('=')
# excludes any line without a key/val pair
valid_line = not line.startswith("# ") and \
'[' not in line and line != '\n' \
and line != '#\n' and "password" \
not in line.lower()
if line.startswith('['):
section = line.replace('[','').replace(']','').replace('\n','')
if '#' not in line and valid_line and not section == None and len(pair) == 2:
pair[0] = strip_chars(pair[0])
pair[1] = strip_chars(pair[1])
values["openstack_S_" + serviceName + "_S_" + section + "_S_" + pair[0]] = pair[1]
return values
def try_type(val):
try:
int(val)
return val
except (ValueError, TypeError):
try:
float(val)
return val
except (ValueError, TypeError):
if type(val) is list:
return "\"" + str(val) + "\""
elif val.lower() in ("true", "false"):
return val
else:
return "\"" + val + "\""
def add_conf_location(serviceName, fileName, values):
# Stores the exact location we gathered this config from.
index = "openstack_S_" + serviceName + "_S_" + "Browbeat" + "_S_" + "gather_conf_path"
if index in values:
values[index].append(fileName)
else:
values[index] = [fileName]
def print_vars_file(values, fileName):
with open(fileName, 'w') as output:
for key in values:
output.write(key + ": " + try_type(values[key]) + "\n")
def is_containerized(service_name):
out = run_cmd("docker ps")
if service_name in out['stdout']:
return True
else:
return False
def get_configs_list(path, extension='.conf'):
configs = []
for item in os.listdir(path):
if item.endswith(extension):
configs.extend([item])
return configs
def get_neutron_plugin(output, cfg_path):
plugin = output['openstack_S_neutron_S_DEFAULT_S_core_plugin']
plugin_path = "{}/plugins/{}/".format(cfg_path, plugin)
for item in get_configs_list(plugin_path, extension='.ini'):
full_path = "{}/{}".format(plugin_path,
item)
output.update(parse_config("neutron-plugin", full_path))
add_conf_location("neutron", full_path, output)
return output
def main():
if len(sys.argv) < 3:
print("usage: openstack-config-parser.py [service] [output file]")
exit(1)
service_name = sys.argv[1]
outfile = sys.argv[2]
# This is a list of services that require exceptions from the usual
# pattern when gathering their config files
pattern_exceptions = ['glance']
in_container = is_containerized(service_name)
if 'undercloud' in service_name:
cfg_path = "/home/stack"
elif in_container and service_name not in pattern_exceptions:
cfg_path = "/var/lib/config-data/{}/etc/{}".format(service_name,
service_name)
# Glance has all configs in a folder named glance_api, ps shows no
# processes outside of the container, so I assume those are the right
# configs, even though the container is also named glance-api
# jkilpatr 7/13/17
elif in_container and 'glance' in service_name:
cfg_path = "/var/lib/config-data/glance_api/etc/glance"
else:
cfg_path = "/etc/{}".format(service_name)
print("Parsing all .conf files in {}".format(cfg_path))
output = {}
for item in get_configs_list(cfg_path):
full_path = "{}/{}".format(cfg_path,
item)
output.update(parse_config(service_name, full_path))
add_conf_location(service_name, full_path, output)
# Required to find and load the active neutron plugin file.
if 'neutron' in service_name:
output.update(get_neutron_plugin(output, cfg_path))
print_vars_file(output, outfile)
if __name__ == '__main__':
sys.exit(main())