blob: b1140fd788934eada4f00e822fcb50351124b6bf [file] [log] [blame]
#!/usr/bin/env python2
# Copyright 2017 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.
"""Transforms and validates cros config from source YAML to target JSON"""
from __future__ import print_function
import argparse
import collections
import json
from jsonschema import validate
import sys
import yaml
COMPONENT_CONFIG = 'componentConfig'
MODELS = 'models'
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.iteritems():
if type(v) is list:
new_list = []
for val in v:
new_list.append(GetNamedTuple(val))
new_mapping[k] = new_list
else:
new_mapping[k] = GetNamedTuple(v)
return collections.namedtuple('Config', new_mapping.iterkeys())(**new_mapping)
def ParseArgs(argv):
"""Parse the available arguments.
Invalid arguments or -h cause this function to print a message and exit.
Args:
argv: List of string arguments (excluding program name / argv[0])
Returns:
argparse.Namespace object containing the attributes.
"""
parser = argparse.ArgumentParser(
description='Validates a YAML cros-config and transforms it to JSON')
parser.add_argument(
'-s',
'--schema',
type=str,
help='Path to the schema file used to validate the config')
parser.add_argument(
'-c',
'--config',
type=str,
help='Path to the config file (YAML) that will be validated/transformed')
parser.add_argument(
'-o',
'--output',
type=str,
help='Output file that will be generated by the transform (system file)')
return parser.parse_args(argv)
def TransformConfig(config):
"""Transforms the source config (YAML) to the target system format (JSON)
Applies consistent transforms to covert a source YAML configuration into
JSON output that will be used on the system by cros_config.
Currently, this copies any shared family level config into the model
levels (if they haven't explicitly overridden the values) and then
deletes any family level config (since this will never be used on the
platform).
Args:
config: Config that will be transformed.
Returns:
Resulting JSON output from the transform.
"""
config_yaml = yaml.load(config)
json_from_yaml = json.dumps(config_yaml, sort_keys=True, indent=2)
json_config = json.loads(json_from_yaml)
# If the model didn't set specific config settings, then have it
# automatically inherit the common settings from the family.
if COMPONENT_CONFIG in json_config:
family_config = json_config[COMPONENT_CONFIG]
for model in json_config[MODELS]:
if not COMPONENT_CONFIG in model or model[COMPONENT_CONFIG] is None:
model[COMPONENT_CONFIG] = family_config
else:
model_config = model[COMPONENT_CONFIG]
for attr_name in family_config.keys():
if attr_name not in model_config or not model_config[attr_name]:
model_config[attr_name] = family_config[attr_name]
json_config.pop(COMPONENT_CONFIG)
return json.dumps(json_config, sort_keys=True, indent=2)
def ValidateConfigSchema(schema, config):
"""Validates a transformed cros 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_json = json.loads(schema)
validate(json_config, schema_json)
class ValidationError(Exception):
"""Exception raised for a validation error"""
pass
def ValidateConfig(config):
"""Validates a transformed cros config for general business rules.
Performs name uniqueness checks and any other validation that can't be
easily performed using the schema.
Args:
config: Config (transformed) that will be verified.
"""
json_config = json.loads(config)
model_names = [model['name'] for model in json_config['models']]
if len(model_names) != len(set(model_names)):
raise ValidationError("Model names are not unique: %s" % model_names)
def Main(schema, config, output):
"""Transforms and validates a cros config file for use on the system
Applies consistent transforms to covert a source YAML configuration into
a JSON file that will be used on the system by cros_config.
Verifies that the file complies with the schema verification rules and
performs additional verification checks for config consistency.
Args:
schema: Schema file used to verify the config.
config: Config file that will be verified.
output: Output file that will be generated by the transform.
"""
with open(config, 'r') as config_stream:
json_transform = TransformConfig(config_stream.read())
with open(schema, 'r') as schema_stream:
ValidateConfigSchema(schema_stream.read(), json_transform)
ValidateConfig(json_transform)
with open(output, 'w') as output_stream:
output_stream.write(json_transform)
if __name__ == "__main__":
args = ParseArgs(sys.argv[1:])
Main(args.schema, args.config, args.output)