async def test_patching_leaves_permanent_webhooks(
        mocker, settings, registry, insights, selector, resource, reason, k8s_mocked):

    @kopf.on.validate(*resource, registry=registry, persistent=True)
    def fn_v(**_): ...

    @kopf.on.mutate(*resource, registry=registry, persistent=True)
    def fn_m(**_): ...

    container = Container()
    mocker.patch.object(container, 'as_changed', return_value=aiter([
        {'url': 'https://hostname/'},
    ]))

    settings.admission.managed = 'xyz'
    await configuration_manager(
        reason=reason,
        selector=selector,
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
    )

    assert k8s_mocked.patch.call_count == 2
    patch = k8s_mocked.patch.call_args_list[-1][1]['payload']
    assert patch['webhooks'][0]['clientConfig']['url'].startswith('https://hostname/')
    assert patch['webhooks'][0]['rules']
    assert patch['webhooks'][0]['rules'][0]['resources'] == ['kopfexamples']
예제 #2
0
async def test_nothing_happens_if_not_managed(mocker, settings, registry,
                                              insights, selector, reason,
                                              k8s_mocked):

    container = Container()
    mocker.patch.object(insights.ready_resources,
                        'wait')  # before the general Event.wait!
    mocker.patch.object(insights.backbone, 'wait_for')
    mocker.patch.object(container, 'as_changed')
    mocker.patch('asyncio.Event.wait')

    settings.admission.managed = None
    await configuration_manager(
        reason=reason,
        selector=selector,
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
    )

    assert not insights.ready_resources.wait.called
    assert not insights.backbone.wait_for.called
    assert not k8s_mocked.create_obj.called
    assert not k8s_mocked.patch_obj.called
    assert not container.as_changed.called
예제 #3
0
async def test_patching_purges_non_permanent_webhooks(mocker, settings,
                                                      registry, insights,
                                                      selector, resource,
                                                      reason, k8s_mocked):
    @kopf.on.validate(*resource, registry=registry, persistent=False)
    def fn_v(**_):
        ...

    @kopf.on.mutate(*resource, registry=registry, persistent=False)
    def fn_m(**_):
        ...

    container = Container()
    mocker.patch.object(container,
                        'as_changed',
                        return_value=aiter([
                            {
                                'url': 'https://hostname/'
                            },
                        ]))

    settings.admission.managed = 'xyz'
    await configuration_manager(
        reason=reason,
        selector=selector,
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
    )

    assert k8s_mocked.patch_obj.call_count == 2
    patch = k8s_mocked.patch_obj.call_args_list[-1][1]['patch']
    assert not patch['webhooks']
예제 #4
0
async def test_contextmanager_decorator(settings, registry, insights):

    aenter_mock = Mock()
    aexit_mock = Mock()
    container = Container()

    async def server(_):
        yield {'url': 'https://hostname/'}

    @contextlib.asynccontextmanager
    async def server_manager():
        aenter_mock()
        try:
            yield server
        finally:
            aexit_mock()

    settings.admission.server = server_manager()  # can be entered only once
    await admission_webhook_server(
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
        webhookfn=webhookfn,
    )

    assert aenter_mock.call_count == 1
    assert aexit_mock.call_count == 1
    assert container.get_nowait() == {'url': 'https://hostname/'}
예제 #5
0
async def test_contextmanager_class(settings, registry, insights):

    aenter_mock = Mock()
    aexit_mock = Mock()
    container = Container()

    class CtxMgrServer:
        async def __aenter__(self) -> "CtxMgrServer":
            aenter_mock()
            return self

        async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
            aexit_mock()

        async def __call__(self, _):
            yield {'url': 'https://hostname/'}

    settings.admission.server = CtxMgrServer()
    await admission_webhook_server(
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
        webhookfn=webhookfn,
    )

    assert aenter_mock.call_count == 1
    assert aexit_mock.call_count == 1
    assert container.get_nowait() == {'url': 'https://hostname/'}
예제 #6
0
async def test_wakes_up_when_preset(event_loop, timer):
    container = Container()
    await container.set(123)

    with timer:
        result = await container.wait()

    assert timer.seconds <= 0.1
    assert result == 123
