airbyte-internal-ops 0.1.3__py3-none-any.whl → 0.1.4__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.
- {airbyte_internal_ops-0.1.3.dist-info → airbyte_internal_ops-0.1.4.dist-info}/METADATA +8 -5
- {airbyte_internal_ops-0.1.3.dist-info → airbyte_internal_ops-0.1.4.dist-info}/RECORD +31 -11
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_pipelines/airbyte_ci/connectors/test/steps/common.py +1 -1
- airbyte_ops_mcp/cli/cloud.py +309 -38
- airbyte_ops_mcp/cloud_admin/connection_config.py +131 -0
- airbyte_ops_mcp/live_tests/__init__.py +16 -0
- airbyte_ops_mcp/live_tests/_connection_retriever/__init__.py +35 -0
- airbyte_ops_mcp/live_tests/_connection_retriever/audit_logging.py +88 -0
- airbyte_ops_mcp/live_tests/_connection_retriever/consts.py +33 -0
- airbyte_ops_mcp/live_tests/_connection_retriever/db_access.py +82 -0
- airbyte_ops_mcp/live_tests/_connection_retriever/retrieval.py +391 -0
- airbyte_ops_mcp/live_tests/_connection_retriever/secrets_resolution.py +130 -0
- airbyte_ops_mcp/live_tests/config.py +190 -0
- airbyte_ops_mcp/live_tests/connection_fetcher.py +159 -2
- airbyte_ops_mcp/live_tests/connection_secret_retriever.py +173 -0
- airbyte_ops_mcp/live_tests/evaluation_modes.py +45 -0
- airbyte_ops_mcp/live_tests/http_metrics.py +81 -0
- airbyte_ops_mcp/live_tests/message_cache/__init__.py +15 -0
- airbyte_ops_mcp/live_tests/message_cache/duckdb_cache.py +415 -0
- airbyte_ops_mcp/live_tests/obfuscation.py +126 -0
- airbyte_ops_mcp/live_tests/regression/__init__.py +29 -0
- airbyte_ops_mcp/live_tests/regression/comparators.py +466 -0
- airbyte_ops_mcp/live_tests/schema_generation.py +154 -0
- airbyte_ops_mcp/live_tests/validation/__init__.py +43 -0
- airbyte_ops_mcp/live_tests/validation/catalog_validators.py +389 -0
- airbyte_ops_mcp/live_tests/validation/record_validators.py +227 -0
- airbyte_ops_mcp/mcp/_mcp_utils.py +3 -0
- airbyte_ops_mcp/mcp/live_tests.py +500 -0
- airbyte_ops_mcp/mcp/server.py +3 -0
- {airbyte_internal_ops-0.1.3.dist-info → airbyte_internal_ops-0.1.4.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.1.3.dist-info → airbyte_internal_ops-0.1.4.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
|
2
|
+
"""MCP tools for live connection tests.
|
|
3
|
+
|
|
4
|
+
This module provides MCP tools for triggering live validation and regression tests
|
|
5
|
+
on Airbyte Cloud connections via GitHub Actions workflows. Tests run asynchronously
|
|
6
|
+
in GitHub Actions and results can be polled via workflow status.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import uuid
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import Annotated, Any
|
|
17
|
+
|
|
18
|
+
import requests
|
|
19
|
+
from airbyte.cloud import CloudWorkspace
|
|
20
|
+
from fastmcp import FastMCP
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
|
|
23
|
+
from airbyte_ops_mcp.mcp._mcp_utils import ToolDomain, mcp_tool, register_mcp_tools
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# =============================================================================
|
|
28
|
+
# GitHub Workflow Configuration
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
GITHUB_API_BASE = "https://api.github.com"
|
|
32
|
+
LIVE_TEST_REPO_OWNER = "airbytehq"
|
|
33
|
+
LIVE_TEST_REPO_NAME = "airbyte-ops-mcp"
|
|
34
|
+
LIVE_TEST_DEFAULT_BRANCH = "main"
|
|
35
|
+
LIVE_TEST_WORKFLOW_FILE = "connector-live-test.yml"
|
|
36
|
+
REGRESSION_TEST_WORKFLOW_FILE = "connector-regression-test.yml"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# GitHub API Helper Functions
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_github_token() -> str:
|
|
45
|
+
"""Get GitHub token from environment.
|
|
46
|
+
|
|
47
|
+
Checks for tokens in order of specificity:
|
|
48
|
+
1. GITHUB_CI_WORKFLOW_TRIGGER_PAT (general workflow triggering)
|
|
49
|
+
2. GITHUB_TOKEN (fallback)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
GitHub token string.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: If no GitHub token environment variable is set.
|
|
56
|
+
"""
|
|
57
|
+
token = os.getenv("GITHUB_CI_WORKFLOW_TRIGGER_PAT") or os.getenv("GITHUB_TOKEN")
|
|
58
|
+
if not token:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"No GitHub token found. Set GITHUB_CI_WORKFLOW_TRIGGER_PAT or GITHUB_TOKEN "
|
|
61
|
+
"environment variable with 'actions:write' permission."
|
|
62
|
+
)
|
|
63
|
+
return token
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _trigger_workflow_dispatch(
|
|
67
|
+
owner: str,
|
|
68
|
+
repo: str,
|
|
69
|
+
workflow_file: str,
|
|
70
|
+
ref: str,
|
|
71
|
+
inputs: dict[str, Any],
|
|
72
|
+
token: str,
|
|
73
|
+
) -> str:
|
|
74
|
+
"""Trigger a GitHub Actions workflow via workflow_dispatch.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
owner: Repository owner (e.g., "airbytehq")
|
|
78
|
+
repo: Repository name (e.g., "airbyte-ops-mcp")
|
|
79
|
+
workflow_file: Workflow file name (e.g., "connector-live-test.yml")
|
|
80
|
+
ref: Git ref to run the workflow on (branch name)
|
|
81
|
+
inputs: Workflow inputs dictionary
|
|
82
|
+
token: GitHub API token
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
URL to view workflow runs.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
requests.HTTPError: If API request fails.
|
|
89
|
+
"""
|
|
90
|
+
url = f"{GITHUB_API_BASE}/repos/{owner}/{repo}/actions/workflows/{workflow_file}/dispatches"
|
|
91
|
+
headers = {
|
|
92
|
+
"Authorization": f"Bearer {token}",
|
|
93
|
+
"Accept": "application/vnd.github+json",
|
|
94
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
95
|
+
}
|
|
96
|
+
payload = {
|
|
97
|
+
"ref": ref,
|
|
98
|
+
"inputs": inputs,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
response = requests.post(url, headers=headers, json=payload, timeout=30)
|
|
102
|
+
response.raise_for_status()
|
|
103
|
+
|
|
104
|
+
# workflow_dispatch returns 204 No Content on success
|
|
105
|
+
# Return URL to view workflow runs
|
|
106
|
+
return f"https://github.com/{owner}/{repo}/actions/workflows/{workflow_file}"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _get_workflow_run_status(
|
|
110
|
+
owner: str,
|
|
111
|
+
repo: str,
|
|
112
|
+
run_id: int,
|
|
113
|
+
token: str,
|
|
114
|
+
) -> dict[str, Any]:
|
|
115
|
+
"""Get workflow run details from GitHub API.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
owner: Repository owner (e.g., "airbytehq")
|
|
119
|
+
repo: Repository name (e.g., "airbyte-ops-mcp")
|
|
120
|
+
run_id: Workflow run ID
|
|
121
|
+
token: GitHub API token
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Workflow run data dictionary.
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
ValueError: If workflow run not found.
|
|
128
|
+
requests.HTTPError: If API request fails.
|
|
129
|
+
"""
|
|
130
|
+
url = f"{GITHUB_API_BASE}/repos/{owner}/{repo}/actions/runs/{run_id}"
|
|
131
|
+
headers = {
|
|
132
|
+
"Authorization": f"Bearer {token}",
|
|
133
|
+
"Accept": "application/vnd.github+json",
|
|
134
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
response = requests.get(url, headers=headers, timeout=30)
|
|
138
|
+
if response.status_code == 404:
|
|
139
|
+
raise ValueError(f"Workflow run {owner}/{repo}/actions/runs/{run_id} not found")
|
|
140
|
+
response.raise_for_status()
|
|
141
|
+
|
|
142
|
+
return response.json()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# =============================================================================
|
|
146
|
+
# Pydantic Models for Test Results
|
|
147
|
+
# =============================================================================
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestRunStatus(str, Enum):
|
|
151
|
+
"""Status of a test run."""
|
|
152
|
+
|
|
153
|
+
QUEUED = "queued"
|
|
154
|
+
RUNNING = "running"
|
|
155
|
+
SUCCEEDED = "succeeded"
|
|
156
|
+
FAILED = "failed"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class TestPhaseStatus(str, Enum):
|
|
160
|
+
"""Status of a test phase (live or regression)."""
|
|
161
|
+
|
|
162
|
+
PENDING = "pending"
|
|
163
|
+
RUNNING = "running"
|
|
164
|
+
PASSED = "passed"
|
|
165
|
+
FAILED = "failed"
|
|
166
|
+
SKIPPED = "skipped"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ValidationResultModel(BaseModel):
|
|
170
|
+
"""Result of a single validation check."""
|
|
171
|
+
|
|
172
|
+
name: str = Field(description="Name of the validation check")
|
|
173
|
+
passed: bool = Field(description="Whether the validation passed")
|
|
174
|
+
message: str = Field(description="Human-readable result message")
|
|
175
|
+
errors: list[str] = Field(
|
|
176
|
+
default_factory=list,
|
|
177
|
+
description="List of error messages if validation failed",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class StreamComparisonResultModel(BaseModel):
|
|
182
|
+
"""Result of comparing a single stream between control and target."""
|
|
183
|
+
|
|
184
|
+
stream_name: str = Field(description="Name of the stream")
|
|
185
|
+
passed: bool = Field(description="Whether all comparisons passed")
|
|
186
|
+
control_record_count: int = Field(description="Number of records in control")
|
|
187
|
+
target_record_count: int = Field(description="Number of records in target")
|
|
188
|
+
missing_pks: list[str] = Field(
|
|
189
|
+
default_factory=list,
|
|
190
|
+
description="Primary keys present in control but missing in target",
|
|
191
|
+
)
|
|
192
|
+
differing_records: int = Field(
|
|
193
|
+
default=0,
|
|
194
|
+
description="Number of records that differ between control and target",
|
|
195
|
+
)
|
|
196
|
+
message: str = Field(description="Human-readable comparison summary")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class LivePhaseResult(BaseModel):
|
|
200
|
+
"""Results from the live test phase."""
|
|
201
|
+
|
|
202
|
+
status: TestPhaseStatus = Field(description="Status of the live phase")
|
|
203
|
+
catalog_validations: list[ValidationResultModel] = Field(
|
|
204
|
+
default_factory=list,
|
|
205
|
+
description="Results of catalog validation checks",
|
|
206
|
+
)
|
|
207
|
+
record_validations: list[ValidationResultModel] = Field(
|
|
208
|
+
default_factory=list,
|
|
209
|
+
description="Results of record validation checks",
|
|
210
|
+
)
|
|
211
|
+
record_count: int = Field(
|
|
212
|
+
default=0,
|
|
213
|
+
description="Total number of records read",
|
|
214
|
+
)
|
|
215
|
+
error_message: str | None = Field(
|
|
216
|
+
default=None,
|
|
217
|
+
description="Error message if the phase failed",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class RegressionPhaseResult(BaseModel):
|
|
222
|
+
"""Results from the regression test phase."""
|
|
223
|
+
|
|
224
|
+
status: TestPhaseStatus = Field(description="Status of the regression phase")
|
|
225
|
+
baseline_version: str | None = Field(
|
|
226
|
+
default=None,
|
|
227
|
+
description="Version of the baseline (control) connector",
|
|
228
|
+
)
|
|
229
|
+
stream_comparisons: list[StreamComparisonResultModel] = Field(
|
|
230
|
+
default_factory=list,
|
|
231
|
+
description="Per-stream comparison results",
|
|
232
|
+
)
|
|
233
|
+
error_message: str | None = Field(
|
|
234
|
+
default=None,
|
|
235
|
+
description="Error message if the phase failed",
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class LiveConnectionTestResult(BaseModel):
|
|
240
|
+
"""Complete result of a live connection test run."""
|
|
241
|
+
|
|
242
|
+
run_id: str = Field(description="Unique identifier for this test run")
|
|
243
|
+
connection_id: str = Field(description="The connection being tested")
|
|
244
|
+
workspace_id: str = Field(description="The workspace containing the connection")
|
|
245
|
+
status: TestRunStatus = Field(description="Overall status of the test run")
|
|
246
|
+
target_version: str | None = Field(
|
|
247
|
+
default=None,
|
|
248
|
+
description="Version of the target connector being tested",
|
|
249
|
+
)
|
|
250
|
+
baseline_version: str | None = Field(
|
|
251
|
+
default=None,
|
|
252
|
+
description="Version of the baseline connector (if regression ran)",
|
|
253
|
+
)
|
|
254
|
+
evaluation_mode: str = Field(
|
|
255
|
+
default="diagnostic",
|
|
256
|
+
description="Evaluation mode used (diagnostic or strict)",
|
|
257
|
+
)
|
|
258
|
+
skip_regression_tests: bool = Field(
|
|
259
|
+
default=False,
|
|
260
|
+
description="Whether regression tests were skipped by request",
|
|
261
|
+
)
|
|
262
|
+
live_phase: LivePhaseResult | None = Field(
|
|
263
|
+
default=None,
|
|
264
|
+
description="Results from the live test phase",
|
|
265
|
+
)
|
|
266
|
+
regression_phase: RegressionPhaseResult | None = Field(
|
|
267
|
+
default=None,
|
|
268
|
+
description="Results from the regression test phase",
|
|
269
|
+
)
|
|
270
|
+
artifacts: dict[str, str] = Field(
|
|
271
|
+
default_factory=dict,
|
|
272
|
+
description="Paths to generated artifacts (JSONL, DuckDB, HAR files)",
|
|
273
|
+
)
|
|
274
|
+
human_summary: str = Field(
|
|
275
|
+
default="",
|
|
276
|
+
description="Human-readable summary of the test results",
|
|
277
|
+
)
|
|
278
|
+
started_at: datetime | None = Field(
|
|
279
|
+
default=None,
|
|
280
|
+
description="When the test run started",
|
|
281
|
+
)
|
|
282
|
+
completed_at: datetime | None = Field(
|
|
283
|
+
default=None,
|
|
284
|
+
description="When the test run completed",
|
|
285
|
+
)
|
|
286
|
+
test_description: str | None = Field(
|
|
287
|
+
default=None,
|
|
288
|
+
description="Optional description/context for this test run",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class RunLiveConnectionTestsResponse(BaseModel):
|
|
293
|
+
"""Response from starting a live connection test via GitHub Actions workflow."""
|
|
294
|
+
|
|
295
|
+
run_id: str = Field(description="Unique identifier for the test run")
|
|
296
|
+
status: TestRunStatus = Field(description="Initial status of the test run")
|
|
297
|
+
message: str = Field(description="Human-readable status message")
|
|
298
|
+
workflow_url: str | None = Field(
|
|
299
|
+
default=None,
|
|
300
|
+
description="URL to view the GitHub Actions workflow runs",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# =============================================================================
|
|
305
|
+
# MCP Tools
|
|
306
|
+
# =============================================================================
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@mcp_tool(
|
|
310
|
+
ToolDomain.LIVE_TESTS,
|
|
311
|
+
read_only=False,
|
|
312
|
+
idempotent=False,
|
|
313
|
+
open_world=True,
|
|
314
|
+
)
|
|
315
|
+
def run_live_connection_tests(
|
|
316
|
+
connection_id: Annotated[str, "The Airbyte Cloud connection ID to test"],
|
|
317
|
+
command: Annotated[
|
|
318
|
+
str,
|
|
319
|
+
"Airbyte command to run: 'spec', 'check', 'discover', or 'read'",
|
|
320
|
+
] = "check",
|
|
321
|
+
workspace_id: Annotated[
|
|
322
|
+
str | None,
|
|
323
|
+
"Optional Airbyte Cloud workspace ID. If provided, validates that the connection "
|
|
324
|
+
"belongs to this workspace before triggering tests. If omitted, no validation is done.",
|
|
325
|
+
] = None,
|
|
326
|
+
skip_regression_tests: Annotated[
|
|
327
|
+
bool,
|
|
328
|
+
"If True, run only live tests (connector-live-test workflow). "
|
|
329
|
+
"If False, run regression tests comparing target vs control versions "
|
|
330
|
+
"(connector-regression-test workflow).",
|
|
331
|
+
] = True,
|
|
332
|
+
connector_image: Annotated[
|
|
333
|
+
str | None,
|
|
334
|
+
"Optional connector image with tag for live tests (e.g., 'airbyte/source-github:1.0.0'). "
|
|
335
|
+
"If not provided, auto-detected from connection. Only used when skip_regression_tests=True.",
|
|
336
|
+
] = None,
|
|
337
|
+
target_image: Annotated[
|
|
338
|
+
str | None,
|
|
339
|
+
"Target connector image (new version) with tag for regression tests "
|
|
340
|
+
"(e.g., 'airbyte/source-github:2.0.0'). Optional if connector_name is provided. "
|
|
341
|
+
"Only used when skip_regression_tests=False.",
|
|
342
|
+
] = None,
|
|
343
|
+
control_image: Annotated[
|
|
344
|
+
str | None,
|
|
345
|
+
"Control connector image (baseline version) with tag for regression tests "
|
|
346
|
+
"(e.g., 'airbyte/source-github:1.0.0'). Optional if connection_id is provided "
|
|
347
|
+
"(auto-detected from connection). Only used when skip_regression_tests=False.",
|
|
348
|
+
] = None,
|
|
349
|
+
connector_name: Annotated[
|
|
350
|
+
str | None,
|
|
351
|
+
"Connector name to build target image from source for regression tests "
|
|
352
|
+
"(e.g., 'source-pokeapi'). If provided, builds the target image locally. "
|
|
353
|
+
"Only used when skip_regression_tests=False.",
|
|
354
|
+
] = None,
|
|
355
|
+
) -> RunLiveConnectionTestsResponse:
|
|
356
|
+
"""Start a live connection test run via GitHub Actions workflow.
|
|
357
|
+
|
|
358
|
+
This tool triggers either the live-test or regression-test workflow depending
|
|
359
|
+
on the skip_regression_tests parameter:
|
|
360
|
+
|
|
361
|
+
- skip_regression_tests=True (default): Triggers connector-live-test workflow.
|
|
362
|
+
Runs the specified command against the connection and validates the output.
|
|
363
|
+
|
|
364
|
+
- skip_regression_tests=False: Triggers connector-regression-test workflow.
|
|
365
|
+
Compares the target connector version against a control (baseline) version.
|
|
366
|
+
For regression tests, provide either target_image or connector_name to specify
|
|
367
|
+
the target version.
|
|
368
|
+
|
|
369
|
+
Returns immediately with a run_id and workflow URL. Check the workflow URL
|
|
370
|
+
to monitor progress and view results.
|
|
371
|
+
|
|
372
|
+
Requires GITHUB_CI_WORKFLOW_TRIGGER_PAT or GITHUB_TOKEN environment variable
|
|
373
|
+
with 'actions:write' permission.
|
|
374
|
+
"""
|
|
375
|
+
# Generate a unique run ID for tracking
|
|
376
|
+
run_id = str(uuid.uuid4())
|
|
377
|
+
|
|
378
|
+
# Get GitHub token
|
|
379
|
+
try:
|
|
380
|
+
token = _get_github_token()
|
|
381
|
+
except ValueError as e:
|
|
382
|
+
return RunLiveConnectionTestsResponse(
|
|
383
|
+
run_id=run_id,
|
|
384
|
+
status=TestRunStatus.FAILED,
|
|
385
|
+
message=str(e),
|
|
386
|
+
workflow_url=None,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Validate workspace membership if workspace_id is provided
|
|
390
|
+
if workspace_id:
|
|
391
|
+
try:
|
|
392
|
+
workspace = CloudWorkspace(workspace_id=workspace_id)
|
|
393
|
+
# This will raise an exception if the connection doesn't belong to the workspace
|
|
394
|
+
workspace.get_connection(connection_id)
|
|
395
|
+
except Exception as e:
|
|
396
|
+
return RunLiveConnectionTestsResponse(
|
|
397
|
+
run_id=run_id,
|
|
398
|
+
status=TestRunStatus.FAILED,
|
|
399
|
+
message=f"Connection {connection_id} validation failed for workspace {workspace_id}: {e}",
|
|
400
|
+
workflow_url=None,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
if skip_regression_tests:
|
|
404
|
+
# Live test workflow
|
|
405
|
+
workflow_inputs: dict[str, str] = {
|
|
406
|
+
"connection_id": connection_id,
|
|
407
|
+
"command": command,
|
|
408
|
+
}
|
|
409
|
+
if connector_image:
|
|
410
|
+
workflow_inputs["connector_image"] = connector_image
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
workflow_url = _trigger_workflow_dispatch(
|
|
414
|
+
owner=LIVE_TEST_REPO_OWNER,
|
|
415
|
+
repo=LIVE_TEST_REPO_NAME,
|
|
416
|
+
workflow_file=LIVE_TEST_WORKFLOW_FILE,
|
|
417
|
+
ref=LIVE_TEST_DEFAULT_BRANCH,
|
|
418
|
+
inputs=workflow_inputs,
|
|
419
|
+
token=token,
|
|
420
|
+
)
|
|
421
|
+
except Exception as e:
|
|
422
|
+
logger.exception("Failed to trigger live test workflow")
|
|
423
|
+
return RunLiveConnectionTestsResponse(
|
|
424
|
+
run_id=run_id,
|
|
425
|
+
status=TestRunStatus.FAILED,
|
|
426
|
+
message=f"Failed to trigger live-test workflow: {e}",
|
|
427
|
+
workflow_url=None,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
return RunLiveConnectionTestsResponse(
|
|
431
|
+
run_id=run_id,
|
|
432
|
+
status=TestRunStatus.QUEUED,
|
|
433
|
+
message=f"Live-test workflow triggered for connection {connection_id}. "
|
|
434
|
+
f"View progress at: {workflow_url}",
|
|
435
|
+
workflow_url=workflow_url,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# Regression test workflow (skip_regression_tests=False)
|
|
439
|
+
# Validate that we have enough info to run regression tests
|
|
440
|
+
if not target_image and not connector_name:
|
|
441
|
+
return RunLiveConnectionTestsResponse(
|
|
442
|
+
run_id=run_id,
|
|
443
|
+
status=TestRunStatus.FAILED,
|
|
444
|
+
message=(
|
|
445
|
+
"For regression tests (skip_regression_tests=False), provide either "
|
|
446
|
+
"target_image or connector_name so the workflow can determine the target image."
|
|
447
|
+
),
|
|
448
|
+
workflow_url=None,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
workflow_inputs = {
|
|
452
|
+
"connection_id": connection_id,
|
|
453
|
+
"command": command,
|
|
454
|
+
}
|
|
455
|
+
if target_image:
|
|
456
|
+
workflow_inputs["target_image"] = target_image
|
|
457
|
+
if control_image:
|
|
458
|
+
workflow_inputs["control_image"] = control_image
|
|
459
|
+
if connector_name:
|
|
460
|
+
workflow_inputs["connector_name"] = connector_name
|
|
461
|
+
|
|
462
|
+
try:
|
|
463
|
+
workflow_url = _trigger_workflow_dispatch(
|
|
464
|
+
owner=LIVE_TEST_REPO_OWNER,
|
|
465
|
+
repo=LIVE_TEST_REPO_NAME,
|
|
466
|
+
workflow_file=REGRESSION_TEST_WORKFLOW_FILE,
|
|
467
|
+
ref=LIVE_TEST_DEFAULT_BRANCH,
|
|
468
|
+
inputs=workflow_inputs,
|
|
469
|
+
token=token,
|
|
470
|
+
)
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.exception("Failed to trigger regression test workflow")
|
|
473
|
+
return RunLiveConnectionTestsResponse(
|
|
474
|
+
run_id=run_id,
|
|
475
|
+
status=TestRunStatus.FAILED,
|
|
476
|
+
message=f"Failed to trigger regression-test workflow: {e}",
|
|
477
|
+
workflow_url=None,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
return RunLiveConnectionTestsResponse(
|
|
481
|
+
run_id=run_id,
|
|
482
|
+
status=TestRunStatus.QUEUED,
|
|
483
|
+
message=f"Regression-test workflow triggered for connection {connection_id}. "
|
|
484
|
+
f"View progress at: {workflow_url}",
|
|
485
|
+
workflow_url=workflow_url,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# =============================================================================
|
|
490
|
+
# Registration
|
|
491
|
+
# =============================================================================
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def register_live_tests_tools(app: FastMCP) -> None:
|
|
495
|
+
"""Register live tests tools with the FastMCP app.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
app: FastMCP application instance
|
|
499
|
+
"""
|
|
500
|
+
register_mcp_tools(app, domain=ToolDomain.LIVE_TESTS)
|
airbyte_ops_mcp/mcp/server.py
CHANGED
|
@@ -17,6 +17,7 @@ from airbyte_ops_mcp.mcp.cloud_connector_versions import (
|
|
|
17
17
|
)
|
|
18
18
|
from airbyte_ops_mcp.mcp.github import register_github_tools
|
|
19
19
|
from airbyte_ops_mcp.mcp.github_repo_ops import register_github_repo_ops_tools
|
|
20
|
+
from airbyte_ops_mcp.mcp.live_tests import register_live_tests_tools
|
|
20
21
|
from airbyte_ops_mcp.mcp.prerelease import register_prerelease_tools
|
|
21
22
|
from airbyte_ops_mcp.mcp.prompts import register_prompts
|
|
22
23
|
from airbyte_ops_mcp.mcp.server_info import register_server_info_resources
|
|
@@ -32,6 +33,7 @@ def register_server_assets(app: FastMCP) -> None:
|
|
|
32
33
|
- REPO: GitHub repository operations
|
|
33
34
|
- CLOUD: Cloud connector version management
|
|
34
35
|
- PROMPTS: Prompt templates for common workflows
|
|
36
|
+
- LIVE_TESTS: Live connection validation and regression tests
|
|
35
37
|
- REGISTRY: Connector registry operations (future)
|
|
36
38
|
- METADATA: Connector metadata operations (future)
|
|
37
39
|
- QA: Connector quality assurance (future)
|
|
@@ -46,6 +48,7 @@ def register_server_assets(app: FastMCP) -> None:
|
|
|
46
48
|
register_prerelease_tools(app)
|
|
47
49
|
register_cloud_connector_version_tools(app)
|
|
48
50
|
register_prompts(app)
|
|
51
|
+
register_live_tests_tools(app)
|
|
49
52
|
|
|
50
53
|
|
|
51
54
|
register_server_assets(app)
|
|
File without changes
|
{airbyte_internal_ops-0.1.3.dist-info → airbyte_internal_ops-0.1.4.dist-info}/entry_points.txt
RENAMED
|
File without changes
|