Skip to content

Commit 46bfb48

Browse files
committed
Do not dump pipeline YAML if no migration is applied
STONEBLD-3438 * Compute file hash to detect if the pipeline is modified. * New test is added, and by the way, the existing tests are refactored with fixtures for adding the new test. * Test test_ensure_updates_to_pipeline_are_saved is renamed to reflect the test purpose specifically.
1 parent 2538aea commit 46bfb48

File tree

5 files changed

+102
-58
lines changed

5 files changed

+102
-58
lines changed

src/pipeline_migration/migrate.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from dataclasses import dataclass, field
1212
from typing import Final, Any
1313

14-
from pipeline_migration.utils import dump_yaml, load_yaml
14+
from pipeline_migration.utils import dump_yaml, load_yaml, file_checksum
1515
from pipeline_migration.registry import Container, Registry, ImageIndex
1616
from pipeline_migration.quay import QuayTagInfo, list_active_repo_tags
1717
from pipeline_migration.types import FilePath
@@ -113,9 +113,11 @@ def resolve_pipeline(pipeline_file: FilePath) -> Generator[FilePath, Any, None]:
113113

114114
kind = origin_pipeline.get("kind")
115115
if kind == TEKTON_KIND_PIPELINE:
116+
origin_checksum = file_checksum(pipeline_file)
116117
yield pipeline_file
117-
pl_yaml = load_yaml(pipeline_file)
118-
dump_yaml(pipeline_file, pl_yaml, style=yaml_style)
118+
if file_checksum(pipeline_file) != origin_checksum:
119+
pl_yaml = load_yaml(pipeline_file)
120+
dump_yaml(pipeline_file, pl_yaml, style=yaml_style)
119121
elif kind == TEKTON_KIND_PIPELINE_RUN:
120122
spec = origin_pipeline.get("spec") or {}
121123
if "pipelineSpec" in spec:
@@ -124,10 +126,12 @@ def resolve_pipeline(pipeline_file: FilePath) -> Generator[FilePath, Any, None]:
124126
os.close(fd)
125127
pipeline = {"spec": spec["pipelineSpec"]}
126128
dump_yaml(temp_pipeline_file, pipeline)
129+
origin_checksum = file_checksum(temp_pipeline_file)
127130
yield temp_pipeline_file
128-
modified_pipeline = load_yaml(temp_pipeline_file)
129-
spec["pipelineSpec"] = modified_pipeline["spec"]
130-
dump_yaml(pipeline_file, origin_pipeline, style=yaml_style)
131+
if file_checksum(temp_pipeline_file) != origin_checksum:
132+
modified_pipeline = load_yaml(temp_pipeline_file)
133+
spec["pipelineSpec"] = modified_pipeline["spec"]
134+
dump_yaml(pipeline_file, origin_pipeline, style=yaml_style)
131135
elif "pipelineRef" in spec:
132136
# Pipeline definition can be referenced here, via either git-resolver or a name field
133137
# pointing to YAML file under the .tekton/.

src/pipeline_migration/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import hashlib
12
from dataclasses import dataclass, field
23
from typing import Any
34

@@ -106,3 +107,8 @@ def load_yaml(yaml_file: FilePath) -> Any:
106107
def dump_yaml(yaml_file: FilePath, data: Any, style: YAMLStyle | None = None) -> None:
107108
with open(yaml_file, "w", encoding="utf-8") as f:
108109
create_yaml_obj(style).dump(data, f)
110+
111+
112+
def file_checksum(file_path: FilePath) -> str:
113+
with open(file_path, "rb") as f:
114+
return hashlib.sha256(f.read()).hexdigest()

