Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions src/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#

#
# Copyright 2024 OmniOS Community Edition (OmniOSce) Association.
# Copyright 2025 OmniOS Community Edition (OmniOSce) Association.
# Copyright 2024 Oxide Computer Company
# Copyright (c) 2007, 2024, Oracle and/or its affiliates.
#
Expand All @@ -41,8 +41,8 @@
# Environment variables
#
# PKG_IMAGE - root path of target image
# PKG_IMAGE_TYPE [entire, partial, user] - type of image
# XXX or is this in the Image configuration?
# PKG_SUCCESS_ON_NOP - when an operation completes with nothing to do, exit with
# the success code (0) instead of the NOP one (4).

try:
import pkg.site_paths
Expand Down Expand Up @@ -200,6 +200,8 @@
tmpdirs = []
tmpfiles = []

EXIT_NOP_VAL = EXIT_OK if os.environ.get("PKG_SUCCESS_ON_NOP") else EXIT_NOP


@atexit.register
def cleanup():
Expand Down Expand Up @@ -620,7 +622,8 @@ def print_cmds(cmd_list, cmd_dic):
--help or -?

Environment:
PKG_IMAGE"""
PKG_IMAGE
PKG_SUCCESS_ON_NOP"""
)
)
else:
Expand Down Expand Up @@ -898,6 +901,8 @@ def gen(meta=False):
if errors:
_generate_error_messages(out_json["status"], errors)

if out_json["status"] == EXIT_NOP:
return EXIT_NOP_VAL
return out_json["status"]


Expand Down Expand Up @@ -2499,7 +2504,7 @@ def __api_op(
if _op == PKG_OP_FIX and _noexecute and _quiet_plan:
return _verify_exit_code(_api_inst)
if _api_inst.planned_nothingtodo():
return EXIT_NOP
return EXIT_NOP_VAL
if _noexecute or _stage == API_STAGE_PLAN:
return EXIT_OK
else:
Expand Down Expand Up @@ -2818,6 +2823,8 @@ def __handle_client_json_api_output(out_json, op, api_inst):
display_repo_failures(out_json["data"]["repo_status"])

__display_plan_messages(api_inst, frozenset([OP_STAGE_PREP, OP_STAGE_EXEC]))
if out_json["status"] == EXIT_NOP:
return EXIT_NOP_VAL
return out_json["status"]


Expand Down Expand Up @@ -3525,7 +3532,7 @@ def autoremove(

if not pargs:
msg(_("No removable packages for this image."))
return EXIT_NOP
return EXIT_NOP_VAL

out_json = client_api._uninstall(
PKG_OP_UNINSTALL,
Expand Down Expand Up @@ -3602,6 +3609,8 @@ def verify(

# Since the verify output has been handled by display_plan_cb, only
# status code needs to be returned.
if out_json["status"] == EXIT_NOP:
return EXIT_NOP_VAL
return out_json["status"]


Expand Down Expand Up @@ -3714,6 +3723,8 @@ def fix(
if "errors" in out_json:
_generate_error_messages(out_json["status"], out_json["errors"], cmd=op)

if out_json["status"] == EXIT_NOP:
return EXIT_NOP_VAL
return out_json["status"]


Expand Down Expand Up @@ -3937,7 +3948,7 @@ def set_mediator(
return EXIT_OOPS
else:
msg(_("No changes required."))
return EXIT_NOP
return EXIT_NOP_VAL

if api_inst.get_dehydrated_publishers():
msg(
Expand Down Expand Up @@ -4038,7 +4049,7 @@ def unset_mediator(
return EXIT_OOPS
else:
msg(_("No changes required."))
return EXIT_NOP
return EXIT_NOP_VAL

if not quiet:
__display_plan(api_inst, verbose, noexecute)
Expand Down Expand Up @@ -4172,7 +4183,7 @@ def unfreeze(api_inst, args):
try:
pkgs = api_inst.freeze_pkgs(pargs, unfreeze=True, dry_run=dry_run)
if not pkgs:
return EXIT_NOP
return EXIT_NOP_VAL
for s in pkgs:
logger.info(_("{0} was unfrozen.").format(s))
return EXIT_OK
Expand Down Expand Up @@ -5594,6 +5605,8 @@ def publisher_set(
add_info={"repo_uri": repo_uri},
)

if out_json["status"] == EXIT_NOP:
return EXIT_NOP_VAL
return out_json["status"]


Expand All @@ -5608,6 +5621,8 @@ def publisher_unset(api_inst, pargs):
out_json["status"], out_json["errors"], cmd="unset-publisher"
)

if out_json["status"] == EXIT_NOP:
return EXIT_NOP_VAL
return out_json["status"]


Expand Down Expand Up @@ -7202,7 +7217,7 @@ def update_format(api_inst, pargs):
return EXIT_OK

logger.info(_("Image format already current."))
return EXIT_NOP
return EXIT_NOP_VAL


def print_version(pargs):
Expand Down
24 changes: 17 additions & 7 deletions src/man/pkg.1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.\" Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
.\" Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
.\" Copyright 2024 Oxide Computer Company
.Dd December 10, 2024
.\" Copyright 2025 OmniOS Community Edition (OmniOSce) Association.
.Dd June 25, 2025
.Dt PKG 1
.Os
.Sh NAME
Expand Down Expand Up @@ -4331,6 +4331,12 @@ before a connection is aborted.
A value of 0 means do not abort the operation.
.Pp
Default value: 5
.It Sy PKG_CLIENT_MAX_TIMEOUT
Maximum number of transport attempts per host before the client aborts the
operation.
A value of 0 means do not abort the operation.
.Pp
Default value: 4
.It Sy PKG_CONCURRENCY
The number of child images to update in parallel.
Ignored if the
Expand All @@ -4353,12 +4359,16 @@ If
is 0 or a negative number, all child images are updated in parallel.
.Pp
Default value: 1
.It Sy PKG_CLIENT_MAX_TIMEOUT
Maximum number of transport attempts per host before the client aborts the
operation.
A value of 0 means do not abort the operation.
.It Sy PKG_SUCCESS_ON_NOP
When set to a non-zero value, cause
.Nm
operations that result in there being no changes to apply to exit
successfully, with exit status 0
.Pq Command succeeded ,
rather than with exit status 4
.Pq \&No changes were made - nothing to do .
.Pp
Default value: 4
Default value: 0
.It Sy http_proxy , Sy https_proxy
HTTP or HTTPS proxy server.
.El
Expand Down
5 changes: 5 additions & 0 deletions src/tests/pkg5testenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ def setup_environment(path_to_proto, debug=False, system_test=False):
if k.startswith("PKG_") or k.lower().endswith("_proxy"):
del os.environ[k]

# This environment variable changes the exit status of operations that
# result in no changes being required. Unset it so tests get the expected
# behaviour.
os.environ.pop("PKG_SUCCESS_ON_NOP", None)

#
# Tell package manager where its application data files live.
#
Expand Down