diff --git a/generic_k8s_webhook/config_parser/operator_parser.py b/generic_k8s_webhook/config_parser/operator_parser.py index c379c81..334357a 100644 --- a/generic_k8s_webhook/config_parser/operator_parser.py +++ b/generic_k8s_webhook/config_parser/operator_parser.py @@ -8,7 +8,7 @@ class MetaOperatorParser: def __init__(self, list_op_parser_classes: list[type], raw_str_parser: expr_parser.IRawStringParser) -> None: - self.dict_op_parser = {} + self.dict_op_parser: dict[str, OperatorParser] = {} for op_parser_class in list_op_parser_classes: # Make sure that op_parser_class is a proper "OperatorParser" derived class if ( @@ -25,15 +25,17 @@ def __init__(self, list_op_parser_classes: list[type], raw_str_parser: expr_pars self.raw_str_parser = raw_str_parser - def parse(self, op_spec: dict | str, path_op: str) -> operators.Operator: + def parse(self, op_spec: dict | str | list, path_op: str) -> operators.Operator: if isinstance(op_spec, dict): return self._parse_dict(op_spec, path_op) if isinstance(op_spec, str): return self._parse_str(op_spec, path_op) + if isinstance(op_spec, list): + return self._parse_list(op_spec, path_op) raise RuntimeError(f"Cannot parse the type {type(op_spec)}. It must be dict or str") def _parse_dict(self, op_spec: dict, path_op: str) -> operators.Operator: - """It's used to parse a structured operator. Example: + """It's used to parse a structured operator with a well defined key. Example: ```yaml sum: @@ -63,6 +65,12 @@ def _parse_str(self, op_spec: str, path_op: str) -> operators.Operator: except Exception as e: raise ParsingException(f"Error when parsing {path_op}") from e + def _parse_list(self, op_spec: list, path_op: str) -> operators.Operator: + if "list" not in self.dict_op_parser: + raise RuntimeError(f"Couldn't find the 'list' parser to parse the list in {path_op}") + list_parser = self.dict_op_parser["list"] + return list_parser.parse(op_spec, path_op) + class OperatorParser(abc.ABC): def __init__(self, meta_op_parser: MetaOperatorParser) -> None: @@ -80,14 +88,7 @@ def get_name(cls) -> str: class BinaryOpParser(OperatorParser): def parse(self, op_inputs: dict | list, path_op: str) -> operators.BinaryOp: - if isinstance(op_inputs, list): - list_parser = ListParser(self.meta_op_parser) - args = list_parser.parse(op_inputs, path_op) - elif isinstance(op_inputs, dict): - args = self.meta_op_parser.parse(op_inputs, path_op) - else: - raise ValueError(f"Expected dict or list as input, but got {op_inputs}") - + args = self.meta_op_parser.parse(op_inputs, path_op) try: return self.get_operator_cls()(args) except TypeError as e: diff --git a/generic_k8s_webhook/main.py b/generic_k8s_webhook/main.py index d234957..ecce0f3 100644 --- a/generic_k8s_webhook/main.py +++ b/generic_k8s_webhook/main.py @@ -24,12 +24,14 @@ def cli(args): accept, patch = webhook.process_manifest(k8s_manifest) if not accept: sys.exit(1) - # Show the patch if it's not None if patch: + # Show the patch if it's not None if args.show_patch: print(json.dumps(patch.patch, indent=2)) else: print(yaml.dump(patch.apply(k8s_manifest), indent=2)) + else: + logging.info("No changes") sys.exit(0) logging.error( f"Couldn't find a webhook called {args.wh_name}. " @@ -95,11 +97,12 @@ def parse_args() -> argparse.ArgumentParser: def main(): args = parse_args() - logging.basicConfig(format="%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s") if args.verbose > 0: logging.basicConfig( format="%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s", level=logging.INFO ) + else: + logging.basicConfig(format="%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s") args.func(args) diff --git a/tests/conditions_test.yaml b/tests/conditions_test.yaml index f34963d..71d2beb 100644 --- a/tests/conditions_test.yaml +++ b/tests/conditions_test.yaml @@ -314,3 +314,31 @@ test_suites: - name: istio maxCPU: 2 expected_result: [true] + - name: BINARY_OP_ON_STR_LIST_MAP + tests: + - schemas: [v1beta1] + cases: + - condition: + all: .nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.*.preference.matchExpressions.* -> .key != "myKey" + context: + - nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: key1 + - preference: + matchExpressions: + - key: key2 + expected_result: true + - condition: + all: .nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.*.preference.matchExpressions.* -> .key != "myKey" + context: + - nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: key1 + - preference: + matchExpressions: + - key: myKey + expected_result: false