Skip to content

Commit 40d9591

Browse files
[ACIX-648] create unique qualification tags for agent 6 (#36349)
1 parent ff6fab6 commit 40d9591

File tree

3 files changed

+167
-25
lines changed

3 files changed

+167
-25
lines changed

tasks/libs/common/git.py

+19
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
if TYPE_CHECKING:
1717
from collections.abc import Iterable
1818

19+
TAG_BATCH_SIZE = 3
20+
1921

2022
@contextmanager
2123
def clone(ctx, repo, branch, options=""):
@@ -344,3 +346,20 @@ def create_tree(ctx, base_branch):
344346
blob["content"] = content
345347
tree["tree"].append(blob)
346348
return tree
349+
350+
351+
def push_tags_in_batches(ctx, tags, force_option="", delete=False):
352+
"""
353+
Push or delete tags to remote in batches
354+
"""
355+
if not tags:
356+
return
357+
358+
tags_list = ' '.join(tags)
359+
command = "push --delete" if delete else "push"
360+
361+
for idx in range(0, len(tags), TAG_BATCH_SIZE):
362+
batch_tags = tags[idx : idx + TAG_BATCH_SIZE]
363+
ctx.run(f"git {command} origin {' '.join(batch_tags)}{force_option}")
364+
365+
print(f"{'Deleted' if delete else 'Pushed'} tags: {tags_list}")

tasks/release.py

+40-24
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
get_last_commit,
3535
get_last_release_tag,
3636
is_agent6,
37+
push_tags_in_batches,
3738
try_git_command,
3839
)
3940
from tasks.libs.common.gomodules import get_default_modules
@@ -84,7 +85,6 @@
8485
from tasks.release_metrics.metrics import get_prs_metrics, get_release_lead_time
8586

8687
BACKPORT_LABEL_COLOR = "5319e7"
87-
TAG_BATCH_SIZE = 3
8888
QUALIFICATION_TAG = "qualification"
8989

9090

@@ -211,12 +211,8 @@ def _tag_modules():
211211
tags.extend(new_tags)
212212

213213
if push:
214-
tags_list = ' '.join(tags)
215214
set_gitconfig_in_ci(ctx)
216-
for idx in range(0, len(tags), TAG_BATCH_SIZE):
217-
batch_tags = tags[idx : idx + TAG_BATCH_SIZE]
218-
ctx.run(f"git push origin {' '.join(batch_tags)}{force_option}")
219-
print(f"Pushed tag {tags_list}")
215+
push_tags_in_batches(ctx, tags, force_option)
220216
print(f"Created module tags for version {agent_version}")
221217

222218
if skip_agent_context:
@@ -266,21 +262,20 @@ def _tag_version():
266262
tags = __tag_single_module(ctx, get_default_modules()["."], agent_version, commit, force_option, devel)
267263

268264
set_gitconfig_in_ci(ctx)
269-
# create or update the qualification tag using the force option (points tag to next RC)
270265
if is_agent6(ctx) and (start_qual or is_qualification(ctx, "6.53.x")):
266+
# remove all the qualification tags if it is the final version
271267
if FINAL_VERSION_RE.match(agent_version):
272-
ctx.run(f"git push --delete origin {QUALIFICATION_TAG}")
268+
qualification_tags = [tag for _, tag in get_qualification_tags(ctx, release_branch)]
269+
push_tags_in_batches(ctx, qualification_tags, delete=True)
270+
# create or update the qualification tag on the current commit
273271
else:
274-
force_option = __get_force_option(not start_qual)
275272
tags += __tag_single_module(
276-
ctx, get_default_modules()["."], QUALIFICATION_TAG, commit, force_option, False
273+
ctx, get_default_modules()["."], f"{QUALIFICATION_TAG}-{int(time.time())}", commit, "", False
277274
)
278275

279276
if push:
280-
tags_list = ' '.join(tags)
281-
ctx.run(f"git push origin {tags_list}{force_option}")
282-
print(f"Pushed tag {tags_list}")
283-
print(f"Created tags for version {agent_version}")
277+
push_tags_in_batches(ctx, tags, force_option)
278+
print(f"Created tags for version {agent_version}")
284279

285280
if skip_agent_context:
286281
_tag_version()
@@ -541,16 +536,36 @@ def create_rc(ctx, release_branch, patch_version=False, upstream="origin"):
541536

