diff --git a/.gitignore b/.gitignore index 3065afa..0999f4d 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ venv/ ENV/ env.bak/ venv.bak/ +.ruff_cache/ diff --git a/GNUmakefile b/GNUmakefile index 5330178..9458349 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -160,7 +160,7 @@ help: @echo '' clean: - rm -rf build dist + rm -rf build dist .ruff_cache ############################################################################# # RPM targets diff --git a/libcsm/__init__.py b/libcsm/__init__.py index e69de29..38525d8 100644 --- a/libcsm/__init__.py +++ b/libcsm/__init__.py @@ -0,0 +1 @@ +"""libCSM.""" diff --git a/libcsm/api.py b/libcsm/api.py index b6ece21..3ffb9a6 100644 --- a/libcsm/api.py +++ b/libcsm/api.py @@ -37,18 +37,17 @@ class AuthException(Exception): - """ An exception for authorization problems from the api.Auth object. """ def __init__(self, message) -> None: + """Initialize an AuthException class.""" self.message = message super().__init__(self.message) class Auth: - """ Class for handling CSM API credentials stored in Kubernetes secrets. @@ -58,13 +57,15 @@ class Auth: """ def __init__(self, **kwargs) -> None: + """Initialize an Auth class.""" config.load_kube_config(**kwargs) self.core = client.CoreV1Api() self._token = None def _get_secret(self) -> dict: """ - Fetches the Kubernetes SECRET for admin authentication. + Fetch the Kubernetes SECRET for admin authentication. + :return: The data dict from the resolved V1Secret. """ try: @@ -79,7 +80,7 @@ def _get_secret(self) -> dict: def refresh_token(self) -> None: """ - Refreshes the authentication token. + Refresh the authentication token. """ del self.token credentials = self._get_secret() @@ -111,6 +112,6 @@ def token(self) -> str: @token.deleter def token(self) -> None: """ - Handles deleting the authentication token. + Handle deleting the authentication token. """ self._token = None diff --git a/libcsm/bss/__init__.py b/libcsm/bss/__init__.py index e69de29..bbe5c69 100644 --- a/libcsm/bss/__init__.py +++ b/libcsm/bss/__init__.py @@ -0,0 +1 @@ +"""libCSM BSS.""" diff --git a/libcsm/bss/api.py b/libcsm/bss/api.py index a40df13..2b83026 100644 --- a/libcsm/bss/api.py +++ b/libcsm/bss/api.py @@ -33,7 +33,6 @@ class API: - """ Class for providing API to interact with BSS. """ @@ -85,13 +84,12 @@ def patch_bss_bootparams(self, xname : str, bss_json) -> None: print('BSS entry patched') def set_bss_image(self, xname: str, image_dict: dict) -> None: - """ Set the images in BSS for a specific xname. + The inputs are the node's xname and a dictionary containing initrd, kernel, and roofs image paths that will be set in BSS. """ - if 'initrd' not in image_dict or 'kernel' not in image_dict or 'rootfs' not in image_dict: raise ValueError(f"ERROR set_bss_image has inputs 'xname' and 'image_dictonary' where" \ f"'image_dictionary' is a dictionary containing values for 'initrd', 'kernel', " \ diff --git a/libcsm/bss/set_image.py b/libcsm/bss/set_image.py index 57ce8a6..8ea93cc 100644 --- a/libcsm/bss/set_image.py +++ b/libcsm/bss/set_image.py @@ -49,9 +49,7 @@ @click.option('--endpoint-url', required=False, type=str, default='http://rgw-vip', help='Address of the Rados-gateway endpoint.') def main(**kwargs) -> None: - """Set the kernel, rootfs, and initrd images in BSS for specified node(s) given an image-id.""" - hsm_role_subrole = kwargs['hsm_role_subrole'] xnames = kwargs['xnames'] bucket = kwargs['bucket'] diff --git a/libcsm/hsm/__init__.py b/libcsm/hsm/__init__.py index e69de29..c1886e5 100644 --- a/libcsm/hsm/__init__.py +++ b/libcsm/hsm/__init__.py @@ -0,0 +1,22 @@ +# MIT License +# +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +"""libCSM HSM.""" diff --git a/libcsm/hsm/components.py b/libcsm/hsm/components.py index 3ada66c..1ab2fd5 100644 --- a/libcsm/hsm/components.py +++ b/libcsm/hsm/components.py @@ -32,10 +32,11 @@ ROLE_SUBROLES = ["Management_Master", "Management_Worker", "Management_Storage"] + def get_components(role_subrole: str, api_gateway_address="api-gw-service-nmn.local") \ -> requests.Response: """ - Function to get management components from HSM based on their role and subrole. + Get management components from HSM based on their role and subrole. """ auth = api.Auth() auth.refresh_token() diff --git a/libcsm/hsm/xnames.py b/libcsm/hsm/xnames.py index 1273ffc..347c44b 100644 --- a/libcsm/hsm/xnames.py +++ b/libcsm/hsm/xnames.py @@ -27,9 +27,10 @@ from typing import List from libcsm.hsm import components + def get_by_role_subrole(role_subrole: str) -> List[str]: """ - Function to get xnames by subrole from HSM. + Get xnames by subrole from HSM. """ components_response = components.get_components(role_subrole) xnames = [] @@ -37,5 +38,8 @@ def get_by_role_subrole(role_subrole: str) -> List[str]: for component in components_response.json()['Components']: xnames.append(component['ID']) else: - print(f'ERROR no components were found with hsm_role_subrole: {role_subrole}') + print( + f'ERROR no components were found with hsm_role_subrole: ' + f'{role_subrole}' + ) return xnames diff --git a/libcsm/logger.py b/libcsm/logger.py index 18ebeab..6f9d1ee 100644 --- a/libcsm/logger.py +++ b/libcsm/logger.py @@ -34,6 +34,8 @@ class Logger(logging.Logger): """ + Custom logger. + Inherits from logging.Logger, configuring custom settings on initialization. """ @@ -44,6 +46,8 @@ def __init__( log_level: int = logging.INFO ) -> None: """ + Customize the ``Logger`` object. + :param module_name: Pass __name__ here or whatever name you want to define the Logger as. :param log_level: Level of logging (default: INFO). @@ -60,7 +64,7 @@ def __init__( print( f'Failed to make {os.path.dirname(LOG_PATH)}\n{error}\n' f'writing logs to present working directory.' - ) + ) handler = RotatingFileHandler(os.path.basename(LOG_PATH)) handler.setFormatter(formatter) self.addHandler(handler) diff --git a/libcsm/os.py b/libcsm/os.py index af6b18e..b13daa9 100644 --- a/libcsm/os.py +++ b/libcsm/os.py @@ -43,8 +43,8 @@ class _CLI: """ An object to abstract the return result from ``run_command``. - """ + _stdout = b'' _stderr = b'' _return_code = None @@ -52,10 +52,12 @@ class _CLI: def __init__(self, args: [str, list], shell: bool = False) -> None: """ + Create a ``Popen`` object. + If shell==True then the arguments will be converted to a string if a list was passed. The conversion is recommended by Popen's documentation: - https://docs.python.org/3/library/subprocess.html + https://docs.python.org/3/library/subprocess.html. :param args: The arguments (as a list or string) to run with Popen. :param shell: Whether to run Popen in a shell (default: False) @@ -69,6 +71,8 @@ def __init__(self, args: [str, list], shell: bool = False) -> None: def _run(self) -> None: """ + Invoke the loaded command with ``Popen``. + Run the arguments and set the object's class variables with the results. """ @@ -121,7 +125,7 @@ def stderr(self) -> [str, bytes]: @property def return_code(self) -> int: """ - return code from the command. + Return code from the command. """ return self._return_code @@ -134,6 +138,8 @@ def duration(self) -> float: def decode(self, charset: str) -> None: """ + Decode ``self.stdout`` and ``self.stderr``. + Decodes ``self._stdout`` and ``self._stderr`` with the given ``charset``. :param charset: The character set to decode with. """ @@ -156,7 +162,9 @@ def decode(self, charset: str) -> None: @contextmanager def chdir(directory: str, create: bool = False) -> None: """ - Changes into a given directory and returns to the original directory on + Change directories and run a command. + + Change into a given directory and returns to the original directory on exit. .. note:: @@ -195,7 +203,7 @@ def run_command( charset: str = None, ) -> _CLI: """ - Runs a given command or list of commands by instantiating a ``CLI`` object. + Run a given command or list of commands by instantiating a ``CLI`` object. .. code-block:: python diff --git a/libcsm/requests/__init__.py b/libcsm/requests/__init__.py index e69de29..e4888b5 100644 --- a/libcsm/requests/__init__.py +++ b/libcsm/requests/__init__.py @@ -0,0 +1 @@ +"""libCSM requests.""" diff --git a/libcsm/s3/__init__.py b/libcsm/s3/__init__.py index e69de29..772762f 100644 --- a/libcsm/s3/__init__.py +++ b/libcsm/s3/__init__.py @@ -0,0 +1,22 @@ +# MIT License +# +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +"""libCSM S3.""" diff --git a/libcsm/s3/images.py b/libcsm/s3/images.py index 5732644..60605df 100644 --- a/libcsm/s3/images.py +++ b/libcsm/s3/images.py @@ -28,12 +28,14 @@ import json from libcsm.s3 import s3object + class ImageFormatException(Exception): """ An exception for problems getting "initrd", "kernel", "rootfs" image paths from an image in S3. """ + def __init__(self, message) -> None: self.message = message super().__init__(self.message) @@ -41,13 +43,14 @@ def __init__(self, message) -> None: def get_s3_image_info(bucket_name, image_id, endpoint_url) -> dict: """ - Function to get the initrd, rootfs, and kernel image paths given an s3 bucket and image ID. + Get the initrd, rootfs, and kernel image paths given an s3 bucket and image ID. + This returns a dictionary { rootfs: "" kernel: "" initrd: "" - } + }. """ image_manifest = image_id + "/manifest.json" image_object = s3object.S3Object(bucket_name, image_manifest) diff --git a/libcsm/s3/s3object.py b/libcsm/s3/s3object.py index 6e28a25..c138c3c 100644 --- a/libcsm/s3/s3object.py +++ b/libcsm/s3/s3object.py @@ -34,18 +34,17 @@ S3_READ_TIMEOUT=1 class RGWAdminException(Exception): - """ An exception for problems running radosgw-admin commands. """ + def __init__(self, message) -> None: self.message = message super().__init__(self.message) class S3Object: - """ - Class for getting s3 object infomation given a bucket and bject name. + Class for getting s3 object infomation given a bucket and bject name. """ def __init__(self, bucket: str, object_name: str): diff --git a/libcsm/sls/__init__.py b/libcsm/sls/__init__.py index e69de29..55d2dbb 100644 --- a/libcsm/sls/__init__.py +++ b/libcsm/sls/__init__.py @@ -0,0 +1,22 @@ +# MIT License +# +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +"""libCSM SLS.""" diff --git a/libcsm/sls/api.py b/libcsm/sls/api.py index c4f5ae1..b3bc0f2 100644 --- a/libcsm/sls/api.py +++ b/libcsm/sls/api.py @@ -32,7 +32,6 @@ class API: - """ Class for providing API to interact with SLS. """ @@ -46,7 +45,7 @@ def __init__(self, api_gateway_address="api-gw-service-nmn.local"): def get_management_components_from_sls(self) -> requests.Response: """ - Function to retrieve all management components from SLS. + Retrieve all management components from SLS. """ session = get_session() try: @@ -64,7 +63,7 @@ def get_management_components_from_sls(self) -> requests.Response: def get_xname(self, hostname: str) -> str: """ - Function to get the xname of a node from SLS based on a provided hostname. + Get the xname of a node from SLS based on a provided hostname. """ try: components_response = (self.get_management_components_from_sls()).json() @@ -84,7 +83,7 @@ def get_xname(self, hostname: str) -> str: def get_hostname(self, xname: str) -> str: """ - Function to get the hostname of a management node from SLS based on a provided xname. + Get the hostname of a management node from SLS based on a provided xname. """ try: components_response = (self.get_management_components_from_sls()).json() diff --git a/libcsm/sls/get_hostname.py b/libcsm/sls/get_hostname.py index e3e254f..069211b 100644 --- a/libcsm/sls/get_hostname.py +++ b/libcsm/sls/get_hostname.py @@ -22,7 +22,7 @@ # OTHER DEALINGS IN THE SOFTWARE. # """ -Function for setting boot-image in BSS +Function for setting boot-image in BSS. """ import sys @@ -36,12 +36,11 @@ @click.option('--api-gateway-address', required=False, type=str, default='api-gw-service-nmn.local', help='API gateway address. Default is \'api-gw-service-nmn.local\'.') def main(xname, api_gateway_address) -> None: - """ - Get the hostname of a NCN given an Xname. + Gets the hostname of a NCN given Xname. + This queries SLS for management nodes' information. """ - sls_api = api.API(api_gateway_address) try: print(sls_api.get_hostname(xname)) diff --git a/libcsm/sls/get_xname.py b/libcsm/sls/get_xname.py index ee60eee..2430f01 100644 --- a/libcsm/sls/get_xname.py +++ b/libcsm/sls/get_xname.py @@ -22,7 +22,7 @@ # OTHER DEALINGS IN THE SOFTWARE. # """ -Function for setting boot-image in BSS +Function for setting boot-image in BSS. """ import sys @@ -36,12 +36,10 @@ @click.option('--api-gateway-address', required=False, type=str, default='api-gw-service-nmn.local', help='API gateway address. Default is \'api-gw-service-nmn.local\'.') def main(hostname, api_gateway_address) -> None: - """ Get the Xname of a NCN given a hostname. This queries SLS for management nodes' information. """ - sls_api = api.API(api_gateway_address) try: print(sls_api.get_xname(hostname)) diff --git a/libcsm/tests/__init__.py b/libcsm/tests/__init__.py index f1a3c6c..738edcb 100644 --- a/libcsm/tests/__init__.py +++ b/libcsm/tests/__init__.py @@ -19,3 +19,4 @@ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. +"""libCSM tests.""" diff --git a/libcsm/tests/bss/__init__.py b/libcsm/tests/bss/__init__.py index e69de29..cb45520 100644 --- a/libcsm/tests/bss/__init__.py +++ b/libcsm/tests/bss/__init__.py @@ -0,0 +1,22 @@ +# MIT License +# +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +"""libCSM tests BSS.""" diff --git a/libcsm/tests/bss/test_api.py b/libcsm/tests/bss/test_api.py index 7a0e1ee..6d774ed 100644 --- a/libcsm/tests/bss/test_api.py +++ b/libcsm/tests/bss/test_api.py @@ -37,8 +37,9 @@ @dataclass() class MockBssSetup: """ - Setup variables that are reused in tests + Setup variables that are reused in tests. """ + mock_bss_components = [ { "ID" : "ncn-x001"}, { "ID" : "nxn-x002"}, @@ -61,6 +62,7 @@ class TestBssApi: """ Testing the bss api submodule. """ + # setup variables that are reused in tests bss_api = None mock_setup = MockBssSetup @@ -69,7 +71,7 @@ class TestBssApi: @mock.patch('libcsm.api.Auth', spec=True) def setup_method(self, *_) -> None: """ - Setup BSS API to be used in tests + Set up BSS API to be used in tests. """ self.bss_api = bssApi.API() diff --git a/libcsm/tests/bss/test_set_image.py b/libcsm/tests/bss/test_set_image.py index 56c6a56..eaa439c 100644 --- a/libcsm/tests/bss/test_set_image.py +++ b/libcsm/tests/bss/test_set_image.py @@ -35,7 +35,6 @@ @mock.patch('libcsm.s3.images.get_s3_image_info', spec=True) @mock.patch('libcsm.s3.s3object.S3Object.verify_bucket_exists') class TestSetImage: - """ Testing the the bss set image main function. """ diff --git a/libcsm/tests/hsm/__init__.py b/libcsm/tests/hsm/__init__.py index e69de29..1041f39 100644 --- a/libcsm/tests/hsm/__init__.py +++ b/libcsm/tests/hsm/__init__.py @@ -0,0 +1,22 @@ +# MIT License +# +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +"""libCSM tests HSM.""" diff --git a/libcsm/tests/hsm/test_api.py b/libcsm/tests/hsm/test_api.py index 5d15416..a8f2123 100644 --- a/libcsm/tests/hsm/test_api.py +++ b/libcsm/tests/hsm/test_api.py @@ -38,8 +38,9 @@ @dataclass() class MockHSMSetup: """ - Setup variables that are reused in tests + Setup variables that are reused in tests. """ + mock_HSM_components = [] ok_mock_http_response=MockHTTPResponse(mock_HSM_components, http.HTTPStatus.OK) unauth_mock_http_response=MockHTTPResponse(mock_HSM_components, http.HTTPStatus.UNAUTHORIZED) @@ -50,6 +51,7 @@ class TestHsmApi: """ Testing the hsm api submodule. """ + mock_setup = MockHSMSetup def test_get_components(self, *_) -> None: diff --git a/libcsm/tests/hsm/test_xnames.py b/libcsm/tests/hsm/test_xnames.py index 06d305c..3cb7f60 100644 --- a/libcsm/tests/hsm/test_xnames.py +++ b/libcsm/tests/hsm/test_xnames.py @@ -67,7 +67,7 @@ def test_xnames_bad_subrole(self, *_) -> None: def test_xnames_with_no_components(self, *_) -> None: """ - Tests get run of HSM get_xnames_by_role_subrole where no components are returned + Tests get run of HSM get_xnames_by_role_subrole where no components are returned. """ hsm_role_subrole = "Management_Storage" mock_components = { "Components": [] diff --git a/libcsm/tests/mock_objects/__init__.py b/libcsm/tests/mock_objects/__init__.py index e69de29..852b001 100644 --- a/libcsm/tests/mock_objects/__init__.py +++ b/libcsm/tests/mock_objects/__init__.py @@ -0,0 +1,22 @@ +# MIT License +# +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +"""libCSM tests mock objects.""" diff --git a/libcsm/tests/mock_objects/mock_http.py b/libcsm/tests/mock_objects/mock_http.py index 1850a7f..78e66e0 100644 --- a/libcsm/tests/mock_objects/mock_http.py +++ b/libcsm/tests/mock_objects/mock_http.py @@ -26,13 +26,15 @@ """ from dataclasses import dataclass + @dataclass class MockHTTPResponse: - """ Class for a mocked HTTP response """ + """Class for a mocked HTTP response.""" + def __init__(self, data, status_code): self.json_data = data self.status_code = status_code def json(self): - """ Return json data from the exception """ + """Return json data from the exception.""" return self.json_data diff --git a/libcsm/tests/mock_objects/mock_sls.py b/libcsm/tests/mock_objects/mock_sls.py index 63da967..a806869 100644 --- a/libcsm/tests/mock_objects/mock_sls.py +++ b/libcsm/tests/mock_objects/mock_sls.py @@ -28,11 +28,13 @@ import http from libcsm.tests.mock_objects.mock_http import MockHTTPResponse + @dataclass() class MockSLSResponse: """ - Setup testing variables that are reused in tests + Setup testing variables that are reused in tests. """ + mock_components = [ { 'Parent': 'par1', 'Xname': 'xname1', @@ -43,14 +45,20 @@ class MockSLSResponse: }, ] mock_http_response = MockHTTPResponse(mock_components, http.HTTPStatus.OK) - unauth_mock_http_response=MockHTTPResponse(mock_components, http.HTTPStatus.UNAUTHORIZED) + unauth_mock_http_response = MockHTTPResponse( + mock_components, + http.HTTPStatus.UNAUTHORIZED + ) bad_mock_components = [ { 'Parent': 'par1', 'Xname': 'xname1', 'No_extra_properties': {'Aliases': ['ncn-w001'], 'A': 100} }, { - 'Parent': 'par2', 'Xname': 'xname2', + 'Parent': 'par2', 'Xname': 'xname2', 'No_extra_properties': {'Aliases': ['ncn-s002'], 'A': 101} }, ] - bad_mock_http_response=MockHTTPResponse(bad_mock_components, http.HTTPStatus.OK) + bad_mock_http_response = MockHTTPResponse( + bad_mock_components, + http.HTTPStatus.OK + ) diff --git a/libcsm/tests/s3/__init__.py b/libcsm/tests/s3/__init__.py index e69de29..269a3e1 100644 --- a/libcsm/tests/s3/__init__.py +++ b/libcsm/tests/s3/__init__.py @@ -0,0 +1,22 @@ +# MIT License +# +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +"""libCSM test S3.""" diff --git a/libcsm/tests/s3/test_get_s3_image_info.py b/libcsm/tests/s3/test_get_s3_image_info.py index 70266de..40270a7 100644 --- a/libcsm/tests/s3/test_get_s3_image_info.py +++ b/libcsm/tests/s3/test_get_s3_image_info.py @@ -34,12 +34,13 @@ from libcsm.s3 import images, s3object from libcsm.s3.images import ImageFormatException + def mock_s3_object(mocked_images: dict) -> dict: """ Mock s3 objects. """ body_json = { - 'artifacts': mocked_images + 'artifacts': mocked_images } body_string = json.dumps(body_json).encode() body_stream = StreamingBody( @@ -48,9 +49,9 @@ def mock_s3_object(mocked_images: dict) -> dict: ) return {'Body': body_stream} + @mock.patch('libcsm.s3.s3object.S3Object.verify_bucket_exists') class TestGetS3ImageInfo: - """ Tests for the get_s3_image_info function. """ @@ -58,17 +59,20 @@ class TestGetS3ImageInfo: @mock.patch('libcsm.s3.s3object.S3Object.get_object') def test_good_get_image_info(self, mock_get_object, *_) -> None: """ - Verify get_s3_image_info runs smoothly when a good repsonse is recieved from s3. + Verify get_s3_image_info runs smoothly when a good repsonse is + recieved from s3. """ mock_get_object.return_value = 0 mocked_images = [ - {"type" : "initrd", "link": {"path": "initrd_path"} }, - {"type" : "kernel", "link": {"path": "kernel_path"} }, - {"type" : "rootfs", "link": {"path": "rootfs_path"} }, + {"type": "initrd", "link": {"path": "initrd_path"}}, + {"type": "kernel", "link": {"path": "kernel_path"}}, + {"type": "rootfs", "link": {"path": "rootfs_path"}}, ] mock_object = mock_s3_object(mocked_images) - with mock.patch.object(s3object.S3Object, 'get_object', \ - return_value=mock_object): + with mock.patch.object( + s3object.S3Object, 'get_object', \ + return_value=mock_object + ): image_dict = images.get_s3_image_info("bucket", "image", "info") assert image_dict['initrd'] == "initrd_path" assert image_dict['kernel'] == "kernel_path" @@ -81,12 +85,14 @@ def test_bad_get_image_info(self, mock_get_object, *_) -> None: """ mock_get_object.return_value = 0 mocked_images = [ - {"type" : "XX-bad-XX", "link": {"path": "initrd_path"} }, - {"type" : "kernel", "link": {"path": "kernel_path"} }, - {"type" : "rootfs", "link": {"path": "rootfs_path"} }, + {"type": "XX-bad-XX", "link": {"path": "initrd_path"}}, + {"type": "kernel", "link": {"path": "kernel_path"}}, + {"type": "rootfs", "link": {"path": "rootfs_path"}}, ] mock_object = mock_s3_object(mocked_images) - with mock.patch.object(s3object.S3Object, 'get_object', \ - return_value=mock_object): + with mock.patch.object( + s3object.S3Object, 'get_object', \ + return_value=mock_object + ): with pytest.raises(ImageFormatException): images.get_s3_image_info("bucket", "image", "info") diff --git a/libcsm/tests/s3/test_s3object.py b/libcsm/tests/s3/test_s3object.py index c215c15..9a26f5c 100644 --- a/libcsm/tests/s3/test_s3object.py +++ b/libcsm/tests/s3/test_s3object.py @@ -33,10 +33,10 @@ class TestS3Object: - """ Tests for the s3object submodule. """ + @mock.patch('libcsm.s3.s3object.S3Object.verify_bucket_exists') def test_object(self, *_) -> None: """ diff --git a/libcsm/tests/sls/__init__.py b/libcsm/tests/sls/__init__.py index e69de29..d04b7db 100644 --- a/libcsm/tests/sls/__init__.py +++ b/libcsm/tests/sls/__init__.py @@ -0,0 +1,22 @@ +# MIT License +# +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +"""libCSM tests SLS.""" diff --git a/libcsm/tests/sls/test_api.py b/libcsm/tests/sls/test_api.py index 161f8b6..7c25168 100644 --- a/libcsm/tests/sls/test_api.py +++ b/libcsm/tests/sls/test_api.py @@ -46,14 +46,14 @@ class TestSLSApi: @mock.patch('libcsm.api.Auth', spec=True) def setup_method(self, *_) -> None: """ - Setup SLS API to be used in tests + Set up SLS API to be used in tests. """ with mock.patch.object(api.Auth, 'refresh_token', return_value=None): self.sls_api = slsApi.API() def test_get_management_components_from_sls(self, *_) -> None: """ - Tests successful run of the SLS get_management_components_from_sls + Tests successful run of the SLS get_management_components_from_sls function. """ with mock.patch.object(Session, 'get', \ @@ -62,7 +62,7 @@ def test_get_management_components_from_sls(self, *_) -> None: def test_get_management_components_from_sls_error(self, *_) -> None: """ - Tests unsuccessful run of the SLS get_management_components_from_sls + Tests unsuccessful run of the SLS get_management_components_from_sls function because of bad response. """ with mock.patch.object(Session, 'get', \ @@ -117,7 +117,7 @@ def test_get_hostname_bad_xname(self, *_) -> None: def test_get_hostname_invalid_response(self, *_) -> None: """ - Tests the SLS get_xname function with an invalid response + Tests the SLS get_xname function with an invalid response from sls.get_management_components_from_sls. """ with mock.patch.object(self.sls_api, 'get_management_components_from_sls', \ diff --git a/libcsm/tests/sls/test_get_xname.py b/libcsm/tests/sls/test_get_xname.py index da15639..e7077ad 100644 --- a/libcsm/tests/sls/test_get_xname.py +++ b/libcsm/tests/sls/test_get_xname.py @@ -38,6 +38,7 @@ class TestGetXname: """ Testing the sls get_xnames function. """ + mock_setup = MockSLSResponse def test_get_management_components_from_sls(self, mock_management_components, *_) -> None: diff --git a/libcsm/tests/test_api.py b/libcsm/tests/test_api.py index c5aa7d9..f4ae041 100644 --- a/libcsm/tests/test_api.py +++ b/libcsm/tests/test_api.py @@ -40,8 +40,9 @@ @dataclass() class MockV1Secret: """ - A mock for kubernetes.client.models.v1_secret.V1Secret + A mock for kubernetes.client.models.v1_secret.V1Secret. """ + id = b'foo-client' secret = b'2fb9d9ad-3333-2222-1111-000000000000' endpoint = b'https://example.com' @@ -55,7 +56,6 @@ class MockV1Secret: @mock.patch('kubernetes.config.load_kube_config') @mock.patch('kubernetes.client.CoreV1Api') class TestApi: - """ Tests for the API submodule. """ diff --git a/libcsm/tests/test_os.py b/libcsm/tests/test_os.py index 661ff3f..cdaf21e 100644 --- a/libcsm/tests/test_os.py +++ b/libcsm/tests/test_os.py @@ -104,6 +104,7 @@ def test_chdir(self) -> None: """ Assert that our context manager will change directories and change back to our original directory. + """ original = getcwd() with chdir('/'): diff --git a/noxfile.py b/noxfile.py index 21c4049..8b937a7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -89,7 +89,7 @@ def lint(session): session.install(".[lint]") session.install(".[test]") session.install(".") - session.run("pylint", 'libcsm') + session.run("ruff", "check", "libcsm/") @nox.session(python="3") diff --git a/pyproject.toml b/pyproject.toml index 30b0ca7..b78b326 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,8 @@ lint = [ ] test = [ 'coverage~=7.1', + 'pytest~=7.2', + 'pytest-cov~=4.0', 'ruff~=0.0.280', 'mock~=5.0', ] @@ -95,15 +97,13 @@ universal = false # This app is not compatible with Python2. [tool.ruff] # Enable pycodestyle (`E`), Pyflakes (`F`), docstrings (`D`) -select = ["E", "F", "D"] +select = ["E", "F", "D", "PL"] ignore = [ "D107", # Missing docstring in `__init__` "D200", # One-line docstring should fit on one line "D203", # 1 blank line required before class docstring "D205", # 1 blank line required between summary line and description - "D211", # No blank lines allowed before class docstring - "D212", # Multi-line docstring summary should start at the first line - "E501", # Line too long + "D212" # Multi-line docstring summary should start at the first line ] # Allow autofix for all enabled rules (when `--fix`) is provided. @@ -136,15 +136,7 @@ exclude = [ "venv", ] -# Same as Black. -line-length = 88 +line-length = 120 # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - -# Assume Python 3.11. -target-version = "py311" - -[tool.ruff.mccabe] -# Unlike Flake8, default to a complexity level of 10. -max-complexity = 10