def deliver_results( *, outcomes: Mapping[handlers_.HandlerId, HandlerOutcome], patch: patches.Patch, ) -> None: """ Store the results (as returned from the handlers) to the resource. This is not the handlers' state persistence, but the results' persistence. First, the state persistence is stored under ``.status.kopf.progress``, and can (later) be configured to be stored in different fields for different operators operating the same objects: ``.status.kopf.{somename}.progress``. The handlers' result are stored in the top-level ``.status``. Second, the handler results can (also later) be delivered to other objects, e.g. to their owners or label-selected related objects. For this, another class/module will be added. For now, we keep state- and result persistence in one module, but separated. """ for handler_id, outcome in outcomes.items(): if outcome.exception is not None: pass elif outcome.result is None: pass elif isinstance(outcome.result, collections.abc.Mapping): # TODO: merge recursively (patch-merge), do not overwrite the keys if they are present. patch.setdefault('status', {}).setdefault(handler_id, {}).update(outcome.result) else: patch.setdefault('status', {})[handler_id] = copy.deepcopy(outcome.result)
def block_deletion( *, body: bodies.Body, patch: patches.Patch, ) -> None: if not is_deletion_blocked(body=body): finalizers = body.get('metadata', {}).get('finalizers', []) patch.setdefault('metadata', {}).setdefault('finalizers', list(finalizers)) patch['metadata']['finalizers'].append(FINALIZER)
def append_finalizers( *, body: bodies.Body, patch: patches.Patch, ) -> None: if not has_finalizers(body=body): finalizers = body.get('metadata', {}).get('finalizers', []) patch.setdefault('metadata', {}).setdefault('finalizers', list(finalizers)) patch['metadata']['finalizers'].append(FINALIZER)
def allow_deletion( *, body: bodies.Body, patch: patches.Patch, finalizer: str, ) -> None: if is_deletion_blocked(body=body, finalizer=finalizer): finalizers = body.get('metadata', {}).get('finalizers', []) patch.setdefault('metadata', {}).setdefault('finalizers', list(finalizers)) if finalizer in patch['metadata']['finalizers']: patch['metadata']['finalizers'].remove(finalizer)
def allow_deletion( *, body: bodies.Body, patch: patches.Patch, ) -> None: if is_deletion_blocked(body=body): finalizers = body.get('metadata', {}).get('finalizers', []) patch.setdefault('metadata', {}).setdefault('finalizers', list(finalizers)) if LEGACY_FINALIZER in patch['metadata']['finalizers']: patch['metadata']['finalizers'].remove(LEGACY_FINALIZER) if FINALIZER in patch['metadata']['finalizers']: patch['metadata']['finalizers'].remove(FINALIZER)
def remove_finalizers( *, body: bodies.Body, patch: patches.Patch, ) -> None: if has_finalizers(body=body): finalizers = body.get('metadata', {}).get('finalizers', []) patch.setdefault('metadata', {}).setdefault('finalizers', list(finalizers)) if LEGACY_FINALIZER in patch['metadata']['finalizers']: patch['metadata']['finalizers'].remove(LEGACY_FINALIZER) if FINALIZER in patch['metadata']['finalizers']: patch['metadata']['finalizers'].remove(FINALIZER)
def store_result( *, patch: patches.Patch, handler: registries.ResourceHandler, result: Any = None, ) -> None: if result is None: pass elif isinstance(result, collections.abc.Mapping): # TODO: merge recursively (patch-merge), do not overwrite the keys if they are present. patch.setdefault('status', {}).setdefault(handler.id, {}).update(result) else: # TODO? Fail if already present? patch.setdefault('status', {})[handler.id] = copy.deepcopy(result)
def store(self, patch: patches.Patch) -> None: for handler_id, handler_state in self.items(): # Nones are not stored by Kubernetes, so we filter them out for comparison. if handler_state.as_dict() != handler_state._origin: # Note: create the 'progress' key only if there are handlers to store, not always. storage = patch.setdefault('status', {}).setdefault('kopf', {}) storage.setdefault('progress', {})[handler_id] = handler_state.as_patch()
def refresh_essence( *, body: bodies.Body, patch: patches.Patch, extra_fields: Optional[Iterable[dicts.FieldSpec]] = None, ) -> None: state = get_essence(body, extra_fields=extra_fields) patch.setdefault('metadata', {}).setdefault( 'annotations', {})[LAST_SEEN_ANNOTATION] = json.dumps(state)
def refresh_essence( *, body: bodies.Body, patch: patches.Patch, extra_fields: Optional[Iterable[dicts.FieldSpec]] = None, ) -> None: old_essence = retrieve_essence(body=body) new_essence = get_essence(body, extra_fields=extra_fields) if new_essence != old_essence: annotations = patch.setdefault('metadata', {}).setdefault('annotations', {}) annotations[LAST_SEEN_ANNOTATION] = json.dumps(new_essence)
def purge(self, patch: patches.Patch, body: bodies.Body) -> None: if 'progress' in body.get('status', {}).get('kopf', {}): patch_storage = patch.setdefault('status', {}).setdefault('kopf', {}) patch_storage['progress'] = None elif 'progress' in patch.get('status', {}).get('kopf', {}): del patch['status']['kopf']['progress'] # Avoid storing the empty status dicts (but do so if they have any content). if 'status' in patch and 'kopf' in patch['status'] and not patch['status']['kopf']: del patch['status']['kopf'] if 'status' in patch and not patch['status']: del patch['status']
def set_start_time( *, body: bodies.Body, patch: patches.Patch, handler: registries.ResourceHandler, ) -> None: progress = patch.setdefault('status', {}).setdefault('kopf', {}).setdefault('progress', {}) progress.setdefault(handler.id, {}).update({ 'started': datetime.datetime.utcnow().isoformat(), })
def set_retry_time( *, body: bodies.Body, patch: patches.Patch, handler: registries.ResourceHandler, delay: Optional[float] = None, ) -> None: retry = get_retry_count(body=body, handler=handler) progress = patch.setdefault('status', {}).setdefault('kopf', {}).setdefault('progress', {}) progress.setdefault(handler.id, {}).update({ 'retries': retry + 1, }) set_awake_time(body=body, patch=patch, handler=handler, delay=delay)
def set_awake_time( *, body: bodies.Body, patch: patches.Patch, handler: registries.ResourceHandler, delay: Optional[float] = None, ) -> None: ts_str: Optional[str] if delay is not None: ts = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay) ts_str = ts.isoformat() else: ts_str = None progress = patch.setdefault('status', {}).setdefault('kopf', {}).setdefault('progress', {}) progress.setdefault(handler.id, {}).update({ 'delayed': ts_str, })
def store_failure( *, body: bodies.Body, patch: patches.Patch, handler: registries.ResourceHandler, exc: BaseException, ) -> None: retry = get_retry_count(body=body, handler=handler) progress = patch.setdefault('status', {}).setdefault('kopf', {}).setdefault('progress', {}) progress.setdefault(handler.id, {}).update({ 'stopped': datetime.datetime.utcnow().isoformat(), 'failure': True, 'retries': retry + 1, 'message': f'{exc}', })
def store_success( *, body: bodies.Body, patch: patches.Patch, handler: registries.ResourceHandler, result: Any = None, ) -> None: retry = get_retry_count(body=body, handler=handler) progress = patch.setdefault('status', {}).setdefault('kopf', {}).setdefault('progress', {}) progress.setdefault(handler.id, {}).update({ 'stopped': datetime.datetime.utcnow().isoformat(), 'success': True, 'retries': retry + 1, 'message': None, }) store_result(patch=patch, handler=handler, result=result)
def purge(self, patch: patches.Patch) -> None: storage = patch.setdefault('status', {}).setdefault('kopf', {}) storage['progress'] = None
def purge_progress( *, body: bodies.Body, patch: patches.Patch, ) -> None: patch.setdefault('status', {}).setdefault('kopf', {})['progress'] = None