Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: CI

on:
push:
pull_request:

jobs:
frontend-format:
name: Check Frontend Format
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
cache-dependency-path: 'frontend/pnpm-lock.yaml'

- name: Install pnpm
run: corepack enable && corepack prepare pnpm@latest --activate

- name: Install dependencies
working-directory: ./frontend
run: pnpm install --frozen-lockfile

- name: Install prettier
working-directory: ./frontend
run: pnpm add -D prettier

- name: Check formatting with prettier
working-directory: ./frontend
run: pnpm exec prettier --check app

frontend-build:
name: Build Frontend
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
cache-dependency-path: 'frontend/pnpm-lock.yaml'

- name: Install pnpm
run: corepack enable && corepack prepare pnpm@latest --activate

- name: Install dependencies
working-directory: ./frontend
run: pnpm install --frozen-lockfile

- name: Build frontend
working-directory: ./frontend
run: pnpm build

backend-format:
name: Check Backend Format
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install autopep8
run: pip install autopep8

- name: Check formatting with autopep8
working-directory: ./backend
run: |
autopep8 --diff --recursive --select=E,W app/ > /tmp/autopep8.diff || true
if [ -s /tmp/autopep8.diff ]; then
echo "Code formatting issues found. Please run: autopep8 --in-place --recursive --select=E,W app/"
cat /tmp/autopep8.diff
exit 1
fi
#
# backend-typecheck:
# name: Check Backend Types and Build
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
#
# - name: Setup Python
# uses: actions/setup-python@v5
# with:
# python-version: '3.13'
#
# - name: Install dependencies
# working-directory: ./backend
# run: |
# pip install -r requirements.txt
# pip install mypy
#
# - name: Type check with mypy
# working-directory: ./backend
# run: |
# mypy app/ --ignore-missing-imports
4 changes: 2 additions & 2 deletions backend/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
engine = create_engine(database_url, echo=True)
SQLModel.metadata.create_all(engine)


class QEMUConfig:
qemu_conn = None

@classmethod
def get_connection(cls):
if cls.qemu_conn is None or cls.qemu_conn.isAlive() == 0:
try:
try:
qemu_conn = libvirt.open("qemu:///system")
except libvirt.libvirtError:
raise
return qemu_conn

16 changes: 11 additions & 5 deletions backend/app/core/xml_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from app.models.vm import VmRead
from app.core.constants import VMS_DIR


def build_xml(vm_read: VmRead):

domain = etree.Element("domain", type="kvm")

etree.SubElement(domain, "name").text = str(vm_read.id)
etree.SubElement(domain, "memory", unit="MiB").text = str(vm_read.mem)
etree.SubElement(domain, "vcpu", placement="static").text = str(vm_read.vcpus)
etree.SubElement(domain, "vcpu", placement="static").text = str(
vm_read.vcpus)

os_el = etree.SubElement(domain, "os")
etree.SubElement(os_el, "type", arch="x86_64", machine="pc").text = "hvm"
Expand All @@ -30,12 +32,14 @@ def build_xml(vm_read: VmRead):

disk_main = etree.SubElement(devices, "disk", type="file", device="disk")
etree.SubElement(disk_main, "driver", name="qemu", type="qcow2")
etree.SubElement(disk_main, "source", file=str(VMS_DIR / str(vm_read.id) / f"distribox-{vm_read.os}.qcow2"))
etree.SubElement(disk_main, "source", file=str(
VMS_DIR / str(vm_read.id) / f"distribox-{vm_read.os}.qcow2"))
etree.SubElement(disk_main, "target", dev="vda", bus="virtio")

channel = etree.SubElement(devices, "channel", type="unix")
etree.SubElement(channel, "source", mode="bind")
etree.SubElement(channel, "target", type="virtio", name="org.qemu.guest_agent.0")
etree.SubElement(channel, "target", type="virtio",
name="org.qemu.guest_agent.0")

# disk_seed = etree.SubElement(devices, "disk", type="file", device="cdrom")
# etree.SubElement(disk_seed, "driver", name="qemu", type="raw")
Expand All @@ -47,11 +51,13 @@ def build_xml(vm_read: VmRead):
etree.SubElement(iface, "source", network="default")
etree.SubElement(iface, "model", type="virtio")

etree.SubElement(devices, "graphics", type="vnc", port="-1", autoport="yes", listen="127.0.0.1")
etree.SubElement(devices, "graphics", type="vnc",
port="-1", autoport="yes", listen="127.0.0.1")

etree.SubElement(devices, "console", type="pty")
etree.SubElement(devices, "input", type="keyboard", bus="ps2")
etree.SubElement(devices, "input", type="tablet", bus="usb")

xml_string = etree.tostring(domain, pretty_print=True, encoding="utf-8").decode()
xml_string = etree.tostring(
domain, pretty_print=True, encoding="utf-8").decode()
return xml_string
3 changes: 2 additions & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from app.routes import vm
from app.routes import vm
app = FastAPI()


Expand All @@ -11,6 +11,7 @@ async def global_exception_handler(_, exc: HTTPException):
content={"detail": exc.detail}
)


@app.exception_handler(Exception)
async def global_exception_handler(_, exc: Exception):
return JSONResponse(
Expand Down
3 changes: 3 additions & 0 deletions backend/app/models/vm.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from pydantic import BaseModel
from typing import Optional


class VmBase(BaseModel):
os: str
mem: int
vcpus: int
disk_size: int


class VmRead(VmBase):
id: str
state: str


class VmCreate(VmBase):
pass
5 changes: 3 additions & 2 deletions backend/app/orm/vm.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from sqlmodel import SQLModel, Field
import uuid


class VmORM(SQLModel, table=True, ):
__tablename__ = "vms"

id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
os: str
mem: int
vcpus: int
disk_size: int
password: str | None = None
password: str | None = None
13 changes: 10 additions & 3 deletions backend/app/routes/vm.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
from fastapi import status, APIRouter
from fastapi import status, APIRouter
from app.models.vm import VmCreate
from app.services.vm_service import VmService

router = APIRouter()


@router.get('/', status_code=status.HTTP_200_OK)
def get_vm_list():
vm_list = VmService.get_vm_list()
return vm_list


@router.get("/{vm_id}", status_code=status.HTTP_200_OK)
def get_vm(vm_id: str):
vm = VmService.get_vm(vm_id)
return vm


@router.get("/{vm_id}/state", status_code=status.HTTP_200_OK)
def get_vm(vm_id: str):
state = VmService.get_state(vm_id)
return state


@router.post("/{vm_id}/start", status_code=status.HTTP_200_OK)
def start_vm(vm_id: str):
vm = VmService.start_vm(vm_id)
return vm


@router.post("/{vm_id}/stop", status_code=status.HTTP_200_OK)
def stop_vm(vm_id: str):
vm = VmService.stop_vm(vm_id)
return vm


@router.post("/", status_code=status.HTTP_201_CREATED)
def create_vm(vm:VmCreate):
def create_vm(vm: VmCreate):
created_vm = VmService.create_vm(vm)
return created_vm


@router.put("/{vm_id}/password", status_code=status.HTTP_200_OK)
def set_vm_password(vm_id):
return VmService.set_vm_password(vm_id)
return VmService.set_vm_password(vm_id)
Loading
Loading