airbyte-internal-ops 0.2.3__py3-none-any.whl → 0.2.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.2.3.dist-info → airbyte_internal_ops-0.2.4.dist-info}/METADATA +1 -1
- {airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.2.4.dist-info}/RECORD +6 -5
- airbyte_ops_mcp/github_api.py +264 -0
- airbyte_ops_mcp/mcp/cloud_connector_versions.py +48 -32
- {airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.2.4.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.2.4.dist-info}/entry_points.txt +0 -0
|
@@ -3,6 +3,7 @@ airbyte_ops_mcp/_annotations.py,sha256=MO-SBDnbykxxHDESG7d8rviZZ4WlZgJKv0a8eBqcE
|
|
|
3
3
|
airbyte_ops_mcp/constants.py,sha256=GeZ2_WWluMSrGkyqGvqUVFCy-5PD-lyzZbQ7eO-vyUo,5192
|
|
4
4
|
airbyte_ops_mcp/gcp_auth.py,sha256=5k-k145ZoYhHLjyDES8nrA8f8BBihRI0ykrdD1IcfOs,3599
|
|
5
5
|
airbyte_ops_mcp/github_actions.py,sha256=wKnuIVmF4u1gMYNdSoryD_PUmvMz5SaHgOvbU0dsolA,9957
|
|
6
|
+
airbyte_ops_mcp/github_api.py,sha256=uupbYKAkm7yLHK_1cDXYKl1bOYhUygZhG5IHspS7duE,8104
|
|
6
7
|
airbyte_ops_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
8
|
airbyte_ops_mcp/_legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
9
|
airbyte_ops_mcp/_legacy/airbyte_ci/README.md,sha256=qEYx4geDR8AEDjrcA303h7Nol-CMDLojxUyiGzQprM8,236
|
|
@@ -369,7 +370,7 @@ airbyte_ops_mcp/mcp/__init__.py,sha256=QqkNkxzdXlg-W03urBAQ3zmtOKFPf35rXgO9ceUjp
|
|
|
369
370
|
airbyte_ops_mcp/mcp/_guidance.py,sha256=48tQSnDnxqXtyGJxxgjz0ZiI814o_7Fj7f6R8jpQ7so,2375
|
|
370
371
|
airbyte_ops_mcp/mcp/_http_headers.py,sha256=9TAH2RYhFR3z2JugW4Q3WrrqJIdaCzAbyA1GhtQ_EMM,7278
|
|
371
372
|
airbyte_ops_mcp/mcp/_mcp_utils.py,sha256=WNwcGzF7XGKZNAYRt0Uhj5BkRfmwqnFABCrk77OZjRw,11512
|
|
372
|
-
airbyte_ops_mcp/mcp/cloud_connector_versions.py,sha256=
|
|
373
|
+
airbyte_ops_mcp/mcp/cloud_connector_versions.py,sha256=sSMTMk1_2zqD-fr5EENZ1FgbBT6mpNHBrRJuk0jm_iI,15391
|
|
373
374
|
airbyte_ops_mcp/mcp/connector_analysis.py,sha256=OC4KrOSkMkKPkOisWnSv96BDDE5TQYHq-Jxa2vtjJpo,298
|
|
374
375
|
airbyte_ops_mcp/mcp/connector_qa.py,sha256=aImpqdnqBPDrz10BS0owsV4kuIU2XdalzgbaGZsbOL0,258
|
|
375
376
|
airbyte_ops_mcp/mcp/github.py,sha256=h3M3VJrq09y_F9ueQVCq3bUbVBNFuTNKprHtGU_ttio,8045
|
|
@@ -410,7 +411,7 @@ airbyte_ops_mcp/regression_tests/regression/comparators.py,sha256=MJkLZEKHivgrG0
|
|
|
410
411
|
airbyte_ops_mcp/regression_tests/validation/__init__.py,sha256=MBEwGOoNuqT4_oCahtoK62OKWIjUCfWa7vZTxNj_0Ek,1532
|
|
411
412
|
airbyte_ops_mcp/regression_tests/validation/catalog_validators.py,sha256=jqqVAMOk0mtdPgwu4d0hA0ZEjtsNh5gapvGydRv3_qk,12553
|
|
412
413
|
airbyte_ops_mcp/regression_tests/validation/record_validators.py,sha256=RjauAhKWNwxMBTu0eNS2hMFNQVs5CLbQU51kp6FOVDk,7432
|
|
413
|
-
airbyte_internal_ops-0.2.
|
|
414
|
-
airbyte_internal_ops-0.2.
|
|
415
|
-
airbyte_internal_ops-0.2.
|
|
416
|
-
airbyte_internal_ops-0.2.
|
|
414
|
+
airbyte_internal_ops-0.2.4.dist-info/METADATA,sha256=MN7C0ze-rXRcES0sQ2IS_HjBQHKTaZ_8tP3N1WrVT88,5679
|
|
415
|
+
airbyte_internal_ops-0.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
416
|
+
airbyte_internal_ops-0.2.4.dist-info/entry_points.txt,sha256=WxP0l7bRFss4Cr5uQqVj9mTEKwnRKouNuphXQF0lotA,171
|
|
417
|
+
airbyte_internal_ops-0.2.4.dist-info/RECORD,,
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
|
2
|
+
"""GitHub API utilities for user and comment operations.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for interacting with GitHub's REST API
|
|
5
|
+
to retrieve user information and comment details. These utilities are
|
|
6
|
+
used by MCP tools for authorization and audit purposes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from urllib.parse import urlparse
|
|
14
|
+
|
|
15
|
+
import requests
|
|
16
|
+
|
|
17
|
+
from airbyte_ops_mcp.github_actions import GITHUB_API_BASE, resolve_github_token
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GitHubCommentParseError(Exception):
|
|
21
|
+
"""Raised when a GitHub comment URL cannot be parsed."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GitHubUserEmailNotFoundError(Exception):
|
|
25
|
+
"""Raised when a GitHub user's public email cannot be found."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GitHubAPIError(Exception):
|
|
29
|
+
"""Raised when a GitHub API call fails."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class GitHubCommentInfo:
|
|
34
|
+
"""Information about a GitHub comment and its author."""
|
|
35
|
+
|
|
36
|
+
comment_id: int
|
|
37
|
+
"""The numeric comment ID."""
|
|
38
|
+
|
|
39
|
+
owner: str
|
|
40
|
+
"""Repository owner (e.g., 'airbytehq')."""
|
|
41
|
+
|
|
42
|
+
repo: str
|
|
43
|
+
"""Repository name (e.g., 'oncall')."""
|
|
44
|
+
|
|
45
|
+
author_login: str
|
|
46
|
+
"""GitHub username of the comment author."""
|
|
47
|
+
|
|
48
|
+
author_association: str
|
|
49
|
+
"""Author's association with the repo (e.g., 'MEMBER', 'OWNER', 'CONTRIBUTOR')."""
|
|
50
|
+
|
|
51
|
+
comment_type: str
|
|
52
|
+
"""Type of comment: 'issue_comment' or 'review_comment'."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class GitHubUserInfo:
|
|
57
|
+
"""Information about a GitHub user."""
|
|
58
|
+
|
|
59
|
+
login: str
|
|
60
|
+
"""GitHub username."""
|
|
61
|
+
|
|
62
|
+
email: str | None
|
|
63
|
+
"""Public email address, if set."""
|
|
64
|
+
|
|
65
|
+
name: str | None
|
|
66
|
+
"""Display name, if set."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _parse_github_comment_url(url: str) -> tuple[str, str, int, str]:
|
|
70
|
+
"""Parse a GitHub comment URL to extract owner, repo, comment_id, and comment_type.
|
|
71
|
+
|
|
72
|
+
Supports two URL formats:
|
|
73
|
+
- Issue/PR timeline comments: https://github.com/{owner}/{repo}/issues/{num}#issuecomment-{id}
|
|
74
|
+
- PR review comments: https://github.com/{owner}/{repo}/pull/{num}#discussion_r{id}
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
url: GitHub comment URL.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Tuple of (owner, repo, comment_id, comment_type).
|
|
81
|
+
comment_type is either 'issue_comment' or 'review_comment'.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
GitHubCommentParseError: If the URL cannot be parsed.
|
|
85
|
+
"""
|
|
86
|
+
parsed = urlparse(url)
|
|
87
|
+
|
|
88
|
+
if parsed.scheme != "https":
|
|
89
|
+
raise GitHubCommentParseError(
|
|
90
|
+
f"Invalid URL scheme: expected 'https', got '{parsed.scheme}'"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if parsed.netloc != "github.com":
|
|
94
|
+
raise GitHubCommentParseError(
|
|
95
|
+
f"Invalid URL host: expected 'github.com', got '{parsed.netloc}'"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
path_parts = parsed.path.strip("/").split("/")
|
|
99
|
+
if len(path_parts) < 2:
|
|
100
|
+
raise GitHubCommentParseError(
|
|
101
|
+
f"Invalid URL path: expected at least owner/repo, got '{parsed.path}'"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
owner = path_parts[0]
|
|
105
|
+
repo = path_parts[1]
|
|
106
|
+
fragment = parsed.fragment
|
|
107
|
+
|
|
108
|
+
issue_comment_match = re.match(r"^issuecomment-(\d+)$", fragment)
|
|
109
|
+
if issue_comment_match:
|
|
110
|
+
comment_id = int(issue_comment_match.group(1))
|
|
111
|
+
return owner, repo, comment_id, "issue_comment"
|
|
112
|
+
|
|
113
|
+
review_comment_match = re.match(r"^discussion_r(\d+)$", fragment)
|
|
114
|
+
if review_comment_match:
|
|
115
|
+
comment_id = int(review_comment_match.group(1))
|
|
116
|
+
return owner, repo, comment_id, "review_comment"
|
|
117
|
+
|
|
118
|
+
raise GitHubCommentParseError(
|
|
119
|
+
f"Invalid URL fragment: expected '#issuecomment-<id>' or '#discussion_r<id>', "
|
|
120
|
+
f"got '#{fragment}'"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_github_comment_info(
|
|
125
|
+
owner: str,
|
|
126
|
+
repo: str,
|
|
127
|
+
comment_id: int,
|
|
128
|
+
comment_type: str,
|
|
129
|
+
token: str | None = None,
|
|
130
|
+
) -> GitHubCommentInfo:
|
|
131
|
+
"""Fetch comment information from GitHub API.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
owner: Repository owner.
|
|
135
|
+
repo: Repository name.
|
|
136
|
+
comment_id: Numeric comment ID.
|
|
137
|
+
comment_type: Either 'issue_comment' or 'review_comment'.
|
|
138
|
+
token: GitHub API token. If None, will be resolved from environment.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
GitHubCommentInfo with comment and author details.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
GitHubAPIError: If the API request fails.
|
|
145
|
+
ValueError: If comment_type is invalid.
|
|
146
|
+
"""
|
|
147
|
+
if token is None:
|
|
148
|
+
token = resolve_github_token()
|
|
149
|
+
|
|
150
|
+
if comment_type == "issue_comment":
|
|
151
|
+
url = f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues/comments/{comment_id}"
|
|
152
|
+
elif comment_type == "review_comment":
|
|
153
|
+
url = f"{GITHUB_API_BASE}/repos/{owner}/{repo}/pulls/comments/{comment_id}"
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError(f"Invalid comment_type: {comment_type}")
|
|
156
|
+
|
|
157
|
+
headers = {
|
|
158
|
+
"Authorization": f"Bearer {token}",
|
|
159
|
+
"Accept": "application/vnd.github+json",
|
|
160
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
response = requests.get(url, headers=headers, timeout=30)
|
|
164
|
+
if not response.ok:
|
|
165
|
+
raise GitHubAPIError(
|
|
166
|
+
f"Failed to fetch comment {comment_id} from {owner}/{repo}: "
|
|
167
|
+
f"{response.status_code} {response.text}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
data = response.json()
|
|
171
|
+
user = data.get("user", {})
|
|
172
|
+
|
|
173
|
+
return GitHubCommentInfo(
|
|
174
|
+
comment_id=comment_id,
|
|
175
|
+
owner=owner,
|
|
176
|
+
repo=repo,
|
|
177
|
+
author_login=user.get("login", ""),
|
|
178
|
+
author_association=data.get("author_association", "NONE"),
|
|
179
|
+
comment_type=comment_type,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_github_user_info(login: str, token: str | None = None) -> GitHubUserInfo:
|
|
184
|
+
"""Fetch user information from GitHub API.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
login: GitHub username.
|
|
188
|
+
token: GitHub API token. If None, will be resolved from environment.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
GitHubUserInfo with user details.
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
GitHubAPIError: If the API request fails.
|
|
195
|
+
"""
|
|
196
|
+
if token is None:
|
|
197
|
+
token = resolve_github_token()
|
|
198
|
+
|
|
199
|
+
url = f"{GITHUB_API_BASE}/users/{login}"
|
|
200
|
+
headers = {
|
|
201
|
+
"Authorization": f"Bearer {token}",
|
|
202
|
+
"Accept": "application/vnd.github+json",
|
|
203
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
response = requests.get(url, headers=headers, timeout=30)
|
|
207
|
+
if not response.ok:
|
|
208
|
+
raise GitHubAPIError(
|
|
209
|
+
f"Failed to fetch user {login}: {response.status_code} {response.text}"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
data = response.json()
|
|
213
|
+
|
|
214
|
+
return GitHubUserInfo(
|
|
215
|
+
login=data.get("login", login),
|
|
216
|
+
email=data.get("email"),
|
|
217
|
+
name=data.get("name"),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get_admin_email_from_approval_comment(approval_comment_url: str) -> str:
|
|
222
|
+
"""Derive the admin email from a GitHub approval comment URL.
|
|
223
|
+
|
|
224
|
+
This function:
|
|
225
|
+
1. Parses the comment URL to extract owner, repo, and comment ID.
|
|
226
|
+
2. Fetches the comment from GitHub API to get the author's username.
|
|
227
|
+
3. Fetches the user's profile to get their public email.
|
|
228
|
+
4. Validates the email is an @airbyte.io address.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
approval_comment_url: GitHub comment URL where approval was given.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The admin's @airbyte.io email address.
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
GitHubCommentParseError: If the URL cannot be parsed.
|
|
238
|
+
GitHubAPIError: If GitHub API calls fail.
|
|
239
|
+
GitHubUserEmailNotFoundError: If the user has no public email or
|
|
240
|
+
the email is not an @airbyte.io address.
|
|
241
|
+
"""
|
|
242
|
+
owner, repo, comment_id, comment_type = _parse_github_comment_url(
|
|
243
|
+
approval_comment_url
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
comment_info = get_github_comment_info(owner, repo, comment_id, comment_type)
|
|
247
|
+
|
|
248
|
+
user_info = get_github_user_info(comment_info.author_login)
|
|
249
|
+
|
|
250
|
+
if not user_info.email:
|
|
251
|
+
raise GitHubUserEmailNotFoundError(
|
|
252
|
+
f"GitHub user '{comment_info.author_login}' does not have a public email set. "
|
|
253
|
+
f"To use this tool, the approver must have a public @airbyte.io email "
|
|
254
|
+
f"configured on their GitHub profile (Settings > Public email)."
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if not user_info.email.endswith("@airbyte.io"):
|
|
258
|
+
raise GitHubUserEmailNotFoundError(
|
|
259
|
+
f"GitHub user '{comment_info.author_login}' has public email '{user_info.email}' "
|
|
260
|
+
f"which is not an @airbyte.io address. Only @airbyte.io emails are authorized "
|
|
261
|
+
f"for admin operations."
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return user_info.email
|
|
@@ -27,6 +27,12 @@ from airbyte_ops_mcp.cloud_admin.models import (
|
|
|
27
27
|
ConnectorVersionInfo,
|
|
28
28
|
VersionOverrideOperationResult,
|
|
29
29
|
)
|
|
30
|
+
from airbyte_ops_mcp.github_api import (
|
|
31
|
+
GitHubAPIError,
|
|
32
|
+
GitHubCommentParseError,
|
|
33
|
+
GitHubUserEmailNotFoundError,
|
|
34
|
+
get_admin_email_from_approval_comment,
|
|
35
|
+
)
|
|
30
36
|
from airbyte_ops_mcp.mcp._http_headers import (
|
|
31
37
|
resolve_bearer_token,
|
|
32
38
|
resolve_client_id,
|
|
@@ -155,6 +161,15 @@ def set_cloud_connector_version_override(
|
|
|
155
161
|
Literal["source", "destination"],
|
|
156
162
|
"The type of connector (source or destination)",
|
|
157
163
|
],
|
|
164
|
+
approval_comment_url: Annotated[
|
|
165
|
+
str,
|
|
166
|
+
Field(
|
|
167
|
+
description="URL to a GitHub comment where the admin has explicitly "
|
|
168
|
+
"requested or authorized this deployment. Must be a valid GitHub comment URL. "
|
|
169
|
+
"Required for authorization. The admin email is automatically derived from "
|
|
170
|
+
"the comment author's GitHub profile.",
|
|
171
|
+
),
|
|
172
|
+
],
|
|
158
173
|
version: Annotated[
|
|
159
174
|
str | None,
|
|
160
175
|
Field(
|
|
@@ -186,14 +201,6 @@ def set_cloud_connector_version_override(
|
|
|
186
201
|
default=None,
|
|
187
202
|
),
|
|
188
203
|
],
|
|
189
|
-
admin_user_email: Annotated[
|
|
190
|
-
str | None,
|
|
191
|
-
Field(
|
|
192
|
-
description="Email of the admin user authorizing this operation. "
|
|
193
|
-
"Must be an @airbyte.io email address. Required for authorization.",
|
|
194
|
-
default=None,
|
|
195
|
-
),
|
|
196
|
-
],
|
|
197
204
|
issue_url: Annotated[
|
|
198
205
|
str | None,
|
|
199
206
|
Field(
|
|
@@ -202,15 +209,6 @@ def set_cloud_connector_version_override(
|
|
|
202
209
|
default=None,
|
|
203
210
|
),
|
|
204
211
|
],
|
|
205
|
-
approval_comment_url: Annotated[
|
|
206
|
-
str | None,
|
|
207
|
-
Field(
|
|
208
|
-
description="URL to a GitHub comment where the admin has explicitly "
|
|
209
|
-
"requested or authorized this deployment. Must be a valid GitHub comment URL. "
|
|
210
|
-
"Required for authorization.",
|
|
211
|
-
default=None,
|
|
212
|
-
),
|
|
213
|
-
],
|
|
214
212
|
ai_agent_session_url: Annotated[
|
|
215
213
|
str | None,
|
|
216
214
|
Field(
|
|
@@ -224,9 +222,13 @@ def set_cloud_connector_version_override(
|
|
|
224
222
|
|
|
225
223
|
**Admin-only operation** - Requires:
|
|
226
224
|
- AIRBYTE_INTERNAL_ADMIN_FLAG=airbyte.io environment variable
|
|
227
|
-
- admin_user_email parameter (must be @airbyte.io email)
|
|
228
225
|
- issue_url parameter (GitHub issue URL for context)
|
|
229
|
-
- approval_comment_url parameter (GitHub comment URL with approval)
|
|
226
|
+
- approval_comment_url parameter (GitHub comment URL with approval from an @airbyte.io user)
|
|
227
|
+
|
|
228
|
+
The admin user email is automatically derived from the approval_comment_url by:
|
|
229
|
+
1. Fetching the comment from GitHub API to get the author's username
|
|
230
|
+
2. Fetching the user's profile to get their public email
|
|
231
|
+
3. Validating the email is an @airbyte.io address
|
|
230
232
|
|
|
231
233
|
You must specify EXACTLY ONE of `version` OR `unset=True`, but not both.
|
|
232
234
|
When setting a version, `override_reason` is required.
|
|
@@ -252,16 +254,9 @@ def set_cloud_connector_version_override(
|
|
|
252
254
|
connector_type=actor_type,
|
|
253
255
|
)
|
|
254
256
|
|
|
255
|
-
# Validate
|
|
257
|
+
# Validate authorization parameters
|
|
256
258
|
validation_errors: list[str] = []
|
|
257
259
|
|
|
258
|
-
if not admin_user_email:
|
|
259
|
-
validation_errors.append("admin_user_email is required for authorization")
|
|
260
|
-
elif "@airbyte.io" not in admin_user_email:
|
|
261
|
-
validation_errors.append(
|
|
262
|
-
f"admin_user_email must be an @airbyte.io email address, got: {admin_user_email}"
|
|
263
|
-
)
|
|
264
|
-
|
|
265
260
|
if not issue_url:
|
|
266
261
|
validation_errors.append(
|
|
267
262
|
"issue_url is required for authorization (GitHub issue URL)"
|
|
@@ -271,11 +266,7 @@ def set_cloud_connector_version_override(
|
|
|
271
266
|
f"issue_url must be a valid GitHub URL (https://github.com/...), got: {issue_url}"
|
|
272
267
|
)
|
|
273
268
|
|
|
274
|
-
if not approval_comment_url:
|
|
275
|
-
validation_errors.append(
|
|
276
|
-
"approval_comment_url is required for authorization (GitHub comment URL)"
|
|
277
|
-
)
|
|
278
|
-
elif not approval_comment_url.startswith("https://github.com/"):
|
|
269
|
+
if not approval_comment_url.startswith("https://github.com/"):
|
|
279
270
|
validation_errors.append(
|
|
280
271
|
f"approval_comment_url must be a valid GitHub URL, got: {approval_comment_url}"
|
|
281
272
|
)
|
|
@@ -296,6 +287,31 @@ def set_cloud_connector_version_override(
|
|
|
296
287
|
connector_type=actor_type,
|
|
297
288
|
)
|
|
298
289
|
|
|
290
|
+
# Derive admin email from approval comment URL
|
|
291
|
+
try:
|
|
292
|
+
admin_user_email = get_admin_email_from_approval_comment(approval_comment_url)
|
|
293
|
+
except GitHubCommentParseError as e:
|
|
294
|
+
return VersionOverrideOperationResult(
|
|
295
|
+
success=False,
|
|
296
|
+
message=f"Failed to parse approval comment URL: {e}",
|
|
297
|
+
connector_id=actor_id,
|
|
298
|
+
connector_type=actor_type,
|
|
299
|
+
)
|
|
300
|
+
except GitHubAPIError as e:
|
|
301
|
+
return VersionOverrideOperationResult(
|
|
302
|
+
success=False,
|
|
303
|
+
message=f"Failed to fetch approval comment from GitHub: {e}",
|
|
304
|
+
connector_id=actor_id,
|
|
305
|
+
connector_type=actor_type,
|
|
306
|
+
)
|
|
307
|
+
except GitHubUserEmailNotFoundError as e:
|
|
308
|
+
return VersionOverrideOperationResult(
|
|
309
|
+
success=False,
|
|
310
|
+
message=str(e),
|
|
311
|
+
connector_id=actor_id,
|
|
312
|
+
connector_type=actor_type,
|
|
313
|
+
)
|
|
314
|
+
|
|
299
315
|
# Build enhanced override reason with audit fields (only for 'set' operations)
|
|
300
316
|
enhanced_override_reason = override_reason
|
|
301
317
|
if not unset and override_reason:
|
|
File without changes
|
{airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.2.4.dist-info}/entry_points.txt
RENAMED
|
File without changes
|