def __init__( self, *, name: str = 'kopf', field: dicts.FieldSpec = 'status.{name}.progress', touch_field: dicts.FieldSpec = 'status.{name}.dummy', ) -> None: super().__init__() self._name = name real_field = field.format(name=name) if isinstance(field, str) else field self._field = dicts.parse_field(real_field) real_field = touch_field.format(name=name) if isinstance(touch_field, str) else touch_field self._touch_field = dicts.parse_field(real_field)
def test_catchall_handlers_with_field_ignored(cause_no_field, registry, handler_factory): cause = cause_no_field handler_factory(field=parse_field('some-field')) handlers = registry.resource_spawning_handlers[ cause.resource].get_handlers(cause) assert not handlers
def register( self, fn: ResourceHandlerFn, *, id: Optional[str] = None, reason: Optional[causation.Reason] = None, event: Optional[str] = None, # deprecated, use `reason` field: Optional[dicts.FieldSpec] = None, errors: Optional[ErrorsMode] = None, timeout: Optional[float] = None, retries: Optional[int] = None, backoff: Optional[float] = None, cooldown: Optional[float] = None, # deprecated, use `backoff` initial: Optional[bool] = None, deleted: Optional[bool] = None, requires_finalizer: bool = False, labels: Optional[bodies.Labels] = None, annotations: Optional[bodies.Annotations] = None, ) -> ResourceHandlerFn: if reason is None and event is not None: reason = causation.Reason(event) real_field = dicts.parse_field(field) or None # to not store tuple() as a no-field case. real_id = generate_id(fn=fn, id=id, prefix=self.prefix, suffix=".".join(real_field or [])) handler = ResourceHandler( id=real_id, fn=fn, reason=reason, field=real_field, errors=errors, timeout=timeout, retries=retries, backoff=backoff, cooldown=cooldown, initial=initial, deleted=deleted, requires_finalizer=requires_finalizer, labels=labels, annotations=annotations, ) self.append(handler) return fn
def decorator( # lgtm[py/similar-function] fn: callbacks.ResourceChangingFn, ) -> callbacks.ResourceChangingFn: _warn_deprecated_signatures(fn) _warn_deprecated_filters(labels, annotations) _warn_conflicting_values(field, value) real_registry = registry if registry is not None else registries.get_default_registry( ) real_field = dicts.parse_field( field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id(fn=fn, id=id) selector = references.Selector(group, version, plural) handler = handlers.ResourceChangingHandler( fn=fn, id=real_id, errors=errors, timeout=timeout, retries=retries, backoff=backoff, cooldown=cooldown, selector=selector, labels=labels, annotations=annotations, when=when, field=real_field, value=value, old=None, new=None, field_needs_change=False, initial=None, deleted=None, requires_finalizer=bool(not optional), reason=handlers.Reason.DELETE, ) real_registry.resource_changing_handlers.append(handler) return fn
def decorator( fn: callbacks.ResourceHandlerFn) -> callbacks.ResourceHandlerFn: real_registry = registry if registry is not None else registries.get_default_registry( ) real_resource = resources.Resource(group, version, plural) real_field = dicts.parse_field( field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id(fn=fn, id=id, suffix=".".join(real_field or [])) handler = handlers.ResourceHandler( fn=fn, id=real_id, field=real_field, errors=errors, timeout=timeout, retries=retries, backoff=backoff, cooldown=cooldown, labels=labels, annotations=annotations, when=when, initial=None, deleted=None, requires_finalizer=None, reason=None, ) real_registry.resource_changing_handlers[real_resource].append(handler) return fn
def decorator( # lgtm[py/similar-function] fn: callbacks.ResourceTimerFn, ) -> callbacks.ResourceTimerFn: _warn_deprecated_signatures(fn) _warn_deprecated_filters(labels, annotations) _warn_conflicting_values(field, value) real_registry = registry if registry is not None else registries.get_default_registry( ) real_field = dicts.parse_field( field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id(fn=fn, id=id) selector = references.Selector(group, version, plural) handler = handlers.ResourceTimerHandler( fn=fn, id=real_id, errors=errors, timeout=timeout, retries=retries, backoff=backoff, cooldown=cooldown, selector=selector, labels=labels, annotations=annotations, when=when, field=real_field, value=value, initial_delay=initial_delay, requires_finalizer=True, sharp=sharp, idle=idle, interval=interval, ) real_registry.resource_spawning_handlers.append(handler) return fn
def decorator( # lgtm[py/similar-function] fn: callbacks.ResourceWatchingFn, ) -> callbacks.ResourceWatchingFn: _warn_deprecated_signatures(fn) _warn_deprecated_filters(labels, annotations) _warn_conflicting_values(field, value) real_registry = registry if registry is not None else registries.get_default_registry( ) real_field = dicts.parse_field( field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id(fn=fn, id=id) selector = references.Selector(group, version, plural) handler = handlers.ResourceWatchingHandler( fn=fn, id=real_id, errors=None, timeout=None, retries=None, backoff=None, cooldown=None, selector=selector, labels=labels, annotations=annotations, when=when, field=real_field, value=value, ) real_registry.resource_watching_handlers.append(handler) return fn
def test_catchall_handlers_with_field_ignored(cause_no_diff, registry, handler_factory): cause = cause_no_diff handler_factory(reason=None, field=parse_field('some-field')) handlers = registry.resource_changing_handlers[ cause.resource].get_handlers(cause) assert not handlers
def decorator( # lgtm[py/similar-function] fn: callbacks.ResourceDaemonFn, ) -> callbacks.ResourceDaemonFn: _warn_conflicting_values(field, value) _verify_filters(labels, annotations) real_registry = registry if registry is not None else registries.get_default_registry() real_field = dicts.parse_field(field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id(fn=fn, id=id, suffix=".".join(real_field or [])) selector = references.Selector( __group_or_groupversion_or_name, __version_or_name, __name, group=group, version=version, kind=kind, plural=plural, singular=singular, shortcut=shortcut, category=category, ) handler = handlers.ResourceDaemonHandler( fn=fn, id=real_id, param=param, errors=errors, timeout=timeout, retries=retries, backoff=backoff, selector=selector, labels=labels, annotations=annotations, when=when, field=real_field, value=value, initial_delay=initial_delay, requires_finalizer=True, cancellation_backoff=cancellation_backoff, cancellation_timeout=cancellation_timeout, cancellation_polling=cancellation_polling, ) real_registry._resource_spawning.append(handler) return fn
def register( self, fn: ResourceHandlerFn, id: Optional[str] = None, reason: Optional[causation.Reason] = None, event: Optional[str] = None, # deprecated, use `reason` field: Optional[dicts.FieldSpec] = None, timeout: Optional[float] = None, initial: Optional[bool] = None, requires_finalizer: bool = False, labels: Optional[bodies.Labels] = None, annotations: Optional[bodies.Annotations] = None, ) -> ResourceHandlerFn: if reason is None and event is not None: reason = causation.Reason(event) real_field = dicts.parse_field(field) or None # to not store tuple() as a no-field case. real_id = generate_id(fn=fn, id=id, prefix=self.prefix, suffix=".".join(real_field or [])) handler = ResourceHandler( id=real_id, fn=fn, reason=reason, field=real_field, timeout=timeout, initial=initial, labels=labels, annotations=annotations, ) self.append(handler) if requires_finalizer: self._handlers_requiring_finalizer.append(handler) return fn # to be usable as a decorator too.
def register( self, fn: callbacks.ResourceChangingFn, *, resource: references.Selector, id: Optional[str] = None, reason: Optional[handlers.Reason] = None, event: Optional[str] = None, # deprecated, use `reason` field: Optional[dicts.FieldSpec] = None, errors: Optional[handlers.ErrorsMode] = None, timeout: Optional[float] = None, retries: Optional[int] = None, backoff: Optional[float] = None, cooldown: Optional[float] = None, # deprecated, use `backoff` initial: Optional[bool] = None, deleted: Optional[bool] = None, requires_finalizer: bool = False, labels: Optional[filters.MetaFilter] = None, annotations: Optional[filters.MetaFilter] = None, when: Optional[callbacks.WhenFilterFn] = None, ) -> callbacks.ResourceChangingFn: warnings.warn( "registry.register() is deprecated; " "use @kopf.on... decorators with registry= kwarg.", DeprecationWarning) if reason is None and event is not None: reason = handlers.Reason(event) real_field = dicts.parse_field( field) or None # to not store tuple() as a no-field case. real_id = generate_id(fn=fn, id=id, suffix=".".join(real_field or [])) handler = handlers.ResourceChangingHandler( id=real_id, fn=fn, reason=reason, errors=errors, timeout=timeout, retries=retries, backoff=backoff, cooldown=cooldown, initial=initial, deleted=deleted, requires_finalizer=requires_finalizer, selector=resource, labels=labels, annotations=annotations, when=when, field=real_field, value=None, old=None, new=None, field_needs_change=None, ) self.append(handler) return fn
def __init__( self, *, name: str = 'kopf', field: dicts.FieldSpec = 'status.{name}.last-handled-configuration', ) -> None: super().__init__() self._name = name real_field = field.format(name=self._name) if isinstance(field, str) else field self._field = dicts.parse_field(real_field)
def decorator( # lgtm[py/similar-function] fn: callbacks.ResourceChangingFn, ) -> callbacks.ResourceChangingFn: _warn_conflicting_values(field, value) _verify_filters(labels, annotations) real_registry = registry if registry is not None else registries.get_default_registry( ) real_field = dicts.parse_field( field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id(fn=fn, id=id, suffix=".".join(real_field or [])) selector = references.Selector( __group_or_groupversion_or_name, __version_or_name, __name, group=group, version=version, kind=kind, plural=plural, singular=singular, shortcut=shortcut, category=category, ) handler = handlers.ResourceChangingHandler( fn=fn, id=real_id, param=param, errors=errors, timeout=timeout, retries=retries, backoff=backoff, selector=selector, labels=labels, annotations=annotations, when=when, field=real_field, value=value, old=None, new=None, field_needs_change=False, initial=None, deleted=None, requires_finalizer=bool(not optional), reason=handlers.Reason.DELETE, ) real_registry._resource_changing.append(handler) return fn
def decorator( # lgtm[py/similar-function] fn: callbacks.ResourceChangingFn, ) -> callbacks.ResourceChangingFn: parent_handler = handling.handler_var.get() if not isinstance(parent_handler, handlers.ResourceChangingHandler): raise TypeError( "Sub-handlers are only supported for resource-changing handlers." ) _warn_incompatible_parent_with_oldnew(parent_handler, old, new) _warn_deprecated_signatures(fn) _warn_deprecated_filters(labels, annotations) _warn_conflicting_values(field, value, old, new) real_registry = registry if registry is not None else handling.subregistry_var.get( ) real_field = dicts.parse_field( field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id( fn=fn, id=id, prefix=parent_handler.id if parent_handler else None) handler = handlers.ResourceChangingHandler( fn=fn, id=real_id, errors=errors, timeout=timeout, retries=retries, backoff=backoff, cooldown=cooldown, selector=None, labels=labels, annotations=annotations, when=when, field=real_field, value=value, old=old, new=new, field_needs_change=parent_handler. field_needs_change, # inherit dymaically initial=None, deleted=None, requires_finalizer=None, reason=None, ) real_registry.append(handler) return fn
def decorator( # lgtm[py/similar-function] fn: callbacks.ResourceWatchingFn, ) -> callbacks.ResourceWatchingFn: _warn_conflicting_values(field, value) _verify_filters(labels, annotations) real_registry = registry if registry is not None else registries.get_default_registry() real_field = dicts.parse_field(field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id(fn=fn, id=id) selector = references.Selector( __group_or_groupversion_or_name, __version_or_name, __name, group=group, version=version, kind=kind, plural=plural, singular=singular, shortcut=shortcut, category=category, ) handler = handlers.ResourceWatchingHandler( fn=fn, id=real_id, errors=None, timeout=None, retries=None, backoff=None, selector=selector, labels=labels, annotations=annotations, when=when, field=real_field, value=value, ) real_registry._resource_watching.append(handler) return fn
def handler(request, callback, selector): handler = ResourceWatchingHandler( selector=selector, annotations={'known': 'value'}, labels={'known': 'value'}, field=parse_field('spec.field'), value='value', when=None, fn=some_fn, id='a', errors=None, timeout=None, retries=None, backoff=None, # irrelevant ) if request.param in ['annotations', 'labels']: handler = dataclasses.replace(handler, **{request.param: { 'known': callback }}) else: handler = dataclasses.replace(handler, **{request.param: callback}) return handler
def decorator( # lgtm[py/similar-function] fn: callbacks.ResourceWebhookFn, ) -> callbacks.ResourceWebhookFn: _warn_conflicting_values(field, value) _verify_filters(labels, annotations) real_registry = registry if registry is not None else registries.get_default_registry() real_field = dicts.parse_field(field) or None # to not store tuple() as a no-field case. real_id = registries.generate_id(fn=fn, id=id, suffix=".".join(real_field or [])) selector = references.Selector( __group_or_groupversion_or_name, __version_or_name, __name, group=group, version=version, kind=kind, plural=plural, singular=singular, shortcut=shortcut, category=category, ) handler = handlers.ResourceWebhookHandler( fn=fn, id=real_id, param=param, 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=handlers.WebhookType.MUTATING, operation=operation, persistent=persistent, side_effects=side_effects, ignore_failures=ignore_failures, ) real_registry._resource_webhooks.append(handler) return fn
def field(self, field: dicts.FieldSpec) -> None: real_field = field.format(name=self._name) if isinstance(field, str) else field self._field = dicts.parse_field(real_field)
import datetime import logging import time from typing import AsyncGenerator, Collection, Iterable, Optional, Tuple, Type, Union from kopf.clients import patching from kopf.engines import loggers from kopf.structs import bodies, configuration, containers, dicts, \ diffs, patches, primitives, references # How often to wake up from the long sleep, to show liveness in the logs. WAITING_KEEPALIVE_INTERVAL = 10 * 60 # K8s-managed fields that are removed completely when patched to an empty list/dict. KNOWN_INCONSISTENCIES = ( dicts.parse_field('metadata.annotations'), dicts.parse_field('metadata.finalizers'), dicts.parse_field('metadata.labels'), ) async def apply( *, settings: configuration.OperatorSettings, resource: references.Resource, body: bodies.Body, patch: patches.Patch, delays: Collection[float], logger: loggers.ObjectLogger, stream_pressure: Optional[asyncio.Event] = None, # None for tests ) -> bool:
def test_from_string_one_level(): path = parse_field('field') assert isinstance(path, tuple) assert path == ('field', )
def test_catchall_handlers_with_field_found( cause_with_diff, registry, handler_factory): cause = cause_with_diff handler_factory(reason=None, field=parse_field('known-field')) handlers = registry._resource_changing.get_handlers(cause) assert handlers
def test_catchall_handlers_with_field_found(cause_with_field, registry, handler_factory): cause = cause_with_field handler_factory(field=parse_field('known-field')) handlers = registry._resource_spawning.get_handlers(cause) assert handlers
def test_from_string_two_levels(): path = parse_field('field.subfield') assert isinstance(path, tuple) assert path == ('field', 'subfield')
def test_from_list(): path = parse_field(['field', 'subfield']) assert isinstance(path, tuple) assert path == ('field', 'subfield')
def test_from_tuple(): path = parse_field(('field', 'subfield')) assert isinstance(path, tuple) assert path == ('field', 'subfield')
def test_catchall_handlers_with_field_found(cause_with_field, registry, handler_factory): cause = cause_with_field handler_factory(field=parse_field('some-field')) handlers = registry.resource_watching_handlers.get_handlers(cause) assert handlers
def test_from_others_fails(val): with pytest.raises(ValueError): parse_field(val)
def test_from_none(): path = parse_field(None) assert isinstance(path, tuple) assert len(path) == 0
def test_catchall_handlers_with_field_ignored(cause_no_field, registry, handler_factory): cause = cause_no_field handler_factory(field=parse_field('known-field')) handlers = registry._resource_watching.get_handlers(cause) assert not handlers