def test_from_dict_generic_res(): Mydb = create_namespaced_resource('myapp.com', 'v1', 'Mydb', 'mydbs') db = codecs.from_dict({ 'apiVersion': 'myapp.com/v1', 'kind': 'Mydb', 'metadata': {'name': 'db1'}, 'key': {'a': 'b', 'c': 'd'} }) assert isinstance(db, Mydb) assert db.kind == 'Mydb' assert db.apiVersion == 'myapp.com/v1' assert db.metadata.name == 'db1' assert 'key' in db assert db['key'] == {'a': 'b', 'c': 'd'} # Try with a generic resource with .k8s.io version # https://github.com/gtsystem/lightkube/issues/18 version = "testing.k8s.io/v1" kind = "Testing" group, version_n = version.split("/") Testing = create_namespaced_resource(group=group, version=version_n, kind=kind, plural=f"{kind.lower()}s") testing = codecs.from_dict({ 'apiVersion': version, 'kind': kind, 'metadata': {'name': 'testing1'}, 'key': {'a': 'b', 'c': 'd'} }) assert isinstance(testing, Testing) assert testing.kind == kind assert testing.apiVersion == version assert testing.metadata.name == 'testing1' assert 'key' in testing assert testing['key'] == {'a': 'b', 'c': 'd'}
def test_from_dict_wrong_model(): # apiVersion and kind are required with pytest.raises(LoadResourceError, match=".*key 'apiVersion' missing"): codecs.from_dict({ 'kind': 'ConfigMap', 'metadata': {'name': 'config-name'}, })
def test_from_dict(): config_map = codecs.from_dict({ 'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'config-name', 'labels': {'label1': 'value1'}}, 'data': { 'file1.txt': 'some content here', 'file2.txt': 'some other content' } }) assert isinstance(config_map, ConfigMap) assert config_map.kind == 'ConfigMap' assert config_map.apiVersion == 'v1' assert config_map.metadata.name == 'config-name' assert config_map.metadata.labels['label1'] == 'value1' assert config_map.data['file1.txt'] == 'some content here' assert config_map.data['file2.txt'] == 'some other content' role = codecs.from_dict({ 'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'read-pod'}, 'rules': [{ 'apiGroup': '', 'resources': ['pods'], 'verbs': ['get','watch', 'list'] }] }) assert isinstance(role, Role) assert role.kind == 'Role' assert role.apiVersion == 'rbac.authorization.k8s.io/v1' assert role.metadata.name == 'read-pod' assert role.rules[0].resources == ['pods']
def test_from_dict_not_found(): with pytest.raises(LoadResourceError): codecs.from_dict({'apiVersion': 'myapp2.com/v1', 'kind': 'Mydb'}) with pytest.raises(AttributeError): codecs.from_dict({'apiVersion': 'v1', 'kind': 'Missing'}) with pytest.raises(LoadResourceError): codecs.from_dict({'apiVersion': 'extra/v1', 'kind': 'Missing'}) # Try with an undefined generic resource with .k8s.io version # https://github.com/gtsystem/lightkube/issues/18 with pytest.raises(LoadResourceError): codecs.from_dict({'apiVersion': "undefined.k8s.io/v1", 'kind': 'Missing'})
def get_custom_resource_class_from_filename(self, filename: str): """Returns a class representing a namespaced K8s resource. Args: - filename: name of the manifest file defining the resource """ # TODO: this is a generic context that is used for rendering # the manifest files. We should improve how we do this # and make it more generic. context = { 'namespace': 'namespace', 'app_name': 'name', 'name': 'generic_resource', 'request_headers': 'request_headers', 'response_headers': 'response_headers', 'port': 'port', 'service': 'service', } manifest = self.env.get_template(filename).render(context) manifest_dict = yaml.safe_load(manifest) ns_resource = codecs.from_dict(manifest_dict, client=self.lightkube_client) return type(ns_resource)
def test_with_ingress_auth_relation(harness, subprocess, helpers, mocked_client, mocker): check_call = subprocess.check_call harness.set_leader(True) rel_id = harness.add_relation("ingress-auth", "app") harness.add_relation_unit(rel_id, "app/0") data = { "service": "service-name", "port": 6666, "allowed-request-headers": ['foo'], "allowed-response-headers": ['bar'], } harness.update_relation_data( rel_id, "app", { "_supported_versions": "- v1", "data": yaml.dump(data) }, ) # No need to begin with all initial hooks. This will prevent # us from mocking all event handlers that run initially. harness.begin() harness.charm.on.install.emit() assert check_call.call_args_list == [ Call([ './istioctl', 'install', '-y', '-s', 'profile=minimal', '-s', 'values.global.istioNamespace=None', ]) ] # Reset the mock so any calls due to previous event triggers are not counted, # and then update the ingress relation, triggering the relation_changed event mocked_client.reset_mock() create_global_resource( group="networking.istio.io", version="v1alpha3", kind="EnvoyFilter", plural="envoyfilters", verbs=None, ) expected = [{ 'apiVersion': 'networking.istio.io/v1alpha3', 'kind': 'EnvoyFilter', 'metadata': { 'name': 'authn-filter', 'labels': { 'app.istio-pilot.io/is-workload-entity': 'true' }, }, 'spec': { 'configPatches': [{ 'applyTo': 'HTTP_FILTER', 'match': { 'context': 'GATEWAY', 'listener': { 'filterChain': { 'filter': { 'name': 'envoy.filters.network.http_connection_manager' } } }, }, 'patch': { 'operation': 'INSERT_BEFORE', 'value': { 'name': 'envoy.filters.http.ext_authz', 'typed_config': { '@type': 'type.googleapis.com/envoy.extensions.filters.http.' 'ext_authz.v3.ExtAuthz', 'http_service': { 'server_uri': { 'uri': 'http://service-name.None.svc.cluster.local:6666', # noqa: E501 'cluster': 'outbound|6666||service-name.None.svc.' 'cluster.local', 'timeout': '10s', }, 'authorization_request': { 'allowed_headers': { 'patterns': [{ 'exact': 'foo' }] } }, 'authorization_response': { 'allowed_upstream_headers': { 'patterns': [{ 'exact': 'bar' }] } }, }, }, }, }, }], 'workloadSelector': { 'labels': { 'istio': 'ingressgateway' } }, }, }] # Mocks `in_left_not_right` mocked_ilnr = mocker.patch('resources_handler.in_left_not_right') mocked_ilnr.return_value = [codecs.from_dict(expected[0])] harness.update_relation_data( rel_id, "app", {"some_key": "some_value"}, ) delete_calls = mocked_client.return_value.delete.call_args_list assert helpers.calls_contain_namespace(delete_calls, harness.model.name) actual_res_names = helpers.get_deleted_resource_types(delete_calls) expected_res_names = ['EnvoyFilter'] assert helpers.compare_deleted_resource_names(actual_res_names, expected_res_names) apply_calls = mocked_client.return_value.apply.call_args_list assert helpers.calls_contain_namespace(apply_calls, harness.model.name) apply_args = [] for call in apply_calls: apply_args.append(call[0][0]) assert apply_args == expected assert isinstance(harness.charm.model.unit.status, ActiveStatus)
def test_with_ingress_relation(harness, subprocess, mocked_client, helpers, mocker, mocked_list): check_call = subprocess.check_call harness.set_leader(True) rel_id = harness.add_relation("ingress", "app") harness.add_relation_unit(rel_id, "app/0") data = {"service": "service-name", "port": 6666, "prefix": "/"} harness.update_relation_data( rel_id, "app", { "_supported_versions": "- v1", "data": yaml.dump(data) }, ) # No need to begin with all initial hooks. This will prevent # us from mocking all event handlers that run initially. harness.begin() harness.charm.on.install.emit() assert check_call.call_args_list == [ Call([ './istioctl', 'install', '-y', '-s', 'profile=minimal', '-s', 'values.global.istioNamespace=None', ]) ] # Reset the mock so any calls due to previous event triggers are not counted, # and then update the ingress relation, triggering the relation_changed event mocked_client.reset_mock() # Create VirtualService resource create_global_resource( group="networking.istio.io", version="v1alpha3", kind="VirtualService", plural="virtualservices", verbs=None, ) apply_expected = [ { 'apiVersion': 'networking.istio.io/v1alpha3', 'kind': 'VirtualService', 'metadata': { 'name': 'service-name', 'labels': { 'app.istio-pilot.io/is-workload-entity': 'true' }, }, 'spec': { 'gateways': ['None/istio-gateway'], 'hosts': ['*'], 'http': [{ 'match': [{ 'uri': { 'prefix': '/' } }], 'rewrite': { 'uri': '/' }, 'route': [{ 'destination': { 'host': 'service-name.None.svc.cluster.local', 'port': { 'number': 6666 }, } }], }], }, }, ] # Mocks `in_left_not_right` mocked_ilnr = mocker.patch('resources_handler.in_left_not_right') mocked_ilnr.return_value = [codecs.from_dict(apply_expected[0])] harness.update_relation_data( rel_id, "app", {"some_key": "some_value"}, ) delete_calls = mocked_client.return_value.delete.call_args_list assert helpers.calls_contain_namespace(delete_calls, harness.model.name) actual_res_names = helpers.get_deleted_resource_types(delete_calls) expected_res_names = ['service-name'] assert helpers.compare_deleted_resource_names(actual_res_names, expected_res_names) apply_calls = mocked_client.return_value.apply.call_args_list assert helpers.calls_contain_namespace(apply_calls, harness.model.name) apply_args = [] for call in apply_calls: apply_args.append(call[0][0]) assert apply_args == apply_expected assert isinstance(harness.charm.model.unit.status, ActiveStatus)