542537
@task
543538
def is_qualification(ctx, release_branch, output=False):
539+
if qualification_tag_query(ctx, release_branch):
540+
if output:
541+
print('true')
542+
return True
543+
if output:
544+
print("false")
545+
return False
546+
547+
548+
def qualification_tag_query(ctx, release_branch, sort=False):
544549
with agent_context(ctx, release_branch):
545-
try:
546-
ctx.run(f"git tag | grep {QUALIFICATION_TAG}", hide=True)
547-
if output:
548-
print('true')
549-
return True
550-
except Failure:
551-
if output:
552-
print("false")
553-
return False
550+
sort_option = " --sort=-refname" if sort else ""
551+
res = ctx.run(f"git ls-remote --tags{sort_option} origin '{QUALIFICATION_TAG}-*^{{}}'", hide=True)
552+
if res.stdout:
553+
return res.stdout.splitlines()
554+
return None
555+
556+
557+
@task
558+
def get_qualification_tags(ctx, release_branch, latest_tag=False):
559+
"""Get the qualification tags in remote repository
560+
561+
Args:
562+
latest_tag: if True, only return the latest commit and tag
563+
"""
564+
qualification_tags = qualification_tag_query(ctx, release_branch, sort=True)
565+
if latest_tag:
566+
qualification_tags = [qualification_tags[0]]
567+
568+
return [ref.replace("^{}", "").split("\t") for ref in qualification_tags]
554569

555570

556571
@task
@@ -634,7 +649,8 @@ def get_qualification_rc_tag(ctx, release_branch):
634649
with agent_context(ctx, release_branch):
635650
err_msg = "Error: Expected exactly one release candidate tag associated with the qualification tag commit. Tags found:"
636651
try:
637-
res = ctx.run(f"git tag --points-at $(git rev-list -n 1 {QUALIFICATION_TAG}) | grep 6.53")
652+
latest_commit, _ = get_qualification_tags(ctx, release_branch, latest_tag=True)[0]
653+
res = ctx.run(f"git tag --points-at {latest_commit} | grep 6.53")
638654
except Failure as err:
639655
raise Exit(message=f"{err_msg} []", code=1) from err
640656

tasks/unit_tests/release_tests.py

+108-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from collections import OrderedDict
88
from contextlib import contextmanager
99
from types import SimpleNamespace
10-
from unittest.mock import MagicMock, call, patch
10+
from unittest.mock import ANY, MagicMock, call, patch
1111

