<commit_before># (C) Datadog, Inc. 2018-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
import click
import yaml

from datadog_checks.dev.tooling.config_validator.validator import validate_config
from datadog_checks.dev.tooling.config_validator.validator_errors import SEVERITY_ERROR, SEVERITY_WARNING
from datadog_checks.dev.tooling.specs.configuration import ConfigSpec
from datadog_checks.dev.tooling.specs.configuration.consumers import ExampleConsumer

from ....fs import basepath, file_exists, path_join, read_file, write_file
from ...utils import (
    complete_valid_checks,
    get_config_files,
    get_config_spec,
    get_data_directory,
    get_valid_checks,
    get_version_string,
    load_manifest,
)
from ..console import CONTEXT_SETTINGS, abort, echo_failure, echo_info, echo_success, echo_waiting, echo_warning

FILE_INDENT = ' ' * 8

IGNORE_DEFAULT_INSTANCE = {'ceph', 'dotnetclr', 'gunicorn', 'marathon', 'pgbouncer', 'process', 'supervisord'}


@click.command(context_settings=CONTEXT_SETTINGS, short_help='Validate default configuration files')
@click.argument('check', autocompletion=complete_valid_checks, required=False)
@click.option('--sync', '-s', is_flag=True, help='Generate example configuration files based on specifications')
@click.option('--verbose', '-v', is_flag=True, help='Verbose mode')
@click.pass_context
def config(ctx, check, sync, verbose):
    """Validate default configuration files."""
    repo_choice = ctx.obj['repo_choice']
    if check:
        checks = [check]
    elif repo_choice == 'agent':
        checks = ['agent']
    else:
        checks = sorted(get_valid_checks())

    files_failed = {}
    files_warned = {}
    file_counter = []

    echo_waiting('Validating default configuration files...')
    for check in checks:
        check_display_queue = []

        spec_path = get_config_spec(check)
        if not file_exists(spec_path):
            validate_config_legacy(check, check_display_queue, files_failed, files_warned, file_counter)
            if verbose:
                check_display_queue.append(lambda: echo_warning('No spec found', indent=True))
            if check_display_queue:
                echo_info(f'{check}:')
            for display in check_display_queue:
                display()
            continue

        file_counter.append(None)

        # source is the default file name
        if check == 'agent':
            display_name = 'Datadog Agent'
            source = 'datadog'
            version = None
        else:
            display_name = load_manifest(check).get('display_name', check)
            source = check
            version = get_version_string(check)

        spec_file = read_file(spec_path)
        default_temp = validate_default_template(spec_file)
        spec = ConfigSpec(spec_file, source=source, version=version)
        spec.load()

        if not default_temp:
            check_display_queue.append(
                lambda **kwargs: echo_failure("Missing default template in init_config or instances section")
            )

        if spec.errors:
            files_failed[spec_path] = True
            for error in spec.errors:
                check_display_queue.append(lambda error=error, **kwargs: echo_failure(error, **kwargs))
        else:
            if spec.data['name'] != display_name:
                files_failed[spec_path] = True
                check_display_queue.append(
                    lambda **kwargs: echo_failure(
                        f"Spec  name `{spec.data['name']}` should be `{display_name}`", **kwargs
                    )
                )

            example_location = get_data_directory(check)
            example_consumer = ExampleConsumer(spec.data)
            for example_file, (contents, errors) in example_consumer.render().items():
                file_counter.append(None)
                example_file_path = path_join(example_location, example_file)
                if errors:
                    files_failed[example_file_path] = True
                    for error in errors:
                        check_display_queue.append(lambda error=error, **kwargs: echo_failure(error, **kwargs))
                else:
                    if not file_exists(example_file_path) or read_file(example_file_path) != contents:
                        if sync:
                            echo_info(f"Writing config file to `{example_file_path}`")
                            write_file(example_file_path, contents)
                        else:
                            files_failed[example_file_path] = True
                            check_display_queue.append(
                                lambda example_file=example_file, **kwargs: echo_failure(
                                    f'File `{example_file}` is not in sync, run "ddev validate config -s"', **kwargs
                                )
                            )

        if check_display_queue or verbose:
            echo_info(f'{check}:')
            if verbose:
                check_display_queue.append(lambda **kwargs: echo_info('Valid spec', **kwargs))
            for display in check_display_queue:
                display(indent=True)

    num_files = len(file_counter)
    files_failed = len(files_failed)
    files_warned = len(files_warned)
    files_passed = num_files - (files_failed + files_warned)

    if files_failed or files_warned:
        click.echo()

    if files_failed:
        echo_failure(f'Files with errors: {files_failed}')

    if files_warned:
        echo_warning(f'Files with warnings: {files_warned}')

    if files_passed:
        if files_failed or files_warned:
            echo_success(f'Files valid: {files_passed}')
        else:
            echo_success(f'All {num_files} configuration files are valid!')

    if files_failed:
        abort()


