-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
273 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
# (C) Datadog, Inc. 2023-present | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
from __future__ import annotations | ||
import os | ||
import re | ||
|
||
import click | ||
from typing import TYPE_CHECKING | ||
|
||
if TYPE_CHECKING: | ||
from ddev.cli.application import Application | ||
EXCLUDED_INTEGRATIONS = {'kubelet', 'openstack'} | ||
|
||
REQUEST_LIBRARY_FUNC_RE = r"requests.[get|post|head|put|patch|delete]*\(" | ||
HTTP_WRAPPER_INIT_CONFIG_RE = r"init_config\/[http|openmetrics_legacy|openmetrics]*" | ||
HTTP_WRAPPER_INSTANCE_RE = r"instances\/[http|openmetrics_legacy|openmetrics]*" | ||
|
||
def get_default_config_spec(check_name, app): | ||
return os.path.join(app.repo.path, check_name, 'assets', 'configuration', 'spec.yaml') | ||
|
||
def validate_config_http(file, check): | ||
"""Determines if integration with http wrapper class | ||
uses the http template in its spec.yaml file. | ||
file -- filepath of file to validate | ||
check -- name of the check that file belongs to | ||
""" | ||
error_message = [] | ||
if not os.path.exists(file): | ||
return | ||
|
||
has_failed = False | ||
with open(file, 'r', encoding='utf-8') as f: | ||
read_file = f.read() | ||
has_init_config_http = re.search(HTTP_WRAPPER_INIT_CONFIG_RE, read_file) | ||
has_instance_http = re.search(HTTP_WRAPPER_INSTANCE_RE, read_file) | ||
|
||
if has_init_config_http and has_instance_http: | ||
return | ||
|
||
if not has_instance_http: | ||
message = ( | ||
f"Detected {check} is missing `instances/http` or `instances/openmetrics_legacy` template in spec.yaml" | ||
) | ||
error_message.append(message) | ||
|
||
has_failed = True | ||
|
||
if not has_init_config_http: | ||
message = ( | ||
f"Detected {check} is missing `init_config/http` or `init_config/openmetrics_legacy` template in spec.yaml" | ||
) | ||
|
||
error_message.append(message) | ||
has_failed = True | ||
|
||
return has_failed, error_message | ||
|
||
def validate_use_http_wrapper_file(file, check): | ||
"""Return true if the file uses the http wrapper class. | ||
Also outputs every instance of deprecated request library function use | ||
file -- filepath of file to validate | ||
check -- name of the check | ||
""" | ||
file_uses_http_wrapper = False | ||
has_failed = False | ||
error_message = '' | ||
with open(file, 'r', encoding='utf-8') as f: | ||
read_file = f.read() | ||
found_match_arg = re.search(r"auth=|header=", read_file) | ||
found_http = re.search(r"self.http|OpenMetricsBaseCheck", read_file) | ||
skip_validation = re.search(r"SKIP_HTTP_VALIDATION", read_file) | ||
http_func = re.search(REQUEST_LIBRARY_FUNC_RE, read_file) | ||
if http_func and not skip_validation: | ||
error_message += ( | ||
f'Check `{check}` uses `{http_func.group(0)}` in `{os.path.basename(file)}`, ' | ||
f'please use the HTTP wrapper instead\n' | ||
f"If this a genuine usage of the parameters, " | ||
f"please inline comment `# SKIP_HTTP_VALIDATION`\n" | ||
) | ||
return False, True, None, error_message | ||
if found_http and not skip_validation: | ||
return found_http, has_failed, found_match_arg, error_message | ||
|
||
return file_uses_http_wrapper, has_failed, None, error_message | ||
|
||
def validate_use_http_wrapper(check, app): | ||
"""Return true if the check uses the http wrapper class in any of its files. | ||
If any of the check's files uses the request library, abort. | ||
check -- name of the check | ||
""" | ||
has_failed = False | ||
check_uses_http_wrapper = False | ||
warning_message = '' | ||
error_message = '' | ||
for file in app.repo.integrations.get(check).package_files(): | ||
file_str = str(file) | ||
if file_str.endswith('.py'): | ||
file_uses_http_wrapper, file_uses_request_lib, has_arg_warning, error = validate_use_http_wrapper_file(file_str, check) | ||
has_failed = has_failed or file_uses_request_lib | ||
error_message += error | ||
check_uses_http_wrapper = check_uses_http_wrapper or file_uses_http_wrapper | ||
if check_uses_http_wrapper and has_arg_warning: | ||
# Check for headers= or auth= | ||
warning_message += ( | ||
f" The HTTP wrapper contains parameter `{has_arg_warning.group().replace('=', '')}`, " | ||
f"this configuration is handled by the wrapper automatically.\n" | ||
f" If this a genuine usage of the parameters, " | ||
f"please inline comment `# SKIP_HTTP_VALIDATION`\n" | ||
) | ||
pass | ||
|
||
if has_failed: | ||
return check_uses_http_wrapper, warning_message, error_message | ||
app.abort() | ||
return check_uses_http_wrapper, warning_message, error_message | ||
|
||
|
||
@click.command(short_help='Validate usage of http wrapper') | ||
@click.argument('check', nargs=-1) | ||
@click.pass_obj | ||
def http(app: Application, check: tuple[str, ...]): | ||
"""Validate all integrations for usage of http wrapper. | ||
If `check` is specified, only the check will be validated, | ||
an 'all' `check` value will validate all checks. | ||
""" | ||
validation_tracker = app.create_validation_tracker('HTTP wrapper validation') | ||
has_failed = False | ||
|
||
check_iterable = app.repo.integrations.iter(check) | ||
app.display_info(f"Validating {sum(1 for _ in check_iterable)} integrations for usage of http wrapper...") | ||
|
||
for curr_check in app.repo.integrations.iter(check): | ||
check_uses_http_wrapper = False | ||
|
||
# Validate use of http wrapper (self.http.[...]) in check's .py files | ||
if curr_check.name not in EXCLUDED_INTEGRATIONS: | ||
check_uses_http_wrapper, warning_message, error_message = validate_use_http_wrapper(curr_check.name, app) | ||
|
||
if warning_message: | ||
validation_tracker.warning((curr_check.display_name,), message=warning_message) | ||
if error_message: | ||
has_failed = True | ||
validation_tracker.error((curr_check.display_name,), message=error_message) | ||
# Validate use of http template in check's spec.yaml (if exists) | ||
if check_uses_http_wrapper: | ||
validate_config_result = validate_config_http(get_default_config_spec(curr_check.name, app), curr_check.name) | ||
if validate_config_result: | ||
config_http_failure, config_http_msg = validate_config_result | ||
has_failed = config_http_failure or has_failed | ||
validation_tracker.error((curr_check.display_name,), message='\n'.join(config_http_msg)) | ||
|
||
if has_failed: | ||
validation_tracker.display() | ||
app.abort() | ||
else: | ||
validation_tracker.success() | ||
validation_tracker.display() | ||
app.display_success('Completed http validation!') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# (C) Datadog, Inc. 2023-present | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
def test_warn_headers_auth(ddev, repository, helpers): | ||
check = 'apache' | ||
file_path = repository.path / check / 'datadog_checks' / check / 'apache.py' | ||
with file_path.open(encoding='utf-8') as file: | ||
file_contents = file.readlines() | ||
|
||
file_contents[16] = " auth='test'" | ||
|
||
with file_path.open(mode='w', encoding='utf-8') as file: | ||
file.writelines(file_contents) | ||
|
||
result = ddev('validate', 'http', check) | ||
assert result.exit_code == 0, result.output | ||
warning = 'The HTTP wrapper contains parameter `auth`, this configuration is' | ||
assert warning in helpers.remove_trailing_spaces(result.output) | ||
|
||
def test_uses_requests(ddev, repository, helpers): | ||
check = 'apache' | ||
file_path = repository.path / check / 'datadog_checks' / check / 'apache.py' | ||
with file_path.open(encoding='utf-8') as file: | ||
file_contents = file.readlines() | ||
|
||
file_contents[16] = " test=requests.get()" | ||
|
||
with file_path.open(mode='w', encoding='utf-8') as file: | ||
file.writelines(file_contents) | ||
|
||
result = ddev('validate', 'http', check) | ||
assert result.exit_code == 1, result.output | ||
error = 'Check `apache` uses `requests.get(` in `apache.py`,' | ||
assert error in helpers.remove_trailing_spaces(result.output) | ||
|
||
def test_spec_missing_info_config(ddev, repository, helpers): | ||
import yaml | ||
check = 'apache' | ||
|
||
spec_yaml = repository.path / check / 'assets' / 'configuration' / 'spec.yaml' | ||
with spec_yaml.open(encoding='utf-8') as file: | ||
spec_info = yaml.safe_load(file) | ||
|
||
spec_info['files'][0]['options'][0]['options'] = [] | ||
|
||
output = yaml.safe_dump(spec_info, default_flow_style=False, sort_keys=False) | ||
with spec_yaml.open(mode='w', encoding='utf-8') as file: | ||
file.write(output) | ||
|
||
result = ddev('validate', 'http', check) | ||
|
||
assert result.exit_code == 1, result.output | ||
error = 'Detected apache is missing `init_config/http` or' | ||
assert error in helpers.remove_trailing_spaces(result.output) | ||
|
||
def test_spec_missing_instance(ddev, repository, helpers): | ||
import yaml | ||
check = 'apache' | ||
|
||
spec_yaml = repository.path / check / 'assets' / 'configuration' / 'spec.yaml' | ||
with spec_yaml.open(encoding='utf-8') as file: | ||
spec_info = yaml.safe_load(file) | ||
|
||
spec_info['files'][0]['options'][1]['options'] = spec_info['files'][0]['options'][1]['options'][0] | ||
|
||
output = yaml.safe_dump(spec_info, default_flow_style=False, sort_keys=False) | ||
with spec_yaml.open(mode='w', encoding='utf-8') as file: | ||
file.write(output) | ||
|
||
result = ddev('validate', 'http', check) | ||
|
||
assert result.exit_code == 1, result.output | ||
error = 'Detected apache is missing `instances/http` or' | ||
assert error in helpers.remove_trailing_spaces(result.output) | ||
|
||
|
||
def test_validate_http_success(ddev, repository, helpers): | ||
result = ddev('validate', 'http', 'apache', 'arangodb', 'zk') | ||
assert result.exit_code == 0, result.output | ||
assert helpers.remove_trailing_spaces(result.output) == helpers.dedent( | ||
""" | ||
Validating 3 integrations for usage of http wrapper... | ||
HTTP wrapper validation | ||
Passed: 1 | ||
Completed http validation! | ||
""" | ||
) | ||
|
||
# def test_exactly_one_flag(ddev, repository, helpers): | ||
# codecov_yaml = repository.path / '.codecov.yml' | ||
|
||
# with codecov_yaml.open(encoding='utf-8') as file: | ||
# codecov_yaml_info = yaml.safe_load(file) | ||
|
||
# codecov_yaml_info['coverage']['status']['project']['ActiveMQ_XML']['flags'].append('test') | ||
|
||
# output = yaml.safe_dump(codecov_yaml_info, default_flow_style=False, sort_keys=False) | ||
# with codecov_yaml.open(mode='w', encoding='utf-8') as file: | ||
# file.write(output) | ||
|
||
# result = ddev("validate", "ci") | ||
|
||
# assert result.exit_code == 1, result.output | ||
# error = "Project `ActiveMQ_XML` must have exactly one flag" | ||
# assert error in helpers.remove_trailing_spaces(result.output) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters