Beispiel #1
0
def test_on_delete_minimal(cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=Reason.DELETE)

    @kopf.on.delete('group', 'version', 'plural')
    def fn(**_):
        pass

    with pytest.deprecated_call(match=r"cease using the internal registries"):
        handlers = registry.get_cause_handlers(cause)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.DELETE
    assert handlers[0].field is None
    assert handlers[0].timeout is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
def test_on_create_minimal(mocker):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE)

    @kopf.on.create('group', 'version', 'plural')
    def fn(**_):
        pass

    handlers = registry.get_resource_changing_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.CREATE
    assert handlers[0].field is None
    assert handlers[0].errors is None
    assert handlers[0].timeout is None
    assert handlers[0].retries is None
    assert handlers[0].cooldown is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
Beispiel #3
0
def test_resumes_ignored_for_non_initial_causes(reason, deleted,
                                                cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(
        resource=resource,
        reason=reason,
        initial=False,
        body={'metadata': {
            'deletionTimestamp': '...'
        } if deleted else {}})

    @kopf.on.resume('group', 'version', 'plural')
    def fn(**_):
        pass

    with pytest.deprecated_call(
            match=r"use registry.resource_changing_handlers"):
        handlers = registry.get_resource_changing_handlers(cause)
    assert len(handlers) == 0
def test_on_field_minimal(mocker):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    diff = [('op', ('field', 'subfield'), 'old', 'new')]
    cause = mocker.MagicMock(resource=resource,
                             reason=Reason.UPDATE,
                             diff=diff)

    @kopf.on.field('group', 'version', 'plural', 'field.subfield')
    def fn(**_):
        pass

    handlers = registry.get_cause_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].timeout is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
Beispiel #5
0
def test_resumes_selected_for_initial_deletions_when_explicitly_marked(
        reason, cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource,
                          reason=reason,
                          initial=True,
                          body={'metadata': {
                              'deletionTimestamp': '...'
                          }})

    @kopf.on.resume('group', 'version', 'plural', deleted=True)
    def fn(**_):
        pass

    with pytest.deprecated_call(
            match=r"use registry.resource_changing_handlers"):
        handlers = registry.get_resource_changing_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
def test_on_delete_with_cooldown(mocker, optional, cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=Reason.DELETE)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    with pytest.deprecated_call(match=r"use backoff="):

        @kopf.on.delete('group', 'version', 'plural', cooldown=78)
        def fn(**_):
            pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(
        cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].backoff == 78

    with pytest.deprecated_call(match=r"use handler.backoff"):
        assert handlers[0].cooldown == 78
def test_requires_finalizer_multiple_handlers(optional, expected):
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')

    @kopf.on.create('group', 'version', 'plural', registry=registry)
    def fn1(**_):
        pass

    @kopf.on.delete('group',
                    'version',
                    'plural',
                    registry=registry,
                    optional=optional)
    def fn2(**_):
        pass

    with pytest.deprecated_call(match=r"cease using the internal registries"):
        requires_finalizer = registry.requires_finalizer(resource=resource,
                                                         cause=CAUSE)
    assert requires_finalizer == expected
Beispiel #8
0
def test_on_resume_with_all_kwargs(mocker, reason):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource,
                             reason=reason,
                             initial=True,
                             deleted=False)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.resume('group',
                    'version',
                    'plural',
                    id='id',
                    registry=registry,
                    errors=ErrorsMode.PERMANENT,
                    timeout=123,
                    retries=456,
                    backoff=78,
                    deleted=True,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'},
                    when=when)
    def fn(**_):
        pass

    handlers = registry.get_resource_changing_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field is None
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].deleted == True
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
Beispiel #9
0
async def watch_objs(
        *,
        resource: resources.Resource,
        namespace: Optional[str] = None,
        timeout: Optional[float] = None,
        since: Optional[str] = None,
        session: Optional[auth.APISession] = None,  # injected by the decorator
) -> AsyncIterator[bodies.RawEvent]:
    """
    Watch objects of a specific resource type.

    The cluster-scoped call is used in two cases:

    * The resource itself is cluster-scoped, and namespacing makes not sense.
    * The operator serves all namespaces for the namespaced custom resource.

    Otherwise, the namespace-scoped call is used:

    * The resource is namespace-scoped AND operator is namespaced-restricted.
    """
    if session is None:
        raise RuntimeError("API instance is not injected by the decorator.")

    params: Dict[str, str] = {}
    params['watch'] = 'true'
    if since is not None:
        params['resourceVersion'] = since
    if timeout is not None:
        params['timeoutSeconds'] = str(timeout)

    # TODO: also add cluster-wide resource when --namespace is set?
    response = await session.get(
        url=resource.get_url(server=session.server, namespace=namespace, params=params),
        timeout=aiohttp.ClientTimeout(total=None),
    )
    response.raise_for_status()

    async with response:
        async for line in response.content:
            event = cast(bodies.RawEvent, json.loads(line.decode("utf-8")))
            yield event
Beispiel #10
0
def test_on_delete_with_most_kwargs(mocker, cause_factory, optional):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=Reason.DELETE)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.delete('group',
                    'version',
                    'plural',
                    id='id',
                    registry=registry,
                    errors=ErrorsMode.PERMANENT,
                    timeout=123,
                    retries=456,
                    backoff=78,
                    optional=optional,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'},
                    when=when)
    def fn(**_):
        pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(
        cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.DELETE
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
    assert handlers[0].field is None
    assert handlers[0].value is None
    assert handlers[0].old is None
    assert handlers[0].new is None
Beispiel #11
0
def test_on_field_with_all_kwargs(mocker):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    diff = [('op', ('field', 'subfield'), 'old', 'new')]
    cause = mocker.MagicMock(resource=resource,
                             reason=Reason.UPDATE,
                             diff=diff)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.field('group',
                   'version',
                   'plural',
                   'field.subfield',
                   id='id',
                   registry=registry,
                   errors=ErrorsMode.PERMANENT,
                   timeout=123,
                   retries=456,
                   backoff=78,
                   labels={'somelabel': 'somevalue'},
                   annotations={'someanno': 'somevalue'},
                   when=when)
    def fn(**_):
        pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(
        cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].id == 'id/field.subfield'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
def test_on_resume_minimal(mocker, reason):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource, reason=reason, initial=True, deleted=False)

    @kopf.on.resume('group', 'version', 'plural')
    def fn(**_):
        pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field is None
    assert handlers[0].errors is None
    assert handlers[0].timeout is None
    assert handlers[0].retries is None
    assert handlers[0].backoff is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
Beispiel #13
0
def test_on_field_warns_with_positional(cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    old = {'field': {'subfield': 'old'}}
    new = {'field': {'subfield': 'new'}}
    cause = cause_factory(resource=resource,
                          reason=Reason.UPDATE,
                          old=old,
                          new=new,
                          body=new)

    with pytest.deprecated_call(match=r"Positional field name is deprecated"):

        @kopf.on.field('group', 'version', 'plural', 'field.subfield')
        def fn(**_):
            pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(
        cause)
    assert len(handlers) == 1
    assert handlers[0].field == ('field', 'subfield')
Beispiel #14
0
def test_on_create_minimal(mocker):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE)

    @kopf.on.create('group', 'version', 'plural')
    def fn(**_):
        pass

    with pytest.deprecated_call(
            match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"):
        handlers = registry.get_cause_handlers(cause)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.CREATE
    assert handlers[0].field is None
    assert handlers[0].timeout is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
def test_on_update_with_all_kwargs(mocker, cause_factory):

    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=Reason.UPDATE)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.update('group',
                    'version',
                    'plural',
                    id='id',
                    registry=registry,
                    errors=ErrorsMode.PERMANENT,
                    timeout=123,
                    retries=456,
                    backoff=78,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'},
                    when=when)
    def fn(**_):
        pass

    with pytest.deprecated_call(
            match=r"use registry.resource_changing_handlers"):
        handlers = registry.get_resource_changing_handlers(cause)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.UPDATE
    assert handlers[0].field is None
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
Beispiel #16
0
async def list_objs_rv(
        *,
        resource: resources.Resource,
        namespace: Optional[str] = None,
        context: Optional[auth.APIContext] = None,  # injected by the decorator
) -> Tuple[Collection[bodies.Body], str]:
    """
    List the objects of specific resource type.

    The cluster-scoped call is used in two cases:

    * The resource itself is cluster-scoped, and namespacing makes not sense.
    * The operator serves all namespaces for the namespaced custom resource.

    Otherwise, the namespace-scoped call is used:

    * The resource is namespace-scoped AND operator is namespaced-restricted.
    """
    if context is None:
        raise RuntimeError("API instance is not injected by the decorator.")

    is_namespaced = await discovery.is_namespaced(resource=resource, context=context)
    namespace = namespace if is_namespaced else None

    response = await context.session.get(
        url=resource.get_url(server=context.server, namespace=namespace),
    )
    response.raise_for_status()
    rsp = await response.json()

    items: List[bodies.Body] = []
    resource_version = rsp.get('metadata', {}).get('resourceVersion', None)
    for item in rsp['items']:
        if 'kind' in rsp:
            item.setdefault('kind', rsp['kind'][:-4] if rsp['kind'][-4:] == 'List' else rsp['kind'])
        if 'apiVersion' in rsp:
            item.setdefault('apiVersion', rsp['apiVersion'])
        items.append(item)

    return items, resource_version
Beispiel #17
0
def test_on_resume_with_cooldown(mocker, reason):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource,
                             reason=reason,
                             initial=True,
                             deleted=False)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    with pytest.deprecated_call(match=r"use backoff="):

        @kopf.on.resume('group', 'version', 'plural', cooldown=78)
        def fn(**_):
            pass

    handlers = registry.get_resource_changing_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].backoff == 78

    with pytest.deprecated_call(match=r"use handler.backoff"):
        assert handlers[0].cooldown == 78
Beispiel #18
0
def test_on_create_with_all_kwargs(mocker):
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    @kopf.on.create('group', 'version', 'plural',
                    id='id', timeout=123, registry=registry,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'})
    def fn(**_):
        pass

    handlers = registry.get_state_changing_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.CREATE
    assert handlers[0].field is None
    assert handlers[0].id == 'id'
    assert handlers[0].timeout == 123
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
Beispiel #19
0
def test_on_create_minimal(cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=Reason.CREATE)

    @kopf.on.create('group', 'version', 'plural')
    def fn(**_):
        pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(
        cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.CREATE
    assert handlers[0].field is None
    assert handlers[0].errors is None
    assert handlers[0].timeout is None
    assert handlers[0].retries is None
    assert handlers[0].backoff is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
Beispiel #20
0
def test_on_field_with_all_kwargs(mocker, cause_factory):
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')
    old = {'field': {'subfield': 'old'}}
    new = {'field': {'subfield': 'new'}}
    cause = cause_factory(resource=resource,
                          reason=Reason.UPDATE,
                          old=old,
                          new=new,
                          body=new)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.field('group',
                   'version',
                   'plural',
                   field='field.subfield',
                   id='id',
                   timeout=123,
                   registry=registry,
                   labels={'somelabel': 'somevalue'},
                   annotations={'someanno': 'somevalue'},
                   when=when)
    def fn(**_):
        pass

    with pytest.deprecated_call(match=r"cease using the internal registries"):
        handlers = registry.get_cause_handlers(cause)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].id == 'id/field.subfield'
    assert handlers[0].timeout == 123
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
Beispiel #21
0
def test_on_field_minimal(cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    diff = [('op', ('field', 'subfield'), 'old', 'new')]
    cause = cause_factory(resource=resource, reason=Reason.UPDATE, diff=diff)

    @kopf.on.field('group', 'version', 'plural', 'field.subfield')
    def fn(**_):
        pass

    with pytest.deprecated_call(
            match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"):
        handlers = registry.get_cause_handlers(cause)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].timeout is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
Beispiel #22
0
async def read_obj(
        *,
        resource: resources.Resource,
        namespace: Optional[str] = None,
        name: Optional[str] = None,
        default: Union[_T, _UNSET] = _UNSET.token,
        session: Optional[auth.APISession] = None,  # injected by the decorator
) -> Union[bodies.Body, _T]:
    if session is None:
        raise RuntimeError("API instance is not injected by the decorator.")

    try:
        # TODO: also add cluster-wide resource when --namespace is set?
        response = await session.get(url=resource.get_url(
            server=session.server, namespace=namespace, name=name), )
        response.raise_for_status()
        respdata = await response.json()
        return cast(bodies.Body, respdata)

    except aiohttp.ClientResponseError as e:
        if e.status in [403, 404] and not isinstance(default, _UNSET):
            return default
        raise
Beispiel #23
0
def test_on_field_minimal(cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    diff = [('op', ('field', 'subfield'), 'old', 'new')]
    cause = cause_factory(resource=resource, reason=Reason.UPDATE, diff=diff)

    @kopf.on.field('group', 'version', 'plural', 'field.subfield')
    def fn(**_):
        pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(
        cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].errors is None
    assert handlers[0].timeout is None
    assert handlers[0].retries is None
    assert handlers[0].backoff is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
Beispiel #24
0
def test_on_field_with_all_kwargs(mocker):
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')
    diff = [('op', ('field', 'subfield'), 'old', 'new')]
    cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE, diff=diff)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    @kopf.on.field('group', 'version', 'plural', 'field.subfield',
                   id='id', timeout=123, registry=registry,
                   labels={'somelabel': 'somevalue'},
                   annotations={'someanno': 'somevalue'})
    def fn(**_):
        pass

    handlers = registry.get_state_changing_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field ==('field', 'subfield')
    assert handlers[0].id == 'id/field.subfield'
    assert handlers[0].timeout == 123
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
Beispiel #25
0
def test_on_resume_minimal(reason, cause_factory):

    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=reason, initial=True)

    @kopf.on.resume('group', 'version', 'plural')
    def fn(**_):
        pass

    with pytest.deprecated_call(match=r"cease using the internal registries"):
        handlers = registry.get_resource_changing_handlers(cause)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field is None
    assert handlers[0].errors is None
    assert handlers[0].timeout is None
    assert handlers[0].retries is None
    assert handlers[0].backoff is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
Beispiel #26
0
def test_field_with_oldnew(mocker, cause_factory, decorator, causeargs,
                           handlers_prop):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, **causeargs)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    @decorator('group',
               'version',
               'plural',
               registry=registry,
               field='spec.field',
               old='old',
               new='new')
    def fn(**_):
        pass

    handlers_registry = getattr(registry, handlers_prop)
    handlers = handlers_registry[resource].get_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].field == ('spec', 'field')
    assert handlers[0].value is None
    assert handlers[0].old == 'old'
    assert handlers[0].new == 'new'
Beispiel #27
0
def resource():
    """ The resource used in the tests. Usually mocked, so it does not matter. """
    return Resource('zalando.org', 'v1', 'kopfexamples')
Beispiel #28
0
def test_url_of_builtin_resource_item_namespaced():
    resource = Resource('', 'v1', 'plural')
    url = resource.get_url(namespace='ns-a.b', name='name-a.b')
    assert url == '/api/v1/namespaces/ns-a.b/plural/name-a.b'
Beispiel #29
0
async def patch_obj(
        *,
        resource: resources.Resource,
        patch: patches.Patch,
        namespace: Optional[str] = None,
        name: Optional[str] = None,
        body: Optional[bodies.Body] = None,
        context: Optional[auth.APIContext] = None,  # injected by the decorator
) -> bodies.RawBody:
    """
    Patch a resource of specific kind.

    Either the namespace+name should be specified, or the body,
    which is used only to get namespace+name identifiers.

    Unlike the object listing, the namespaced call is always
    used for the namespaced resources, even if the operator serves
    the whole cluster (i.e. is not namespace-restricted).

    Returns the patched body. The patched body can be partial (status-only,
    no-status, or empty) -- depending on whether there were fields in the body
    or in the status to patch; if neither had fields for patching, the result
    is an empty body. The result should only be used to check against the patch:
    if there was nothing to patch, it does not matter if the fields are absent.
    """
    if context is None:
        raise RuntimeError("API instance is not injected by the decorator.")

    if body is not None and (name is not None or namespace is not None):
        raise TypeError("Either body, or name+namespace can be specified. Got both.")

    namespace = body.get('metadata', {}).get('namespace') if body is not None else namespace
    name = body.get('metadata', {}).get('name') if body is not None else name

    is_namespaced = await discovery.is_namespaced(resource=resource, context=context)
    namespace = namespace if is_namespaced else None

    if body is None:
        body = cast(bodies.Body, {'metadata': {'name': name}})
        if namespace is not None:
            body['metadata']['namespace'] = namespace

    as_subresource = await discovery.is_status_subresource(resource=resource, context=context)
    body_patch = dict(patch)  # shallow: for mutation of the top-level keys below.
    status_patch = body_patch.pop('status', None) if as_subresource else None

    # Patch & reconstruct the actual body as reported by the server. The reconstructed body can be
    # partial or empty -- if the body/status patches are empty. This is fine: it is only used
    # to verify that the patched fields are matching the patch. No patch? No mismatch!
    patched_body = bodies.RawBody()
    try:
        if body_patch:
            response = await context.session.patch(
                url=resource.get_url(server=context.server, namespace=namespace, name=name),
                headers={'Content-Type': 'application/merge-patch+json'},
                json=body_patch,
                raise_for_status=True,
            )
            patched_body = await response.json()

        if status_patch:
            response = await context.session.patch(
                url=resource.get_url(server=context.server, namespace=namespace, name=name,
                                     subresource='status' if as_subresource else None),
                headers={'Content-Type': 'application/merge-patch+json'},
                json={'status': status_patch},
                raise_for_status=True,
            )
            patched_body['status'] = await response.json()

    except aiohttp.ClientResponseError as e:
        if e.status == 404:
            pass
        else:
            raise

    return patched_body
