lfx-gitlab 0.1.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.
- lfx_gitlab/__init__.py +1 -0
- lfx_gitlab/base.py +102 -0
- lfx_gitlab/client.py +244 -0
- lfx_gitlab/components/__init__.py +1 -0
- lfx_gitlab/components/gitlab/__init__.py +27 -0
- lfx_gitlab/components/gitlab/capabilities.v1.json +284 -0
- lfx_gitlab/components/gitlab/issue_comment_create.py +21 -0
- lfx_gitlab/components/gitlab/issue_create.py +21 -0
- lfx_gitlab/components/gitlab/issue_get.py +20 -0
- lfx_gitlab/components/gitlab/issue_trigger.py +11 -0
- lfx_gitlab/components/gitlab/issue_update.py +30 -0
- lfx_gitlab/components/gitlab/merge_request_comment_create.py +21 -0
- lfx_gitlab/components/gitlab/merge_request_create.py +23 -0
- lfx_gitlab/components/gitlab/merge_request_get.py +20 -0
- lfx_gitlab/components/gitlab/merge_request_trigger.py +11 -0
- lfx_gitlab/components/gitlab/merge_request_update.py +30 -0
- lfx_gitlab/components/gitlab/project_get.py +16 -0
- lfx_gitlab/extension.json +25 -0
- lfx_gitlab/triggers.py +58 -0
- lfx_gitlab-0.1.0.dist-info/METADATA +17 -0
- lfx_gitlab-0.1.0.dist-info/RECORD +23 -0
- lfx_gitlab-0.1.0.dist-info/WHEEL +4 -0
- lfx_gitlab-0.1.0.dist-info/entry_points.txt +5 -0
lfx_gitlab/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""GitLab personal-token actions and signed project triggers."""
|
lfx_gitlab/base.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Connection and telemetry boundary for GitLab action components."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from lfx.custom.custom_component.component import Component
|
|
6
|
+
from lfx.integrations.errors import AuthExpiredError, InvalidRequestError, ProviderUnavailableError
|
|
7
|
+
from lfx.integrations.telemetry import integration_action
|
|
8
|
+
from lfx.io import ConnectionRefInput, IntInput
|
|
9
|
+
from lfx.schema.data import Data
|
|
10
|
+
|
|
11
|
+
from lfx_gitlab.client import GitLabAuthExpiredError, GitLabClient, bounded_text, positive_id
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def connection_input(capability):
|
|
15
|
+
return ConnectionRefInput(
|
|
16
|
+
name="connection",
|
|
17
|
+
display_name="GitLab Connection",
|
|
18
|
+
provider="gitlab",
|
|
19
|
+
auth_profile_id="gitlab-personal-token",
|
|
20
|
+
identity_kind="user",
|
|
21
|
+
ownership_mode="user",
|
|
22
|
+
required_scopes=["api"],
|
|
23
|
+
capabilities=[capability],
|
|
24
|
+
required=True,
|
|
25
|
+
info="Use a verified personal access token for this deployment's configured GitLab instance.",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def project_input(*, connectable=True):
|
|
30
|
+
return IntInput(
|
|
31
|
+
name="project_id",
|
|
32
|
+
display_name="Project ID",
|
|
33
|
+
required=True,
|
|
34
|
+
input_types=["Message"] if connectable else [],
|
|
35
|
+
info="Numeric GitLab project ID. Project paths and URLs are unavailable.",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class GitLabActionComponent(Component):
|
|
40
|
+
icon = "Gitlab"
|
|
41
|
+
capability_id: ClassVar[str] = ""
|
|
42
|
+
operation: ClassVar[str] = ""
|
|
43
|
+
resource: ClassVar[str] = ""
|
|
44
|
+
|
|
45
|
+
def _pre_run_setup(self):
|
|
46
|
+
self.__dict__.pop("_gitlab_result", None)
|
|
47
|
+
|
|
48
|
+
async def build_result(self) -> Data:
|
|
49
|
+
if "_gitlab_result" in self.__dict__:
|
|
50
|
+
return Data(data=self.__dict__["_gitlab_result"])
|
|
51
|
+
path = f"/projects/{positive_id(self.project_id, field='Project ID')}"
|
|
52
|
+
operation = self.operation
|
|
53
|
+
if self.resource != "project":
|
|
54
|
+
path += "/" + {"issue": "issues", "merge_request": "merge_requests"}[self.resource]
|
|
55
|
+
if self.resource != "project" and operation in {"get", "update", "comment"}:
|
|
56
|
+
number_field = "Issue Number" if self.resource == "issue" else "Merge Request Number"
|
|
57
|
+
path += "/" + str(positive_id(self.number, field=number_field))
|
|
58
|
+
body = None
|
|
59
|
+
if operation == "comment":
|
|
60
|
+
path += "/notes"
|
|
61
|
+
body = {"body": bounded_text(self.body, required=True, field="Comment")}
|
|
62
|
+
elif operation == "create":
|
|
63
|
+
body = {
|
|
64
|
+
"title": bounded_text(self.title, required=True, maximum=255, field="Title"),
|
|
65
|
+
"description": bounded_text(self.body_content, field="Description"),
|
|
66
|
+
}
|
|
67
|
+
if self.resource == "merge_request":
|
|
68
|
+
body.update(
|
|
69
|
+
source_branch=bounded_text(self.source_branch, required=True, maximum=255, field="Source Branch"),
|
|
70
|
+
target_branch=bounded_text(self.target_branch, required=True, maximum=255, field="Target Branch"),
|
|
71
|
+
)
|
|
72
|
+
elif operation == "update":
|
|
73
|
+
body = {}
|
|
74
|
+
if self.title not in (None, ""):
|
|
75
|
+
body["title"] = bounded_text(self.title, required=True, maximum=255, field="Title")
|
|
76
|
+
if self.body_content not in (None, ""):
|
|
77
|
+
body["description"] = bounded_text(self.body_content, field="Description")
|
|
78
|
+
if self.state_event not in {"unchanged", "close", "reopen"}:
|
|
79
|
+
msg = "Choose a supported state change."
|
|
80
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
81
|
+
if self.state_event != "unchanged":
|
|
82
|
+
body["state_event"] = self.state_event
|
|
83
|
+
if not body:
|
|
84
|
+
msg = "Provide a new title, description, or state change."
|
|
85
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
86
|
+
lease = self.resolve_connection("connection")
|
|
87
|
+
try:
|
|
88
|
+
credential = await lease.get_credential()
|
|
89
|
+
except AuthExpiredError:
|
|
90
|
+
raise GitLabAuthExpiredError from None
|
|
91
|
+
method = "POST" if operation in {"create", "comment"} else "PUT" if operation == "update" else "GET"
|
|
92
|
+
async with (
|
|
93
|
+
integration_action(
|
|
94
|
+
self, provider="gitlab", capability=self.capability_id, owner_kind=credential.owner_kind
|
|
95
|
+
),
|
|
96
|
+
GitLabClient(lease) as client,
|
|
97
|
+
):
|
|
98
|
+
result = await client.request(method, path, body=body)
|
|
99
|
+
if not isinstance(result, dict):
|
|
100
|
+
raise ProviderUnavailableError(provider="gitlab")
|
|
101
|
+
self.__dict__["_gitlab_result"] = result
|
|
102
|
+
return Data(data=result)
|
lfx_gitlab/client.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Bounded GitLab API requests using a trusted deployment origin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import ipaddress
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from urllib.parse import urlsplit
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
from lfx.integrations.errors import (
|
|
16
|
+
AuthExpiredError,
|
|
17
|
+
ConnectionNotAuthorizedError,
|
|
18
|
+
IntegrationNetworkError,
|
|
19
|
+
InvalidRequestError,
|
|
20
|
+
ProviderUnavailableError,
|
|
21
|
+
RateLimitedError,
|
|
22
|
+
ResourceNotFoundError,
|
|
23
|
+
)
|
|
24
|
+
from lfx.utils.ssrf_httpx import ssrf_protected_strict_httpx_client_kwargs_for_url
|
|
25
|
+
from lfx.utils.ssrf_protection import SSRFProtectionError
|
|
26
|
+
from lfx.utils.url_redaction import suppress_sensitive_http_logs
|
|
27
|
+
|
|
28
|
+
_PATH = re.compile(
|
|
29
|
+
r"/(?:user|users/[1-9][0-9]*|version|personal_access_tokens/self|projects/[1-9][0-9]*(?:/(?:hooks|issues|merge_requests)(?:/[1-9][0-9]*(?:/notes)?)?)?)\Z"
|
|
30
|
+
)
|
|
31
|
+
MAX_RESPONSE_BYTES = 2 * 1024 * 1024
|
|
32
|
+
MAX_REQUEST_BYTES = 64 * 1024
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class GitLabAuthExpiredError(AuthExpiredError):
|
|
36
|
+
"""Keep reconnect guidance visible in canvas errors as well as structured hints."""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
super().__init__(provider="gitlab")
|
|
40
|
+
self.message = (
|
|
41
|
+
"The GitLab credential is expired or was rejected. Reconnect the GitLab connection and try again."
|
|
42
|
+
)
|
|
43
|
+
self.safe_message = self.message
|
|
44
|
+
self.args = (self.safe_message,)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def api_origin() -> str:
|
|
48
|
+
"""Read operator configuration rather than any flow or provider URL."""
|
|
49
|
+
value = os.environ.get("LANGFLOW_GITLAB_ORIGIN", "https://gitlab.com")
|
|
50
|
+
parts = urlsplit(value)
|
|
51
|
+
host = parts.hostname or ""
|
|
52
|
+
if (
|
|
53
|
+
parts.scheme != "https"
|
|
54
|
+
or parts.username is not None
|
|
55
|
+
or parts.password is not None
|
|
56
|
+
or parts.query
|
|
57
|
+
or parts.fragment
|
|
58
|
+
or parts.path not in {"", "/"}
|
|
59
|
+
or parts.port not in {None, 443}
|
|
60
|
+
or not re.fullmatch(r"[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?", host)
|
|
61
|
+
or "." not in host
|
|
62
|
+
or host.endswith((".localhost", ".local"))
|
|
63
|
+
):
|
|
64
|
+
msg = "Configure one public HTTPS GitLab origin without a path or port."
|
|
65
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
66
|
+
try:
|
|
67
|
+
ipaddress.ip_address(host)
|
|
68
|
+
except ValueError:
|
|
69
|
+
pass
|
|
70
|
+
else:
|
|
71
|
+
msg = "Configure a GitLab DNS hostname rather than an IP address."
|
|
72
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
73
|
+
return "https://" + host.lower()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def positive_id(value, *, field="ID") -> int:
|
|
77
|
+
msg = f"{field} must be an integer from 1 to 9223372036854775807 (the signed 64-bit range)."
|
|
78
|
+
if isinstance(value, bool):
|
|
79
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
80
|
+
try:
|
|
81
|
+
number = int(value)
|
|
82
|
+
except (TypeError, ValueError, OverflowError):
|
|
83
|
+
raise InvalidRequestError(msg, provider="gitlab") from None
|
|
84
|
+
if str(number) != str(value) or not 1 <= number <= 2**63 - 1:
|
|
85
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
86
|
+
return number
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def bounded_text(value, *, required: bool = False, maximum: int = 60000, field="Text") -> str:
|
|
90
|
+
if required and isinstance(value, str) and not value.strip():
|
|
91
|
+
msg = f"{field} must contain non-whitespace text."
|
|
92
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
93
|
+
try:
|
|
94
|
+
valid = isinstance(value, str) and len(value.encode()) <= maximum and (not required or bool(value.strip()))
|
|
95
|
+
except UnicodeEncodeError:
|
|
96
|
+
valid = False
|
|
97
|
+
if not valid:
|
|
98
|
+
msg = f"{field} must be text containing at most {maximum} UTF-8 bytes."
|
|
99
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _safe_output(value):
|
|
104
|
+
if isinstance(value, dict):
|
|
105
|
+
return {
|
|
106
|
+
key: _safe_output(item)
|
|
107
|
+
for key, item in value.items()
|
|
108
|
+
if not any(
|
|
109
|
+
term in key.lower()
|
|
110
|
+
for term in (
|
|
111
|
+
"token",
|
|
112
|
+
"secret",
|
|
113
|
+
"password",
|
|
114
|
+
"private_key",
|
|
115
|
+
"clone_url",
|
|
116
|
+
"http_url_to_repo",
|
|
117
|
+
"ssh_url_to_repo",
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
if isinstance(value, list):
|
|
122
|
+
return [_safe_output(item) for item in value]
|
|
123
|
+
return value
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def response_error(response: httpx.Response):
|
|
127
|
+
status = response.status_code
|
|
128
|
+
if status == httpx.codes.UNAUTHORIZED:
|
|
129
|
+
return GitLabAuthExpiredError()
|
|
130
|
+
if status == httpx.codes.TOO_MANY_REQUESTS:
|
|
131
|
+
try:
|
|
132
|
+
delay = float(response.headers.get("retry-after", "60"))
|
|
133
|
+
except (ValueError, OverflowError):
|
|
134
|
+
delay = 60.0
|
|
135
|
+
return RateLimitedError(provider="gitlab", retry_after=max(0, delay) if math.isfinite(delay) else 60.0)
|
|
136
|
+
if status == httpx.codes.FORBIDDEN:
|
|
137
|
+
return ConnectionNotAuthorizedError(provider="gitlab", reason="provider")
|
|
138
|
+
if status == httpx.codes.NOT_FOUND:
|
|
139
|
+
return ResourceNotFoundError(provider="gitlab")
|
|
140
|
+
if httpx.codes.BAD_REQUEST <= status < httpx.codes.INTERNAL_SERVER_ERROR:
|
|
141
|
+
return InvalidRequestError(provider="gitlab", http_status=status)
|
|
142
|
+
return ProviderUnavailableError(provider="gitlab", http_status=status)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class GitLabClient:
|
|
146
|
+
"""Reject redirects and arbitrary paths before resolving credentials."""
|
|
147
|
+
|
|
148
|
+
def __init__(self, lease, *, transport: httpx.AsyncBaseTransport | None = None):
|
|
149
|
+
self.lease = lease
|
|
150
|
+
self.origin = api_origin()
|
|
151
|
+
try:
|
|
152
|
+
kwargs = (
|
|
153
|
+
ssrf_protected_strict_httpx_client_kwargs_for_url(self.origin)[1]
|
|
154
|
+
if transport is None
|
|
155
|
+
else {"transport": transport}
|
|
156
|
+
)
|
|
157
|
+
except SSRFProtectionError:
|
|
158
|
+
raise IntegrationNetworkError(
|
|
159
|
+
reason="connection", provider="gitlab", host=urlsplit(self.origin).hostname
|
|
160
|
+
) from None
|
|
161
|
+
kwargs["follow_redirects"] = False
|
|
162
|
+
self._client = httpx.AsyncClient(timeout=20, trust_env=False, **kwargs)
|
|
163
|
+
|
|
164
|
+
async def __aenter__(self):
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
async def __aexit__(self, *_args):
|
|
168
|
+
await self._client.aclose()
|
|
169
|
+
|
|
170
|
+
async def request(self, method, path, *, body=None, params=None):
|
|
171
|
+
if method not in {"GET", "POST", "PUT", "DELETE"} or not _PATH.fullmatch(path):
|
|
172
|
+
msg = "The GitLab REST request is invalid."
|
|
173
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
174
|
+
content = json.dumps(body, ensure_ascii=False).encode() if body is not None else None
|
|
175
|
+
if content is not None and len(content) > MAX_REQUEST_BYTES:
|
|
176
|
+
msg = "The GitLab request exceeds 64 KiB."
|
|
177
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
178
|
+
try:
|
|
179
|
+
credential = await self.lease.get_credential()
|
|
180
|
+
except AuthExpiredError:
|
|
181
|
+
raise GitLabAuthExpiredError from None
|
|
182
|
+
if (
|
|
183
|
+
credential.provider != "gitlab"
|
|
184
|
+
or credential.identity != "user_delegated"
|
|
185
|
+
or credential.account is None
|
|
186
|
+
or credential.account.tenant_id != self.origin
|
|
187
|
+
or not credential.scopes_verified
|
|
188
|
+
or "api" not in credential.granted_scopes
|
|
189
|
+
):
|
|
190
|
+
raise ConnectionNotAuthorizedError(
|
|
191
|
+
provider="gitlab", hint="Choose a verified GitLab personal access token connection for this deployment."
|
|
192
|
+
)
|
|
193
|
+
expiry = credential.expires_at
|
|
194
|
+
if expiry is None or expiry.tzinfo is None or expiry <= datetime.now(timezone.utc):
|
|
195
|
+
raise GitLabAuthExpiredError
|
|
196
|
+
token = credential.access_token.get_secret_value()
|
|
197
|
+
try:
|
|
198
|
+
try:
|
|
199
|
+
return await asyncio.wait_for(self._request(method, path, content, params, token), timeout=25)
|
|
200
|
+
except AuthExpiredError as error:
|
|
201
|
+
try:
|
|
202
|
+
replacement = await self.lease.get_token_after_auth_error(error, rejected_token=token)
|
|
203
|
+
except AuthExpiredError:
|
|
204
|
+
raise GitLabAuthExpiredError from None
|
|
205
|
+
if replacement == token:
|
|
206
|
+
raise
|
|
207
|
+
return await self.request(method, path, body=body, params=params)
|
|
208
|
+
except (asyncio.TimeoutError, httpx.TimeoutException):
|
|
209
|
+
raise IntegrationNetworkError(
|
|
210
|
+
reason="timeout", provider="gitlab", host=urlsplit(self.origin).hostname
|
|
211
|
+
) from None
|
|
212
|
+
except httpx.TransportError:
|
|
213
|
+
raise IntegrationNetworkError(
|
|
214
|
+
reason="connection", provider="gitlab", host=urlsplit(self.origin).hostname
|
|
215
|
+
) from None
|
|
216
|
+
|
|
217
|
+
async def _request(self, method, path, content, params, token):
|
|
218
|
+
async with (
|
|
219
|
+
suppress_sensitive_http_logs(),
|
|
220
|
+
self._client.stream(
|
|
221
|
+
method,
|
|
222
|
+
self.origin + "/api/v4" + path,
|
|
223
|
+
content=content,
|
|
224
|
+
params=params,
|
|
225
|
+
headers={"PRIVATE-TOKEN": token, "Content-Type": "application/json", "Accept": "application/json"},
|
|
226
|
+
) as response,
|
|
227
|
+
):
|
|
228
|
+
chunks = bytearray()
|
|
229
|
+
async for chunk in response.aiter_bytes():
|
|
230
|
+
chunks.extend(chunk)
|
|
231
|
+
if len(chunks) > MAX_RESPONSE_BYTES:
|
|
232
|
+
msg = "The GitLab response exceeds 2 MiB."
|
|
233
|
+
raise InvalidRequestError(msg, provider="gitlab")
|
|
234
|
+
if not httpx.codes.OK <= response.status_code < httpx.codes.MULTIPLE_CHOICES:
|
|
235
|
+
raise response_error(response)
|
|
236
|
+
if not chunks:
|
|
237
|
+
return {}
|
|
238
|
+
try:
|
|
239
|
+
payload = json.loads(chunks)
|
|
240
|
+
except (ValueError, UnicodeDecodeError):
|
|
241
|
+
raise ProviderUnavailableError(provider="gitlab") from None
|
|
242
|
+
if not isinstance(payload, (dict, list)):
|
|
243
|
+
raise ProviderUnavailableError(provider="gitlab")
|
|
244
|
+
return _safe_output(payload)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""GitLab component categories."""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Stable GitLab component identities."""
|
|
2
|
+
|
|
3
|
+
from .issue_comment_create import GitLabCreateIssueCommentComponent
|
|
4
|
+
from .issue_create import GitLabCreateIssueComponent
|
|
5
|
+
from .issue_get import GitLabGetIssueComponent
|
|
6
|
+
from .issue_trigger import GitLabOnIssueTriggerComponent
|
|
7
|
+
from .issue_update import GitLabUpdateIssueComponent
|
|
8
|
+
from .merge_request_comment_create import GitLabCreateMergeRequestCommentComponent
|
|
9
|
+
from .merge_request_create import GitLabCreateMergeRequestComponent
|
|
10
|
+
from .merge_request_get import GitLabGetMergeRequestComponent
|
|
11
|
+
from .merge_request_trigger import GitLabOnMergeRequestTriggerComponent
|
|
12
|
+
from .merge_request_update import GitLabUpdateMergeRequestComponent
|
|
13
|
+
from .project_get import GitLabGetProjectComponent
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"GitLabCreateIssueCommentComponent",
|
|
17
|
+
"GitLabCreateIssueComponent",
|
|
18
|
+
"GitLabCreateMergeRequestCommentComponent",
|
|
19
|
+
"GitLabCreateMergeRequestComponent",
|
|
20
|
+
"GitLabGetIssueComponent",
|
|
21
|
+
"GitLabGetMergeRequestComponent",
|
|
22
|
+
"GitLabGetProjectComponent",
|
|
23
|
+
"GitLabOnIssueTriggerComponent",
|
|
24
|
+
"GitLabOnMergeRequestTriggerComponent",
|
|
25
|
+
"GitLabUpdateIssueComponent",
|
|
26
|
+
"GitLabUpdateMergeRequestComponent",
|
|
27
|
+
]
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"provider_id": "gitlab",
|
|
4
|
+
"display_name": "GitLab",
|
|
5
|
+
"icon": "Gitlab",
|
|
6
|
+
"docs_url": "https://docs.langflow.org/next/bundles-gitlab",
|
|
7
|
+
"auth_profiles": [
|
|
8
|
+
{
|
|
9
|
+
"id": "gitlab-personal-token",
|
|
10
|
+
"kind": "api_key",
|
|
11
|
+
"identity": "user_delegated",
|
|
12
|
+
"supports_pkce": false,
|
|
13
|
+
"supports_refresh": false,
|
|
14
|
+
"default_scopes": [
|
|
15
|
+
"api"
|
|
16
|
+
],
|
|
17
|
+
"client_type_by_context": {
|
|
18
|
+
"hosted": "external",
|
|
19
|
+
"self_managed": "external",
|
|
20
|
+
"desktop": "external",
|
|
21
|
+
"headless": "external"
|
|
22
|
+
},
|
|
23
|
+
"owner_by_context": {
|
|
24
|
+
"hosted": "customer",
|
|
25
|
+
"self_managed": "customer",
|
|
26
|
+
"desktop": "customer",
|
|
27
|
+
"headless": "customer"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"capabilities": [
|
|
32
|
+
{
|
|
33
|
+
"id": "gitlab.project_read",
|
|
34
|
+
"display_name": "GitLab: Get Project",
|
|
35
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
36
|
+
"identity": "user_delegated",
|
|
37
|
+
"required_scopes": [
|
|
38
|
+
"api"
|
|
39
|
+
],
|
|
40
|
+
"conditional_scopes": [],
|
|
41
|
+
"policy_keys": [
|
|
42
|
+
"integrations.gitlab.project_read"
|
|
43
|
+
],
|
|
44
|
+
"substrate": "rest",
|
|
45
|
+
"maturity": "preview",
|
|
46
|
+
"deployment_contexts": [
|
|
47
|
+
"hosted",
|
|
48
|
+
"self_managed",
|
|
49
|
+
"desktop",
|
|
50
|
+
"headless"
|
|
51
|
+
],
|
|
52
|
+
"risk": "read",
|
|
53
|
+
"component_ref": "GitLabGetProjectComponent"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "gitlab.issue_read",
|
|
57
|
+
"display_name": "GitLab: Get Issue",
|
|
58
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
59
|
+
"identity": "user_delegated",
|
|
60
|
+
"required_scopes": [
|
|
61
|
+
"api"
|
|
62
|
+
],
|
|
63
|
+
"conditional_scopes": [],
|
|
64
|
+
"policy_keys": [
|
|
65
|
+
"integrations.gitlab.issue_read"
|
|
66
|
+
],
|
|
67
|
+
"substrate": "rest",
|
|
68
|
+
"maturity": "preview",
|
|
69
|
+
"deployment_contexts": [
|
|
70
|
+
"hosted",
|
|
71
|
+
"self_managed",
|
|
72
|
+
"desktop",
|
|
73
|
+
"headless"
|
|
74
|
+
],
|
|
75
|
+
"risk": "read",
|
|
76
|
+
"component_ref": "GitLabGetIssueComponent"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "gitlab.issue_create",
|
|
80
|
+
"display_name": "GitLab: Create Issue",
|
|
81
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
82
|
+
"identity": "user_delegated",
|
|
83
|
+
"required_scopes": [
|
|
84
|
+
"api"
|
|
85
|
+
],
|
|
86
|
+
"conditional_scopes": [],
|
|
87
|
+
"policy_keys": [
|
|
88
|
+
"integrations.gitlab.issue_create"
|
|
89
|
+
],
|
|
90
|
+
"substrate": "rest",
|
|
91
|
+
"maturity": "preview",
|
|
92
|
+
"deployment_contexts": [
|
|
93
|
+
"hosted",
|
|
94
|
+
"self_managed",
|
|
95
|
+
"desktop",
|
|
96
|
+
"headless"
|
|
97
|
+
],
|
|
98
|
+
"risk": "write",
|
|
99
|
+
"component_ref": "GitLabCreateIssueComponent"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "gitlab.issue_update",
|
|
103
|
+
"display_name": "GitLab: Update Issue",
|
|
104
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
105
|
+
"identity": "user_delegated",
|
|
106
|
+
"required_scopes": [
|
|
107
|
+
"api"
|
|
108
|
+
],
|
|
109
|
+
"conditional_scopes": [],
|
|
110
|
+
"policy_keys": [
|
|
111
|
+
"integrations.gitlab.issue_update"
|
|
112
|
+
],
|
|
113
|
+
"substrate": "rest",
|
|
114
|
+
"maturity": "preview",
|
|
115
|
+
"deployment_contexts": [
|
|
116
|
+
"hosted",
|
|
117
|
+
"self_managed",
|
|
118
|
+
"desktop",
|
|
119
|
+
"headless"
|
|
120
|
+
],
|
|
121
|
+
"risk": "write",
|
|
122
|
+
"component_ref": "GitLabUpdateIssueComponent"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "gitlab.merge_request_read",
|
|
126
|
+
"display_name": "GitLab: Get Merge Request",
|
|
127
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
128
|
+
"identity": "user_delegated",
|
|
129
|
+
"required_scopes": [
|
|
130
|
+
"api"
|
|
131
|
+
],
|
|
132
|
+
"conditional_scopes": [],
|
|
133
|
+
"policy_keys": [
|
|
134
|
+
"integrations.gitlab.merge_request_read"
|
|
135
|
+
],
|
|
136
|
+
"substrate": "rest",
|
|
137
|
+
"maturity": "preview",
|
|
138
|
+
"deployment_contexts": [
|
|
139
|
+
"hosted",
|
|
140
|
+
"self_managed",
|
|
141
|
+
"desktop",
|
|
142
|
+
"headless"
|
|
143
|
+
],
|
|
144
|
+
"risk": "read",
|
|
145
|
+
"component_ref": "GitLabGetMergeRequestComponent"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": "gitlab.merge_request_create",
|
|
149
|
+
"display_name": "GitLab: Create Merge Request",
|
|
150
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
151
|
+
"identity": "user_delegated",
|
|
152
|
+
"required_scopes": [
|
|
153
|
+
"api"
|
|
154
|
+
],
|
|
155
|
+
"conditional_scopes": [],
|
|
156
|
+
"policy_keys": [
|
|
157
|
+
"integrations.gitlab.merge_request_create"
|
|
158
|
+
],
|
|
159
|
+
"substrate": "rest",
|
|
160
|
+
"maturity": "preview",
|
|
161
|
+
"deployment_contexts": [
|
|
162
|
+
"hosted",
|
|
163
|
+
"self_managed",
|
|
164
|
+
"desktop",
|
|
165
|
+
"headless"
|
|
166
|
+
],
|
|
167
|
+
"risk": "write",
|
|
168
|
+
"component_ref": "GitLabCreateMergeRequestComponent"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "gitlab.merge_request_update",
|
|
172
|
+
"display_name": "GitLab: Update Merge Request",
|
|
173
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
174
|
+
"identity": "user_delegated",
|
|
175
|
+
"required_scopes": [
|
|
176
|
+
"api"
|
|
177
|
+
],
|
|
178
|
+
"conditional_scopes": [],
|
|
179
|
+
"policy_keys": [
|
|
180
|
+
"integrations.gitlab.merge_request_update"
|
|
181
|
+
],
|
|
182
|
+
"substrate": "rest",
|
|
183
|
+
"maturity": "preview",
|
|
184
|
+
"deployment_contexts": [
|
|
185
|
+
"hosted",
|
|
186
|
+
"self_managed",
|
|
187
|
+
"desktop",
|
|
188
|
+
"headless"
|
|
189
|
+
],
|
|
190
|
+
"risk": "write",
|
|
191
|
+
"component_ref": "GitLabUpdateMergeRequestComponent"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"id": "gitlab.issue_comment_create",
|
|
195
|
+
"display_name": "GitLab: Create Issue Comment",
|
|
196
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
197
|
+
"identity": "user_delegated",
|
|
198
|
+
"required_scopes": [
|
|
199
|
+
"api"
|
|
200
|
+
],
|
|
201
|
+
"conditional_scopes": [],
|
|
202
|
+
"policy_keys": [
|
|
203
|
+
"integrations.gitlab.issue_comment_create"
|
|
204
|
+
],
|
|
205
|
+
"substrate": "rest",
|
|
206
|
+
"maturity": "preview",
|
|
207
|
+
"deployment_contexts": [
|
|
208
|
+
"hosted",
|
|
209
|
+
"self_managed",
|
|
210
|
+
"desktop",
|
|
211
|
+
"headless"
|
|
212
|
+
],
|
|
213
|
+
"risk": "write",
|
|
214
|
+
"component_ref": "GitLabCreateIssueCommentComponent"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"id": "gitlab.merge_request_comment_create",
|
|
218
|
+
"display_name": "GitLab: Create Merge Request Comment",
|
|
219
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
220
|
+
"identity": "user_delegated",
|
|
221
|
+
"required_scopes": [
|
|
222
|
+
"api"
|
|
223
|
+
],
|
|
224
|
+
"conditional_scopes": [],
|
|
225
|
+
"policy_keys": [
|
|
226
|
+
"integrations.gitlab.merge_request_comment_create"
|
|
227
|
+
],
|
|
228
|
+
"substrate": "rest",
|
|
229
|
+
"maturity": "preview",
|
|
230
|
+
"deployment_contexts": [
|
|
231
|
+
"hosted",
|
|
232
|
+
"self_managed",
|
|
233
|
+
"desktop",
|
|
234
|
+
"headless"
|
|
235
|
+
],
|
|
236
|
+
"risk": "write",
|
|
237
|
+
"component_ref": "GitLabCreateMergeRequestCommentComponent"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"id": "gitlab.trigger.issue",
|
|
241
|
+
"display_name": "GitLab: On Issue",
|
|
242
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
243
|
+
"identity": "user_delegated",
|
|
244
|
+
"required_scopes": [
|
|
245
|
+
"api"
|
|
246
|
+
],
|
|
247
|
+
"conditional_scopes": [],
|
|
248
|
+
"policy_keys": [
|
|
249
|
+
"integrations.gitlab.trigger.issue"
|
|
250
|
+
],
|
|
251
|
+
"substrate": "rest",
|
|
252
|
+
"maturity": "preview",
|
|
253
|
+
"deployment_contexts": [
|
|
254
|
+
"hosted",
|
|
255
|
+
"self_managed",
|
|
256
|
+
"headless"
|
|
257
|
+
],
|
|
258
|
+
"risk": "read",
|
|
259
|
+
"component_ref": "GitLabOnIssueTriggerComponent"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"id": "gitlab.trigger.merge_request",
|
|
263
|
+
"display_name": "GitLab: On Merge Request",
|
|
264
|
+
"auth_profile_id": "gitlab-personal-token",
|
|
265
|
+
"identity": "user_delegated",
|
|
266
|
+
"required_scopes": [
|
|
267
|
+
"api"
|
|
268
|
+
],
|
|
269
|
+
"conditional_scopes": [],
|
|
270
|
+
"policy_keys": [
|
|
271
|
+
"integrations.gitlab.trigger.merge_request"
|
|
272
|
+
],
|
|
273
|
+
"substrate": "rest",
|
|
274
|
+
"maturity": "preview",
|
|
275
|
+
"deployment_contexts": [
|
|
276
|
+
"hosted",
|
|
277
|
+
"self_managed",
|
|
278
|
+
"headless"
|
|
279
|
+
],
|
|
280
|
+
"risk": "read",
|
|
281
|
+
"component_ref": "GitLabOnMergeRequestTriggerComponent"
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Create a GitLab comment using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import IntInput, MultilineInput, Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabCreateIssueCommentComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Create Issue Comment"
|
|
10
|
+
name = "GitLabCreateIssueCommentComponent"
|
|
11
|
+
description = "Create a comment as the connection owner."
|
|
12
|
+
capability_id = "gitlab.issue_comment_create"
|
|
13
|
+
operation = "comment"
|
|
14
|
+
resource = "issue"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capability_id),
|
|
17
|
+
project_input(),
|
|
18
|
+
IntInput(name="number", display_name="Issue Number", required=True, input_types=["Message"]),
|
|
19
|
+
MultilineInput(name="body", display_name="Comment", required=True),
|
|
20
|
+
]
|
|
21
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Create one GitLab issue using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import MessageTextInput, MultilineInput, Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabCreateIssueComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Create Issue"
|
|
10
|
+
name = "GitLabCreateIssueComponent"
|
|
11
|
+
description = "Create a GitLab issue as the connection owner."
|
|
12
|
+
capability_id = "gitlab.issue_create"
|
|
13
|
+
operation = "create"
|
|
14
|
+
resource = "issue"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capability_id),
|
|
17
|
+
project_input(),
|
|
18
|
+
MessageTextInput(name="title", display_name="Title", required=True),
|
|
19
|
+
MultilineInput(name="body_content", display_name="Description", value=""),
|
|
20
|
+
]
|
|
21
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Get one GitLab issue using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import IntInput, Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabGetIssueComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Get Issue"
|
|
10
|
+
name = "GitLabGetIssueComponent"
|
|
11
|
+
description = "Get a GitLab issue as the connection owner."
|
|
12
|
+
capability_id = "gitlab.issue_read"
|
|
13
|
+
operation = "get"
|
|
14
|
+
resource = "issue"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capability_id),
|
|
17
|
+
project_input(),
|
|
18
|
+
IntInput(name="number", display_name="Issue Number", required=True, input_types=["Message"]),
|
|
19
|
+
]
|
|
20
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Declare a signed GitLab project webhook trigger."""
|
|
2
|
+
|
|
3
|
+
from lfx_gitlab.triggers import GitLabProjectTriggerComponent, trigger_inputs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GitLabOnIssueTriggerComponent(GitLabProjectTriggerComponent):
|
|
7
|
+
display_name = "GitLab: On Issue"
|
|
8
|
+
name = "GitLabOnIssueTriggerComponent"
|
|
9
|
+
description = "Start the flow for selected signed project events."
|
|
10
|
+
trigger_kind = "gitlab.issue"
|
|
11
|
+
inputs = trigger_inputs("issue")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Update one GitLab issue using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabUpdateIssueComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Update Issue"
|
|
10
|
+
name = "GitLabUpdateIssueComponent"
|
|
11
|
+
description = "Update a GitLab issue as the connection owner."
|
|
12
|
+
capability_id = "gitlab.issue_update"
|
|
13
|
+
operation = "update"
|
|
14
|
+
resource = "issue"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capability_id),
|
|
17
|
+
project_input(),
|
|
18
|
+
IntInput(name="number", display_name="Issue Number", required=True, input_types=["Message"]),
|
|
19
|
+
MessageTextInput(name="title", display_name="Title", value="", info="Leave blank to keep the existing title."),
|
|
20
|
+
MultilineInput(
|
|
21
|
+
name="body_content",
|
|
22
|
+
display_name="Description",
|
|
23
|
+
value="",
|
|
24
|
+
info="Leave blank to keep the existing description.",
|
|
25
|
+
),
|
|
26
|
+
DropdownInput(
|
|
27
|
+
name="state_event", display_name="State Change", options=["unchanged", "close", "reopen"], value="unchanged"
|
|
28
|
+
),
|
|
29
|
+
]
|
|
30
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Create a GitLab comment using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import IntInput, MultilineInput, Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabCreateMergeRequestCommentComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Create Merge Request Comment"
|
|
10
|
+
name = "GitLabCreateMergeRequestCommentComponent"
|
|
11
|
+
description = "Create a comment as the connection owner."
|
|
12
|
+
capability_id = "gitlab.merge_request_comment_create"
|
|
13
|
+
operation = "comment"
|
|
14
|
+
resource = "merge_request"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capability_id),
|
|
17
|
+
project_input(),
|
|
18
|
+
IntInput(name="number", display_name="Merge Request Number", required=True, input_types=["Message"]),
|
|
19
|
+
MultilineInput(name="body", display_name="Comment", required=True),
|
|
20
|
+
]
|
|
21
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Create one GitLab merge request using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import MessageTextInput, MultilineInput, Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabCreateMergeRequestComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Create Merge Request"
|
|
10
|
+
name = "GitLabCreateMergeRequestComponent"
|
|
11
|
+
description = "Create a GitLab merge request as the connection owner."
|
|
12
|
+
capability_id = "gitlab.merge_request_create"
|
|
13
|
+
operation = "create"
|
|
14
|
+
resource = "merge_request"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capability_id),
|
|
17
|
+
project_input(),
|
|
18
|
+
MessageTextInput(name="title", display_name="Title", required=True),
|
|
19
|
+
MultilineInput(name="body_content", display_name="Description", value=""),
|
|
20
|
+
MessageTextInput(name="source_branch", display_name="Source Branch", required=True),
|
|
21
|
+
MessageTextInput(name="target_branch", display_name="Target Branch", required=True),
|
|
22
|
+
]
|
|
23
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Get one GitLab merge request using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import IntInput, Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabGetMergeRequestComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Get Merge Request"
|
|
10
|
+
name = "GitLabGetMergeRequestComponent"
|
|
11
|
+
description = "Get a GitLab merge request as the connection owner."
|
|
12
|
+
capability_id = "gitlab.merge_request_read"
|
|
13
|
+
operation = "get"
|
|
14
|
+
resource = "merge_request"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capability_id),
|
|
17
|
+
project_input(),
|
|
18
|
+
IntInput(name="number", display_name="Merge Request Number", required=True, input_types=["Message"]),
|
|
19
|
+
]
|
|
20
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Declare a signed GitLab project webhook trigger."""
|
|
2
|
+
|
|
3
|
+
from lfx_gitlab.triggers import GitLabProjectTriggerComponent, trigger_inputs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GitLabOnMergeRequestTriggerComponent(GitLabProjectTriggerComponent):
|
|
7
|
+
display_name = "GitLab: On Merge Request"
|
|
8
|
+
name = "GitLabOnMergeRequestTriggerComponent"
|
|
9
|
+
description = "Start the flow for selected signed project events."
|
|
10
|
+
trigger_kind = "gitlab.merge_request"
|
|
11
|
+
inputs = trigger_inputs("merge_request")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Update one GitLab merge request using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabUpdateMergeRequestComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Update Merge Request"
|
|
10
|
+
name = "GitLabUpdateMergeRequestComponent"
|
|
11
|
+
description = "Update a GitLab merge request as the connection owner."
|
|
12
|
+
capability_id = "gitlab.merge_request_update"
|
|
13
|
+
operation = "update"
|
|
14
|
+
resource = "merge_request"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capability_id),
|
|
17
|
+
project_input(),
|
|
18
|
+
IntInput(name="number", display_name="Merge Request Number", required=True, input_types=["Message"]),
|
|
19
|
+
MessageTextInput(name="title", display_name="Title", value="", info="Leave blank to keep the existing title."),
|
|
20
|
+
MultilineInput(
|
|
21
|
+
name="body_content",
|
|
22
|
+
display_name="Description",
|
|
23
|
+
value="",
|
|
24
|
+
info="Leave blank to keep the existing description.",
|
|
25
|
+
),
|
|
26
|
+
DropdownInput(
|
|
27
|
+
name="state_event", display_name="State Change", options=["unchanged", "close", "reopen"], value="unchanged"
|
|
28
|
+
),
|
|
29
|
+
]
|
|
30
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Get one GitLab project using a connection."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import Output
|
|
4
|
+
|
|
5
|
+
from lfx_gitlab.base import GitLabActionComponent, connection_input, project_input
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabGetProjectComponent(GitLabActionComponent):
|
|
9
|
+
display_name = "GitLab: Get Project"
|
|
10
|
+
name = "GitLabGetProjectComponent"
|
|
11
|
+
description = "Get a GitLab project as the connection owner."
|
|
12
|
+
capability_id = "gitlab.project_read"
|
|
13
|
+
operation = "get"
|
|
14
|
+
resource = "project"
|
|
15
|
+
inputs = [connection_input(capability_id), project_input()]
|
|
16
|
+
outputs = [Output(display_name="Result", name="result", method="build_result")]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.langflow.org/extension/v1.json",
|
|
3
|
+
"id": "lfx-gitlab",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"name": "GitLab",
|
|
6
|
+
"description": "GitLab project, issue, merge request and comment actions with signed project event triggers.",
|
|
7
|
+
"lfx": {
|
|
8
|
+
"compat": [
|
|
9
|
+
"1"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"bundles": [
|
|
13
|
+
{
|
|
14
|
+
"name": "gitlab",
|
|
15
|
+
"path": "components/gitlab"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"integrations": [
|
|
19
|
+
{
|
|
20
|
+
"provider_id": "gitlab",
|
|
21
|
+
"bundle": "gitlab",
|
|
22
|
+
"path": "capabilities.v1.json"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
lfx_gitlab/triggers.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Declarative GitLab project triggers owned by the server runtime."""
|
|
2
|
+
|
|
3
|
+
from lfx.base.triggers.base import BaseTriggerComponent
|
|
4
|
+
from lfx.io import BoolInput, DropdownInput, MultiselectInput
|
|
5
|
+
|
|
6
|
+
from lfx_gitlab.base import connection_input, project_input
|
|
7
|
+
|
|
8
|
+
ISSUE_ACTIONS = ["open", "update", "close", "reopen"]
|
|
9
|
+
MR_ACTIONS = ["open", "update", "close", "reopen", "merge", "approved", "unapproved", "approval", "unapproval"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def trigger_inputs(resource):
|
|
13
|
+
return [
|
|
14
|
+
connection_input(f"gitlab.trigger.{resource}"),
|
|
15
|
+
project_input(connectable=False),
|
|
16
|
+
MultiselectInput(
|
|
17
|
+
name="actions",
|
|
18
|
+
display_name="Events",
|
|
19
|
+
options=ISSUE_ACTIONS if resource == "issue" else MR_ACTIONS,
|
|
20
|
+
value=["open", "close", "reopen"],
|
|
21
|
+
),
|
|
22
|
+
BoolInput(
|
|
23
|
+
name="include_self_events",
|
|
24
|
+
display_name="Include Own Events",
|
|
25
|
+
value=False,
|
|
26
|
+
advanced=True,
|
|
27
|
+
info="Enable only when the flow cannot write back into a trigger loop.",
|
|
28
|
+
),
|
|
29
|
+
BoolInput(name="include_bot_events", display_name="Include Bot Events", value=False, advanced=True),
|
|
30
|
+
DropdownInput(
|
|
31
|
+
name="delivery_mode",
|
|
32
|
+
display_name="Delivery Mode",
|
|
33
|
+
options=["push"],
|
|
34
|
+
value="push",
|
|
35
|
+
advanced=True,
|
|
36
|
+
info="Requires public HTTPS ingress and GitLab 19.1 or newer signing tokens.",
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class GitLabProjectTriggerComponent(BaseTriggerComponent):
|
|
42
|
+
provider = "gitlab"
|
|
43
|
+
icon = "Gitlab"
|
|
44
|
+
needs_connection = True
|
|
45
|
+
|
|
46
|
+
def trigger_config(self):
|
|
47
|
+
return {
|
|
48
|
+
"connection": getattr(self, "connection", None),
|
|
49
|
+
"project_id": getattr(self, "project_id", None),
|
|
50
|
+
"actions": getattr(self, "actions", []),
|
|
51
|
+
"delivery_mode": "push",
|
|
52
|
+
"include_self_events": getattr(self, "include_self_events", False),
|
|
53
|
+
"include_bot_events": getattr(self, "include_bot_events", False),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Preserve imports of the original helper while component source uses the loader-compatible name.
|
|
58
|
+
GitLabProjectTrigger = GitLabProjectTriggerComponent
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lfx-gitlab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GitLab actions and signed project webhook triggers.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: <3.16,>=3.10
|
|
7
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
8
|
+
Requires-Dist: lfx<2.0.0,>=1.14.0.dev0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# GitLab integrations
|
|
12
|
+
|
|
13
|
+
This preview bundle provides nine actions and two signed project webhook triggers. Actions run as the verified personal access token owner.
|
|
14
|
+
|
|
15
|
+
Follow the [GitLab setup guide](https://docs.langflow.org/next/bundles-gitlab) for personal access token setup, deployment configuration, connections, background runs, event identity, limits, and recovery. The maintained [guide source](../../../docs/docs/Components/bundles-gitlab.mdx) ships with the next-version documentation.
|
|
16
|
+
|
|
17
|
+
The [provider contract](../../../design/provider-integrations/contracts/gitlab.v1.json) records capabilities and acceptance evidence. Live tenant authorization and production delivery acceptance remain unverified.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
lfx_gitlab/__init__.py,sha256=7KFL1joUnl8LcUsAfB_Oi0JaoOeLgkLsUg-eR9yyeYs,65
|
|
2
|
+
lfx_gitlab/base.py,sha256=G8DycRfLRxqzU2BR4qtG5yagndW6qLFxUBj4nqRmjeM,4524
|
|
3
|
+
lfx_gitlab/client.py,sha256=xbWmQM9BlKMa_G7sEuWSMjvyUAHHoE_dePUrQG7Zo60,9799
|
|
4
|
+
lfx_gitlab/extension.json,sha256=ofRKzuWrsTc2un8_3j1JxuYBIn0s4q30s_P7u-1fBWg,512
|
|
5
|
+
lfx_gitlab/triggers.py,sha256=iVQdReJwgUNrFFzs1WJhc4JVT78iS9PzqtKo63Nz8DM,2149
|
|
6
|
+
lfx_gitlab/components/__init__.py,sha256=emLso-Zy9TdiRC52KtESwi7Ao8MQAV-Z7KhBG6BkK0Q,35
|
|
7
|
+
lfx_gitlab/components/gitlab/__init__.py,sha256=pzAPpinipS15G_tjHrGpqlkRJPoeq932p0wBXFb3lzo,1162
|
|
8
|
+
lfx_gitlab/components/gitlab/capabilities.v1.json,sha256=ZlSp0pcwJoPXLmCJK7CqU2ySywd5RF_Cy_NBhjJnADE,7393
|
|
9
|
+
lfx_gitlab/components/gitlab/issue_comment_create.py,sha256=KCLG4XeMHs3C_m70Q8XL3ZE0qrUnizcD0z-jNjioQyU,860
|
|
10
|
+
lfx_gitlab/components/gitlab/issue_create.py,sha256=zPZCDib2va-ja-sn3zyi2cWL9w4uwHJT7AErOFnRDPE,824
|
|
11
|
+
lfx_gitlab/components/gitlab/issue_get.py,sha256=iu9CJzKK2cOdVBWFPWYJMl_V_nNXlI3Pys8xJrONrHY,722
|
|
12
|
+
lfx_gitlab/components/gitlab/issue_trigger.py,sha256=STfzgODfeIFVAvocLR5bw8kXKRa8XSdfRcv4Bebtr9Y,427
|
|
13
|
+
lfx_gitlab/components/gitlab/issue_update.py,sha256=nq-XmsWvOl19Kipl9o6-UeNxl7LbgMA0bII0DYEi9YQ,1262
|
|
14
|
+
lfx_gitlab/components/gitlab/merge_request_comment_create.py,sha256=2hlQppfDnhM_PBnDE8UbRQoJrl5y0HrEmi2nd-DzO6Q,906
|
|
15
|
+
lfx_gitlab/components/gitlab/merge_request_create.py,sha256=mTF2-UAHUcKGZjZa246ssbkk9VHwCCpk2QSLB3sY-Ko,1064
|
|
16
|
+
lfx_gitlab/components/gitlab/merge_request_get.py,sha256=1ZO9UZO8F8z1XgThI3cb4lY_DrLYVbgFTuA8q8k_fIg,784
|
|
17
|
+
lfx_gitlab/components/gitlab/merge_request_trigger.py,sha256=HI_AsRmv2ilMIoTtUJl-GHtcYOl-1RmWinDweUYx39k,465
|
|
18
|
+
lfx_gitlab/components/gitlab/merge_request_update.py,sha256=zlQcqbE41cbum4AGAfYajsTeX8V3grJWxoLwUJNUzKU,1324
|
|
19
|
+
lfx_gitlab/components/gitlab/project_get.py,sha256=Ad_i7umjquGms7miZn8kgcTmegJMN0Vqmo9Y8gh4Kww,601
|
|
20
|
+
lfx_gitlab-0.1.0.dist-info/METADATA,sha256=eTE67_T49Tba6BAsJZSxxRleFRV4dFHuHMv05aBO6w0,978
|
|
21
|
+
lfx_gitlab-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
22
|
+
lfx_gitlab-0.1.0.dist-info/entry_points.txt,sha256=N8vyQMhI8xJkcDJZzek6KuU4h44RB143_utxcMlxRcU,146
|
|
23
|
+
lfx_gitlab-0.1.0.dist-info/RECORD,,
|