def validate_default_template(spec_file):
    init_config_default = False
    instances_default = False
    if 'template: init_config' not in spec_file or 'template: instances' not in spec_file:
        # This config spec does not have init_config or instances
        return True

    for line in spec_file.split('\n'):
        if any(
            template in line
            for template in ['init_config/default', 'init_config/openmetrics_legacy', 'init_config/jmx']
        ):
            init_config_default = True
        if any(template in line for template in ['instances/default', 'instances/openmetrics_legacy', 'instances/jmx']):
            instances_default = True

        if instances_default and init_config_default:
            return True
    return False


def validate_config_legacy(check, check_display_queue, files_failed, files_warned, file_counter):
    config_files = get_config_files(check)
    for config_file in config_files:
        file_counter.append(None)
        file_name = basepath(config_file)
        try:
            file_data = read_file(config_file)
            config_data = yaml.safe_load(file_data)
        except Exception as e:
            files_failed[config_file] = True
<commit_msg>Fix default config validation to include openmetrics template (#9151)<commit_after># (C) Datadog, Inc. 2018-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
import click
import yaml

from datadog_checks.dev.tooling.config_validator.validator import validate_config
from datadog_checks.dev.tooling.config_validator.validator_errors import SEVERITY_ERROR, SEVERITY_WARNING
from datadog_checks.dev.tooling.specs.configuration import ConfigSpec
from datadog_checks.dev.tooling.specs.configuration.consumers import ExampleConsumer

from ....fs import basepath, file_exists, path_join, read_file, write_file
from ...utils import (
    complete_valid_checks,
    get_config_files,
    get_config_spec,
    get_data_directory,
    get_valid_checks,
    get_version_string,
    load_manifest,
)
from ..console import CONTEXT_SETTINGS, abort, echo_failure, echo_info, echo_success, echo_waiting, echo_warning

FILE_INDENT = ' ' * 8

IGNORE_DEFAULT_INSTANCE = {'ceph', 'dotnetclr', 'gunicorn', 'marathon', 'pgbouncer', 'process', 'supervisord'}

TEMPLATES = ['default', 'openmetrics_legacy', 'openmetrics', 'jmx']


@click.command(context_settings=CONTEXT_SETTINGS, short_help='Validate default configuration files')
@click.argument('check', autocompletion=complete_valid_checks, required=False)
@click.option('--sync', '-s', is_flag=True, help='Generate example configuration files based on specifications')
@click.option('--verbose', '-v', is_flag=True, help='Verbose mode')
@click.pass_context
def config(ctx, check, sync, verbose):
    """Validate default configuration files."""
    repo_choice = ctx.obj['repo_choice']
    if check:
        checks = [check]
    elif repo_choice == 'agent':
        checks = ['agent']
    else:
        checks = sorted(get_valid_checks())

    files_failed = {}
    files_warned = {}
    file_counter = []

    echo_waiting('Validating default configuration files...')
    for check in checks:
        check_display_queue = []

        spec_path = get_config_spec(check)
        if not file_exists(spec_path):
            validate_config_legacy(check, check_display_queue, files_failed, files_warned, file_counter)
            if verbose:
                check_display_queue.append(lambda: echo_warning('No spec found', indent=True))
            if check_display_queue:
                echo_info(f'{check}:')
            for display in check_display_queue:
                display()
            continue

        file_counter.append(None)

        # source is the default file name
        if check == 'agent':
            display_name = 'Datadog Agent'
            source = 'datadog'
            version = None
        else:
            display_name = load_manifest(check).get('display_name', check)
            source = check
            version = get_version_string(check)

        spec_file = read_file(spec_path)
        default_temp = validate_default_template(spec_file)
        spec = ConfigSpec(spec_file, source=source, version=version)
        spec.load()

        if not default_temp:
            check_display_queue.append(
                lambda **kwargs: echo_failure("Missing default template in init_config or instances section")
            )

        if spec.errors:
            files_failed[spec_path] = True
            for error in spec.errors:
                check_display_queue.append(lambda error=error, **kwargs: echo_failure(error, **kwargs))
        else:
            if spec.data['name'] != display_name:
                files_failed[spec_path] = True
                check_display_queue.append(
                    lambda **kwargs: echo_failure(
                        f"Spec  name `{spec.data['name']}` should be `{display_name}`", **kwargs
                    )
                )

            example_location = get_data_directory(check)
            example_consumer = ExampleConsumer(spec.data)
            for example_file, (contents, errors) in example_consumer.render().items():
                file_counter.append(None)
                example_file_path = path_join(example_location, example_file)
                if errors:
                    files_failed[example_file_path] = True
                    for error in errors:
                        check_display_queue.append(lambda error=error, **kwargs: echo_failure(error, **kwargs))
                else:
                    if not file_exists(example_file_path) or read_file(example_file_path) != contents:
                        if sync:
                            echo_info(f"Writing config file to `{example_file_path}`")
                            write_file(example_file_path, contents)
                        else:
                            files_failed[example_file_path] = True
                            check_display_queue.append(
                                lambda example_file=example_file, **kwargs: echo_failure(
                                    f'File `{example_file}` is not in sync, run "ddev validate config -s"', **kwargs
                                )
                            )

        if check_display_queue or verbose:
            echo_info(f'{check}:')
            if verbose:
                check_display_queue.append(lambda **kwargs: echo_info('Valid spec', **kwargs))
            for display in check_display_queue:
                display(indent=True)

    num_files = len(file_counter)
    files_failed = len(files_failed)
    files_warned = len(files_warned)
    files_passed = num_files - (files_failed + files_warned)

    if files_failed or files_warned:
        click.echo()

    if files_failed:
        echo_failure(f'Files with errors: {files_failed}')

    if files_warned:
        echo_warning(f'Files with warnings: {files_warned}')

    if files_passed:
        if files_failed or files_warned:
            echo_success(f'Files valid: {files_passed}')
        else:
            echo_success(f'All {num_files} configuration files are valid!')

    if files_failed:
        abort()


def validate_default_template(spec_file):
    init_config_default = False
    instances_default = False
    if 'template: init_config' not in spec_file or 'template: instances' not in spec_file:
        # This config spec does not have init_config or instances
        return True

    for line in spec_file.split('\n'):
        if any("init_config/{}".format(template) in line for template in TEMPLATES):
            init_config_default = True
        if any("instances/{}".format(template) in line for template in TEMPLATES):
            instances_default = True

        if instances_default and init_config_default:
            return True
    return False


def validate_config_legacy(check, check_display_queue, files_failed, files_warned, file_counter):
    config_files = get_config_files(check)
    for config_file in config_files:
        file_counter.append(None)
        file_name = basepath(config_file)
        try:
            file_data = read_file(config_file)
            config_data = yaml.safe_load(file_data)
        except Exception as e:
            files_failed[config_file] = True

{'fraction_non_alphanumeric': 0.08575448469926135, 'fraction_numerical': 0.0011255715793176222, 'mean_word_length': 2.4454677653902084, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4671980', 'n_tokens_mistral': 3981, 'n_tokens_neox': 3849, 'n_words': 922}