Exemple #1
0
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'}
Exemple #2
0
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'},
        })
Exemple #3
0
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']
Exemple #4
0
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'})
Exemple #5
0
    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)
Exemple #6
0
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)
Exemple #7
0
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)