fluidattacks_core_git 12.0.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.
- fluidattacks_core/git/__init__.py +382 -0
- fluidattacks_core/git/classes.py +29 -0
- fluidattacks_core/git/clone.py +243 -0
- fluidattacks_core/git/codecommit_utils.py +129 -0
- fluidattacks_core/git/constants.py +7 -0
- fluidattacks_core/git/delete_files.py +47 -0
- fluidattacks_core/git/download_file.py +47 -0
- fluidattacks_core/git/download_repo.py +148 -0
- fluidattacks_core/git/https_utils.py +230 -0
- fluidattacks_core/git/py.typed +0 -0
- fluidattacks_core/git/remote.py +45 -0
- fluidattacks_core/git/show_file.py +408 -0
- fluidattacks_core/git/ssh_utils.py +214 -0
- fluidattacks_core/git/upload_repo.py +338 -0
- fluidattacks_core/git/utils.py +143 -0
- fluidattacks_core/git/warp.py +184 -0
- fluidattacks_core_git-12.0.0.dist-info/METADATA +22 -0
- fluidattacks_core_git-12.0.0.dist-info/RECORD +19 -0
- fluidattacks_core_git-12.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from subprocess import ( # nosec
|
|
7
|
+
SubprocessError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from git.exc import GitError
|
|
11
|
+
from git.repo import Repo
|
|
12
|
+
|
|
13
|
+
from .classes import (
|
|
14
|
+
CommitInfo,
|
|
15
|
+
InvalidParameter,
|
|
16
|
+
RebaseResult,
|
|
17
|
+
)
|
|
18
|
+
from .clone import (
|
|
19
|
+
call_codecommit_clone,
|
|
20
|
+
https_clone,
|
|
21
|
+
ssh_clone,
|
|
22
|
+
)
|
|
23
|
+
from .download_repo import (
|
|
24
|
+
download_repo_from_s3,
|
|
25
|
+
remove_symlinks_in_directory,
|
|
26
|
+
reset_repo,
|
|
27
|
+
)
|
|
28
|
+
from .https_utils import (
|
|
29
|
+
https_ls_remote,
|
|
30
|
+
)
|
|
31
|
+
from .remote import (
|
|
32
|
+
ls_remote,
|
|
33
|
+
)
|
|
34
|
+
from .show_file import (
|
|
35
|
+
show_file_at_ref,
|
|
36
|
+
show_file_at_ref_https,
|
|
37
|
+
show_file_at_ref_ssh,
|
|
38
|
+
)
|
|
39
|
+
from .ssh_utils import (
|
|
40
|
+
SSHHostValidationError,
|
|
41
|
+
ssh_ls_remote,
|
|
42
|
+
)
|
|
43
|
+
from .upload_repo import (
|
|
44
|
+
MultipartCallbacks,
|
|
45
|
+
UploadPartResult,
|
|
46
|
+
upload_repo_to_s3,
|
|
47
|
+
)
|
|
48
|
+
from .utils import run_git
|
|
49
|
+
|
|
50
|
+
LOGGER = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"CommitInfo",
|
|
54
|
+
"InvalidParameter",
|
|
55
|
+
"MultipartCallbacks",
|
|
56
|
+
"RebaseResult",
|
|
57
|
+
"SSHHostValidationError",
|
|
58
|
+
"UploadPartResult",
|
|
59
|
+
"clone",
|
|
60
|
+
"disable_quotepath",
|
|
61
|
+
"download_repo_from_s3",
|
|
62
|
+
"get_head_commit",
|
|
63
|
+
"get_last_commit_info_new",
|
|
64
|
+
"get_line_author",
|
|
65
|
+
"get_modified_filenames",
|
|
66
|
+
"https_clone",
|
|
67
|
+
"https_ls_remote",
|
|
68
|
+
"is_commit_in_branch",
|
|
69
|
+
"ls_remote",
|
|
70
|
+
"rebase",
|
|
71
|
+
"remove_symlinks_in_directory",
|
|
72
|
+
"reset_repo",
|
|
73
|
+
"show_file_at_ref",
|
|
74
|
+
"show_file_at_ref_https",
|
|
75
|
+
"show_file_at_ref_ssh",
|
|
76
|
+
"ssh_clone",
|
|
77
|
+
"ssh_ls_remote",
|
|
78
|
+
"upload_repo_to_s3",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def disable_quotepath(git_path: str) -> None:
|
|
83
|
+
try:
|
|
84
|
+
_, stderr, returncode = await run_git(
|
|
85
|
+
f"--git-dir={git_path}",
|
|
86
|
+
"config",
|
|
87
|
+
"core.quotepath",
|
|
88
|
+
"off",
|
|
89
|
+
)
|
|
90
|
+
except asyncio.exceptions.TimeoutError:
|
|
91
|
+
LOGGER.exception(
|
|
92
|
+
"Timed out disabling git quotepath",
|
|
93
|
+
extra={"extra": {"git_path": git_path}},
|
|
94
|
+
)
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
if returncode != 0:
|
|
98
|
+
LOGGER.error(
|
|
99
|
+
"Failed to disable git quotepath",
|
|
100
|
+
extra={"extra": {"git_path": git_path, "stderr": stderr.decode()}},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
async def get_last_commit_info_new(
|
|
105
|
+
repo_path: str,
|
|
106
|
+
filename: str,
|
|
107
|
+
) -> CommitInfo | None:
|
|
108
|
+
proc = await asyncio.create_subprocess_exec(
|
|
109
|
+
"git",
|
|
110
|
+
"log",
|
|
111
|
+
"--max-count",
|
|
112
|
+
"1",
|
|
113
|
+
"--format=%H%n%ce%n%cI",
|
|
114
|
+
"--",
|
|
115
|
+
filename,
|
|
116
|
+
stderr=asyncio.subprocess.PIPE,
|
|
117
|
+
stdout=asyncio.subprocess.PIPE,
|
|
118
|
+
cwd=repo_path,
|
|
119
|
+
)
|
|
120
|
+
stdout, stderr = await proc.communicate()
|
|
121
|
+
git_log = stdout.decode().splitlines()
|
|
122
|
+
|
|
123
|
+
if stderr or proc.returncode != 0 or not git_log:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
return CommitInfo(
|
|
127
|
+
hash=git_log[0],
|
|
128
|
+
author=git_log[1],
|
|
129
|
+
modified_date=datetime.fromisoformat(git_log[2]),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def get_line_author(
|
|
134
|
+
repo_path: str,
|
|
135
|
+
filename: str,
|
|
136
|
+
line: int,
|
|
137
|
+
rev: str = "HEAD",
|
|
138
|
+
) -> CommitInfo | None:
|
|
139
|
+
try:
|
|
140
|
+
proc = await asyncio.create_subprocess_exec(
|
|
141
|
+
"git",
|
|
142
|
+
"blame",
|
|
143
|
+
"-L",
|
|
144
|
+
f"{line!s},+1",
|
|
145
|
+
"-l",
|
|
146
|
+
"-p",
|
|
147
|
+
"-M",
|
|
148
|
+
"-C",
|
|
149
|
+
"-C",
|
|
150
|
+
rev,
|
|
151
|
+
"--",
|
|
152
|
+
filename,
|
|
153
|
+
stderr=asyncio.subprocess.PIPE,
|
|
154
|
+
stdout=asyncio.subprocess.PIPE,
|
|
155
|
+
cwd=repo_path,
|
|
156
|
+
)
|
|
157
|
+
stdout, stderr = await proc.communicate()
|
|
158
|
+
cmd_output = stdout.decode("utf-8", "ignore")
|
|
159
|
+
except (
|
|
160
|
+
OSError,
|
|
161
|
+
SubprocessError,
|
|
162
|
+
UnicodeDecodeError,
|
|
163
|
+
):
|
|
164
|
+
LOGGER.exception(
|
|
165
|
+
"An error occurred while getting the line author",
|
|
166
|
+
extra={
|
|
167
|
+
"extra": {
|
|
168
|
+
"repo_path": repo_path,
|
|
169
|
+
"filename": filename,
|
|
170
|
+
"line": str(line),
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
if stderr or proc.returncode != 0 or not cmd_output:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
commit_hash = cmd_output.splitlines()[0].split(" ")[0]
|
|
181
|
+
mail_search = re.search(r"author-mail <(.*?)>", cmd_output)
|
|
182
|
+
author_email = mail_search.group(1) if mail_search else ""
|
|
183
|
+
time_search = re.search(r"committer-time (\d*)", cmd_output)
|
|
184
|
+
committer_time = time_search.group(1) if time_search else "0"
|
|
185
|
+
commit_date = datetime.fromtimestamp(float(committer_time), UTC)
|
|
186
|
+
|
|
187
|
+
return CommitInfo(
|
|
188
|
+
hash=commit_hash,
|
|
189
|
+
author=author_email,
|
|
190
|
+
modified_date=commit_date,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def get_modified_filenames(repo_path: str, commit_sha: str) -> list[str]:
|
|
195
|
+
proc = await asyncio.create_subprocess_exec(
|
|
196
|
+
"git",
|
|
197
|
+
"diff",
|
|
198
|
+
"--name-only",
|
|
199
|
+
f"{commit_sha}..HEAD",
|
|
200
|
+
stderr=asyncio.subprocess.PIPE,
|
|
201
|
+
stdout=asyncio.subprocess.PIPE,
|
|
202
|
+
cwd=repo_path,
|
|
203
|
+
)
|
|
204
|
+
stdout, stderr = await proc.communicate()
|
|
205
|
+
if stderr or proc.returncode != 0:
|
|
206
|
+
return []
|
|
207
|
+
|
|
208
|
+
return stdout.decode().splitlines()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def is_commit_in_branch(
|
|
212
|
+
repo_path: str,
|
|
213
|
+
branch: str,
|
|
214
|
+
commit_sha: str,
|
|
215
|
+
) -> bool:
|
|
216
|
+
proc = await asyncio.create_subprocess_exec(
|
|
217
|
+
"git",
|
|
218
|
+
"branch",
|
|
219
|
+
"--contains",
|
|
220
|
+
f"{commit_sha}",
|
|
221
|
+
stderr=asyncio.subprocess.PIPE,
|
|
222
|
+
stdout=asyncio.subprocess.PIPE,
|
|
223
|
+
cwd=repo_path,
|
|
224
|
+
)
|
|
225
|
+
stdout, stderr = await proc.communicate()
|
|
226
|
+
if stderr or proc.returncode != 0:
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
return branch in stdout.decode()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def rebase( # noqa: PLR0913
|
|
233
|
+
repo: Repo,
|
|
234
|
+
*,
|
|
235
|
+
path: str,
|
|
236
|
+
line: int,
|
|
237
|
+
rev_a: str,
|
|
238
|
+
rev_b: str,
|
|
239
|
+
ignore_errors: bool = True,
|
|
240
|
+
) -> RebaseResult | None:
|
|
241
|
+
try:
|
|
242
|
+
result: list[str] = (
|
|
243
|
+
repo.git(c="core.quotepath=off")
|
|
244
|
+
.blame(
|
|
245
|
+
f"{rev_a}..{rev_b}",
|
|
246
|
+
"--",
|
|
247
|
+
path,
|
|
248
|
+
L=f"{line},+1",
|
|
249
|
+
l=True,
|
|
250
|
+
p=True,
|
|
251
|
+
show_number=True,
|
|
252
|
+
reverse=True,
|
|
253
|
+
show_name=True,
|
|
254
|
+
M=True,
|
|
255
|
+
C=True,
|
|
256
|
+
)
|
|
257
|
+
.splitlines()
|
|
258
|
+
)
|
|
259
|
+
except GitError:
|
|
260
|
+
if ignore_errors:
|
|
261
|
+
LOGGER.exception("A git error occurred while rebasing")
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
raise
|
|
265
|
+
|
|
266
|
+
new_rev = result[0].split(" ")[0]
|
|
267
|
+
new_line = int(result[0].split(" ")[1])
|
|
268
|
+
new_path = next(
|
|
269
|
+
(row.split(" ", maxsplit=1)[1] for row in result if row.startswith("filename ")),
|
|
270
|
+
path,
|
|
271
|
+
)
|
|
272
|
+
# With core.quotepath=off, git only wraps the path in double quotes when it
|
|
273
|
+
# C-style-escapes "unusual" characters (", \, control chars). Bare Unicode
|
|
274
|
+
# paths (CJK, Cyrillic, ...) come through as proper Python strings and must
|
|
275
|
+
# skip the unicode-escape pipeline, which would fail .encode("latin-1") on
|
|
276
|
+
# codepoints above U+00FF.
|
|
277
|
+
if new_path.startswith('"'):
|
|
278
|
+
try:
|
|
279
|
+
decoded_bytes = new_path.encode("latin-1").decode("unicode-escape").encode("latin-1")
|
|
280
|
+
except (UnicodeDecodeError, UnicodeEncodeError):
|
|
281
|
+
if ignore_errors:
|
|
282
|
+
LOGGER.exception(
|
|
283
|
+
"Error decoding the new path",
|
|
284
|
+
extra={
|
|
285
|
+
"extra": {
|
|
286
|
+
"path": path,
|
|
287
|
+
"new_path": new_path,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
)
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
raise
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
new_path = decoded_bytes.decode("utf-8").strip('"')
|
|
297
|
+
except UnicodeDecodeError:
|
|
298
|
+
# Filesystem locales may emit non-UTF-8 (e.g. Latin-1) path bytes
|
|
299
|
+
new_path = decoded_bytes.decode("latin-1").strip('"')
|
|
300
|
+
|
|
301
|
+
return RebaseResult(path=new_path, line=new_line, rev=new_rev)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def get_head_commit(path_to_repo: Path, branch: str) -> str | None:
|
|
305
|
+
try:
|
|
306
|
+
return (
|
|
307
|
+
Repo(path_to_repo.resolve(), search_parent_directories=True).heads[branch].object.hexsha
|
|
308
|
+
)
|
|
309
|
+
except (GitError, AttributeError, IndexError):
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
async def clone( # noqa: PLR0913
|
|
314
|
+
repo_url: str,
|
|
315
|
+
repo_branch: str,
|
|
316
|
+
*,
|
|
317
|
+
temp_dir: str,
|
|
318
|
+
credential_key: str | None = None,
|
|
319
|
+
user: str | None = None,
|
|
320
|
+
password: str | None = None,
|
|
321
|
+
token: str | None = None,
|
|
322
|
+
provider: str | None = None,
|
|
323
|
+
is_pat: bool = False,
|
|
324
|
+
arn: str | None = None,
|
|
325
|
+
org_external_id: str | None = None,
|
|
326
|
+
follow_redirects: bool = False,
|
|
327
|
+
mirror: bool = False,
|
|
328
|
+
) -> tuple[str | None, str | None]:
|
|
329
|
+
if credential_key:
|
|
330
|
+
return await ssh_clone(
|
|
331
|
+
branch=repo_branch,
|
|
332
|
+
credential_key=credential_key,
|
|
333
|
+
repo_url=repo_url,
|
|
334
|
+
temp_dir=temp_dir,
|
|
335
|
+
mirror=mirror,
|
|
336
|
+
)
|
|
337
|
+
if user is not None and password is not None:
|
|
338
|
+
return await https_clone(
|
|
339
|
+
branch=repo_branch,
|
|
340
|
+
password=password,
|
|
341
|
+
repo_url=repo_url,
|
|
342
|
+
temp_dir=temp_dir,
|
|
343
|
+
token=None,
|
|
344
|
+
user=user,
|
|
345
|
+
follow_redirects=follow_redirects,
|
|
346
|
+
mirror=mirror,
|
|
347
|
+
)
|
|
348
|
+
if token is not None:
|
|
349
|
+
return await https_clone(
|
|
350
|
+
branch=repo_branch,
|
|
351
|
+
password=None,
|
|
352
|
+
repo_url=repo_url,
|
|
353
|
+
temp_dir=temp_dir,
|
|
354
|
+
token=token,
|
|
355
|
+
user=None,
|
|
356
|
+
provider=provider,
|
|
357
|
+
is_pat=is_pat,
|
|
358
|
+
follow_redirects=follow_redirects,
|
|
359
|
+
mirror=mirror,
|
|
360
|
+
)
|
|
361
|
+
if arn is not None and org_external_id is not None:
|
|
362
|
+
return await call_codecommit_clone(
|
|
363
|
+
branch=repo_branch,
|
|
364
|
+
repo_url=repo_url,
|
|
365
|
+
temp_dir=temp_dir,
|
|
366
|
+
arn=arn,
|
|
367
|
+
org_external_id=org_external_id,
|
|
368
|
+
follow_redirects=follow_redirects,
|
|
369
|
+
mirror=mirror,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if repo_url.startswith("http"):
|
|
373
|
+
# it can be a public repository
|
|
374
|
+
return await https_clone(
|
|
375
|
+
branch=repo_branch,
|
|
376
|
+
repo_url=repo_url,
|
|
377
|
+
temp_dir=temp_dir,
|
|
378
|
+
follow_redirects=follow_redirects,
|
|
379
|
+
mirror=mirror,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
raise InvalidParameter
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from datetime import (
|
|
2
|
+
datetime,
|
|
3
|
+
)
|
|
4
|
+
from typing import (
|
|
5
|
+
NamedTuple,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CommitInfo(NamedTuple):
|
|
10
|
+
hash: str
|
|
11
|
+
author: str
|
|
12
|
+
modified_date: datetime
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RebaseResult(NamedTuple):
|
|
16
|
+
path: str
|
|
17
|
+
line: int
|
|
18
|
+
rev: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InvalidParameter(Exception): # noqa: N818
|
|
22
|
+
"""Exception to control empty required parameters."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, field: str = "") -> None:
|
|
25
|
+
if field:
|
|
26
|
+
msg = f"Exception - Field {field} is invalid"
|
|
27
|
+
else:
|
|
28
|
+
msg = "Exception - Error value is not valid"
|
|
29
|
+
super().__init__(msg)
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
import boto3
|
|
8
|
+
from botocore.exceptions import ClientError
|
|
9
|
+
|
|
10
|
+
from .codecommit_utils import (
|
|
11
|
+
extract_region,
|
|
12
|
+
)
|
|
13
|
+
from .ssh_utils import (
|
|
14
|
+
parse_ssh_url,
|
|
15
|
+
ssh_git_env_context,
|
|
16
|
+
)
|
|
17
|
+
from .utils import (
|
|
18
|
+
format_url,
|
|
19
|
+
get_https_git_config_args,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
LOGGER = logging.getLogger(__name__)
|
|
23
|
+
MSG = "Repo cloning failed"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def ssh_clone(
|
|
27
|
+
*,
|
|
28
|
+
branch: str,
|
|
29
|
+
credential_key: str,
|
|
30
|
+
repo_url: str,
|
|
31
|
+
temp_dir: str,
|
|
32
|
+
mirror: bool = False,
|
|
33
|
+
) -> tuple[str | None, str | None]:
|
|
34
|
+
parsed_repo_url = parse_ssh_url(repo_url)
|
|
35
|
+
folder_to_clone_root = f"{temp_dir}/{uuid.uuid4()}"
|
|
36
|
+
with ssh_git_env_context(temp_dir, credential_key) as env:
|
|
37
|
+
try:
|
|
38
|
+
proc = await asyncio.create_subprocess_exec(
|
|
39
|
+
"git",
|
|
40
|
+
"-c",
|
|
41
|
+
"core.symlinks=false",
|
|
42
|
+
"clone",
|
|
43
|
+
*(
|
|
44
|
+
["--mirror"]
|
|
45
|
+
if mirror
|
|
46
|
+
else [
|
|
47
|
+
"--branch",
|
|
48
|
+
branch,
|
|
49
|
+
"--single-branch",
|
|
50
|
+
]
|
|
51
|
+
),
|
|
52
|
+
"--",
|
|
53
|
+
parsed_repo_url,
|
|
54
|
+
folder_to_clone_root,
|
|
55
|
+
stderr=asyncio.subprocess.PIPE,
|
|
56
|
+
stdout=asyncio.subprocess.PIPE,
|
|
57
|
+
env=env,
|
|
58
|
+
cwd=temp_dir,
|
|
59
|
+
)
|
|
60
|
+
_, stderr = await proc.communicate()
|
|
61
|
+
except OSError as ex:
|
|
62
|
+
LOGGER.exception(MSG, extra={"extra": {"branch": branch, "repo": repo_url}})
|
|
63
|
+
return None, str(ex)
|
|
64
|
+
|
|
65
|
+
if mirror and proc.returncode == 0:
|
|
66
|
+
with open(f"{folder_to_clone_root}/.info.json", "w") as f: # noqa: ASYNC230,PTH123
|
|
67
|
+
json.dump({"fluid_branch": branch, "repo": repo_url}, f)
|
|
68
|
+
if proc.returncode == 0:
|
|
69
|
+
return (folder_to_clone_root, None)
|
|
70
|
+
|
|
71
|
+
LOGGER.error(MSG, extra={"extra": {"message": stderr.decode()}})
|
|
72
|
+
|
|
73
|
+
return (None, stderr.decode("utf-8"))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def https_clone( # noqa: PLR0913
|
|
77
|
+
*,
|
|
78
|
+
branch: str,
|
|
79
|
+
repo_url: str,
|
|
80
|
+
temp_dir: str,
|
|
81
|
+
password: str | None = None,
|
|
82
|
+
token: str | None = None,
|
|
83
|
+
user: str | None = None,
|
|
84
|
+
provider: str | None = None,
|
|
85
|
+
is_pat: bool = False,
|
|
86
|
+
follow_redirects: bool = False,
|
|
87
|
+
mirror: bool = False,
|
|
88
|
+
) -> tuple[str | None, str | None]:
|
|
89
|
+
url = format_url(
|
|
90
|
+
repo_url=repo_url,
|
|
91
|
+
user=user,
|
|
92
|
+
password=password,
|
|
93
|
+
token=token,
|
|
94
|
+
provider=provider,
|
|
95
|
+
is_pat=is_pat,
|
|
96
|
+
)
|
|
97
|
+
config_args = get_https_git_config_args(
|
|
98
|
+
follow_redirects=follow_redirects,
|
|
99
|
+
is_pat=is_pat,
|
|
100
|
+
token=token,
|
|
101
|
+
)
|
|
102
|
+
folder_to_clone_root = f"{temp_dir}/{uuid.uuid4()}"
|
|
103
|
+
try:
|
|
104
|
+
proc = await asyncio.create_subprocess_exec(
|
|
105
|
+
"git",
|
|
106
|
+
*config_args,
|
|
107
|
+
"clone",
|
|
108
|
+
*(
|
|
109
|
+
["--mirror"]
|
|
110
|
+
if mirror
|
|
111
|
+
else [
|
|
112
|
+
"--branch",
|
|
113
|
+
branch,
|
|
114
|
+
"--single-branch",
|
|
115
|
+
]
|
|
116
|
+
),
|
|
117
|
+
"--",
|
|
118
|
+
url,
|
|
119
|
+
folder_to_clone_root,
|
|
120
|
+
stderr=asyncio.subprocess.PIPE,
|
|
121
|
+
stdout=asyncio.subprocess.PIPE,
|
|
122
|
+
cwd=temp_dir,
|
|
123
|
+
)
|
|
124
|
+
_, stderr = await proc.communicate()
|
|
125
|
+
except OSError as ex:
|
|
126
|
+
LOGGER.exception(MSG, extra={"extra": {"branch": branch, "repo": repo_url}})
|
|
127
|
+
|
|
128
|
+
return None, str(ex)
|
|
129
|
+
|
|
130
|
+
if mirror and proc.returncode == 0:
|
|
131
|
+
with open(f"{folder_to_clone_root}/.info.json", "w") as f: # noqa: ASYNC230,PTH123
|
|
132
|
+
json.dump({"fluid_branch": branch, "repo": repo_url}, f)
|
|
133
|
+
|
|
134
|
+
if proc.returncode == 0:
|
|
135
|
+
return (folder_to_clone_root, None)
|
|
136
|
+
|
|
137
|
+
LOGGER.error(MSG, extra={"extra": {"message": stderr.decode()}})
|
|
138
|
+
|
|
139
|
+
return (None, stderr.decode("utf-8"))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def codecommit_clone( # noqa: PLR0913
|
|
143
|
+
*,
|
|
144
|
+
env: dict[str, str],
|
|
145
|
+
branch: str,
|
|
146
|
+
repo_url: str,
|
|
147
|
+
temp_dir: str,
|
|
148
|
+
mirror: bool = False,
|
|
149
|
+
follow_redirects: bool = False,
|
|
150
|
+
) -> tuple[str | None, str | None]:
|
|
151
|
+
folder_to_clone_root = f"{temp_dir}/{uuid.uuid4()}"
|
|
152
|
+
try:
|
|
153
|
+
proc = await asyncio.create_subprocess_exec(
|
|
154
|
+
"git",
|
|
155
|
+
"-c",
|
|
156
|
+
"core.symlinks=false",
|
|
157
|
+
"-c",
|
|
158
|
+
"http.sslVerify=false",
|
|
159
|
+
"-c",
|
|
160
|
+
f"http.followRedirects={follow_redirects}",
|
|
161
|
+
"clone",
|
|
162
|
+
*(
|
|
163
|
+
["--mirror"]
|
|
164
|
+
if mirror
|
|
165
|
+
else [
|
|
166
|
+
"--branch",
|
|
167
|
+
branch,
|
|
168
|
+
"--single-branch",
|
|
169
|
+
]
|
|
170
|
+
),
|
|
171
|
+
"--",
|
|
172
|
+
repo_url,
|
|
173
|
+
folder_to_clone_root,
|
|
174
|
+
cwd=temp_dir,
|
|
175
|
+
env={**os.environ.copy(), **env},
|
|
176
|
+
stderr=asyncio.subprocess.PIPE,
|
|
177
|
+
stdout=asyncio.subprocess.PIPE,
|
|
178
|
+
)
|
|
179
|
+
_, stderr = await proc.communicate()
|
|
180
|
+
except OSError as ex:
|
|
181
|
+
LOGGER.exception(MSG, extra={"extra": {"branch": branch, "repo": repo_url}})
|
|
182
|
+
|
|
183
|
+
return None, str(ex)
|
|
184
|
+
|
|
185
|
+
if mirror and proc.returncode == 0:
|
|
186
|
+
with open(f"{folder_to_clone_root}/.info.json", "w") as f: # noqa: ASYNC230, PTH123
|
|
187
|
+
json.dump({"fluid_branch": branch, "repo": repo_url}, f)
|
|
188
|
+
|
|
189
|
+
if proc.returncode == 0:
|
|
190
|
+
return (folder_to_clone_root, None)
|
|
191
|
+
|
|
192
|
+
LOGGER.error(MSG, extra={"extra": {"message": stderr.decode()}})
|
|
193
|
+
|
|
194
|
+
return (None, stderr.decode("utf-8"))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
async def call_codecommit_clone( # noqa: PLR0913
|
|
198
|
+
*,
|
|
199
|
+
branch: str,
|
|
200
|
+
repo_url: str,
|
|
201
|
+
temp_dir: str,
|
|
202
|
+
arn: str,
|
|
203
|
+
org_external_id: str,
|
|
204
|
+
follow_redirects: bool = False,
|
|
205
|
+
mirror: bool = False,
|
|
206
|
+
) -> tuple[str | None, str | None]:
|
|
207
|
+
try:
|
|
208
|
+
sts_client = boto3.client("sts")
|
|
209
|
+
assumed_role = sts_client.assume_role(
|
|
210
|
+
RoleArn=arn,
|
|
211
|
+
RoleSessionName=f"session-{uuid.uuid4()}",
|
|
212
|
+
ExternalId=org_external_id,
|
|
213
|
+
)
|
|
214
|
+
credentials = assumed_role["Credentials"]
|
|
215
|
+
|
|
216
|
+
return await codecommit_clone(
|
|
217
|
+
env={
|
|
218
|
+
"AWS_ACCESS_KEY_ID": credentials["AccessKeyId"],
|
|
219
|
+
"AWS_SECRET_ACCESS_KEY": credentials["SecretAccessKey"],
|
|
220
|
+
"AWS_SESSION_TOKEN": credentials["SessionToken"],
|
|
221
|
+
"AWS_DEFAULT_REGION": extract_region(repo_url),
|
|
222
|
+
},
|
|
223
|
+
branch=branch,
|
|
224
|
+
repo_url=repo_url,
|
|
225
|
+
temp_dir=temp_dir,
|
|
226
|
+
follow_redirects=follow_redirects,
|
|
227
|
+
mirror=mirror,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
except ClientError as exc:
|
|
231
|
+
LOGGER.exception(
|
|
232
|
+
MSG,
|
|
233
|
+
extra={
|
|
234
|
+
"extra": {
|
|
235
|
+
"repo_url": repo_url,
|
|
236
|
+
"arn": arn,
|
|
237
|
+
"org_external_id": org_external_id,
|
|
238
|
+
"exc": exc,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return None, str(exc)
|