Skip to content

Commit 14f8018

Browse files
committed
feat: link Tobiko Cloud plans from GitHub CICD bot
Signed-off-by: mday-io <mdaytn@gmail.com>
1 parent aebfac9 commit 14f8018

3 files changed

Lines changed: 446 additions & 7 deletions

File tree

docs/integrations/github.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ jobs:
5959
6060
Next checkout the [Core Bot Behavior Configuration Guide](#core-bot-behavior-configuration-guide) to see how to configure the bot's core behavior. Then checkout [Bot Configuration](#bot-configuration) to see how to configure the bot's behavior in general. Finally, checkout [Custom Workflow Configuration](#custom-workflow-configuration) to see the full set of customizations available to the bot.
6161
62+
If the workflow runs SQLMesh through Tobiko Cloud, set `TCLOUD_URL` or `TCLOUD_BASE_URL` in the workflow environment. When one of these variables is present, the bot adds or updates pull request comments with links to Tobiko Cloud plans for PR environments and production deploys.
63+
6264
## Core Bot Behavior Configuration Guide
6365

6466
There are two fundamental ways the bot can be configured: synchronized or desynchronized production code and data.

sqlmesh/integrations/github/cicd/controller.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656

5757
logger = logging.getLogger(__name__)
5858

59+
PR_ENVIRONMENT_PLAN_LINK_COMMENT_MARKER = "<!-- sqlmesh-pr-plan-link -->"
60+
PROD_PLAN_LINK_COMMENT_MARKER = "<!-- sqlmesh-prod-plan-link -->"
61+
5962

6063
class TestFailure(Exception):
6164
pass
@@ -678,16 +681,19 @@ def run_linter(self) -> None:
678681
self._context.lint_models()
679682

680683
def _get_or_create_comment(self, header: str = BOT_HEADER_MSG) -> IssueComment:
681-
comment = seq_get(
682-
[comment for comment in self._issue.get_comments() if header in comment.body],
683-
0,
684-
)
684+
comment = self._get_comment(header=header)
685685
if not comment:
686686
logger.debug(f"Did not find comment so creating one with header: {header}")
687687
return self._issue.create_comment(header)
688688
logger.debug(f"Found comment with header: {header}")
689689
return comment
690690

691+
def _get_comment(self, header: str) -> t.Optional[IssueComment]:
692+
return seq_get(
693+
[comment for comment in self._issue.get_comments() if header in comment.body],
694+
0,
695+
)
696+
691697
def _get_merge_state_status(self) -> MergeStateStatus:
692698
"""
693699
This feature is currently in preview and therefore not available in the python module.
@@ -741,13 +747,59 @@ def update_sqlmesh_comment_info(
741747
comment.edit(body=body)
742748
return True, comment
743749

750+
def update_pr_environment_plan_comment(self, plan: Plan) -> None:
751+
self._update_tobiko_cloud_plan_comment(
752+
plan=plan,
753+
environment=self.pr_environment_name,
754+
marker=PR_ENVIRONMENT_PLAN_LINK_COMMENT_MARKER,
755+
description="SQLMesh PR environment plan progress.",
756+
link_label=f"Tobiko Cloud / environments / {self.pr_environment_name} / plans / {plan.plan_id}",
757+
)
758+
759+
def update_prod_plan_comment(self, plan: Plan) -> None:
760+
self._update_tobiko_cloud_plan_comment(
761+
plan=plan,
762+
environment=c.PROD,
763+
marker=PROD_PLAN_LINK_COMMENT_MARKER,
764+
description="SQLMesh production deploy plan.",
765+
link_label=f"Tobiko Cloud / environments / {c.PROD} / plans / {plan.plan_id}",
766+
)
767+
768+
def _update_tobiko_cloud_plan_comment(
769+
self,
770+
*,
771+
plan: Plan,
772+
environment: str,
773+
marker: str,
774+
description: str,
775+
link_label: str,
776+
) -> None:
777+
cloud_url = os.environ.get("TCLOUD_URL") or os.environ.get("TCLOUD_BASE_URL")
778+
if not cloud_url:
779+
return
780+
if (
781+
not plan.context_diff.has_changes
782+
and not plan.requires_backfill
783+
and not plan.has_unmodified_unpromoted
784+
):
785+
if comment := self._get_comment(header=marker):
786+
comment.edit(body=f"{marker}\n\nNo current SQLMesh plan.")
787+
return
788+
789+
plan_url = f"{cloud_url.rstrip('/')}/environments/{environment}/plans/{plan.plan_id}"
790+
body = f"{marker}\n\n{description}\n\nPlan: [{link_label}]({plan_url})"
791+
comment = self._get_or_create_comment(header=marker)
792+
comment.edit(body=body)
793+
744794
def update_pr_environment(self) -> None:
745795
"""
746796
Creates a PR environment from the logic present in the PR. If the PR contains changes that are
747797
uncategorized, then an error will be raised.
748798
"""
749799
self._console.consume_captured_output() # clear output buffer
750-
self._context.apply(self.pr_plan) # will raise if PR environment creation fails
800+
plan = self.pr_plan
801+
self._context.apply(plan) # will raise if PR environment creation fails
802+
self.update_pr_environment_plan_comment(plan)
751803

752804
# update PR info comment
753805
vde_title = "- :eyes: To **review** this PR's changes, use virtual data environment:"
@@ -800,7 +852,9 @@ def deploy_to_prod(self) -> None:
800852
value=plan_summary,
801853
dedup_regex=None,
802854
)
803-
self._context.apply(self.prod_plan)
855+
plan = self.prod_plan
856+
self.update_prod_plan_comment(plan)
857+
self._context.apply(plan)
804858

805859
def try_invalidate_pr_environment(self) -> None:
806860
"""

0 commit comments

Comments
 (0)