Beispiel #30
0
async def watch_objs(
    *,
    settings: configuration.OperatorSettings,
    resource: resources.Resource,
    namespace: Optional[str],
    timeout: Optional[float] = None,
    since: Optional[str] = None,
    context: Optional[auth.APIContext] = None,  # injected by the decorator
    freeze_waiter: aiotasks.Future,
) -> AsyncIterator[bodies.RawInput]:
    """
    Watch objects of a specific resource type.

    The cluster-scoped call is used in two cases:

    * The resource itself is cluster-scoped, and namespacing makes not sense.
    * The operator serves all namespaces for the namespaced custom resource.

    Otherwise, the namespace-scoped call is used:

    * The resource is namespace-scoped AND operator is namespaced-restricted.
    """
    if context is None:
        raise RuntimeError("API instance is not injected by the decorator.")

    is_namespaced = await discovery.is_namespaced(resource=resource,
                                                  context=context)
    namespace = namespace if is_namespaced else None

    params: Dict[str, str] = {}
    params['watch'] = 'true'
    if since is not None:
        params['resourceVersion'] = since
    if timeout is not None:
        params['timeoutSeconds'] = str(timeout)

    # Stream the parsed events from the response until it is closed server-side,
    # or until it is closed client-side by the freeze-waiting future's callbacks.
    try:
        response = await context.session.get(
            url=resource.get_url(server=context.server,
                                 namespace=namespace,
                                 params=params),
            timeout=aiohttp.ClientTimeout(
                total=settings.watching.client_timeout,
                sock_connect=settings.watching.connect_timeout,
            ),
        )
        await errors.check_response(response)

        response_close_callback = lambda _: response.close()
        freeze_waiter.add_done_callback(response_close_callback)
        try:
            async with response:
                async for line in _iter_jsonlines(response.content):
                    raw_input = cast(bodies.RawInput,
                                     json.loads(line.decode("utf-8")))
                    yield raw_input
        finally:
            freeze_waiter.remove_done_callback(response_close_callback)

    except (aiohttp.ClientConnectionError, aiohttp.ClientPayloadError,
            asyncio.TimeoutError):
        pass