Skip to content

Commit f064ad5

Browse files
authored
Merge pull request #15 from konflux-ci/refine-file-io
Refine file I/O operations
2 parents f82c903 + 19e0789 commit f064ad5

File tree

2 files changed

+78
-101
lines changed

2 files changed

+78
-101
lines changed

src/pipeline_migration/migrate.py

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -250,39 +250,36 @@ def resolve_migrations(self) -> None:
250250
"""Resolve migrations for given task bundle upgrades"""
251251
self._resolver.resolve(list(self._task_bundle_upgrades.values()))
252252

253-
@staticmethod
254-
def _apply_migration(pipeline_file: FilePath, migration: TaskBundleMigration) -> None:
255-
if not os.path.exists(pipeline_file):
256-
raise ValueError(f"Pipeline file does not exist: {pipeline_file}")
257-
258-
logger.info(
259-
"Apply migration of task bundle %s in package file %s",
260-
migration.task_bundle,
261-
pipeline_file,
262-
)
263-
264-
fd, migration_file = tempfile.mkstemp()
265-
try:
266-
os.write(fd, migration.migration_script.encode("utf-8"))
267-
finally:
268-
os.close(fd)
269-
270-
with resolve_pipeline(pipeline_file) as file_path:
271-
logger.info("Executing migration script %s on %s", migration_file, file_path)
272-
cmd = ["bash", migration_file, file_path]
273-
logger.debug("Run: %r", cmd)
274-
try:
275-
proc = sp.run(cmd, stderr=sp.STDOUT, stdout=sp.PIPE)
276-
logger.debug("%r", proc.stdout)
277-
proc.check_returncode()
278-
finally:
279-
os.unlink(migration_file)
280-
281253
def apply_migrations(self) -> None:
282254
for package_file in self._package_file_updates.values():
283-
for task_bundle_upgrade in package_file.task_bundle_upgrades:
284-
for migration in task_bundle_upgrade.migrations:
285-
self._apply_migration(package_file.file_path, migration)
255+
if not os.path.exists(package_file.file_path):
256+
raise ValueError(f"Pipeline file does not exist: {package_file.file_path}")
257+
with resolve_pipeline(package_file.file_path) as pipeline_file:
258+
fd, migration_file = tempfile.mkstemp(suffix="-migration-file")
259+
prev_size = 0
260+
try:
261+
for task_bundle_upgrade in package_file.task_bundle_upgrades:
262+
for migration in task_bundle_upgrade.migrations:
263+
logger.info(
264+
"Apply migration of task bundle %s in package file %s",
265+
migration.task_bundle,
266+
package_file.file_path,
267+
)
268+
269+
os.lseek(fd, 0, 0)
270+
content = migration.migration_script.encode("utf-8")
271+
if len(content) < prev_size:
272+
os.truncate(fd, len(content))
273+
prev_size = os.write(fd, content)
274+
275+
cmd = ["bash", migration_file, pipeline_file]
276+
logger.debug("Run: %r", cmd)
277+
proc = sp.run(cmd, stderr=sp.STDOUT, stdout=sp.PIPE)
278+
logger.debug("%r", proc.stdout)
279+
proc.check_returncode()
280+
finally:
281+
os.close(fd)
282+
os.unlink(migration_file)
286283

287284

288285
class IncorrectMigrationAttachment(Exception):

tests/test_migrate.py

Lines changed: 50 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -665,67 +665,76 @@ def _fetch_migration_file(image: str, digest: str) -> str | None:
665665
class TestApplyMigrations:
666666

667667
@pytest.mark.parametrize("chdir", [True, False])
668-
def test_apply_single_migration(self, chdir, monkeypatch, tmp_path) -> None:
669-
"""Test applying a single migration to a pipeline file
668+
def test_apply_migrations(self, chdir, tmp_path, monkeypatch):
669+
"""Ensure applying all resolved migrations"""
670670

671-
The migration tool aims to run inside a component repository, from
672-
where the packageFile is accessed by a relative path. Test parameter
673-
``chdir`` indicates to change the working directory for this test to
674-
test the different behaviors.
675-
"""
676-
from dataclasses import dataclass, field
671+
renovate_upgrades = [
672+
{
673+
"depName": TASK_BUNDLE_CLONE,
674+
"currentValue": "0.1",
675+
"currentDigest": "sha256:cff6b68a194a",
676+
"newValue": "0.2",
677+
"newDigest": "sha256:96e797480ac5",
678+
"depTypes": ["tekton-bundle"],
679+
"packageFile": ".tekton/pipeline.yaml",
680+
"parentDir": ".tekton",
681+
},
682+
]
683+
manager = TaskBundleUpgradesManager(renovate_upgrades, SimpleIterationResolver)
684+
685+
# Not really resolve migrations. Mock them instead, then apply.
686+
tb_upgrade = list(manager._task_bundle_upgrades.values())[0]
687+
688+
c = Container(f"{tb_upgrade.dep_name}:{tb_upgrade.new_value}@{generate_digest()}")
689+
m = TaskBundleMigration(task_bundle=c.uri_with_tag, migration_script="echo add a new task")
690+
tb_upgrade.migrations.append(m)
691+
692+
# Less content of the migration script than previous one, which covers file truncate.
693+
c = Container(f"{tb_upgrade.dep_name}:{tb_upgrade.new_value}@{generate_digest()}")
694+
m = TaskBundleMigration(task_bundle=c.uri_with_tag, migration_script="echo hello")
695+
tb_upgrade.migrations.append(m)
677696

