diff options
Diffstat (limited to 'tools/yaml-validate.py')
-rwxr-xr-x | tools/yaml-validate.py | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/tools/yaml-validate.py b/tools/yaml-validate.py index 95c7d025..1d0dba02 100755 --- a/tools/yaml-validate.py +++ b/tools/yaml-validate.py @@ -19,11 +19,81 @@ import yaml required_params = ['EndpointMap', 'ServiceNetMap', 'DefaultPasswords'] +envs_containing_endpoint_map = ['tls-endpoints-public-dns.yaml', + 'tls-endpoints-public-ip.yaml', + 'tls-everywhere-endpoints-dns.yaml'] +ENDPOINT_MAP_FILE = 'endpoint_map.yaml' + def exit_usage(): print('Usage %s <yaml file or directory>' % sys.argv[0]) sys.exit(1) +def get_base_endpoint_map(filename): + try: + tpl = yaml.load(open(filename).read()) + return tpl['parameters']['EndpointMap']['default'] + except Exception: + print(traceback.format_exc()) + return None + + +def get_endpoint_map_from_env(filename): + try: + tpl = yaml.load(open(filename).read()) + return { + 'file': filename, + 'map': tpl['parameter_defaults']['EndpointMap'] + } + except Exception: + print(traceback.format_exc()) + return None + + +def validate_endpoint_map(base_map, env_map): + return sorted(base_map.keys()) == sorted(env_map.keys()) + + +def validate_mysql_connection(settings): + no_op = lambda *args: False + error_status = [0] + + def mysql_protocol(items): + return items == ['EndpointMap', 'MysqlInternal', 'protocol'] + + def client_bind_address(item): + return 'read_default_file' in item and \ + 'read_default_group' in item + + def validate_mysql_uri(key, items): + # Only consider a connection if it targets mysql + if key.endswith('connection') and \ + search(items, mysql_protocol, no_op): + # Assume the "bind_address" option is one of + # the token that made up the uri + if not search(items, client_bind_address, no_op): + error_status[0] = 1 + return False + + def search(item, check_item, check_key): + if check_item(item): + return True + elif isinstance(item, list): + for i in item: + if search(i, check_item, check_key): + return True + elif isinstance(item, dict): + for k in item.keys(): + if check_key(k, item[k]): + return True + elif search(item[k], check_item, check_key): + return True + return False + + search(settings, no_op, validate_mysql_uri) + return error_status[0] + + def validate_service(filename, tpl): if 'outputs' in tpl and 'role_data' in tpl['outputs']: if 'value' not in tpl['outputs']['role_data']: @@ -41,6 +111,12 @@ def validate_service(filename, tpl): print('ERROR: service_name should match file name for service: %s.' % filename) return 1 + # if service connects to mysql, the uri should use option + # bind_address to avoid issues with VIP failover + if 'config_settings' in role_data and \ + validate_mysql_connection(role_data['config_settings']): + print('ERROR: mysql connection uri should use option bind_address') + return 1 if 'parameters' in tpl: for param in required_params: if param not in tpl['parameters']: @@ -56,6 +132,13 @@ def validate(filename): try: tpl = yaml.load(open(filename).read()) + # The template alias version should be used instead a date, this validation + # will be applied to all templates not just for those in the services folder. + if 'heat_template_version' in tpl and not str(tpl['heat_template_version']).isalpha(): + print('ERROR: heat_template_version needs to be the release alias not a date: %s' + % filename) + return 1 + if (filename.startswith('./puppet/services/') and filename != './puppet/services/services.yaml'): retval = validate_service(filename, tpl) @@ -83,6 +166,8 @@ if len(sys.argv) < 2: path_args = sys.argv[1:] exit_val = 0 failed_files = [] +base_endpoint_map = None +env_endpoint_maps = list() for base_path in path_args: if os.path.isdir(base_path): @@ -94,6 +179,12 @@ for base_path in path_args: if failed: failed_files.append(file_path) exit_val |= failed + if f == ENDPOINT_MAP_FILE: + base_endpoint_map = get_base_endpoint_map(file_path) + if f in envs_containing_endpoint_map: + env_endpoint_map = get_endpoint_map_from_env(file_path) + if env_endpoint_map: + env_endpoint_maps.append(env_endpoint_map) elif os.path.isfile(base_path) and base_path.endswith('.yaml'): failed = validate(base_path) if failed: @@ -103,6 +194,30 @@ for base_path in path_args: print('Unexpected argument %s' % base_path) exit_usage() +if base_endpoint_map and \ + len(env_endpoint_maps) == len(envs_containing_endpoint_map): + for env_endpoint_map in env_endpoint_maps: + matches = validate_endpoint_map(base_endpoint_map, + env_endpoint_map['map']) + if not matches: + print("ERROR: %s needs to be updated to match changes in base " + "endpoint map" % env_endpoint_map['file']) + failed_files.append(env_endpoint_map['file']) + exit_val |= 1 + else: + print("%s matches base endpoint map" % env_endpoint_map['file']) +else: + print("ERROR: Can't validate endpoint maps since a file is missing. " + "If you meant to delete one of these files you should update this " + "tool as well.") + if not base_endpoint_map: + failed_files.append(ENDPOINT_MAP_FILE) + if len(env_endpoint_maps) != len(envs_containing_endpoint_map): + matched_files = set(os.path.basename(matched_env_file['file']) + for matched_env_file in env_endpoint_maps) + failed_files.extend(set(envs_containing_endpoint_map) - matched_files) + exit_val |= 1 + if failed_files: print('Validation failed on:') for f in failed_files: |