Skip to content

Commit 4f46515

Browse files
authored
Merge pull request #21 from pavinjosdev/dev
Safety checks and general improvements
2 parents 8d23204 + 34872c3 commit 4f46515

File tree

1 file changed

+71
-67
lines changed

1 file changed

+71
-67
lines changed

zypperoni

Lines changed: 71 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,19 @@ from shlex import quote
2929
import xml.etree.ElementTree as ET
3030

3131
# Constants
32-
ZYPPERONI_VERSION = "1.0.1"
32+
ZYPPERONI_VERSION = "1.1.0"
3333
ZYPPER_PID_FILE = "/run/zypp.pid"
34+
ZYPPER_ENV = "ZYPP_CURL2=1 ZYPP_PCK_PRELOAD=1 ZYPP_SINGLE_RPMTRANS=1"
3435

3536
################################
3637

38+
# Function to get zypper version
39+
def zypper_version():
40+
out, ret = shell_exec("zypper --version")
41+
version = out.split()[1].split(".")
42+
version = [int(x) for x in version] # MAJOR.MINOR.PATCH
43+
return version
44+
3745
# Function to get ANSI colored text
3846
def color(text_type, text):
3947
# rgb color codes
@@ -47,10 +55,12 @@ def color(text_type, text):
4755
}
4856
color = colors.get(text_type)
4957
# color only if running in terminal and color output is not disabled
50-
if color and sys.stdout.isatty() and args and not args.no_color:
51-
return f"\033[38;2;{color[0]};{color[1]};{color[2]}m{text} \033[38;2;255;255;255m"
52-
else:
53-
return text
58+
try:
59+
if color and sys.stdout.isatty() and args and not args.no_color:
60+
return f"\033[38;2;{color[0]};{color[1]};{color[2]}m{text} \033[38;2;255;255;255m"
61+
except NameError:
62+
pass
63+
return text
5464

5565
# Function to query user for yes or no
5666
def query_yes_no(question, default=None):
@@ -89,6 +99,10 @@ def release_zypp_lock():
8999

90100
# Function to unmount temp dirs provided list of UUIDs
91101
def unmount(UUID):
102+
try:
103+
ZYPPERONI_TMP_DIR
104+
except NameError:
105+
return False
92106
umount_counter = 0
93107
while umount_counter < len(UUID):
94108
umount_counter = 0
@@ -110,7 +124,14 @@ def unmount(UUID):
110124
# Function to recursively delete temp files
111125
def recursive_delete(path):
112126
# perform some sanity checks
113-
if not path.startswith(ZYPPERONI_TMP_DIR):
127+
try:
128+
ZYPPERONI_TMP_DIR
129+
except NameError:
130+
return False
131+
if not path.startswith("/tmp") or not path.startswith(ZYPPERONI_TMP_DIR):
132+
return False
133+
out, ret = shell_exec("mount -l | grep -c '/tmp/zypperoni_'")
134+
if int(out) > 0:
114135
return False
115136
command = f"rm -r {quote(path)} > /dev/null 2>&1"
116137
os.system(command)
@@ -119,7 +140,10 @@ def recursive_delete(path):
119140
# Function to cleanup on zypperoni exit
120141
def zypperoni_cleanup():
121142
release_zypp_lock()
122-
recursive_delete(ZYPPERONI_TMP_DIR)
143+
try:
144+
recursive_delete(ZYPPERONI_TMP_DIR)
145+
except NameError:
146+
pass
123147

124148
# Function to get output and exit code of shell command
125149
def shell_exec(command):
@@ -128,50 +152,32 @@ def shell_exec(command):
128152
return output.strip(), res.returncode
129153

