diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7975dcb..22f51e8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -68,7 +68,7 @@ jobs:
- name: Build
run: |
pip install --user -r dev-requirements.txt
- pylint --rcfile ${GITHUB_WORKSPACE}/.pylintrc ${GITHUB_WORKSPACE}/src/mrmat_python_cli --exit-zero
+ pylint --rcfile ${GITHUB_WORKSPACE}/.pylintrc ${GITHUB_WORKSPACE}/src/kaso_mashin --exit-zero
python -m build --wheel -n
PYTHONPATH=${GITHUB_WORKSPACE}/src python -m pytest
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 91444ca..712f985 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 8dd3dce..b4653c4 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -4,23 +4,23 @@
# Build/Test requirements
-setuptools>=42.0.0
-build>=0.9.0 # MIT
-wheel>=0.36.0 # MIT
-pylint~=2.17.5 # MIT
-pytest~=7.4.2 # GPL-2.0-or-later
-pytest-cov~=4.1.0 # MIT
+setuptools==69.0.2
+build==0.9.0 # MIT
+wheel==0.41.3 # MIT
+pylint==3.0.2 # MIT
+pytest==7.4.3 # GPL-2.0-or-later
+pytest-cov==4.1.0 # MIT
# Runtime requirements
-rich~=13.5.3 # MIT
-requests~=2.31.0 # Apache 2.0
-pyyaml~=6.0 # MIT
-netifaces~=0.11.0 # MIT
-sqlalchemy~=2.0.20 # MIT
-fastapi~=0.103.1 # MIT
-uvicorn~=0.23.2 # BSD 3-Clause
-httpx~=0.25.0 # BSD 3-Clause
-aiofiles~=23.2.1 # Apache 2.0
-qemu.qmp~=0.0.3 # ?
-passlib~=1.7.4 # BSD
+rich==13.7.0 # MIT
+requests==2.31.0 # Apache 2.0
+pyyaml==6.0 # MIT
+netifaces==0.11.0 # MIT
+sqlalchemy==2.0.23 # MIT
+fastapi==0.104.1 # MIT
+uvicorn==0.23.2 # BSD 3-Clause
+httpx==0.24.1 # BSD 3-Clause
+aiofiles==23.2.1 # Apache 2.0
+qemu.qmp==0.0.3 # ?
+passlib==1.7.4 # BSD
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 011a2e2..d465cfd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[build-system]
requires = [
- 'setuptools>=42.0.0',
- 'wheel >= 0.36.0'
+ 'setuptools==69.0.2',
+ 'wheel==0.41.3'
]
build-backend = 'setuptools.build_meta'
@@ -9,7 +9,7 @@ build-backend = 'setuptools.build_meta'
name = "kaso-mashin"
description = "Building a mini-cloud as a playground"
urls = { "Sources" = "https://github.com/MrMatAP/kaso-mashin" }
-keywords = ["experimental"]
+keywords = ["mac", "virtualization", "virtualisation", "arm64"]
readme = "README.md"
license = { text = "MIT" }
authors = [
@@ -25,17 +25,17 @@ classifiers = [
]
requires-python = ">=3.10"
dependencies = [
- "rich~=13.5.2", # MIT
- "requests~=2.31.0", # Apache 2.0
- "pyyaml~=6.0", # MIT
- "netifaces~=0.11.0", # MIT
- "sqlalchemy~=2.0.20", # MIT
- "fastapi~=0.103.1", # MIT
- "uvicorn~=0.23.2", # BSD 3-Clause
- "httpx~=0.25.0", # BSD 3-Clause
- "aiofiles~=23.2.1", # Apache 2.0
- "qemu.qmp~=0.0.3", # ?
- "passlib~=1.7.4" # BSD
+ "rich==13.7.0", # MIT
+ "requests==2.31.0", # Apache 2.0
+ "pyyaml==6.0", # MIT
+ "netifaces==0.11.0", # MIT
+ "sqlalchemy==2.0.23", # MIT
+ "fastapi==0.104.1", # MIT
+ "uvicorn==0.23.2", # BSD 3-Clause
+ "httpx==0.24.1", # BSD 3-Clause
+ "aiofiles==23.2.1", # Apache 2.0
+ "qemu.qmp==0.0.3", # ?
+ "passlib==1.7.4" # BSD
]
dynamic = ["version"]
diff --git a/src/kaso_mashin/cli/commands/identity_commands.py b/src/kaso_mashin/cli/commands/identity_commands.py
index 55698ea..c0ed584 100644
--- a/src/kaso_mashin/cli/commands/identity_commands.py
+++ b/src/kaso_mashin/cli/commands/identity_commands.py
@@ -139,7 +139,7 @@ def create(self, args: argparse.Namespace) -> int:
homedir=args.homedir,
shell=args.shell)
if not args.pubkey and not args.passwd:
- console.print(f'[red]ERROR[/red]: You must either provide the path to a public key or a password')
+ console.print('[red]ERROR[/red]: You must either provide the path to a public key or a password')
return 1
if args.pubkey:
schema.kind = IdentityKind.PUBKEY
diff --git a/src/kaso_mashin/common/config.py b/src/kaso_mashin/common/config.py
index d06c096..f1cd32c 100644
--- a/src/kaso_mashin/common/config.py
+++ b/src/kaso_mashin/common/config.py
@@ -41,7 +41,7 @@ def load(self, config_file: pathlib.Path):
if not config_file.exists():
self._logger.debug('No configuration file exists, using defaults')
return
- self._logger.debug(f'Loading config file at {config_file}')
+ self._logger.debug('Loading config file at %s', config_file)
configurable = {field.name: field.type for field in dataclasses.fields(self)}
try:
with open(config_file, 'r', encoding='UTF-8') as c:
@@ -53,7 +53,7 @@ def load(self, config_file: pathlib.Path):
setattr(self, key, pathlib.Path(value))
else:
setattr(self, key, value)
- self._logger.debug(f'Config file overrides {key} to {value}')
+ self._logger.debug('Config file overrides %s to %s', key, value)
except yaml.YAMLError as exc:
raise KasoMashinException(status=400, msg='Invalid config file') from exc
@@ -69,10 +69,10 @@ def cli_override(self, args: argparse.Namespace):
value = configured.get(key)
if value != getattr(self, key):
setattr(self, key, value)
- self._logger.debug(f'CLI overrides {key} to {value}')
+ self._logger.debug('CLI overrides %s to %s', key, value)
def save(self, config_file: pathlib.Path):
- self._logger.debug(f'Saving configuration at {config_file}')
+ self._logger.debug('Saving configuration at %s', config_file)
configured = {field.name: getattr(self, field.name) for field in dataclasses.fields(self)}
try:
with open(config_file, 'w+', encoding='UTF-8') as c:
diff --git a/src/kaso_mashin/common/model/instance_model.py b/src/kaso_mashin/common/model/instance_model.py
index 02da562..3a93d5a 100644
--- a/src/kaso_mashin/common/model/instance_model.py
+++ b/src/kaso_mashin/common/model/instance_model.py
@@ -58,7 +58,9 @@ class InstanceCreateSchema(pydantic.BaseModel):
image_id: int = pydantic.Field(description='Image ID to use as the backing OS disk', examples=[1])
network_id: int = pydantic.Field(description='Network ID to connect the instance to', examples=[1])
os_disk_size: str = pydantic.Field(description='The OS disk size in GB', default='5G', examples=['5G'])
- identities: list = pydantic.Field(description='The identities on that instance', default_factory=list, examples=['1'])
+ identities: list = pydantic.Field(description='The identities on that instance',
+ default_factory=list,
+ examples=['1'])
class InstanceModifySchema(pydantic.BaseModel):
diff --git a/src/kaso_mashin/common/model/qemu_model.py b/src/kaso_mashin/common/model/qemu_model.py
index b44d970..1580e45 100644
--- a/src/kaso_mashin/common/model/qemu_model.py
+++ b/src/kaso_mashin/common/model/qemu_model.py
@@ -63,14 +63,16 @@ def __init__(self, model: InstanceModel):
self._cmd = self._generate_cmd()
self._process: subprocess.Popen | None = None
self._logger = logging.getLogger(f'{self.__class__.__module__}.{self.__class__.__name__}')
- self._logger.info(f'Initialised for {model.name}')
+ self._logger.info('Initialised for %s', model.name)
def _generate_cmd(self) -> str:
- cmd = (f'{self.emulator} -name {self.model.name} -machine virt -cpu host -accel hvf -smp {self.model.vcpu} -m {self.model.ram} '
+ cmd = (f'{self.emulator} -name {self.model.name} -machine virt -cpu host -accel hvf '
+ f'-smp {self.model.vcpu} -m {self.model.ram} '
f'-bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd '
'-device virtio-rng-pci -device nec-usb-xhci,id=usb-bus -device usb-kbd,bus=usb-bus.0 '
f'-drive if=virtio,file={self.model.os_disk_path},format=qcow2,cache=writethrough '
- f'-smbios type=3,manufacturer=MrMat,version=0,serial=instance_{self.model.instance_id},asset={self.model.name},sku=MrMat '
+ f'-smbios type=3,manufacturer=MrMat,version=0,serial=instance_{self.model.instance_id},'
+ f'asset={self.model.name},sku=MrMat '
f'-chardev socket,id=char0,server=on,wait=off,path={self.model.console_path} '
f'-qmp unix:{self.model.qmp_path},server=on,wait=off ')
match self.model.display:
@@ -120,7 +122,8 @@ def _generate_cmd(self) -> str:
def generate_script(self):
with open(self.model.vm_script_path, mode='w', encoding='UTF-8') as v:
v.write(f'#!/bin/bash\n# '
- f'This script can be used to manually start the instance it is located in\n\n{self._generate_cmd()}')
+ f'This script can be used to manually start the instance it is located in'
+ f'\n\n{self._generate_cmd()}')
self.model.vm_script_path.chmod(0o755)
def _wait_for_bridge(self) -> bool | None:
@@ -137,13 +140,14 @@ def _wait_for_bridge(self) -> bool | None:
return self.model.network.host_ip4 in addr2if
def start(self):
+ # pylint: disable=consider-using-with
self.process = subprocess.Popen(args=shlex.split(self._generate_cmd()), encoding='UTF-8')
self._logger.info('Started QEmu process')
# TODO: Cloud-init will only ever phone home once per instance, unless we make it a runcmd
# Wait for the bridge to come up
attempt = 0
while attempt < 15 and not self._wait_for_bridge():
- self._logger.info(f'Waiting for bridge to come up ({attempt}/15)')
+ self._logger.info('Waiting for bridge to come up (%s/15)', attempt)
attempt += 1
time.sleep(1)
if attempt == 9:
@@ -151,10 +155,11 @@ def start(self):
self._logger.info('Bridge has come up')
def _instance_phoned_home(actual_ip: str):
- self._logger.info(f'Instance has phoned home. Actual IP: ${actual_ip}')
+ self._logger.info('Instance has phoned home. Actual IP: %s', actual_ip)
- self._logger.info(f'Starting phone home server on host {self.model.network.host_ip4}')
- httpd = PhoneHomeServer(server_address=(str(self.model.network.host_ip4), self.model.network.host_phone_home_port),
+ self._logger.info('Starting phone home server on host %s', self.model.network.host_ip4)
+ httpd = PhoneHomeServer(server_address=(str(self.model.network.host_ip4),
+ self.model.network.host_phone_home_port),
callback=_instance_phoned_home,
RequestHandlerClass=PhoneHomeHandler)
httpd.timeout = 60
diff --git a/src/kaso_mashin/common/model/relation_tables.py b/src/kaso_mashin/common/model/relation_tables.py
index e182bd8..45b44fc 100644
--- a/src/kaso_mashin/common/model/relation_tables.py
+++ b/src/kaso_mashin/common/model/relation_tables.py
@@ -7,4 +7,4 @@
Base.metadata,
Column('instance_id', ForeignKey('instances.instance_id')),
Column('identity_id', ForeignKey('identities.identity_id'))
-)
\ No newline at end of file
+)
diff --git a/src/kaso_mashin/server/controllers/identity_controller.py b/src/kaso_mashin/server/controllers/identity_controller.py
index bbc48d5..9b8fa77 100644
--- a/src/kaso_mashin/server/controllers/identity_controller.py
+++ b/src/kaso_mashin/server/controllers/identity_controller.py
@@ -43,7 +43,7 @@ def create(self, model: IdentityModel) -> IdentityModel:
return model
except sqlalchemy.exc.SQLAlchemyError as sae:
self.db.session.rollback()
- raise KasoMashinException(status=500, msg=f'Database exception: {sae}')
+ raise KasoMashinException(status=500, msg=f'Database exception: {sae}') from sae
def modify(self, identity_id: int, update: IdentityModel) -> IdentityModel:
try:
@@ -69,7 +69,7 @@ def modify(self, identity_id: int, update: IdentityModel) -> IdentityModel:
return current
except sqlalchemy.exc.SQLAlchemyError as sae:
self.db.session.rollback()
- raise KasoMashinException(status=500, msg=f'Database exception: {sae}')
+ raise KasoMashinException(status=500, msg=f'Database exception: {sae}') from sae
def remove(self, identity_id: int):
try:
@@ -87,4 +87,4 @@ def remove(self, identity_id: int):
return False
except sqlalchemy.exc.SQLAlchemyError as sae:
self.db.session.rollback()
- raise KasoMashinException(status=500, msg=f'Database exception: {sae}')
+ raise KasoMashinException(status=500, msg=f'Database exception: {sae}') from sae
diff --git a/src/kaso_mashin/server/run.py b/src/kaso_mashin/server/run.py
index 79bd38f..7df0262 100644
--- a/src/kaso_mashin/server/run.py
+++ b/src/kaso_mashin/server/run.py
@@ -31,7 +31,7 @@ def create_server(runtime: Runtime) -> fastapi.applications.FastAPI:
@app.exception_handler(KasoMashinException)
# pylint: disable=unused-argument
async def kaso_mashin_exception_handler(request: fastapi.Request, exc: KasoMashinException):
- logging.getLogger('kaso_mashin.server').error(f'({exc.status}) {exc.msg}')
+ logging.getLogger('kaso_mashin.server').error('(%s) %s', exc.status, exc.msg)
return fastapi.responses.JSONResponse(status_code=exc.status,
content=ExceptionSchema(status=exc.status, msg=exc.msg)
.model_dump())
@@ -39,7 +39,7 @@ async def kaso_mashin_exception_handler(request: fastapi.Request, exc: KasoMashi
@app.exception_handler(sqlalchemy.exc.SQLAlchemyError)
# pylint: disable=unused-argument
async def sqlalchemy_exception_handler(request: fastapi.Request, exc: sqlalchemy.exc.SQLAlchemyError):
- logging.getLogger('kaso_mashin.server').error(f'(500) Database exception {exc}')
+ logging.getLogger('kaso_mashin.server').error('(500) Database exception %s', str(exc))
return fastapi.responses.JSONResponse(status_code=500,
content=ExceptionSchema(status=500, msg=f'Database exception {exc}')
.model_dump())
@@ -87,8 +87,8 @@ def main(args: typing.Optional[typing.List] = None) -> int:
# TODO: We should move this into config
runtime.late_init(server=True)
try:
- logger.info(f'Effective user {runtime.effective_user}')
- logger.info(f'Owning user {runtime.owning_user}')
+ logger.info('Effective user %s', runtime.effective_user)
+ logger.info('Owning user %s', runtime.owning_user)
app = create_server(runtime)
uvicorn.run(app, host=config.default_server_host, port=config.default_server_port)
return 0