"""Services mutations""" # pylint: disable=too-few-public-methods from typing import Optional import strawberry from selfprivacy_api.utils import pretty_error from selfprivacy_api.graphql import IsAuthenticated from selfprivacy_api.graphql.common_types.jobs import job_to_api_job from selfprivacy_api.jobs import JobStatus from selfprivacy_api.graphql.mutations.mutation_interface import ( GenericJobMutationReturn, GenericMutationReturn, ) from selfprivacy_api.graphql.common_types.service import ( Service, service_to_graphql_service, ) from selfprivacy_api.actions.services import ( move_service, ServiceNotFoundError, VolumeNotFoundError, ) from selfprivacy_api.services import ServiceManager @strawberry.type class ServiceMutationReturn(GenericMutationReturn): """Service mutation return type.""" service: Optional[Service] = None @strawberry.input class SetServiceConfigurationInput: """Set service configuration input type. The values might be of different types: str or bool. """ service_id: str configuration: strawberry.scalars.JSON """Yes, it is a JSON scalar, which is supposed to be a Map. I can't define it as a proper type because GraphQL doesn't support unions in input types. There is a @oneOf directive, but it doesn't fit this usecase. Other option would have been doing something like this: ```python @strawberry.type class StringConfigurationInputField: fieldId: str value: str @strawberry.type class BoolConfigurationInputField: fieldId: str value: bool // ... @strawberry.input class SetServiceConfigurationInput: service_id: str stringFields: List[StringConfigurationInputField] boolFields: List[BoolConfigurationInputField] enumFields: List[EnumConfigurationInputField] intFields: List[IntConfigurationInputField] ``` But it would be very painful to maintain and will break compatibility with every change. Be careful when parsing it. Probably it will be wise to add a parser/validator later when we get a new Pydantic integration in Strawberry. -- Inex, 26.07.2024 """ @strawberry.input class MoveServiceInput: """Move service input type.""" service_id: str location: str @strawberry.type class ServiceJobMutationReturn(GenericJobMutationReturn): """Service job mutation return type.""" service: Optional[Service] = None @strawberry.type class ServicesMutations: """Services mutations.""" @strawberry.mutation(permission_classes=[IsAuthenticated]) def enable_service(self, service_id: str) -> ServiceMutationReturn: """Enable service.""" try: service = ServiceManager.get_service_by_id(service_id) if service is None: return ServiceMutationReturn( success=False, message="Service not found.", code=404, ) service.enable() except Exception as e: return ServiceMutationReturn( success=False, message=pretty_error(e), code=400, ) return ServiceMutationReturn( success=True, message="Service enabled.", code=200, service=service_to_graphql_service(service), ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def disable_service(self, service_id: str) -> ServiceMutationReturn: """Disable service.""" try: service = ServiceManager.get_service_by_id(service_id) if service is None: return ServiceMutationReturn( success=False, message="Service not found.", code=404, ) service.disable() except Exception as e: return ServiceMutationReturn( success=False, message=pretty_error(e), code=400, ) return ServiceMutationReturn( success=True, message="Service disabled.", code=200, service=service_to_graphql_service(service), ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def stop_service(self, service_id: str) -> ServiceMutationReturn: """Stop service.""" service = ServiceManager.get_service_by_id(service_id) if service is None: return ServiceMutationReturn( success=False, message="Service not found.", code=404, ) service.stop() return ServiceMutationReturn( success=True, message="Service stopped.", code=200, service=service_to_graphql_service(service), ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def start_service(self, service_id: str) -> ServiceMutationReturn: """Start service.""" service = ServiceManager.get_service_by_id(service_id) if service is None: return ServiceMutationReturn( success=False, message="Service not found.", code=404, ) service.start() return ServiceMutationReturn( success=True, message="Service started.", code=200, service=service_to_graphql_service(service), ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def restart_service(self, service_id: str) -> ServiceMutationReturn: """Restart service.""" service = ServiceManager.get_service_by_id(service_id) if service is None: return ServiceMutationReturn( success=False, message="Service not found.", code=404, ) service.restart() return ServiceMutationReturn( success=True, message="Service restarted.", code=200, service=service_to_graphql_service(service), ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def set_service_configuration( self, input: SetServiceConfigurationInput ) -> ServiceMutationReturn: """Set the new configuration values""" service = ServiceManager.get_service_by_id(input.service_id) if service is None: return ServiceMutationReturn( success=False, message=f"Service does not exist: {input.service_id}", code=404, ) try: service.set_configuration(input.configuration) return ServiceMutationReturn( success=True, message="Service configuration updated.", code=200, service=service_to_graphql_service(service), ) except ValueError as e: return ServiceMutationReturn( success=False, message=e.args[0], code=400, service=service_to_graphql_service(service), ) except Exception as e: return ServiceMutationReturn( success=False, message=pretty_error(e), code=400, service=service_to_graphql_service(service), ) @strawberry.mutation(permission_classes=[IsAuthenticated]) def move_service(self, input: MoveServiceInput) -> ServiceJobMutationReturn: """Move service.""" # We need a service instance for a reply later service = ServiceManager.get_service_by_id(input.service_id) if service is None: return ServiceJobMutationReturn( success=False, message=f"Service does not exist: {input.service_id}", code=404, ) try: job = move_service(input.service_id, input.location) except (ServiceNotFoundError, VolumeNotFoundError) as e: return ServiceJobMutationReturn( success=False, message=pretty_error(e), code=404, ) except Exception as e: return ServiceJobMutationReturn( success=False, message=pretty_error(e), code=400, service=service_to_graphql_service(service), ) if job.status in [JobStatus.CREATED, JobStatus.RUNNING]: return ServiceJobMutationReturn( success=True, message="Started moving the service.", code=200, service=service_to_graphql_service(service), job=job_to_api_job(job), ) elif job.status == JobStatus.FINISHED: return ServiceJobMutationReturn( success=True, message="Service moved.", code=200, service=service_to_graphql_service(service), job=job_to_api_job(job), ) else: return ServiceJobMutationReturn( success=False, message=f"While moving service and performing the step '{job.status_text}', error occured: {job.error}", code=400, service=service_to_graphql_service(service), job=job_to_api_job(job), )