From 4dba3e14a5c967edbcd56f6946d007804478203f Mon Sep 17 00:00:00 2001 From: Tomuta Gabriel Date: Thu, 21 May 2026 16:47:57 +0300 Subject: [PATCH] Fix SonarQube found issues --- .github/actions/ci-setup/action.yml | 6 +- .github/workflows/release.yml | 3 +- ankaios_sdk/_components/complete_state.py | 7 +- ankaios_sdk/_components/control_interface.py | 3 +- ankaios_sdk/_components/manifest.py | 6 +- ankaios_sdk/_components/response.py | 47 +++++----- ankaios_sdk/_components/workload.py | 98 +++++++++++--------- ankaios_sdk/_components/workload_state.py | 6 +- ankaios_sdk/_protos/__init__.py | 7 +- ankaios_sdk/ankaios.py | 80 ++++++++-------- examples/app/Dockerfile | 6 +- examples/apps/sleepy.py | 1 - examples/run_example.sh | 6 +- setup.py | 4 +- tests/response/test_response.py | 2 +- tests/test_ankaios.py | 4 +- tests/test_manifest.py | 2 +- tests/workload/test_workload.py | 11 ++- tools/fetch_protos.sh | 24 ++--- tools/generate_docs.sh | 2 +- tools/install_ankaios.sh | 43 +++++---- tools/update_version.sh | 18 ++-- 22 files changed, 206 insertions(+), 180 deletions(-) diff --git a/.github/actions/ci-setup/action.yml b/.github/actions/ci-setup/action.yml index 7f1e911..b06fa9b 100644 --- a/.github/actions/ci-setup/action.yml +++ b/.github/actions/ci-setup/action.yml @@ -18,10 +18,12 @@ runs: python-version: ${{ inputs.python-version }} - name: Install dependencies + env: + EXTRAS: ${{ inputs.extras }} run: | python3 -m pip install --upgrade pip - if [ -n "${{ inputs.extras }}" ]; then - pip install .[${{ inputs.extras }}] + if [ -n "$EXTRAS" ]; then + pip install ".[$EXTRAS]" else pip install . fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ed0472..520e67e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,8 @@ jobs: release: needs: build - permissions: write-all + permissions: + contents: write runs-on: ubuntu-latest steps: diff --git a/ankaios_sdk/_components/complete_state.py b/ankaios_sdk/_components/complete_state.py index c0db984..223fc1e 100644 --- a/ankaios_sdk/_components/complete_state.py +++ b/ankaios_sdk/_components/complete_state.py @@ -66,7 +66,7 @@ __all__ = ["CompleteState", "AgentAttributes"] -from typing import Union +from typing import Optional, Union from .._protos import _ank_base from .workload import Workload from .workload_state import WorkloadStateCollection @@ -124,7 +124,6 @@ def __init__( workload.name ].CopyFrom(workload._to_proto()) logger.debug("CompleteState initialized from workloads") - return def __str__(self) -> str: """ @@ -153,7 +152,7 @@ def get_api_version(self) -> str: """ return str(self._complete_state.desiredState.apiVersion) - def get_workload(self, workload_name: str) -> Workload: + def get_workload(self, workload_name: str) -> Optional[Workload]: """ Gets a workload from the complete state by its name. @@ -256,7 +255,7 @@ def get_configs(self) -> dict: def _from_config_item( item: _ank_base.ConfigItem, - ) -> Union[str, list, dict]: + ) -> Optional[Union[str, list, dict]]: if item.HasField("String"): return item.String if item.HasField("array"): diff --git a/ankaios_sdk/_components/control_interface.py b/ankaios_sdk/_components/control_interface.py index 9c6ebe7..d7a2173 100644 --- a/ankaios_sdk/_components/control_interface.py +++ b/ankaios_sdk/_components/control_interface.py @@ -390,7 +390,6 @@ def _handle_response(self, response: Response) -> None: "CONTROL_INTERFACE_ACCEPTED. Ignoring..", response.content_type, ) - return # Handle the connected state elif self._state == ControlInterfaceState.CONNECTED: @@ -494,7 +493,7 @@ def write_request(self, request: Request) -> None: raise ConnectionClosedException( "Could not write to pipe, connection closed." ) - if not self._state == ControlInterfaceState.CONNECTED: + if self._state != ControlInterfaceState.CONNECTED: raise ControlInterfaceException( "Could not write to pipe, not connected." ) diff --git a/ankaios_sdk/_components/manifest.py b/ankaios_sdk/_components/manifest.py index aa9d073..c0019ee 100644 --- a/ankaios_sdk/_components/manifest.py +++ b/ankaios_sdk/_components/manifest.py @@ -118,10 +118,10 @@ def from_dict(manifest: dict) -> "Manifest": :rtype: Manifest """ desired_state = _ank_base.State() - if "apiVersion" not in manifest.keys(): + if "apiVersion" not in manifest: raise InvalidManifestException("apiVersion is missing.") desired_state.apiVersion = manifest["apiVersion"] - if "workloads" in manifest.keys(): + if "workloads" in manifest: workloads = manifest["workloads"] for wl_name, wl_data in workloads.items(): try: @@ -133,7 +133,7 @@ def from_dict(manifest: dict) -> "Manifest": raise InvalidManifestException( f"Error building workload {wl_name}: {e}" ) from e - if "configs" in manifest.keys(): + if "configs" in manifest: configs = manifest["configs"] for key, value in configs.items(): desired_state.configs.configs[key].CopyFrom( diff --git a/ankaios_sdk/_components/response.py b/ankaios_sdk/_components/response.py index 8dfcd30..d59538c 100644 --- a/ankaios_sdk/_components/response.py +++ b/ankaios_sdk/_components/response.py @@ -80,7 +80,7 @@ ] from dataclasses import dataclass -from typing import Any, Union +from typing import Any, Optional, Union from enum import Enum from .._protos import _ank_base, _control_api from ..exceptions import ResponseException @@ -114,7 +114,7 @@ def __init__(self, message_buffer: bytes) -> None: """ self.buffer = message_buffer self._response = None - self.content_type: ResponseType = None + self.content_type: Optional[ResponseType] = None self.content = None self._parse_response() @@ -175,23 +175,7 @@ def _from_proto(self) -> None: _proto=self._response.completeStateResponse.completeState ) elif self._response.HasField("UpdateStateSuccess"): - update_state_msg = self._response.UpdateStateSuccess - self.content_type = ResponseType.UPDATE_STATE_SUCCESS - self.content = UpdateStateSuccess() - for workload in update_state_msg.addedWorkloads: - workload_name, workload_id, agent_name = workload.split(".") - self.content.added_workloads.append( - WorkloadInstanceName( - agent_name, workload_name, workload_id - ) - ) - for workload in update_state_msg.deletedWorkloads: - workload_name, workload_id, agent_name = workload.split(".") - self.content.deleted_workloads.append( - WorkloadInstanceName( - agent_name, workload_name, workload_id - ) - ) + self._parse_update_state_success() elif self._response.HasField("logEntriesResponse"): self.content_type = ResponseType.LOGS_ENTRY self.content = [] @@ -222,12 +206,31 @@ def _from_proto(self) -> None: else: raise ResponseException("Invalid response type.") - def get_request_id(self) -> str: + def _parse_update_state_success(self) -> None: + """ + Parses the UpdateStateSuccess response type and + populates content_type and content. + """ + update_state_msg = self._response.UpdateStateSuccess + self.content_type = ResponseType.UPDATE_STATE_SUCCESS + self.content = UpdateStateSuccess() + for workload in update_state_msg.addedWorkloads: + workload_name, workload_id, agent_name = workload.split(".") + self.content.added_workloads.append( + WorkloadInstanceName(agent_name, workload_name, workload_id) + ) + for workload in update_state_msg.deletedWorkloads: + workload_name, workload_id, agent_name = workload.split(".") + self.content.deleted_workloads.append( + WorkloadInstanceName(agent_name, workload_name, workload_id) + ) + + def get_request_id(self) -> Optional[str]: """ Gets the request id of the response. - :returns: The request id of the response. - :rtype: str + :returns: The request id of the response, or None if not applicable. + :rtype: Optional[str] """ if self.content_type in [ ResponseType.CONTROL_INTERFACE_ACCEPTED, diff --git a/ankaios_sdk/_components/workload.py b/ankaios_sdk/_components/workload.py index 7b41d21..d4080a4 100644 --- a/ankaios_sdk/_components/workload.py +++ b/ankaios_sdk/_components/workload.py @@ -409,6 +409,21 @@ def _add_mask(self, mask: str) -> None: if self._main_mask not in self._masks and mask not in self._masks: self._masks.append(mask) + def _control_interface_access_to_dict(self) -> dict: + """ + Convert the control interface access rules to a dictionary. + + :returns: The dictionary representation of the control + interface access rules. + :rtype: dict + """ + result = {"allowRules": [], "denyRules": []} + for rule in self._workload.controlInterfaceAccess.allowRules: + result["allowRules"].append(AccessRightRule(rule).to_dict()) + for rule in self._workload.controlInterfaceAccess.denyRules: + result["denyRules"].append(AccessRightRule(rule).to_dict()) + return result + # pylint: disable=too-many-branches def to_dict(self) -> dict: """ @@ -440,20 +455,9 @@ def to_dict(self) -> dict: if self._workload.tags: for key, value in self._workload.tags.tags.items(): workload_dict["tags"].update({key: value}) - workload_dict["controlInterfaceAccess"] = {} - if self._workload.controlInterfaceAccess: - workload_dict["controlInterfaceAccess"]["allowRules"] = [] - for rule in self._workload.controlInterfaceAccess.allowRules: - access_rule = AccessRightRule(rule) - workload_dict["controlInterfaceAccess"]["allowRules"].append( - access_rule.to_dict() - ) - workload_dict["controlInterfaceAccess"]["denyRules"] = [] - for rule in self._workload.controlInterfaceAccess.denyRules: - access_rule = AccessRightRule(rule) - workload_dict["controlInterfaceAccess"]["denyRules"].append( - access_rule.to_dict() - ) + workload_dict["controlInterfaceAccess"] = ( + self._control_interface_access_to_dict() + ) workload_dict["configs"] = {} for alias, name in self._workload.configs.configs.items(): workload_dict["configs"][alias] = name @@ -462,7 +466,30 @@ def to_dict(self) -> dict: workload_dict["files"].append(File._from_proto(file).to_dict()) return workload_dict - # pylint: disable=too-many-branches + @staticmethod + def _apply_control_interface_access_from_dict( + workload, cia_dict: dict + ): + """ + Apply control interface access rules from a dictionary + to a workload builder. + + :param workload: The workload builder to apply rules to. + :param cia_dict: The control interface access dictionary. + :type cia_dict: dict + + :returns: The updated workload builder. + """ + for rule in cia_dict.get("allowRules", []): + workload = workload.add_allow_state_rule( + rule["operation"], rule["filterMask"] + ) + for rule in cia_dict.get("denyRules", []): + workload = workload.add_deny_state_rule( + rule["operation"], rule["filterMask"] + ) + return workload + @staticmethod def _from_dict(workload_name: str, dict_workload: dict) -> "Workload": """ @@ -485,34 +512,19 @@ def _from_dict(workload_name: str, dict_workload: dict) -> "Workload": workload = workload.runtime_config(dict_workload["runtimeConfig"]) if "restartPolicy" in dict_workload: workload = workload.restart_policy(dict_workload["restartPolicy"]) - if "dependencies" in dict_workload: - for dep_key, dep_value in dict_workload["dependencies"].items(): - workload = workload.add_dependency(dep_key, dep_value) - if "tags" in dict_workload: - for key, value in dict_workload["tags"].items(): - workload = workload.add_tag(key, value) - if "controlInterfaceAccess" in dict_workload: - if "allowRules" in dict_workload["controlInterfaceAccess"]: - for rule in dict_workload["controlInterfaceAccess"][ - "allowRules" - ]: - workload = workload.add_allow_state_rule( - rule["operation"], rule["filterMask"] - ) - if "denyRules" in dict_workload["controlInterfaceAccess"]: - for rule in dict_workload["controlInterfaceAccess"][ - "denyRules" - ]: - workload = workload.add_deny_state_rule( - rule["operation"], rule["filterMask"] - ) - if "configs" in dict_workload: - for alias, name in dict_workload["configs"].items(): - workload = workload.add_config(alias, name) - if "files" in dict_workload: - for file in dict_workload["files"]: - workload = workload.add_file(File._from_dict(file)) - + for dep_key, dep_value in dict_workload.get( + "dependencies", {} + ).items(): + workload = workload.add_dependency(dep_key, dep_value) + for key, value in dict_workload.get("tags", {}).items(): + workload = workload.add_tag(key, value) + workload = Workload._apply_control_interface_access_from_dict( + workload, dict_workload.get("controlInterfaceAccess", {}) + ) + for alias, name in dict_workload.get("configs", {}).items(): + workload = workload.add_config(alias, name) + for file in dict_workload.get("files", []): + workload = workload.add_file(File._from_dict(file)) return workload.build() def _to_proto(self) -> _ank_base.Workload: diff --git a/ankaios_sdk/_components/workload_state.py b/ankaios_sdk/_components/workload_state.py index c20445f..12bb572 100644 --- a/ankaios_sdk/_components/workload_state.py +++ b/ankaios_sdk/_components/workload_state.py @@ -244,9 +244,9 @@ def __init__(self, state: _ank_base.ExecutionState) -> None: :param state: The execution state to interpret. :type state: _ank_base.ExecutionState """ - self.state: WorkloadStateEnum = None - self.substate: WorkloadSubStateEnum = None - self.additional_info: str = None + self.state: Optional[WorkloadStateEnum] = None + self.substate: Optional[WorkloadSubStateEnum] = None + self.additional_info: Optional[str] = None self._interpret_state(state) diff --git a/ankaios_sdk/_protos/__init__.py b/ankaios_sdk/_protos/__init__.py index c0a0c94..dd6d8a7 100644 --- a/ankaios_sdk/_protos/__init__.py +++ b/ankaios_sdk/_protos/__init__.py @@ -25,10 +25,7 @@ Used for exchanging messages with the control interface. """ -try: - import ankaios_sdk._protos.ank_base_pb2 as _ank_base - import ankaios_sdk._protos.control_api_pb2 as _control_api -except ImportError as r: - raise r +import ankaios_sdk._protos.ank_base_pb2 as _ank_base +import ankaios_sdk._protos.control_api_pb2 as _control_api __all__ = ["_ank_base", "_control_api"] diff --git a/ankaios_sdk/ankaios.py b/ankaios_sdk/ankaios.py index 0364e09..bd42d3c 100644 --- a/ankaios_sdk/ankaios.py +++ b/ankaios_sdk/ankaios.py @@ -171,6 +171,14 @@ ) +_UPDATE_SUCCESS_MSG = ( + "Update successful: %s added workloads, %s deleted workloads." +) +_UNEXPECTED_CONTENT_TYPE_MSG = "Received unexpected content type." +_SET_CONFIGS_ERROR_MSG = "Error while trying to set the configs: %s" +_UPDATE_SUCCESSFUL_MSG = "Update successful" + + # pylint: disable=too-many-public-methods, too-many-instance-attributes # pylint: disable=too-many-lines class Ankaios: @@ -418,13 +426,12 @@ def apply_manifest( raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: self.logger.info( - "Update successful: %s added workloads, " - + "%s deleted workloads.", + _UPDATE_SUCCESS_MSG, len(content.added_workloads), len(content.deleted_workloads), ) return content - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def delete_manifest( self, manifest: Manifest, timeout: float = DEFAULT_TIMEOUT @@ -468,13 +475,12 @@ def delete_manifest( raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: self.logger.info( - "Update successful: %s added workloads, " - + "%s deleted workloads.", + _UPDATE_SUCCESS_MSG, len(content.added_workloads), len(content.deleted_workloads), ) return content - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def apply_workload( self, workload: Workload, timeout: float = DEFAULT_TIMEOUT @@ -523,13 +529,12 @@ def apply_workload( raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: self.logger.info( - "Update successful: %s added workloads, " - + "%s deleted workloads.", + _UPDATE_SUCCESS_MSG, len(content.added_workloads), len(content.deleted_workloads), ) return content - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def get_workload( self, workload_name: str, timeout: float = DEFAULT_TIMEOUT @@ -598,13 +603,12 @@ def delete_workload( raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: self.logger.info( - "Update successful: %s added workloads, " - + "%s deleted workloads.", + _UPDATE_SUCCESS_MSG, len(content.added_workloads), len(content.deleted_workloads), ) return content - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def update_configs(self, configs: dict, timeout: float = DEFAULT_TIMEOUT): """ @@ -637,13 +641,13 @@ def update_configs(self, configs: dict, timeout: float = DEFAULT_TIMEOUT): (content_type, content) = response.get_content() if content_type == ResponseType.ERROR: self.logger.error( - "Error while trying to set the configs: %s", content + _SET_CONFIGS_ERROR_MSG, content ) raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: - self.logger.info("Update successful") + self.logger.info(_UPDATE_SUCCESSFUL_MSG) return - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def add_config( self, @@ -688,9 +692,9 @@ def add_config( ) raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: - self.logger.info("Update successful") + self.logger.info(_UPDATE_SUCCESSFUL_MSG) return - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def get_configs(self, timeout: float = DEFAULT_TIMEOUT) -> dict: """ @@ -709,10 +713,9 @@ def get_configs(self, timeout: float = DEFAULT_TIMEOUT) -> dict: the state. :raises ConnectionClosedException: If the connection is closed. """ - return ( - self.get_state(field_masks=[CONFIGS_PREFIX]).get_configs(), - timeout, - ) + return self.get_state( + field_masks=[CONFIGS_PREFIX], timeout=timeout + ).get_configs() def get_config(self, name: str, timeout: float = DEFAULT_TIMEOUT) -> dict: """ @@ -733,12 +736,9 @@ def get_config(self, name: str, timeout: float = DEFAULT_TIMEOUT) -> dict: the state. :raises ConnectionClosedException: If the connection is closed. """ - return ( - self.get_state( - field_masks=[f"{CONFIGS_PREFIX}.{name}"] - ).get_configs(), - timeout, - ) + return self.get_state( + field_masks=[f"{CONFIGS_PREFIX}.{name}"], timeout=timeout + ).get_configs() def delete_all_configs(self, timeout: float = DEFAULT_TIMEOUT): """ @@ -771,9 +771,9 @@ def delete_all_configs(self, timeout: float = DEFAULT_TIMEOUT): ) raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: - self.logger.info("Update successful") + self.logger.info(_UPDATE_SUCCESSFUL_MSG) return - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def delete_config(self, name: str, timeout: float = DEFAULT_TIMEOUT): """ @@ -810,9 +810,9 @@ def delete_config(self, name: str, timeout: float = DEFAULT_TIMEOUT): ) raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: - self.logger.info("Update successful") + self.logger.info(_UPDATE_SUCCESSFUL_MSG) return - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def get_state( self, @@ -853,7 +853,7 @@ def get_state( raise AnkaiosResponseError(content) if content_type == ResponseType.COMPLETE_STATE: return content - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def set_agent_tags( self, @@ -899,9 +899,9 @@ def set_agent_tags( ) raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.UPDATE_STATE_SUCCESS: - self.logger.info("Update successful") + self.logger.info(_UPDATE_SUCCESSFUL_MSG) return - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def get_agents(self, timeout: float = DEFAULT_TIMEOUT) -> dict: """ @@ -1166,7 +1166,7 @@ def request_logs( (content_type, content) = response.get_content() if content_type == ResponseType.ERROR: self.logger.error( - "Error while trying to set the configs: %s", content + _SET_CONFIGS_ERROR_MSG, content ) raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.LOGS_REQUEST_ACCEPTED: @@ -1175,7 +1175,7 @@ def request_logs( return LogCampaignResponse( queue=log_queue, accepted_workload_names=content ) - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def stop_receiving_logs( self, @@ -1205,14 +1205,14 @@ def stop_receiving_logs( (content_type, content) = response.get_content() if content_type == ResponseType.ERROR: self.logger.error( - "Error while trying to set the configs: %s", content + _SET_CONFIGS_ERROR_MSG, content ) raise AnkaiosResponseError(f"Received error: {content}") if content_type == ResponseType.LOGS_CANCEL_ACCEPTED: self.logger.info("Logs cancel request accepted.") self._logs_callbacks.pop(request.get_id(), None) return None - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def register_event( self, field_masks: list[str], timeout: float = DEFAULT_TIMEOUT @@ -1259,7 +1259,7 @@ def register_event( event_queue.put(initial_entry) self._events_callbacks[request.get_id()] = event_queue.put return event_queue - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) def unregister_event( self, event_queue: "EventQueue", timeout: float = DEFAULT_TIMEOUT @@ -1298,4 +1298,4 @@ def unregister_event( self.logger.info("Event unregister request accepted.") self._events_callbacks.pop(request.get_id(), None) return None - raise AnkaiosProtocolException("Received unexpected content type.") + raise AnkaiosProtocolException(_UNEXPECTED_CONTENT_TYPE_MSG) diff --git a/examples/app/Dockerfile b/examples/app/Dockerfile index 6358a54..9f2413b 100644 --- a/examples/app/Dockerfile +++ b/examples/app/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-slim-bookworm as base +FROM python:3.12-slim-bookworm AS base RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install \ @@ -8,7 +8,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ WORKDIR /usr/src/app -FROM base as prod +FROM base AS prod COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt @@ -16,7 +16,7 @@ COPY app.py ./ CMD [ "python", "-u", "./app.py" ] -FROM base as dev +FROM base AS dev COPY . ./ank-sdk-python RUN pip install ./ank-sdk-python diff --git a/examples/apps/sleepy.py b/examples/apps/sleepy.py index 6eb5005..6514d75 100644 --- a/examples/apps/sleepy.py +++ b/examples/apps/sleepy.py @@ -19,7 +19,6 @@ def signal_handler(sig, frame): - still_sleepy = False sys.exit(0) diff --git a/examples/run_example.sh b/examples/run_example.sh index 2f33823..c9eea31 100755 --- a/examples/run_example.sh +++ b/examples/run_example.sh @@ -39,19 +39,19 @@ run_ankaios() { exit $? } -if [ -z $1 ]; then +if [[ -z $1 ]]; then display_usage exit 1 fi # Check if app exists and copy it to the example directory -if [ ! -f "apps/$1.py" ]; then +if [[ ! -f "apps/$1.py" ]]; then echo "Python app '$1.py' not found!" exit 2 fi cp -f apps/$1.py app/app.py -if [ -z ${ANK_BIN_DIR} ]; then +if [[ -z ${ANK_BIN_DIR} ]]; then ANK_BIN_DIR=${DEFAULT_ANKAIOS_BIN_PATH} fi diff --git a/setup.py b/setup.py index 86d36e0..3b0d021 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,7 @@ def generate_protos(): for proto_file in PROTO_FILES: proto_path = os.path.join(protos_dir, proto_file) if not os.path.exists(proto_path): - raise Exception(f"Error: {proto_file} not found.") + raise FileNotFoundError(f"Error: {proto_file} not found.") output_file = proto_path.replace(".proto", "_pb2.py") if not os.path.exists(output_file) or os.path.getmtime( @@ -99,7 +99,7 @@ def generate_protos(): proto_path, ] if protoc.main(command) != 0: - raise Exception(f"Error: {proto_file} compilation failed") + raise RuntimeError(f"Error: {proto_file} compilation failed") # Fix the import path in the generated control_api_pb2 # https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-261914766 diff --git a/tests/response/test_response.py b/tests/response/test_response.py index d25c31f..a644709 100644 --- a/tests/response/test_response.py +++ b/tests/response/test_response.py @@ -249,7 +249,7 @@ def test_initialisation(): # Test invalid response type with pytest.raises(ResponseException, match="Invalid response type"): - response = Response(MESSAGE_BUFFER_INVALID_RESPONSE) + _ = Response(MESSAGE_BUFFER_INVALID_RESPONSE) def test_getters(): diff --git a/tests/test_ankaios.py b/tests/test_ankaios.py index 8204ac1..165a1a5 100644 --- a/tests/test_ankaios.py +++ b/tests/test_ankaios.py @@ -565,7 +565,7 @@ def test_get_configs(): mock_get_state.return_value = CompleteState() ankaios.get_configs() mock_get_state.assert_called_once_with( - field_masks=["desiredState.configs"] + field_masks=["desiredState.configs"], timeout=5.0 ) mock_state_get_configs.assert_called_once() @@ -582,7 +582,7 @@ def test_get_config(): mock_get_state.return_value = CompleteState() ankaios.get_config("config_name") mock_get_state.assert_called_once_with( - field_masks=["desiredState.configs.config_name"] + field_masks=["desiredState.configs.config_name"], timeout=5.0 ) mock_state_get_configs.assert_called_once() diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 7e88899..f72d0cb 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -121,7 +121,7 @@ def test_from_dict(): with pytest.raises( InvalidManifestException, match="apiVersion is missing." ): - manifest = Manifest.from_dict({}) + _ = Manifest.from_dict({}) with pytest.raises(InvalidManifestException): _ = Manifest.from_dict( diff --git a/tests/workload/test_workload.py b/tests/workload/test_workload.py index a684b21..a97bb61 100644 --- a/tests/workload/test_workload.py +++ b/tests/workload/test_workload.py @@ -220,7 +220,7 @@ def test_tags(workload: Workload): # pylint: disable=redefined-outer-name assert len(workload.get_tags()) == 2 tags = workload.get_tags() - _ = tags.pop(list(tags.keys())[0]) + _ = tags.pop(next(iter(tags.keys()))) workload.update_tags(tags) assert len(workload.get_tags()) == 1 @@ -356,6 +356,15 @@ def test_from_to_dict(): assert str(workload_new) == str(workload_other) +def test_control_interface_access_to_dict_empty(): + """Test _control_interface_access_to_dict returns empty dict + when no control interface access is set.""" + empty_workload = Workload("test") + assert empty_workload._control_interface_access_to_dict() == { + "allowRules": [], "denyRules": [] + } + + @pytest.mark.parametrize( "function_name, data, mask", [ diff --git a/tools/fetch_protos.sh b/tools/fetch_protos.sh index 110ae5a..b744a6f 100755 --- a/tools/fetch_protos.sh +++ b/tools/fetch_protos.sh @@ -17,12 +17,12 @@ usage() { } # Parse arguments -if [ $# -lt 1 ]; then +if [[ $# -lt 1 ]]; then echo "Error: Missing required argument " >&2 usage fi -if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then +if [[ "$1" = "--help" ]] || [[ "$1" = "-h" ]]; then usage fi @@ -31,10 +31,10 @@ shift # Parse optional version argument SDK_VERSION="" -while [ $# -gt 0 ]; do +while [[ $# -gt 0 ]]; do case "$1" in --version|-v) - if [ -z "$2" ]; then + if [[ -z "$2" ]]; then echo "Error: version flag requires a value" >&2 exit 1 fi @@ -52,16 +52,16 @@ while [ $# -gt 0 ]; do done # Extract SDK version from setup.cfg if not provided -if [ -z "$SDK_VERSION" ]; then +if [[ -z "$SDK_VERSION" ]]; then SETUP_CFG="setup.cfg" - if [ ! -f "$SETUP_CFG" ]; then + if [[ ! -f "$SETUP_CFG" ]]; then echo "Error: setup.cfg not found in current directory" >&2 exit 1 fi SDK_VERSION=$(grep "^version = " "$SETUP_CFG" | sed 's/^version = //' | tr -d ' ') - if [ -z "$SDK_VERSION" ]; then + if [[ -z "$SDK_VERSION" ]]; then echo "Error: Could not extract version from $SETUP_CFG" >&2 exit 1 fi @@ -75,16 +75,16 @@ BASE_URL="https://raw.githubusercontent.com/eclipse-ankaios/ankaios/refs/heads/$ PROTO_FILES=("ank_base.proto" "control_api.proto") # Clean and create target directory -if [ -d "$PROTO_DIR" ]; then +if [[ -d "$PROTO_DIR" ]]; then rm -rf "$PROTO_DIR" - if [ $? -ne 0 ]; then + if [[ $? -ne 0 ]]; then echo "Error: Failed to clean directory $PROTO_DIR" >&2 exit 1 fi fi mkdir -p "$PROTO_DIR" -if [ $? -ne 0 ]; then +if [[ $? -ne 0 ]]; then echo "Error: Failed to create directory $PROTO_DIR" >&2 exit 1 fi @@ -96,14 +96,14 @@ for PROTO_FILE in "${PROTO_FILES[@]}"; do HTTP_CODE=$(curl -s -w "%{http_code}" -o "$TARGET_PATH" "$URL") - if [ "$HTTP_CODE" -ne 200 ]; then + if [[ "$HTTP_CODE" -ne 200 ]]; then echo "Error: Failed to download $PROTO_FILE (HTTP $HTTP_CODE)" >&2 echo "URL: $URL" >&2 rm -f "$TARGET_PATH" exit 1 fi - if [ ! -s "$TARGET_PATH" ]; then + if [[ ! -s "$TARGET_PATH" ]]; then echo "Error: Downloaded $PROTO_FILE is empty" >&2 rm -f "$TARGET_PATH" exit 1 diff --git a/tools/generate_docs.sh b/tools/generate_docs.sh index caf5a7e..3efeb7a 100755 --- a/tools/generate_docs.sh +++ b/tools/generate_docs.sh @@ -32,7 +32,7 @@ usage() { exit 0 } -if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then +if [[ "$1" = "--help" ]] || [[ "$1" = "-h" ]]; then usage fi diff --git a/tools/install_ankaios.sh b/tools/install_ankaios.sh index 0b6d97c..805e898 100755 --- a/tools/install_ankaios.sh +++ b/tools/install_ankaios.sh @@ -57,12 +57,14 @@ multiple_arguments_error() { } parse_arguments() { - while [ "$#" -gt 0 ]; do - case "$1" in + local arg + while [[ "$#" -gt 0 ]]; do + arg="$1" + case "$arg" in --branch|-b) shift branch_name="$1" - if [ -n "$mode" ]; then + if [[ -n "$mode" ]]; then multiple_arguments_error fi mode="branch" @@ -70,7 +72,7 @@ parse_arguments() { --action|-a) shift action_id="$1" - if [ -n "$mode" ]; then + if [[ -n "$mode" ]]; then multiple_arguments_error fi mode="action" @@ -78,14 +80,14 @@ parse_arguments() { --version|-v) shift version="$1" - if [ -n "$mode" ]; then + if [[ -n "$mode" ]]; then multiple_arguments_error fi mode="version" ;; --latest|-l) version="latest" - if [ -n "$mode" ]; then + if [[ -n "$mode" ]]; then multiple_arguments_error fi mode="version" @@ -98,7 +100,7 @@ parse_arguments() { usage ;; *) - echo "Unknown argument: $1" + echo "Unknown argument: $arg" usage ;; esac @@ -106,12 +108,12 @@ parse_arguments() { done } -if [ "$#" -eq 0 ]; then +if [[ "$#" -eq 0 ]]; then usage fi parse_arguments "$@" -if [ -z "$mode" ]; then +if [[ -z "$mode" ]]; then echo "Error: One of --branch, --action, --version, or --latest must be specified." >&2 usage fi @@ -132,14 +134,14 @@ target=$(get_target) # Install by version install_by_version() { - if [ "$version" = "latest" ]; then + if [[ "$version" = "latest" ]]; then curl -sfL "$INSTALL_LATEST_URL" | bash - else VERSION_URL=${INSTALL_VERSION_URL///$version} curl -sfL "$VERSION_URL" | bash -s -- -v "$version" fi } -if [ "$mode" = "version" ]; then +if [[ "$mode" = "version" ]]; then install_by_version exit 0 fi @@ -147,7 +149,8 @@ fi # Install by branch branch_exists() { - if curl -s "https://api.github.com/repos/$REPO/branches/$1" | grep -q '"name":'; then + local branch="$1" + if curl -s "https://api.github.com/repos/$REPO/branches/$branch" | grep -q '"name":'; then return 0 else return 1 @@ -158,7 +161,7 @@ get_action_id_for_branch() { WORKFLOW_ID=$(curl -s "https://api.github.com/repos/$REPO/actions/workflows/$WORKFLOW_NAME" | \ python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))") - if [ -z "$WORKFLOW_ID" ]; then + if [[ -z "$WORKFLOW_ID" ]]; then echo "Error: Workflow '$WORKFLOW_NAME' not found" >&2 exit 1 fi @@ -180,14 +183,14 @@ for run in data.get('workflow_runs', []): sys.exit(0) ") - if [ -z "$RUN_ID" ]; then + if [[ -z "$RUN_ID" ]]; then echo "Error: No runs with successful '$job_name' job found on branch '$branch_name'" >&2 exit 1 fi echo "$RUN_ID" } -if [ "$mode" = "branch" ]; then +if [[ "$mode" = "branch" ]]; then if ! branch_exists "$branch_name"; then echo "Error: Branch '$branch_name' does not exist in repository '$REPO'" >&2 exit 1 @@ -235,7 +238,7 @@ fi echo "Downloading artifact..." provide_token() { - if [ -n "$GITHUB_TOKEN" ]; then + if [[ -n "$GITHUB_TOKEN" ]]; then gh_token="$GITHUB_TOKEN" else echo -n "Enter GitHub token: " @@ -249,7 +252,7 @@ check_token() { fi } -if [ -z "$gh_token" ]; then +if [[ -z "$gh_token" ]]; then provide_token fi check_token @@ -261,7 +264,7 @@ HTTP_CODE=$(curl -L -w "%{http_code}" -o "$TEMP_DIR/artifact.zip" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "https://api.github.com/repos/$REPO/actions/artifacts/$ARTIFACT_ID/zip") -if [ "$HTTP_CODE" != "200" ]; then +if [[ "$HTTP_CODE" != "200" ]]; then echo "Error: Failed to download artifact (HTTP $HTTP_CODE)" >&2 exit 1 fi @@ -271,7 +274,7 @@ cd "$TEMP_DIR" unzip -q artifact.zip # Verify binaries -if [ ! -f "ank" ] || [ ! -f "ank-server" ] || [ ! -f "ank-agent" ]; then +if [[ ! -f "ank" ]] || [[ ! -f "ank-server" ]] || [[ ! -f "ank-agent" ]]; then echo "Error: Expected binaries not found in artifact" >&2 exit 1 fi @@ -279,7 +282,7 @@ fi # Install binaries echo "Installing binaries to $INSTALL_PATH..." -if [ -w "$INSTALL_PATH" ]; then +if [[ -w "$INSTALL_PATH" ]]; then SUDO="" else SUDO="sudo" diff --git a/tools/update_version.sh b/tools/update_version.sh index 3b14a3a..565c40c 100755 --- a/tools/update_version.sh +++ b/tools/update_version.sh @@ -37,8 +37,10 @@ usage() { } parse_arguments() { - while [ "$#" -gt 0 ]; do - case "$1" in + local arg + while [[ "$#" -gt 0 ]]; do + arg="$1" + case "$arg" in --sdk) shift sdk_version="$1" @@ -55,7 +57,7 @@ parse_arguments() { usage ;; *) - echo "Unknown argument: $1" + echo "Unknown argument: $arg" usage ;; esac @@ -63,29 +65,29 @@ parse_arguments() { done } -if [ "$#" -eq 0 ]; then +if [[ "$#" -eq 0 ]]; then usage fi parse_arguments "$@" -if [ -z "$sdk_version" ] && [ -z "$ankaios_version" ] && [ -z "$api_version" ]; then +if [[ -z "$sdk_version" ]] && [[ -z "$ankaios_version" ]] && [[ -z "$api_version" ]]; then echo "You must specify at least one version to update." usage fi -if [ -n "$sdk_version" ]; then +if [[ -n "$sdk_version" ]]; then echo "Updating SDK version to $sdk_version" sed -i "s/^version = .*/version = $sdk_version/" "$base_dir"/setup.cfg fi -if [ -n "$ankaios_version" ]; then +if [[ -n "$ankaios_version" ]]; then echo "Updating Ankaios version to $ankaios_version" sed -i "s/^ankaios_version = .*/ankaios_version = $ankaios_version/" "$base_dir"/setup.cfg sed -i "s/^ANKAIOS_VERSION = .*/ANKAIOS_VERSION = \"$ankaios_version\"/" "$base_dir"/ankaios_sdk/utils.py fi -if [ -n "$api_version" ]; then +if [[ -n "$api_version" ]]; then echo "Updating API version to $api_version" sed -i "s/^SUPPORTED_API_VERSION = .*/SUPPORTED_API_VERSION = \"$api_version\"/" "$base_dir"/ankaios_sdk/utils.py fi