130154
# Async function to perform zypper shell commands
131-
async def zypper_task(lock, UUID, task_type, task_item, total_items, item_counter):
155+
async def zypper_task(lock, UUID, args, task_item, total_items, item_counter):
132156
try:
133157
async with lock:
134158
uuid = UUID.pop()
135159
log_messages = {}
136160
commands = ""
137161
temp_dir = f"{ZYPPERONI_TMP_DIR}/{uuid}/rootfs"
138-
if task_type == "ref":
139-
log_messages.update({"start": f"Refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
140-
log_messages.update({"success": f"Successfully refreshed repo {task_item!r}"})
141-
log_messages.update({"error": f"Error refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
142-
log_messages.update({"exception": f"Received SIGINT while refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
143-
if not os.path.isdir(temp_dir):
144-
commands = refresh_mount_commands + refresh_shell_commands
145-
commands = commands.format(
146-
uuid=uuid,
147-
refresh_type="refresh",
148-
repo_alias=task_item,
149-
)
150-
else:
151-
commands = refresh_shell_commands.format(
152-
uuid=uuid,
153-
refresh_type="refresh",
154-
repo_alias=task_item,
155-
)
156-
elif task_type == "force-ref":
157-
log_messages.update({"start": f"Force refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
158-
log_messages.update({"success": f"Successfully force refreshed repo {task_item!r}"})
159-
log_messages.update({"error": f"Error force refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
160-
log_messages.update({"exception": f"Received SIGINT while force refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
162+
if args.command_name in ["refresh", "ref"]:
163+
log_messages.update({"start": f"{'Force ' if args.force else ''}Refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
164+
log_messages.update({"success": f"Successfully {'force ' if args.force else ''}refreshed repo {task_item!r}"})
165+
log_messages.update({"error": f"Error {'force ' if args.force else ''}refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
166+
log_messages.update({"exception": f"Received SIGINT while {'force ' if args.force else ''}refreshing repo [{item_counter}/{total_items}] {task_item!r}"})
161167
if not os.path.isdir(temp_dir):
162168
commands = refresh_mount_commands + refresh_shell_commands
163169
commands = commands.format(
164170
uuid=uuid,
165-
refresh_type="refresh --force",
171+
refresh_type="refresh --force" if args.force else "refresh",
166172
repo_alias=task_item,
167173
)
168174
else:
169175
commands = refresh_shell_commands.format(
170176
uuid=uuid,
171-
refresh_type="refresh --force",
177+
refresh_type="refresh --force" if args.force else "refresh",
172178
repo_alias=task_item,
173179
)
174-
elif task_type in ["dup", "dup-download", "in", "in-download", "inr", "inr-download"]:
180+
elif args.command_name in ["dist-upgrade", "dup", "install", "in", "install-new-recommends", "inr"]:
175181
log_messages.update({"start": f"Downloading package [{item_counter}/{total_items}] {task_item!r}"})
176182
log_messages.update({"success": f"Successfully downloaded package {task_item!r}"})
177183
log_messages.update({"error": f"Error downloading package [{item_counter}/{total_items}] {task_item!r}"})
@@ -208,14 +214,14 @@ async def zypper_task(lock, UUID, task_type, task_item, total_items, item_counte
208214
logging.debug(log_messages.get("exception"))
209215

210216
# Async function to perform multiple tasks concurrently
211-
async def main_task(num_jobs, task_type, task_items, no_confirm=None):
217+
async def main_task(task_items, args):
212218
EXCEPTION_OCCUR = False
213219
# init array of temp dir UUIDs corresponding to max num of jobs
214-
UUID = [f"{uuid4()!s}" for _ in range(num_jobs)]
220+
UUID = [f"{uuid4()!s}" for _ in range(args.jobs)]
215221
UUID_UNCHANGED = UUID.copy()
216222
total_items = len(task_items)
217223
item_counter = 0
218-
if task_type == "in":
224+
if args.command_name in ["install", "in"]:
219225
install_pkgs = task_items.copy()
220226
try:
221227
# start processing tasks
@@ -227,13 +233,11 @@ async def main_task(num_jobs, task_type, task_items, no_confirm=None):
227233
log_messages = {}
228234
task_item = task_items.pop(0)
229235
item_counter += 1
230-
if task_type == "ref":
231-
log_messages.update({"exception": "Received SIGINT while processing tasks to refresh repo"})
232-
elif task_type == "force-ref":
233-
log_messages.update({"exception": "Received SIGINT while processing tasks to force refresh repo"})
234-
elif task_type in ["dup", "dup-download", "in", "in-download", "inr", "inr-download"]:
236+
if args.command_name in ["refresh", "ref"]:
237+
log_messages.update({"exception": f"Received SIGINT while processing tasks to {'force ' if args.force else ''}refresh repo"})
238+
elif args.command_name in ["dist-upgrade", "dup", "install", "in", "install-new-recommends", "inr"]:
235239
log_messages.update({"exception": "Received SIGINT while processing tasks to download packages"})
236-
asyncio.create_task(zypper_task(lock, UUID, task_type, task_item, total_items, item_counter))
240+
asyncio.create_task(zypper_task(lock, UUID, args, task_item, total_items, item_counter))
237241
await asyncio.sleep(0.1)
238242
# finished processing all tasks
239243
tasks = asyncio.all_tasks()
@@ -258,19 +262,19 @@ async def main_task(num_jobs, task_type, task_items, no_confirm=None):
258262
# release zypper exclusive lock
259263
release_zypp_lock()
260264
# perform additional zypper commands (if any) on no exception
261-
if not EXCEPTION_OCCUR:
265+
if not EXCEPTION_OCCUR and \
266+
args.command_name in ["dist-upgrade", "dup", "install", "in", "install-new-recommends", "inr"] and \
267+
not args.download_only:
262268
msg = "Zypperoni has finished its tasks. Handing you over to zypper..."
263-
if task_type == "dup":
264-
logging.info(color("info", msg))
265-
command = f"env ZYPP_SINGLE_RPMTRANS=1 zypper {'--non-interactive' if no_confirm else ''} --no-cd dist-upgrade"
269+
logging.info(color("info", msg))
270+
if args.command_name in ["dist-upgrade", "dup"]:
271+
command = f"env {ZYPPER_ENV} zypper {'--non-interactive' if args.no_confirm else ''} --no-cd dist-upgrade"
266272
os.system(command)
267-
elif task_type == "in":
268-
logging.info(color("info", msg))
269-
command = f"env ZYPP_SINGLE_RPMTRANS=1 zypper {'--non-interactive' if no_confirm else ''} --no-cd install {' '.join(install_pkgs)}"
273+
elif args.command_name in ["install", "in"]:
274+
command = f"env {ZYPPER_ENV} zypper {'--non-interactive' if args.no_confirm else ''} --no-cd install {' '.join(install_pkgs)}"
270275
os.system(command)
271-
elif task_type == "inr":
272-
logging.info(color("info", msg))
273-
command = f"env ZYPP_SINGLE_RPMTRANS=1 zypper {'--non-interactive' if no_confirm else ''} --no-cd install-new-recommends"
276+
elif args.command_name in ["install-new-recommends", "inr"]:
277+
command = f"env {ZYPPER_ENV} zypper {'--non-interactive' if args.no_confirm else ''} --no-cd install-new-recommends"
274278
os.system(command)
275279

276280
################################
@@ -396,7 +400,7 @@ mount -o bind,ro /var/lib/ca-certificates {ZYPPERONI_TMP_DIR}/{{uuid}}/rootfs/va
396400

397401
# Shell commands to perform zypper refresh / force-refresh
398402
refresh_shell_commands = f"""
399-
chroot {ZYPPERONI_TMP_DIR}/{{uuid}}/rootfs env -i zypper --non-interactive {{refresh_type}} {{repo_alias}};
403+
chroot {ZYPPERONI_TMP_DIR}/{{uuid}}/rootfs env -i {ZYPPER_ENV} zypper --non-interactive {{refresh_type}} {{repo_alias}};
400404
"""
401405

402406
# Shell commands to prepare temp mounts for zypper download
@@ -420,7 +424,7 @@ mount -o bind,ro /var/lib/ca-certificates {ZYPPERONI_TMP_DIR}/{{uuid}}/rootfs/va
420424

421425
# Shell commands to perform zypper download
422426
download_shell_commands = f"""
423-
chroot {ZYPPERONI_TMP_DIR}/{{uuid}}/rootfs env -i zypper --non-interactive download {{pkg_name}};
427+
chroot {ZYPPERONI_TMP_DIR}/{{uuid}}/rootfs env -i {ZYPPER_ENV} zypper --non-interactive download {{pkg_name}};
424428
"""
425429

426430
# Dirs to unmount (one per line)
@@ -439,7 +443,7 @@ if args.command_name in ["refresh", "ref"]:
439443
# get all enabled repos
440444
logging.info(color("info", "Getting all enabled repos"))
441445
REPO_ALIAS = []
442-
xml_output, ret = shell_exec("env -i zypper --non-interactive --no-cd --xmlout repos")
446+
xml_output, ret = shell_exec(f"env -i {ZYPPER_ENV} zypper --non-interactive --no-cd --xmlout repos")
443447
logging.debug(xml_output)
444448
get_zypp_lock()
445449
docroot = ET.fromstring(xml_output)
@@ -452,7 +456,7 @@ if args.command_name in ["refresh", "ref"]:
452456
zypperoni_cleanup()
453457
sys.exit()
454458
try:
455-
asyncio.run(main_task(args.jobs, "force-ref" if args.force else "ref", REPO_ALIAS))
459+
asyncio.run(main_task(REPO_ALIAS, args))
456460
except asyncio.exceptions.CancelledError:
457461
logging.debug("Received SIGINT for asyncio runner")
458462
except:
@@ -462,7 +466,7 @@ if args.command_name in ["refresh", "ref"]:
462466
elif args.command_name in ["dist-upgrade", "dup"]:
463467
# get info about dup packages
464468
logging.info(color("info", "Getting all packages to be downloaded for distribution upgrade"))
465-
xml_output, ret = shell_exec("env -i zypper --non-interactive --no-cd --xmlout dist-upgrade --dry-run")
469+
xml_output, ret = shell_exec(f"env -i {ZYPPER_ENV} zypper --non-interactive --no-cd --xmlout dist-upgrade --dry-run")
466470
logging.debug(xml_output)
467471
if ret == 0 and xml_output.find("Nothing to do") != -1:
468472
logging.info(color("info", "Nothing to do. Exiting..."))
@@ -498,7 +502,7 @@ elif args.command_name in ["dist-upgrade", "dup"]:
498502
logging.warning(color("warning", msg))
499503
# get info about dup packages from 'zypper lu'
500504
logging.info(color("info", "Getting all packages to be upgraded"))
501-
xml_output, ret = shell_exec("env -i zypper --non-interactive --no-cd --xmlout list-updates --type package --all")
505+
xml_output, ret = shell_exec(f"env -i {ZYPPER_ENV} zypper --non-interactive --no-cd --xmlout list-updates --type package --all")
502506
logging.debug(xml_output)
503507
docroot = ET.fromstring(xml_output)
504508
# parse all packages from xml output
@@ -520,15 +524,15 @@ elif args.command_name in ["dist-upgrade", "dup"]:
520524
if not args.download_only and download_size_bytes == 0:
521525
zypperoni_cleanup()
522526
logging.info(color("info", "Zypperoni has finished its tasks. Handing you over to zypper..."))
523-
command = f"env ZYPP_SINGLE_RPMTRANS=1 zypper {'--non-interactive' if args.no_confirm else ''} --no-cd dist-upgrade"
527+
command = f"env {ZYPPER_ENV} zypper {'--non-interactive' if args.no_confirm else ''} --no-cd dist-upgrade"
524528
os.system(command)
525529
sys.exit()
526530
logging.info(color("info", f"Packages to download: {' '.join(DUP_PKG)}"))
527531
if not args.no_confirm and not query_yes_no("Would you like to continue?", default="yes"):
528532
zypperoni_cleanup()
529533
sys.exit()
530534
try:
531-
asyncio.run(main_task(args.jobs, "dup-download" if args.download_only else "dup", DUP_PKG, args.no_confirm))
535+
asyncio.run(main_task(DUP_PKG, args))
532536
except asyncio.exceptions.CancelledError:
533537
logging.debug("Received SIGINT for asyncio runner")
534538
except:
@@ -538,7 +542,7 @@ elif args.command_name in ["dist-upgrade", "dup"]:
538542
elif args.command_name in ["install", "in"]:
539543
# get info about install packages
540544
logging.info(color("info", "Getting packages and their dependencies to be downloaded for installation"))
541-
xml_output, ret = shell_exec(f"env -i zypper --non-interactive --no-cd --xmlout install --dry-run {' '.join(args.package)}")
545+
xml_output, ret = shell_exec(f"env -i {ZYPPER_ENV} zypper --non-interactive --no-cd --xmlout install --dry-run {' '.join(args.package)}")
542546
logging.debug(xml_output)
543547
if ret == 0 and xml_output.find("Nothing to do") != -1:
544548
logging.info(color("info", "Nothing to do. Exiting..."))
@@ -596,15 +600,15 @@ elif args.command_name in ["install", "in"]:
596600
if not args.download_only and download_size_bytes == 0:
597601
zypperoni_cleanup()
598602
logging.info(color("info", "Zypperoni has finished its tasks. Handing you over to zypper..."))
599-
command = f"env ZYPP_SINGLE_RPMTRANS=1 zypper {'--non-interactive' if args.no_confirm else ''} --no-cd install {' '.join(args.package)}"
603+
command = f"env {ZYPPER_ENV} zypper {'--non-interactive' if args.no_confirm else ''} --no-cd install {' '.join(args.package)}"
600604
os.system(command)
601605
sys.exit()
602606
logging.info(color("info", f"Packages to download: {' '.join(IN_PKG)}"))
603607
if not args.no_confirm and not query_yes_no("Would you like to continue?", default="yes"):
604608
zypperoni_cleanup()
605609
sys.exit()
606610
try:
607-
asyncio.run(main_task(args.jobs, "in-download" if args.download_only else "in", IN_PKG, args.no_confirm))
611+
asyncio.run(main_task(IN_PKG, args))
608612
except asyncio.exceptions.CancelledError:
609613
logging.debug("Received SIGINT for asyncio runner")
610614
except:
@@ -614,7 +618,7 @@ elif args.command_name in ["install", "in"]:
614618
elif args.command_name in ["install-new-recommends", "inr"]:
615619
# get info about recommended install packages
616620
logging.info(color("info", "Getting new packages and their dependencies to be downloaded for recommended installation"))
617-
xml_output, ret = shell_exec(f"env -i zypper --non-interactive --no-cd --xmlout install-new-recommends --dry-run")
621+
xml_output, ret = shell_exec(f"env -i {ZYPPER_ENV} zypper --non-interactive --no-cd --xmlout install-new-recommends --dry-run")
618622
logging.debug(xml_output)
619623
if ret == 0 and xml_output.find("Nothing to do") != -1:
620624
logging.info(color("info", "Nothing to do. Exiting..."))
@@ -663,15 +667,15 @@ elif args.command_name in ["install-new-recommends", "inr"]:
663667
if not args.download_only and download_size_bytes == 0:
664668
zypperoni_cleanup()
665669
logging.info(color("info", "Zypperoni has finished its tasks. Handing you over to zypper..."))
666-
command = f"env ZYPP_SINGLE_RPMTRANS=1 zypper {'--non-interactive' if args.no_confirm else ''} --no-cd install-new-recommends"
670+
command = f"env {ZYPPER_ENV} zypper {'--non-interactive' if args.no_confirm else ''} --no-cd install-new-recommends"
667671
os.system(command)
668672
sys.exit()
669673
logging.info(color("info", f"Packages to download: {' '.join(INR_PKG)}"))
670674
if not args.no_confirm and not query_yes_no("Would you like to continue?", default="yes"):
671675
zypperoni_cleanup()
672676
sys.exit()
673677
try:
674-
asyncio.run(main_task(args.jobs, "inr-download" if args.download_only else "inr", INR_PKG, args.no_confirm))
678+
asyncio.run(main_task(INR_PKG, args))
675679
except asyncio.exceptions.CancelledError:
676680
logging.debug("Received SIGINT for asyncio runner")
677681
except:

0 commit comments

Comments
 (0)