678-
@dataclass
679-
class TestContext:
680-
bash_run: bool = False
681-
temp_files: list[tuple[int, str]] = field(default_factory=list)
697+
c = Container(f"{tb_upgrade.dep_name}:{tb_upgrade.new_value}@{tb_upgrade.new_digest}")
698+
m = TaskBundleMigration(
699+
task_bundle=c.uri_with_tag, migration_script="echo remove task param"
700+
)
701+
tb_upgrade.migrations.append(m)
702+
703+
tekton_dir = tmp_path / ".tekton"
704+
tekton_dir.mkdir()
705+
package_file = tekton_dir / "pipeline.yaml"
706+
package_file.write_text(PIPELINE_DEFINITION)
682707

683-
test_context = TestContext()
708+
test_context = {"executed_scripts": []}
684709
counter = itertools.count()
685-
migration_script: Final = "echo hello world"
686710

687711
def _mkstemp(*args, **kwargs):
688712
tmp_file_path = tmp_path / f"temp_file-{next(counter)}"
689713
tmp_file_path.write_text("")
690714
fd = os.open(tmp_file_path, os.O_RDWR)
691-
test_context.temp_files.append((fd, tmp_file_path))
692715
return fd, tmp_file_path
693716

694717
def subprocess_run(*args, **kwargs):
695718
cmd = args[0]
696-
with open(cmd[-1], "r") as f:
697-
assert f.read() == PIPELINE_DEFINITION
698719
with open(cmd[-2], "r") as f:
699-
assert f.read() == migration_script
700-
assert not kwargs.get("check")
701-
test_context.bash_run = True
720+
test_context["executed_scripts"].append(f.read())
702721
return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="")
703722

704723
monkeypatch.setattr("tempfile.mkstemp", _mkstemp)
705724
monkeypatch.setattr("subprocess.run", subprocess_run)
706725

707-
pipeline_file: Final = tmp_path / "pipeline.yaml"
708-
pipeline_file.write_text(PIPELINE_DEFINITION)
709-
710-
renovate_upgrades = deepcopy(RENOVATE_UPGRADES)
711-
manager = TaskBundleUpgradesManager(renovate_upgrades, SimpleIterationResolver)
712-
713-
tb_migration = TaskBundleMigration("task-bundle:0.3@sha256:1234", migration_script)
714726
if chdir:
715727
monkeypatch.chdir(tmp_path)
716-
manager._apply_migration("pipeline.yaml", tb_migration)
717-
else:
718-
with pytest.raises(ValueError, match="Pipeline file does not exist: pipeline.yaml"):
719-
manager._apply_migration("pipeline.yaml", tb_migration)
720-
return
728+
manager.apply_migrations()
721729

722-
assert test_context.bash_run
723-
assert len(test_context.temp_files) > 0
724-
for _, file_path in test_context.temp_files:
725-
assert not os.path.exists(file_path)
730+
expected = ["echo add a new task", "echo hello", "echo remove task param"]
731+
assert test_context["executed_scripts"] == expected
732+
else:
733+
with pytest.raises(ValueError, match="Pipeline file does not exist: .+"):
734+
manager.apply_migrations()
726735

727-
def test_apply_migrations(self, tmp_path, monkeypatch):
728-
"""Ensure applying all resolved migrations"""
736+
def test_raise_error_if_migration_process_fails(self, caplog, monkeypatch, tmp_path):
737+
caplog.set_level(logging.DEBUG, logger="migrate")
729738

730739
renovate_upgrades = [
731740
{
@@ -764,32 +773,6 @@ def test_apply_migrations(self, tmp_path, monkeypatch):
764773
package_file = tekton_dir / "pipeline.yaml"
765774
package_file.write_text(PIPELINE_DEFINITION)
766775

767-
test_context = {"executed_scripts": []}
768-
counter = itertools.count()
769-
770-
def _mkstemp(*args, **kwargs):
771-
tmp_file_path = tmp_path / f"temp_file-{next(counter)}"
772-
tmp_file_path.write_text("")
773-
fd = os.open(tmp_file_path, os.O_RDWR)
774-
return fd, tmp_file_path
775-
776-
def subprocess_run(*args, **kwargs):
777-
cmd = args[0]
778-
with open(cmd[-2], "r") as f:
779-
test_context["executed_scripts"].append(f.read())
780-
return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="")
781-
782-
monkeypatch.setattr("tempfile.mkstemp", _mkstemp)
783-
monkeypatch.setattr("subprocess.run", subprocess_run)
784-
785-
monkeypatch.chdir(tmp_path)
786-
manager.apply_migrations()
787-
788-
assert test_context["executed_scripts"] == ["echo add a new task", "echo remove task param"]
789-
790-
def test_raise_error_if_migration_process_fails(self, caplog, monkeypatch, tmp_path):
791-
caplog.set_level(logging.DEBUG, logger="migrate")
792-
793776
def _mkstemp(*args, **kwargs):
794777
tmp_file_path = tmp_path / "migration_file"
795778
tmp_file_path.write_text("")
@@ -805,13 +788,10 @@ def subprocess_run(cmd, *args, **kwargs):
805788
monkeypatch.setattr("tempfile.mkstemp", _mkstemp)
806789
monkeypatch.setattr("subprocess.run", subprocess_run)
807790

808-
pipeline_file: Final = tmp_path / "pipeline.yaml"
809-
pipeline_file.write_text("kind: Pipeline")
810-
811-
manager = TaskBundleUpgradesManager(deepcopy(RENOVATE_UPGRADES), SimpleIterationResolver)
812-
tb_migration = TaskBundleMigration("task-bundle:0.3@sha256:1234", "echo remove a param")
791+
monkeypatch.chdir(tmp_path)
813792
with pytest.raises(subprocess.CalledProcessError):
814-
manager._apply_migration(pipeline_file, tb_migration)
793+
manager.apply_migrations()
794+
815795
assert "something is wrong" in caplog.text
816796

817797

0 commit comments

Comments
 (0)