From eb69bfdaa226150fa14af5dcfc4826ffdcd9a27f Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Sun, 2 Jun 2019 05:08:20 +0200 Subject: [PATCH 1/3] Parametrize K8s API statuses in tests --- tests/k8s/test_list_objs.py | 7 ++++--- tests/k8s/test_make_list_fn.py | 7 ++++--- tests/k8s/test_read_crd.py | 19 +++++++++++-------- tests/k8s/test_read_obj.py | 19 +++++++++++-------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/tests/k8s/test_list_objs.py b/tests/k8s/test_list_objs.py index e1a8a1a9..4a48ba76 100644 --- a/tests/k8s/test_list_objs.py +++ b/tests/k8s/test_list_objs.py @@ -47,12 +47,13 @@ def test_when_successful_namespaced(client_mock, resource): @pytest.mark.parametrize('namespace', [None, 'ns1'], ids=['without-namespace', 'with-namespace']) -def test_raises_api_error(client_mock, resource, namespace): - error = kubernetes.client.rest.ApiException(status=666) +@pytest.mark.parametrize('status', [400, 401, 403, 500, 666]) +def test_raises_api_error(client_mock, resource, namespace, status): + error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.CustomObjectsApi apicls_mock.return_value.list_cluster_custom_object.side_effect = error apicls_mock.return_value.list_namespaced_custom_object.side_effect = error with pytest.raises(kubernetes.client.rest.ApiException) as e: list_objs(resource=resource, namespace=namespace) - assert e.value.status == 666 + assert e.value.status == status diff --git a/tests/k8s/test_make_list_fn.py b/tests/k8s/test_make_list_fn.py index 32eddc3a..39b6d465 100644 --- a/tests/k8s/test_make_list_fn.py +++ b/tests/k8s/test_make_list_fn.py @@ -63,8 +63,9 @@ def test_when_present_namespaced(client_mock, resource): @pytest.mark.parametrize('namespace', [None, 'ns1'], ids=['without-namespace', 'with-namespace']) -def test_raises_api_error(client_mock, resource, namespace): - error = kubernetes.client.rest.ApiException(status=666) +@pytest.mark.parametrize('status', [400, 401, 403, 404, 500, 666]) +def test_raises_api_error(client_mock, resource, namespace, status): + error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.CustomObjectsApi apicls_mock.return_value.list_cluster_custom_object.side_effect = error apicls_mock.return_value.list_namespaced_custom_object.side_effect = error @@ -72,7 +73,7 @@ def test_raises_api_error(client_mock, resource, namespace): fn = make_list_fn(resource=resource, namespace=namespace) with pytest.raises(kubernetes.client.rest.ApiException) as e: fn(opt1='val1', opt2=123) - assert e.value.status == 666 + assert e.value.status == status @pytest.mark.parametrize('namespace', [None, 'ns1'], ids=['without-namespace', 'with-namespace']) diff --git a/tests/k8s/test_read_crd.py b/tests/k8s/test_read_crd.py index fea09764..1779bcd3 100644 --- a/tests/k8s/test_read_crd.py +++ b/tests/k8s/test_read_crd.py @@ -22,19 +22,21 @@ def test_when_present(client_mock, resource): ] -def test_when_absent_with_no_default(client_mock, resource): - error = kubernetes.client.rest.ApiException(status=404) +@pytest.mark.parametrize('status', [404]) +def test_when_absent_with_no_default(client_mock, resource, status): + error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.ApiextensionsV1beta1Api apicls_mock.return_value.read_custom_resource_definition.side_effect = error with pytest.raises(kubernetes.client.rest.ApiException) as e: read_crd(resource=resource) - assert e.value.status == 404 + assert e.value.status == status @pytest.mark.parametrize('default', [None, object()], ids=['none', 'object']) -def test_when_absent_with_default(client_mock, resource, default): - error = kubernetes.client.rest.ApiException(status=404) +@pytest.mark.parametrize('status', [404]) +def test_when_absent_with_default(client_mock, resource, default, status): + error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.ApiextensionsV1beta1Api apicls_mock.return_value.read_custom_resource_definition.side_effect = error @@ -42,11 +44,12 @@ def test_when_absent_with_default(client_mock, resource, default): assert crd is default -def test_raises_api_error_despite_default(client_mock, resource): - error = kubernetes.client.rest.ApiException(status=666) +@pytest.mark.parametrize('status', [400, 401, 403, 500, 666]) +def test_raises_api_error_despite_default(client_mock, resource, status): + error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.ApiextensionsV1beta1Api apicls_mock.return_value.read_custom_resource_definition.side_effect = error with pytest.raises(kubernetes.client.rest.ApiException) as e: read_crd(resource=resource, default=object()) - assert e.value.status == 666 + assert e.value.status == status diff --git a/tests/k8s/test_read_obj.py b/tests/k8s/test_read_obj.py index fab302be..757d22d8 100644 --- a/tests/k8s/test_read_obj.py +++ b/tests/k8s/test_read_obj.py @@ -49,21 +49,23 @@ def test_when_present_namespaced(client_mock, resource): @pytest.mark.parametrize('namespace', [None, 'ns1'], ids=['without-namespace', 'with-namespace']) -def test_when_absent_with_no_default(client_mock, resource, namespace): - error = kubernetes.client.rest.ApiException(status=404) +@pytest.mark.parametrize('status', [404]) +def test_when_absent_with_no_default(client_mock, resource, namespace, status): + error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.CustomObjectsApi apicls_mock.return_value.get_cluster_custom_object.side_effect = error apicls_mock.return_value.get_namespaced_custom_object.side_effect = error with pytest.raises(kubernetes.client.rest.ApiException) as e: read_obj(resource=resource, namespace=namespace, name='name1') - assert e.value.status == 404 + assert e.value.status == status @pytest.mark.parametrize('default', [None, object()], ids=['none', 'object']) @pytest.mark.parametrize('namespace', [None, 'ns1'], ids=['without-namespace', 'with-namespace']) -def test_when_absent_with_default(client_mock, resource, namespace, default): - error = kubernetes.client.rest.ApiException(status=404) +@pytest.mark.parametrize('status', [404]) +def test_when_absent_with_default(client_mock, resource, namespace, default, status): + error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.CustomObjectsApi apicls_mock.return_value.get_cluster_custom_object.side_effect = error apicls_mock.return_value.get_namespaced_custom_object.side_effect = error @@ -73,12 +75,13 @@ def test_when_absent_with_default(client_mock, resource, namespace, default): @pytest.mark.parametrize('namespace', [None, 'ns1'], ids=['without-namespace', 'with-namespace']) -def test_raises_api_error_despite_default(client_mock, resource, namespace): - error = kubernetes.client.rest.ApiException(status=666) +@pytest.mark.parametrize('status', [400, 401, 403, 500, 666]) +def test_raises_api_error_despite_default(client_mock, resource, namespace, status): + error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.CustomObjectsApi apicls_mock.return_value.get_cluster_custom_object.side_effect = error apicls_mock.return_value.get_namespaced_custom_object.side_effect = error with pytest.raises(kubernetes.client.rest.ApiException) as e: read_obj(resource=resource, namespace=namespace, name='name1', default=object()) - assert e.value.status == 666 + assert e.value.status == status From 1ff55f4f68296c95becebd875674a5381aabe36b Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Sun, 2 Jun 2019 05:09:16 +0200 Subject: [PATCH 2/3] Treat absence of RBAC permissions on CRDs as absence of legacy CRD --- kopf/k8s/fetching.py | 2 +- tests/k8s/test_read_crd.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kopf/k8s/fetching.py b/kopf/k8s/fetching.py index 1e273b44..716a7e05 100644 --- a/kopf/k8s/fetching.py +++ b/kopf/k8s/fetching.py @@ -12,7 +12,7 @@ def read_crd(*, resource, default=_UNSET_): rsp = api.read_custom_resource_definition(name=name) return rsp except kubernetes.client.rest.ApiException as e: - if e.status == 404 and default is not _UNSET_: + if e.status in [404, 403] and default is not _UNSET_: return default raise diff --git a/tests/k8s/test_read_crd.py b/tests/k8s/test_read_crd.py index 1779bcd3..26b1c36d 100644 --- a/tests/k8s/test_read_crd.py +++ b/tests/k8s/test_read_crd.py @@ -22,7 +22,7 @@ def test_when_present(client_mock, resource): ] -@pytest.mark.parametrize('status', [404]) +@pytest.mark.parametrize('status', [403, 404]) def test_when_absent_with_no_default(client_mock, resource, status): error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.ApiextensionsV1beta1Api @@ -34,7 +34,7 @@ def test_when_absent_with_no_default(client_mock, resource, status): @pytest.mark.parametrize('default', [None, object()], ids=['none', 'object']) -@pytest.mark.parametrize('status', [404]) +@pytest.mark.parametrize('status', [403, 404]) def test_when_absent_with_default(client_mock, resource, default, status): error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.ApiextensionsV1beta1Api @@ -44,7 +44,7 @@ def test_when_absent_with_default(client_mock, resource, default, status): assert crd is default -@pytest.mark.parametrize('status', [400, 401, 403, 500, 666]) +@pytest.mark.parametrize('status', [400, 401, 500, 666]) def test_raises_api_error_despite_default(client_mock, resource, status): error = kubernetes.client.rest.ApiException(status=status) apicls_mock = client_mock.ApiextensionsV1beta1Api From 0c5fbff386c14179e323595fb124d0e1825d290f Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Sun, 2 Jun 2019 05:09:49 +0200 Subject: [PATCH 3/3] Add the recommended RBAC for cluster-scoped CRD scanning (optional) --- docs/deployment-rbac.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/deployment-rbac.yaml b/docs/deployment-rbac.yaml index 3ddcebfb..b012b676 100644 --- a/docs/deployment-rbac.yaml +++ b/docs/deployment-rbac.yaml @@ -15,6 +15,9 @@ rules: - apiGroups: [zalando.org] resources: [clusterkopfpeerings] verbs: [list, watch, patch, get] + - apiGroups: [apiextensions.k8s.io] + resources: [customresourcedefinitions] + verbs: [list, get] # Application: read-only access for watching cluster-wide. - apiGroups: [zalando.org]