2023-02-03 17:04:35 +00:00
|
|
|
"""Class representing Bitwarden service"""
|
|
|
|
import base64
|
|
|
|
import typing
|
2023-07-10 17:03:10 +00:00
|
|
|
import subprocess
|
|
|
|
|
2023-04-14 11:20:03 +00:00
|
|
|
from typing import List
|
2023-07-10 17:03:10 +00:00
|
|
|
from os import path
|
|
|
|
|
|
|
|
# from enum import Enum
|
2023-02-03 17:04:35 +00:00
|
|
|
|
2023-10-11 17:19:45 +00:00
|
|
|
from selfprivacy_api.jobs import Job, Jobs, JobStatus
|
2023-02-03 17:04:35 +00:00
|
|
|
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
|
|
|
|
from selfprivacy_api.utils.block_devices import BlockDevice
|
2023-10-11 17:19:45 +00:00
|
|
|
from selfprivacy_api.services.generic_service_mover import move_service, FolderMoveNames
|
2023-02-03 17:04:35 +00:00
|
|
|
import selfprivacy_api.utils.network as network_utils
|
|
|
|
|
|
|
|
from selfprivacy_api.services.test_service.icon import BITWARDEN_ICON
|
|
|
|
|
2023-07-10 17:03:10 +00:00
|
|
|
DEFAULT_DELAY = 0
|
|
|
|
|
2023-02-03 17:04:35 +00:00
|
|
|
|
|
|
|
class DummyService(Service):
|
|
|
|
"""A test service"""
|
|
|
|
|
2023-07-10 17:03:10 +00:00
|
|
|
folders: List[str] = []
|
2023-10-06 10:45:46 +00:00
|
|
|
startstop_delay = 0.0
|
2023-07-19 15:09:49 +00:00
|
|
|
backuppable = True
|
2023-10-06 13:17:48 +00:00
|
|
|
movable = True
|
2023-10-11 17:19:45 +00:00
|
|
|
# if False, we try to actually move
|
|
|
|
simulate_moving = True
|
|
|
|
drive = "sda1"
|
2023-07-10 17:03:10 +00:00
|
|
|
|
2023-04-14 12:29:23 +00:00
|
|
|
def __init_subclass__(cls, folders: List[str]):
|
|
|
|
cls.folders = folders
|
2023-02-03 17:04:35 +00:00
|
|
|
|
2023-07-10 17:03:10 +00:00
|
|
|
def __init__(self):
|
|
|
|
super().__init__()
|
2023-09-29 12:52:13 +00:00
|
|
|
with open(self.status_file(), "w") as file:
|
2023-07-10 17:03:10 +00:00
|
|
|
file.write(ServiceStatus.ACTIVE.value)
|
|
|
|
|
2023-02-03 17:04:35 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_id() -> str:
|
|
|
|
"""Return service id."""
|
|
|
|
return "testservice"
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_display_name() -> str:
|
|
|
|
"""Return service display name."""
|
|
|
|
return "Test Service"
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_description() -> str:
|
|
|
|
"""Return service description."""
|
|
|
|
return "A small service used for test purposes. Does nothing."
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_svg_icon() -> str:
|
|
|
|
"""Read SVG icon from file and return it as base64 encoded string."""
|
|
|
|
# return ""
|
|
|
|
return base64.b64encode(BITWARDEN_ICON.encode("utf-8")).decode("utf-8")
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_url() -> typing.Optional[str]:
|
|
|
|
"""Return service url."""
|
2023-06-14 11:55:46 +00:00
|
|
|
domain = "test.com"
|
2023-02-03 17:04:35 +00:00
|
|
|
return f"https://password.{domain}"
|
|
|
|
|
2023-10-06 13:17:48 +00:00
|
|
|
@classmethod
|
|
|
|
def is_movable(cls) -> bool:
|
|
|
|
return cls.movable
|
2023-02-03 17:04:35 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def is_required() -> bool:
|
|
|
|
return False
|
|
|
|
|
2023-06-29 11:27:08 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_backup_description() -> str:
|
|
|
|
return "How did we get here?"
|
|
|
|
|
2023-07-10 17:03:10 +00:00
|
|
|
@classmethod
|
|
|
|
def status_file(cls) -> str:
|
|
|
|
dir = cls.folders[0]
|
2023-07-12 16:00:36 +00:00
|
|
|
# we do not REALLY want to store our state in our declared folders
|
|
|
|
return path.join(dir, "..", "service_status")
|
2023-02-03 17:04:35 +00:00
|
|
|
|
2023-07-10 17:03:10 +00:00
|
|
|
@classmethod
|
|
|
|
def set_status(cls, status: ServiceStatus):
|
|
|
|
with open(cls.status_file(), "w") as file:
|
|
|
|
status_string = file.write(status.value)
|
2023-02-03 17:04:35 +00:00
|
|
|
|
2023-07-10 17:03:10 +00:00
|
|
|
@classmethod
|
|
|
|
def get_status(cls) -> ServiceStatus:
|
|
|
|
with open(cls.status_file(), "r") as file:
|
|
|
|
status_string = file.read().strip()
|
|
|
|
return ServiceStatus[status_string]
|
2023-02-03 17:04:35 +00:00
|
|
|
|
2023-07-10 17:03:10 +00:00
|
|
|
@classmethod
|
|
|
|
def change_status_with_async_delay(
|
|
|
|
cls, new_status: ServiceStatus, delay_sec: float
|
|
|
|
):
|
|
|
|
"""simulating a delay on systemd side"""
|
2023-07-12 16:00:36 +00:00
|
|
|
status_file = cls.status_file()
|
2023-07-10 17:03:10 +00:00
|
|
|
|
|
|
|
command = [
|
|
|
|
"bash",
|
|
|
|
"-c",
|
|
|
|
f" sleep {delay_sec} && echo {new_status.value} > {status_file}",
|
|
|
|
]
|
|
|
|
handle = subprocess.Popen(command)
|
|
|
|
if delay_sec == 0:
|
|
|
|
handle.communicate()
|
2023-02-03 17:04:35 +00:00
|
|
|
|
2023-07-19 15:09:49 +00:00
|
|
|
@classmethod
|
|
|
|
def set_backuppable(cls, new_value: bool) -> None:
|
|
|
|
"""For tests: because can_be_backed_up is static,
|
|
|
|
we can only set it up dynamically for tests via a classmethod"""
|
|
|
|
cls.backuppable = new_value
|
|
|
|
|
2023-10-06 13:17:48 +00:00
|
|
|
@classmethod
|
|
|
|
def set_movable(cls, new_value: bool) -> None:
|
|
|
|
"""For tests: because is_movale is static,
|
|
|
|
we can only set it up dynamically for tests via a classmethod"""
|
|
|
|
cls.movable = new_value
|
|
|
|
|
2023-07-19 15:09:49 +00:00
|
|
|
@classmethod
|
|
|
|
def can_be_backed_up(cls) -> bool:
|
|
|
|
"""`True` if the service can be backed up."""
|
|
|
|
return cls.backuppable
|
|
|
|
|
2023-07-10 17:03:10 +00:00
|
|
|
@classmethod
|
2023-10-06 10:45:46 +00:00
|
|
|
def set_delay(cls, new_delay_sec: float) -> None:
|
|
|
|
cls.startstop_delay = new_delay_sec
|
2023-07-12 12:27:55 +00:00
|
|
|
|
2023-10-11 17:19:45 +00:00
|
|
|
@classmethod
|
|
|
|
def set_drive(cls, new_drive: str) -> None:
|
|
|
|
cls.drive = new_drive
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def set_simulated_moves(cls, enabled: bool) -> None:
|
|
|
|
"""If True, this service will not actually call moving code
|
|
|
|
when moved"""
|
|
|
|
cls.simulate_moving = enabled
|
|
|
|
|
2023-07-12 12:27:55 +00:00
|
|
|
@classmethod
|
|
|
|
def stop(cls):
|
2023-08-23 13:39:12 +00:00
|
|
|
# simulate a failing service unable to stop
|
|
|
|
if not cls.get_status() == ServiceStatus.FAILED:
|
|
|
|
cls.set_status(ServiceStatus.DEACTIVATING)
|
|
|
|
cls.change_status_with_async_delay(
|
|
|
|
ServiceStatus.INACTIVE, cls.startstop_delay
|
|
|
|
)
|
2023-07-10 17:03:10 +00:00
|
|
|
|
|
|
|
@classmethod
|
2023-07-12 12:27:55 +00:00
|
|
|
def start(cls):
|
2023-07-10 17:03:10 +00:00
|
|
|
cls.set_status(ServiceStatus.ACTIVATING)
|
2023-07-12 12:27:55 +00:00
|
|
|
cls.change_status_with_async_delay(ServiceStatus.ACTIVE, cls.startstop_delay)
|
2023-07-10 17:03:10 +00:00
|
|
|
|
|
|
|
@classmethod
|
2023-07-12 12:27:55 +00:00
|
|
|
def restart(cls):
|
2023-07-10 17:03:10 +00:00
|
|
|
cls.set_status(ServiceStatus.RELOADING) # is a correct one?
|
2023-07-12 12:27:55 +00:00
|
|
|
cls.change_status_with_async_delay(ServiceStatus.ACTIVE, cls.startstop_delay)
|
2023-07-10 17:03:10 +00:00
|
|
|
|
2023-02-03 17:04:35 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_configuration():
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def set_configuration(config_items):
|
|
|
|
return super().set_configuration(config_items)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_logs():
|
|
|
|
return ""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_storage_usage() -> int:
|
|
|
|
storage_usage = 0
|
|
|
|
return storage_usage
|
|
|
|
|
2023-10-11 17:19:45 +00:00
|
|
|
@classmethod
|
|
|
|
def get_drive(cls) -> str:
|
|
|
|
return cls.drive
|
2023-04-14 10:32:14 +00:00
|
|
|
|
|
|
|
@classmethod
|
2023-04-14 11:20:03 +00:00
|
|
|
def get_folders(cls) -> List[str]:
|
2023-04-14 12:29:23 +00:00
|
|
|
return cls.folders
|
2023-02-03 17:04:35 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_dns_records() -> typing.List[ServiceDnsRecord]:
|
|
|
|
"""Return list of DNS records for Bitwarden service."""
|
|
|
|
return [
|
|
|
|
ServiceDnsRecord(
|
|
|
|
type="A",
|
|
|
|
name="password",
|
|
|
|
content=network_utils.get_ip4(),
|
|
|
|
ttl=3600,
|
2023-11-24 10:57:52 +00:00
|
|
|
display_name="Test Service",
|
2023-02-03 17:04:35 +00:00
|
|
|
),
|
|
|
|
ServiceDnsRecord(
|
|
|
|
type="AAAA",
|
|
|
|
name="password",
|
|
|
|
content=network_utils.get_ip6(),
|
|
|
|
ttl=3600,
|
2023-11-24 10:57:52 +00:00
|
|
|
display_name="Test Service (IPv6)",
|
2023-02-03 17:04:35 +00:00
|
|
|
),
|
|
|
|
]
|
|
|
|
|
|
|
|
def move_to_volume(self, volume: BlockDevice) -> Job:
|
2023-10-11 17:19:45 +00:00
|
|
|
job = Jobs.add(
|
|
|
|
type_id=f"services.{self.get_id()}.move",
|
|
|
|
name=f"Move {self.get_display_name()}",
|
|
|
|
description=f"Moving {self.get_display_name()} data to {volume.name}",
|
|
|
|
)
|
|
|
|
if self.simulate_moving is False:
|
|
|
|
# completely generic code, TODO: make it the default impl.
|
|
|
|
move_service(
|
|
|
|
self,
|
|
|
|
volume,
|
|
|
|
job,
|
|
|
|
FolderMoveNames.default_foldermoves(self),
|
|
|
|
self.get_id(),
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
Jobs.update(job, status=JobStatus.FINISHED)
|
|
|
|
|
|
|
|
self.set_drive(volume.name)
|
|
|
|
return job
|