def test_annotated_cloudconfig_file_schema_annotates_and_adds_footer(self): """With schema_errors, error lines are annotated and a footer added.""" content = dedent("""\ #cloud-config # comment ntp: pools: [-99, 75] """).encode() expected = dedent("""\ #cloud-config # comment ntp: # E1 pools: [-99, 75] # E2,E3 # Errors: ------------- # E1: Some type error # E2: -99 is not a string # E3: 75 is not a string """) parsed_config, schemamarks = load_with_marks(content[13:]) schema_errors = [ ("ntp", "Some type error"), ("ntp.pools.0", "-99 is not a string"), ("ntp.pools.1", "75 is not a string"), ] assert expected == annotated_cloudconfig_file(parsed_config, content, schema_errors, schemamarks=schemamarks)
def test_annotated_cloudconfig_file_annotates_separate_line_items(self): """Errors are annotated for lists with items on separate lines.""" content = dedent("""\ #cloud-config # comment ntp: pools: - -99 - 75 """).encode() expected = dedent("""\ ntp: pools: - -99 # E1 - 75 # E2 """) parsed_config, schemamarks = load_with_marks(content[13:]) schema_errors = [ ("ntp.pools.0", "-99 is not a string"), ("ntp.pools.1", "75 is not a string"), ] assert expected in annotated_cloudconfig_file(parsed_config, content, schema_errors, schemamarks=schemamarks)
def test_annotated_cloudconfig_file_no_schema_errors(self): """With no schema_errors, print the original content.""" content = b"ntp:\n pools: [ntp1.pools.com]\n" parse_cfg, schemamarks = load_with_marks(content) assert content == annotated_cloudconfig_file(parse_cfg, content, schema_errors=[], schemamarks=schemamarks)
def validate_cloudconfig_file(config_path, schema, annotate=False): """Validate cloudconfig file adheres to a specific jsonschema. @param config_path: Path to the yaml cloud-config file to parse, or None to default to system userdata from Paths object. @param schema: Dict describing a valid jsonschema to validate against. @param annotate: Boolean set True to print original config file with error annotations on the offending lines. @raises SchemaValidationError containing any of schema_errors encountered. @raises RuntimeError when config_path does not exist. """ if config_path is None: # Use system's raw userdata path if os.getuid() != 0: raise RuntimeError( "Unable to read system userdata as non-root user." " Try using sudo") paths = read_cfg_paths() user_data_file = paths.get_ipath_cur("userdata_raw") content = load_file(user_data_file, decode=False) else: if not os.path.exists(config_path): raise RuntimeError( "Configfile {0} does not exist".format(config_path)) content = load_file(config_path, decode=False) if not content.startswith(CLOUD_CONFIG_HEADER): errors = (( "format-l1.c1", 'File {0} needs to begin with "{1}"'.format( config_path, CLOUD_CONFIG_HEADER.decode()), ), ) error = SchemaValidationError(errors) if annotate: print( annotated_cloudconfig_file({}, content, error.schema_errors, {})) raise error try: if annotate: cloudconfig, marks = safeyaml.load_with_marks(content) else: cloudconfig = safeyaml.load(content) marks = {} except (yaml.YAMLError) as e: line = column = 1 mark = None if hasattr(e, "context_mark") and getattr(e, "context_mark"): mark = getattr(e, "context_mark") elif hasattr(e, "problem_mark") and getattr(e, "problem_mark"): mark = getattr(e, "problem_mark") if mark: line = mark.line + 1 column = mark.column + 1 errors = (( "format-l{line}.c{col}".format(line=line, col=column), "File {0} is not valid yaml. {1}".format(config_path, str(e)), ), ) error = SchemaValidationError(errors) if annotate: print( annotated_cloudconfig_file({}, content, error.schema_errors, {})) raise error from e if not isinstance(cloudconfig, dict): # Return a meaningful message on empty cloud-config if not annotate: raise RuntimeError("Cloud-config is not a YAML dict.") try: validate_cloudconfig_schema(cloudconfig, schema, strict=True) except SchemaValidationError as e: if annotate: print( annotated_cloudconfig_file(cloudconfig, content, e.schema_errors, marks)) raise
def test_schema_marks_preserved(self, source_yaml, loaded_yaml, schemamarks): assert (loaded_yaml, schemamarks) == load_with_marks(source_yaml)