Skip to content

Commit e98f112

Browse files
authored
fix(core): migrate Dockerfile after metadata (#3690)
* fix(core): migrate Dockerfile after metadata
1 parent 33a29aa commit e98f112

File tree

8 files changed

+136
-34
lines changed

8 files changed

+136
-34
lines changed

renku/core/migration/migrate.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,42 @@ def migrate_project(
123123
if not is_renku_project():
124124
return False, template_updated, docker_updated
125125

126+
n_migrations_executed = 0
127+
128+
if not skip_migrations:
129+
project_version = project_version or get_project_version()
130+
131+
migration_context = MigrationContext(
132+
strict=strict, type=migration_type, preserve_identifiers=preserve_identifiers
133+
)
134+
135+
version = 1
136+
for version, path in get_migrations():
137+
if max_version and version > max_version:
138+
break
139+
if version > project_version:
140+
module = importlib.import_module(path)
141+
module_name = module.__name__.split(".")[-1]
142+
communication.echo(f"Applying migration {module_name}...")
143+
try:
144+
module.migrate(migration_context)
145+
except (Exception, BaseException) as e:
146+
raise MigrationError("Couldn't execute migration") from e
147+
n_migrations_executed += 1
148+
149+
if not is_using_temporary_datasets_path():
150+
if n_migrations_executed > 0:
151+
project_context.project.version = str(version)
152+
project_gateway.update_project(project_context.project)
153+
154+
communication.echo(f"Successfully applied {n_migrations_executed} migrations.")
155+
156+
_remove_untracked_renku_files(metadata_path=project_context.metadata_path)
157+
158+
# we might not have been able to tell if a docker update is possible due to outstanding migrations.
159+
# so we need to check again here.
160+
skip_docker_update |= not is_docker_update_possible()
161+
126162
try:
127163
project = project_context.project
128164
except ValueError:
@@ -155,36 +191,6 @@ def migrate_project(
155191
except Exception as e:
156192
raise DockerfileUpdateError("Couldn't update renku version in Dockerfile.") from e
157193

158-
if skip_migrations:
159-
return False, template_updated, docker_updated
160-
161-
project_version = project_version or get_project_version()
162-
n_migrations_executed = 0
163-
164-
migration_context = MigrationContext(strict=strict, type=migration_type, preserve_identifiers=preserve_identifiers)
165-
166-
version = 1
167-
for version, path in get_migrations():
168-
if max_version and version > max_version:
169-
break
170-
if version > project_version:
171-
module = importlib.import_module(path)
172-
module_name = module.__name__.split(".")[-1]
173-
communication.echo(f"Applying migration {module_name}...")
174-
try:
175-
module.migrate(migration_context)
176-
except (Exception, BaseException) as e:
177-
raise MigrationError("Couldn't execute migration") from e
178-
n_migrations_executed += 1
179-
if not is_using_temporary_datasets_path():
180-
if n_migrations_executed > 0:
181-
project_context.project.version = str(version)
182-
project_gateway.update_project(project_context.project)
183-
184-
communication.echo(f"Successfully applied {n_migrations_executed} migrations.")
185-
186-
_remove_untracked_renku_files(metadata_path=project_context.metadata_path)
187-
188194
return n_migrations_executed != 0, template_updated, docker_updated
189195

190196

renku/ui/cli/migrate.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def migrate(check, skip_template_update, skip_docker_update, strict, preserve_id
9898

9999
template_update_possible = status & TEMPLATE_UPDATE_POSSIBLE and status & AUTOMATED_TEMPLATE_UPDATE_SUPPORTED
100100
docker_update_possible = status & DOCKERFILE_UPDATE_POSSIBLE
101+
migration_required = status & MIGRATION_REQUIRED
101102

102103
if check:
103104
if template_update_possible:
@@ -113,7 +114,7 @@ def migrate(check, skip_template_update, skip_docker_update, strict, preserve_id
113114
+ "using 'renku migrate'."
114115
)
115116

116-
if status & MIGRATION_REQUIRED:
117+
if migration_required:
117118
raise MigrationRequired
118119

119120
if status & UNSUPPORTED_PROJECT:
@@ -125,7 +126,9 @@ def migrate(check, skip_template_update, skip_docker_update, strict, preserve_id
125126
if check:
126127
return
127128

128-
skip_docker_update = skip_docker_update or not docker_update_possible
129+
# In case where a migration is required, we can't tell if a dockerupdate is possible, as the metadata for deciding
130+
# might not be there yet. We'll check this again after the migration
131+
skip_docker_update = skip_docker_update or (not migration_required and not docker_update_possible)
129132
skip_template_update = skip_template_update or not template_update_possible
130133

131134
communicator = ClickCallback()

renku/ui/service/controllers/cache_migrate_project.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def execute_migration(
3333
from renku.command.migrate import (
3434
AUTOMATED_TEMPLATE_UPDATE_SUPPORTED,
3535
DOCKERFILE_UPDATE_POSSIBLE,
36+
MIGRATION_REQUIRED,
3637
TEMPLATE_UPDATE_POSSIBLE,
3738
check_project,
3839
migrate_project_command,
@@ -47,8 +48,9 @@ def execute_migration(
4748

4849
template_update_possible = status & TEMPLATE_UPDATE_POSSIBLE and status & AUTOMATED_TEMPLATE_UPDATE_SUPPORTED
4950
docker_update_possible = status & DOCKERFILE_UPDATE_POSSIBLE
51+
migration_required = status & MIGRATION_REQUIRED
5052

51-
skip_docker_update = skip_docker_update or not docker_update_possible
53+
skip_docker_update = skip_docker_update or (not migration_required and not docker_update_possible)
5254
skip_template_update = skip_template_update or not template_update_possible
5355

5456
result = (

tests/cli/test_migrate.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,27 @@ def test_migrate_project(isolated_runner, old_project, with_injection):
5353
assert project_context.project.name
5454

5555

56+
@pytest.mark.migration
57+
@pytest.mark.parametrize(
58+
"old_project",
59+
[
60+
"v9-migration-docker-version-change.git",
61+
"v9-migration-docker-mult-changes.git",
62+
],
63+
indirect=["old_project"],
64+
)
65+
def test_migrate_old_project_with_docker_change(isolated_runner, old_project, with_injection):
66+
"""Test migrating projects with changes to the Dockerfile."""
67+
result = isolated_runner.invoke(cli, ["migrate", "--strict"])
68+
assert 0 == result.exit_code, format_result_exception(result)
69+
assert not old_project.repository.is_dirty()
70+
assert "Updated dockerfile" in result.output
71+
72+
with project_context.with_path(old_project.path), with_injection():
73+
assert project_context.project
74+
assert project_context.project.name
75+
76+
5677
@pytest.mark.migration
5778
@pytest.mark.serial
5879
def test_migration_check(isolated_runner, project):
Binary file not shown.
Binary file not shown.

tests/service/fixtures/service_integration.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
from renku.core import errors
2727
from renku.infrastructure.repository import Repository
28-
from tests.utils import format_result_exception, modified_environ
28+
from tests.utils import clone_compressed_repository, format_result_exception, modified_environ
2929

3030

3131
@contextlib.contextmanager
@@ -272,6 +272,57 @@ def _parse(href):
272272
pass
273273

274274

275+
@pytest.fixture
276+
def old_local_remote_project(request, svc_client, tmp_path, mock_redis, identity_headers, real_sync):
277+
"""Fixture for testing service with project tarfiles containing old projects."""
278+
from renku.domain_model import git
279+
from renku.ui.service.cache import cache as redis_cache
280+
from renku.ui.service.gateways.repository_cache import LocalRepositoryCache
281+
from renku.ui.service.serializers.headers import RequiredIdentityHeaders
282+
283+
name = request.param
284+
remote_repo_path = tmp_path / name
285+
remote_repo = clone_compressed_repository(base_path=tmp_path, name=name)
286+
remote_repo_path = remote_repo_path / "repository"
287+
remote_repo_checkout_path = tmp_path / "remote_repo_checkout"
288+
remote_repo_checkout_path.mkdir()
289+
290+
remote_repo_checkout = Repository.clone_from(url=remote_repo_path, path=remote_repo_checkout_path)
291+
292+
# NOTE: Mock GitURL parsing for local URL
293+
def _parse(href):
294+
return git.GitURL(href=href, regex="", owner="dummy", name="project", slug="project", path=remote_repo_path)
295+
296+
original_giturl_parse = git.GitURL.parse
297+
git.GitURL.parse = _parse
298+
299+
home = tmp_path / "user_home"
300+
home.mkdir()
301+
302+
user_data = RequiredIdentityHeaders().load(identity_headers)
303+
user = redis_cache.ensure_user(user_data)
304+
remote_url = f"file://{remote_repo_path}"
305+
306+
project = LocalRepositoryCache().get(redis_cache, remote_url, branch=None, user=user, shallow=False)
307+
308+
project_id = project.project_id
309+
310+
try:
311+
yield svc_client, identity_headers, project_id, remote_repo, remote_repo_checkout, remote_url
312+
finally:
313+
git.GitURL.parse = original_giturl_parse
314+
315+
try:
316+
shutil.rmtree(remote_repo_path)
317+
except OSError:
318+
pass
319+
320+
try:
321+
shutil.rmtree(remote_repo_checkout_path)
322+
except OSError:
323+
pass
324+
325+
275326
@pytest.fixture
276327
def quick_cache_synchronization(mocker):
277328
"""Forces cache to synchronize on every request."""

tests/service/views/test_cache_views.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,25 @@ def test_execute_migrations(svc_client_setup):
704704
assert not response.json["result"]["errors"]
705705

706706

707+
@pytest.mark.service
708+
@pytest.mark.parametrize(
709+
"old_local_remote_project",
710+
[
711+
"v9-migration-docker-version-change.git",
712+
"v9-migration-docker-mult-changes.git",
713+
],
714+
indirect=["old_local_remote_project"],
715+
)
716+
def test_migrate_old_project(old_local_remote_project):
717+
"""Test migrating old projects with docker file changes."""
718+
svc_client, identity_headers, project_id, remote_repo, remote_repo_checkout, remote_url = old_local_remote_project
719+
720+
response = svc_client.post("/cache.migrate", data=json.dumps(dict(git_url=remote_url)), headers=identity_headers)
721+
assert response
722+
assert 200 == response.status_code
723+
assert response.json.get("result", {}).get("docker_migrated", False)
724+
725+
707726
@pytest.mark.service
708727
@pytest.mark.integration
709728
def test_execute_migrations_job(svc_client_setup):

0 commit comments

Comments
 (0)