Skip to content
Closed
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,12 @@ AZURE_MAX_RESULTS=100
VICTORIA_LOGS_URL=
VICTORIA_LOGS_TENANT_ID=

# Trello
TRELLO_API_KEY=
TRELLO_TOKEN=
TRELLO_BOARD_ID=
TRELLO_LIST_ID=

# Vercel
VERCEL_API_TOKEN=
VERCEL_TEAM_ID=
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ OpenSRE connects to 60+ tools and services across the modern cloud stack, from L
| **Database** | MongoDB · ClickHouse · PostgreSQL · MySQL · MariaDB · MongoDB Atlas · Azure SQL · Snowflake · Redis · RDS · Supabase | |
| **Data Platform** | Apache Airflow · Apache Kafka · Apache Spark · Prefect · RabbitMQ · Dagster | |
| **Dev Tools** | <img src="docs/assets/icons/github.webp" width="16"> GitHub · GitHub MCP · Bitbucket · GitLab | |
| **Incident Management** | <img src="docs/assets/icons/pagerduty.png" width="16"> PagerDuty · Opsgenie · Jira · Alertmanager · incident.io · ServiceNow | [Trello](https://github.com/Tracer-Cloud/opensre/issues/361) · [Linear](https://github.com/Tracer-Cloud/opensre/issues/124) |
| **Incident Management** | <img src="docs/assets/icons/pagerduty.png" width="16"> PagerDuty · Opsgenie · Jira · Alertmanager · incident.io · ServiceNow · Trello | [Linear](https://github.com/Tracer-Cloud/opensre/issues/124) |
| **Communication** | <img src="docs/assets/icons/slack.png" width="16"> Slack · Google Docs · Discord · Telegram · <img src="docs/assets/icons/rocketchat.png" width="16"> Rocket.Chat · WhatsApp | [Notion](https://github.com/Tracer-Cloud/opensre/issues/286) · [Teams](https://github.com/Tracer-Cloud/opensre/issues/138) · [Confluence](https://github.com/Tracer-Cloud/opensre/issues/313) |
| **Agent Deployment** | <img src="docs/assets/icons/vercel.png" width="16"> Vercel · <img src="docs/assets/icons/aws.png" width="16"> EC2 · <img src="docs/assets/icons/aws.png" width="16"> ECS · Railway | |
| **Protocols** | <img src="docs/assets/icons/mcp.svg" width="16"> MCP · <img src="docs/assets/icons/acp.png" width="16"> ACP · <img src="docs/assets/icons/openclaw.jpg" width="16"> OpenClaw | |
Expand Down
6 changes: 1 addition & 5 deletions docs/integrations/trello.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ You can also save your Trello configuration to `~/.opensre/integrations.json`:

## Getting your API key and token

1. Visit https://trello.com/app-key
1. Visit https://trello.com/power-ups/admin
2. Copy your Trello API key
3. Generate a token from the same page
4. Authorize access when prompted
Expand Down Expand Up @@ -111,7 +111,3 @@ Once configured, OpenSRE can:
- Track follow-up tasks on your incident board

Set `TRELLO_LIST_ID` to the list where new cards should appear. If omitted, card creation requires the list ID at investigation time.

<Info>
Trello does not currently support `opensre integrations verify trello`. Confirm your credentials during interactive setup or by creating a test card from an investigation.
</Info>
10 changes: 10 additions & 0 deletions integrations/_catalog_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@
from integrations.tempo import classify as _classify_tempo
from integrations.tempo import tempo_config_from_env
from integrations.temporal import classify as _classify_temporal
from integrations.trello.config import trello_config_from_env
from integrations.temporal.client import TemporalConfig
from integrations.twilio import classify as _classify_twilio
from integrations.vercel import classify as _classify_vercel
Expand Down Expand Up @@ -1009,6 +1010,15 @@ def load_env_integrations() -> list[dict[str, Any]]:
else:
integrations.append(_active_env_record("discord", discord_config.model_dump()))

trello_config = trello_config_from_env()
if trello_config:
integrations.append(
_active_env_record(
"trello",
trello_config.model_dump(exclude={"integration_id"}),
)
)

airflow_config = airflow_config_from_env()
if airflow_config is not None:
integrations.append(_active_env_record("airflow", airflow_config.model_dump()))
Expand Down
8 changes: 7 additions & 1 deletion integrations/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,13 @@ class IntegrationSpec:
setup_order=40,
verify_order=54,
),
IntegrationSpec(service="trello"),
IntegrationSpec(
service="trello",
has_verifier=True,
direct_effective=True,
setup_order=43,
verify_order=57,
),
IntegrationSpec(service="rds", setup_order=11),
IntegrationSpec(
service="supabase",
Expand Down
6 changes: 2 additions & 4 deletions integrations/trello/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@
trello_config_from_env,
)
from integrations.trello.verifier import (
TrelloValidationResult,
validate_trello_config,
verify_trello,
)

__all__ = [
"DEFAULT_TRELLO_BASE_URL",
"TrelloConfig",
"TrelloValidationResult",
"build_trello_config",
"create_trello_card",
"trello_config_from_env",
"validate_trello_config",
"validate_trello_connection",
"verify_trello",
]
43 changes: 43 additions & 0 deletions integrations/trello/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""What Trello needs before it is considered configured."""

from __future__ import annotations

from integrations.setup_flow import IntegrationSetupSpec, SetupField
from integrations.trello.verifier import verify_trello

TRELLO_SETUP = IntegrationSetupSpec(
service="trello",
fields=(
SetupField(
name="api_key",
label="API Key",
prompt="Trello API Key",
env_var="TRELLO_API_KEY",
secret=True,
),
SetupField(
name="token",
label="Token",
prompt="Trello Token",
env_var="TRELLO_TOKEN",
secret=True,
),
SetupField(
name="board_id",
label="Board ID",
prompt="Trello Board ID (optional)",
env_var="TRELLO_BOARD_ID",
optional=True,
),
SetupField(
name="list_id",
label="List ID",
prompt="Trello List ID",
env_var="TRELLO_LIST_ID",
optional=False,
),
),
verify=verify_trello,
)

__all__ = ["TRELLO_SETUP"]
7 changes: 7 additions & 0 deletions integrations/trello/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Trello integration tools."""

from __future__ import annotations

from integrations.trello.tools.create_card import create_trello_card_tool
Comment thread
greptile-apps[bot] marked this conversation as resolved.

__all__ = ["create_trello_card_tool"]
115 changes: 115 additions & 0 deletions integrations/trello/tools/create_card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Trello create card tool for investigation workflows."""

from __future__ import annotations

from typing import Any

from core.tool_framework.base import BaseTool
from core.tool_framework.utils.tool_availability import tool_unavailable
from integrations.trello.client import create_trello_card
from integrations.trello.config import build_trello_config


class CreateTrelloCardTool(BaseTool):
"""Create a new card in Trello for tracking tasks or incidents."""

name = "create_trello_card"
source = "trello"
description = (
"Create a new card in Trello to track incident resolution, action items, "
"or postmortem tasks. Requires the name and description of the card."
)
use_cases = [
"Creating a tracking card for a newly discovered bug",
"Adding action items from an incident postmortem",
"Tracking remediation steps during an active incident",
]
requires = ["api_key", "token", "list_id", "name", "desc"]
injected_params = ["api_key", "token"]
input_schema = {
"type": "object",
"properties": {
"api_key": {"type": "string", "description": "Trello API key"},
"token": {"type": "string", "description": "Trello token"},
"board_id": {"type": "string", "description": "Trello Board ID (optional)"},
"list_id": {
"type": "string",
"description": "Trello List ID (optional if configured in environment)",
},
"name": {"type": "string", "description": "Title of the card"},
"desc": {"type": "string", "description": "Detailed description for the card"},
},
"required": ["api_key", "token", "name", "desc"],
}
outputs = {
"card_id": "The ID of the created card",
"url": "The URL of the created card",
}

def is_available(self, sources: dict) -> bool:
trello_config = sources.get("trello", {})
return bool(
trello_config.get("api_key")
and trello_config.get("token")
and trello_config.get("list_id")
)

def extract_params(self, sources: dict) -> dict[str, Any]:
trello = sources.get("trello", {})
return {
"api_key": trello.get("api_key", ""),
"token": trello.get("token", ""),
"board_id": trello.get("board_id", ""),
"list_id": trello.get("list_id", ""),
"name": "",
"desc": "",
}

def run(
self,
api_key: str,
token: str,
name: str,
desc: str,
board_id: str = "",
list_id: str = "",
**_kwargs: Any,
) -> dict[str, Any]:
if not api_key or not token:
return tool_unavailable("trello", "Trello integration is not configured.")
if not name:
return tool_unavailable("trello", "Card name is required.")
if not desc:
return tool_unavailable("trello", "Card description is required.")

config = build_trello_config(
{
"api_key": api_key,
"token": token,
"board_id": board_id,
"list_id": list_id,
}
)

target_list_id = (list_id or config.list_id).strip()
if not target_list_id:
return tool_unavailable("trello", "List ID is required to create a Trello card.")

try:
result = create_trello_card(
config=config,
name=name,
desc=desc,
list_id=target_list_id,
)
return {
"source": "trello",
"available": True,
"card_id": result.get("id", ""),
"url": result.get("shortUrl", ""),
}
except Exception as err:
return tool_unavailable("trello", str(err))


create_trello_card_tool = CreateTrelloCardTool()
45 changes: 23 additions & 22 deletions integrations/trello/verifier.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,48 @@
"""Trello credential and connectivity verification."""

import logging
from dataclasses import dataclass
from typing import Any

import httpx

import integrations.trello.client as client
from integrations._validation_helpers import report_validation_failure
from integrations.trello.config import TrelloConfig
from integrations.trello.config import build_trello_config
from integrations.verification import register_verifier, result

logger = logging.getLogger(__name__)


Comment thread
greptile-apps[bot] marked this conversation as resolved.
@dataclass(frozen=True)
class TrelloValidationResult:
"""Result of validating a Trello integration."""

ok: bool
detail: str

@register_verifier("trello")
def verify_trello(source: str, config: dict[str, Any]) -> dict[str, str]:
Comment thread
greptile-apps[bot] marked this conversation as resolved.
"""Validate Trello connectivity via the standard registry."""
try:
trello_config = build_trello_config(config)
except Exception as err:
return result("trello", source, "missing", f"Invalid Trello configuration: {err}")

def validate_trello_config(config: TrelloConfig) -> TrelloValidationResult:
"""Validate Trello connectivity."""
if not config.api_key:
return TrelloValidationResult(ok=False, detail="Trello API key is required.")
if not config.token:
return TrelloValidationResult(ok=False, detail="Trello token is required.")
if not trello_config.api_key:
return result("trello", source, "missing", "Trello API key is required.")
if not trello_config.token:
return result("trello", source, "missing", "Trello token is required.")

try:
member = client.validate_trello_connection(config=config)
member = client.validate_trello_connection(config=trello_config)
username = member.get("username", "unknown")
return TrelloValidationResult(
ok=True,
detail=f"Trello connectivity successful. Authenticated as @{username}",
return result(
"trello",
source,
"passed",
f"Trello connectivity successful. Authenticated as @{username}",
)
except httpx.HTTPStatusError as err:
detail = err.response.text.strip() or str(err)
return TrelloValidationResult(ok=False, detail=f"Trello validation failed: {detail}")
return result("trello", source, "failed", f"Trello validation failed: {detail}")
except Exception as err:
report_validation_failure(
err,
logger=logger,
integration="trello",
method="validate_trello_config",
method="verify_trello",
)
return TrelloValidationResult(ok=False, detail=f"Trello validation failed: {err}")
return result("trello", source, "error", f"Trello validation failed: {err}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Verifier errors bypass failure handling

When Trello verification encounters a connection, timeout, response, or other generic exception, this returns the unsupported error status. The shared verifier handles failures as failed or missing and does not count error as a failing exit condition, so a real Trello connectivity failure can produce a successful verification exit code.

Knowledge Base Used: Integrations Framework

3 changes: 3 additions & 0 deletions surfaces/cli/wizard/_integration_configurators.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
_configure_jira,
_configure_notion,
_configure_servicenow,
_configure_trello,
)
from surfaces.cli.wizard.configurators.sentry import _configure_sentry, _configure_sentry_mcp
from surfaces.cli.wizard.configurators.vercel import _configure_vercel
Expand Down Expand Up @@ -98,6 +99,7 @@ def _configure_selected_integrations() -> tuple[list[str], str | None]:
"betterstack": _configure_betterstack,
"jira": _configure_jira,
"servicenow": _configure_servicenow,
"trello": _configure_trello,
"alertmanager": _configure_alertmanager,
"opsgenie": _configure_opsgenie,
"pagerduty": _configure_pagerduty,
Expand Down Expand Up @@ -131,6 +133,7 @@ def _configure_selected_integrations() -> tuple[list[str], str | None]:
"dagster": "dagster",
"jira": "jira",
"servicenow": "servicenow",
"trello": "trello",
"alertmanager": "alertmanager",
"opsgenie": "opsgenie",
"pagerduty": "pagerduty",
Expand Down
14 changes: 14 additions & 0 deletions surfaces/cli/wizard/configurators/productivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,20 @@ def _configure_servicenow() -> tuple[str, str]:
)


def _configure_trello() -> tuple[str, str]:
from integrations.trello.setup import TRELLO_SETUP

return configure_from_spec(
TRELLO_SETUP,
title="Trello",
intro=(
"\n[bold]Trello Integration[/bold]\n"
"Create a Power-Up and generate an API key and token at "
"https://trello.com/power-ups/admin\n"
),
)


def _configure_google_docs() -> tuple[str, str]:
_, credentials = _integration_defaults("google_docs")
while True:
Expand Down
6 changes: 6 additions & 0 deletions surfaces/cli/wizard/onboard_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@
group="Incident & Comms",
hint="Post findings to a Rocket.Chat channel",
),
Choice(
value="trello",
label="Trello",
group="Incident & Comms",
hint="Manage tasks and cards in Trello",
),
Choice(
value="google_docs",
label="Google Docs",
Expand Down
Loading