예제 #7
0
async def test_patching_on_changes(mocker, settings, registry, insights,
                                   selector, resource, reason, k8s_mocked):
    @kopf.on.validate(*resource, registry=registry)
    def fn_v(**_):
        ...

    @kopf.on.mutate(*resource, registry=registry)
    def fn_m(**_):
        ...

    container = Container()
    mocker.patch.object(container,
                        'as_changed',
                        return_value=aiter([
                            {
                                'url': 'https://hostname1/'
                            },
                            {
                                'url': 'https://hostname2/'
                            },
                        ]))

    settings.admission.managed = 'xyz'
    await configuration_manager(
        reason=reason,
        selector=selector,
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
    )

    assert k8s_mocked.patch_obj.call_count == 3
    assert k8s_mocked.patch_obj.call_args_list[0][1][
        'resource'].group == 'admissionregistration.k8s.io'
    assert k8s_mocked.patch_obj.call_args_list[0][1]['name'] == 'xyz'
    assert k8s_mocked.patch_obj.call_args_list[1][1][
        'resource'].group == 'admissionregistration.k8s.io'
    assert k8s_mocked.patch_obj.call_args_list[1][1]['name'] == 'xyz'
    assert k8s_mocked.patch_obj.call_args_list[2][1][
        'resource'].group == 'admissionregistration.k8s.io'
    assert k8s_mocked.patch_obj.call_args_list[2][1]['name'] == 'xyz'

    patch = k8s_mocked.patch_obj.call_args_list[0][1]['patch']
    assert patch['webhooks']
    assert patch['webhooks'][0]['clientConfig']['url'].startswith(
        'https://hostname1/')
    assert patch['webhooks'][0]['rules']
    assert patch['webhooks'][0]['rules'][0]['resources'] == ['kopfexamples']

    patch = k8s_mocked.patch_obj.call_args_list[1][1]['patch']
    assert patch['webhooks']
    assert patch['webhooks'][0]['clientConfig']['url'].startswith(
        'https://hostname2/')
    assert patch['webhooks'][0]['rules']
    assert patch['webhooks'][0]['rules'][0]['resources'] == ['kopfexamples']
예제 #8
0
async def test_does_not_wake_up_when_reset(event_loop, timer):
    container = Container()

    async def reset_it():
        await container.reset()

    event_loop.call_later(0.05, asyncio.create_task, reset_it())

    with pytest.raises(asyncio.TimeoutError):
        await asyncio.wait_for(container.wait(), timeout=0.1)
예제 #9
0
async def test_wakes_up_when_preset(event_loop, timer):
    container = Container()
    await container.set(123)

    with timer, async_timeout.timeout(10) as timeout:
        result = await container.wait()

    assert not timeout.expired
    assert timer.seconds <= 0.1
    assert result == 123
예제 #10
0
async def test_iterates_when_preset(event_loop, timer):
    container = Container()
    await container.set(123)

    values = []
    with timer:
        async for value in container.as_changed():
            values.append(value)
            break

    assert timer.seconds <= 0.1
    assert values == [123]
예제 #11
0
async def test_wakes_up_when_set(event_loop, timer):
    container = Container()

    async def set_it():
        await container.set(123)

    event_loop.call_later(0.1, asyncio.create_task, set_it())

    with timer:
        result = await container.wait()

    assert 0.1 <= timer.seconds <= 0.2
    assert result == 123
예제 #12
0
async def test_does_not_wake_up_when_reset(event_loop, timer):
    container = Container()

    async def reset_it():
        await container.reset()

    event_loop.call_later(0.05, asyncio.create_task, reset_it())

    with pytest.raises(asyncio.TimeoutError):
        with async_timeout.timeout(0.1) as timeout:
            await container.wait()

    assert timeout.expired
예제 #13
0
async def test_configures_client_configs(settings, registry, insights):
    async def server(_):
        yield {'url': 'https://hostname/'}

    container = Container()
    settings.admission.server = server
    await admission_webhook_server(
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
        webhookfn=webhookfn,
    )

    assert container.get_nowait() == {'url': 'https://hostname/'}
