blob: b1da1b047fec5bf3718fac82c6e0b68b28ab0c13 [file] [log] [blame]
#!/usr/bin/env python3
# -*- 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.
"""Transforms and validates cros config test from source YAML to target JSON"""
from __future__ import print_function
import argparse
import json
import os
import sys
import yaml # pylint: disable=import-error
# pylint: disable=wrong-import-position
this_dir = os.path.dirname(__file__)
sys.path.insert(0, this_dir)
import libcros_schema
sys.path.pop(0)
default_test_schema = os.path.join(this_dir, 'cros_config_test_schema.yaml')
CHROMEOS = 'chromeos'
DEVICES = 'devices'
ROOT_PATH = 'properties/chromeos/properties/devices/items/properties'
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=__doc__)
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 YAML config file that will be validated/transformed',
required=True)
parser.add_argument(
'-f',
'--filter',
type=str,
help='Filter device by name')
parser.add_argument(
'-o',
'--output',
type=str,
help='Output file that will be generated by the transform (system file)',
required=True)
return parser.parse_args(argv)
def TransformConfig(config, device_filter=None):
"""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 by the tast test program.
Args:
config: Config that will be transformed.
device_filter: Only returns configs that match the filter.
Returns:
Resulting JSON output from the transform.
"""
config_yaml = yaml.load(config, Loader=yaml.SafeLoader)
json_from_yaml = json.dumps(config_yaml, sort_keys=True, indent=2)
json_config = json.loads(json_from_yaml)
configs = []
if DEVICES in json_config[CHROMEOS]:
for device in json_config[CHROMEOS][DEVICES]:
configs.append(device)
if device_filter:
configs = [
config for config in configs if device_filter == config['device-name']
]
# Drop everything except for devices since they were just used as shared
# config in the source yaml.
json_config = {
CHROMEOS: {
DEVICES: configs,
},
}
return libcros_schema.FormatJson(json_config)
def MergeConfig(yaml_file, filter_name):
"""Evaluates and merges all config files into a single configuration.
Args:
yaml_file: List of source config files that will be transformed/merged.
filter_name: Name of device to filter on.
Returns:
Final merged JSON result.
"""
yaml_with_imports = libcros_schema.ApplyImports(yaml_file)
json_transformed_file = TransformConfig(yaml_with_imports, filter_name)
return json_transformed_file
def Start(config, filter_name, output, schema):
"""Transforms and validates a cros config test 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 tast tests.
Verifies that the file complies with the schema verification rules and
performs additional verification checks for config consistency.
Args:
config: Source config file that will be transformed/verified.
filter_name: Device name to filter on.
output: Output file that will be generated by the transform.
schema: Schema file used to verify the config.
"""
json_transform = MergeConfig(config, filter_name)
if schema is None:
schema = default_test_schema
with open(schema, 'r') as schema_stream:
libcros_schema.ValidateConfigSchema(
schema_stream.read(), libcros_schema.FormatJson(json_transform))
if output:
with open(output, 'w') as output_stream:
# Using print function adds proper trailing newline.
print(json_transform, file=output_stream)
else:
print(json_transform)
# The distutils generated command line wrappers will not pass us argv.
def main(argv=None):
"""Main program which parses sys.argv and runs
Args:
argv: List of command line arguments, if None uses sys.argv.
"""
if argv is None:
argv = sys.argv[1:]
opts = ParseArgs(argv)
Start(opts.config, opts.filter, opts.output, opts.schema)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))