1212
from invoke import Context, MockContext, Result
1313
from invoke.exceptions import Exit
@@ -1075,3 +1075,110 @@ def test_100_tags(self):
10751075
mock_modules.return_value = mock_dict
10761076
release.tag_modules(c, version="version")
10771077
self.assertEqual(c.run.call_count, 34)
1078+
1079+
1080+
class TestTagVersion(unittest.TestCase):
1081+
c = MockContext(run=Result("yolo"))
1082+
1083+
@patch('tasks.release.__tag_single_module')
1084+
@patch('tasks.release.push_tags_in_batches')
1085+
@patch('tasks.release.is_agent6', new=MagicMock(return_value=True))
1086+
@patch('tasks.release.is_qualification', new=MagicMock(return_value=False))
1087+
@patch('tasks.release.agent_context', new=MagicMock())
1088+
@patch.dict(os.environ, {'GITLAB_CI': 'false', 'GITHUB_ACTIONS': 'false'})
1089+
def test_not_in_qualification_phase(self, push_tags_in_batches_mock, tag_single_module_mock):
1090+
rc_version = "6.53.5-rc.2"
1091+
release.tag_version(self.c, start_qual=False, version=rc_version)
1092+
tag_single_module_mock.assert_called_with(self.c, ANY, rc_version, ANY, ANY, ANY)
1093+
assert tag_single_module_mock.call_count == 1
1094+
assert push_tags_in_batches_mock.call_count == 1
1095+
1096+
@patch('tasks.release.__tag_single_module')
1097+
@patch('tasks.release.push_tags_in_batches')
1098+
@patch('time.time', new=MagicMock(return_value=1234))
1099+
@patch('tasks.release.is_agent6', new=MagicMock(return_value=True))
1100+
@patch('tasks.release.is_qualification', new=MagicMock(return_value=False))
1101+
@patch('tasks.release.agent_context', new=MagicMock())
1102+
@patch.dict(os.environ, {'GITLAB_CI': 'false', 'GITHUB_ACTIONS': 'false'})
1103+
def test_start_qualification_phase(self, push_tags_in_batches_mock, tag_single_module_mock):
1104+
rc_version = "6.53.5-rc.2"
1105+
release.tag_version(self.c, start_qual=True, version=rc_version)
1106+
calls = tag_single_module_mock.call_args_list
1107+
calls[0].assert_called_with(self.c, ANY, rc_version, ANY, ANY, ANY)
1108+
calls[1].assert_called_with(self.c, ANY, "qualification-1234", ANY, ANY, ANY)
1109+
assert tag_single_module_mock.call_count == 2
1110+
assert push_tags_in_batches_mock.call_count == 1
1111+
1112+
@patch('tasks.release.__tag_single_module')
1113+
@patch('tasks.release.push_tags_in_batches')
1114+
@patch('time.time', new=MagicMock(return_value=2345))
1115+
@patch('tasks.release.is_agent6', new=MagicMock(return_value=True))
1116+
@patch('tasks.release.is_qualification', new=MagicMock(return_value=True))
1117+
@patch('tasks.release.agent_context', new=MagicMock())
1118+
@patch.dict(os.environ, {'GITLAB_CI': 'false', 'GITHUB_ACTIONS': 'false'})
1119+
def test_during_qualification_phase(self, push_tags_in_batches_mock, tag_single_module_mock):
1120+
rc_version = "6.53.5-rc.3"
1121+
release.tag_version(self.c, start_qual=False, version=rc_version)
1122+
calls = tag_single_module_mock.call_args_list
1123+
calls[0].assert_called_with(self.c, ANY, rc_version, ANY, ANY, ANY)
1124+
calls[1].assert_called_with(self.c, ANY, "qualification-2345", ANY, ANY, ANY)
1125+
assert tag_single_module_mock.call_count == 2
1126+
assert push_tags_in_batches_mock.call_count == 1
1127+
1128+
@patch('tasks.release.__tag_single_module')
1129+
@patch('tasks.release.push_tags_in_batches')
1130+
@patch('tasks.release.is_agent6', new=MagicMock(return_value=True))
1131+
@patch('tasks.release.is_qualification', new=MagicMock(return_value=True))
1132+
@patch('tasks.release.agent_context', new=MagicMock())
1133+
@patch('tasks.release.get_qualification_tags', new=MagicMock())
1134+
@patch.dict(os.environ, {'GITLAB_CI': 'false', 'GITHUB_ACTIONS': 'false'})
1135+
def test_end_qualification_phase(self, push_tags_in_batches_mock, tag_single_module_mock):
1136+
final_release_version = "6.53.5"
1137+
release.tag_version(self.c, start_qual=False, version=final_release_version)
1138+
tag_single_module_mock.assert_called_with(self.c, ANY, final_release_version, ANY, ANY, ANY)
1139+
assert tag_single_module_mock.call_count == 1
1140+
assert push_tags_in_batches_mock.call_count == 2
1141+
1142+
1143+
class TestGetQualificationTags(unittest.TestCase):
1144+
c = MockContext(run=Result("yolo"))
1145+
1146+
@patch('tasks.release.qualification_tag_query')
1147+
@patch('tasks.release.agent_context', new=MagicMock())
1148+
def test_returns_all_tags(self, qualification_tag_query_mock):
1149+
qualification_tag_query_mock.return_value = ['hash2\tqualification_2345^{}', 'hash1\tqualification_1234^{}']
1150+
tags = release.get_qualification_tags(self.c, "6.53.x")
1151+
qualification_tag_query_mock.assert_called_with(self.c, "6.53.x", sort=True)
1152+
assert tags == [['hash2', 'qualification_2345'], ['hash1', 'qualification_1234']]
1153+
self.assertEqual(len(tags), 2)
1154+
1155+
@patch('tasks.release.qualification_tag_query')
1156+
@patch('tasks.release.agent_context', new=MagicMock())
1157+
def test_returns_only_one_tag(self, qualification_tag_query_mock):
1158+
qualification_tag_query_mock.return_value = ['hash2\tqualification_2345^{}', 'hash1\tqualification_1234^{}']
1159+
tags = release.get_qualification_tags(self.c, "6.53.x", latest_tag=True)
1160+
qualification_tag_query_mock.assert_called_with(self.c, "6.53.x", sort=True)
1161+
assert tags == [['hash2', 'qualification_2345']]
1162+
self.assertEqual(len(tags), 1)
1163+
1164+
1165+
class TestIsQualification(unittest.TestCase):
1166+
c = MockContext(run=Result("yolo"))
1167+
1168+
@patch('builtins.print')
1169+
@patch('tasks.release.qualification_tag_query', new=MagicMock(return_value="hash1\tqualification_1234"))
1170+
def test_is_qualification(self, print_mock):
1171+
self.assertTrue(release.is_qualification(self.c, "6.53.x"))
1172+
assert print_mock.call_count == 0
1173+
self.assertTrue(release.is_qualification(self.c, "6.53.x", output=True))
1174+
print_mock.assert_called_with("true")
1175+
assert print_mock.call_count == 1
1176+
1177+
@patch('builtins.print')
1178+
@patch('tasks.release.qualification_tag_query', new=MagicMock(return_value=None))
1179+
def test_is_not_qualification(self, print_mock):
1180+
self.assertFalse(release.is_qualification(self.c, "6.53.x"))
1181+
assert print_mock.call_count == 0
1182+
self.assertFalse(release.is_qualification(self.c, "6.53.x", output=True))
1183+
print_mock.assert_called_with("false")
1184+
assert print_mock.call_count == 1

0 commit comments

Comments
 (0)