Skip to content

Commit 9dd6a2d

Browse files
authored
fix(service): fix working dir when cloning outside of project_clone view (#3164)
* Fix multiple projects in cache issue
1 parent 85f7a9e commit 9dd6a2d

File tree

11 files changed

+53
-42
lines changed

11 files changed

+53
-42
lines changed

renku/core/util/contexts.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,13 @@ def measure(message="TOTAL"):
108108

109109

110110
@contextlib.contextmanager
111-
def renku_project_context(path):
111+
def renku_project_context(path, check_git_path=True):
112112
"""Provide a project context with repo path injected."""
113113
from renku.core.util.git import get_git_path
114114
from renku.domain_model.project_context import project_context
115115

116-
path = get_git_path(path)
116+
if check_git_path:
117+
path = get_git_path(path)
117118

118119
with project_context.with_path(path=path), chdir(path):
119120
project_context.external_storage_requested = True

renku/ui/service/cache/models/project.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class Project(Model):
5858
@property
5959
def abs_path(self):
6060
"""Full path of cached project."""
61-
return CACHE_PROJECTS_PATH / self.user_id / self.project_id / self.owner / self.slug
61+
return CACHE_PROJECTS_PATH / self.user_id / self.owner / self.slug
6262

6363
def read_lock(self, timeout: Optional[float] = None):
6464
"""Shared read lock on the project."""

renku/ui/service/cache/projects.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ class ProjectManagementCache(BaseCache):
3030

3131
project_schema = ProjectSchema()
3232

33-
def make_project(self, user, project_data):
33+
def make_project(self, user, project_data, persist=True):
3434
"""Store user project metadata."""
3535
project_data.update({"user_id": user.user_id})
3636

3737
project_obj = self.project_schema.load(project_data, unknown=EXCLUDE)
38-
project_obj.save()
38+
39+
if persist:
40+
project_obj.save()
3941

4042
return project_obj
4143

renku/ui/service/controllers/utils/project_clone.py

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
# limitations under the License.
1818
"""Utilities for renku service controllers."""
1919
from renku.command.clone import project_clone_command
20-
from renku.core.errors import GitCommandError
20+
from renku.core.util.contexts import renku_project_context
21+
from renku.ui.service.cache.models.project import Project
2122
from renku.ui.service.logger import service_log
2223
from renku.ui.service.views.decorators import requires_cache
2324

@@ -29,33 +30,45 @@ def user_project_clone(cache, user_data, project_data):
2930
project_data.pop("project_id")
3031

3132
user = cache.ensure_user(user_data)
32-
project = cache.make_project(user, project_data)
33+
project = cache.make_project(user, project_data, persist=False)
3334
project.abs_path.mkdir(parents=True, exist_ok=True)
3435

35-
try:
36-
with project.write_lock():
37-
repo, project.initialized = (
38-
project_clone_command()
39-
.build()
40-
.execute(
41-
project_data["url_with_auth"],
42-
path=project.abs_path,
43-
depth=project_data["depth"],
44-
raise_git_except=True,
45-
config={
46-
"user.name": project_data["fullname"],
47-
"user.email": project_data["email"],
48-
"pull.rebase": False,
49-
},
50-
checkout_revision=project_data["ref"],
36+
with project.write_lock(), renku_project_context(project.abs_path, check_git_path=False):
37+
git_url = project_data.get("git_url")
38+
39+
if git_url is not None:
40+
try:
41+
# NOTE: If two requests ran at the same time, by the time we acquire the lock a project might already
42+
# be cloned by an earlier request.
43+
found_project = Project.get(
44+
(Project.user_id == user_data["user_id"])
45+
& (Project.git_url == git_url)
46+
& (Project.project_id != project.project_id)
5147
)
52-
).output
53-
project.save()
54-
except GitCommandError as e:
55-
cache.invalidate_project(user, project.project_id)
56-
raise e
48+
except ValueError:
49+
pass
50+
else:
51+
service_log.debug(f"project already cloned, skipping clone: {git_url}")
52+
return found_project
53+
54+
repo, project.initialized = (
55+
project_clone_command()
56+
.build()
57+
.execute(
58+
project_data["url_with_auth"],
59+
path=project.abs_path,
60+
depth=project_data["depth"],
61+
raise_git_except=True,
62+
config={
63+
"user.name": project_data["fullname"],
64+
"user.email": project_data["email"],
65+
"pull.rebase": False,
66+
},
67+
checkout_revision=project_data["ref"],
68+
)
69+
).output
70+
project.save()
5771

5872
service_log.debug(f"project successfully cloned: {repo}")
59-
service_log.debug(f"project folder exists: {project.exists()}")
6073

6174
return project

renku/ui/service/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ class IntermittentProjectIdError(ServiceError):
632632
"""
633633

634634
code = SVC_ERROR_INTERMITTENT + 1
635-
userMessage = "An unexpcted error occurred. This may be a temporary problem. Please try again in a few minutes."
635+
userMessage = "An unexpected error occurred. This may be a temporary problem. Please try again in a few minutes."
636636
devMessage = (
637637
"Project id cannot be found. It may be a temporary problem. Check the Sentry exception for further details."
638638
)

renku/ui/service/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def make_project_path(user, project):
2727
valid_project = project and "owner" in project and "name" in project and "project_id" in project
2828

2929
if valid_user and valid_project:
30-
return CACHE_PROJECTS_PATH / user["user_id"] / project["project_id"] / project["owner"] / project["slug"]
30+
return CACHE_PROJECTS_PATH / user["user_id"] / project["owner"] / project["slug"]
3131

3232

3333
def make_file_path(user, cached_file):

renku/ui/service/views/error_handlers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def decorated_function(*args, **kwargs):
197197
except GitError as e:
198198
return error_response(ProgramGitError(e, e.message if e.message else None))
199199
except (Exception, BaseException, OSError, IOError) as e:
200-
if hasattr(e, "stderr"):
200+
if hasattr(e, "stderr") and e.stderr:
201201
error_message = " ".join(e.stderr.strip().split("\n"))
202202
else:
203203
error_message = str(e)

tests/service/controllers/utils/test_project_clone.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_service_user_project_clone(svc_client_cache):
7070
assert project_two.exists()
7171

7272
new_path = project_two.abs_path
73-
assert old_path != new_path
73+
assert old_path == new_path
7474
user = cache.get_user(user_data["user_id"])
7575
projects = [project.project_id for project in cache.get_projects(user)]
7676
assert project_one.project_id in projects

tests/service/views/test_cache_views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,10 @@ def test_clone_projects_multiple(svc_client, identity_headers, it_remote_repo_ur
514514

515515
pids = [p["project_id"] for p in response.json["result"]["projects"]]
516516
assert last_pid in pids
517+
assert 1 == len(pids)
517518

518519
for inserted in project_ids:
519-
assert inserted["project_id"] not in pids
520+
assert inserted["project_id"] == last_pid
520521

521522

522523
@pytest.mark.service

tests/service/views/test_dataset_views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def test_create_dataset_wrong_ref_view(svc_client_with_repo):
160160

161161
response = svc_client.post("/datasets.create", data=json.dumps(payload), headers=headers)
162162
assert_rpc_response(response, "error")
163-
assert IntermittentProjectIdError.code == response.json["error"]["code"]
163+
assert IntermittentProjectIdError.code == response.json["error"]["code"], response.json
164164

165165

166166
@pytest.mark.service

0 commit comments

Comments
 (0)