airbyte-internal-ops 0.9.1__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,6 +23,7 @@ from airbyte_ops_mcp.prod_db_access.queries import (
23
23
  query_connections_by_connector,
24
24
  query_connections_by_destination_connector,
25
25
  query_connections_by_stream,
26
+ query_connector_rollouts,
26
27
  query_connector_versions,
27
28
  query_dataplanes_list,
28
29
  query_destination_connection_stats,
@@ -1272,6 +1273,232 @@ def query_prod_connector_connection_stats(
1272
1273
  )
1273
1274
 
1274
1275
 
1276
+ # =============================================================================
1277
+ # Connector Rollout Models and Tools
1278
+ # =============================================================================
1279
+
1280
+
1281
+ class ConnectorRolloutInfo(BaseModel):
1282
+ """Information about a connector rollout."""
1283
+
1284
+ rollout_id: str = Field(description="The rollout UUID")
1285
+ actor_definition_id: str = Field(description="The connector definition UUID")
1286
+ state: str = Field(
1287
+ description="Rollout state: initialized, workflow_started, in_progress, "
1288
+ "paused, finalizing, succeeded, errored, failed_rolled_back, canceled"
1289
+ )
1290
+ initial_rollout_pct: int | None = Field(
1291
+ default=None, description="Initial rollout percentage"
1292
+ )
1293
+ current_target_rollout_pct: int | None = Field(
1294
+ default=None, description="Current target rollout percentage"
1295
+ )
1296
+ final_target_rollout_pct: int | None = Field(
1297
+ default=None, description="Final target rollout percentage"
1298
+ )
1299
+ has_breaking_changes: bool = Field(
1300
+ description="Whether the RC has breaking changes"
1301
+ )
1302
+ max_step_wait_time_mins: int | None = Field(
1303
+ default=None, description="Maximum wait time between rollout steps in minutes"
1304
+ )
1305
+ rollout_strategy: str | None = Field(
1306
+ default=None, description="Rollout strategy: manual, automated, overridden"
1307
+ )
1308
+ workflow_run_id: str | None = Field(
1309
+ default=None, description="Temporal workflow run ID"
1310
+ )
1311
+ error_msg: str | None = Field(default=None, description="Error message if errored")
1312
+ failed_reason: str | None = Field(
1313
+ default=None, description="Reason for failure if failed"
1314
+ )
1315
+ paused_reason: str | None = Field(
1316
+ default=None, description="Reason for pause if paused"
1317
+ )
1318
+ tag: str | None = Field(default=None, description="Optional tag for the rollout")
1319
+ created_at: datetime | None = Field(
1320
+ default=None, description="When the rollout was created"
1321
+ )
1322
+ updated_at: datetime | None = Field(
1323
+ default=None, description="When the rollout was last updated"
1324
+ )
1325
+ completed_at: datetime | None = Field(
1326
+ default=None, description="When the rollout completed (if terminal)"
1327
+ )
1328
+ expires_at: datetime | None = Field(
1329
+ default=None, description="When the rollout expires"
1330
+ )
1331
+ rc_docker_image_tag: str | None = Field(
1332
+ default=None, description="Docker image tag of the release candidate"
1333
+ )
1334
+ rc_docker_repository: str | None = Field(
1335
+ default=None, description="Docker repository of the release candidate"
1336
+ )
1337
+ initial_docker_image_tag: str | None = Field(
1338
+ default=None, description="Docker image tag of the initial version"
1339
+ )
1340
+ initial_docker_repository: str | None = Field(
1341
+ default=None, description="Docker repository of the initial version"
1342
+ )
1343
+
1344
+
1345
+ def _row_to_connector_rollout_info(row: dict[str, Any]) -> ConnectorRolloutInfo:
1346
+ """Convert a database row to a ConnectorRolloutInfo model."""
1347
+ return ConnectorRolloutInfo(
1348
+ rollout_id=str(row["rollout_id"]),
1349
+ actor_definition_id=str(row["actor_definition_id"]),
1350
+ state=row["state"],
1351
+ initial_rollout_pct=row.get("initial_rollout_pct"),
1352
+ current_target_rollout_pct=row.get("current_target_rollout_pct"),
1353
+ final_target_rollout_pct=row.get("final_target_rollout_pct"),
1354
+ has_breaking_changes=row["has_breaking_changes"],
1355
+ max_step_wait_time_mins=row.get("max_step_wait_time_mins"),
1356
+ rollout_strategy=row.get("rollout_strategy"),
1357
+ workflow_run_id=row.get("workflow_run_id"),
1358
+ error_msg=row.get("error_msg"),
1359
+ failed_reason=row.get("failed_reason"),
1360
+ paused_reason=row.get("paused_reason"),
1361
+ tag=row.get("tag"),
1362
+ created_at=row.get("created_at"),
1363
+ updated_at=row.get("updated_at"),
1364
+ completed_at=row.get("completed_at"),
1365
+ expires_at=row.get("expires_at"),
1366
+ rc_docker_image_tag=row.get("rc_docker_image_tag"),
1367
+ rc_docker_repository=row.get("rc_docker_repository"),
1368
+ initial_docker_image_tag=row.get("initial_docker_image_tag"),
1369
+ initial_docker_repository=row.get("initial_docker_repository"),
1370
+ )
1371
+
1372
+
1373
+ @mcp_tool(
1374
+ read_only=True,
1375
+ idempotent=True,
1376
+ )
1377
+ def query_prod_connector_rollouts(
1378
+ actor_definition_id: Annotated[
1379
+ str | None,
1380
+ Field(description="Connector definition UUID to filter by (optional)"),
1381
+ ] = None,
1382
+ rollout_id: Annotated[
1383
+ str | None,
1384
+ Field(description="Specific rollout UUID to look up (optional)"),
1385
+ ] = None,
1386
+ active_only: Annotated[
1387
+ bool,
1388
+ Field(description="If true, only return active (non-terminal) rollouts"),
1389
+ ] = False,
1390
+ limit: Annotated[
1391
+ int,
1392
+ Field(description="Maximum number of results (default: 100)"),
1393
+ ] = 100,
1394
+ ) -> list[ConnectorRolloutInfo]:
1395
+ """Query connector rollouts with flexible filtering.
1396
+
1397
+ Returns rollouts based on the provided filters. If no filters are specified,
1398
+ returns all active rollouts. Useful for monitoring rollout status and history.
1399
+
1400
+ Filter behavior:
1401
+ - rollout_id: Returns that specific rollout (ignores other filters)
1402
+ - active_only: Returns only active (non-terminal) rollouts
1403
+ - actor_definition_id: Returns rollouts for that specific connector
1404
+ - No filters: Returns all active rollouts (same as active_only=True)
1405
+ """
1406
+ rows = query_connector_rollouts(
1407
+ actor_definition_id=actor_definition_id,
1408
+ rollout_id=rollout_id,
1409
+ active_only=active_only,
1410
+ limit=limit,
1411
+ )
1412
+ return [_row_to_connector_rollout_info(row) for row in rows]
1413
+
1414
+
1415
+ class RolloutMonitoringStats(BaseModel):
1416
+ """Aggregate monitoring stats for a connector rollout."""
1417
+
1418
+ rollout_id: str = Field(description="Rollout UUID")
1419
+ actor_definition_id: str = Field(description="Connector definition UUID")
1420
+ num_actors: int = Field(description="Total actors using this connector")
1421
+ num_actors_pinned: int = Field(
1422
+ description="Actors with any version pin (eligible or already pinned)"
1423
+ )
1424
+ num_pinned_to_rollout: int = Field(
1425
+ description="Actors specifically pinned to this rollout"
1426
+ )
1427
+
1428
+
1429
+ class RolloutActorSyncStats(BaseModel):
1430
+ """Per-actor sync stats for a rollout."""
1431
+
1432
+ actor_id: str = Field(description="Actor UUID")
1433
+ num_succeeded: int = Field(description="Number of successful syncs")
1434
+ num_failed: int = Field(description="Number of failed syncs")
1435
+ num_connections: int = Field(description="Number of connections using this actor")
1436
+
1437
+
1438
+ class RolloutMonitoringResult(BaseModel):
1439
+ """Complete monitoring result for a rollout."""
1440
+
1441
+ aggregate_stats: RolloutMonitoringStats = Field(
1442
+ description="Aggregate counts for the rollout"
1443
+ )
1444
+ actor_stats: list[RolloutActorSyncStats] = Field(
1445
+ description="Per-actor sync stats for actors pinned to the rollout"
1446
+ )
1447
+
1448
+
1449
+ @mcp_tool(
1450
+ read_only=True,
1451
+ idempotent=True,
1452
+ )
1453
+ def query_prod_rollout_monitoring_stats(
1454
+ rollout_id: Annotated[
1455
+ str,
1456
+ Field(description="Rollout UUID to get monitoring stats for"),
1457
+ ],
1458
+ days_back: Annotated[
1459
+ int,
1460
+ Field(description="Number of days to look back for sync stats (default: 7)"),
1461
+ ] = 7,
1462
+ ) -> RolloutMonitoringResult:
1463
+ """Get monitoring stats for a connector rollout.
1464
+
1465
+ Returns aggregate counts (total actors, pinned actors, rollout-pinned actors)
1466
+ and per-actor sync stats (succeeded, failed, connections) for actors
1467
+ participating in the rollout. This replaces the Retool rollout monitoring UI.
1468
+ """
1469
+ from airbyte_ops_mcp.prod_db_access.queries import (
1470
+ query_rollout_actor_sync_stats,
1471
+ query_rollout_monitoring_stats,
1472
+ )
1473
+
1474
+ agg_stats = query_rollout_monitoring_stats(rollout_id=rollout_id)
1475
+ if agg_stats is None:
1476
+ raise ValueError(f"Rollout not found: {rollout_id}")
1477
+
1478
+ actor_stats = query_rollout_actor_sync_stats(
1479
+ rollout_id=rollout_id, days_back=days_back
1480
+ )
1481
+
1482
+ return RolloutMonitoringResult(
1483
+ aggregate_stats=RolloutMonitoringStats(
1484
+ rollout_id=str(agg_stats["rollout_id"]),
1485
+ actor_definition_id=str(agg_stats["actor_definition_id"]),
1486
+ num_actors=agg_stats["num_actors"] or 0,
1487
+ num_actors_pinned=agg_stats["num_actors_pinned"] or 0,
1488
+ num_pinned_to_rollout=agg_stats["num_pinned_to_rollout"] or 0,
1489
+ ),
1490
+ actor_stats=[
1491
+ RolloutActorSyncStats(
1492
+ actor_id=str(row["actor_id"]),
1493
+ num_succeeded=row["num_succeeded"] or 0,
1494
+ num_failed=row["num_failed"] or 0,
1495
+ num_connections=row["num_connections"] or 0,
1496
+ )
1497
+ for row in actor_stats
1498
+ ],
1499
+ )
1500
+
1501
+
1275
1502
  def register_prod_db_query_tools(app: FastMCP) -> None:
1276
1503
  """Register prod DB query tools with the FastMCP app."""
1277
1504
  register_mcp_tools(app, mcp_module=__name__)
@@ -33,6 +33,7 @@ from airbyte_ops_mcp.mcp._guidance import MCP_SERVER_INSTRUCTIONS
33
33
  from airbyte_ops_mcp.mcp.cloud_connector_versions import (
34
34
  register_cloud_connector_version_tools,
35
35
  )
36
+ from airbyte_ops_mcp.mcp.connector_rollout import register_connector_rollout_tools
36
37
  from airbyte_ops_mcp.mcp.gcp_logs import register_gcp_logs_tools
37
38
  from airbyte_ops_mcp.mcp.github_actions import register_github_actions_tools
38
39
  from airbyte_ops_mcp.mcp.github_repo_ops import register_github_repo_ops_tools
@@ -117,6 +118,7 @@ def register_server_assets(app: FastMCP) -> None:
117
118
  register_github_actions_tools(app)
118
119
  register_prerelease_tools(app)
119
120
  register_cloud_connector_version_tools(app)
121
+ register_connector_rollout_tools(app)
120
122
  register_prod_db_query_tools(app)
121
123
  register_gcp_logs_tools(app)
122
124
  register_prompts(app)
@@ -19,6 +19,8 @@ from google.cloud import secretmanager
19
19
  from airbyte_ops_mcp.gcp_auth import get_secret_manager_client
20
20
  from airbyte_ops_mcp.prod_db_access.db_engine import get_pool
21
21
  from airbyte_ops_mcp.prod_db_access.sql import (
22
+ SELECT_ACTIVE_CONNECTOR_ROLLOUTS,
23
+ SELECT_ACTIVE_CONNECTOR_ROLLOUTS_BY_DEFINITION,
22
24
  SELECT_ACTORS_PINNED_TO_VERSION,
23
25
  SELECT_CONNECTIONS_BY_CONNECTOR,
24
26
  SELECT_CONNECTIONS_BY_CONNECTOR_AND_ORG,
@@ -26,6 +28,8 @@ from airbyte_ops_mcp.prod_db_access.sql import (
26
28
  SELECT_CONNECTIONS_BY_DESTINATION_CONNECTOR_AND_ORG,
27
29
  SELECT_CONNECTIONS_BY_SOURCE_CONNECTOR_AND_STREAM,
28
30
  SELECT_CONNECTIONS_BY_SOURCE_CONNECTOR_AND_STREAM_AND_ORG,
31
+ SELECT_CONNECTOR_ROLLOUT_BY_ID,
32
+ SELECT_CONNECTOR_ROLLOUTS,
29
33
  SELECT_CONNECTOR_VERSIONS,
30
34
  SELECT_DATAPLANES_LIST,
31
35
  SELECT_DESTINATION_CONNECTION_STATS,
@@ -38,6 +42,8 @@ from airbyte_ops_mcp.prod_db_access.sql import (
38
42
  SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_SOURCE_CONNECTOR,
39
43
  SELECT_RECENT_SYNCS_FOR_DESTINATION_CONNECTOR,
40
44
  SELECT_RECENT_SYNCS_FOR_SOURCE_CONNECTOR,
45
+ SELECT_ROLLOUT_ACTOR_SYNC_STATS,
46
+ SELECT_ROLLOUT_AGGREGATE_STATS,
41
47
  SELECT_SOURCE_CONNECTION_STATS,
42
48
  SELECT_SUCCESSFUL_SYNCS_FOR_VERSION,
43
49
  SELECT_SYNC_RESULTS_FOR_VERSION,
@@ -616,3 +622,141 @@ def query_connections_by_stream(
616
622
  query_name="SELECT_CONNECTIONS_BY_SOURCE_CONNECTOR_AND_STREAM_AND_ORG",
617
623
  gsm_client=gsm_client,
618
624
  )
625
+
626
+
627
+ def query_connector_rollouts(
628
+ actor_definition_id: str | None = None,
629
+ rollout_id: str | None = None,
630
+ active_only: bool = False,
631
+ limit: int = 100,
632
+ *,
633
+ gsm_client: secretmanager.SecretManagerServiceClient | None = None,
634
+ ) -> list[dict[str, Any]]:
635
+ """Query connector rollouts with flexible filtering.
636
+
637
+ This is the unified query function for connector rollouts. Based on the
638
+ arguments provided, it will:
639
+ - If rollout_id is provided: Return that specific rollout (as a single-item list)
640
+ - If active_only is True AND actor_definition_id is provided: Return active rollouts for that connector
641
+ - If active_only is True: Return all active (non-terminal) rollouts
642
+ - If actor_definition_id is provided: Return all rollouts for that connector (including terminal)
643
+ - Otherwise: Return all active rollouts (up to limit)
644
+
645
+ Args:
646
+ actor_definition_id: Optional connector definition UUID to filter by
647
+ rollout_id: Optional specific rollout UUID to look up
648
+ active_only: If True, only return active (non-terminal) rollouts
649
+ limit: Maximum number of results (default: 100)
650
+ gsm_client: GCP Secret Manager client. If None, a new client will be instantiated.
651
+
652
+ Returns:
653
+ List of rollout records with version details
654
+ """
655
+ if rollout_id is not None:
656
+ rows = _run_sql_query(
657
+ SELECT_CONNECTOR_ROLLOUT_BY_ID,
658
+ parameters={"rollout_id": rollout_id},
659
+ query_name="SELECT_CONNECTOR_ROLLOUT_BY_ID",
660
+ gsm_client=gsm_client,
661
+ )
662
+ return rows
663
+
664
+ # Handle active_only with optional actor_definition_id filter
665
+ if active_only:
666
+ if actor_definition_id is not None:
667
+ # Filter by both active states AND actor_definition_id
668
+ return _run_sql_query(
669
+ SELECT_ACTIVE_CONNECTOR_ROLLOUTS_BY_DEFINITION,
670
+ parameters={
671
+ "actor_definition_id": actor_definition_id,
672
+ "limit": limit,
673
+ },
674
+ query_name="SELECT_ACTIVE_CONNECTOR_ROLLOUTS_BY_DEFINITION",
675
+ gsm_client=gsm_client,
676
+ )
677
+ # Only active filter, no actor_definition_id
678
+ return _run_sql_query(
679
+ SELECT_ACTIVE_CONNECTOR_ROLLOUTS,
680
+ parameters={"limit": limit},
681
+ query_name="SELECT_ACTIVE_CONNECTOR_ROLLOUTS",
682
+ gsm_client=gsm_client,
683
+ )
684
+
685
+ # Not active_only, but filter by actor_definition_id (all states)
686
+ if actor_definition_id is not None:
687
+ return _run_sql_query(
688
+ SELECT_CONNECTOR_ROLLOUTS,
689
+ parameters={
690
+ "actor_definition_id": actor_definition_id,
691
+ "limit": limit,
692
+ },
693
+ query_name="SELECT_CONNECTOR_ROLLOUTS",
694
+ gsm_client=gsm_client,
695
+ )
696
+
697
+ # Default: return active rollouts if no filters specified
698
+ return _run_sql_query(
699
+ SELECT_ACTIVE_CONNECTOR_ROLLOUTS,
700
+ parameters={"limit": limit},
701
+ query_name="SELECT_ACTIVE_CONNECTOR_ROLLOUTS",
702
+ gsm_client=gsm_client,
703
+ )
704
+
705
+
706
+ def query_rollout_monitoring_stats(
707
+ rollout_id: str,
708
+ *,
709
+ gsm_client: secretmanager.SecretManagerServiceClient | None = None,
710
+ ) -> dict[str, Any] | None:
711
+ """Query aggregate monitoring stats for a rollout.
712
+
713
+ Returns counts of total actors, actors with any pin, and actors pinned
714
+ specifically to this rollout.
715
+
716
+ Args:
717
+ rollout_id: Rollout UUID to get stats for
718
+ gsm_client: GCP Secret Manager client. If None, a new client will be instantiated.
719
+
720
+ Returns:
721
+ Dict with rollout_id, actor_definition_id, num_actors, num_actors_pinned,
722
+ num_pinned_to_rollout, or None if rollout not found
723
+ """
724
+ rows = _run_sql_query(
725
+ SELECT_ROLLOUT_AGGREGATE_STATS,
726
+ parameters={"rollout_id": rollout_id},
727
+ query_name="SELECT_ROLLOUT_AGGREGATE_STATS",
728
+ gsm_client=gsm_client,
729
+ )
730
+ # The SQL query uses scalar subqueries that always return one row,
731
+ # but with NULL values if the rollout doesn't exist
732
+ if rows and rows[0].get("rollout_id") is not None:
733
+ return rows[0]
734
+ return None
735
+
736
+
737
+ def query_rollout_actor_sync_stats(
738
+ rollout_id: str,
739
+ days_back: int = 7,
740
+ *,
741
+ gsm_client: secretmanager.SecretManagerServiceClient | None = None,
742
+ ) -> list[dict[str, Any]]:
743
+ """Query per-actor sync stats for actors pinned to a rollout.
744
+
745
+ Returns succeeded/failed job counts and connection counts for each actor
746
+ that is pinned to the specified rollout.
747
+
748
+ Args:
749
+ rollout_id: Rollout UUID to get actor stats for
750
+ days_back: Number of days to look back for job stats (default: 7)
751
+ gsm_client: GCP Secret Manager client. If None, a new client will be instantiated.
752
+
753
+ Returns:
754
+ List of dicts with actor_id, num_succeeded, num_failed, num_connections
755
+ """
756
+ cutoff_date = datetime.now(timezone.utc) - timedelta(days=days_back)
757
+ return _run_sql_query(
758
+ SELECT_ROLLOUT_ACTOR_SYNC_STATS,
759
+ parameters={"rollout_id": rollout_id, "cutoff_date": cutoff_date},
760
+ query_name="SELECT_ROLLOUT_ACTOR_SYNC_STATS",
761
+ gsm_client=gsm_client,
762
+ )