From 2673bcbbffeb9b1bad40ba1f0a17cd47dd2268ca Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 01:20:49 -0400 Subject: [PATCH 01/16] feat: change listing to include passwords --- appwrite_lab/_orchestrator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appwrite_lab/_orchestrator.py b/appwrite_lab/_orchestrator.py index 47fa714..c6195c3 100644 --- a/appwrite_lab/_orchestrator.py +++ b/appwrite_lab/_orchestrator.py @@ -69,7 +69,7 @@ def get_formatted_labs(self, collapsed: bool = False): """ labs: dict = self.state.get("labs", {}) if collapsed: - headers = ["Name", "Version", "URL", "Admin Email", "Project ID", "API Key"] + headers = ["Lab Name", "Version", "URL", "Admin Email", "Admin Password", "Project ID"] data = [] for val in labs.values(): project = Project(**val.get("projects", {}).get("default")) @@ -79,8 +79,8 @@ def get_formatted_labs(self, collapsed: bool = False): val["version"], val["url"], val["admin_email"], + val["admin_password"], project.project_id, - project.api_key, ] ) return headers, data From 35e8eb75179e58f1d3b7b7d453a175e3b07937a5 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 03:43:13 -0400 Subject: [PATCH 02/16] fix: ocassional bug for new deployment of lab --- appwrite_lab/_orchestrator.py | 29 +++++++++++++++---- appwrite_lab/automations/functions.py | 6 ++++ .../scripts/create_user_and_api_key.py | 6 ++-- appwrite_lab/cli/new_menu.py | 19 ++++++++++-- appwrite_lab/labs.py | 5 +++- 5 files changed, 55 insertions(+), 10 deletions(-) diff --git a/appwrite_lab/_orchestrator.py b/appwrite_lab/_orchestrator.py index c6195c3..866a83b 100644 --- a/appwrite_lab/_orchestrator.py +++ b/appwrite_lab/_orchestrator.py @@ -69,7 +69,14 @@ def get_formatted_labs(self, collapsed: bool = False): """ labs: dict = self.state.get("labs", {}) if collapsed: - headers = ["Lab Name", "Version", "URL", "Admin Email", "Admin Password", "Project ID"] + headers = [ + "Lab Name", + "Version", + "URL", + "Admin Email", + "Admin Password", + "Project ID", + ] data = [] for val in labs.values(): project = Project(**val.get("projects", {}).get("default")) @@ -151,7 +158,12 @@ def _deploy_service( return self._run_cmd_safely(cmd, envs=new_env) def deploy_appwrite_lab( - self, name: str, version: str, port: int, meta: dict[str, str] + self, + name: str, + version: str, + port: int, + meta: dict[str, str], + **kwargs: dict[str, str], ): """ Deploy an Appwrite lab. @@ -161,6 +173,7 @@ def deploy_appwrite_lab( version: The version of the service to deploy. port: The port to use for the Appwrite service. Must not be in use by another service. meta: Extra metadata to pass to the deployment. + """ # sync appwrite_config = meta.get("appwrite_config", {}) @@ -208,7 +221,7 @@ def deploy_appwrite_lab( url = "" proj_id = appwrite_config.pop("project_id", None) proj_name = appwrite_config.pop("project_name", None) - kwargs = { + _kwargs = { **appwrite_config, "projects": {"default": Project(proj_id, proj_name, None)}, } @@ -216,14 +229,19 @@ def deploy_appwrite_lab( name=name, version=version, url=url, - **kwargs, + **_kwargs, ) lab.generate_missing_config() # ensure project_id and project_name are set proj_id = proj_id or lab.projects.get("default").project_id proj_name = proj_name or lab.projects.get("default").project_name - + if kwargs.get("just_deploy", False): + return Response( + error=False, + message=f"Lab '{name}' deployed with --just-deploy flag.", + data=lab, + ) # Deploy playwright automations for creating user and API key api_key_res = self.deploy_playwright_automation( lab=lab, @@ -257,6 +275,7 @@ def deploy_playwright_automation( args: list[str] = [], *, print_data: bool = False, + **kwargs, ) -> str | Response: """ Deploy playwright automations on a lab (very few automations supported). diff --git a/appwrite_lab/automations/functions.py b/appwrite_lab/automations/functions.py index 0b48681..f3a2902 100644 --- a/appwrite_lab/automations/functions.py +++ b/appwrite_lab/automations/functions.py @@ -1,6 +1,11 @@ from playwright.async_api import Playwright, Page, BrowserContext, Browser +async def wait_until_loaded(page: Page): + """Wait until the page is loaded.""" + await page.wait_for_selector('img[alt="Appwrite Logo"]') + + async def create_browser_context(playwright: Playwright, headless: bool = True): """Create a browser context for automation.""" browser = await playwright.chromium.launch(headless=headless) @@ -24,6 +29,7 @@ async def select_project_after_login(page: Page, project_name: str): async def register_user(page: Page, url: str, admin_email: str, admin_password: str): """Register a new user in Appwrite.""" await page.goto(f"{url}/console/register") + await wait_until_loaded(page) await page.wait_for_timeout(200) await page.get_by_role("textbox", name="Name").fill("Test User") await page.get_by_role("textbox", name="Email").fill(admin_email) diff --git a/appwrite_lab/automations/scripts/create_user_and_api_key.py b/appwrite_lab/automations/scripts/create_user_and_api_key.py index 999ac49..5a137f1 100644 --- a/appwrite_lab/automations/scripts/create_user_and_api_key.py +++ b/appwrite_lab/automations/scripts/create_user_and_api_key.py @@ -7,6 +7,7 @@ create_project_ui, create_api_key_ui, cleanup_browser, + wait_until_loaded, ) from ..utils import resultify from ..models import AppwriteAPIKeyCreation, AppwriteUserCreation @@ -27,7 +28,6 @@ async def create_user_and_api_key(playwright: Playwright) -> str: auth = AppwriteUserCreation.from_env() api_key_env = AppwriteAPIKeyCreation.from_env() work_dir = os.getenv("HOME") - browser, context = await create_browser_context(playwright, headless=True) page = await context.new_page() @@ -37,7 +37,9 @@ async def create_user_and_api_key(playwright: Playwright) -> str: admin_email=auth.admin_email, admin_password=auth.admin_password, ) - await create_project_ui(page=page, project_name=auth.project_name, project_id=auth.project_id) + await create_project_ui( + page=page, project_name=auth.project_name, project_id=auth.project_id + ) api_key = await create_api_key_ui( page=page, key_name=api_key_env.key_name, key_expiry=api_key_env.key_expiry diff --git a/appwrite_lab/cli/new_menu.py b/appwrite_lab/cli/new_menu.py index b0deeab..4966cca 100644 --- a/appwrite_lab/cli/new_menu.py +++ b/appwrite_lab/cli/new_menu.py @@ -35,6 +35,12 @@ def new_lab( help="The name of the project to use for the lab. Unset for random.", show_envvar=False, ), + just_deploy: bool = typer.Option( + False, + is_flag=True, + help="Just deploy the lab without creating an API key or project.", + show_envvar=False, + ), ): """ Create a new lab. @@ -49,7 +55,10 @@ def new_lab( project_name: The name of the project to use for the lab. Unset for random. """ labs = get_global_labs() - with console.status(f"Creating lab '{name}'...", spinner="dots") as status: + extra_str = " with simple deployment" if just_deploy else "" + with console.status( + f"Creating lab '{name}'{extra_str}...", spinner="dots" + ) as status: creds = { "admin_email": email, "admin_password": password, @@ -57,7 +66,13 @@ def new_lab( "project_name": project_name, } - labs.new(name=name, version=version, port=port, meta={"appwrite_config": creds}) + labs.new( + name=name, + version=version, + port=port, + meta={"appwrite_config": creds}, + just_deploy=just_deploy, + ) status.update(f"Creating lab '{name}'... done") diff --git a/appwrite_lab/labs.py b/appwrite_lab/labs.py index 0db0f69..ab6fef1 100644 --- a/appwrite_lab/labs.py +++ b/appwrite_lab/labs.py @@ -26,6 +26,7 @@ def new( version: str, port: int, meta: dict[str, str] = {}, + just_deploy: bool = False, ): """ Deploy a new Appwrite lab. @@ -35,7 +36,9 @@ def new( version: The version of the lab. port: The port of the lab. """ - return self.orchestrator.deploy_appwrite_lab(name, version, port, meta) + return self.orchestrator.deploy_appwrite_lab( + name, version, port, meta, just_deploy=just_deploy + ) def get_lab(self, name: str) -> Lab | None: """ From 26468fbe16ddabd4d43e29b05935cb41c90dae3c Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 18:53:10 -0400 Subject: [PATCH 03/16] feat: add testing appwrite.json --- tests/data/appwrite.json | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/data/appwrite.json diff --git a/tests/data/appwrite.json b/tests/data/appwrite.json new file mode 100644 index 0000000..f959660 --- /dev/null +++ b/tests/data/appwrite.json @@ -0,0 +1,76 @@ +{ + "projectId": "68869940003e6e2600ff", + "projectName": "KubeProject", + "settings": { + "services": { + "account": true, + "avatars": true, + "databases": true, + "locale": true, + "health": true, + "storage": true, + "teams": true, + "users": true, + "sites": true, + "functions": true, + "graphql": true, + "messaging": true + }, + "auth": { + "methods": { + "jwt": true, + "phone": true, + "invites": true, + "anonymous": true, + "email-otp": true, + "magic-url": true, + "email-password": true + }, + "security": { + "duration": 31536000, + "limit": 0, + "sessionsLimit": 10, + "passwordHistory": 0, + "passwordDictionary": false, + "personalDataCheck": false, + "sessionAlerts": false, + "mockNumbers": [] + } + } + }, + "databases": [ + { + "$id": "6886997800119a565d99", + "name": "KubeTable", + "enabled": true + } + ], + "collections": [ + { + "$id": "6886998b001f814e0099", + "$permissions": [], + "databaseId": "6886997800119a565d99", + "name": "KubeLogs", + "enabled": true, + "documentSecurity": false, + "attributes": [ + { + "key": "log_data", + "type": "string", + "required": true, + "array": false, + "size": 1000, + "default": null + }, + { + "key": "is_error", + "type": "boolean", + "required": true, + "array": false, + "default": null + } + ], + "indexes": [] + } + ] +} \ No newline at end of file From 2db67a562c29262acad52aee5c90c65ba39dbe2b Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 18:53:25 -0400 Subject: [PATCH 04/16] chore: script for selinux template patching --- scripts/selinuxify_template_patch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/selinuxify_template_patch.py b/scripts/selinuxify_template_patch.py index 2985ef6..65a712c 100644 --- a/scripts/selinuxify_template_patch.py +++ b/scripts/selinuxify_template_patch.py @@ -9,7 +9,6 @@ templates_dir = Path(__file__).parent.parent / "appwrite_lab" / "templates" list_of_templates = [f for f in os.listdir(templates_dir) if f.endswith(".yml")] -# print(list_of_templates) for template in list_of_templates: with open(templates_dir / template) as f: data = yaml.load(f) From a95acde8e950a39a93a1572140f9a19deb916ab3 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 18:55:01 -0400 Subject: [PATCH 05/16] feat: new targets --- Makefile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 221fba8..9dcb996 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,6 @@ build_appwrite_playwright: # push_appwrite_cli: # docker tag appwrite-cli:latest appwrite-cli:$(APPWRITE_CLI_TAG) # docker push appwrite-cli:$(APPWRITE_CLI_TAG) -clean-tests: - appwrite-lab stop test-lab - -tests: - source .venv/bin/activate && pytest -m e2e - patch_templates: @VENV=$$(mktemp -d) && \ uv venv $$VENV && \ @@ -25,4 +19,11 @@ patch_templates: python scripts/selinuxify_template_patch.py && \ rm -rf $$VENV +tests: + uv run pytest -rs -m e2e + +clean-tests: + @source .venv/bin/activate && \ + appwrite-lab stop test-lab + .PHONY: patch_templates tests clean-tests build_appwrite_cli build_appwrite_playwright \ No newline at end of file From f3a9601dd041c8dd104e4065810dba1b88de7592 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 18:58:28 -0400 Subject: [PATCH 06/16] refactor: supprot for api key names, other fixes --- appwrite_lab/_orchestrator.py | 5 ++--- appwrite_lab/cli/new_menu.py | 4 ++-- appwrite_lab/labs.py | 19 ++++++++++++------- appwrite_lab/models.py | 2 +- tests/test_labs.py | 24 ++++++++++++++++++++++++ 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/appwrite_lab/_orchestrator.py b/appwrite_lab/_orchestrator.py index 866a83b..29c1dff 100644 --- a/appwrite_lab/_orchestrator.py +++ b/appwrite_lab/_orchestrator.py @@ -9,7 +9,7 @@ from appwrite_lab.automations.models import BaseVarModel, AppwriteAPIKeyCreation from ._state import State from dataclasses import dataclass -from .models import Lab, Automation, SyncType, Project +from .models import Lab, Automation, Project from dotenv import dotenv_values from appwrite_lab.utils import console from .utils import is_cli @@ -20,7 +20,7 @@ @dataclass class Response: message: str - data: any = None + data: str | dict | None = None error: bool = False _print_data: bool = False @@ -275,7 +275,6 @@ def deploy_playwright_automation( args: list[str] = [], *, print_data: bool = False, - **kwargs, ) -> str | Response: """ Deploy playwright automations on a lab (very few automations supported). diff --git a/appwrite_lab/cli/new_menu.py b/appwrite_lab/cli/new_menu.py index 4966cca..78dcf25 100644 --- a/appwrite_lab/cli/new_menu.py +++ b/appwrite_lab/cli/new_menu.py @@ -103,8 +103,8 @@ def new_api_key( key = labs.create_api_key( project_name=project_name, lab_name=lab_name, expiration=expiration ) - return key - # status.update(f"Creating API key for project '{project_name}'... done") + status.update(f"Creating API key for project '{project_name}'... done") + return key.data @new_menu.command(name="project", help="Create a new project") diff --git a/appwrite_lab/labs.py b/appwrite_lab/labs.py index ab6fef1..8ad26f9 100644 --- a/appwrite_lab/labs.py +++ b/appwrite_lab/labs.py @@ -100,13 +100,14 @@ def sync_with_appwrite_config( model=AppwriteSyncProject(sync_type), args=addn_args, ) + key_name = f"{proj_name}-key" key = self.create_api_key( - lab=lab, expiration=expiration, project_name=proj_name + lab=lab, expiration=expiration, project_name=proj_name, key_name=key_name ) lab.projects[proj_name] = Project( project_id=proj_id, project_name=proj_name, - api_key=key, + api_key=key.data, ) labs = self.state.get("labs") labs[name] = lab.to_dict() @@ -115,6 +116,7 @@ def sync_with_appwrite_config( def create_api_key( self, project_name: str, + key_name: str, expiration: Expiration = "30 days", lab_name: str | None = None, lab: Lab | None = None, @@ -135,7 +137,7 @@ def create_api_key( automation=Automation.CREATE_API_KEY, model=AppwriteAPIKeyCreation( project_name=project_name, - key_name=project_name, + key_name=key_name, key_expiry=str(expiration.value), ), print_data=True, @@ -144,9 +146,11 @@ def create_api_key( return Response( message=f"Failed to create API key: {api_key.message}", error=True ) - # create another Response to print key - # return Response(message=api_key.data, data=api_key.data) - return api_key + return Response( + message=f"API key created for {project_name}", + data=api_key.data, + _print_data=True, + ) def stop(self, name: str): return self.orchestrator.teardown_service(name) @@ -177,8 +181,9 @@ def create_project( project_name=project_name, project_id=project_id, ) - self.orchestrator.deploy_playwright_automation( + return self.orchestrator.deploy_playwright_automation( lab=lab, automation=Automation.CREATE_PROJECT, + project=Project(project_id=project_id, project_name=project_name), model=apc, ) diff --git a/appwrite_lab/models.py b/appwrite_lab/models.py index c4e1aa6..7c6431a 100644 --- a/appwrite_lab/models.py +++ b/appwrite_lab/models.py @@ -37,7 +37,7 @@ def to_dict(self): class Project(_BaseClass): project_id: str project_name: str - api_key: str + api_key: str | None = None @dataclass diff --git a/tests/test_labs.py b/tests/test_labs.py index aad55bb..ada3ca4 100644 --- a/tests/test_labs.py +++ b/tests/test_labs.py @@ -11,11 +11,35 @@ def test_labs_new(lab: Lab): assert lab.url.endswith("8080") assert lab.projects.get("default") is not None + @pytest.mark.e2e def test_labs_create_api_key(lab: Lab, lab_svc: Labs): default = lab.projects.get("default") res = lab_svc.create_api_key( project_name=default.project_name, + key_name="default-api-key", + expiration=Expiration.THIRTY_DAYS, + lab=lab, + ) + assert not res.error + assert type(res.data) is str + assert res.data.startswith("standard_") + + +@pytest.mark.e2e +def test_labs_create_project(lab: Lab, lab_svc: Labs): + project_name = "test-project" + project_id = "test-project-id" + res = lab_svc.create_project( + project_name=project_name, + project_id=project_id, + lab=lab, + ) + assert not res.error + + res = lab_svc.create_api_key( + project_name=project_name, + key_name=project_name, expiration=Expiration.THIRTY_DAYS, lab=lab, ) From c70ab04c1b2f5b698a92ace8f714fada3ec3e81c Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:05:32 -0400 Subject: [PATCH 07/16] ci: add tests runs and template patch ensuring --- .../workflows/ensure_patched_templates.yaml | 41 +++++++++++++++++++ .github/workflows/run_tests.yaml | 31 ++++++++++++++ tests/test_labs.py | 9 ++++ 3 files changed, 81 insertions(+) create mode 100644 .github/workflows/ensure_patched_templates.yaml create mode 100644 .github/workflows/run_tests.yaml diff --git a/.github/workflows/ensure_patched_templates.yaml b/.github/workflows/ensure_patched_templates.yaml new file mode 100644 index 0000000..7ccbb0c --- /dev/null +++ b/.github/workflows/ensure_patched_templates.yaml @@ -0,0 +1,41 @@ +name: Ensure Patched Templates + +on: + pull_request: + branches: [ main, dev ] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install uv + uses: astral-sh/setup-uv@v1 + with: + version: latest + + - name: Install dependencies + run: | + uv pip install pytest ruamel.yaml + + - name: Test patch_templates target + run: | + make patch_templates + + if [ -n "$(git status --porcelain)" ]; then + echo "Error: Running patch_templates produced changes. Please run 'make patch_templates' locally and commit the changes." + echo "git status" + git status + echo "git diff" + git diff + exit 1 + fi diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml new file mode 100644 index 0000000..69959c7 --- /dev/null +++ b/.github/workflows/run_tests.yaml @@ -0,0 +1,31 @@ +name: Run Tests + +on: + pull_request: + branches: [ main, dev ] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install uv + uses: astral-sh/setup-uv@v1 + with: + version: latest + + - name: Ensure Docker Compose is installed + run: docker compose --version + + - name: Run tests + run: | + make tests diff --git a/tests/test_labs.py b/tests/test_labs.py index ada3ca4..adec80c 100644 --- a/tests/test_labs.py +++ b/tests/test_labs.py @@ -26,6 +26,15 @@ def test_labs_create_api_key(lab: Lab, lab_svc: Labs): assert res.data.startswith("standard_") +@pytest.mark.e2e +def test_labs_synced_project(lab: Lab, lab_svc: Labs): + synced_proj_name = "KubeProject" + synced_proj = lab.projects.get(synced_proj_name) + assert synced_proj is not None + assert synced_proj.api_key.startswith("standard_") + assert synced_proj.project_name == "KubeProject" + + @pytest.mark.e2e def test_labs_create_project(lab: Lab, lab_svc: Labs): project_name = "test-project" From 058c95c4a0466b6559d6c02943a0e71aa0666880 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:10:31 -0400 Subject: [PATCH 08/16] ci: fixes --- .github/workflows/ensure_patched_templates.yaml | 7 ++----- .github/workflows/run_tests.yaml | 6 +----- pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ensure_patched_templates.yaml b/.github/workflows/ensure_patched_templates.yaml index 7ccbb0c..a4077e7 100644 --- a/.github/workflows/ensure_patched_templates.yaml +++ b/.github/workflows/ensure_patched_templates.yaml @@ -12,16 +12,13 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' + - name: Install uv uses: astral-sh/setup-uv@v1 with: version: latest + python-version: '3.11' - name: Install dependencies run: | diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 69959c7..b282aa9 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -13,15 +13,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - name: Install uv uses: astral-sh/setup-uv@v1 with: version: latest + python-version: '3.11' - name: Ensure Docker Compose is installed run: docker compose --version diff --git a/pyproject.toml b/pyproject.toml index 77bdc1c..d2d6a4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.0.7" description = "Zero-click Appwrite test environments." readme = "README.md" requires-python = ">=3.11" -dependencies = ["playwright", "typer>=0.16.0", "python-dotenv>=1.1.0"] +dependencies = ["playwright", "typer>=0.16.0", "python-dotenv>=1.1.0", "pytest>=8.4.1"] license = "MIT" [tool.setuptools] From 1deed1daac01d71f98c8796ad6f620c9ffcd665c Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:18:40 -0400 Subject: [PATCH 09/16] ci: fix tests for ci to check for docker subcommand --- .github/workflows/run_tests.yaml | 3 +-- appwrite_lab/_orchestrator.py | 16 ++++++++++++++-- tests/test_orchestrator.py | 4 +++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index b282aa9..765b0ea 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -23,5 +23,4 @@ jobs: run: docker compose --version - name: Run tests - run: | - make tests + run: make tests diff --git a/appwrite_lab/_orchestrator.py b/appwrite_lab/_orchestrator.py index 29c1dff..51652d1 100644 --- a/appwrite_lab/_orchestrator.py +++ b/appwrite_lab/_orchestrator.py @@ -448,11 +448,23 @@ def util(self): @property def compose(self): - return shutil.which(f"{self.backend}-compose") + if self.backend == "docker": + # Try docker-compose first, then fall back to docker compose + compose_cmd = shutil.which("docker-compose") + if compose_cmd: + return compose_cmd + else: + return "docker compose" + else: + return shutil.which(f"{self.backend}-compose") def detect_backend(): - if shutil.which("docker") and shutil.which("docker-compose"): + if shutil.which("docker") and ( + shutil.which("docker-compose") or shutil.which("docker compose") + ): + # If docker is available, we can use docker compose (subcommand) + # or docker-compose (standalone binary) return "docker" elif shutil.which("podman") and shutil.which("podman-compose"): return "podman" diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py index 75fe556..8bf78fc 100644 --- a/tests/test_orchestrator.py +++ b/tests/test_orchestrator.py @@ -19,7 +19,9 @@ def orchestrator(state: State): def test_orchestrator_init(orchestrator: ServiceOrchestrator): assert orchestrator.backend == "docker" assert orchestrator.util.endswith("docker") - assert orchestrator.compose.endswith("docker-compose") + # Check for either docker-compose or docker compose + compose_cmd = orchestrator.compose + assert compose_cmd.endswith("docker-compose") or compose_cmd == "docker compose" def test_get_templates(): From d0d8315e8e4e95907c9aa34bac93532a88adfb0f Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:20:16 -0400 Subject: [PATCH 10/16] ci: fixes --- .github/workflows/run_tests.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 765b0ea..e68bb13 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -19,8 +19,10 @@ jobs: version: latest python-version: '3.11' - - name: Ensure Docker Compose is installed - run: docker compose --version + - name: Ensure Docker and Docker Compose is installed + run: | + docker --version + docker compose --version - name: Run tests run: make tests From 353336b7011dc2c68dd75b341813dd7e618fa6ed Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:23:20 -0400 Subject: [PATCH 11/16] ci: fixes --- .github/workflows/run_tests.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index e68bb13..e86214b 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -18,7 +18,10 @@ jobs: with: version: latest python-version: '3.11' - + + - name: Set up Docker + uses: docker/setup-buildx-action@v3 + - name: Ensure Docker and Docker Compose is installed run: | docker --version From ea6a9408c582a2ab30868e9893acc8ab2ca4e381 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:30:06 -0400 Subject: [PATCH 12/16] ci: fixes --- appwrite_lab/_orchestrator.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/appwrite_lab/_orchestrator.py b/appwrite_lab/_orchestrator.py index 51652d1..a11765d 100644 --- a/appwrite_lab/_orchestrator.py +++ b/appwrite_lab/_orchestrator.py @@ -460,16 +460,23 @@ def compose(self): def detect_backend(): - if shutil.which("docker") and ( - shutil.which("docker-compose") or shutil.which("docker compose") - ): - # If docker is available, we can use docker compose (subcommand) - # or docker-compose (standalone binary) - return "docker" - elif shutil.which("podman") and shutil.which("podman-compose"): + if shutil.which("docker"): + try: + subprocess.run( + ["docker", "compose", "version"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return "docker" + except Exception: + pass + # Check for legacy 'docker-compose' binary + if shutil.which("docker-compose"): + return "docker" + if shutil.which("podman") and shutil.which("podman-compose"): return "podman" - else: - raise RuntimeError("Neither Docker nor Podman found.") + raise RuntimeError("Neither Docker nor Podman found.") def run_cmd(cmd: list[str], envs: dict[str, str] | None = None): From 9835fb3165a9694143926dab46368873eb93c6fd Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:37:10 -0400 Subject: [PATCH 13/16] ci: fix compose --- appwrite_lab/_orchestrator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appwrite_lab/_orchestrator.py b/appwrite_lab/_orchestrator.py index a11765d..35e8774 100644 --- a/appwrite_lab/_orchestrator.py +++ b/appwrite_lab/_orchestrator.py @@ -146,7 +146,7 @@ def _deploy_service( """ new_env = {**os.environ, **env_vars} cmd = [ - self.compose, + *self.compose, "-f", template_path, "-p", @@ -378,7 +378,7 @@ def teardown_service(self, name: str): data=None, ) cmd = [ - self.compose, + *self.compose, "-p", name, "down", @@ -452,11 +452,11 @@ def compose(self): # Try docker-compose first, then fall back to docker compose compose_cmd = shutil.which("docker-compose") if compose_cmd: - return compose_cmd + return [compose_cmd] else: - return "docker compose" + return [shutil.which("docker"), "compose"] else: - return shutil.which(f"{self.backend}-compose") + return [shutil.which(f"{self.backend}-compose")] def detect_backend(): From 88fd4c8fbf5d51842ed5b1c88bd4d4a235d4af05 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:40:33 -0400 Subject: [PATCH 14/16] ci: fix ensure patches --- .github/workflows/ensure_patched_templates.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ensure_patched_templates.yaml b/.github/workflows/ensure_patched_templates.yaml index a4077e7..64fcdb8 100644 --- a/.github/workflows/ensure_patched_templates.yaml +++ b/.github/workflows/ensure_patched_templates.yaml @@ -19,11 +19,7 @@ jobs: with: version: latest python-version: '3.11' - - - name: Install dependencies - run: | - uv pip install pytest ruamel.yaml - + - name: Test patch_templates target run: | make patch_templates From 100a02d5d20814b57b1b95584e12ea1f2d9b86c8 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:42:20 -0400 Subject: [PATCH 15/16] fix: source to . for ci --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9dcb996..cb16c21 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ build_appwrite_playwright: patch_templates: @VENV=$$(mktemp -d) && \ uv venv $$VENV && \ - source $$VENV/bin/activate && \ + . $$VENV/bin/activate && \ uv pip install ruamel.yaml && \ python scripts/selinuxify_template_patch.py && \ rm -rf $$VENV From 04b8200a04e94ce841212f880116073bda8ef0b6 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 19:54:02 -0400 Subject: [PATCH 16/16] ci: more logging for patching and fix for ci --- Makefile | 6 +++--- appwrite_lab/test_suite/fixtures.py | 1 + scripts/selinuxify_template_patch.py | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index cb16c21..d5c6651 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,9 @@ build_appwrite_playwright: # docker push appwrite-cli:$(APPWRITE_CLI_TAG) patch_templates: @VENV=$$(mktemp -d) && \ - uv venv $$VENV && \ - . $$VENV/bin/activate && \ - uv pip install ruamel.yaml && \ + uv venv $$VENV > /dev/null 2>&1 && \ + . $$VENV/bin/activate > /dev/null 2>&1 && \ + uv pip install ruamel.yaml > /dev/null 2>&1 && \ python scripts/selinuxify_template_patch.py && \ rm -rf $$VENV diff --git a/appwrite_lab/test_suite/fixtures.py b/appwrite_lab/test_suite/fixtures.py index 8fcbb5b..5f64862 100644 --- a/appwrite_lab/test_suite/fixtures.py +++ b/appwrite_lab/test_suite/fixtures.py @@ -4,6 +4,7 @@ from appwrite_lab.labs import Labs from appwrite_lab.models import Lab + @pytest.fixture(scope="session") def lab_svc(): """Lab service instance for managing Appwrite labs.""" diff --git a/scripts/selinuxify_template_patch.py b/scripts/selinuxify_template_patch.py index 65a712c..e1d3432 100644 --- a/scripts/selinuxify_template_patch.py +++ b/scripts/selinuxify_template_patch.py @@ -9,9 +9,14 @@ templates_dir = Path(__file__).parent.parent / "appwrite_lab" / "templates" list_of_templates = [f for f in os.listdir(templates_dir) if f.endswith(".yml")] + +changes_made = False + for template in list_of_templates: with open(templates_dir / template) as f: data = yaml.load(f) + original_data = yaml.load(f.read()) + f.seek(0) for service in data.get("services", {}).values(): if "volumes" in service: @@ -26,10 +31,20 @@ if item in volume_skip: skip = True break + # Skip if item already has SELinux label (:Z or ,Z) + if item.endswith("Z"): + skip = True + break if not skip: v = v + ":Z" if len(split) == 2 else v + ",Z" + changes_made = True new_vols.append(v) service["volumes"] = new_vols with open(templates_dir / template, "w") as f: yaml.dump(data, f) + +if not changes_made: + print("✅ All templates are already patched.") +else: + print("✅ Templates updated with SELinux labels.")