blob: bbcc9c9e50671ed32ea65b92b6d1bf3be2714b3d [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Chrome OS Configuration Schema library.
Provides common cros_config and cros_config_test schema functions.
"""
from __future__ import print_function
import collections
import json
import os
import re
from jsonschema import validate # pylint: disable=import-error
import yaml # pylint: disable=import-error
def GetNamedTuple(mapping):
"""Converts a mapping into Named Tuple recursively.
Args:
mapping: A mapping object to be converted.
Returns:
A named tuple generated from mapping
"""
if not isinstance(mapping, collections.Mapping):
return mapping
new_mapping = {}
for k, v in mapping.items():
if isinstance(v, list):
new_list = []
for val in v:
new_list.append(GetNamedTuple(val))
new_mapping[k.replace('-', '_').replace('@', '_')] = new_list
else:
new_mapping[k.replace('-', '_').replace('@', '_')] = GetNamedTuple(v)
return collections.namedtuple('Config', list(new_mapping))(**new_mapping)
def FormatJson(config):
"""Formats JSON for output or printing.
Args:
config: Dictionary to be output
"""
return json.dumps(config, sort_keys=True, indent=2, separators=(',', ': '))
def ValidateConfigSchema(schema, config):
"""Validates a transformed config against the schema specified.
Verifies that the config complies with the schema supplied.
Args:
schema: Source schema used to verify the config.
config: Config (transformed) that will be verified.
"""
json_config = json.loads(config)
schema_yaml = yaml.load(schema, Loader=yaml.SafeLoader)
schema_json_from_yaml = json.dumps(schema_yaml, sort_keys=True, indent=2)
schema_json = json.loads(schema_json_from_yaml)
validate(json_config, schema_json)
def FindImports(config_file, includes):
"""Recursively looks up and finds files to include for yaml.
Args:
config_file: Path to the config file for which to apply imports.
includes: List that is built up through processing the files.
"""
working_dir = os.path.dirname(config_file)
with open(config_file, 'r') as config_stream:
config_lines = config_stream.readlines()
yaml_import_lines = []
found_imports = False
# Parsing out just the imports snippet is required because the YAML
# isn't valid until the imports are eval'd.
for line in config_lines:
if re.match(r'^imports', line):
found_imports = True
yaml_import_lines.append(line)
elif found_imports:
match = re.match(r' *- (.*)', line)
if match:
yaml_import_lines.append(line)
else:
break
if yaml_import_lines:
yaml_import = yaml.load(
'\n'.join(yaml_import_lines), Loader=yaml.SafeLoader)
for import_file in yaml_import.get('imports', []):
full_path = os.path.join(working_dir, import_file)
FindImports(full_path, includes)
includes.append(config_file)
def ApplyImports(config_file):
"""Parses the imports statements and applies them to a result config.
Args:
config_file: Path to the config file for which to apply imports.
Returns:
Raw config with the imports applied.
"""
import_files = []
FindImports(config_file, import_files)
all_yaml_files = []
for import_file in import_files:
with open(import_file, 'r') as yaml_stream:
all_yaml_files.append(yaml_stream.read())
return '\n'.join(all_yaml_files)
# Attributes that are defined as a function of the schema.
# build_only_element: Property is only used during build time.
# default_value: Default value if no property is present.
PropertyAttrs = collections.namedtuple(
'PropertyAttrs', ['build_only_element', 'default_value'])
def GetSchemaPropertyAttrs(schema_yaml):
"""Returns schema defined attributes on a per property basis.
Args:
schema_yaml: Source schema that contains the properties.
Returns:
Dictionary
key - full path to the property in the schema
value - PropertyAttrs object with the schema attributes
"""
root_path = 'properties/chromeos/properties/configs/items/properties'
schema_node = schema_yaml
for element in root_path.split('/'):
schema_node = schema_node[element]
result = collections.OrderedDict()
_GetSchemaPropertyAttrs(schema_node, [], result)
return result
def _GetSchemaPropertyAttrs(schema_node, path, result):
"""Recursively extracts property attributes from the schema.
Args:
schema_node: Single node from the schema
path: Running path that a given node maps to
result: Running collection of results
"""
for key in schema_node:
new_path = path + [key]
current_node = schema_node[key]
if not isinstance(current_node, dict):
# Skip over additionalProperties, required fields.
continue
node_type = current_node['type']
build_only = current_node.get('build-only-element', False)
default_value = current_node.get('default', None)
if build_only or default_value:
result['/%s' % '/'.join(new_path)] = PropertyAttrs(
build_only, default_value)
if node_type == 'array':
if 'properties' in current_node['items']:
_GetSchemaPropertyAttrs(
current_node['items']['properties'], new_path, result)
elif node_type == 'object':
if 'oneOf' in current_node:
for element in current_node['oneOf']:
_GetSchemaPropertyAttrs(element['properties'], new_path, result)
elif 'properties' in current_node:
_GetSchemaPropertyAttrs(
current_node['properties'], new_path, result)