M dlrepo-cli => dlrepo-cli +21 -0
@@ 368,6 368,7 @@ def status(args):
else:
print("URL: %s" % client.make_url(url))
print("released=%s" % data["tag"]["released"])
+ print("stable=%s" % data["tag"]["stable"])
print("locked=%s" % data["tag"]["locked"])
print("publish_status=%s" % data["tag"].get("publish_status", ""))
@@ 399,6 400,26 @@ def release(args):
@sub_command(
Arg("branch", metavar="BRANCH", help="the branch name"),
Arg("tag", metavar="TAG", help="the tag name"),
+ Arg(
+ "-u",
+ "--unset",
+ action="store_true",
+ help="unset the 'stable' status from the tag instead of setting it",
+ ),
+)
+def stable(args):
+ """
+ Set or unset the 'stable' status on a tag.
+ """
+ client = HttpClient(args.url)
+ url = os.path.join("branches", args.branch, args.tag) + "/"
+ client.put(url, {"tag": {"stable": not args.unset}})
+
+
+# --------------------------------------------------------------------------------------
+@sub_command(
+ Arg("branch", metavar="BRANCH", help="the branch name"),
+ Arg("tag", metavar="TAG", help="the tag name"),
Arg("job", metavar="JOB", help="the job name"),
Arg(
"-u",
M dlrepo/fs/branch.py => dlrepo/fs/branch.py +1 -1
@@ 42,7 42,7 @@ class Branch(SubDir):
for t in tags:
if name == "latest":
return t
- if t.is_released():
+ if t.is_stable():
return t
raise FileNotFoundError(name)
return Tag(self, name)
M dlrepo/fs/product.py => dlrepo/fs/product.py +8 -1
@@ 71,7 71,7 @@ class ProductBranch(SubDir):
for v in versions:
if name == "latest":
return v
- if v.is_released():
+ if v.is_stable():
return v
raise FileNotFoundError(name)
return Version(self, name)
@@ 122,6 122,13 @@ class Version(SubDir):
return True
return False
+ def is_stable(self) -> bool:
+ for fmt in self.get_formats():
+ stable_path = fmt.path().resolve().parent / ".stable"
+ if stable_path.is_file():
+ return True
+ return False
+
def get_formats(self) -> Iterator[ArtifactFormat]:
yield from ArtifactFormat.all(self)
M dlrepo/fs/tag.py => dlrepo/fs/tag.py +15 -0
@@ 74,6 74,21 @@ class Tag(SubDir):
task = loop.create_task(self.do_release(released, semaphore))
task.add_done_callback(self.done_cb)
+ def _stable_path(self) -> Path:
+ return self._path / ".stable"
+
+ def is_stable(self) -> bool:
+ return self._stable_path().is_file()
+
+ def set_stable(self, stable: bool):
+ if not self._path.is_dir():
+ raise FileNotFoundError()
+ path = self._stable_path()
+ if stable:
+ path.touch()
+ elif path.is_file():
+ path.unlink()
+
def done_cb(self, task):
if task.cancelled():
return
M dlrepo/templates/branch.html => dlrepo/templates/branch.html +1 -1
@@ 17,7 17,7 @@
<a href="latest/" class="tag alias">latest</a>
</div>
{% endif %}
- {% if tags|selectattr("released")|list %}
+ {% if tags|selectattr("stable")|list %}
<div>
<a href="stable/" class="tag alias">stable</a>
</div>
M dlrepo/templates/product_branch.html => dlrepo/templates/product_branch.html +1 -1
@@ 17,7 17,7 @@
<a href="latest/" class="version alias">latest</a>
</div>
{% endif %}
- {% if versions|selectattr("released")|list %}
+ {% if versions|selectattr("stable")|list %}
<div>
<a href="stable/" class="version alias">stable</a>
</div>
M dlrepo/templates/tag.html => dlrepo/templates/tag.html +4 -1
@@ 8,11 8,14 @@
{% endblock %}
{% block page_content %}
-{% if tag.released or tag.locked or tag.publish_status %}
+{% if tag.released or tag.locked or tag.publish_status or tag.stable %}
<section class="tag-status">
{% if tag.released %}
<span class="badge released">released</span>
{% endif %}
+ {% if tag.stable %}
+ <span class="badge released">stable</span>
+ {% endif %}
{% if tag.locked %}
<span class="badge locked">locked</span>
{% endif %}
M dlrepo/views/branch.py => dlrepo/views/branch.py +1 -0
@@ 87,6 87,7 @@ class BranchView(BaseView):
"timestamp": t.timestamp,
"released": t.is_released(),
"locked": t.is_locked(),
+ "stable": t.is_stable(),
"publish_status": t.publish_status(),
}
)
M dlrepo/views/product.py => dlrepo/views/product.py +1 -0
@@ 146,6 146,7 @@ class ProductBranchView(BaseView):
"timestamp": v.timestamp,
"locked": v.is_locked(),
"released": v.is_released(),
+ "stable": v.is_stable(),
}
)
data = {
M dlrepo/views/tag.py => dlrepo/views/tag.py +7 -1
@@ 49,6 49,7 @@ class TagView(BaseView):
"name": tag.name,
"released": tag.is_released(),
"locked": tag.is_locked(),
+ "stable": tag.is_stable(),
"publish_status": tag.publish_status(),
"jobs": [],
},
@@ 71,7 72,7 @@ class TagView(BaseView):
async def put(self):
"""
- Change the released and/or locked status of a tag.
+ Change the released, stable and/or locked statuses of a tag.
"""
tag = self._get_tag()
try:
@@ 82,6 83,9 @@ class TagView(BaseView):
locked = data.get("locked")
if locked is not None and not isinstance(locked, bool):
raise TypeError()
+ stable = data.get("stable")
+ if stable is not None and not isinstance(stable, bool):
+ raise TypeError()
except (TypeError, KeyError) as e:
raise web.HTTPBadRequest(reason="invalid parameters") from e
@@ 91,6 95,8 @@ class TagView(BaseView):
tag.set_released(released, semaphore)
if locked is not None:
tag.set_locked(locked)
+ if stable is not None:
+ tag.set_stable(stable)
except FileNotFoundError as e:
raise web.HTTPNotFound() from e