예제 #14
0
async def test_iterates_when_set(event_loop, timer):
    container = Container()

    async def set_it(v):
        await container.set(v)

    event_loop.call_later(0.1, asyncio.create_task, set_it(123))
    event_loop.call_later(0.2, asyncio.create_task, set_it(234))

    values = []
    with timer:
        async for value in container.as_changed():
            values.append(value)
            if value == 234:
                break

    assert 0.2 <= timer.seconds <= 0.3
    assert values == [123, 234]
async def test_creation_is_attempted(
        mocker, settings, registry, insights, selector, resource, reason, k8s_mocked):

    container = Container()
    mocker.patch.object(container, 'as_changed', return_value=aiter([]))

    settings.admission.managed = 'xyz'
    await configuration_manager(
        reason=reason,
        selector=selector,
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
    )

    assert k8s_mocked.post.call_count == 1
    assert k8s_mocked.post.call_args_list[0][1]['url'].startswith('/apis/admissionregistration.k8s.io/')
    assert k8s_mocked.post.call_args_list[0][1]['payload']['metadata']['name'] == 'xyz'
예제 #16
0
async def test_creation_is_attempted(mocker, settings, registry, insights,
                                     selector, resource, reason, k8s_mocked):

    container = Container()
    mocker.patch.object(container, 'as_changed', return_value=aiter([]))

    settings.admission.managed = 'xyz'
    await configuration_manager(
        reason=reason,
        selector=selector,
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
    )

    assert k8s_mocked.create_obj.call_count == 1
    assert k8s_mocked.create_obj.call_args_list[0][1][
        'resource'].group == 'admissionregistration.k8s.io'
    assert k8s_mocked.create_obj.call_args_list[0][1]['name'] == 'xyz'
async def test_creation_escalates_on_errors(
        mocker, settings, registry, insights, selector, resource, reason, k8s_mocked, error):

    container = Container()
    mocker.patch.object(container, 'as_changed', return_value=aiter([]))
    k8s_mocked.post.side_effect = error({}, status=400)

    with pytest.raises(error):
        settings.admission.managed = 'xyz'
        await configuration_manager(
            reason=reason,
            selector=selector,
            registry=registry,
            settings=settings,
            insights=insights,
            container=container,
        )

    assert k8s_mocked.post.call_count == 1
    assert k8s_mocked.post.call_args_list[0][1]['url'].startswith('/apis/admissionregistration.k8s.io/')
    assert k8s_mocked.post.call_args_list[0][1]['payload']['metadata']['name'] == 'xyz'
예제 #18
0
async def test_creation_ignores_if_exists_already(mocker, settings, registry,
                                                  insights, selector, resource,
                                                  reason, k8s_mocked):

    container = Container()
    mocker.patch.object(container, 'as_changed', return_value=aiter([]))
    k8s_mocked.create_obj.side_effect = APIConflictError({}, status=409)

    settings.admission.managed = 'xyz'
    await configuration_manager(
        reason=reason,
        selector=selector,
        registry=registry,
        settings=settings,
        insights=insights,
        container=container,
    )

    assert k8s_mocked.create_obj.call_count == 1
    assert k8s_mocked.create_obj.call_args_list[0][1][
        'resource'].group == 'admissionregistration.k8s.io'
    assert k8s_mocked.create_obj.call_args_list[0][1]['name'] == 'xyz'
예제 #19
0
async def test_requires_webserver_if_webhooks_are_defined(
        settings, registry, insights, resource):
    @kopf.on.validate(*resource, registry=registry)
    def fn_v(**_):
        ...

    @kopf.on.mutate(*resource, registry=registry)
    def fn_m(**_):
        ...

    container = Container()
    with pytest.raises(Exception) as err:
        settings.admission.server = None
        await admission_webhook_server(
            registry=registry,
            settings=settings,
            insights=insights,
            container=container,
            webhookfn=webhookfn,
        )

    assert "Admission handlers exist, but no admission server/tunnel" in str(
        err.value)
예제 #20
0
async def test_empty_by_default():
    container = Container()
    with pytest.raises(asyncio.TimeoutError):
        with async_timeout.timeout(0.1) as timeout:
            await container.wait()
    assert timeout.expired
예제 #21
0
async def test_empty_by_default():
    container = Container()
    with pytest.raises(asyncio.TimeoutError):
        await asyncio.wait_for(container.wait(), timeout=0.1)