2
2
Core set of steps for specifying a Prefect project pull step.
3
3
"""
4
4
import os
5
- import subprocess
6
- import sys
7
- import urllib .parse
5
+ from pathlib import Path
8
6
from typing import Optional
9
7
10
8
from prefect ._internal .compatibility .deprecated import deprecated_callable
11
9
from prefect .blocks .core import Block
12
10
from prefect .logging .loggers import get_logger
11
+ from prefect .runner .storage import GitRepository
12
+ from prefect .utilities .asyncutils import sync_compatible
13
13
14
14
deployment_logger = get_logger ("deployment" )
15
15
@@ -29,90 +29,8 @@ def set_working_directory(directory: str) -> dict:
29
29
return dict (directory = directory )
30
30
31
31
32
- def _format_token_from_access_token (netloc : str , access_token : str ) -> str :
33
- if "bitbucketserver" in netloc :
34
- if ":" not in access_token :
35
- raise ValueError (
36
- "Please prefix your BitBucket Server access_token with a username,"
37
- " e.g. 'username:token'."
38
- )
39
- # If they pass a header themselves, we can use it as-is
40
- return access_token
41
-
42
- elif "bitbucket" in netloc :
43
- return (
44
- access_token
45
- if (access_token .startswith ("x-token-auth:" ) or ":" in access_token )
46
- else f"x-token-auth:{ access_token } "
47
- )
48
-
49
- elif "gitlab" in netloc :
50
- return (
51
- f"oauth2:{ access_token } "
52
- if not access_token .startswith ("oauth2:" )
53
- else access_token
54
- )
55
-
56
- # all other cases (GitHub, etc.)
57
- return access_token
58
-
59
-
60
- def _format_token_from_credentials (netloc : str , credentials : dict ) -> str :
61
- """
62
- Formats the credentials block for the git provider.
63
-
64
- BitBucket supports the following syntax:
65
- git clone "https://x-token-auth:{token}@bitbucket.org/yourRepoOwnerHere/RepoNameHere"
66
- git clone https://username:<token>@bitbucketserver.com/scm/projectname/teamsinspace.git
67
- """
68
- username = credentials .get ("username" ) if credentials else None
69
- password = credentials .get ("password" ) if credentials else None
70
- token = credentials .get ("token" ) if credentials else None
71
-
72
- user_provided_token = token or password
73
-
74
- if not user_provided_token :
75
- raise ValueError (
76
- "Please provide a `token` or `password` in your Credentials block to clone"
77
- " a repo."
78
- )
79
-
80
- if "bitbucketserver" in netloc :
81
- # If they pass a BitBucketCredentials block and we don't have both a username and at
82
- # least one of a password or token and they don't provide a header themselves,
83
- # we can raise the appropriate error to avoid the wrong format for BitBucket Server.
84
- if not username and ":" not in user_provided_token :
85
- raise ValueError (
86
- "Please provide a `username` and a `password` or `token` in your"
87
- " BitBucketCredentials block to clone a repo from BitBucket Server."
88
- )
89
- # if username or if no username but it's provided in the token
90
- return (
91
- f"{ username } :{ user_provided_token } "
92
- if username and username not in user_provided_token
93
- else user_provided_token
94
- )
95
-
96
- elif "bitbucket" in netloc :
97
- return (
98
- user_provided_token
99
- if user_provided_token .startswith ("x-token-auth:" )
100
- or ":" in user_provided_token
101
- else f"x-token-auth:{ user_provided_token } "
102
- )
103
-
104
- elif "gitlab" in netloc :
105
- return (
106
- f"oauth2:{ user_provided_token } "
107
- if not user_provided_token .startswith ("oauth2:" )
108
- else user_provided_token
109
- )
110
-
111
- # all other cases (GitHub, etc.)
112
- return user_provided_token
113
-
114
-
115
- def git_clone (
32
+ @sync_compatible
33
+ async def git_clone (
116
34
repository : str ,
117
35
branch : Optional [str ] = None ,
118
36
include_submodules : bool = False ,
@@ -123,13 +41,13 @@ def git_clone(
123
41
Clones a git repository into the current working directory.
124
42
125
43
Args:
126
- repository (str) : the URL of the repository to clone
127
- branch (str, optional) : the branch to clone; if not provided, the default branch will be used
44
+ repository: the URL of the repository to clone
45
+ branch: the branch to clone; if not provided, the default branch will be used
128
46
include_submodules (bool): whether to include git submodules when cloning the repository
129
- access_token (str, optional) : an access token to use for cloning the repository; if not provided
47
+ access_token: an access token to use for cloning the repository; if not provided
130
48
the repository will be cloned using the default git credentials
131
- credentials (optional) : a GitHubCredentials, GitLabCredentials, or BitBucketCredentials block can be used to specify the
132
- credentials to use for cloning the repository.
49
+ credentials: a GitHubCredentials, GitLabCredentials, or BitBucketCredentials block can be used to specify the
50
+ credentials to use for cloning the repository.
133
51
134
52
Returns:
135
53
dict: a dictionary containing a `directory` key of the new directory that was created
@@ -193,60 +111,32 @@ def git_clone(
193
111
"Please provide either an access token or credentials but not both."
194
112
)
195
113
196
- url_components = urllib . parse . urlparse ( repository )
114
+ credentials = { "access_token" : access_token } if access_token else credentials
197
115
198
- if access_token :
199
- access_token = _format_token_from_access_token (
200
- url_components .netloc , access_token
201
- )
202
- if credentials :
203
- access_token = _format_token_from_credentials (
204
- url_components .netloc , credentials
205
- )
116
+ storage = GitRepository (
117
+ url = repository ,
118
+ credentials = credentials ,
119
+ branch = branch ,
120
+ include_submodules = include_submodules ,
121
+ )
206
122
207
- if url_components .scheme == "https" and access_token is not None :
208
- updated_components = url_components ._replace (
209
- netloc = f"{ access_token } @{ url_components .netloc } "
210
- )
211
- repository_url = urllib .parse .urlunparse (updated_components )
212
- else :
213
- repository_url = repository
214
-
215
- cmd = ["git" , "clone" , repository_url ]
216
- if branch :
217
- cmd += ["-b" , branch ]
218
- if include_submodules :
219
- cmd += ["--recurse-submodules" ]
220
-
221
- # Limit git history
222
- cmd += ["--depth" , "1" ]
223
-
224
- try :
225
- subprocess .check_call (
226
- cmd , shell = sys .platform == "win32" , stderr = sys .stderr , stdout = sys .stdout
227
- )
228
- except subprocess .CalledProcessError as exc :
229
- # Hide the command used to avoid leaking the access token
230
- exc_chain = None if access_token else exc
231
- raise RuntimeError (
232
- f"Failed to clone repository { repository !r} with exit code"
233
- f" { exc .returncode } ."
234
- ) from exc_chain
235
-
236
- directory = "/" .join (repository .strip ().split ("/" )[- 1 :]).replace (".git" , "" )
123
+ await storage .pull_code ()
124
+
125
+ directory = str (storage .destination .relative_to (Path .cwd ()))
237
126
deployment_logger .info (f"Cloned repository { repository !r} into { directory !r} " )
238
127
return {"directory" : directory }
239
128
240
129
241
130
@deprecated_callable (start_date = "Jun 2023" , help = "Use 'git clone' instead." )
242
- def git_clone_project (
131
+ @sync_compatible
132
+ async def git_clone_project (
243
133
repository : str ,
244
134
branch : Optional [str ] = None ,
245
135
include_submodules : bool = False ,
246
136
access_token : Optional [str ] = None ,
247
137
) -> dict :
248
138
"""Deprecated. Use `git_clone` instead."""
249
- return git_clone (
139
+ return await git_clone (
250
140
repository = repository ,
251
141
branch = branch ,
252
142
include_submodules = include_submodules ,
0 commit comments