tests/conftest.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,34 @@ def pipeline_yaml_with_various_indent_styles(request, pipeline_yaml):
148148
git clone https://git.host/project
149149
"""
150150
)
151+
case _:
152+
raise ValueError(f"Unexpected param {request.param}")
153+
154+
155+
@pytest.fixture
156+
def pipeline_run_yaml() -> str:
157+
return dedent(
158+
"""\
159+
apiVersion: tekton.dev/v1
160+
kind: PipelineRun
161+
metadata:
162+
name: docker-build
163+
spec:
164+
pipelineSpec:
165+
params:
166+
tasks:
167+
- name: clone
168+
- name: build
169+
"""
170+
)
171+
172+
173+
@pytest.fixture(params=["pipeline", "pipeline_run"])
174+
def pipeline_and_run_yaml(request, pipeline_yaml, pipeline_run_yaml) -> str:
175+
match request.param:
176+
case "pipeline":
177+
return pipeline_yaml
178+
case "pipeline_run":
179+
return pipeline_run_yaml
180+
case _:
181+
raise ValueError(f"Unexpected param {request.param}")

tests/test_cli.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
ensure_container,
2424
)
2525

26-
from pipeline_migration.utils import YAMLStyle
26+
from pipeline_migration.utils import YAMLStyle, load_yaml, dump_yaml
2727
from tests.test_migrate import APP_IMAGE_REPO, TASK_BUNDLE_CLONE
2828
from tests.utils import generate_digest, generate_git_sha
2929

@@ -296,6 +296,12 @@ def _subprocess_run(cmd, *args, **kwargs):
296296
assert pipeline_file == tb_upgrades[0]["packageFile"]
297297
migration_file = cmd[-2]
298298
assert Path(migration_file).read_bytes() in migration_steps
299+
300+
# Modify the pipeline as if a migration is applied
301+
doc = load_yaml(pipeline_file)
302+
doc["spec"]["tasks"] += {"name": "summary"}
303+
dump_yaml(pipeline_file, doc)
304+
299305
return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="")
300306

301307
monkeypatch.setattr("subprocess.run", _subprocess_run)

tests/test_migrate.py

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,31 @@
33
import logging
44
import subprocess
55
from copy import deepcopy
6+
from pathlib import Path
67
from textwrap import dedent
78
from typing import Any, Final
9+
from unittest.mock import patch
810

911
import responses
1012
import pytest
13+
from ruamel.yaml import YAML
1114

1215
from pipeline_migration.migrate import (
1316
ANNOTATION_HAS_MIGRATION,
1417
ANNOTATION_IS_MIGRATION,
1518
ANNOTATION_TRUTH_VALUE,
16-
LinkedMigrationsResolver,
1719
determine_task_bundle_upgrades_range,
1820
fetch_migration_file,
1921
IncorrectMigrationAttachment,
2022
InvalidRenovateUpgradesData,
23+
LinkedMigrationsResolver,
2124
resolve_pipeline,
25+
SimpleIterationResolver,
2226
TaskBundleMigration,
2327
TaskBundleUpgrade,
2428
TaskBundleUpgradesManager,
25-
SimpleIterationResolver,
29+
TEKTON_KIND_PIPELINE,
30+
TEKTON_KIND_PIPELINE_RUN,
2631
)
2732
from pipeline_migration.quay import QuayTagInfo
2833
from pipeline_migration.registry import Container
@@ -268,63 +273,55 @@ def test_invalid_upgrade(self, data, expected):
268273

269274
class TestResolvePipeline:
270275

271-
def test_resolve_from_a_pipeline_definition(self, tmp_path):
276+
def test_resolve_from_a_pipeline_definition(self, pipeline_yaml, tmp_path):
272277
pipeline_file = tmp_path / "pl.yaml"
273-
content = dedent(
274-
"""\
275-
apiVersion: tekton.dev/v1
276-
kind: Pipeline
277-
metadata:
278-
name: pl
279-
spec:
280-
params:
281-
tasks:
282-
"""
283-
)
284-
pipeline_file.write_text(content)
285-
with resolve_pipeline(pipeline_file):
286-
pass
278+
pipeline_file.write_text(pipeline_yaml)
279+
with resolve_pipeline(pipeline_file) as f:
280+
assert pipeline_yaml == Path(f).read_text()
287281

288-
def test_resolve_from_a_pipeline_run_definition(self, tmp_path):
282+
def test_resolve_from_a_pipeline_run_definition(self, pipeline_run_yaml, tmp_path):
289283
pipeline_file = tmp_path / "plr.yaml"
290-
content = dedent(
291-
"""\
292-
apiVersion: tekton.dev/v1
293-
kind: PipelineRun
294-
metadata:
295-
name: pl
296-
spec:
297-
pipelineSpec:
298-
params:
299-
tasks:
300-
"""
301-
)
302-
pipeline_file.write_text(content)
303-
with resolve_pipeline(pipeline_file):
304-
pass
284+
pipeline_file.write_text(pipeline_run_yaml)
285+
with resolve_pipeline(pipeline_file) as f:
286+
resolved_pipeline = load_yaml(f)
287+
assert "spec" in resolved_pipeline
288+
pipeline_run = load_yaml(pipeline_file)
289+
assert resolved_pipeline["spec"] == pipeline_run["spec"]["pipelineSpec"]
290+
291+
def test_updates_to_pipeline_are_dumped(self, pipeline_and_run_yaml, tmp_path):
292+
pipeline_file = tmp_path / "file.yaml"
293+
pipeline_file.write_text(pipeline_and_run_yaml)
305294

306-
def test_ensure_updates_to_pipeline_are_saved(self, tmp_path):
307-
pipeline_file = tmp_path / "plr.yaml"
308-
content = dedent(
309-
"""\
310-
apiVersion: tekton.dev/v1
311-
kind: PipelineRun
312-
metadata:
313-
name: pl
314-
spec:
315-
pipelineSpec:
316-
params:
317-
tasks:
318-
"""
319-
)
320-
pipeline_file.write_text(content)
321295
with resolve_pipeline(pipeline_file) as f:
322296
pl = load_yaml(f)
323-
pl["spec"]["tasks"] = [{"name": "init"}]
297+
pl["spec"]["tasks"].append({"name": "test"})
324298
dump_yaml(f, pl)
325299

326-
plr = load_yaml(pipeline_file)
327-
assert plr["spec"]["pipelineSpec"]["tasks"] == [{"name": "init"}]
300+
doc = load_yaml(pipeline_file)
301+
if doc["kind"] == TEKTON_KIND_PIPELINE:
302+
tasks = doc["spec"]["tasks"]
303+
elif doc["kind"] == TEKTON_KIND_PIPELINE_RUN:
304+
tasks = doc["spec"]["pipelineSpec"]["tasks"]
305+
else:
306+
raise ValueError(f"Unexpected kind {doc['kind']}")
307+
308+
assert tasks[-1]["name"] == "test"
309+
310+
@patch("pipeline_migration.migrate.dump_yaml")
311+
def test_do_not_save_if_pipeline_is_not_modified(
312+
self, mock_dump_yaml, pipeline_and_run_yaml, tmp_path
313+
):
314+
pipeline_file = tmp_path / "plr.yaml"
315+
pipeline_file.write_text(pipeline_and_run_yaml)
316+
317+
with resolve_pipeline(pipeline_file):
318+
pass # Nothing is changed
319+
320+
doc = YAML().load(pipeline_and_run_yaml)
321+
if doc["kind"] == TEKTON_KIND_PIPELINE:
322+
assert mock_dump_yaml.call_count == 0
323+
elif doc["kind"] == TEKTON_KIND_PIPELINE_RUN:
324+
assert mock_dump_yaml.call_count == 1
328325

329326
def test_do_not_handle_pipelineref(self, tmp_path):
330327
pipeline_file = tmp_path / "plr.yaml"

0 commit comments

Comments
 (0)