From ade3f4ef965caf49584bf0714220fdd851080ddd Mon Sep 17 00:00:00 2001 From: Erick Daniszewski Date: Wed, 30 Dec 2020 12:03:47 -0500 Subject: [PATCH] fmt: automated source code formatting --- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yaml | 2 +- .gitignore | 2 +- docs/Makefile | 2 +- docs/conf.py | 66 ++-- docs/markers.rst | 1 - docs/module_ref.rst | 2 +- docs/reference.rst | 2 +- docs/writing_tests.rst | 15 +- examples/configs/ingress.yaml | 2 +- examples/configs/serviceaccount.yaml | 2 +- examples/get-all-pods-via-fixture/README.md | 4 +- examples/get-all-pods-via-fixture/conftest.py | 1 - .../manifests/deployment-frontend.yaml | 2 +- .../get-all-pods-via-fixture/test_examples.py | 46 +-- examples/test_clusterrolebinding.py | 4 +- examples/test_configmap.py | 4 +- examples/test_deployment.py | 30 +- examples/test_ingress.py | 4 +- examples/test_service.py | 10 +- examples/test_serviceaccount.py | 4 +- kubetest/__init__.py | 14 +- kubetest/client.py | 310 +++++++++--------- kubetest/condition.py | 4 +- kubetest/errors.py | 2 - kubetest/manager.py | 83 ++--- kubetest/manifest.py | 37 ++- kubetest/markers.py | 235 +++++++------ kubetest/objects/api_object.py | 64 ++-- kubetest/objects/clusterrolebinding.py | 23 +- kubetest/objects/configmap.py | 12 +- kubetest/objects/container.py | 6 +- kubetest/objects/daemonset.py | 24 +- kubetest/objects/deployment.py | 24 +- kubetest/objects/endpoints.py | 17 +- kubetest/objects/event.py | 6 +- kubetest/objects/ingress.py | 27 +- kubetest/objects/namespace.py | 22 +- kubetest/objects/networkpolicy.py | 16 +- kubetest/objects/node.py | 8 +- kubetest/objects/persistentvolumeclaim.py | 14 +- kubetest/objects/pod.py | 117 +++---- kubetest/objects/replicaset.py | 22 +- kubetest/objects/rolebinding.py | 20 +- kubetest/objects/secret.py | 12 +- kubetest/objects/service.py | 26 +- kubetest/objects/serviceaccount.py | 16 +- kubetest/objects/statefulset.py | 24 +- kubetest/plugin.py | 168 +++++----- kubetest/response.py | 4 +- kubetest/utils.py | 40 +-- regression/130/test.kubeconfig | 2 +- regression/130/test_130.py | 4 +- regression/154/README.md | 2 +- regression/154/test_154.py | 3 +- regression/156-bl/test_156.py | 4 +- regression/156/test_156.py | 5 +- regression/88/deployment.yaml | 2 +- regression/88/test_88.py | 6 +- regression/90/README.md | 2 +- regression/90/test_90.py | 8 +- setup.py | 57 ++-- tests/conftest.py | 230 +++++-------- tests/data/invalid.yaml | 2 +- tests/data/manifests/deployment.yaml | 2 +- tests/data/manifests/no-ext | 2 +- tests/data/manifests/not-a-manifest.txt | 2 +- tests/data/manifests/service.yml | 2 +- tests/data/simple-daemonset.yaml | 2 +- tests/data/simple-deployment.yaml | 2 +- tests/data/simple-ingress.yaml | 2 +- tests/data/simple-replicaset.yaml | 2 +- tests/data/simple-service.yaml | 2 +- tests/data/simple-serviceaccount.yaml | 2 +- tests/data/simple-statefulset.yaml | 2 +- tests/objects/test_api_object.py | 26 +- tests/test_conditions.py | 78 ++--- tests/test_manager.py | 22 +- tests/test_manifest.py | 204 +++++------- tests/test_utils.py | 43 +-- 80 files changed, 1123 insertions(+), 1203 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 9560abf..0ff4377 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -18,4 +18,4 @@ jobs: python -m pip install -U pip setuptools wheel python -m pip install -U tox tox-gh-actions - name: Lint - run: tox -e lint \ No newline at end of file + run: tox -e lint diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a7205ee..50cdb22 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -22,4 +22,4 @@ jobs: python -m pip install -U pip setuptools wheel python -m pip install -U tox tox-gh-actions - name: Run tests - run: tox \ No newline at end of file + run: tox diff --git a/.gitignore b/.gitignore index 3762d92..21552aa 100644 --- a/.gitignore +++ b/.gitignore @@ -107,4 +107,4 @@ venv.bak/ .idea # Local configuration files -.local \ No newline at end of file +.local diff --git a/docs/Makefile b/docs/Makefile index 298ea9e..5128596 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -16,4 +16,4 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py index c826021..0127a48 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,25 +8,26 @@ # -- Path setup -------------------------------------------------------------- +import datetime +import os +import sys + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # from kubetest import __version__ -import os -import sys -import datetime -sys.path.insert(0, os.path.abspath('..')) -sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, os.path.abspath(".")) # -- Project information ----------------------------------------------------- -project = 'kubetest' +project = "kubetest" year = datetime.datetime.now().year -copyright = '2018, Vapor IO' -author = 'Vapor IO' +copyright = "2018, Vapor IO" +author = "Vapor IO" # The short X.Y version version = __version__ @@ -44,22 +45,22 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -71,10 +72,10 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'friendly' +pygments_style = "friendly" # -- Options for HTML output ------------------------------------------------- @@ -82,7 +83,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -93,7 +94,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -109,7 +110,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'kubetestdoc' +htmlhelp_basename = "kubetestdoc" # -- Options for LaTeX output ------------------------------------------------ @@ -118,15 +119,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -136,8 +134,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'kubetest.tex', 'kubetest Documentation', - 'Vapor IO', 'manual'), + (master_doc, "kubetest.tex", "kubetest Documentation", "Vapor IO", "manual"), ] @@ -145,10 +142,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'kubetest', 'kubetest Documentation', - [author], 1) -] +man_pages = [(master_doc, "kubetest", "kubetest Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -157,9 +151,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'kubetest', 'kubetest Documentation', - author, 'kubetest', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "kubetest", + "kubetest Documentation", + author, + "kubetest", + "One line description of project.", + "Miscellaneous", + ), ] @@ -178,13 +178,13 @@ # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- # Autodoc -autodoc_member_order = 'bysource' +autodoc_member_order = "bysource" # Configure the Napoleon extension for Google style docstrings napoleon_google_docstring = True diff --git a/docs/markers.rst b/docs/markers.rst index 4c13fb7..6624878 100644 --- a/docs/markers.rst +++ b/docs/markers.rst @@ -352,4 +352,3 @@ Examples @pytest.mark.namespace(create=True, name='specific-name') def test_something(kube): ... - diff --git a/docs/module_ref.rst b/docs/module_ref.rst index fab86b6..707e375 100644 --- a/docs/module_ref.rst +++ b/docs/module_ref.rst @@ -7,4 +7,4 @@ Below is a complete module reference .. toctree:: :maxdepth: 4 - source/modules \ No newline at end of file + source/modules diff --git a/docs/reference.rst b/docs/reference.rst index b62ec94..eea406c 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -166,4 +166,4 @@ Helpers .. autofunction:: kubetest.condition.check_all -.. autofunction:: kubetest.condition.check_and_sort \ No newline at end of file +.. autofunction:: kubetest.condition.check_and_sort diff --git a/docs/writing_tests.rst b/docs/writing_tests.rst index 1f42557..a043d75 100644 --- a/docs/writing_tests.rst +++ b/docs/writing_tests.rst @@ -11,18 +11,18 @@ you can access it from wherever the tests are being run. Cluster Configuration --------------------- -By default, kubetest will look for a config file at ``~/.kube/config`` and the -current context -- this is the same behavior that ``kubectl`` utilizes for the -resolving cluster config. Generally, if you can reach your cluster via. +By default, kubetest will look for a config file at ``~/.kube/config`` and the +current context -- this is the same behavior that ``kubectl`` utilizes for the +resolving cluster config. Generally, if you can reach your cluster via. ``kubectl``, you should be able to use it with kubetest. -If you wish to specify a different config file and/or context, you can pass it +If you wish to specify a different config file and/or context, you can pass it in via the ``--kube-config`` and ``--kube-context`` flags. See :ref:`command_line_usage` for more details. You can also write a ``kubeconfig`` fixture which provides the path to the -config file and/or a ``kubecontext`` fixture which provides the name of the -context to be used. This may be useful in case your cluster is generated as +config file and/or a ``kubecontext`` fixture which provides the name of the +context to be used. This may be useful in case your cluster is generated as part of the tests or you wish to use specific contexts in different parts of the suite. @@ -39,7 +39,7 @@ the suite. # at somepath subprocess.check_call(['terraform', 'apply']) return 'somepath/kubeconfig' - + @pytest.fixture def kubecontext() -> Optional[str]: @@ -247,4 +247,3 @@ Wait until a Pod's containers have all started. pods = kube.get_pods() for pod in pods.values(): pod.wait_until_containers_start(timeout=60) - diff --git a/examples/configs/ingress.yaml b/examples/configs/ingress.yaml index de2217d..ea5f2a1 100644 --- a/examples/configs/ingress.yaml +++ b/examples/configs/ingress.yaml @@ -10,4 +10,4 @@ spec: - path: / backend: serviceName: my-service - servicePort: 80 \ No newline at end of file + servicePort: 80 diff --git a/examples/configs/serviceaccount.yaml b/examples/configs/serviceaccount.yaml index ecc7005..55aa886 100644 --- a/examples/configs/serviceaccount.yaml +++ b/examples/configs/serviceaccount.yaml @@ -3,4 +3,4 @@ apiVersion: v1 kind: ServiceAccount metadata: name: build-robot -automountServiceAccountToken: false \ No newline at end of file +automountServiceAccountToken: false diff --git a/examples/get-all-pods-via-fixture/README.md b/examples/get-all-pods-via-fixture/README.md index d92f4de..925956e 100644 --- a/examples/get-all-pods-via-fixture/README.md +++ b/examples/get-all-pods-via-fixture/README.md @@ -27,7 +27,7 @@ kubetest config file: ./config kubetest context: current context rootdir: /Users/edaniszewski/dev/vaporio/kubetest plugins: asyncio-0.10.0, kubetest-0.1.0 -collected 5 items +collected 5 items test_examples.py ..... [100%] @@ -36,4 +36,4 @@ test_examples.py ..... ``` > Note: The `--suppress-insecure-request` seen above is to suppress warnings generated -> when running tests on a cluster without SSL enabled (e.g. a local cluster). \ No newline at end of file +> when running tests on a cluster without SSL enabled (e.g. a local cluster). diff --git a/examples/get-all-pods-via-fixture/conftest.py b/examples/get-all-pods-via-fixture/conftest.py index 0af19a9..e89306b 100644 --- a/examples/get-all-pods-via-fixture/conftest.py +++ b/examples/get-all-pods-via-fixture/conftest.py @@ -1,4 +1,3 @@ - import pytest diff --git a/examples/get-all-pods-via-fixture/manifests/deployment-frontend.yaml b/examples/get-all-pods-via-fixture/manifests/deployment-frontend.yaml index 710be07..9e2e083 100644 --- a/examples/get-all-pods-via-fixture/manifests/deployment-frontend.yaml +++ b/examples/get-all-pods-via-fixture/manifests/deployment-frontend.yaml @@ -31,4 +31,4 @@ spec: # line below: # value: env ports: - - containerPort: 80 \ No newline at end of file + - containerPort: 80 diff --git a/examples/get-all-pods-via-fixture/test_examples.py b/examples/get-all-pods-via-fixture/test_examples.py index aef1c0b..899ec6b 100644 --- a/examples/get-all-pods-via-fixture/test_examples.py +++ b/examples/get-all-pods-via-fixture/test_examples.py @@ -1,11 +1,12 @@ +import os +import time import pytest -import time -import os -@pytest.mark.applymanifest(os.path.join(os.path.dirname(__file__), - 'manifests/deployment-redis.yaml')) +@pytest.mark.applymanifest( + os.path.join(os.path.dirname(__file__), "manifests/deployment-redis.yaml") +) def test_pods_from_deployment_loaded_from_marker(kube): """Get the Pods for a Deployment which is loaded via the kubetest 'applymanifest' marker. @@ -26,8 +27,8 @@ def test_pods_from_deployment_loaded_from_marker(kube): # Get the redis-master deployment. The `deployments` response is a # dict where the key is the name for the object (e.g. as defined in # the manifest), and the value is the corresponding Deployment object. - assert 'redis-master' in deployments - redis_master = deployments['redis-master'] + assert "redis-master" in deployments + redis_master = deployments["redis-master"] # Get the pods for the deployment. Since the deployment is defined # as having one replica, we expect to only get back one pod. @@ -44,8 +45,9 @@ def test_pods_from_deployment_loaded_in_test_case(kube): use that to get the pods for the deployment. """ - deployment = kube.load_deployment(os.path.join( - os.path.dirname(__file__), 'manifests/deployment-redis.yaml')) + deployment = kube.load_deployment( + os.path.join(os.path.dirname(__file__), "manifests/deployment-redis.yaml") + ) deployment.create() deployment.wait_until_ready(timeout=30) @@ -56,10 +58,13 @@ def test_pods_from_deployment_loaded_in_test_case(kube): assert len(pods) == 1 -@pytest.mark.applymanifests('manifests', files=[ - 'deployment-redis.yaml', - 'deployment-frontend.yaml', -]) +@pytest.mark.applymanifests( + "manifests", + files=[ + "deployment-redis.yaml", + "deployment-frontend.yaml", + ], +) def test_all_pods_for_test_namespace(kube): """Get all of the Pods within the test namespace. @@ -93,14 +98,17 @@ def test_all_pods_for_other_namespace(kube): # Get the pods belonging to the kube-system namespace. Since this # is running on a Kubernetes cluster, there should be some pods in # this namespace. - pods = kube.get_pods(namespace='kube-system') + pods = kube.get_pods(namespace="kube-system") assert len(pods) > 0 -@pytest.mark.applymanifests('manifests', files=[ - 'deployment-redis.yaml', - 'deployment-frontend.yaml', -]) +@pytest.mark.applymanifests( + "manifests", + files=[ + "deployment-redis.yaml", + "deployment-frontend.yaml", + ], +) def test_all_pods_via_custom_fixture(kube, custom_pods): """Get all of the Pods with a custom fixture. @@ -127,9 +135,9 @@ def test_all_pods_via_custom_fixture(kube, custom_pods): count_master = 0 for pod_name in custom_pods: - if 'frontend' in pod_name: + if "frontend" in pod_name: count_frontend += 1 - if 'redis-master' in pod_name: + if "redis-master" in pod_name: count_master += 1 assert count_frontend == 3 diff --git a/examples/test_clusterrolebinding.py b/examples/test_clusterrolebinding.py index c57f353..74e1763 100644 --- a/examples/test_clusterrolebinding.py +++ b/examples/test_clusterrolebinding.py @@ -7,8 +7,8 @@ def test_cluster_role_binding(kube): f = os.path.join( os.path.dirname(os.path.realpath(__file__)), - 'configs', - 'clusterrolebinding.yaml' + "configs", + "clusterrolebinding.yaml", ) cm = kube.load_clusterrolebinding(f) diff --git a/examples/test_configmap.py b/examples/test_configmap.py index 5d14fda..d3e3e4c 100644 --- a/examples/test_configmap.py +++ b/examples/test_configmap.py @@ -6,9 +6,7 @@ def test_configmap(kube): f = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'configs', - 'configmap.yaml' + os.path.dirname(os.path.realpath(__file__)), "configs", "configmap.yaml" ) cm = kube.load_configmap(f) diff --git a/examples/test_deployment.py b/examples/test_deployment.py index 4c08de7..dfd58fb 100644 --- a/examples/test_deployment.py +++ b/examples/test_deployment.py @@ -6,9 +6,7 @@ def test_deployment(kube): f = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'configs', - 'deployment.yaml' + os.path.dirname(os.path.realpath(__file__)), "configs", "deployment.yaml" ) d = kube.load_deployment(f) @@ -28,36 +26,36 @@ def test_deployment(kube): # is an http echo server, so we should get back data # about the request. r = p.http_proxy_get( - '/test/get', - query_params={'abc': 123}, + "/test/get", + query_params={"abc": 123}, ) get_data = r.json() - assert get_data['path'] == '/test/get' - assert get_data['method'] == 'GET' - assert get_data['body'] == '' + assert get_data["path"] == "/test/get" + assert get_data["method"] == "GET" + assert get_data["body"] == "" # fixme (etd): I would expect this to be {'abc': 123}, matching # the input data types (e.g. value not a string). Need to determine # where this issue lies.. # This may be an issue with the image reflecting the request back. - assert get_data['query'] == {'abc': '123'} + assert get_data["query"] == {"abc": "123"} # Issue an HTTP POST against the Pod. The deployment # is an http echo server, so we should get back data # about the request. r = p.http_proxy_post( - '/test/post', - query_params={'abc': 123}, - data='foobar', + "/test/post", + query_params={"abc": 123}, + data="foobar", ) post_data = r.json() - assert post_data['path'] == '/test/post' - assert post_data['method'] == 'POST' - assert post_data['body'] == '"foobar"' + assert post_data["path"] == "/test/post" + assert post_data["method"] == "POST" + assert post_data["body"] == '"foobar"' # fixme (etd): I would expect this to be {'abc': 123}, matching # the input data types (e.g. value not a string). Need to determine # where this issue lies.. # This may be an issue with the image reflecting the request back. - assert post_data['query'] == {'abc': '123'} + assert post_data["query"] == {"abc": "123"} containers = p.get_containers() c = containers[0] diff --git a/examples/test_ingress.py b/examples/test_ingress.py index ede0617..82355fa 100644 --- a/examples/test_ingress.py +++ b/examples/test_ingress.py @@ -6,9 +6,7 @@ def test_ingress(kube): f = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'configs', - 'ingress.yaml' + os.path.dirname(os.path.realpath(__file__)), "configs", "ingress.yaml" ) ing = kube.load_ingress(f) diff --git a/examples/test_service.py b/examples/test_service.py index cd16ed3..c0ee51a 100644 --- a/examples/test_service.py +++ b/examples/test_service.py @@ -1,21 +1,17 @@ """An example of using kubetest to manage a service.""" -import os import ast +import os def test_service(kube): f = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'configs', - 'service.yaml' + os.path.dirname(os.path.realpath(__file__)), "configs", "service.yaml" ) d = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'configs', - 'deployment.yaml' + os.path.dirname(os.path.realpath(__file__)), "configs", "deployment.yaml" ) svc = kube.load_service(f) diff --git a/examples/test_serviceaccount.py b/examples/test_serviceaccount.py index d446cbd..28323f5 100644 --- a/examples/test_serviceaccount.py +++ b/examples/test_serviceaccount.py @@ -7,9 +7,7 @@ def test_serviceaccount(kube): f = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'configs', - 'serviceaccount.yaml' + os.path.dirname(os.path.realpath(__file__)), "configs", "serviceaccount.yaml" ) sa = kube.load_serviceaccount(f) diff --git a/kubetest/__init__.py b/kubetest/__init__.py index 3042583..46acc3b 100644 --- a/kubetest/__init__.py +++ b/kubetest/__init__.py @@ -1,9 +1,9 @@ """kubetest -- a Kubernetes integration test framework in Python.""" -__title__ = 'kubetest' -__version__ = '0.9.3' -__description__ = 'A Kubernetes integration test framework in Python.' -__author__ = 'Vapor IO' -__author_email__ = 'vapor@vapor.io' -__url__ = 'https://github.com/vapor-ware/kubetest' -__license__ = 'GNU General Public License v3.0' +__title__ = "kubetest" +__version__ = "0.9.3" +__description__ = "A Kubernetes integration test framework in Python." +__author__ = "Vapor IO" +__author_email__ = "vapor@vapor.io" +__url__ = "https://github.com/vapor-ware/kubetest" +__license__ = "GNU General Public License v3.0" diff --git a/kubetest/client.py b/kubetest/client.py index f7a7629..0704efc 100644 --- a/kubetest/client.py +++ b/kubetest/client.py @@ -13,7 +13,7 @@ from kubetest import objects, utils from kubetest.condition import Condition, Policy, check_and_sort -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class TestClient: @@ -47,7 +47,9 @@ def create(self, obj: objects.ApiObject) -> None: obj.create() - def delete(self, obj: objects.ApiObject, options: client.V1DeleteOptions = None) -> None: + def delete( + self, obj: objects.ApiObject, options: client.V1DeleteOptions = None + ) -> None: """Delete the provided ApiObject from the Kubernetes cluster. If the object does not already have a namespace assigned to it, the client's @@ -75,8 +77,8 @@ def refresh(obj: objects.ApiObject) -> None: @staticmethod def load_clusterrolebinding( - path: str, - name: Optional[str] = None, + path: str, + name: Optional[str] = None, ) -> objects.ClusterRoleBinding: """Load a manifest YAML into a ClusterRoleBinding object. @@ -92,14 +94,14 @@ def load_clusterrolebinding( Returns: The ClusterRoleBinding for the specified manifest. """ - log.info(f'loading clusterrolebinding from path: {path}') + log.info(f"loading clusterrolebinding from path: {path}") return objects.ClusterRoleBinding.load(path, name=name) def load_configmap( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.ConfigMap: """Load a manifest YAML into a ConfigMap object. @@ -121,17 +123,17 @@ def load_configmap( Returns: The ConfigMap for the specified manifest. """ - log.info(f'loading configmap from path: {path}') + log.info(f"loading configmap from path: {path}") configmap = objects.ConfigMap.load(path, name=name) if set_namespace: configmap.namespace = self.namespace return configmap def load_daemonset( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.DaemonSet: """Load a manifest YAML into a DaemonSet object. @@ -152,17 +154,17 @@ def load_daemonset( Returns: The DaemonSet for the specified manifest. """ - log.info(f'loading daemonset from path: {path}') + log.info(f"loading daemonset from path: {path}") daemonset = objects.DaemonSet.load(path, name=name) if set_namespace: daemonset.namespace = self.namespace return daemonset def load_deployment( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.Deployment: """Load a manifest YAML into a Deployment object. @@ -184,17 +186,17 @@ def load_deployment( Returns: The Deployment for the specified manifest. """ - log.info(f'loading deployment from path: {path}') + log.info(f"loading deployment from path: {path}") deployment = objects.Deployment.load(path, name=name) if set_namespace: deployment.namespace = self.namespace return deployment def load_pod( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.Pod: """Load a manifest YAML into a Pod object. @@ -215,17 +217,17 @@ def load_pod( Returns: The Pod for the specified manifest. """ - log.info(f'loading pod from path: {path}') + log.info(f"loading pod from path: {path}") pod = objects.Pod.load(path, name=name) if set_namespace: pod.namespace = self.namespace return pod def load_rolebinding( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.RoleBinding: """Load a manifest YAML into a RoleBinding object. @@ -247,17 +249,17 @@ def load_rolebinding( Returns: The RoleBinding for the specified manifest. """ - log.info(f'loading rolebinding from path: {path}') + log.info(f"loading rolebinding from path: {path}") rolebinding = objects.RoleBinding.load(path, name=name) if set_namespace: rolebinding.namespace = self.namespace return rolebinding def load_secret( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.Secret: """Load a manifest YAML into a Secret object. @@ -279,17 +281,17 @@ def load_secret( Returns: The Secret for the specified manifest. """ - log.info(f'loading secret from path: {path}') + log.info(f"loading secret from path: {path}") secret = objects.Secret.load(path, name=name) if set_namespace: secret.namespace = self.namespace return secret def load_service( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.Service: """Load a manifest YAML into a Service object. @@ -311,17 +313,17 @@ def load_service( Returns: The Service for the specified manifest. """ - log.info(f'loading service from path: {path}') + log.info(f"loading service from path: {path}") service = objects.Service.load(path, name=name) if set_namespace: service.namespace = self.namespace return service def load_persistentvolumeclaim( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.PersistentVolumeClaim: """Load a manifest YAML into a PersistentVolumeClaim object. @@ -344,17 +346,17 @@ def load_persistentvolumeclaim( objects.PersistentVolumeClaim: The PersistentVolumeClaim for the specified manifest. """ - log.info('loading persistentvolumeclaim from path: %s', path) + log.info("loading persistentvolumeclaim from path: %s", path) persistentvolumeclaim = objects.PersistentVolumeClaim.load(path, name=name) if set_namespace: persistentvolumeclaim.namespace = self.namespace return persistentvolumeclaim def load_ingress( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.Ingress: """Load a manifest YAML into a Ingress object. @@ -376,16 +378,17 @@ def load_ingress( Returns: objects.Ingress: The ingress for the specified manifest. """ - log.info('loading ingress from path: %s', path) + log.info("loading ingress from path: %s", path) ingress = objects.Ingress.load(path, name=name) if set_namespace: ingress.namespace = self.namespace return ingress def load_replicaset( - self, path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.ReplicaSet: """Load a manifest YAML into a ReplicaSet object. @@ -407,16 +410,17 @@ def load_replicaset( Returns: The ReplicaSet for the specified manifest. """ - log.info(f'loading replicaset from path: {path}') + log.info(f"loading replicaset from path: {path}") replicaset = objects.ReplicaSet.load(path, name=name) if set_namespace: replicaset.namespace = self.namespace return replicaset def load_statefulset( - self, path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.StatefulSet: """Load a manifest YAML into a StatefulSet object. @@ -438,17 +442,17 @@ def load_statefulset( Returns: The StatefulSet for the specified manifest. """ - log.info(f'loading statefulset from path: {path}') + log.info(f"loading statefulset from path: {path}") statefulset = objects.StatefulSet.load(path, name=name) if set_namespace: statefulset.namespace = self.namespace return statefulset def load_serviceaccount( - self, - path: str, - set_namespace: bool = True, - name: Optional[str] = None, + self, + path: str, + set_namespace: bool = True, + name: Optional[str] = None, ) -> objects.ServiceAccount: """Load a manifest YAML into a ServiceAccount object. @@ -470,7 +474,7 @@ def load_serviceaccount( Returns: The ServiceAccount for the specified manifest. """ - log.info(f'loading serviceaccount from path: {path}') + log.info(f"loading serviceaccount from path: {path}") serviceaccount = objects.ServiceAccount.load(path, name=name) if set_namespace: serviceaccount.namespace = self.namespace @@ -479,10 +483,10 @@ def load_serviceaccount( # ****** General Helpers ****** def get_configmaps( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.ConfigMap]: """Get ConfigMaps from the cluster. @@ -518,10 +522,10 @@ def get_configmaps( return configmaps def get_daemonsets( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.DaemonSet]: """Get DaemonSets from the cluster. @@ -557,10 +561,10 @@ def get_daemonsets( return daemonsets def get_deployments( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.Deployment]: """Get Deployments from the cluster. @@ -596,10 +600,10 @@ def get_deployments( return deployments def get_endpoints( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.Endpoints]: """Get Endpoints from the cluster. @@ -635,10 +639,10 @@ def get_endpoints( return endpoints def get_events( - self, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, - all_namespaces: bool = False, + self, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, + all_namespaces: bool = False, ) -> Dict[str, objects.Event]: """Get the latest Events that occurred in the cluster. @@ -658,13 +662,10 @@ def get_events( selectors = utils.selector_kwargs(fields, labels) if all_namespaces: - results = client.CoreV1Api().list_event_for_all_namespaces( - **selectors - ) + results = client.CoreV1Api().list_event_for_all_namespaces(**selectors) else: results = client.CoreV1Api().list_namespaced_event( - namespace=self.namespace, - **selectors + namespace=self.namespace, **selectors ) events = {} @@ -675,9 +676,9 @@ def get_events( return events def get_namespaces( - self, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.Namespace]: """Get Namespaces from the cluster. @@ -708,8 +709,8 @@ def get_namespaces( @staticmethod def get_nodes( - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.Node]: """Get the Nodes that make up the cluster. @@ -739,10 +740,10 @@ def get_nodes( return nodes def get_pods( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.Pod]: """Get Pods from the cluster. @@ -778,10 +779,10 @@ def get_pods( return pods def get_secrets( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.Secret]: """Get Secrets from the cluster. @@ -817,10 +818,10 @@ def get_secrets( return secrets def get_services( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.Service]: """Get Services under the test case namespace. @@ -856,10 +857,10 @@ def get_services( return services def get_persistentvolumeclaims( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.PersistentVolumeClaim]: """Get PersistentVolumeClaims from the cluster. @@ -897,10 +898,10 @@ def get_persistentvolumeclaims( return persistentvolumeclaims def get_ingresses( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.Ingress]: """Get Ingresses from the cluster. @@ -937,10 +938,10 @@ def get_ingresses( return ingresses def get_replicasets( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.ReplicaSet]: """Get ReplicaSets from the cluster. @@ -976,10 +977,10 @@ def get_replicasets( return replicasets def get_statefulsets( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.StatefulSet]: """Get StatefulSets from the cluster. @@ -1015,10 +1016,10 @@ def get_statefulsets( return statefulsets def get_serviceaccounts( - self, - namespace: str = None, - fields: Dict[str, str] = None, - labels: Dict[str, str] = None, + self, + namespace: str = None, + fields: Dict[str, str] = None, + labels: Dict[str, str] = None, ) -> Dict[str, objects.ServiceAccount]: """Get ServiceAccounts from the cluster. @@ -1041,9 +1042,11 @@ def get_serviceaccounts( selectors = utils.selector_kwargs(fields, labels) - results = objects.ServiceAccount.preferred_client().list_namespaced_service_account( - namespace=namespace, - **selectors, + results = ( + objects.ServiceAccount.preferred_client().list_namespaced_service_account( + namespace=namespace, + **selectors, + ) ) serviceaccount = {} @@ -1057,11 +1060,11 @@ def get_serviceaccounts( @staticmethod def wait_for_conditions( - *args: Condition, - timeout: int = None, - interval: Union[float, int] = 1, - policy: Policy = Policy.ONCE, - fail_on_api_error: bool = True, + *args: Condition, + timeout: int = None, + interval: Union[float, int] = 1, + policy: Policy = Policy.ONCE, + fail_on_api_error: bool = True, ) -> None: """Wait for all of the provided Conditions to be met. @@ -1095,7 +1098,7 @@ def wait_for_conditions( # If something was given, make sure they are all Conditions if not all(map(lambda c: isinstance(c, Condition), args)): - raise ValueError('All arguments must be a Condition') + raise ValueError("All arguments must be a Condition") # make a copy of the conditions to_check = list(args) @@ -1105,7 +1108,7 @@ def condition_checker(conditions): # condition checking policy met, unmet = check_and_sort(*conditions) if policy == Policy.ONCE: - log.info(f'check met: {met}') + log.info(f"check met: {met}") conditions[:] = unmet return len(unmet) == 0 @@ -1114,11 +1117,11 @@ def condition_checker(conditions): else: raise ValueError( - f'Invalid condition policy specified: {policy}', + f"Invalid condition policy specified: {policy}", ) wait_condition = Condition( - 'wait for conditions', + "wait for conditions", condition_checker, to_check, ) @@ -1135,14 +1138,14 @@ def condition_checker(conditions): # that we weren't able to resolve in the error message, not # the 'wait for conditions' wrapper. raise TimeoutError( - f'timed out wile waiting for conditions to be met: {to_check}', + f"timed out wile waiting for conditions to be met: {to_check}", ) def wait_for_ready_nodes( - self, - count: int, - timeout: int = None, - interval: Union[int, float] = 1, + self, + count: int, + timeout: int = None, + interval: Union[int, float] = 1, ) -> None: """Wait until there are at least ``count`` number of nodes available in the cluster. @@ -1157,12 +1160,13 @@ def wait_for_ready_nodes( interval: The time, in seconds, to sleep before re-checking the number of nodes. """ + def node_count_match(node_count): nodes = self.get_nodes() return [n.is_ready() for n in nodes.values()].count(True) >= node_count wait_condition = Condition( - f'wait for {count} nodes', + f"wait for {count} nodes", node_count_match, count, ) @@ -1173,7 +1177,9 @@ def node_count_match(node_count): interval=interval, ) - def wait_for_registered(self, timeout: int = None, interval: Union[int, float] = 1) -> None: + def wait_for_registered( + self, timeout: int = None, interval: Union[int, float] = 1 + ) -> None: """Wait for all of the pre-registered objects to be ready on the cluster. An object is pre-registered with the test client if it is specified @@ -1187,6 +1193,7 @@ def wait_for_registered(self, timeout: int = None, interval: Union[int, float] = interval: The time, in seconds, to sleep before re-checking the ready state for pre-registered objects. """ + def check_registered(): for obj in self.pre_registered: if not obj.is_ready(): @@ -1194,7 +1201,7 @@ def check_registered(): return True wait_condition = Condition( - 'wait for pre-registered objects to be ready', + "wait for pre-registered objects to be ready", check_registered, ) @@ -1206,9 +1213,9 @@ def check_registered(): @staticmethod def wait_until_created( - obj: objects.ApiObject, - timeout: int = None, - interval: Union[int, float] = 1, + obj: objects.ApiObject, + timeout: int = None, + interval: Union[int, float] = 1, ) -> None: """Wait until the specified object has been created. @@ -1221,6 +1228,7 @@ def wait_until_created( interval: The time, in seconds, to sleep before re-checking the created state of the object. """ + def check_ready(api_obj): try: api_obj.refresh() @@ -1229,13 +1237,11 @@ def check_ready(api_obj): return True wait_condition = Condition( - f'wait for {type(obj).__name__}:{obj.name} to be created', + f"wait for {type(obj).__name__}:{obj.name} to be created", check_ready, obj, ) utils.wait_for_condition( - condition=wait_condition, - timeout=timeout, - interval=interval + condition=wait_condition, timeout=timeout, interval=interval ) diff --git a/kubetest/condition.py b/kubetest/condition.py index 2cfbd6c..e1b5e15 100644 --- a/kubetest/condition.py +++ b/kubetest/condition.py @@ -48,7 +48,7 @@ class Condition: def __init__(self, name: str, fn: Callable, *args, **kwargs) -> None: if not callable(fn): - raise ValueError('The Condition function must be callable') + raise ValueError("The Condition function must be callable") self.name = name self.fn = fn @@ -59,7 +59,7 @@ def __init__(self, name: str, fn: Callable, *args, **kwargs) -> None: self.last_check = False def __str__(self) -> str: - return f'' + return f"" def __repr__(self) -> str: return self.__str__() diff --git a/kubetest/errors.py b/kubetest/errors.py index d6f418f..5c9abd2 100644 --- a/kubetest/errors.py +++ b/kubetest/errors.py @@ -1,5 +1,3 @@ - - class KubetestError(Exception): """Base class for all kubetest exceptions.""" diff --git a/kubetest/manager.py b/kubetest/manager.py index cedd7bc..194b152 100644 --- a/kubetest/manager.py +++ b/kubetest/manager.py @@ -7,7 +7,7 @@ from kubetest import client, objects, utils -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class ObjectManager: @@ -27,20 +27,20 @@ class ObjectManager: # The ApiObject buckets in the order that that should be # applied when creating them on the cluster. ordered_buckets = [ - 'namespace', - 'rolebinding', - 'clusterrolebinding', - 'secret', - 'networkpolicy', - 'service', - 'configmap', - 'persistentvolumeclaim', - 'ingress', - 'daemonset', - 'statefulset', - 'replicaset', - 'deployment', - 'pod', + "namespace", + "rolebinding", + "clusterrolebinding", + "secret", + "networkpolicy", + "service", + "configmap", + "persistentvolumeclaim", + "ingress", + "daemonset", + "statefulset", + "replicaset", + "deployment", + "pod", ] def __init__(self): @@ -72,8 +72,8 @@ def add(self, *args: objects.ApiObject) -> None: for arg in args: if not isinstance(arg, objects.ApiObject): raise ValueError( - 'Only ApiObject instances can be added to the ObjectManager, ' - f'but was given: {arg}' + "Only ApiObject instances can be added to the ObjectManager, " + f"but was given: {arg}" ) # Get the type name of the ApiObject wrapper and lower case it, @@ -86,7 +86,7 @@ def add(self, *args: objects.ApiObject) -> None: if name in self.ordered_buckets: self.__getattribute__(name).append(arg) else: - raise ValueError(f'Unable to determine bucket for ApiObject: {arg}') + raise ValueError(f"Unable to determine bucket for ApiObject: {arg}") def get_objects_in_apply_order(self) -> Generator[objects.ApiObject, None, None]: """Get all of the managed objects in the order that they should be @@ -137,11 +137,11 @@ class TestMeta: """ def __init__( - self, - name: str, - node_id: str, - namespace_create: bool = True, - namespace_name: str = None, + self, + name: str, + node_id: str, + namespace_create: bool = True, + namespace_name: str = None, ) -> None: self.name = name @@ -217,7 +217,8 @@ def teardown(self) -> None: # possibility of things existing on the cluster. if self._pt_setup_failed: log.info( - f'pytest setup failed for {self.name}: not running test case teardown') + f"pytest setup failed for {self.name}: not running test case teardown" + ) return # Delete the test case namespace if we've created it. @@ -230,7 +231,9 @@ def teardown(self) -> None: for crb in self.clusterrolebindings: self.client.delete(crb) - def yield_container_logs(self, tail_lines: int = None) -> Generator[str, None, None]: + def yield_container_logs( + self, tail_lines: int = None + ) -> Generator[str, None, None]: """Yield the container logs for the test case. These logs will be printed out if the test was in error to provide @@ -257,7 +260,7 @@ def yield_container_logs(self, tail_lines: int = None) -> Generator[str, None, N log_kwargs = {} if tail_lines is not None and tail_lines > 0: - log_kwargs['tail_lines'] = tail_lines + log_kwargs["tail_lines"] = tail_lines for pod in pods_list.items: for container in pod.spec.containers: @@ -273,14 +276,14 @@ def yield_container_logs(self, tail_lines: int = None) -> Generator[str, None, N ) except Exception as e: log.warning( - f'Unable to cache logs for {pod_name}::{container_name} ({e})', + f"Unable to cache logs for {pod_name}::{container_name} ({e})", ) continue - if logs != '': - _id = f'=== {self.node_id} -> {pod_name}::{container_name} ===' - border = '=' * len(_id) - yield '\n'.join([border, _id, border, logs, '\n']) + if logs != "": + _id = f"=== {self.node_id} -> {pod_name}::{container_name} ===" + border = "=" * len(_id) + yield "\n".join([border, _id, border, logs, "\n"]) return def register_rolebindings(self, *rolebindings: objects.RoleBinding) -> None: @@ -292,8 +295,8 @@ def register_rolebindings(self, *rolebindings: objects.RoleBinding) -> None: self.rolebindings.extend(rolebindings) def register_clusterrolebindings( - self, - *clusterrolebindings: objects.ClusterRoleBinding, + self, + *clusterrolebindings: objects.ClusterRoleBinding, ) -> None: """Register a ClusterRoleBinding requirement with the test case. @@ -331,11 +334,11 @@ def __init__(self): self.nodes = {} def new_test( - self, - node_id: str, - test_name: str, - namespace_create: bool = True, - namespace_name: str = None, + self, + node_id: str, + test_name: str, + namespace_create: bool = True, + namespace_name: str = None, ) -> TestMeta: """Create a new TestMeta for a test case. @@ -354,12 +357,12 @@ def new_test( Returns: The newly created TestMeta for the test case. """ - log.info(f'creating test meta for {node_id}') + log.info(f"creating test meta for {node_id}") meta = TestMeta( node_id=node_id, name=test_name, namespace_create=namespace_create, - namespace_name=namespace_name + namespace_name=namespace_name, ) self.nodes[node_id] = meta diff --git a/kubetest/manifest.py b/kubetest/manifest.py index 7d42816..3fee3a2 100644 --- a/kubetest/manifest.py +++ b/kubetest/manifest.py @@ -54,7 +54,9 @@ class ContextRenderer: rendering. Defaults to an empty dictionary. """ - def __init__(self, renderer: Renderer = render, context: Dict[str, Any] = {}) -> None: + def __init__( + self, renderer: Renderer = render, context: Dict[str, Any] = {} + ) -> None: self._renderer = renderer self._context = context @@ -63,7 +65,9 @@ def context(self) -> Dict[str, Any]: """The context variables set on the renderer.""" return self._context - def __call__(self, template: Union[str, TextIO], context: Dict[str, Any]) -> Union[str, TextIO]: + def __call__( + self, template: Union[str, TextIO], context: Dict[str, Any] + ) -> Union[str, TextIO]: """Render a manifest template file to a YAML docoment. Args: @@ -92,7 +96,7 @@ def load_file(path: str, *, renderer: Renderer = render) -> List[object]: Returns: A list of the Kubernetes API objects for this manifest file. """ - with open(path, 'r') as f: + with open(path, "r") as f: content = renderer(f, dict(path=path)) manifests = yaml.load_all(content, Loader=yaml.SafeLoader) @@ -101,7 +105,7 @@ def load_file(path: str, *, renderer: Renderer = render) -> List[object]: obj_type = get_type(manifest) if obj_type is None: raise ValueError( - f'Unable to determine object type for manifest: {manifest}', + f"Unable to determine object type for manifest: {manifest}", ) objs.append(new_object(obj_type, manifest)) @@ -124,13 +128,13 @@ def load_path(path: str, *, renderer: Renderer = render) -> List[object]: ValueError: The provided path is not a directory. """ if not os.path.isdir(path): - raise ValueError(f'{path} is not a directory') + raise ValueError(f"{path} is not a directory") objs = [] if isinstance(renderer, ContextRenderer): renderer.context["objs"] = objs for f in os.listdir(path): - if os.path.splitext(f)[1].lower() in ['.yaml', '.yml']: + if os.path.splitext(f)[1].lower() in [".yaml", ".yml"]: objs = objs + load_file(os.path.join(path, f), renderer=renderer) return objs @@ -154,11 +158,11 @@ def get_type(manifest: Dict[str, Any]) -> Union[object, None]: ValueError: The manifest dictionary does not have a `version` or `kind` specified. """ - version = manifest.get('apiVersion') + version = manifest.get("apiVersion") if version is None: raise ValueError('manifest has no "version" field specified') - kind = manifest.get('kind') + kind = manifest.get("kind") if kind is None: raise ValueError('manifest has no "kind" field specified') @@ -176,12 +180,13 @@ def get_type(manifest: Dict[str, Any]) -> Union[object, None]: # yields nothing. possibilities = [ # add the default case - version.replace('/', '').replace('.', '') + kind + version.replace("/", "").replace(".", "") + + kind ] # if the prefix exists, add the non-prefixed version as a secondary check - if version.count('/') == 1: - possibilities.append(version.split('/')[1] + kind) + if version.count("/") == 1: + possibilities.append(version.split("/")[1] + kind) for to_check in possibilities: type_name = lookup.get(to_check.lower()) @@ -212,7 +217,7 @@ def load_type(obj_type, path: str, *, renderer: Renderer = render): Raises: FileNotFoundError: The specified file was not found. """ - with open(path, 'r') as f: + with open(path, "r") as f: content = renderer(f, dict(path=path, obj_type=obj_type)) manifest = yaml.full_load(content) @@ -257,7 +262,7 @@ def new_object(root_type, config): # The config value matches an expected key in the attribute dict. # Now, we want to cast that config to the appropriate type based # on the contents of the 'swagger_types'/'openapi_types' dict. - if hasattr(root_type, 'swagger_types'): + if hasattr(root_type, "swagger_types"): t = root_type.swagger_types[k] else: t = root_type.openapi_types[k] @@ -273,7 +278,7 @@ def new_object(root_type, config): # Check if the type is a list of some other type. # This should match to something like: 'list[str]', where the # element type (in this case 'str') will be isolated as a group. - list_match = re.match(r'^list\[(.*)\]$', t) + list_match = re.match(r"^list\[(.*)\]$", t) if list_match is not None: element_type = list_match.group(1) list_value = [cast_value(i, element_type) for i in cfg_value] @@ -284,7 +289,7 @@ def new_object(root_type, config): # This should match to something lint: 'dict(str, str)', where # the element types (in this case, both 'str') will be isolated # as separate groups. - dict_match = re.match(r'^dict\((.*), (.*)\)$', t) + dict_match = re.match(r"^dict\((.*), (.*)\)$", t) if dict_match is not None: key_type = dict_match.group(1) val_type = dict_match.group(2) @@ -337,4 +342,4 @@ def cast_value(value: Any, t: str) -> Any: if k_type is not None: return new_object(k_type, value) - raise ValueError(f'Unable to determine cast type behavior: {t}') + raise ValueError(f"Unable to determine cast type behavior: {t}") diff --git a/kubetest/markers.py b/kubetest/markers.py index 70a18ae..97e3e0f 100644 --- a/kubetest/markers.py +++ b/kubetest/markers.py @@ -1,87 +1,86 @@ """Custom pytest markers for kubetest.""" import os -from typing import Any, Dict, List, TextIO, Union +from typing import List import pytest from kubernetes import client from kubetest import manager -from kubetest.manifest import (ContextRenderer, Renderer, load_file, load_path, - render) +from kubetest.manifest import ContextRenderer, Renderer, load_file, load_path, render from kubetest.objects import ApiObject, ClusterRoleBinding, RoleBinding APPLYMANIFEST_INI = ( - 'applymanifest(path, render=None): ' - 'load a YAML manifest file from the specified path and create it on the cluster. ' + "applymanifest(path, render=None): " + "load a YAML manifest file from the specified path and create it on the cluster. " 'This marker is similar to the "kubectl apply -f " command. Loading a ' - 'manifest via this marker will not prohibit you from loading other manifests ' + "manifest via this marker will not prohibit you from loading other manifests " 'manually. Use the "kube" fixture to get references to the created objects. The ' - 'manifest loaded via this marker is registered with the internal test case ' + "manifest loaded via this marker is registered with the internal test case " 'metainfo and can be waited upon for creation via the "kube" fixture\'s ' '"wait_until_created" method.' ) APPLYMANIFESTS_INI = ( - 'applymanifests(dir, files=None, render=None): ' - 'load YAML manifests from the specified path and create them on the cluster. ' - 'By default, all YAML files found in the specified path will be loaded and created. ' - 'If a list is passed to the files parameter, only the files in the path matching ' - 'a name in the files list will be loaded and created. This marker is similar to ' + "applymanifests(dir, files=None, render=None): " + "load YAML manifests from the specified path and create them on the cluster. " + "By default, all YAML files found in the specified path will be loaded and created. " + "If a list is passed to the files parameter, only the files in the path matching " + "a name in the files list will be loaded and created. This marker is similar to " 'the "kubectl apply -f " command. Loading manifests via this marker will not ' 'prohibit you from loading other manifests manually. Use the "kube" fixture to get ' - 'references to the created objects. Manifests loaded via this marker are registered ' - 'with the internal test case metainfo and can be waited upon for creation via the ' + "references to the created objects. Manifests loaded via this marker are registered " + "with the internal test case metainfo and can be waited upon for creation via the " '"kube" fixture\'s "wait_until_created" method.' ) RENDER_MANIFESTS_INI = ( - 'render_manifests(render, context={}): ' - 'set a callable for rendering manifest templates. ' - 'The render argument must be a callable that accepts a template file or string' - 'value and returns a rendered YAML document that can be applied to Kubernetes.' - 'The optional context argument can be used to set template variables that are made' - 'available to the template at render time.' + "render_manifests(render, context={}): " + "set a callable for rendering manifest templates. " + "The render argument must be a callable that accepts a template file or string" + "value and returns a rendered YAML document that can be applied to Kubernetes." + "The optional context argument can be used to set template variables that are made" + "available to the template at render time." ) CLUSTERROLEBINDING_INI = ( - 'clusterrolebinding(name, subject_kind=None, subject_name=None): ' - 'create and use a Kubernetes ClusterRoleBinding for the test case. The generated ' - 'cluster role binding will be automatically created and removed for each marked ' - 'test. The name of the role must be specified. Only existing ClusterRoles can be ' - 'used. Optionally, the subject_kind (User, Group, ServiceAccount) and subject_name ' - 'can be specified to set a target subject for the ClusterRoleBinding. If no ' - 'subject is specified, it will default to all users in the namespace and all ' - 'service accounts. If a subject is specified, both the subject kind and name must ' - 'be present. The ClusterRoleBinding will always use the apiGroup ' + "clusterrolebinding(name, subject_kind=None, subject_name=None): " + "create and use a Kubernetes ClusterRoleBinding for the test case. The generated " + "cluster role binding will be automatically created and removed for each marked " + "test. The name of the role must be specified. Only existing ClusterRoles can be " + "used. Optionally, the subject_kind (User, Group, ServiceAccount) and subject_name " + "can be specified to set a target subject for the ClusterRoleBinding. If no " + "subject is specified, it will default to all users in the namespace and all " + "service accounts. If a subject is specified, both the subject kind and name must " + "be present. The ClusterRoleBinding will always use the apiGroup " '"rbac.authorization.k8s.io" for both subjects and roleRefs. For more information, ' - 'see: https://kubernetes.io/docs/reference/access-authn-authz/rbac/' + "see: https://kubernetes.io/docs/reference/access-authn-authz/rbac/" ) ROLEBINDING_INI = ( - 'rolebinding(kind, name, subject_kind=None, subject_name=None): ' - 'create and use a Kubernetes RoleBinding for the test case. The generated role ' - 'binding will use the generated test-case namespace and will be automatically ' - 'removed once the test completes. The role kind (Role, ClusterRole) must be ' - 'specified along with the name of the role. Only existing Roles or ClusterRoles ' - 'can be used. Optionally, the subject_kind (User, Group, ServiceAccount) and ' - 'subject_name can be specified to set a target subject for the RoleBinding. If ' - 'no subject is specified, it will default to all users in the namespace and all ' - 'service accounts. If a subject is specified, both the subject kind and name ' - 'must be present. The RoleBinding will always use the apiGroup ' + "rolebinding(kind, name, subject_kind=None, subject_name=None): " + "create and use a Kubernetes RoleBinding for the test case. The generated role " + "binding will use the generated test-case namespace and will be automatically " + "removed once the test completes. The role kind (Role, ClusterRole) must be " + "specified along with the name of the role. Only existing Roles or ClusterRoles " + "can be used. Optionally, the subject_kind (User, Group, ServiceAccount) and " + "subject_name can be specified to set a target subject for the RoleBinding. If " + "no subject is specified, it will default to all users in the namespace and all " + "service accounts. If a subject is specified, both the subject kind and name " + "must be present. The RoleBinding will always use the apiGroup " '"rbac.authorization.k8s.io" for both subjects and roleRefs. For more information, ' - 'see: https://kubernetes.io/docs/reference/access-authn-authz/rbac/' + "see: https://kubernetes.io/docs/reference/access-authn-authz/rbac/" ) NAMESPACE_INI = ( - 'namespace(create=True, name=None): ' - 'finer-grained namespace configuration for the test case. By default, a new ' - 'Kubernetes namespace is generated for each test case, using the test name and a ' - 'timestamp to ensure uniqueness. This marker allows you to override this behavior ' + "namespace(create=True, name=None): " + "finer-grained namespace configuration for the test case. By default, a new " + "Kubernetes namespace is generated for each test case, using the test name and a " + "timestamp to ensure uniqueness. This marker allows you to override this behavior " 'to define your own namespace names, or to use existing namespaces. Set "create" ' 'to False to disable namespace creation entirely. Set "name" to a string to set ' - 'the name of the namespace to create/use.' + "the name of the namespace to create/use." ) @@ -91,12 +90,12 @@ def register(config) -> None: Args: config: The pytest config that markers will be registered to. """ - config.addinivalue_line('markers', APPLYMANIFEST_INI) - config.addinivalue_line('markers', APPLYMANIFESTS_INI) - config.addinivalue_line('markers', RENDER_MANIFESTS_INI) - config.addinivalue_line('markers', CLUSTERROLEBINDING_INI) - config.addinivalue_line('markers', ROLEBINDING_INI) - config.addinivalue_line('markers', NAMESPACE_INI) + config.addinivalue_line("markers", APPLYMANIFEST_INI) + config.addinivalue_line("markers", APPLYMANIFESTS_INI) + config.addinivalue_line("markers", RENDER_MANIFESTS_INI) + config.addinivalue_line("markers", CLUSTERROLEBINDING_INI) + config.addinivalue_line("markers", ROLEBINDING_INI) + config.addinivalue_line("markers", NAMESPACE_INI) def get_manifest_renderer_for_item(item: pytest.Item) -> Renderer: @@ -131,18 +130,20 @@ def apply_manifest_from_marker(item: pytest.Item, meta: manager.TestMeta) -> Non meta: The metainfo object for the marked test case. """ item_renderer = get_manifest_renderer_for_item(item) - for mark in item.iter_markers(name='applymanifest'): + for mark in item.iter_markers(name="applymanifest"): path = mark.args[0] - renderer = mark.kwargs.get('renderer', item_renderer) + renderer = mark.kwargs.get("renderer", item_renderer) if not callable(renderer): - raise TypeError(f"renderer given is not callable") + raise TypeError("renderer given is not callable") # Normalize the path to be absolute. if not os.path.isabs(path): path = os.path.abspath(path) # Load the manifest - context = dict(namespace=meta.ns, test_node_id=meta.node_id, test_name=meta.name) + context = dict( + namespace=meta.ns, test_node_id=meta.node_id, test_name=meta.name + ) context_renderer = ContextRenderer(renderer, context) objs = load_file(path, renderer=context_renderer) @@ -160,7 +161,7 @@ def apply_manifest_from_marker(item: pytest.Item, meta: manager.TestMeta) -> Non break if not found: raise ValueError( - f'Unable to match loaded object to an internal wrapper class: {obj}', + f"Unable to match loaded object to an internal wrapper class: {obj}", ) meta.register_objects(wrapped) @@ -180,13 +181,13 @@ def apply_manifests_from_marker(item: pytest.Item, meta: manager.TestMeta) -> No meta: The metainfo object for the marked test case. """ item_renderer = get_manifest_renderer_for_item(item) - for mark in item.iter_markers(name='applymanifests'): + for mark in item.iter_markers(name="applymanifests"): dir_path = mark.args[0] - files = mark.kwargs.get('files') - renderer = mark.kwargs.get('renderer', item_renderer) + files = mark.kwargs.get("files") + renderer = mark.kwargs.get("renderer", item_renderer) if not callable(renderer): - raise TypeError(f"renderer given is not callable") + raise TypeError("renderer given is not callable") # We expect the path specified to either be absolute or relative # from the test file. If the path is relative, add the directory @@ -197,8 +198,12 @@ def apply_manifests_from_marker(item: pytest.Item, meta: manager.TestMeta) -> No ) # Setup template rendering context - context = dict(dir_path=dir_path, namespace=meta.ns, - test_node_id=meta.node_id, test_name=meta.name) + context = dict( + dir_path=dir_path, + namespace=meta.ns, + test_node_id=meta.node_id, + test_name=meta.name, + ) context_renderer = ContextRenderer(renderer, context) # If there are any files specified, we will only load those files. @@ -209,7 +214,9 @@ def apply_manifests_from_marker(item: pytest.Item, meta: manager.TestMeta) -> No objs = [] context_renderer.context["objs"] = objs for f in files: - objs.extend(load_file(os.path.join(dir_path, f), renderer=context_renderer)) + objs.extend( + load_file(os.path.join(dir_path, f), renderer=context_renderer) + ) # For each of the loaded Kubernetes resources, we'll want to wrap it # in the equivalent kubetest wrapper. If the resource does not have @@ -225,7 +232,7 @@ def apply_manifests_from_marker(item: pytest.Item, meta: manager.TestMeta) -> No break if not found: raise ValueError( - f'Unable to match loaded object to an internal wrapper class: {obj}', + f"Unable to match loaded object to an internal wrapper class: {obj}", ) meta.register_objects(wrapped) @@ -243,33 +250,39 @@ def rolebindings_from_marker(item: pytest.Item, namespace: str) -> List[RoleBind The RoleBindings that were generated from the test case markers. """ rolebindings = [] - for mark in item.iter_markers(name='rolebinding'): + for mark in item.iter_markers(name="rolebinding"): kind = mark.args[0] name = mark.args[1] - subj_kind = mark.kwargs.get('subject_kind') - subj_name = mark.kwargs.get('subject_name') + subj_kind = mark.kwargs.get("subject_kind") + subj_name = mark.kwargs.get("subject_name") subj = get_custom_rbac_subject(namespace, subj_kind, subj_name) if not subj: subj = get_default_rbac_subjects(namespace) - rolebindings.append(RoleBinding(client.V1RoleBinding( - metadata=client.V1ObjectMeta( - name=f'kubetest:{item.name}', - namespace=namespace, - ), - role_ref=client.V1RoleRef( - api_group='rbac.authorization.k8s.io', - kind=kind, - name=name, - ), - subjects=subj, - ))) + rolebindings.append( + RoleBinding( + client.V1RoleBinding( + metadata=client.V1ObjectMeta( + name=f"kubetest:{item.name}", + namespace=namespace, + ), + role_ref=client.V1RoleRef( + api_group="rbac.authorization.k8s.io", + kind=kind, + name=name, + ), + subjects=subj, + ) + ) + ) return rolebindings -def clusterrolebindings_from_marker(item: pytest.Item, namespace: str) -> List[ClusterRoleBinding]: +def clusterrolebindings_from_marker( + item: pytest.Item, namespace: str +) -> List[ClusterRoleBinding]: """Create ClusterRoleBindings for the test case if the test case is marked with the `pytest.mark.clusterrolebinding` marker. @@ -281,31 +294,37 @@ def clusterrolebindings_from_marker(item: pytest.Item, namespace: str) -> List[C The ClusterRoleBindings which were generated from the test case markers. """ clusterrolebindings = [] - for mark in item.iter_markers(name='clusterrolebinding'): + for mark in item.iter_markers(name="clusterrolebinding"): name = mark.args[0] - subj_kind = mark.kwargs.get('subject_kind') - subj_name = mark.kwargs.get('subject_name') + subj_kind = mark.kwargs.get("subject_kind") + subj_name = mark.kwargs.get("subject_name") subj = get_custom_rbac_subject(namespace, subj_kind, subj_name) if not subj: subj = get_default_rbac_subjects(namespace) - clusterrolebindings.append(ClusterRoleBinding(client.V1ClusterRoleBinding( - metadata=client.V1ObjectMeta( - name=f'kubetest:{item.name}', - ), - role_ref=client.V1RoleRef( - api_group='rbac.authorization.k8s.io', - kind='ClusterRole', - name=name, - ), - subjects=subj, - ))) + clusterrolebindings.append( + ClusterRoleBinding( + client.V1ClusterRoleBinding( + metadata=client.V1ObjectMeta( + name=f"kubetest:{item.name}", + ), + role_ref=client.V1RoleRef( + api_group="rbac.authorization.k8s.io", + kind="ClusterRole", + name=name, + ), + subjects=subj, + ) + ) + ) return clusterrolebindings -def get_custom_rbac_subject(namespace: str, kind: str, name: str) -> List[client.V1Subject]: +def get_custom_rbac_subject( + namespace: str, kind: str, name: str +) -> List[client.V1Subject]: """Create a custom RBAC subject for the given namespace. Both `kind` and `name` must be specified. If one is set and @@ -326,15 +345,15 @@ def get_custom_rbac_subject(namespace: str, kind: str, name: str) -> List[client # check that both `kind` and `name` are set. if (kind and not name) or (not kind and name): raise ValueError( - 'One of subject_kind and subject_name were specified, but both must ' - 'be specified when defining a custom Subject.' + "One of subject_kind and subject_name were specified, but both must " + "be specified when defining a custom Subject." ) # otherwise, if a custom subject is specified, create it if name is not None and kind is not None: return [ client.V1Subject( - api_group='rbac.authorization.k8s.io', + api_group="rbac.authorization.k8s.io", namespace=namespace, kind=kind, name=name, @@ -361,23 +380,23 @@ def get_default_rbac_subjects(namespace: str) -> List[client.V1Subject]: return [ # all authenticated users client.V1Subject( - api_group='rbac.authorization.k8s.io', + api_group="rbac.authorization.k8s.io", namespace=namespace, - name='system:authenticated', - kind='Group', + name="system:authenticated", + kind="Group", ), # all unauthenticated users client.V1Subject( - api_group='rbac.authorization.k8s.io', + api_group="rbac.authorization.k8s.io", namespace=namespace, - name='system:unauthenticated', - kind='Group', + name="system:unauthenticated", + kind="Group", ), # all service accounts client.V1Subject( - api_group='rbac.authorization.k8s.io', + api_group="rbac.authorization.k8s.io", namespace=namespace, - name='system:serviceaccounts', - kind='Group', + name="system:serviceaccounts", + kind="Group", ), ] diff --git a/kubetest/objects/api_object.py b/kubetest/objects/api_object.py index f91e6a3..a1a27d2 100644 --- a/kubetest/objects/api_object.py +++ b/kubetest/objects/api_object.py @@ -10,7 +10,7 @@ from kubetest import condition, utils from kubetest.manifest import load_file -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class ApiObject(abc.ABC): @@ -36,18 +36,18 @@ class ApiObject(abc.ABC): # The Kubernetes API object type. Each subclass should # define its own obj_type. obj_type = None - '''The default Kubernetes API object type for the class. + """The default Kubernetes API object type for the class. Each subclass should define its own ``obj_type``. - ''' + """ api_clients = None - '''A mapping of all the supported api clients for the API + """A mapping of all the supported api clients for the API object type. Various resources can have multiple versions, e.g. "apps/v1", "apps/v1beta1", etc. The preferred version for each resource type should be defined under the "preferred" key. The preferred API client will be used when the apiVersion is not specified for the resource. - ''' + """ def __init__(self, api_object) -> None: # The underlying Kubernetes Api Object @@ -93,7 +93,9 @@ def namespace(self, name: str): if self.obj.metadata.namespace is None: self.obj.metadata.namespace = name else: - raise AttributeError('Cannot set namespace - object already has a namespace') + raise AttributeError( + "Cannot set namespace - object already has a namespace" + ) @property def api_client(self): @@ -109,13 +111,13 @@ def api_client(self): # preferred version. if c is None: log.warning( - f'unknown version ({self.version}), falling back to preferred version' + f"unknown version ({self.version}), falling back to preferred version" ) - c = self.api_clients.get('preferred') + c = self.api_clients.get("preferred") if c is None: raise ValueError( - 'unknown version specified and no preferred version ' - f'defined for resource ({self.version})' + "unknown version specified and no preferred version " + f"defined for resource ({self.version})" ) # If we did find it, initialize that client version. self._api_client = c() @@ -129,18 +131,18 @@ def preferred_client(cls): Raises: ValueError: No preferred client is defined for the object. """ - c = cls.api_clients.get('preferred') + c = cls.api_clients.get("preferred") if c is None: raise ValueError( - f'no preferred api client defined for object {cls.__name__}', + f"no preferred api client defined for object {cls.__name__}", ) return c() def wait_until_ready( - self, - timeout: int = None, - interval: Union[int, float] = 1, - fail_on_api_error: bool = False, + self, + timeout: int = None, + interval: Union[int, float] = 1, + fail_on_api_error: bool = False, ) -> None: """Wait until the resource is in the ready state. @@ -161,7 +163,7 @@ def wait_until_ready( TimeoutError: The specified timeout was exceeded. """ ready_condition = condition.Condition( - 'api object ready', + "api object ready", self.is_ready, ) @@ -172,7 +174,9 @@ def wait_until_ready( fail_on_api_error=fail_on_api_error, ) - def wait_until_deleted(self, timeout: int = None, interval: Union[int, float] = 1) -> None: + def wait_until_deleted( + self, timeout: int = None, interval: Union[int, float] = 1 + ) -> None: """Wait until the resource is deleted from the cluster. Args: @@ -186,25 +190,23 @@ def wait_until_deleted(self, timeout: int = None, interval: Union[int, float] = Raises: TimeoutError: The specified timeout was exceeded. """ + def deleted_fn(): try: self.refresh() except ApiException as e: # If we can no longer find the deployment, it is deleted. # If we get any other exception, raise it. - if e.status == 404 and e.reason == 'Not Found': + if e.status == 404 and e.reason == "Not Found": return True else: - log.error('error refreshing object state') + log.error("error refreshing object state") raise e else: # The object was still found, so it has not been deleted return False - delete_condition = condition.Condition( - 'api object deleted', - deleted_fn - ) + delete_condition = condition.Condition("api object deleted", deleted_fn) utils.wait_for_condition( condition=delete_condition, @@ -213,7 +215,7 @@ def deleted_fn(): ) @classmethod - def load(cls, path: str, name: Optional[str] = None) -> 'ApiObject': + def load(cls, path: str, name: Optional[str] = None) -> "ApiObject": """Load the Kubernetes resource from file. Generally, this is used to load the Kubernetes manifest files @@ -256,8 +258,8 @@ def load(cls, path: str, name: Optional[str] = None) -> 'ApiObject': if len(filtered) == 0: raise ValueError( - 'Unable to load resource from file - no resource definitions found ' - f'with type {cls.obj_type}.' + "Unable to load resource from file - no resource definitions found " + f"with type {cls.obj_type}." ) if len(filtered) == 1: @@ -268,8 +270,8 @@ def load(cls, path: str, name: Optional[str] = None) -> 'ApiObject': # matches. if not name: raise ValueError( - 'Unable to load resource from file - multiple resource definitions ' - f'found for {cls.obj_type}, but no name specified to select which one.' + "Unable to load resource from file - multiple resource definitions " + f"found for {cls.obj_type}, but no name specified to select which one." ) for o in filtered: @@ -278,8 +280,8 @@ def load(cls, path: str, name: Optional[str] = None) -> 'ApiObject': return api_obj raise ValueError( - 'Unable to load resource from file - multiple resource definitions found ' - f'for {cls.obj_type}, but none match specified name: {name}' + "Unable to load resource from file - multiple resource definitions found " + f"for {cls.obj_type}, but none match specified name: {name}" ) @abc.abstractmethod diff --git a/kubetest/objects/clusterrolebinding.py b/kubetest/objects/clusterrolebinding.py index 548b19e..4b9f2c4 100644 --- a/kubetest/objects/clusterrolebinding.py +++ b/kubetest/objects/clusterrolebinding.py @@ -6,7 +6,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class ClusterRoleBinding(ApiObject): @@ -25,10 +25,10 @@ class ClusterRoleBinding(ApiObject): obj_type = client.V1ClusterRoleBinding api_clients = { - 'preferred': client.RbacAuthorizationV1Api, - 'rbac.authorization.k8s.io/v1': client.RbacAuthorizationV1Api, - 'rbac.authorization.k8s.io/v1alpha1': client.RbacAuthorizationV1alpha1Api, - 'rbac.authorization.k8s.io/v1beta1': client.RbacAuthorizationV1beta1Api, + "preferred": client.RbacAuthorizationV1Api, + "rbac.authorization.k8s.io/v1": client.RbacAuthorizationV1Api, + "rbac.authorization.k8s.io/v1alpha1": client.RbacAuthorizationV1alpha1Api, + "rbac.authorization.k8s.io/v1beta1": client.RbacAuthorizationV1beta1Api, } def create(self, namespace: str = None) -> None: @@ -38,8 +38,9 @@ def create(self, namespace: str = None) -> None: namespace: This argument is ignored for ClusterRoleBindings. """ log.info( - f'creating clusterrolebinding "{self.name}" in namespace "{self.namespace}"') - log.debug(f'clusterrolebinding: {self.obj}') + f'creating clusterrolebinding "{self.name}" in namespace "{self.namespace}"' + ) + log.debug(f"clusterrolebinding: {self.obj}") self.obj = self.api_client.create_cluster_role_binding( body=self.obj, @@ -62,8 +63,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting clusterrolebinding "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'clusterrolebinding: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"clusterrolebinding: {self.obj}") return self.api_client.delete_cluster_role_binding( name=self.name, @@ -72,9 +73,7 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: def refresh(self) -> None: """Refresh the underlying Kubernetes ClusterRoleBinding resource.""" - self.obj = self.api_client.read_cluster_role_binding( - name=self.name - ) + self.obj = self.api_client.read_cluster_role_binding(name=self.name) def is_ready(self) -> bool: """Check if the ClusterRoleBinding is in the ready state. diff --git a/kubetest/objects/configmap.py b/kubetest/objects/configmap.py index 08afc31..561200e 100644 --- a/kubetest/objects/configmap.py +++ b/kubetest/objects/configmap.py @@ -6,7 +6,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class ConfigMap(ApiObject): @@ -25,8 +25,8 @@ class ConfigMap(ApiObject): obj_type = client.V1ConfigMap api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } def create(self, namespace: str = None) -> None: @@ -42,7 +42,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating configmap "{self.name}" in namespace "{self.namespace}"') - log.debug(f'configmap: {self.obj}') + log.debug(f"configmap: {self.obj}") self.obj = self.api_client.create_namespaced_config_map( namespace=namespace, @@ -66,8 +66,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting configmap "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'configmap: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"configmap: {self.obj}") return self.api_client.delete_namespaced_config_map( name=self.name, diff --git a/kubetest/objects/container.py b/kubetest/objects/container.py index 09924c2..2d96cb3 100644 --- a/kubetest/objects/container.py +++ b/kubetest/objects/container.py @@ -4,7 +4,7 @@ from kubernetes import client -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Container: @@ -48,9 +48,7 @@ def get_restart_count(self) -> int: if status.name == container_name: return status.restart_count - raise RuntimeError( - f'Unable to determine container status for {container_name}' - ) + raise RuntimeError(f"Unable to determine container status for {container_name}") def get_logs(self) -> str: """Get all the logs for the Container. diff --git a/kubetest/objects/daemonset.py b/kubetest/objects/daemonset.py index b923a0a..fa9e372 100644 --- a/kubetest/objects/daemonset.py +++ b/kubetest/objects/daemonset.py @@ -11,7 +11,7 @@ from .api_object import ApiObject from .pod import Pod -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class DaemonSet(ApiObject): @@ -30,10 +30,10 @@ class DaemonSet(ApiObject): obj_type = client.V1DaemonSet api_clients = { - 'preferred': client.AppsV1Api, - 'apps/v1': client.AppsV1Api, - 'apps/v1beta1': client.AppsV1beta1Api, - 'apps/v1beta2': client.AppsV1beta2Api, + "preferred": client.AppsV1Api, + "apps/v1": client.AppsV1Api, + "apps/v1beta1": client.AppsV1beta1Api, + "apps/v1beta2": client.AppsV1beta2Api, } def __init__(self, *args, **kwargs) -> None: @@ -49,7 +49,7 @@ def _add_kubetest_labels(self) -> None: The kubetest label key is "kubetest/" where the obj kind is the lower-cased kind of the obj. """ - self.klabel_key = 'kubetest/daemonset' + self.klabel_key = "kubetest/daemonset" if self.obj.metadata.labels: self.klabel_uid = self.obj.metadata.labels.get(self.klabel_key, None) else: @@ -73,7 +73,7 @@ def _add_kubetest_labels(self) -> None: # If no spec is set, there is nothing to set additional labels on if self.obj.spec is None: - log.warning('daemonset spec not set - cannot set kubetest label') + log.warning("daemonset spec not set - cannot set kubetest label") return # Set the selector label @@ -109,7 +109,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating daemonset "{self.name}" in namespace "{self.namespace}"') - log.debug(f'daemonset: {self.obj}') + log.debug(f"daemonset: {self.obj}") self.obj = self.api_client.create_namespaced_daemon_set( namespace=namespace, @@ -133,8 +133,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting daemonset "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'daemonset: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"daemonset: {self.obj}") return self.api_client.delete_namespaced_daemon_set( name=self.name, @@ -197,9 +197,9 @@ def get_pods(self) -> List[Pod]: pods = client.CoreV1Api().list_namespaced_pod( namespace=self.namespace, - label_selector=selector_string({self.klabel_key: self.klabel_uid}) + label_selector=selector_string({self.klabel_key: self.klabel_uid}), ) pods = [Pod(p) for p in pods.items] - log.debug(f'pods: {pods}') + log.debug(f"pods: {pods}") return pods diff --git a/kubetest/objects/deployment.py b/kubetest/objects/deployment.py index c6bd1d4..fbc006b 100644 --- a/kubetest/objects/deployment.py +++ b/kubetest/objects/deployment.py @@ -11,7 +11,7 @@ from .api_object import ApiObject from .pod import Pod -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Deployment(ApiObject): @@ -30,10 +30,10 @@ class Deployment(ApiObject): obj_type = client.V1Deployment api_clients = { - 'preferred': client.AppsV1Api, - 'apps/v1': client.AppsV1Api, - 'apps/v1beta1': client.AppsV1beta1Api, - 'apps/v1beta2': client.AppsV1beta2Api, + "preferred": client.AppsV1Api, + "apps/v1": client.AppsV1Api, + "apps/v1beta1": client.AppsV1beta1Api, + "apps/v1beta2": client.AppsV1beta2Api, } def __init__(self, *args, **kwargs) -> None: @@ -49,7 +49,7 @@ def _add_kubetest_labels(self) -> None: The kubetest label key is "kubetest/" where the obj kind is the lower-cased kind of the obj. """ - self.klabel_key = 'kubetest/deployment' + self.klabel_key = "kubetest/deployment" if self.obj.metadata.labels: self.klabel_uid = self.obj.metadata.labels.get(self.klabel_key, None) else: @@ -73,7 +73,7 @@ def _add_kubetest_labels(self) -> None: # If no spec is set, there is nothing to set additional labels on if self.obj.spec is None: - log.warning('deployment spec not set - cannot set kubetest label') + log.warning("deployment spec not set - cannot set kubetest label") return # Set the selector label @@ -109,7 +109,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating deployment "{self.name}" in namespace "{self.namespace}"') - log.debug(f'deployment: {self.obj}') + log.debug(f"deployment: {self.obj}") self.obj = self.api_client.create_namespaced_deployment( namespace=namespace, @@ -133,8 +133,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting deployment "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'deployment: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"deployment: {self.obj}") return self.api_client.delete_namespaced_deployment( name=self.name, @@ -197,9 +197,9 @@ def get_pods(self) -> List[Pod]: pods = client.CoreV1Api().list_namespaced_pod( namespace=self.namespace, - label_selector=selector_string({self.klabel_key: self.klabel_uid}) + label_selector=selector_string({self.klabel_key: self.klabel_uid}), ) pods = [Pod(p) for p in pods.items] - log.debug(f'pods: {pods}') + log.debug(f"pods: {pods}") return pods diff --git a/kubetest/objects/endpoints.py b/kubetest/objects/endpoints.py index f8c050b..d0fcbf1 100644 --- a/kubetest/objects/endpoints.py +++ b/kubetest/objects/endpoints.py @@ -6,7 +6,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Endpoints(ApiObject): @@ -25,8 +25,8 @@ class Endpoints(ApiObject): obj_type = client.V1Endpoints api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } def create(self, namespace: str = None) -> None: @@ -39,7 +39,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating endpoints "{self.name}" in namespace "{self.namespace}"') - log.debug(f'endpoints: {self.obj}') + log.debug(f"endpoints: {self.obj}") self.obj = self.api_client.create_namespaced_endpoints( namespace=namespace, @@ -63,8 +63,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting endpoints "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'endpoints: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"endpoints: {self.obj}") return self.api_client.delete_namespaced_endpoints( name=self.name, @@ -96,6 +96,9 @@ def is_ready(self) -> bool: return False for subset in self.obj.subsets: - if subset.not_ready_addresses is not None and len(subset.not_ready_addresses) > 0: + if ( + subset.not_ready_addresses is not None + and len(subset.not_ready_addresses) > 0 + ): return False return True diff --git a/kubetest/objects/event.py b/kubetest/objects/event.py index 11e6534..7ca087d 100644 --- a/kubetest/objects/event.py +++ b/kubetest/objects/event.py @@ -4,7 +4,7 @@ from kubernetes import client -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Event: @@ -25,8 +25,8 @@ class Event: obj_type = client.V1Event api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } def __init__(self, api_object) -> None: diff --git a/kubetest/objects/ingress.py b/kubetest/objects/ingress.py index 47a9449..0fb5582 100644 --- a/kubetest/objects/ingress.py +++ b/kubetest/objects/ingress.py @@ -8,7 +8,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Ingress(ApiObject): @@ -27,8 +27,8 @@ class Ingress(ApiObject): obj_type = client.ExtensionsV1beta1Api api_clients = { - 'preferred': client.ExtensionsV1beta1Api, - 'extensions/v1beta1': client.ExtensionsV1beta1Api, + "preferred": client.ExtensionsV1beta1Api, + "extensions/v1beta1": client.ExtensionsV1beta1Api, } def __str__(self): @@ -49,12 +49,8 @@ def create(self, namespace: str = None) -> None: if namespace is None: namespace = self.namespace - log.info( - 'creating ingress "%s" in namespace "%s"', - self.name, - self.namespace - ) - log.debug('ingress: %s', self.obj) + log.info('creating ingress "%s" in namespace "%s"', self.name, self.namespace) + log.debug("ingress: %s", self.obj) self.obj = self.api_client.create_namespaced_ingress( namespace=namespace, @@ -78,8 +74,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info('deleting ingress "%s"', self.name) - log.debug('delete options: %s', options) - log.debug('ingress: %s', self.obj) + log.debug("delete options: %s", options) + log.debug("ingress: %s", self.obj) return self.api_client.delete_namespaced_ingress( name=self.name, @@ -129,10 +125,7 @@ def wait_for_load_balancer_ingress(self, timeout: int = None) -> None: TimeoutError: The specified timeout was exceeded. """ wait_condition = condition.Condition( - 'Ingress has been assigned an ingress', - self.has_load_balancer_ingress) + "Ingress has been assigned an ingress", self.has_load_balancer_ingress + ) - utils.wait_for_condition( - condition=wait_condition, - timeout=timeout, - interval=1) + utils.wait_for_condition(condition=wait_condition, timeout=timeout, interval=1) diff --git a/kubetest/objects/namespace.py b/kubetest/objects/namespace.py index 4ae0370..c1dbca9 100644 --- a/kubetest/objects/namespace.py +++ b/kubetest/objects/namespace.py @@ -6,7 +6,7 @@ from kubetest.objects import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Namespace(ApiObject): @@ -25,12 +25,12 @@ class Namespace(ApiObject): obj_type = client.V1Namespace api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } @classmethod - def new(cls, name: str) -> 'Namespace': + def new(cls, name: str) -> "Namespace": """Create a new Namespace with object backing. Args: @@ -39,11 +39,7 @@ def new(cls, name: str) -> 'Namespace': Returns: A new Namespace instance. """ - return cls(client.V1Namespace( - metadata=client.V1ObjectMeta( - name=name - ) - )) + return cls(client.V1Namespace(metadata=client.V1ObjectMeta(name=name))) def create(self, name: str = None) -> None: """Create the Namespace under the given name. @@ -58,7 +54,7 @@ def create(self, name: str = None) -> None: self.name = name log.info(f'creating namespace "{self.name}"') - log.debug(f'namespace: {self.obj}') + log.debug(f"namespace: {self.obj}") self.obj = self.api_client.create_namespace( body=self.obj, @@ -77,8 +73,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting namespace "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'namespace: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"namespace: {self.obj}") return self.api_client.delete_namespace( name=self.name, @@ -103,4 +99,4 @@ def is_ready(self) -> bool: if status is None: return False - return status.phase.lower() == 'active' + return status.phase.lower() == "active" diff --git a/kubetest/objects/networkpolicy.py b/kubetest/objects/networkpolicy.py index c4a0f9a..1e9bb6f 100644 --- a/kubetest/objects/networkpolicy.py +++ b/kubetest/objects/networkpolicy.py @@ -6,7 +6,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class NetworkPolicy(ApiObject): @@ -25,8 +25,8 @@ class NetworkPolicy(ApiObject): obj_type = client.V1NetworkPolicy api_clients = { - 'preffered': client.NetworkingV1Api, - 'networking.k8s.io/v1': client.NetworkingV1Api, + "preffered": client.NetworkingV1Api, + "networking.k8s.io/v1": client.NetworkingV1Api, } def __str__(self): @@ -48,11 +48,9 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info( - 'creating networkpolicy "%s" in namespace "%s"', - self.name, - self.namespace + 'creating networkpolicy "%s" in namespace "%s"', self.name, self.namespace ) - log.debug('network_policy: %s', self.obj) + log.debug("network_policy: %s", self.obj) self.obj = self.api_client.create_namespaced_network_policy( namespace=namespace, @@ -76,8 +74,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info('deleting network_policy "%s"', self.name) - log.debug('delete options: %s', options) - log.debug('network_policy: %s', self.obj) + log.debug("delete options: %s", options) + log.debug("network_policy: %s", self.obj) return self.api_client.delete_namespaced_network_policy( name=self.name, diff --git a/kubetest/objects/node.py b/kubetest/objects/node.py index 3d50138..c5f6e3d 100644 --- a/kubetest/objects/node.py +++ b/kubetest/objects/node.py @@ -4,7 +4,7 @@ from kubernetes import client -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Node: @@ -36,7 +36,7 @@ def refresh(self) -> None: if node.metadata.name == self.name: self.obj = node return - log.warning(f'unable to refresh node: no node found with name: {self.name}') + log.warning(f"unable to refresh node: no node found with name: {self.name}") def status(self) -> client.V1NodeStatus: """Get the status of the Node. @@ -66,11 +66,11 @@ def is_ready(self) -> bool: for cond in status.conditions: # we only care about the 'ready' condition - if cond.type.lower() != 'ready': + if cond.type.lower() != "ready": continue # check that the readiness condition is true - return cond.status.lower() == 'true' + return cond.status.lower() == "true" # Catchall return False diff --git a/kubetest/objects/persistentvolumeclaim.py b/kubetest/objects/persistentvolumeclaim.py index f860102..6c37db2 100644 --- a/kubetest/objects/persistentvolumeclaim.py +++ b/kubetest/objects/persistentvolumeclaim.py @@ -6,7 +6,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class PersistentVolumeClaim(ApiObject): @@ -25,8 +25,8 @@ class PersistentVolumeClaim(ApiObject): obj_type = client.V1PersistentVolumeClaim api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } def __str__(self): @@ -50,9 +50,9 @@ def create(self, namespace: str = None) -> None: log.info( 'creating persistentvolumeclaim "%s" in namespace "%s"', self.name, - self.namespace + self.namespace, ) - log.debug('persistentvolumeclaim: %s', self.obj) + log.debug("persistentvolumeclaim: %s", self.obj) self.obj = self.api_client.create_namespaced_persistent_volume_claim( namespace=namespace, @@ -76,8 +76,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info('deleting persistentvolumeclaim "%s"', self.name) - log.debug('delete options: %s', options) - log.debug('persistentvolumeclaim: %s', self.obj) + log.debug("delete options: %s", options) + log.debug("persistentvolumeclaim: %s", self.obj) return self.api_client.delete_namespaced_persistent_volume_claim( name=self.name, diff --git a/kubetest/objects/pod.py b/kubetest/objects/pod.py index 7108ad3..34c68c8 100644 --- a/kubetest/objects/pod.py +++ b/kubetest/objects/pod.py @@ -11,7 +11,7 @@ from .api_object import ApiObject from .container import Container -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Pod(ApiObject): @@ -30,8 +30,8 @@ class Pod(ApiObject): obj_type = client.V1Pod api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } def create(self, namespace: str = None) -> None: @@ -47,7 +47,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating pod "{self.name}" in namespace "{self.namespace}"') - log.debug(f'pod: {self.obj}') + log.debug(f"pod: {self.obj}") self.obj = self.api_client.create_namespaced_pod( namespace=namespace, @@ -71,8 +71,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting pod "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'pod: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"pod: {self.obj}") return self.api_client.delete_namespaced_pod( name=self.name, @@ -104,16 +104,16 @@ def is_ready(self) -> bool: # the 'failed' or 'success' state will no longer be running, # so we only care if the pod is in the 'running' state. phase = status.phase - if phase.lower() != 'running': + if phase.lower() != "running": return False for cond in status.conditions: # we only care about the condition type 'ready' - if cond.type.lower() != 'ready': + if cond.type.lower() != "ready": continue # check that the readiness condition is True - return cond.status.lower() == 'true' + return cond.status.lower() == "true" # Catchall return False @@ -172,7 +172,9 @@ def get_restart_count(self) -> int: return total - def http_proxy_get(self, path: str, query_params: Dict[str, str] = None) -> response.Response: + def http_proxy_get( + self, path: str, query_params: Dict[str, str] = None + ) -> response.Response: """Issue a GET request to a proxy for the Pod. Notes: @@ -196,32 +198,32 @@ def http_proxy_get(self, path: str, query_params: Dict[str, str] = None) -> resp if query_params is None: query_params = {} - path_params = { - 'name': self.name, - 'namespace': self.namespace - } + path_params = {"name": self.name, "namespace": self.namespace} header_params = { - 'Accept': c.api_client.select_header_accept(['*/*']), - 'Content-Type': c.api_client.select_header_content_type(['*/*']) + "Accept": c.api_client.select_header_accept(["*/*"]), + "Content-Type": c.api_client.select_header_content_type(["*/*"]), } - auth_settings = ['BearerToken'] + auth_settings = ["BearerToken"] try: - resp = response.Response(*c.api_client.call_api( - '/api/v1/namespaces/{namespace}/pods/{name}/proxy/' + path, 'GET', - path_params=path_params, - query_params=query_params, - header_params=header_params, - body=None, - post_params=[], - files={}, - response_type='str', - auth_settings=auth_settings, - _return_http_data_only=False, # we want all info, not just data - _preload_content=True, - _request_timeout=None, - collection_formats={} - )) + resp = response.Response( + *c.api_client.call_api( + "/api/v1/namespaces/{namespace}/pods/{name}/proxy/" + path, + "GET", + path_params=path_params, + query_params=query_params, + header_params=header_params, + body=None, + post_params=[], + files={}, + response_type="str", + auth_settings=auth_settings, + _return_http_data_only=False, # we want all info, not just data + _preload_content=True, + _request_timeout=None, + collection_formats={}, + ) + ) except ApiException as e: # if the ApiException does not have a body or headers, that # means the raised exception did not get a response (even if @@ -240,7 +242,10 @@ def http_proxy_get(self, path: str, query_params: Dict[str, str] = None) -> resp return resp def http_proxy_post( - self, path: str, query_params: Dict[str, str] = None, data=None, + self, + path: str, + query_params: Dict[str, str] = None, + data=None, ) -> response.Response: """Issue a POST request to a proxy for the Pod. @@ -266,32 +271,32 @@ def http_proxy_post( if query_params is None: query_params = {} - path_params = { - 'name': self.name, - 'namespace': self.namespace - } + path_params = {"name": self.name, "namespace": self.namespace} header_params = { - 'Accept': c.api_client.select_header_accept(['*/*']), - 'Content-Type': c.api_client.select_header_content_type(['*/*']) + "Accept": c.api_client.select_header_accept(["*/*"]), + "Content-Type": c.api_client.select_header_content_type(["*/*"]), } - auth_settings = ['BearerToken'] + auth_settings = ["BearerToken"] try: - resp = response.Response(*c.api_client.call_api( - '/api/v1/namespaces/{namespace}/pods/{name}/proxy/' + path, 'POST', - path_params=path_params, - query_params=query_params, - header_params=header_params, - body=data, - post_params=[], - files={}, - response_type='str', - auth_settings=auth_settings, - _return_http_data_only=False, # we want all info, not just data - _preload_content=True, - _request_timeout=None, - collection_formats={} - )) + resp = response.Response( + *c.api_client.call_api( + "/api/v1/namespaces/{namespace}/pods/{name}/proxy/" + path, + "POST", + path_params=path_params, + query_params=query_params, + header_params=header_params, + body=data, + post_params=[], + files={}, + response_type="str", + auth_settings=auth_settings, + _return_http_data_only=False, # we want all info, not just data + _preload_content=True, + _request_timeout=None, + collection_formats={}, + ) + ) except ApiException as e: # if the ApiException does not have a body or headers, that # means the raised exception did not get a response (even if @@ -354,7 +359,7 @@ def wait_until_containers_start(self, timeout: int = None) -> None: TimeoutError: The specified timeout was exceeded. """ wait_condition = condition.Condition( - 'all pod containers started', + "all pod containers started", self.containers_started, ) diff --git a/kubetest/objects/replicaset.py b/kubetest/objects/replicaset.py index 3510b7c..95298a3 100644 --- a/kubetest/objects/replicaset.py +++ b/kubetest/objects/replicaset.py @@ -11,7 +11,7 @@ from .api_object import ApiObject from .pod import Pod -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class ReplicaSet(ApiObject): @@ -30,9 +30,9 @@ class ReplicaSet(ApiObject): obj_type = client.V1ReplicaSet api_clients = { - 'preferred': client.AppsV1Api, - 'apps/v1': client.AppsV1Api, - 'apps/v1beta2': client.AppsV1beta2Api, + "preferred": client.AppsV1Api, + "apps/v1": client.AppsV1Api, + "apps/v1beta2": client.AppsV1beta2Api, } def __init__(self, *args, **kwargs) -> None: @@ -50,7 +50,7 @@ def _add_kubetest_labels(self) -> None: The kubetest label key is "kubetest/" where the obj kind is the lower-cased kind of the obj. """ - self.klabel_key = 'kubetest/replicaset' + self.klabel_key = "kubetest/replicaset" if self.obj.metadata.labels: self.klabel_uid = self.obj.metadata.labels.get(self.klabel_key, None) else: @@ -74,7 +74,7 @@ def _add_kubetest_labels(self) -> None: # If no spec is set, there is nothing to set additional labels on if self.obj.spec is None: - log.warning('replicaset spec not set - cannot set kubetest label') + log.warning("replicaset spec not set - cannot set kubetest label") return # Set the selector label @@ -110,7 +110,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating replicaset "{self.name}" in namespace "{self.namespace}"') - log.debug(f'replicaset: {self.obj}') + log.debug(f"replicaset: {self.obj}") self.obj = self.api_client.create_namespaced_replica_set( namespace=namespace, @@ -134,8 +134,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting replicaset "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'replicaset: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"replicaset: {self.obj}") return self.api_client.delete_namespaced_replica_set( name=self.name, @@ -198,9 +198,9 @@ def get_pods(self) -> List[Pod]: pods = client.CoreV1Api().list_namespaced_pod( namespace=self.namespace, - label_selector=selector_string({self.klabel_key: self.klabel_uid}) + label_selector=selector_string({self.klabel_key: self.klabel_uid}), ) pods = [Pod(p) for p in pods.items] - log.debug(f'pods: {pods}') + log.debug(f"pods: {pods}") return pods diff --git a/kubetest/objects/rolebinding.py b/kubetest/objects/rolebinding.py index ccb0c3e..e819ea4 100644 --- a/kubetest/objects/rolebinding.py +++ b/kubetest/objects/rolebinding.py @@ -6,7 +6,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class RoleBinding(ApiObject): @@ -25,10 +25,10 @@ class RoleBinding(ApiObject): obj_type = client.V1RoleBinding api_clients = { - 'preferred': client.RbacAuthorizationV1Api, - 'rbac.authorization.k8s.io/v1': client.RbacAuthorizationV1Api, - 'rbac.authorization.k8s.io/v1alpha1': client.RbacAuthorizationV1alpha1Api, - 'rbac.authorization.k8s.io/v1beta1': client.RbacAuthorizationV1beta1Api, + "preferred": client.RbacAuthorizationV1Api, + "rbac.authorization.k8s.io/v1": client.RbacAuthorizationV1Api, + "rbac.authorization.k8s.io/v1alpha1": client.RbacAuthorizationV1alpha1Api, + "rbac.authorization.k8s.io/v1beta1": client.RbacAuthorizationV1beta1Api, } def create(self, namespace: str = None) -> None: @@ -43,8 +43,10 @@ def create(self, namespace: str = None) -> None: if namespace is None: namespace = self.namespace - log.info(f'creating rolebinding "{self.name}" in namespace "{self.namespace}"') # noqa - log.debug(f'rolebinding: {self.obj}') + log.info( + f'creating rolebinding "{self.name}" in namespace "{self.namespace}"' + ) # noqa + log.debug(f"rolebinding: {self.obj}") self.obj = self.api_client.create_namespaced_role_binding( namespace=namespace, @@ -68,8 +70,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting rolebinding "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'rolebinding: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"rolebinding: {self.obj}") return self.api_client.delete_namespaced_role_binding( namespace=self.namespace, diff --git a/kubetest/objects/secret.py b/kubetest/objects/secret.py index 3a54758..283d586 100644 --- a/kubetest/objects/secret.py +++ b/kubetest/objects/secret.py @@ -6,7 +6,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Secret(ApiObject): @@ -25,8 +25,8 @@ class Secret(ApiObject): obj_type = client.V1Secret api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } def create(self, namespace: str = None) -> None: @@ -42,7 +42,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating secret "{self.name}" in namespace "{self.namespace}"') - log.debug(f'secret: {self.obj}') + log.debug(f"secret: {self.obj}") self.obj = self.api_client.create_namespaced_secret( namespace=namespace, @@ -66,8 +66,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting secret "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'secret: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"secret: {self.obj}") return self.api_client.delete_namespaced_secret( name=self.name, diff --git a/kubetest/objects/service.py b/kubetest/objects/service.py index ec15b9e..69cf667 100644 --- a/kubetest/objects/service.py +++ b/kubetest/objects/service.py @@ -7,7 +7,7 @@ from .api_object import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Service(ApiObject): @@ -26,8 +26,8 @@ class Service(ApiObject): obj_type = client.V1Service api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } def create(self, namespace: str = None) -> None: @@ -43,7 +43,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating service "{self.name}" in namespace "{self.namespace}"') - log.debug(f'service: {self.obj}') + log.debug(f"service: {self.obj}") self.obj = self.api_client.create_namespaced_service( namespace=namespace, @@ -67,8 +67,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting service "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'service: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"service: {self.obj}") return self.api_client.delete_namespaced_service( name=self.name, @@ -167,7 +167,7 @@ def get_endpoints(self) -> List[client.V1Endpoints]: if endpoint.metadata.name == self.name: svc_endpoints.append(endpoint) - log.debug(f'endpoints: {svc_endpoints}') + log.debug(f"endpoints: {svc_endpoints}") return svc_endpoints def _proxy_http_request(self, method, path, **kwargs) -> tuple: @@ -182,15 +182,15 @@ def _proxy_http_request(self, method, path, **kwargs) -> tuple: The response data """ path_params = { - "name": f'{self.name}:{self.obj.spec.ports[0].port}', + "name": f"{self.name}:{self.obj.spec.ports[0].port}", "namespace": self.namespace, - "path": path + "path": path, } return client.CoreV1Api().api_client.call_api( - '/api/v1/namespaces/{namespace}/services/{name}/proxy/{path}', + "/api/v1/namespaces/{namespace}/services/{name}/proxy/{path}", method, path_params=path_params, - **kwargs + **kwargs, ) def proxy_http_get(self, path: str, **kwargs) -> tuple: @@ -203,7 +203,7 @@ def proxy_http_get(self, path: str, **kwargs) -> tuple: Returns: The response data """ - return self._proxy_http_request('GET', path, **kwargs) + return self._proxy_http_request("GET", path, **kwargs) def proxy_http_post(self, path: str, **kwargs) -> tuple: """Issue a POST request to proxy of a Service. @@ -215,4 +215,4 @@ def proxy_http_post(self, path: str, **kwargs) -> tuple: Returns: The response data """ - return self._proxy_http_request('POST', path, **kwargs) + return self._proxy_http_request("POST", path, **kwargs) diff --git a/kubetest/objects/serviceaccount.py b/kubetest/objects/serviceaccount.py index 878af71..daedca6 100644 --- a/kubetest/objects/serviceaccount.py +++ b/kubetest/objects/serviceaccount.py @@ -6,7 +6,7 @@ from kubetest.objects import ApiObject -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class ServiceAccount(ApiObject): @@ -22,8 +22,8 @@ class ServiceAccount(ApiObject): obj_type = client.V1ServiceAccount api_clients = { - 'preferred': client.CoreV1Api, - 'v1': client.CoreV1Api, + "preferred": client.CoreV1Api, + "v1": client.CoreV1Api, } def create(self, name: str = None) -> None: @@ -38,7 +38,7 @@ def create(self, name: str = None) -> None: self.name = name log.info(f'creating serviceaccount "{self.name}"') - log.debug(f'serviceaccount: {self.obj}') + log.debug(f"serviceaccount: {self.obj}") self.obj = self.api_client.create_namespaced_service_account( body=self.obj, @@ -56,13 +56,11 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting ServiceAccount "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'service account: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"service account: {self.obj}") return self.api_client.delete_namespaced_service_account( - name=self.name, - namespace=self.namespace, - body=options + name=self.name, namespace=self.namespace, body=options ) def refresh(self) -> None: diff --git a/kubetest/objects/statefulset.py b/kubetest/objects/statefulset.py index 9b4b770..1f3ebb2 100644 --- a/kubetest/objects/statefulset.py +++ b/kubetest/objects/statefulset.py @@ -11,7 +11,7 @@ from .api_object import ApiObject from .pod import Pod -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class StatefulSet(ApiObject): @@ -30,10 +30,10 @@ class StatefulSet(ApiObject): obj_type = client.V1StatefulSet api_clients = { - 'preferred': client.AppsV1Api, - 'apps/v1': client.AppsV1Api, - 'apps/v1beta1': client.AppsV1beta1Api, - 'apps/v1beta2': client.AppsV1beta2Api, + "preferred": client.AppsV1Api, + "apps/v1": client.AppsV1Api, + "apps/v1beta1": client.AppsV1beta1Api, + "apps/v1beta2": client.AppsV1beta2Api, } def __init__(self, *args, **kwargs) -> None: @@ -49,7 +49,7 @@ def _add_kubetest_labels(self) -> None: The kubetest label key is "kubetest/" where the obj kind is the lower-cased kind of the obj. """ - self.klabel_key = 'kubetest/statefulset' + self.klabel_key = "kubetest/statefulset" if self.obj.metadata.labels: self.klabel_uid = self.obj.metadata.labels.get(self.klabel_key, None) else: @@ -73,7 +73,7 @@ def _add_kubetest_labels(self) -> None: # If no spec is set, there is nothing to set additional labels on if self.obj.spec is None: - log.warning('statefulset spec not set - cannot set kubetest label') + log.warning("statefulset spec not set - cannot set kubetest label") return # Set the selector label @@ -109,7 +109,7 @@ def create(self, namespace: str = None) -> None: namespace = self.namespace log.info(f'creating statefulset "{self.name}" in namespace "{self.namespace}"') - log.debug(f'statefulset: {self.obj}') + log.debug(f"statefulset: {self.obj}") self.obj = self.api_client.create_namespaced_stateful_set( namespace=namespace, @@ -133,8 +133,8 @@ def delete(self, options: client.V1DeleteOptions = None) -> client.V1Status: options = client.V1DeleteOptions() log.info(f'deleting statefulset "{self.name}"') - log.debug(f'delete options: {options}') - log.debug(f'statefulset: {self.obj}') + log.debug(f"delete options: {options}") + log.debug(f"statefulset: {self.obj}") return self.api_client.delete_namespaced_stateful_set( name=self.name, @@ -197,9 +197,9 @@ def get_pods(self) -> List[Pod]: pods = client.CoreV1Api().list_namespaced_pod( namespace=self.namespace, - label_selector=selector_string({self.klabel_key: self.klabel_uid}) + label_selector=selector_string({self.klabel_key: self.klabel_uid}), ) pods = [Pod(p) for p in pods.items] - log.debug(f'pods: {pods}') + log.debug(f"pods: {pods}") return pods diff --git a/kubetest/plugin.py b/kubetest/plugin.py index f64eab7..717fb77 100644 --- a/kubetest/plugin.py +++ b/kubetest/plugin.py @@ -18,9 +18,9 @@ from kubetest.client import TestClient from kubetest.manager import KubetestManager -GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS' +GOOGLE_APPLICATION_CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS" -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") # A global instance of the KubetestManager. This will be used by various # pytest hooks and fixtures in order to create and manage the TestClient @@ -32,6 +32,7 @@ # ********** pytest hooks ********** + def pytest_addoption(parser): """Add options to pytest to configure kubetest. @@ -39,36 +40,36 @@ def pytest_addoption(parser): https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_addoption """ - group = parser.getgroup('kubetest', 'kubernetes integration test support') + group = parser.getgroup("kubetest", "kubernetes integration test support") group.addoption( - '--kube-config', - action='store', - metavar='path', + "--kube-config", + action="store", + metavar="path", default=os.getenv("KUBECONFIG"), help=( - 'the kubernetes config for kubetest; this is required for ' - 'resources to be installed on the cluster' - ) + "the kubernetes config for kubetest; this is required for " + "resources to be installed on the cluster" + ), ) group.addoption( - '--kube-context', - action='store', - metavar='context', + "--kube-context", + action="store", + metavar="context", default=None, - help='the name of the kubernetes config context to use', + help="the name of the kubernetes config context to use", ) group.addoption( - '--kube-disable', - action='store_true', + "--kube-disable", + action="store_true", default=False, - help='[DEPRECATED] disable automatic configuration with the kubeconfig file' + help="[DEPRECATED] disable automatic configuration with the kubeconfig file", ) group.addoption( - '--in-cluster', - action='store_true', + "--in-cluster", + action="store_true", default=False, - help='use the kubernetes in-cluster config', + help="use the kubernetes in-cluster config", ) # FIXME (etd) - this was an attempt to fix occasional permissions errors @@ -83,27 +84,27 @@ def pytest_addoption(parser): # ) group.addoption( - '--kube-log-level', - action='store', - default='warning', - help='log level for the kubetest logger' + "--kube-log-level", + action="store", + default="warning", + help="log level for the kubetest logger", ) group.addoption( - '--kube-error-log-lines', - action='store', + "--kube-error-log-lines", + action="store", default=50, type=int, - help='set the number of lines to tail from container logs on error. ' - 'to show all lines, set this to -1.' + help="set the number of lines to tail from container logs on error. " + "to show all lines, set this to -1.", ) group.addoption( - '--suppress-insecure-request', - action='store', + "--suppress-insecure-request", + action="store", default=False, - help='suppress the urllib3 InsecureRequestWarning. This is useful if testing ' - 'against a cluster without HTTPS set up.' + help="suppress the urllib3 InsecureRequestWarning. This is useful if testing " + "against a cluster without HTTPS set up.", ) @@ -113,20 +114,20 @@ def pytest_report_header(config): See Also: https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_report_header """ - config_file = config.getoption('kube_config') + config_file = config.getoption("kube_config") if config_file is None: - config_file = 'default' + config_file = "default" - if config.getoption('in_cluster'): - config_file = 'in-cluster' + if config.getoption("in_cluster"): + config_file = "in-cluster" - context = config.getoption('kube_context') + context = config.getoption("kube_context") if context is None: - context = 'current context' + context = "current context" return [ - f'kubetest config file: {config_file}', - f'kubetest context: {context}', + f"kubetest config file: {config_file}", + f"kubetest context: {context}", ] @@ -142,7 +143,7 @@ def pytest_configure(config): markers.register(config) # Disable warnings, if configured to do so. - if config.getoption('suppress_insecure_request'): + if config.getoption("suppress_insecure_request"): urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) @@ -165,17 +166,17 @@ def pytest_sessionstart(session): https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_sessionstart """ # Setup the kubetest logger - log_level = session.config.getoption('kube_log_level') + log_level = session.config.getoption("kube_log_level") level = logging._nameToLevel.get(log_level.upper(), logging.WARNING) - logger = logging.getLogger('kubetest') + logger = logging.getLogger("kubetest") logger.setLevel(level) # Check for configuration deprecations - if session.config.getoption('kube_disable'): + if session.config.getoption("kube_disable"): warnings.warn( - '--kube-disable flag is deprecated (v0.2.0) and is no longer functional. ' - 'To disable the plugin for a project, see: ' - 'https://docs.pytest.org/en/latest/plugins.html', + "--kube-disable flag is deprecated (v0.2.0) and is no longer functional. " + "To disable the plugin for a project, see: " + "https://docs.pytest.org/en/latest/plugins.html", ) # # Configure kubetest with the kubernetes config, if not disabled. @@ -231,9 +232,9 @@ def pytest_runtest_setup(item): namespace_create = True namespace_name = None - for mark in item.iter_markers(name='namespace'): - namespace_create = mark.kwargs.get('create', True) - namespace_name = mark.kwargs.get('name', None) + for mark in item.iter_markers(name="namespace"): + namespace_create = mark.kwargs.get("create", True) + namespace_name = mark.kwargs.get("name", None) # Register a new test case with the manager and setup the test case state. test_case = manager.new_test( @@ -293,12 +294,14 @@ def pytest_runtest_teardown(item): # override, there will be more than one fixture associated with the 'kubeconfig' # name. In such case, we should assume that a fixture was used to load the config # and allow test cleanup to proceed. - _, _, fixtures = item.session._fixturemanager.getfixtureclosure(['kubeconfig'], item) + _, _, fixtures = item.session._fixturemanager.getfixtureclosure( + ["kubeconfig"], item + ) if ( - item.config.getoption('kube_config') or - len(fixtures.get('kubeconfig', [])) > 1 or - item.config.getoption('in_cluster') + item.config.getoption("kube_config") + or len(fixtures.get("kubeconfig", [])) > 1 + or item.config.getoption("in_cluster") ): manager.teardown(item.nodeid) @@ -313,24 +316,22 @@ def pytest_runtest_makereport(item, call): """ # skip for tests without fixtures (eg: doctests) - if not hasattr(item, 'fixturenames'): + if not hasattr(item, "fixturenames"): return - if 'kube' in item.fixturenames and call.when == 'call': - if call.excinfo is not None and call.excinfo.typename != 'Skipped': - tail_lines = item.config.getoption('kube_error_log_lines') + if "kube" in item.fixturenames and call.when == "call": + if call.excinfo is not None and call.excinfo.typename != "Skipped": + tail_lines = item.config.getoption("kube_error_log_lines") if tail_lines != 0: test_case = manager.get_test(item.nodeid) if test_case: - logs = test_case.yield_container_logs( - tail_lines=tail_lines - ) + logs = test_case.yield_container_logs(tail_lines=tail_lines) for container_log in logs: # Add a report section to the test output item.add_report_section( when=call.when, - key='kubernetes container logs', - content=container_log + key="kubernetes container logs", + content=container_log, ) @@ -348,9 +349,9 @@ def pytest_keyboard_interrupt(): name = ns.metadata.name status = ns.status if ( - name.startswith('kubetest-') and - status is not None and - status.phase.lower() == 'active' + name.startswith("kubetest-") + and status is not None + and status.phase.lower() == "active" ): print(f'keyboard interrupt: cleaning up namespace "{name}"') kubernetes.client.CoreV1Api().delete_namespace( @@ -362,7 +363,7 @@ def pytest_keyboard_interrupt(): for crb in crbs.items: # if the cluster role binding has a 'kubetest:' prefix, remove it. name = crb.metadata.name - if name.startswith('kubetest:'): + if name.startswith("kubetest:"): print(f'keyboard interrupt: cleaning up clusterrolebinding "{crb}"') kubernetes.client.RbacAuthorizationV1Api().delete_cluster_role_binding( body=kubernetes.client.V1DeleteOptions(), @@ -370,8 +371,8 @@ def pytest_keyboard_interrupt(): ) except Exception as e: log.error( - 'Failed to clean up kubetest artifacts from cluster on keyboard interrupt. ' - 'You may need to manually remove items from your cluster. Check for ' + "Failed to clean up kubetest artifacts from cluster on keyboard interrupt. " + "You may need to manually remove items from your cluster. Check for " 'namespaces with the "kubetest-" prefix and cluster role bindings with ' f'the "kubetest:" prefix. ({e})' ) @@ -379,6 +380,7 @@ def pytest_keyboard_interrupt(): # ********** pytest fixtures ********** + class ClusterInfo: """Information about the cluster the kubetest is being run on. @@ -393,9 +395,9 @@ class ClusterInfo: """ def __init__(self, current, config): - self.cluster = current['context']['cluster'] - self.user = current['context']['user'] - self.context = current['name'] + self.cluster = current["context"]["cluster"] + self.user = current["context"]["user"] + self.context = current["name"] self.host = config.host self.verify_ssl = config.verify_ssl @@ -412,9 +414,11 @@ def clusterinfo(kubeconfig) -> ClusterInfo: # Get the current context. _, current = kubernetes.config.list_kube_config_contexts( - os.path.expandvars(os.path.expanduser( - kubeconfig, - )) + os.path.expandvars( + os.path.expanduser( + kubeconfig, + ) + ) ) return ClusterInfo( @@ -427,7 +431,7 @@ def clusterinfo(kubeconfig) -> ClusterInfo: def kubeconfig(request) -> Optional[str]: """Return the name of the configured kube config file loaded for the tests.""" - config_file = request.session.config.getoption('kube_config') + config_file = request.session.config.getoption("kube_config") return config_file @@ -438,7 +442,7 @@ def kubecontext(request) -> Optional[str]: When None, use the current context as set in the kubeconfig. """ - context = request.session.config.getoption('kube_context') + context = request.session.config.getoption("kube_context") return context @@ -446,7 +450,7 @@ def kubecontext(request) -> Optional[str]: def kube(kubeconfig, kubecontext, request) -> TestClient: """Return a client for managing a Kubernetes cluster for testing.""" - if request.session.config.getoption('in_cluster'): + if request.session.config.getoption("in_cluster"): kubernetes.config.load_incluster_config() else: if kubeconfig: @@ -456,18 +460,18 @@ def kube(kubeconfig, kubecontext, request) -> TestClient: ) else: log.error( - 'unable to interact with cluster: kube fixture used without kube config ' - 'set. the config may be set with the flags --kube-config or --in-cluster or by' - 'an env var KUBECONFIG or custom kubeconfig fixture definition.' + "unable to interact with cluster: kube fixture used without kube config " + "set. the config may be set with the flags --kube-config or --in-cluster or by" + "an env var KUBECONFIG or custom kubeconfig fixture definition." ) - raise errors.SetupError('no kube config defined for test run') + raise errors.SetupError("no kube config defined for test run") test_case = manager.get_test(request.node.nodeid) if test_case is None: log.error( f'No kubetest client found for test using the "kube" fixture. ({request.node.nodeid})', # noqa ) - raise errors.SetupError('error generating test client') + raise errors.SetupError("error generating test client") # Setup the test case. This will create the namespace and any other # objects (e.g. role bindings) that the test case will need. diff --git a/kubetest/response.py b/kubetest/response.py index d76778b..02dcd25 100644 --- a/kubetest/response.py +++ b/kubetest/response.py @@ -7,7 +7,7 @@ import urllib3 -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") class Response: @@ -62,7 +62,7 @@ def json(self) -> Dict[str, Any]: try: data = ast.literal_eval(self.data) except Exception as e: - log.debug(f'failed literal eval of data {self.data} ({e})') + log.debug(f"failed literal eval of data {self.data} ({e})") data = json.loads(self.data) return data diff --git a/kubetest/utils.py b/kubetest/utils.py index 86db361..ecbcbf9 100644 --- a/kubetest/utils.py +++ b/kubetest/utils.py @@ -8,7 +8,7 @@ from kubetest.condition import Condition -log = logging.getLogger('kubetest') +log = logging.getLogger("kubetest") def new_namespace(test_name: str) -> str: @@ -26,11 +26,11 @@ def new_namespace(test_name: str) -> str: Returns: The namespace name. """ - prefix = 'kubetest' + prefix = "kubetest" timestamp = str(int(time.time())) - test_name = test_name.replace('_', '-').lower() - test_name = test_name.replace('[', '-') - test_name = test_name.replace(']', '-') + test_name = test_name.replace("_", "-").lower() + test_name = test_name.replace("[", "-") + test_name = test_name.replace("]", "-") # The length of a resource name in Kubernetes may not exceed 63 # characters. Check the length of all components (+2 for the dashes @@ -39,9 +39,9 @@ def new_namespace(test_name: str) -> str: name_len = len(prefix) + len(timestamp) + len(test_name) + 2 if name_len > 63: - test_name = test_name[:-(name_len-63)] + test_name = test_name[: -(name_len - 63)] - return '-'.join((prefix, test_name, timestamp)) + return "-".join((prefix, test_name, timestamp)) def selector_string(selectors: Mapping[str, str]) -> str: @@ -53,12 +53,12 @@ def selector_string(selectors: Mapping[str, str]) -> str: Returns: The selector string for the given dictionary. """ - return ','.join([f'{k}={v}' for k, v in selectors.items()]) + return ",".join([f"{k}={v}" for k, v in selectors.items()]) def selector_kwargs( - fields: Mapping[str, str] = None, - labels: Mapping[str, str] = None, + fields: Mapping[str, str] = None, + labels: Mapping[str, str] = None, ) -> Dict[str, str]: """Create a dictionary of kwargs for Kubernetes object selectors. @@ -76,18 +76,18 @@ def selector_kwargs( """ kwargs = {} if fields is not None: - kwargs['field_selector'] = selector_string(fields) + kwargs["field_selector"] = selector_string(fields) if labels is not None: - kwargs['label_selector'] = selector_string(labels) + kwargs["label_selector"] = selector_string(labels) return kwargs def wait_for_condition( - condition: Condition, - timeout: int = None, - interval: Union[int, float] = 1, - fail_on_api_error: bool = True, + condition: Condition, + timeout: int = None, + interval: Union[int, float] = 1, + fail_on_api_error: bool = True, ) -> None: """Wait for a condition to be met. @@ -106,7 +106,7 @@ def wait_for_condition( Raises: TimeoutError: The specified timeout was exceeded. """ - log.info(f'waiting for condition: {condition}') + log.info(f"waiting for condition: {condition}") # define the maximum time to wait. once this is met, we should # stop waiting. @@ -119,7 +119,7 @@ def wait_for_condition( while True: if max_time and time.time() >= max_time: raise TimeoutError( - f'timed out ({timeout}s) while waiting for condition {condition}' + f"timed out ({timeout}s) while waiting for condition {condition}" ) # check if the condition is met and break out if it is @@ -127,7 +127,7 @@ def wait_for_condition( if condition.check(): break except ApiException as e: - log.warning(f'got api exception while waiting: {e}') + log.warning(f"got api exception while waiting: {e}") if fail_on_api_error: raise @@ -136,4 +136,4 @@ def wait_for_condition( time.sleep(interval) end = time.time() - log.info(f'wait completed (total={end-start}s) {condition}') + log.info(f"wait completed (total={end-start}s) {condition}") diff --git a/regression/130/test.kubeconfig b/regression/130/test.kubeconfig index 0397556..1476ff0 100644 --- a/regression/130/test.kubeconfig +++ b/regression/130/test.kubeconfig @@ -16,4 +16,4 @@ users: - name: docker-desktop user: client-certificate-data: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - client-key-data: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000= \ No newline at end of file + client-key-data: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000= diff --git a/regression/130/test_130.py b/regression/130/test_130.py index 09e575e..80c36de 100644 --- a/regression/130/test_130.py +++ b/regression/130/test_130.py @@ -1,11 +1,11 @@ +import os import pytest -import os @pytest.fixture def kubeconfig(request): - x = os.path.join(os.path.dirname(__file__), 'test.kubeconfig') + x = os.path.join(os.path.dirname(__file__), "test.kubeconfig") return x diff --git a/regression/154/README.md b/regression/154/README.md index aac9eb2..264baca 100644 --- a/regression/154/README.md +++ b/regression/154/README.md @@ -26,7 +26,7 @@ kubetest config file: default kubetest context: current context rootdir: /Users/edaniszewski/dev/vaporio/kubetest plugins: requests-mock-1.6.0, grpc-0.7.0, asyncio-0.10.0, kubetest-0.5.0 -collected 1 item +collected 1 item test_154.py . diff --git a/regression/154/test_154.py b/regression/154/test_154.py index e08af27..e0c9184 100644 --- a/regression/154/test_154.py +++ b/regression/154/test_154.py @@ -1,10 +1,9 @@ - import pytest @pytest.fixture def kubeconfig(request): - return '~/.kube/config' + return "~/.kube/config" def test_154(kube): diff --git a/regression/156-bl/test_156.py b/regression/156-bl/test_156.py index 80fd291..552b809 100644 --- a/regression/156-bl/test_156.py +++ b/regression/156-bl/test_156.py @@ -1,5 +1,3 @@ - - def test_156_baseline(kube, clusterinfo): kube.wait_for_ready_nodes(1, timeout=3 * 60) - print(f'cluster info: {vars(clusterinfo)}') + print(f"cluster info: {vars(clusterinfo)}") diff --git a/regression/156/test_156.py b/regression/156/test_156.py index 026e304..541df6f 100644 --- a/regression/156/test_156.py +++ b/regression/156/test_156.py @@ -1,12 +1,11 @@ - import pytest @pytest.fixture def kubeconfig(request): - return '~/.kube/config' + return "~/.kube/config" def test_156(kube, clusterinfo): kube.wait_for_ready_nodes(1, timeout=3 * 60) - print(f'cluster info: {vars(clusterinfo)}') + print(f"cluster info: {vars(clusterinfo)}") diff --git a/regression/88/deployment.yaml b/regression/88/deployment.yaml index 40186b7..227f805 100644 --- a/regression/88/deployment.yaml +++ b/regression/88/deployment.yaml @@ -19,4 +19,4 @@ spec: image: nginx:1.7.9 imagePullPolicy: IfNotPresent ports: - - containerPort: 80 \ No newline at end of file + - containerPort: 80 diff --git a/regression/88/test_88.py b/regression/88/test_88.py index c39185e..692c887 100644 --- a/regression/88/test_88.py +++ b/regression/88/test_88.py @@ -1,11 +1,9 @@ - - def test_88(kube): - d = kube.load_deployment('deployment.yaml') + d = kube.load_deployment("deployment.yaml") d.create() d.wait_until_ready(timeout=30) pods = d.get_pods() print(pods) - print('hello') + print("hello") diff --git a/regression/90/README.md b/regression/90/README.md index 9b88a5b..50849f3 100644 --- a/regression/90/README.md +++ b/regression/90/README.md @@ -10,4 +10,4 @@ ended up being that it was attempting to load a ServiceAccount, which is not sup #### Expectations The test should fail, with an error message clearly noting that the ServiceAccount is not -supported. \ No newline at end of file +supported. diff --git a/regression/90/test_90.py b/regression/90/test_90.py index 4d070d1..6fca144 100644 --- a/regression/90/test_90.py +++ b/regression/90/test_90.py @@ -1,15 +1,15 @@ import pytest -@pytest.mark.rolebinding('Role', 'test-role') -@pytest.mark.applymanifests('.', files=['service-account.yaml']) +@pytest.mark.rolebinding("Role", "test-role") +@pytest.mark.applymanifests(".", files=["service-account.yaml"]) def test_deployment(kube): """""" - d = kube.load_deployment('deployment.yaml') + d = kube.load_deployment("deployment.yaml") d.create() d.wait_until_ready(timeout=30) pods = d.get_pods() print(pods) - print('hello') + print("hello") diff --git a/setup.py b/setup.py index 842443e..098e014 100644 --- a/setup.py +++ b/setup.py @@ -1,59 +1,54 @@ """Setup and packaging for kubetest.""" import os - from codecs import open # for consistent encoding -from setuptools import setup, find_packages +from setuptools import find_packages, setup here = os.path.abspath(os.path.dirname(__file__)) # Load the package's __init__.py file as a dictionary. pkg = {} -with open(os.path.join(here, 'kubetest', '__init__.py'), 'r', 'utf-8') as f: +with open(os.path.join(here, "kubetest", "__init__.py"), "r", "utf-8") as f: exec(f.read(), pkg) # Load the README -readme = '' -if os.path.exists('README.md'): - with open('README.md', 'r', 'utf-8') as f: +readme = "" +if os.path.exists("README.md"): + with open("README.md", "r", "utf-8") as f: readme = f.read() setup( - name=pkg['__title__'], - version=pkg['__version__'], - description=pkg['__description__'], + name=pkg["__title__"], + version=pkg["__version__"], + description=pkg["__description__"], long_description=readme, - long_description_content_type='text/markdown', - url=pkg['__url__'], - author=pkg['__author__'], - author_email=pkg['__author_email__'], - license=pkg['__license__'], + long_description_content_type="text/markdown", + url=pkg["__url__"], + author=pkg["__author__"], + author_email=pkg["__author_email__"], + license=pkg["__license__"], packages=find_packages(), - python_requires='>=3.6', + python_requires=">=3.6", package_data={ - '': ['LICENSE'], + "": ["LICENSE"], }, install_requires=[ - 'kubernetes', - 'pyyaml>=4.2b1', - 'pytest', + "kubernetes", + "pyyaml>=4.2b1", + "pytest", ], zip_safe=False, classifiers=[ - 'Environment :: Plugins', - 'Framework :: Pytest', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', + "Environment :: Plugins", + "Framework :: Pytest", + "Intended Audience :: Developers", + "Natural Language :: English", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", ], # this make a plugin available to pytest # https://docs.pytest.org/en/latest/writing_plugins.html#making-your-plugin-installable-by-others - entry_points={ - 'pytest11': [ - 'kubetest = kubetest.plugin' - ] - } + entry_points={"pytest11": ["kubetest = kubetest.plugin"]}, ) diff --git a/tests/conftest.py b/tests/conftest.py index 2a8975a..b5ca69f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,49 +9,32 @@ @pytest.fixture() def manifest_dir(): """Get the path to the test manifest directory.""" - return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data') + return os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") @pytest.fixture() def simple_deployment(): """Return the Kubernetes config matching the simple-deployment.yaml manifest.""" return client.V1Deployment( - api_version='apps/v1', - kind='Deployment', - metadata=client.V1ObjectMeta( - name='nginx-deployment', - labels={ - 'app': 'nginx' - } - ), + api_version="apps/v1", + kind="Deployment", + metadata=client.V1ObjectMeta(name="nginx-deployment", labels={"app": "nginx"}), spec=client.V1DeploymentSpec( replicas=3, - selector=client.V1LabelSelector( - match_labels={ - 'app': 'nginx' - } - ), + selector=client.V1LabelSelector(match_labels={"app": "nginx"}), template=client.V1PodTemplateSpec( - metadata=client.V1ObjectMeta( - labels={ - 'app': 'nginx' - } - ), + metadata=client.V1ObjectMeta(labels={"app": "nginx"}), spec=client.V1PodSpec( containers=[ client.V1Container( - name='nginx', - image='nginx:1.7.9', - ports=[ - client.V1ContainerPort( - container_port=80 - ) - ] + name="nginx", + image="nginx:1.7.9", + ports=[client.V1ContainerPort(container_port=80)], ) ] - ) - ) - ) + ), + ), + ), ) @@ -59,43 +42,28 @@ def simple_deployment(): def simple_statefulset(): """Return the Kubernetes config matching the simple-statefulset.yaml manifest.""" return client.V1StatefulSet( - api_version='apps/v1', - kind='StatefulSet', + api_version="apps/v1", + kind="StatefulSet", metadata=client.V1ObjectMeta( - name='postgres-statefulset', - labels={ - 'app': 'postgres' - } + name="postgres-statefulset", labels={"app": "postgres"} ), spec=client.V1StatefulSetSpec( replicas=3, - selector=client.V1LabelSelector( - match_labels={ - 'app': 'postgres' - } - ), - service_name='simple-service', + selector=client.V1LabelSelector(match_labels={"app": "postgres"}), + service_name="simple-service", template=client.V1PodTemplateSpec( - metadata=client.V1ObjectMeta( - labels={ - 'app': 'postgres' - } - ), + metadata=client.V1ObjectMeta(labels={"app": "postgres"}), spec=client.V1PodSpec( containers=[ client.V1Container( - name='postgres', - image='postgres:9.6', - ports=[ - client.V1ContainerPort( - container_port=5432 - ) - ] + name="postgres", + image="postgres:9.6", + ports=[client.V1ContainerPort(container_port=5432)], ) ] - ) - ) - ) + ), + ), + ), ) @@ -103,41 +71,24 @@ def simple_statefulset(): def simple_daemonset(): """Return the Kubernetes config matching the simple-daemonset.yaml manifest.""" return client.V1DaemonSet( - api_version='apps/v1', - kind='DaemonSet', - metadata=client.V1ObjectMeta( - name='canal-daemonset', - labels={ - 'app': 'canal' - } - ), + api_version="apps/v1", + kind="DaemonSet", + metadata=client.V1ObjectMeta(name="canal-daemonset", labels={"app": "canal"}), spec=client.V1DaemonSetSpec( - selector=client.V1LabelSelector( - match_labels={ - 'app': 'canal' - } - ), + selector=client.V1LabelSelector(match_labels={"app": "canal"}), template=client.V1PodTemplateSpec( - metadata=client.V1ObjectMeta( - labels={ - 'app': 'canal' - } - ), + metadata=client.V1ObjectMeta(labels={"app": "canal"}), spec=client.V1PodSpec( containers=[ client.V1Container( - name='canal', - image='canal:3.7.2', - ports=[ - client.V1ContainerPort( - container_port=9099 - ) - ] + name="canal", + image="canal:3.7.2", + ports=[client.V1ContainerPort(container_port=9099)], ) ] - ) - ) - ) + ), + ), + ), ) @@ -145,23 +96,13 @@ def simple_daemonset(): def simple_service(): """Return the Kubernetes config matching the simple-service.yaml manifest.""" return client.V1Service( - api_version='v1', - kind='Service', - metadata=client.V1ObjectMeta( - name='my-service' - ), + api_version="v1", + kind="Service", + metadata=client.V1ObjectMeta(name="my-service"), spec=client.V1ServiceSpec( - selector={ - 'app': 'MyApp' - }, - ports=[ - client.V1ServicePort( - protocol='TCP', - port=80, - target_port=9376 - ) - ] - ) + selector={"app": "MyApp"}, + ports=[client.V1ServicePort(protocol="TCP", port=80, target_port=9376)], + ), ) @@ -169,21 +110,13 @@ def simple_service(): def simple_persistentvolumeclaim(): """Return the Kubernetes config matching the simple-persistentvolumeclaim.yaml manifest.""" return client.V1PersistentVolumeClaim( - api_version='v1', - kind='PersistentVolumeClaim', - metadata=client.V1ObjectMeta( - name='my-pvc' - ), + api_version="v1", + kind="PersistentVolumeClaim", + metadata=client.V1ObjectMeta(name="my-pvc"), spec=client.V1PersistentVolumeClaimSpec( - access_modes=[ - 'ReadWriteMany' - ], - resources=client.V1ResourceRequirements( - requests={ - 'storage': '16Mi' - } - ) - ) + access_modes=["ReadWriteMany"], + resources=client.V1ResourceRequirements(requests={"storage": "16Mi"}), + ), ) @@ -191,24 +124,25 @@ def simple_persistentvolumeclaim(): def simple_ingress(): """Return the Kubernetes config matching the simple-ingress.yaml manifest.""" return client.ExtensionsV1beta1Ingress( - api_version='extensions/v1beta1', - kind='Ingress', - metadata=client.V1ObjectMeta( - name='my-ingress' - ), + api_version="extensions/v1beta1", + kind="Ingress", + metadata=client.V1ObjectMeta(name="my-ingress"), spec=client.ExtensionsV1beta1IngressSpec( - rules=[client.ExtensionsV1beta1IngressRule( - http=client.ExtensionsV1beta1HTTPIngressRuleValue( - paths=[client.ExtensionsV1beta1HTTPIngressPath( - backend=client.ExtensionsV1beta1IngressBackend( - service_name='my-service', - service_port=80 - ), - path='/' - )] + rules=[ + client.ExtensionsV1beta1IngressRule( + http=client.ExtensionsV1beta1HTTPIngressRuleValue( + paths=[ + client.ExtensionsV1beta1HTTPIngressPath( + backend=client.ExtensionsV1beta1IngressBackend( + service_name="my-service", service_port=80 + ), + path="/", + ) + ] + ) ) - )], - ) + ], + ), ) @@ -216,33 +150,33 @@ def simple_ingress(): def simple_replicaset(): """Return the Kubernetes config matching the simple-replicaset.yaml manifest.""" return client.V1ReplicaSet( - api_version='apps/v1', - kind='ReplicaSet', + api_version="apps/v1", + kind="ReplicaSet", metadata=client.V1ObjectMeta( - name='frontend', + name="frontend", labels={ - 'app': 'guestbook', - 'tier': 'frontend', + "app": "guestbook", + "tier": "frontend", }, ), spec=client.V1ReplicaSetSpec( replicas=3, selector=client.V1LabelSelector( match_labels={ - 'tier': 'frontend', + "tier": "frontend", }, ), template=client.V1PodTemplateSpec( metadata=client.V1ObjectMeta( labels={ - 'tier': 'frontend', + "tier": "frontend", }, ), spec=client.V1PodSpec( containers=[ client.V1Container( - name='php-redis', - image='gcr.io/google_samples/gb-frontend:v3', + name="php-redis", + image="gcr.io/google_samples/gb-frontend:v3", ), ], ), @@ -255,11 +189,9 @@ def simple_replicaset(): def simple_serviceaccount(): """Return the Kubernetes config matching the simple-serviceaccount.yaml manifest.""" return client.V1ServiceAccount( - api_version='v1', - kind='ServiceAccount', - metadata=client.V1ObjectMeta( - name='build-robot' - ) + api_version="v1", + kind="ServiceAccount", + metadata=client.V1ObjectMeta(name="build-robot"), ) @@ -267,13 +199,11 @@ def simple_serviceaccount(): def simple_networkpolicy(): """Return the Kubernetes config matching the simple-networkpolicy.yaml manifest.""" return client.V1NetworkPolicy( - api_version='networking.k8s.io/v1', - kind='NetworkPolicy', - metadata=client.V1ObjectMeta( - name='default-deny' - ), + api_version="networking.k8s.io/v1", + kind="NetworkPolicy", + metadata=client.V1ObjectMeta(name="default-deny"), spec=client.V1NetworkPolicySpec( pod_selector=client.V1LabelSelector(), policy_types=["Egress", "Ingress"], - ) + ), ) diff --git a/tests/data/invalid.yaml b/tests/data/invalid.yaml index 66f84d8..d42d0ce 100644 --- a/tests/data/invalid.yaml +++ b/tests/data/invalid.yaml @@ -2,4 +2,4 @@ invalid:: foo bar: bar.a - - baz \ No newline at end of file + - baz diff --git a/tests/data/manifests/deployment.yaml b/tests/data/manifests/deployment.yaml index e60206c..6be0f97 100644 --- a/tests/data/manifests/deployment.yaml +++ b/tests/data/manifests/deployment.yaml @@ -18,4 +18,4 @@ spec: - name: nginx image: nginx:1.7.9 ports: - - containerPort: 80 \ No newline at end of file + - containerPort: 80 diff --git a/tests/data/manifests/no-ext b/tests/data/manifests/no-ext index f131df7..41f316c 100644 --- a/tests/data/manifests/no-ext +++ b/tests/data/manifests/no-ext @@ -1 +1 @@ -a file with no extension \ No newline at end of file +a file with no extension diff --git a/tests/data/manifests/not-a-manifest.txt b/tests/data/manifests/not-a-manifest.txt index 4a38bda..69340f2 100644 --- a/tests/data/manifests/not-a-manifest.txt +++ b/tests/data/manifests/not-a-manifest.txt @@ -1 +1 @@ -placeholder file for something that is not a manifest yaml \ No newline at end of file +placeholder file for something that is not a manifest yaml diff --git a/tests/data/manifests/service.yml b/tests/data/manifests/service.yml index d59103b..aac8417 100644 --- a/tests/data/manifests/service.yml +++ b/tests/data/manifests/service.yml @@ -8,4 +8,4 @@ spec: ports: - protocol: TCP port: 80 - targetPort: 9376 \ No newline at end of file + targetPort: 9376 diff --git a/tests/data/simple-daemonset.yaml b/tests/data/simple-daemonset.yaml index c1fffcd..2cc865f 100644 --- a/tests/data/simple-daemonset.yaml +++ b/tests/data/simple-daemonset.yaml @@ -17,4 +17,4 @@ spec: - name: canal image: canal:3.7.2 ports: - - containerPort: 9099 \ No newline at end of file + - containerPort: 9099 diff --git a/tests/data/simple-deployment.yaml b/tests/data/simple-deployment.yaml index d59c26c..1caca91 100644 --- a/tests/data/simple-deployment.yaml +++ b/tests/data/simple-deployment.yaml @@ -19,4 +19,4 @@ spec: - name: nginx image: nginx:1.7.9 ports: - - containerPort: 80 \ No newline at end of file + - containerPort: 80 diff --git a/tests/data/simple-ingress.yaml b/tests/data/simple-ingress.yaml index de2217d..ea5f2a1 100644 --- a/tests/data/simple-ingress.yaml +++ b/tests/data/simple-ingress.yaml @@ -10,4 +10,4 @@ spec: - path: / backend: serviceName: my-service - servicePort: 80 \ No newline at end of file + servicePort: 80 diff --git a/tests/data/simple-replicaset.yaml b/tests/data/simple-replicaset.yaml index 3ffcd9d..47ebdd5 100644 --- a/tests/data/simple-replicaset.yaml +++ b/tests/data/simple-replicaset.yaml @@ -20,4 +20,4 @@ spec: spec: containers: - name: php-redis - image: gcr.io/google_samples/gb-frontend:v3 \ No newline at end of file + image: gcr.io/google_samples/gb-frontend:v3 diff --git a/tests/data/simple-service.yaml b/tests/data/simple-service.yaml index bbd5cbc..da5bdeb 100644 --- a/tests/data/simple-service.yaml +++ b/tests/data/simple-service.yaml @@ -9,4 +9,4 @@ spec: ports: - protocol: TCP port: 80 - targetPort: 9376 \ No newline at end of file + targetPort: 9376 diff --git a/tests/data/simple-serviceaccount.yaml b/tests/data/simple-serviceaccount.yaml index 786fd8a..be75ac6 100644 --- a/tests/data/simple-serviceaccount.yaml +++ b/tests/data/simple-serviceaccount.yaml @@ -2,4 +2,4 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: build-robot \ No newline at end of file + name: build-robot diff --git a/tests/data/simple-statefulset.yaml b/tests/data/simple-statefulset.yaml index 973f8fa..ca6854f 100644 --- a/tests/data/simple-statefulset.yaml +++ b/tests/data/simple-statefulset.yaml @@ -19,4 +19,4 @@ spec: - name: postgres image: postgres:9.6 ports: - - containerPort: 5432 \ No newline at end of file + - containerPort: 5432 diff --git a/tests/objects/test_api_object.py b/tests/objects/test_api_object.py index 1b73a1a..08f4df5 100644 --- a/tests/objects/test_api_object.py +++ b/tests/objects/test_api_object.py @@ -8,18 +8,17 @@ class TestApiObject: - def test_load_obj_from_manifest_one_definition(self, manifest_dir): """Load an object from a manifest file which only defines the definition for the object. """ obj = Deployment.load( - os.path.join(manifest_dir, 'simple-deployment.yaml'), + os.path.join(manifest_dir, "simple-deployment.yaml"), ) assert isinstance(obj, Deployment) - assert obj.name == 'nginx-deployment' + assert obj.name == "nginx-deployment" def test_load_obj_from_manifest_many_definitions(self, manifest_dir): """Load an object from a manifest file which defines multiple objects. @@ -29,11 +28,11 @@ def test_load_obj_from_manifest_many_definitions(self, manifest_dir): """ obj = Deployment.load( - os.path.join(manifest_dir, 'multi-obj-manifest.yaml'), + os.path.join(manifest_dir, "multi-obj-manifest.yaml"), ) assert isinstance(obj, Deployment) - assert obj.name == 'kubetest-test-app' + assert obj.name == "kubetest-test-app" def test_load_obj_from_manifest_many_definitions_no_identifier(self, manifest_dir): """Load an object from a manifest file which defines multiple objects. @@ -45,10 +44,12 @@ def test_load_obj_from_manifest_many_definitions_no_identifier(self, manifest_di with pytest.raises(ValueError): Service.load( - os.path.join(manifest_dir, 'multi-obj-manifest.yaml'), + os.path.join(manifest_dir, "multi-obj-manifest.yaml"), ) - def test_load_obj_from_manifest_many_definitions_with_identifier(self, manifest_dir): + def test_load_obj_from_manifest_many_definitions_with_identifier( + self, manifest_dir + ): """Load an object from a manifest file which defines multiple objects. In this case, the manifest contains multiple object definitions for @@ -57,12 +58,12 @@ def test_load_obj_from_manifest_many_definitions_with_identifier(self, manifest_ """ obj = Service.load( - os.path.join(manifest_dir, 'multi-obj-manifest.yaml'), - name='service-b', + os.path.join(manifest_dir, "multi-obj-manifest.yaml"), + name="service-b", ) assert isinstance(obj, Service) - assert obj.name == 'service-b' + assert obj.name == "service-b" def test_load_obj_from_manifest_many_definitions_no_match(self, manifest_dir): """Load an object from a manifest file which defines multiple objects. @@ -73,7 +74,7 @@ def test_load_obj_from_manifest_many_definitions_no_match(self, manifest_dir): with pytest.raises(ValueError): ConfigMap.load( - os.path.join(manifest_dir, 'multi-obj-manifest.yaml'), + os.path.join(manifest_dir, "multi-obj-manifest.yaml"), ) def test_load_obj_from_manifest_many_definitions_name_no_match(self, manifest_dir): @@ -86,6 +87,5 @@ def test_load_obj_from_manifest_many_definitions_name_no_match(self, manifest_di with pytest.raises(ValueError): Service.load( - os.path.join(manifest_dir, 'multi-obj-manifest.yaml'), - name='service-c' + os.path.join(manifest_dir, "multi-obj-manifest.yaml"), name="service-c" ) diff --git a/tests/test_conditions.py b/tests/test_conditions.py index 5fac62f..8f206c0 100644 --- a/tests/test_conditions.py +++ b/tests/test_conditions.py @@ -8,9 +8,9 @@ def test_condition_init_ok(): """Test initializing a Condition with no errors.""" - c = Condition('test', lambda x: x == 'foo', 'foo') + c = Condition("test", lambda x: x == "foo", "foo") assert c.fn is not None - assert c.args == ('foo',) + assert c.args == ("foo",) assert c.kwargs == {} @@ -18,61 +18,64 @@ def test_condition_init_err(): """Test initializing a Condition where the provided fn is not callable.""" with pytest.raises(ValueError): - Condition('test', 'not-callable') + Condition("test", "not-callable") @pytest.mark.parametrize( - 'fn,args,kwargs', [ + "fn,args,kwargs", + [ (lambda: True, (), {}), (lambda x: x, (True,), {}), (lambda x: x, (1,), {}), - (lambda x: x, ('xyz',), {}), - (lambda x: x, ({'xyz': 1}), {}), - (lambda x: x, (), {'x': True}), - (lambda x: x, (), {'x': 1}), - (lambda x: x, (), {'x': 'xyz'}), - (lambda x: x, (), {'x': ['xyz']}), - (lambda x: x, (), {'x': {'xyz': 1}}), - ] + (lambda x: x, ("xyz",), {}), + (lambda x: x, ({"xyz": 1}), {}), + (lambda x: x, (), {"x": True}), + (lambda x: x, (), {"x": 1}), + (lambda x: x, (), {"x": "xyz"}), + (lambda x: x, (), {"x": ["xyz"]}), + (lambda x: x, (), {"x": {"xyz": 1}}), + ], ) def test_condition_check_true(fn, args, kwargs): """Test checking when the function returns Truthy values.""" - c = Condition('test', fn, *args, **kwargs) + c = Condition("test", fn, *args, **kwargs) assert c.check() @pytest.mark.parametrize( - 'fn,args,kwargs', [ + "fn,args,kwargs", + [ (lambda: False, (), {}), (lambda x: x, (False,), {}), (lambda x: x, (0,), {}), - (lambda x: x, ('',), {}), + (lambda x: x, ("",), {}), (lambda x: x, ([],), {}), (lambda x: x, ({},), {}), - (lambda x: x, (), {'x': False}), - (lambda x: x, (), {'x': 0}), - (lambda x: x, (), {'x': ''}), - (lambda x: x, (), {'x': []}), - (lambda x: x, (), {'x': {}}), - ] + (lambda x: x, (), {"x": False}), + (lambda x: x, (), {"x": 0}), + (lambda x: x, (), {"x": ""}), + (lambda x: x, (), {"x": []}), + (lambda x: x, (), {"x": {}}), + ], ) def test_condition_check_false(fn, args, kwargs): """Test checking when the function returns Falsey values.""" - c = Condition('test', fn, *args, **kwargs) + c = Condition("test", fn, *args, **kwargs) assert not c.check() @pytest.mark.parametrize( - 'conditions,expected', [ + "conditions,expected", + [ ([], True), - ([Condition('test', lambda: True)], True), - ([Condition('test', lambda: False)], False), - ([Condition('test', lambda: True), Condition('test', lambda: True)], True), - ([Condition('test', lambda: True), Condition('test', lambda: False)], False), - ([Condition('test', lambda: False), Condition('test', lambda: False)], False), - ] + ([Condition("test", lambda: True)], True), + ([Condition("test", lambda: False)], False), + ([Condition("test", lambda: True), Condition("test", lambda: True)], True), + ([Condition("test", lambda: True), Condition("test", lambda: False)], False), + ([Condition("test", lambda: False), Condition("test", lambda: False)], False), + ], ) def test_check_all(conditions, expected): """Test checking all conditions.""" @@ -82,15 +85,16 @@ def test_check_all(conditions, expected): @pytest.mark.parametrize( - 'conditions,total_met,total_unmet', [ + "conditions,total_met,total_unmet", + [ ([], 0, 0), - ([Condition('test', lambda: True)], 1, 0), - ([Condition('test', lambda: False)], 0, 1), - ([Condition('test', lambda: True), Condition('test', lambda: True)], 2, 0), - ([Condition('test', lambda: False), Condition('test', lambda: True)], 1, 1), - ([Condition('test', lambda: True), Condition('test', lambda: False)], 1, 1), - ([Condition('test', lambda: False), Condition('test', lambda: False)], 0, 2), - ] + ([Condition("test", lambda: True)], 1, 0), + ([Condition("test", lambda: False)], 0, 1), + ([Condition("test", lambda: True), Condition("test", lambda: True)], 2, 0), + ([Condition("test", lambda: False), Condition("test", lambda: True)], 1, 1), + ([Condition("test", lambda: True), Condition("test", lambda: False)], 1, 1), + ([Condition("test", lambda: False), Condition("test", lambda: False)], 0, 2), + ], ) def test_check_and_sort(conditions, total_met, total_unmet): """Test checking and sorting conditions.""" diff --git a/tests/test_manager.py b/tests/test_manager.py index 4a4eecb..27dd17a 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -9,12 +9,12 @@ def test_manager_new_test(): m = manager.KubetestManager() assert len(m.nodes) == 0 - c = m.new_test('node-id', 'test-name', True, None) + c = m.new_test("node-id", "test-name", True, None) assert isinstance(c, manager.TestMeta) - assert 'kubetest-test-name-' in c.ns + assert "kubetest-test-name-" in c.ns assert len(m.nodes) == 1 - assert 'node-id' in m.nodes + assert "node-id" in m.nodes def test_manager_new_test_with_ns_name(): @@ -23,9 +23,9 @@ def test_manager_new_test_with_ns_name(): """ m = manager.KubetestManager() - c = m.new_test('node-id', 'test-name', True, 'my-test') + c = m.new_test("node-id", "test-name", True, "my-test") assert isinstance(c, manager.TestMeta) - assert c.ns == 'my-test' + assert c.ns == "my-test" assert c.namespace_create is True @@ -35,7 +35,7 @@ def test_manager_new_test_without_ns(): """ m = manager.KubetestManager() - c = m.new_test('node-id', 'test-name', False, None) + c = m.new_test("node-id", "test-name", False, None) assert isinstance(c, manager.TestMeta) assert c.namespace_create is False @@ -44,12 +44,12 @@ def test_manager_get_test(): """Test getting an existing TestMeta from the manager.""" m = manager.KubetestManager() - m.nodes['foobar'] = manager.TestMeta('foo', 'bar', True, None) + m.nodes["foobar"] = manager.TestMeta("foo", "bar", True, None) - c = m.get_test('foobar') + c = m.get_test("foobar") assert isinstance(c, manager.TestMeta) - assert 'foo' == c.name - assert 'bar' == c.node_id + assert "foo" == c.name + assert "bar" == c.node_id def test_manager_get_test_none(): @@ -57,5 +57,5 @@ def test_manager_get_test_none(): m = manager.KubetestManager() - c = m.get_test('foobar') + c = m.get_test("foobar") assert c is None diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 34f00fa..ce92538 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -13,34 +13,38 @@ class TestCastValue: """Tests for kubetest.manifest.cast_value""" @pytest.mark.parametrize( - 'value,t,expected', [ + "value,t,expected", + [ # builtin types - (11, 'int', int(11)), - ('11', 'int', int(11)), - (11.0, 'int', int(11)), - (11, 'float', float(11)), - (11, 'str', '11'), - + (11, "int", int(11)), + ("11", "int", int(11)), + (11.0, "int", int(11)), + (11, "float", float(11)), + (11, "str", "11"), # casting to object should result in no change - (11, 'object', 11), - ('11', 'object', '11'), - + (11, "object", 11), + ("11", "object", "11"), # kubernetes types ( - {'apiVersion': 'apps/v1', 'kind': 'Namespace'}, - 'V1Namespace', - client.V1Namespace(kind='Namespace', api_version='apps/v1')), + {"apiVersion": "apps/v1", "kind": "Namespace"}, + "V1Namespace", + client.V1Namespace(kind="Namespace", api_version="apps/v1"), + ), ( - {'fieldRef': {'apiVersion': 'apps/v1beta1', 'fieldPath': 'foobar'}}, - 'V1EnvVarSource', - client.V1EnvVarSource(field_ref=client.V1ObjectFieldSelector( - api_version='apps/v1beta1', field_path='foobar' - ))), + {"fieldRef": {"apiVersion": "apps/v1beta1", "fieldPath": "foobar"}}, + "V1EnvVarSource", + client.V1EnvVarSource( + field_ref=client.V1ObjectFieldSelector( + api_version="apps/v1beta1", field_path="foobar" + ) + ), + ), ( - {'finalizers': ['a', 'b', 'c']}, - 'V1ObjectMeta', - client.V1ObjectMeta(finalizers=['a', 'b', 'c'])), - ] + {"finalizers": ["a", "b", "c"]}, + "V1ObjectMeta", + client.V1ObjectMeta(finalizers=["a", "b", "c"]), + ), + ], ) def test_ok(self, value, t, expected): """Test casting values to the specified type successfully.""" @@ -50,21 +54,20 @@ def test_ok(self, value, t, expected): assert actual == expected @pytest.mark.parametrize( - 'value,t,error', [ + "value,t,error", + [ # builtin types - ({'foo': 'bar'}, 'int', TypeError), - ([1, 3, 5], 'float', TypeError), - (1.0, 'set', TypeError), - + ({"foo": "bar"}, "int", TypeError), + ([1, 3, 5], "float", TypeError), + (1.0, "set", TypeError), # kubernetes types - (11, 'V1Namespace', AttributeError), - ('foo', 'V1Deployment', AttributeError), - (['a', 'b', 'c'], 'V1Service', AttributeError), - ({1, 2, 3, 4}, 'V1Pod', AttributeError), - + (11, "V1Namespace", AttributeError), + ("foo", "V1Deployment", AttributeError), + (["a", "b", "c"], "V1Service", AttributeError), + ({1, 2, 3, 4}, "V1Pod", AttributeError), # unknown type - (11, 'NotARealType', ValueError), - ] + (11, "NotARealType", ValueError), + ], ) def test_error(self, value, t, error): """Test casting values to the specified type unsuccessfully.""" @@ -86,8 +89,7 @@ class TestLoadType: def test_simple_deployment_ok(self, manifest_dir, simple_deployment): """Test loading the simple deployment successfully.""" obj = manifest.load_type( - client.V1Deployment, - os.path.join(manifest_dir, 'simple-deployment.yaml') + client.V1Deployment, os.path.join(manifest_dir, "simple-deployment.yaml") ) assert obj == simple_deployment @@ -97,15 +99,13 @@ def test_simple_deployment_wrong_type(self, manifest_dir): # The V1Container requires a name -- since the manifest has no name, # it will cause V1Container construction to fail with ValueError. manifest.load_type( - client.V1Container, - os.path.join(manifest_dir, 'simple-deployment.yaml') + client.V1Container, os.path.join(manifest_dir, "simple-deployment.yaml") ) def test_simple_statefulset_ok(self, manifest_dir, simple_statefulset): """Test loading the simple statefulset successfully.""" obj = manifest.load_type( - client.V1StatefulSet, - os.path.join(manifest_dir, 'simple-statefulset.yaml') + client.V1StatefulSet, os.path.join(manifest_dir, "simple-statefulset.yaml") ) assert obj == simple_statefulset @@ -116,14 +116,13 @@ def test_simple_statefulset_wrong_type(self, manifest_dir): # it will cause V1Container construction to fail with ValueError. manifest.load_type( client.V1Container, - os.path.join(manifest_dir, 'simple-statefulset.yaml') + os.path.join(manifest_dir, "simple-statefulset.yaml"), ) def test_simple_daemonset_ok(self, manifest_dir, simple_daemonset): """Test loading the simple daemonset successfully.""" obj = manifest.load_type( - client.V1DaemonSet, - os.path.join(manifest_dir, 'simple-daemonset.yaml') + client.V1DaemonSet, os.path.join(manifest_dir, "simple-daemonset.yaml") ) assert obj == simple_daemonset @@ -133,15 +132,13 @@ def test_simple_daemonset_wrong_type(self, manifest_dir): # The V1Container requires a name -- since the manifest has no name, # it will cause V1Container construction to fail with ValueError. manifest.load_type( - client.V1Container, - os.path.join(manifest_dir, 'simple-daemonset.yaml') + client.V1Container, os.path.join(manifest_dir, "simple-daemonset.yaml") ) def test_simple_service_ok(self, manifest_dir, simple_service): """Test loading the simple service successfully.""" obj = manifest.load_type( - client.V1Service, - os.path.join(manifest_dir, 'simple-service.yaml') + client.V1Service, os.path.join(manifest_dir, "simple-service.yaml") ) assert obj == simple_service @@ -151,15 +148,14 @@ def test_simple_service_wrong_type(self, manifest_dir): # The V1Container requires a name -- since the manifest has no name, # it will cause V1Container construction to fail with ValueError. manifest.load_type( - client.V1Container, - os.path.join(manifest_dir, 'simple-service.yaml') + client.V1Container, os.path.join(manifest_dir, "simple-service.yaml") ) def test_simple_ingress_ok(self, manifest_dir, simple_ingress): """Test loading the simple service successfully.""" obj = manifest.load_type( client.ExtensionsV1beta1Ingress, - os.path.join(manifest_dir, 'simple-ingress.yaml') + os.path.join(manifest_dir, "simple-ingress.yaml"), ) assert obj == simple_ingress @@ -169,15 +165,13 @@ def test_simple_ingress_wrong_type(self, manifest_dir): # The V1Container requires a name -- since the manifest has no name, # it will cause V1Container construction to fail with ValueError. manifest.load_type( - client.V1Container, - os.path.join(manifest_dir, 'simple-ingress.yaml') + client.V1Container, os.path.join(manifest_dir, "simple-ingress.yaml") ) def test_simple_replicaset_ok(self, manifest_dir, simple_replicaset): """Test loading the simple ReplicaSet successfully.""" obj = manifest.load_type( - client.V1ReplicaSet, - os.path.join(manifest_dir, 'simple-replicaset.yaml') + client.V1ReplicaSet, os.path.join(manifest_dir, "simple-replicaset.yaml") ) assert obj == simple_replicaset @@ -187,19 +181,16 @@ def test_simple_replicaset_wrong_type(self, manifest_dir): # The V1Container requires a name -- since the manifest has no name, # it will cause V1Container construction to fail with ValueError. manifest.load_type( - client.V1Container, - os.path.join(manifest_dir, 'simple-replicaset.yaml') + client.V1Container, os.path.join(manifest_dir, "simple-replicaset.yaml") ) def test_simple_persistentvolumeclaim_ok( - self, - manifest_dir, - simple_persistentvolumeclaim + self, manifest_dir, simple_persistentvolumeclaim ): """Test loading the simple persistentvolumeclaim successfully.""" obj = manifest.load_type( client.V1PersistentVolumeClaim, - os.path.join(manifest_dir, 'simple-persistentvolumeclaim.yaml') + os.path.join(manifest_dir, "simple-persistentvolumeclaim.yaml"), ) assert obj == simple_persistentvolumeclaim @@ -210,18 +201,14 @@ def test_simple_persistentvolumeclaim_wrong_type(self, manifest_dir): # it will cause V1Container construction to fail with ValueError. manifest.load_type( client.V1Container, - os.path.join(manifest_dir, 'simple-persistentvolumeclaim.yaml') + os.path.join(manifest_dir, "simple-persistentvolumeclaim.yaml"), ) - def test_simple_serviceaccount_ok( - self, - manifest_dir, - simple_serviceaccount - ): + def test_simple_serviceaccount_ok(self, manifest_dir, simple_serviceaccount): """Test loading the simple serviceaccount successfully.""" obj = manifest.load_type( client.V1ServiceAccount, - os.path.join(manifest_dir, 'simple-serviceaccount.yaml') + os.path.join(manifest_dir, "simple-serviceaccount.yaml"), ) assert obj == simple_serviceaccount @@ -229,7 +216,7 @@ def test_simple_networkpolicy_ok(self, manifest_dir, simple_networkpolicy): """Test loading the simple networkpolicy successfully.""" obj = manifest.load_type( client.V1NetworkPolicy, - os.path.join(manifest_dir, 'simple-networkpolicy.yaml') + os.path.join(manifest_dir, "simple-networkpolicy.yaml"), ) assert obj == simple_networkpolicy @@ -240,23 +227,21 @@ def test_simple_networkpolicy_wrong_type(self, manifest_dir): # it will cause V1Container construction to fail with ValueError. manifest.load_type( client.V1Container, - os.path.join(manifest_dir, 'simple-networkpolicy.yaml') + os.path.join(manifest_dir, "simple-networkpolicy.yaml"), ) def test_bad_path(self, manifest_dir): """Test specifying an invalid manifest path.""" with pytest.raises(FileNotFoundError): manifest.load_type( - client.V1Container, - os.path.join(manifest_dir, 'foo', 'bar', 'baz.yaml') + client.V1Container, os.path.join(manifest_dir, "foo", "bar", "baz.yaml") ) def test_bad_yaml(self, manifest_dir): """Test specifying a file that is not valid YAML.""" with pytest.raises(yaml.YAMLError): manifest.load_type( - client.V1Container, - os.path.join(manifest_dir, 'invalid.yaml') + client.V1Container, os.path.join(manifest_dir, "invalid.yaml") ) @@ -264,46 +249,38 @@ class TestGetType: """Tests for kubetest.manifest.get_type""" @pytest.mark.parametrize( - 'data,expected', [ + "data,expected", + [ + ({"apiVersion": "v1", "kind": "Secret"}, client.V1Secret), + ({"apiVersion": "v1", "kind": "Deployment"}, client.V1Deployment), + ({"apiVersion": "apps/v1", "kind": "Deployment"}, client.V1Deployment), ( - {'apiVersion': 'v1', 'kind': 'Secret'}, - client.V1Secret + {"apiVersion": "apps/v1beta1", "kind": "Deployment"}, + client.AppsV1beta1Deployment, ), ( - {'apiVersion': 'v1', 'kind': 'Deployment'}, - client.V1Deployment + {"apiVersion": "apps/v1beta2", "kind": "Deployment"}, + client.V1beta2Deployment, ), ( - {'apiVersion': 'apps/v1', 'kind': 'Deployment'}, - client.V1Deployment - ), - ( - {'apiVersion': 'apps/v1beta1', 'kind': 'Deployment'}, - client.AppsV1beta1Deployment - ), - ( - {'apiVersion': 'apps/v1beta2', 'kind': 'Deployment'}, - client.V1beta2Deployment - ), - ( - {'apiVersion': 'extensions/v1beta1', 'kind': 'Deployment'}, - client.ExtensionsV1beta1Deployment + {"apiVersion": "extensions/v1beta1", "kind": "Deployment"}, + client.ExtensionsV1beta1Deployment, ), ( { - 'apiVersion': 'rbac.authorization.k8s.io/v1', - 'kind': 'ClusterRoleBinding' + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", }, - client.V1ClusterRoleBinding + client.V1ClusterRoleBinding, ), ( { - 'apiVersion': 'rbac.authorization.k8s.io/v1beta1', - 'kind': 'ClusterRoleBinding' + "apiVersion": "rbac.authorization.k8s.io/v1beta1", + "kind": "ClusterRoleBinding", }, - client.V1beta1ClusterRoleBinding + client.V1beta1ClusterRoleBinding, ), - ] + ], ) def test_ok(self, data, expected): """Test getting Kubernetes object types correctly.""" @@ -314,23 +291,20 @@ def test_ok(self, data, expected): def test_nonexistent_type(self): """Test getting a type that Kubernetes does not have.""" - t = manifest.get_type({ - 'apiVersion': 'v1', - 'kind': 'foobar' - }) + t = manifest.get_type({"apiVersion": "v1", "kind": "foobar"}) assert t is None def test_no_version(self): """Test getting a type when no version is given.""" with pytest.raises(ValueError): - manifest.get_type({'kind': 'Deployment'}) + manifest.get_type({"kind": "Deployment"}) def test_no_kind(self): """Test getting a type when no kind is given.""" with pytest.raises(ValueError): - manifest.get_type({'version': 'v1'}) + manifest.get_type({"version": "v1"}) class TestLoadPath: @@ -339,22 +313,22 @@ class TestLoadPath: def test_ok(self, manifest_dir): """Test loading the manifests into API objects successfully.""" - objs = manifest.load_path(os.path.join(manifest_dir, 'manifests')) + objs = manifest.load_path(os.path.join(manifest_dir, "manifests")) assert len(objs) == 3 for obj in objs: - assert obj.kind in ['Deployment', 'ConfigMap', 'Service'] + assert obj.kind in ["Deployment", "ConfigMap", "Service"] def test_no_dir(self): """Test loading manifests when the specified path is not a directory.""" with pytest.raises(ValueError): - manifest.load_path('foobar') + manifest.load_path("foobar") def test_empty_dir(self, tmpdir): """Test loading manifests from an empty directory.""" - d = tmpdir.mkdir('foo') + d = tmpdir.mkdir("foo") objs = manifest.load_path(d) assert len(objs) == 0 @@ -371,9 +345,7 @@ class TestLoadFile: def test_ok_single(self, manifest_dir): """Load manifest file with single object definition.""" - objs = manifest.load_file( - os.path.join(manifest_dir, 'simple-deployment.yaml') - ) + objs = manifest.load_file(os.path.join(manifest_dir, "simple-deployment.yaml")) assert len(objs) == 1 assert isinstance(objs[0], client.models.v1_deployment.V1Deployment) @@ -381,9 +353,7 @@ def test_ok_single(self, manifest_dir): def test_ok_multi(self, manifest_dir): """Load manifest file with multiple object definitions.""" - objs = manifest.load_file( - os.path.join(manifest_dir, 'multi-obj-manifest.yaml') - ) + objs = manifest.load_file(os.path.join(manifest_dir, "multi-obj-manifest.yaml")) assert len(objs) == 3 assert isinstance(objs[0], client.models.v1_service.V1Service) @@ -394,10 +364,10 @@ def test_no_file(self, manifest_dir): """Load manifest file which does not exist.""" with pytest.raises(FileNotFoundError): - manifest.load_file(os.path.join(manifest_dir, 'file-does-not-exist.yaml')) + manifest.load_file(os.path.join(manifest_dir, "file-does-not-exist.yaml")) def test_invalid_yaml(self, manifest_dir): """Load manifest file with invalid YAML.""" with pytest.raises(yaml.YAMLError): - manifest.load_file(os.path.join(manifest_dir, 'invalid.yaml')) + manifest.load_file(os.path.join(manifest_dir, "invalid.yaml")) diff --git a/tests/test_utils.py b/tests/test_utils.py index 71dd07d..a778a25 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,17 +6,21 @@ @pytest.mark.parametrize( - 'name,expected', [ - ('', 'kubetest--1536849367'), - ('TestName', 'kubetest-testname-1536849367'), - ('TESTNAME', 'kubetest-testname-1536849367'), - ('Test-Name', 'kubetest-test-name-1536849367'), - ('Test1_FOO-BAR_2', 'kubetest-test1-foo-bar-2-1536849367'), - ('123456', 'kubetest-123456-1536849367'), - ('___', 'kubetest-----1536849367'), - ('test-'*14, 'kubetest-test-test-test-test-test-test-test-test-tes-1536849367'), - ('test[a]-foo', 'kubetest-test-a--foo-1536849367'), - ] + "name,expected", + [ + ("", "kubetest--1536849367"), + ("TestName", "kubetest-testname-1536849367"), + ("TESTNAME", "kubetest-testname-1536849367"), + ("Test-Name", "kubetest-test-name-1536849367"), + ("Test1_FOO-BAR_2", "kubetest-test1-foo-bar-2-1536849367"), + ("123456", "kubetest-123456-1536849367"), + ("___", "kubetest-----1536849367"), + ( + "test-" * 14, + "kubetest-test-test-test-test-test-test-test-test-tes-1536849367", + ), + ("test[a]-foo", "kubetest-test-a--foo-1536849367"), + ], ) def test_new_namespace(name, expected): """Test creating a new namespace for the given function name.""" @@ -29,14 +33,15 @@ def test_new_namespace(name, expected): @pytest.mark.parametrize( - 'labels,expected', [ - ({}, ''), - ({'foo': 'bar'}, 'foo=bar'), - ({'foo': 2}, 'foo=2'), - ({'foo': 2.024}, 'foo=2.024'), - ({'foo': 'bar', 'abc': 'xyz'}, 'foo=bar,abc=xyz'), - ({'foo': 'bar', 'abc': 'xyz', 'app': 'synse'}, 'foo=bar,abc=xyz,app=synse'), - ] + "labels,expected", + [ + ({}, ""), + ({"foo": "bar"}, "foo=bar"), + ({"foo": 2}, "foo=2"), + ({"foo": 2.024}, "foo=2.024"), + ({"foo": "bar", "abc": "xyz"}, "foo=bar,abc=xyz"), + ({"foo": "bar", "abc": "xyz", "app": "synse"}, "foo=bar,abc=xyz,app=synse"), + ], ) def test_selector_string(labels, expected): """Test creating a string for a dictionary of selectors."""