Cleanup. Prepared artifact fetch.

This commit is contained in:
Mark Qvist
2026-05-18 01:24:15 +02:00
parent 875d8ef7eb
commit 64749b4d18
+84 -48
View File
@@ -86,6 +86,7 @@ def program_setup(configdir, rnsconfigdir=None, verbosity=0, quietness=0, servic
git_client = ReticulumGitClient(configdir=configdir, verbosity=targetverbosity, identitypath=identity)
if operation == "list": git_client.list_releases(remote=task["remote"])
elif operation == "view": git_client.view_release(remote=task["remote"], target=task["target"])
elif operation == "fetch": git_client.fetch_release(remote=task["remote"], target=task["target"])
elif operation == "create": git_client.create_release(remote=task["remote"], target=task["target"], signer=task["signer"], name=task["name"])
elif operation == "delete": git_client.delete_release(remote=task["remote"], target=task["target"])
elif operation == "latest": git_client.latest_release(remote=task["remote"], target=task["target"])
@@ -161,7 +162,7 @@ def main():
parser.add_argument("-s", "--signer", action="store", metavar="PATH", default=None, help="path to signing identity, if different from release identity", type=str)
parser.add_argument("-n", "--name", action="store", metavar="name", default=None, help="package name if different from repo name", type=str)
parser.add_argument("repository", nargs="?", default=None, help="URL of remote repository", type=str)
parser.add_argument("operation", nargs="?", default=None, help="list, view, create, latest or delete", type=str)
parser.add_argument("operation", nargs="?", default=None, help="list, view, fetch, create, latest or delete", type=str)
parser.add_argument("target", nargs="?", default=None, help="tag and path to release artifacts directory", type=str)
elif subcommand == "perms":
@@ -524,7 +525,7 @@ class ReticulumGitClient():
#########################
def create_repository(self, remote=None):
if not remote: print(f"No remote specified"); exit(1)
if not remote: self.abort(f"No remote specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -565,13 +566,13 @@ class ReticulumGitClient():
####################
def fork_repository(self, source=None, target=None):
if not source: print(f"No source specified"); exit(1)
if not target: print(f"No target specified"); exit(1)
if not source: self.abort(f"No source specified")
if not target: self.abort(f"No target specified")
self._remote_clone_operation(source, target, self.PATH_FORK, "fork")
def mirror_repository(self, source=None, target=None):
if not source: print(f"No source specified"); exit(1)
if not target: print(f"No target specified"); exit(1)
if not source: self.abort(f"No source specified")
if not target: self.abort(f"No target specified")
self._remote_clone_operation(source, target, self.PATH_MIRROR, "mirror")
def _resolve_aliased_url(self, url):
@@ -627,7 +628,7 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def sync_repository(self, remote=None):
if not remote: print(f"No remote specified"); exit(1)
if not remote: self.abort(f"No remote specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -717,7 +718,7 @@ class ReticulumGitClient():
return None
def list_releases(self, remote=None):
if not remote: print(f"No remote specified"); exit(1)
if not remote: self.abort(f"No remote specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -775,8 +776,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def view_release(self, remote=None, target=None):
if not remote: print(f"No remote specified"); exit(1)
if not target: print(f"No target specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if not target: self.abort(f"No target specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -833,17 +834,56 @@ class ReticulumGitClient():
finally:
if self.link: self.link.teardown()
def fetch_release(self, remote=None, target=None):
if not remote: self.abort(f"No remote specified")
if not target: self.abort(f"No target specified")
self.connect_remote(remote)
timeout = self.link_timeout
while not self.link_ready and not self.link_failed and timeout > 0:
time.sleep(self.wait_sleep)
timeout -= self.wait_sleep
if not self.link_ready: self.abort("Link establishment failed")
try:
destination_hash, group, repo = self.parse_remote_url(remote)
repo_path = f"{group}/{repo}"
parts = target.split(":")
if len(parts) < 2: self.abort("Invalid release specification")
tag = parts[0]
artifact = parts[1]
def fetch(name):
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "fetch", "tag": tag, "artifact": name}
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30)
print("\r \r", end="")
if not response or not isinstance(response, bytes): self.abort("No response from remote")
status_byte = response[0]
if status_byte != 0:
error_msg = response[1:].decode("utf-8", errors="ignore")
self.abort(f"Remote error: {error_msg}")
if len(response) <= 1: self.abort("Empty response from remote")
print()
# TODO: Implement
except Exception as e: self.abort(f"Error viewing release: {e}")
finally:
if self.link: self.link.teardown()
def create_release(self, remote=None, target=None, signer=None, name=None):
if signer:
try:
identity_path = os.path.expanduser(signer)
if not os.path.isfile(identity_path): print(f"Signer identity {identity_path} does not exist"); exit(1)
if not os.path.isfile(identity_path): self.abort(f"Signer identity {identity_path} does not exist")
else: signer = RNS.Identity.from_file(signer)
if not signer: print(f"Could not load signer identity from {identity_path}"); exit(1)
except Exception as e: print(f"Could not load signer identity from {identity_path}: {e}"); exit(1)
if not signer: self.abort(f"Could not load signer identity from {identity_path}")
except Exception as e: self.abort(f"Could not load signer identity from {identity_path}: {e}")
if not remote: print(f"No remote specified"); exit(1)
if not target: print(f"No target specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if not target: self.abort(f"No target specified")
if not signer: signer = self.identity
self.connect_remote(remote)
@@ -861,15 +901,11 @@ class ReticulumGitClient():
release_time = int(time.time())
release_time_iso = datetime.fromtimestamp(release_time, tz=timezone.utc).isoformat().replace("+00:00", "Z")
# Parse target - can be:
# 1. Just a tag name: "v1.0.0"
# 3. Tag with path to artifacts directory: "v1.0.0:/path/to/artifacts"
parts = target.split(":")
if len(parts) < 2: self.abort("Invalid release specification\nDid you provide both a tag and artifacts path such as \"1.0.0:./dist\"?")
tag = parts[0]
artifacts_path = os.path.expanduser(parts[1])
commit_hash = ""
commit_hash = None # TODO: Get commit hash from tag
if not os.path.isdir(artifacts_path): self.abort("Specified artifacts directory does not exist")
artifacts = [f for f in os.listdir(artifacts_path) if os.path.isfile(os.path.join(artifacts_path, f))]
@@ -882,8 +918,8 @@ class ReticulumGitClient():
# Generate manifest
package_name = name or repo
manifest_meta = {"name": package_name ,"version": tag, "released": release_time_iso,
"timestamp": release_time, "artifacts": []}
manifest_meta = {"name": package_name ,"version": tag, "released": release_time_iso, "timestamp": release_time,
"origin": destination_hash, "commit": commit_hash, "artifacts": []}
try:
manifest_path = artifacts_path+f"/manifest.{self.MSG_EXT}"
for artifact in artifacts:
@@ -902,7 +938,7 @@ class ReticulumGitClient():
manifest = create_rsg(signer, notes, embed=True, meta=manifest_meta)
with open(manifest_path, "wb") as fh: fh.write(manifest)
except Exception as e: print(f"Release manifest generation failed: {e}"); exit(1)
except Exception as e: self.abort(f"Release manifest generation failed: {e}")
# Step 1: Initialize release
print("Initializing release on remote...")
@@ -967,8 +1003,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def delete_release(self, remote=None, target=None):
if not remote: print(f"No remote specified"); exit(1)
if not target: print(f"No target specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if not target: self.abort(f"No target specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1010,8 +1046,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def latest_release(self, remote=None, target=None):
if not remote: print(f"No remote specified"); exit(1)
if not target: print(f"No target specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if not target: self.abort(f"No target specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1057,7 +1093,7 @@ class ReticulumGitClient():
##########################
def group_permissions(self, remote=None):
if not remote: print(f"No remote specified"); exit(1)
if not remote: self.abort(f"No remote specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1107,7 +1143,7 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def repository_permissions(self, remote=None):
if not remote: print(f"No remote specified"); exit(1)
if not remote: self.abort(f"No remote specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1163,7 +1199,7 @@ class ReticulumGitClient():
########################
def work_list(self, remote=None, scope="active"):
if not remote: print(f"No remote specified"); exit(1)
if not remote: self.abort(f"No remote specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1220,8 +1256,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_view(self, remote=None, doc_id=None, scope="active"):
if not remote: print(f"No remote specified"); exit(1)
if doc_id is None: print(f"No document ID specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if doc_id is None: self.abort(f"No document ID specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1295,8 +1331,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_create(self, remote=None, title=None):
if not remote: print(f"No remote specified"); exit(1)
if not title: print(f"No title specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if not title: self.abort(f"No title specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1340,8 +1376,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_propose(self, remote=None, title=None):
if not remote: print(f"No remote specified"); exit(1)
if not title: print(f"No title specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if not title: self.abort(f"No title specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1385,8 +1421,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_edit(self, remote=None, doc_id=None, title=None, scope="active"):
if not remote: print(f"No remote specified"); exit(1)
if doc_id is None: print(f"No document ID specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if doc_id is None: self.abort(f"No document ID specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1439,8 +1475,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_delete(self, remote=None, doc_id=None, scope="active"):
if not remote: print(f"No remote specified"); exit(1)
if doc_id is None: print(f"No document ID specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if doc_id is None: self.abort(f"No document ID specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1479,8 +1515,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_comment(self, remote=None, doc_id=None, scope="active"):
if not remote: print(f"No remote specified"); exit(1)
if doc_id is None: print(f"No document ID specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if doc_id is None: self.abort(f"No document ID specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1522,8 +1558,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_complete(self, remote=None, doc_id=None):
if not remote: print(f"No remote specified"); exit(1)
if doc_id is None: print(f"No document ID specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if doc_id is None: self.abort(f"No document ID specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1561,8 +1597,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_activate(self, remote=None, doc_id=None):
if not remote: print(f"No remote specified"); exit(1)
if doc_id is None: print(f"No document ID specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if doc_id is None: self.abort(f"No document ID specified")
self.connect_remote(remote)
timeout = self.link_timeout
@@ -1600,8 +1636,8 @@ class ReticulumGitClient():
if self.link: self.link.teardown()
def work_permissions(self, remote=None, doc_id=None):
if not remote: print(f"No remote specified"); exit(1)
if doc_id is None: print(f"No document ID specified"); exit(1)
if not remote: self.abort(f"No remote specified")
if doc_id is None: self.abort(f"No document ID specified")
self.connect_remote(remote)
timeout = self.link_timeout