Skip to content

Commit 17f75d5

Browse files
authored
Support for CI, Tests, and fixes (#4)
* feat: change listing to include passwords * fix: ocassional bug for new deployment of lab * feat: add testing appwrite.json * chore: script for selinux template patching * feat: new targets * refactor: supprot for api key names, other fixes * ci: add tests runs and template patch ensuring * ci: fixes * ci: fix tests for ci to check for docker subcommand * ci: fixes * ci: fixes * ci: fixes * ci: fix compose * ci: fix ensure patches * fix: source to . for ci * ci: more logging for patching and fix for ci
1 parent d1a6e0d commit 17f75d5

File tree

15 files changed

+303
-43
lines changed

15 files changed

+303
-43
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Ensure Patched Templates
2+
3+
on:
4+
pull_request:
5+
branches: [ main, dev ]
6+
workflow_dispatch:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
17+
- name: Install uv
18+
uses: astral-sh/setup-uv@v1
19+
with:
20+
version: latest
21+
python-version: '3.11'
22+
23+
- name: Test patch_templates target
24+
run: |
25+
make patch_templates
26+
27+
if [ -n "$(git status --porcelain)" ]; then
28+
echo "Error: Running patch_templates produced changes. Please run 'make patch_templates' locally and commit the changes."
29+
echo "git status"
30+
git status
31+
echo "git diff"
32+
git diff
33+
exit 1
34+
fi

.github/workflows/run_tests.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Run Tests
2+
3+
on:
4+
pull_request:
5+
branches: [ main, dev ]
6+
workflow_dispatch:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Install uv
17+
uses: astral-sh/setup-uv@v1
18+
with:
19+
version: latest
20+
python-version: '3.11'
21+
22+
- name: Set up Docker
23+
uses: docker/setup-buildx-action@v3
24+
25+
- name: Ensure Docker and Docker Compose is installed
26+
run: |
27+
docker --version
28+
docker compose --version
29+
30+
- name: Run tests
31+
run: make tests

Makefile

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,19 @@ build_appwrite_playwright:
1111
# push_appwrite_cli:
1212
# docker tag appwrite-cli:latest appwrite-cli:$(APPWRITE_CLI_TAG)
1313
# docker push appwrite-cli:$(APPWRITE_CLI_TAG)
14-
clean-tests:
15-
appwrite-lab stop test-lab
16-
17-
tests:
18-
source .venv/bin/activate && pytest -m e2e
19-
2014
patch_templates:
2115
@VENV=$$(mktemp -d) && \
22-
uv venv $$VENV && \
23-
source $$VENV/bin/activate && \
24-
uv pip install ruamel.yaml && \
16+
uv venv $$VENV > /dev/null 2>&1 && \
17+
. $$VENV/bin/activate > /dev/null 2>&1 && \
18+
uv pip install ruamel.yaml > /dev/null 2>&1 && \
2519
python scripts/selinuxify_template_patch.py && \
2620
rm -rf $$VENV
2721

22+
tests:
23+
uv run pytest -rs -m e2e
24+
25+
clean-tests:
26+
@source .venv/bin/activate && \
27+
appwrite-lab stop test-lab
28+
2829
.PHONY: patch_templates tests clean-tests build_appwrite_cli build_appwrite_playwright

appwrite_lab/_orchestrator.py

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from appwrite_lab.automations.models import BaseVarModel, AppwriteAPIKeyCreation
1010
from ._state import State
1111
from dataclasses import dataclass
12-
from .models import Lab, Automation, SyncType, Project
12+
from .models import Lab, Automation, Project
1313
from dotenv import dotenv_values
1414
from appwrite_lab.utils import console
1515
from .utils import is_cli
@@ -20,7 +20,7 @@
2020
@dataclass
2121
class Response:
2222
message: str
23-
data: any = None
23+
data: str | dict | None = None
2424
error: bool = False
2525
_print_data: bool = False
2626

@@ -69,7 +69,14 @@ def get_formatted_labs(self, collapsed: bool = False):
6969
"""
7070
labs: dict = self.state.get("labs", {})
7171
if collapsed:
72-
headers = ["Name", "Version", "URL", "Admin Email", "Project ID", "API Key"]
72+
headers = [
73+
"Lab Name",
74+
"Version",
75+
"URL",
76+
"Admin Email",
77+
"Admin Password",
78+
"Project ID",
79+
]
7380
data = []
7481
for val in labs.values():
7582
project = Project(**val.get("projects", {}).get("default"))
@@ -79,8 +86,8 @@ def get_formatted_labs(self, collapsed: bool = False):
7986
val["version"],
8087
val["url"],
8188
val["admin_email"],
89+
val["admin_password"],
8290
project.project_id,
83-
project.api_key,
8491
]
8592
)
8693
return headers, data
@@ -139,7 +146,7 @@ def _deploy_service(
139146
"""
140147
new_env = {**os.environ, **env_vars}
141148
cmd = [
142-
self.compose,
149+
*self.compose,
143150
"-f",
144151
template_path,
145152
"-p",
@@ -151,7 +158,12 @@ def _deploy_service(
151158
return self._run_cmd_safely(cmd, envs=new_env)
152159

153160
def deploy_appwrite_lab(
154-
self, name: str, version: str, port: int, meta: dict[str, str]
161+
self,
162+
name: str,
163+
version: str,
164+
port: int,
165+
meta: dict[str, str],
166+
**kwargs: dict[str, str],
155167
):
156168
"""
157169
Deploy an Appwrite lab.
@@ -161,6 +173,7 @@ def deploy_appwrite_lab(
161173
version: The version of the service to deploy.
162174
port: The port to use for the Appwrite service. Must not be in use by another service.
163175
meta: Extra metadata to pass to the deployment.
176+
164177
"""
165178
# sync
166179
appwrite_config = meta.get("appwrite_config", {})
@@ -208,22 +221,27 @@ def deploy_appwrite_lab(
208221
url = ""
209222
proj_id = appwrite_config.pop("project_id", None)
210223
proj_name = appwrite_config.pop("project_name", None)
211-
kwargs = {
224+
_kwargs = {
212225
**appwrite_config,
213226
"projects": {"default": Project(proj_id, proj_name, None)},
214227
}
215228
lab = Lab(
216229
name=name,
217230
version=version,
218231
url=url,
219-
**kwargs,
232+
**_kwargs,
220233
)
221234

222235
lab.generate_missing_config()
223236
# ensure project_id and project_name are set
224237
proj_id = proj_id or lab.projects.get("default").project_id
225238
proj_name = proj_name or lab.projects.get("default").project_name
226-
239+
if kwargs.get("just_deploy", False):
240+
return Response(
241+
error=False,
242+
message=f"Lab '{name}' deployed with --just-deploy flag.",
243+
data=lab,
244+
)
227245
# Deploy playwright automations for creating user and API key
228246
api_key_res = self.deploy_playwright_automation(
229247
lab=lab,
@@ -360,7 +378,7 @@ def teardown_service(self, name: str):
360378
data=None,
361379
)
362380
cmd = [
363-
self.compose,
381+
*self.compose,
364382
"-p",
365383
name,
366384
"down",
@@ -430,16 +448,35 @@ def util(self):
430448

431449
@property
432450
def compose(self):
433-
return shutil.which(f"{self.backend}-compose")
451+
if self.backend == "docker":
452+
# Try docker-compose first, then fall back to docker compose
453+
compose_cmd = shutil.which("docker-compose")
454+
if compose_cmd:
455+
return [compose_cmd]
456+
else:
457+
return [shutil.which("docker"), "compose"]
458+
else:
459+
return [shutil.which(f"{self.backend}-compose")]
434460

435461

436462
def detect_backend():
437-
if shutil.which("docker") and shutil.which("docker-compose"):
438-
return "docker"
439-
elif shutil.which("podman") and shutil.which("podman-compose"):
463+
if shutil.which("docker"):
464+
try:
465+
subprocess.run(
466+
["docker", "compose", "version"],
467+
check=True,
468+
stdout=subprocess.DEVNULL,
469+
stderr=subprocess.DEVNULL,
470+
)
471+
return "docker"
472+
except Exception:
473+
pass
474+
# Check for legacy 'docker-compose' binary
475+
if shutil.which("docker-compose"):
476+
return "docker"
477+
if shutil.which("podman") and shutil.which("podman-compose"):
440478
return "podman"
441-
else:
442-
raise RuntimeError("Neither Docker nor Podman found.")
479+
raise RuntimeError("Neither Docker nor Podman found.")
443480

444481

445482
def run_cmd(cmd: list[str], envs: dict[str, str] | None = None):

appwrite_lab/automations/functions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from playwright.async_api import Playwright, Page, BrowserContext, Browser
22

33

4+
async def wait_until_loaded(page: Page):
5+
"""Wait until the page is loaded."""
6+
await page.wait_for_selector('img[alt="Appwrite Logo"]')
7+
8+
49
async def create_browser_context(playwright: Playwright, headless: bool = True):
510
"""Create a browser context for automation."""
611
browser = await playwright.chromium.launch(headless=headless)
@@ -24,6 +29,7 @@ async def select_project_after_login(page: Page, project_name: str):
2429
async def register_user(page: Page, url: str, admin_email: str, admin_password: str):
2530
"""Register a new user in Appwrite."""
2631
await page.goto(f"{url}/console/register")
32+
await wait_until_loaded(page)
2733
await page.wait_for_timeout(200)
2834
await page.get_by_role("textbox", name="Name").fill("Test User")
2935
await page.get_by_role("textbox", name="Email").fill(admin_email)

appwrite_lab/automations/scripts/create_user_and_api_key.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
create_project_ui,
88
create_api_key_ui,
99
cleanup_browser,
10+
wait_until_loaded,
1011
)
1112
from ..utils import resultify
1213
from ..models import AppwriteAPIKeyCreation, AppwriteUserCreation
@@ -27,7 +28,6 @@ async def create_user_and_api_key(playwright: Playwright) -> str:
2728
auth = AppwriteUserCreation.from_env()
2829
api_key_env = AppwriteAPIKeyCreation.from_env()
2930
work_dir = os.getenv("HOME")
30-
3131
browser, context = await create_browser_context(playwright, headless=True)
3232
page = await context.new_page()
3333

@@ -37,7 +37,9 @@ async def create_user_and_api_key(playwright: Playwright) -> str:
3737
admin_email=auth.admin_email,
3838
admin_password=auth.admin_password,
3939
)
40-
await create_project_ui(page=page, project_name=auth.project_name, project_id=auth.project_id)
40+
await create_project_ui(
41+
page=page, project_name=auth.project_name, project_id=auth.project_id
42+
)
4143

4244
api_key = await create_api_key_ui(
4345
page=page, key_name=api_key_env.key_name, key_expiry=api_key_env.key_expiry

appwrite_lab/cli/new_menu.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ def new_lab(
3535
help="The name of the project to use for the lab. Unset for random.",
3636
show_envvar=False,
3737
),
38+
just_deploy: bool = typer.Option(
39+
False,
40+
is_flag=True,
41+
help="Just deploy the lab without creating an API key or project.",
42+
show_envvar=False,
43+
),
3844
):
3945
"""
4046
Create a new lab.
@@ -49,15 +55,24 @@ def new_lab(
4955
project_name: The name of the project to use for the lab. Unset for random.
5056
"""
5157
labs = get_global_labs()
52-
with console.status(f"Creating lab '{name}'...", spinner="dots") as status:
58+
extra_str = " with simple deployment" if just_deploy else ""
59+
with console.status(
60+
f"Creating lab '{name}'{extra_str}...", spinner="dots"
61+
) as status:
5362
creds = {
5463
"admin_email": email,
5564
"admin_password": password,
5665
"project_id": project_id,
5766
"project_name": project_name,
5867
}
5968

60-
labs.new(name=name, version=version, port=port, meta={"appwrite_config": creds})
69+
labs.new(
70+
name=name,
71+
version=version,
72+
port=port,
73+
meta={"appwrite_config": creds},
74+
just_deploy=just_deploy,
75+
)
6176
status.update(f"Creating lab '{name}'... done")
6277

6378

@@ -88,8 +103,8 @@ def new_api_key(
88103
key = labs.create_api_key(
89104
project_name=project_name, lab_name=lab_name, expiration=expiration
90105
)
91-
return key
92-
# status.update(f"Creating API key for project '{project_name}'... done")
106+
status.update(f"Creating API key for project '{project_name}'... done")
107+
return key.data
93108

94109

95110
@new_menu.command(name="project", help="Create a new project")

0 commit comments

Comments
 (0)