diff --git a/kopf/__init__.py b/kopf/__init__.py index 49138c65..32b5aa86 100644 --- a/kopf/__init__.py +++ b/kopf/__init__.py @@ -87,6 +87,7 @@ from kopf._cogs.structs.reviews import ( WebhookClientConfigService, WebhookClientConfig, + Operation, Operations, UserInfo, Headers, @@ -203,6 +204,7 @@ 'WebhookClientConfigService', 'WebhookClientConfig', 'Operations', + 'Operation', 'UserInfo', 'Headers', 'SSLPeer', diff --git a/kopf/_cogs/structs/reviews.py b/kopf/_cogs/structs/reviews.py index 84bb4219..96f52adc 100644 --- a/kopf/_cogs/structs/reviews.py +++ b/kopf/_cogs/structs/reviews.py @@ -58,7 +58,7 @@ class RequestPayload(TypedDict): userInfo: UserInfo name: str namespace: Optional[str] - operation: Operations + operation: Operation options: Union[None, CreateOptions, UpdateOptions, DeleteOptions] dryRun: bool object: bodies.RawBody diff --git a/kopf/_core/engines/admission.py b/kopf/_core/engines/admission.py index 2fd879d7..5fb11193 100644 --- a/kopf/_core/engines/admission.py +++ b/kopf/_core/engines/admission.py @@ -422,7 +422,11 @@ def build_webhooks( [resource.plural] if handler.subresource is None else [f'{resource.plural}/{handler.subresource}'] ), - 'operations': ['*'] if handler.operations is None else handler.operations, + 'operations': ['*'] if handler.operation is None + else ( + handler.operation if isinstance(handler.operation,list) + else [handler.operation] + ), 'scope': '*', # doesn't matter since a specific resource is used. } for resource in resources diff --git a/kopf/_core/intents/handlers.py b/kopf/_core/intents/handlers.py index 8021b3dd..11f12d91 100644 --- a/kopf/_core/intents/handlers.py +++ b/kopf/_core/intents/handlers.py @@ -40,7 +40,7 @@ def adjust_cause(self, cause: execution.CauseT) -> execution.CauseT: class WebhookHandler(ResourceHandler): fn: callbacks.WebhookFn # typing clarification reason: causes.WebhookType - operations: Optional[list[str]] + operation: Optional[list[str] | str] subresource: Optional[str] persistent: Optional[bool] side_effects: Optional[bool] @@ -68,13 +68,9 @@ class ChangingHandler(ResourceHandler): fn: callbacks.ChangingFn # typing clarification reason: Optional[causes.Reason] initial: Optional[bool] - deleted: Optional[ - bool - ] # used for mixed-in (initial==True) @on.resume handlers only. + deleted: Optional[bool] # used for mixed-in (initial==True) @on.resume handlers only. requires_finalizer: Optional[bool] - field_needs_change: Optional[ - bool - ] # to identify on-field/on-update with support for old=/new=. + field_needs_change: Optional[bool] # to identify on-field/on-update with support for old=/new=. old: Optional[filters.ValueFilter] new: Optional[filters.ValueFilter] @@ -88,12 +84,8 @@ class SpawningHandler(ResourceHandler): @dataclasses.dataclass(frozen=True) class DaemonHandler(SpawningHandler): fn: callbacks.DaemonFn # typing clarification - cancellation_backoff: Optional[ - float - ] # how long to wait before actual cancellation. - cancellation_timeout: Optional[ - float - ] # how long to wait before giving up on cancellation. + cancellation_backoff: Optional[float] # how long to wait before actual cancellation. + cancellation_timeout: Optional[float] # how long to wait before giving up on cancellation. cancellation_polling: Optional[float] # how often to check for cancellation status. def __str__(self) -> str: diff --git a/kopf/_core/intents/registries.py b/kopf/_core/intents/registries.py index bbebedc6..02eded36 100644 --- a/kopf/_core/intents/registries.py +++ b/kopf/_core/intents/registries.py @@ -241,7 +241,9 @@ def iter_handlers( # For deletion, exclude all mutation handlers unless explicitly enabled. non_mutating = handler.reason != causes.WebhookType.MUTATING non_deletion = cause.operation != 'DELETE' - explicitly_for_deletion = handler.operations == ['DELETE'] + explicitly_for_deletion = ( + handler.operation == ['DELETE'] or handler.operation == 'DELETE' + ) if non_mutating or non_deletion or explicitly_for_deletion: # Filter by usual criteria: labels, annotations, fields, callbacks. if match(handler=handler, cause=cause): diff --git a/kopf/on.py b/kopf/on.py index d131ca85..9b6d054a 100644 --- a/kopf/on.py +++ b/kopf/on.py @@ -153,7 +153,7 @@ def validate( # lgtm[py/similar-function] # Handler's behaviour specification: id: Optional[str] = None, param: Optional[Any] = None, - operations: Optional[reviews.Operations] = None, # -> .webhooks.*.rules.*.operations[0] + operation: Optional[reviews.Operations | reviews.Operation] = None, subresource: Optional[str] = None, # -> .webhooks.*.rules.*.resources[] persistent: Optional[bool] = None, side_effects: Optional[bool] = None, # -> .webhooks.*.sideEffects @@ -186,7 +186,7 @@ def decorator( # lgtm[py/similar-function] errors=None, timeout=None, retries=None, backoff=None, # TODO: add some meaning later selector=selector, labels=labels, annotations=annotations, when=when, field=real_field, value=value, - reason=causes.WebhookType.VALIDATING, operations=operations, subresource=subresource, + reason=causes.WebhookType.VALIDATING, operation=operation, subresource=subresource, persistent=persistent, side_effects=side_effects, ignore_failures=ignore_failures, ) real_registry._webhooks.append(handler) @@ -210,7 +210,7 @@ def mutate( # lgtm[py/similar-function] # Handler's behaviour specification: id: Optional[str] = None, param: Optional[Any] = None, - operations: Optional[reviews.Operations] = None, # -> .webhooks.*.rules.*.operations[0] + operation: Optional[reviews.Operations | reviews.Operation] = None, subresource: Optional[str] = None, # -> .webhooks.*.rules.*.resources[] persistent: Optional[bool] = None, side_effects: Optional[bool] = None, # -> .webhooks.*.sideEffects @@ -243,7 +243,7 @@ def decorator( # lgtm[py/similar-function] errors=None, timeout=None, retries=None, backoff=None, # TODO: add some meaning later selector=selector, labels=labels, annotations=annotations, when=when, field=real_field, value=value, - reason=causes.WebhookType.MUTATING, operations=operations, subresource=subresource, + reason=causes.WebhookType.MUTATING, operation=operation, subresource=subresource, persistent=persistent, side_effects=side_effects, ignore_failures=ignore_failures, ) real_registry._webhooks.append(handler) diff --git a/tests/admission/test_managed_webhooks.py b/tests/admission/test_managed_webhooks.py index 003ee38a..775ef82c 100644 --- a/tests/admission/test_managed_webhooks.py +++ b/tests/admission/test_managed_webhooks.py @@ -128,10 +128,12 @@ def fn(**_): @pytest.mark.parametrize('opts, key, val', [ (dict(), 'operations', ['*']), - (dict(operations=['CREATE']), 'operations', ['CREATE']), - (dict(operations=['UPDATE']), 'operations', ['UPDATE']), - (dict(operations=['DELETE']), 'operations', ['DELETE']), - (dict(operations=['CREATE','UPDATE']), 'operations', ['CREATE','UPDATE']), + (dict(operation='CREATE'), 'operations', ['CREATE']), + (dict(operation='UPDATE'), 'operations', ['UPDATE']), + (dict(operation='DELETE'), 'operations', ['DELETE']), + (dict(operation=['CREATE','UPDATE']), 'operations', ['CREATE','UPDATE']), + (dict(operation=['CREATE','DELETE']), 'operations', ['CREATE','DELETE']), + (dict(operation=['UPDATE','DELETE']), 'operations', ['UPDATE','DELETE']), ]) @pytest.mark.parametrize('decorator', [kopf.on.validate, kopf.on.mutate]) def test_rule_options_are_mapped(registry, resource, decorator, opts, key, val): diff --git a/tests/admission/test_serving_handler_selection.py b/tests/admission/test_serving_handler_selection.py index ab154568..cdf287b1 100644 --- a/tests/admission/test_serving_handler_selection.py +++ b/tests/admission/test_serving_handler_selection.py @@ -214,7 +214,7 @@ async def test_mutating_handlers_are_selected_for_deletion_if_explicitly_marked( def v_fn(**kwargs): v_mock(**kwargs) - @kopf.on.mutate(*resource, operations=['DELETE']) + @kopf.on.mutate(*resource, operation='DELETE') def m_fn(**kwargs): m_mock(**kwargs)