diff --git a/k8s/base.py b/k8s/base.py index ffe8658..db975a4 100644 --- a/k8s/base.py +++ b/k8s/base.py @@ -34,6 +34,7 @@ def __new__(mcs, cls, bases, attrs): bases += (ApiMixIn,) meta = { "url_template": getattr(attr_meta, "url_template", ""), + "list_url": getattr(attr_meta, "list_url", ""), "watch_list_url": getattr(attr_meta, "watch_list_url", ""), "watch_list_url_template": getattr(attr_meta, "watch_list_url_template", ""), "fields": [], @@ -64,7 +65,12 @@ def _build_url(cls, **kwargs): @classmethod def find(cls, name, namespace="default", labels=None): - url = cls._build_url(name="", namespace=namespace) + if namespace is None: + if not cls._meta.list_url: + raise NotImplementedError("Cannot find without namespace, no list_url defined on class {}".format(cls)) + url = cls._meta.list_url + else: + url = cls._build_url(name="", namespace=namespace) if labels: selector = ",".join("{}={}".format(k, v) for k, v in labels.items()) else: @@ -74,7 +80,12 @@ def find(cls, name, namespace="default", labels=None): @classmethod def list(cls, namespace="default"): - url = cls._build_url(name="", namespace=namespace) + if namespace is None: + if not cls._meta.list_url: + raise NotImplementedError("Cannot list without namespace, no list_url defined on class {}".format(cls)) + url = cls._meta.list_url + else: + url = cls._build_url(name="", namespace=namespace) resp = cls._client.get(url) return [cls.from_dict(item) for item in resp.json()[u"items"]] diff --git a/tests/k8s/test_client.py b/tests/k8s/test_client.py index b868a78..fc52340 100644 --- a/tests/k8s/test_client.py +++ b/tests/k8s/test_client.py @@ -106,6 +106,61 @@ def test_watch_list_with_namespace(self, session): "GET", _absolute_url("/watch/explicitly-set/example"), json=None, timeout=None, stream=True ) + def test_list_without_namespace_should_raise_exception_when_list_url_is_not_set_on_metaclass(self, session): + with pytest.raises(NotImplementedError): + WatchListExampleUnsupported.list(namespace=None) + + def test_list_default_namespace(self, session): + WatchListExample.list() + session.request.assert_called_once_with( + "GET", _absolute_url("/apis/namespaces/default/example"), json=None, timeout=10 + ) + + def test_list_explicit_namespace(self, session): + WatchListExample.list(namespace="explicitly-set") + session.request.assert_called_once_with( + "GET", _absolute_url("/apis/namespaces/explicitly-set/example"), json=None, timeout=10 + ) + + def test_list_without_namespace(self, session): + WatchListExample.list(namespace=None) + session.request.assert_called_once_with( + "GET", _absolute_url("/example/list"), json=None, timeout=10 + ) + + @pytest.mark.parametrize("key", SENSITIVE_HEADERS) + def test_redacts_sensitive_headers(self, key): + message = [] + sensitive_value = "super sensitive data that should not be exposed" + Client._add_headers(message, {key: sensitive_value}, "") + text = "".join(message) + assert sensitive_value not in text + + def test_find_without_namespace_should_raise_exception_when_list_url_is_not_set_on_metaclass(self, session): + with pytest.raises(NotImplementedError): + list(WatchListExampleUnsupported.find("foo", namespace=None)) + + def test_find_default_namespace(self, session): + WatchListExample.find("foo") + session.request.assert_called_once_with( + "GET", _absolute_url("/apis/namespaces/default/example"), json=None, timeout=10, + params={"labelSelector": "app=foo"} + ) + + def test_find_explicit_namespace(self, session): + WatchListExample.find("foo", namespace="explicitly-set") + session.request.assert_called_once_with( + "GET", _absolute_url("/apis/namespaces/explicitly-set/example"), json=None, timeout=10, + params={"labelSelector": "app=foo"} + ) + + def test_find_without_namespace(self, session): + WatchListExample.find("foo", namespace=None) + session.request.assert_called_once_with( + "GET", _absolute_url("/example/list"), json=None, timeout=10, + params={"labelSelector": "app=foo"} + ) + @pytest.mark.parametrize("key", SENSITIVE_HEADERS) def test_redacts_sensitive_headers(self, key): message = [] @@ -121,7 +176,8 @@ def _absolute_url(url): class WatchListExample(Model): class Meta: - url_template = "/example" + list_url = "/example/list" + url_template = "/apis/namespaces/{namespace}/example" watch_list_url = "/watch/example" watch_list_url_template = "/watch/{namespace}/example"