diff --git a/press/api/account.py b/press/api/account.py index a701f93ccc..6910083e59 100644 --- a/press/api/account.py +++ b/press/api/account.py @@ -981,6 +981,11 @@ def get_frappe_io_auth_url() -> str | None: return None +@frappe.whitelist(allow_guest=True) +def frappe_io_login(): + return redirect_to(get_frappe_io_auth_url() or "/") + + @frappe.whitelist() def get_emails(): team = get_current_team(get_doc=False) diff --git a/press/hooks.py b/press/hooks.py index c139015d30..e935531f51 100644 --- a/press/hooks.py +++ b/press/hooks.py @@ -1,5 +1,3 @@ -from press.api.account import get_frappe_io_auth_url - from . import __version__ as app_version app_name = "press" @@ -69,7 +67,7 @@ ] website_redirects = [ - {"source": "/dashboard/f-login", "target": get_frappe_io_auth_url() or "/"}, + {"source": "/dashboard/f-login", "target": "/api/method/press.api.account.frappe_io_login"}, { "source": "/suspended-site", "target": "/api/method/press.api.handle_suspended_site_redirection", diff --git a/press/press/doctype/bench_shell_log/bench_shell_log.py b/press/press/doctype/bench_shell_log/bench_shell_log.py index 5dbc3ff5c1..57882747f0 100644 --- a/press/press/doctype/bench_shell_log/bench_shell_log.py +++ b/press/press/doctype/bench_shell_log/bench_shell_log.py @@ -7,20 +7,17 @@ import frappe from frappe.model.document import Document -ExecuteResult = TypedDict( - "ExecuteResult", - { - "command": str, - "status": str, - "start": str, - "end": str, - "duration": float, - "output": str, - "directory": Optional[str], - "traceback": Optional[str], - "returncode": Optional[int], - }, -) + +class ExecuteResult(TypedDict): + command: str + status: str + start: str + end: str + duration: float + output: str + directory: Optional[str] + traceback: Optional[str] + returncode: Optional[int] class BenchShellLog(Document): diff --git a/press/press/doctype/deploy_candidate/cache_utils.py b/press/press/doctype/deploy_candidate/cache_utils.py index a599d6d5a0..5e6e2fa657 100644 --- a/press/press/doctype/deploy_candidate/cache_utils.py +++ b/press/press/doctype/deploy_candidate/cache_utils.py @@ -10,13 +10,12 @@ from textwrap import dedent from typing import Tuple, TypedDict -CommandOutput = TypedDict( - "CommandOutput", - cwd=str, - image_tag=str, - returncode=int, - output=str, -) + +class CommandOutput(TypedDict): + cwd: str + image_tag: str + returncode: int + output: str def copy_file_from_docker_cache( diff --git a/press/press/doctype/deploy_candidate/docker_output_parsers.py b/press/press/doctype/deploy_candidate/docker_output_parsers.py index 8f934cb65a..184dac9a5f 100644 --- a/press/press/doctype/deploy_candidate/docker_output_parsers.py +++ b/press/press/doctype/deploy_candidate/docker_output_parsers.py @@ -2,10 +2,10 @@ import json import re +import shlex import typing from typing import Literal -import dockerfile import frappe from frappe.core.utils import find from frappe.utils import now_datetime, rounded @@ -211,9 +211,7 @@ def ansi_escape(text: str) -> str: def get_command(name: str) -> str: - # Strip docker flags and commands from the line - line = dockerfile.parse_string(name)[0] - command = " ".join(line.value).strip() or line.original.split(maxsplit=1)[1] + command = _get_run_command(name) command = command.split("`#stage-", maxsplit=1)[0] # Remove line fold slashes @@ -227,6 +225,27 @@ def get_command(name: str) -> str: return "\n".join([p for p in splits if len(p)]) +def _get_run_command(line: str) -> str: + instruction, _, value = line.partition(" ") + if instruction.upper() != "RUN": + return value + + value = value.strip() + if value.startswith("["): + return value + + try: + parts = shlex.split(value, posix=True) + except ValueError: + return value + + for i, part in enumerate(parts): + if not part.startswith("--"): + return shlex.join(parts[i:]) + + return "" + + class StepMixin: def _get_agent_step( self, diff --git a/press/press/doctype/deploy_candidate/test_docker_output_parsers.py b/press/press/doctype/deploy_candidate/test_docker_output_parsers.py new file mode 100644 index 0000000000..6c4327bcd0 --- /dev/null +++ b/press/press/doctype/deploy_candidate/test_docker_output_parsers.py @@ -0,0 +1,28 @@ +from unittest import TestCase + +from press.press.doctype.deploy_candidate.docker_output_parsers import get_command + + +class TestDockerOutputParsers(TestCase): + def test_get_command_strips_run_flags_and_stage_marker(self): + name = "RUN --mount=type=cache,target=/root/.cache pip install -e apps/frappe `#stage-install-frappe`" + + self.assertEqual(get_command(name), "pip install -e apps/frappe") + + def test_get_command_normalizes_line_folds(self): + name = "RUN apt-get update \\\n && apt-get install -y curl \\\n && rm -rf /var/lib/apt/lists/* `#stage-setup-apt`" + + self.assertEqual( + get_command(name), + "apt-get update\n&& apt-get install -y curl\n&& rm -rf /var/lib/apt/lists/*", + ) + + def test_get_command_keeps_json_form_run(self): + name = 'RUN ["python", "-m", "compileall", "apps"] `#stage-compile-assets`' + + self.assertEqual(get_command(name), '["python", "-m", "compileall", "apps"]') + + def test_get_command_preserves_quoted_arguments(self): + name = 'RUN echo "hello world" `#stage-print-message`' + + self.assertEqual(get_command(name), 'echo "hello world"') diff --git a/press/press/doctype/log_counter/log_counter.py b/press/press/doctype/log_counter/log_counter.py index f9ae1f4f3f..79d51c547f 100644 --- a/press/press/doctype/log_counter/log_counter.py +++ b/press/press/doctype/log_counter/log_counter.py @@ -17,14 +17,11 @@ "Error Log": "method", } -Counts = TypedDict( - "Counts", - { - "counts": dict[str, int], - "date": datetime.date, - "total": int, - }, -) + +class Counts(TypedDict): + counts: dict[str, int] + date: datetime.date + total: int class LogCounter(Document): diff --git a/press/telegram_utils.py b/press/telegram_utils.py index f1ca5292b6..14855fa740 100644 --- a/press/telegram_utils.py +++ b/press/telegram_utils.py @@ -2,8 +2,11 @@ # For license information, please see license.txt +import asyncio + import frappe import telegram +from telegram.constants import MessageLimit, ParseMode from press.utils import log_error @@ -30,14 +33,13 @@ def send(self, message, html=False, reraise=False): if not message: return None try: - text = message[: telegram.MAX_MESSAGE_LENGTH] + text = message[: MessageLimit.MAX_TEXT_LENGTH] parse_mode = self._get_parse_mode(html) - return self.bot.send_message( - chat_id=self.chat_id, - text=text, - parse_mode=parse_mode, - message_thread_id=self.topic_id, - timeout=3, + return asyncio.run( + self._send_message( + text=text, + parse_mode=parse_mode, + ) ) except Exception: if reraise: @@ -52,8 +54,30 @@ def send(self, message, html=False, reraise=False): def _get_parse_mode(self, html): if html: - return telegram.ParseMode.HTML - return telegram.ParseMode.MARKDOWN + return ParseMode.HTML + return ParseMode.MARKDOWN + + async def _send_message(self, text, parse_mode): + bot = self.bot + async with bot: + return await bot.send_message( + chat_id=self.chat_id, + text=text, + parse_mode=parse_mode, + message_thread_id=self.topic_id, + read_timeout=3, + write_timeout=3, + connect_timeout=3, + pool_timeout=3, + ) + + def _get_bot_username(self): + return asyncio.run(self._get_bot_username_async()) + + async def _get_bot_username_async(self): + bot = self.bot + async with bot: + return bot.username @property def bot(self): @@ -82,7 +106,7 @@ def respond(self, message): mention = text[begin:end] # Only respond to messages mentioning the bot - if mention != f"@{self.bot.username}": + if mention != f"@{self._get_bot_username()}": return command = text.replace(mention, "") diff --git a/pyproject.toml b/pyproject.toml index b6c8a71462..b9a5f406ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,14 +22,13 @@ classifiers = [ "Programming Language :: Python :: 3.12", ] dependencies = [ - "ansible==3.4.0", + "ansible==13.6.0", "beautifulsoup4", "boto3==1.39.14", "Click", "coverage", "dnspython==1.16.0", "docker==6.1.2", - "dockerfile==3.2.0", "oci==2.116.0", "paramiko==3.3.1", "pexpect==4.8.0", @@ -37,14 +36,14 @@ dependencies = [ "prometheus-client==0.19.0", "PyGithub==1.44.1", "pyOpenSSL~=23.2.0", - "python-telegram-bot==13.15", + "python-telegram-bot==22.7", "razorpay>=1.3.0", "requests<2.32", "responses==0.23.1", "selenium==4.23.1", "semantic-version==2.10.0", "sql_metadata==2.10.0", - "stripe~=2.56.0", + "stripe==15.1.0", "tldextract==3.4.4", "tomli==2.0.1", "tqdm==4.66.3", @@ -52,7 +51,8 @@ dependencies = [ "wrapt~=1.15.0", "elasticsearch-dsl>=8.0.0,<9.0.0", "hcloud==2.2.1", - "playwright==1.49.1", + "greenlet==3.5.0", + "playwright==1.59.0", "prometheus-api-client==0.6.0", "pydo==0.24.0", "semgrep==1.159.0",