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']
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
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']
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/'}
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/'}
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
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']
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)
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
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]
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
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
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/'}
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'
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'
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'
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)
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
async def test_empty_by_default(): container = Container() with pytest.raises(asyncio.TimeoutError): await asyncio.wait_for(container.wait(), timeout=0.1)