Skip to content

Commit

Permalink
Update dashboard validation for Manifest V2 (#10398)
Browse files Browse the repository at this point in the history
* Update dashboard validation for Manifest V2

* Handle embedded widget objects in dashboards
  • Loading branch information
mgarabed authored Oct 12, 2021
1 parent b4cc35b commit d46143f
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

import click

from ....fs import write_file
from ....utils import read_file
from ...annotations import annotate_display_queue, annotate_error
from ...manifest_utils import Manifest
from ...testing import process_checks_option
from ...utils import complete_valid_checks, get_assets_from_manifest, get_manifest_file
from ..console import CONTEXT_SETTINGS, abort, echo_failure, echo_info, echo_success
from ..console import CONTEXT_SETTINGS, abort, echo_debug, echo_failure, echo_info, echo_success

REQUIRED_ATTRIBUTES = {"description", "template_variables", "widgets"}
DASHBOARD_ONLY_FIELDS = {"layout_type", "title", "created_at"}
Expand All @@ -30,9 +32,38 @@ def _is_dashboard_format(payload):
return False


def check_widgets(decoded, filename, app_uuid, fix, file_fixed, file_failed, display_queue):
"""Recursively check the decoded dashboard object for widget references and validate the app_id inside."""
for widget in decoded.get('widgets', []):

if widget.get('definition', {}).get('widgets'):
decoded = {'widgets': widget['definition']['widgets']}
file_fixed, file_failed = check_widgets(
decoded, filename, app_uuid, fix, file_fixed, file_failed, display_queue
)

widget_app_uuid = widget.get('definition', {}).get('app_id')
if widget_app_uuid and widget_app_uuid != app_uuid:
if fix:
widget['definition']['app_id'] = app_uuid
file_fixed = True
continue
else:
file_failed = True
msg = (
f" {filename} widget {widget['id']} does not contain correct app_uuid: "
f"{widget_app_uuid} should be {app_uuid}"
)
display_queue.append(
(echo_failure, msg),
)
return file_fixed, file_failed


@click.command('dashboards', context_settings=CONTEXT_SETTINGS, short_help='Validate dashboard definition JSON files')
@click.argument('check', autocompletion=complete_valid_checks, required=False)
def dashboards(check):
@click.option('--fix', is_flag=True, help='Attempt to fix errors')
def dashboards(check, fix):
"""Validate all Dashboard definition files.
If `check` is specified, only the check will be validated, if check value is 'changed' will only apply to changed
Expand All @@ -45,11 +76,15 @@ def dashboards(check):
echo_info(f"Validating Dashboard definition files for {len(checks)} checks...")

for check_name in checks:
echo_debug(f"Validating Dashboard definition files for {check_name} check...")
display_queue = []
file_failed = False
file_fixed = False

dashboard_relative_locations, invalid_files = get_assets_from_manifest(check_name, 'dashboards')
manifest = Manifest.load_manifest(check_name)
manifest_file = get_manifest_file(check_name)

dashboard_relative_locations, invalid_files = get_assets_from_manifest(check_name, 'dashboards')
for invalid in invalid_files:
message = f'{invalid} does not exist'
echo_info(f'{check_name}... ', nl=False)
Expand Down Expand Up @@ -90,6 +125,21 @@ def dashboards(check):
(echo_failure, f' {dashboard_filename} is not using the new /dashboard payload format.'),
)

# check app_id for Manifest V2 dashboards
if manifest.version == Manifest.V2:
echo_debug(f'Validating app dashboard {dashboard_filename} ..')
app_uuid = manifest.get_app_uuid()

file_fixed, file_failed = check_widgets(
decoded, dashboard_filename, app_uuid, fix, file_fixed, file_failed, display_queue
)

if fix and file_fixed:
new_dashboard = f"{json.dumps(decoded, indent=2, separators=(',', ': '))}\n"
write_file(dashboard_file, new_dashboard)
echo_info(f"{dashboard_file}... ", nl=False)
echo_success("FIXED")

if file_failed:
failed_checks += 1
# Display detailed info if file is invalid
Expand Down
26 changes: 26 additions & 0 deletions datadog_checks_dev/datadog_checks/dev/tooling/manifest_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .commands.console import abort
from .constants import get_root
from .datastructures import JSONDict
from .manifest_validator.constants import V1, V2
from .utils import load_manifest

NON_INTEGRATION_PATHS = [
Expand All @@ -23,6 +24,10 @@ class Manifest:
This also supports the case of querying file information about the Agent
"""

# add version constants to avoid extra imports
V1 = V1
V2 = V2

@staticmethod
def load_manifest(check):
"""
Expand Down Expand Up @@ -50,6 +55,7 @@ class Agent:
def __init__(self, check_name, manifest_json):
self._check_name = check_name
self._manifest_json = manifest_json
self.version = None

def get_config_spec(self):
return os.path.join(get_root(), 'pkg', 'config', 'conf_spec.yaml')
Expand All @@ -64,10 +70,20 @@ class ManifestV1:
def __init__(self, check_name, manifest_json):
self._check_name = check_name
self._manifest_json = JSONDict(manifest_json)
self.version = V1

def get_path(self, path):
return self._manifest_json.get(path)

def get_display_name(self):
return self._manifest_json['display_name']

def get_app_id(self):
return None

def get_app_uuid(self):
return None

def get_metric_prefix(self):
return self._manifest_json['metric_prefix']

Expand Down Expand Up @@ -100,10 +116,20 @@ class ManifestV2:
def __init__(self, check_name, manifest_json):
self._check_name = check_name
self._manifest_json = JSONDict(manifest_json)
self.version = V2

def get_path(self, path):
return self._manifest_json.get(path)

def get_display_name(self):
return self._manifest_json.get_path("/assets/integration/source_type_name")

def get_app_id(self):
return self._manifest_json['app_id']

def get_app_uuid(self):
return self._manifest_json['app_uuid']

def get_metric_prefix(self):
return self._manifest_json.get_path("/assets/integration/metrics/prefix") or ''

Expand Down

0 comments on commit d46143f

Please sign in to comment.