|
1 | 1 | import json |
2 | 2 | import copy |
3 | 3 | import requests |
| 4 | +import re |
4 | 5 |
|
5 | 6 | from sdcclient._common import _SdcCommon |
6 | 7 |
|
@@ -430,31 +431,10 @@ def add_dashboard_panel(self, dashboard, name, panel_type, metrics, scope=None, |
430 | 431 | 'groupAggregation': metric['aggregations']['group'] if 'aggregations' in metric else None, |
431 | 432 | 'propertyName': property_name + str(i) |
432 | 433 | }) |
433 | | - # |
434 | | - # Convert scope to format used by Sysdig Monitor |
435 | | - # |
436 | | - if scope != None: |
437 | | - filter_expressions = scope.strip(' \t\n\r?!.').split(" and ") |
438 | | - filters = [] |
439 | | - |
440 | | - for filter_expression in filter_expressions: |
441 | | - values = filter_expression.strip(' \t\n\r?!.').split("=") |
442 | | - if len(values) != 2: |
443 | | - return [False, "invalid scope format"] |
444 | | - filters.append({ |
445 | | - 'metric': values[0].strip(' \t\n\r?!.'), |
446 | | - 'op': '=', |
447 | | - 'value': values[1].strip(' \t\n\r"?!.'), |
448 | | - 'filters': None |
449 | | - }) |
450 | | - |
451 | | - if len(filters) > 0: |
452 | | - panel_configuration['filter'] = { |
453 | | - 'filters': { |
454 | | - 'logic': 'and', |
455 | | - 'filters': filters |
456 | | - } |
457 | | - } |
| 434 | + |
| 435 | + panel_configuration['scope'] = scope |
| 436 | + # if chart scope is equal to dashboard scope, set it as non override |
| 437 | + panel_configuration['overrideFilter'] = ('scope' in dashboard and dashboard['scope'] != scope) or ('scope' not in dashboard and scope != None) |
458 | 438 |
|
459 | 439 | # |
460 | 440 | # Configure panel type |
@@ -580,17 +560,32 @@ def create_dashboard_from_template(self, dashboard_name, template, scope, shared |
580 | 560 | template['isPublic'] = public |
581 | 561 | template['publicToken'] = None |
582 | 562 |
|
583 | | - # |
584 | 563 | # set dashboard scope to the specific parameter |
585 | | - # NOTE: Individual panels might override the dashboard scope, the override will NOT be reset |
586 | | - # |
| 564 | + scopeExpression = self.convert_scope_string_to_expression(scope) |
| 565 | + if scopeExpression[0] == False: |
| 566 | + return scopeExpression |
587 | 567 | template['filterExpression'] = scope |
| 568 | + template['scopeExpressionList'] = map(lambda ex: {'operand':ex['operand'], 'operator':ex['operator'],'value':ex['value'],'displayName':'', 'isVariable':False}, scopeExpression[1]) |
| 569 | + |
| 570 | + if 'widgets' in template and template['widgets'] is not None: |
| 571 | + # Default dashboards (aka Explore views) specify panels with the property `widgets`, |
| 572 | + # while custom dashboards use `items` |
| 573 | + template['items'] = list(template['widgets']) |
| 574 | + del template['widgets'] |
588 | 575 |
|
589 | | - if 'items' in template: |
| 576 | + # NOTE: Individual panels might override the dashboard scope, the override will NOT be reset |
| 577 | + if 'items' in template and template['items'] is not None: |
590 | 578 | for chart in template['items']: |
591 | | - if 'overrideFilter' in chart and chart['overrideFilter'] == False: |
| 579 | + if 'overrideFilter' not in chart: |
| 580 | + chart['overrideFilter'] = False |
| 581 | + |
| 582 | + if chart['overrideFilter'] == False: |
592 | 583 | # patch frontend bug to hide scope override warning even when it's not really overridden |
593 | 584 | chart['scope'] = scope |
| 585 | + |
| 586 | + # if chart scope is equal to dashboard scope, set it as non override |
| 587 | + chart_scope = chart['scope'] if 'scope' in chart else None |
| 588 | + chart['overrideFilter'] = chart_scope != scope |
594 | 589 |
|
595 | 590 | if 'annotations' in template: |
596 | 591 | template['annotations'].update(annotations) |
@@ -754,6 +749,64 @@ def get_metrics(self): |
754 | 749 | res = requests.get(self.url + '/api/data/metrics', headers=self.hdrs, verify=self.ssl_verify) |
755 | 750 | return self._request_result(res) |
756 | 751 |
|
| 752 | + @staticmethod |
| 753 | + def convert_scope_string_to_expression(scope): |
| 754 | + '''**Description** |
| 755 | + Internal function to convert a filter string to a filter object to be used with dashboards. |
| 756 | + ''' |
| 757 | + # |
| 758 | + # NOTE: The supported grammar is not perfectly aligned with the grammar supported by the Sysdig backend. |
| 759 | + # Proper grammar implementation will happen soon. |
| 760 | + # For practical purposes, the parsing will have equivalent results. |
| 761 | + # |
| 762 | + |
| 763 | + if scope is None or not scope: |
| 764 | + return [True, []] |
| 765 | + |
| 766 | + expressions = [] |
| 767 | + string_expressions = scope.strip(' \t\n\r').split(' and ') |
| 768 | + expression_re = re.compile('^(?P<not>not )?(?P<operand>[^ ]+) (?P<operator>=|!=|in|contains|starts with) (?P<value>(:?"[^"]+"|\'[^\']+\'|\(.+\)|.+))$') |
| 769 | + |
| 770 | + for string_expression in string_expressions: |
| 771 | + matches = expression_re.match(string_expression) |
| 772 | + |
| 773 | + if matches is None: |
| 774 | + return [False, 'invalid scope format'] |
| 775 | + |
| 776 | + is_not_operator = matches.group('not') is not None |
| 777 | + |
| 778 | + if matches.group('operator') == 'in': |
| 779 | + list_value = matches.group('value').strip(' ()') |
| 780 | + value_matches = re.findall('(:?\'[^\',]+\')|(:?"[^",]+")|(:?[,]+)', list_value) |
| 781 | + |
| 782 | + if len(value_matches) == 0: |
| 783 | + return [False, 'invalid scope value list format'] |
| 784 | + |
| 785 | + value_matches = map(lambda v: v[0] if v[0] else v[1], value_matches) |
| 786 | + values = map(lambda v: v.strip(' "\''), value_matches) |
| 787 | + else: |
| 788 | + values = [matches.group('value').strip('"\'')] |
| 789 | + |
| 790 | + operator_parse_dict = { |
| 791 | + 'in': 'in' if not is_not_operator else 'notIn', |
| 792 | + '=': 'equals' if not is_not_operator else 'notEquals', |
| 793 | + '!=': 'notEquals' if not is_not_operator else 'equals', |
| 794 | + 'contains': 'contains' if not is_not_operator else 'notContains', |
| 795 | + 'starts with': 'startsWith' |
| 796 | + } |
| 797 | + |
| 798 | + operator = operator_parse_dict.get(matches.group('operator'), None) |
| 799 | + if operator is None: |
| 800 | + return [False, 'invalid scope operator'] |
| 801 | + |
| 802 | + expressions.append({ |
| 803 | + 'operand': matches.group('operand'), |
| 804 | + 'operator': operator, |
| 805 | + 'value': values |
| 806 | + }) |
| 807 | + |
| 808 | + return [True, expressions] |
| 809 | + |
757 | 810 |
|
758 | 811 | # For backwards compatibility |
759 | 812 | SdcClient = SdMonitorClient |
0 commit comments