LIBDNF5-ACTIONS(8)

๐Ÿฆ‹ NAME ๐Ÿฆ‹

Okay bestie, this is libdnf5-actions โ€” the Actions Plugin page from the dnf5 manual. It's giving backstage-pass energy for callback hooks, and we're gonna get into all of it. ๐Ÿ’…

โœจ DESCRIPTION โœจ

This plugin lets you define actions that get executed through libdnf5 callback hooks. Each action is hooked to one specific callback. Actions for goal_resolved, pre_transaction, and post_transaction callbacks may define a glob-like filtering rule on the package NEVRA or package files, plus whether the package is incoming or outgoing. No cap.

๐ŸŽ€ CONFIGURATION ๐ŸŽ€

The plugin does not extend the standard configuration. However, it reads actions files.

The actions files are read from the <libdnf5_plugins_config_dir>/actions.d/ directory. Only files with a .actions extension are read. Action files are read in lexical order (sorted by filename) by comparing character values, ignoring locale. Slay.

๐ŸŒธ ACTIONS FILE FORMAT ๐ŸŒธ

Empty lines and lines that start with a # character (comment line) are ignored.

Each non-comment line defines an action and consists of five items separated by colons: callback_name:package_filter:direction:options:command.

๐Ÿ’– callback_name ๐Ÿ’–

  • pre_base_setup
  • post_base_setup
  • repos_configured (added in version 1.0.0)
  • repos_loaded (added in version 1.0.0)
  • pre_add_cmdline_packages (added in version 1.0.0)
  • post_add_cmdline_packages (added in version 1.0.0)
  • goal_resolved (added in version 1.3.0)
  • pre_transaction
  • post_transaction

๐Ÿฆ‹ package_filter ๐Ÿฆ‹

A glob-like filtering rule applied on the package NEVRA (also in the shortened forms) or package files.

An empty filter means executing the command once with no information about the package. The "*" filter means executing the command for each package in the transaction that matches the direction filter. The filter can be non-empty only for goal_resolved, pre_transaction, and post_transaction callbacks.

โญ direction โญ

Filters packages by their direction (coming into the system/going out of the system) in a transaction. If empty, the filter is not applied. The filter can only be non-empty when combined with a non-empty package_filter.

  • in โ€” packages coming to the system (downgrade, install, reinstall, upgrade)
  • out โ€” packages going out of the system (upgraded, downgraded, reinstalled, removed, replaced/obsoleted)

๐Ÿ”ฅ options ๐Ÿ”ฅ

Options are separated by spaces. A space within an option can be written using escaping.

  • enabled=<value> โ€” the <value> specifies when the action is enabled (added in version 0.3.0)
    • 1 โ€” action is always enabled
    • host-only โ€” the action is only enabled for operations on the host
    • installroot-only โ€” the action is only enabled for operations in the alternative installroot
  • mode=<value> โ€” the <value> specifies the action plugin communication mode (added in version 1.2.0)
    • plain โ€” the original and default communication mode. Data is only passed to the process via arguments when it is started. The process can send data to the actions plugin by writing to standard output.
    • json โ€” this mode establishes a bidirectional communication channel between the actions plugin and the process, using a request-response model. The process writes requests to standard output and reads responses from standard input. All communication is handled in JSON format (added in version 1.2.0).
  • raise_error=<value> โ€” the <value> specifies how action process errors are handled. What happens if the action process did not start, ended with a non-zero exit code, ended abnormally (received a signal), or an error occurred during communication (syntax error, communication interrupt, failed to process the output line in plain communication mode). If the option is not present, raise_error=0 for backward compatibility (added in version 1.4.0).
    • 0 โ€” the errors are logged
    • 1 โ€” an exception is thrown

๐ŸŒˆ command ๐ŸŒˆ

Any executable file with arguments.

Arguments are separated by spaces. A space within an argument can be written using escaping. Escaping can also be used to prevent substitution and to pass special characters: \a, \b, \f, \n, \r, \t, \v. Unescaping of arguments is done after substitution.

The following variables in the command will be substituted:

  • ${pid} โ€” process ID
  • ${plugin.version} โ€” version of the actions plugin (added in version 0.3.0)
  • ${conf.<option_name>} โ€” option from base configuration
  • ${conf.<repoid_pattern>.<option_name>[=<value_pattern>]} โ€” list of repoid.option=value pairs (added in version 1.1.0)
  • ${var.<variable_name>} โ€” variable
  • ${tmp.<actions_plugin_variable_name>} โ€” variable exists only in actions plugin context
  • ${pkg.<package_attribute_name>} โ€” value of the package attribute
    • ${pkg.name} โ€” package name
    • ${pkg.arch} โ€” package arch
    • ${pkg.version} โ€” package version
    • ${pkg.release} โ€” package release
    • ${pkg.epoch} โ€” package epoch
    • ${pkg.na} โ€” package name.architecture
    • ${pkg.evr} โ€” package epoch-version-release
    • ${pkg.nevra} โ€” package nevra (0 epoch is hidden)
    • ${pkg.full_nevra} โ€” package full nevra (epoch is always present) name-epoch:version-release.arch
    • ${pkg.repo_id} โ€” package repository id
    • ${pkg.license} โ€” package license
    • ${pkg.location} โ€” package relative path/location from repodata
    • ${pkg.vendor} โ€” package vendor
    • ${pkg.action} โ€” action performed on the package:
      • I โ€” newly installed package
      • U โ€” package installed as an upgrade
      • D โ€” package installed as a downgrade
      • R โ€” package used for reinstallation
      • E โ€” erased (removed) package from the system
      • O โ€” replaced package (was obsoleted/upgraded/downgraded/reinstalled/removed)
      • ? โ€” package with changed installation reason

The command will be evaluated for each package that matched the package_filter and the direction. However, after variable substitution, any duplicate commands will be removed and each command will only be executed once per transaction. The commands are executed in sequence. There is no parallelism. Argument substitution is performed after the previous command has completed. This allows the substitution to use the results of the previous commands. The order of execution of the commands follows the order in the action files, but may differ from the order of packages in the transaction. In other words, when you define several action lines for the same package_filter and direction these lines will be executed in the order they were defined in the action file when the package_filter and direction matches a package. However, the order of when a particular package_filter is invoked depends on the position of the corresponding package in the transaction.

The repoid.option=value pairs in the list are separated by the , character. The , character in the value is replaced by the escape sequence "\x2C". If value_pattern is used, only pairs with the matching value are listed. The repoid_pattern and value_pattern can contain globs.

๐Ÿ“ฌ ACTION PROCESS STANDARD OUTPUT FORMAT IN PLAIN COMMUNICATION MODE ๐Ÿ“ฌ

The standard output of each executed action (command) is captured and processed. Each line of output can change the value of a base configuration option, the value of a configuration option in matching repositories, or a variable. It can also set or unset one actions plugin variable. The value of this variable is available for the following commands using the ${tmp.<actions_plugin_variable_name>} substitution.

Actions should change the repositories configuration in the repos_configured hook. At this point, the repositories configuration is loaded but not yet applied.

Since version 1.4.0, the output line can write a message to the logger, throw a stop exception and an error exception.

๐Ÿ“ Output line format ๐Ÿ“

  • tmp.<actions_plugin_variable_name>=<value> โ€” sets the value of action plugins variable <actions_plugin_variable_name>
  • tmp.<actions_plugin_variable_name> โ€” removes the action plugins variable if it exists
  • conf.<option_name>=<value> โ€” sets the value of option <option_name> in the base configuration
  • conf.<repoid_pattern>.<option_name>=<value> โ€” sets the value of option <option_name> in the matching repositories (added in version 1.1.0)
  • var.<variable_name>=<value> โ€” sets value of the variable <variable_name>
  • stop=<message> โ€” throws a stop exception with <message> (added in version 1.4.0)
  • error=<message> โ€” the error <message> is logged or throws an error exception with <message> if raise_error=1 (added in version 1.4.0)
  • log.<level>=<message> โ€” writes <message> to the logger with priority <level> (added in version 1.4.0)
    • Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACE

๐Ÿงš MESSAGES IN JSON COMMUNICATION MODE ๐Ÿงš

The "json" communication mode establishes a bidirectional communication channel between the actions plugin and the process, using a request-response model. The process writes requests to standard output and reads responses from the actions plugin from standard input. All communication is handled in JSON format.

Format of a request message from the process:

{"op":"<operation>", "domain":"<domain>", "args":{}}

Format of a response:

{"op":"reply", "requested_op":"<operation_from_request>", "domain":"<domain_from_request>",
  "status":"OK", "return":{}}

Format of a response with an error message:

{"op":"reply", "requested_op":"<operation_from_request>", "domain":"<domain_from_request>",
  "status":"ERROR", "message":"<error message>"}

๐Ÿ’œ Supported <operation> values ๐Ÿ’œ

  • get โ€” request to get values
  • set โ€” request to set a value
  • new โ€” creates a new repository configuration
  • log โ€” writes a message to the logger (added in version 1.4.0)
  • stop โ€” throws a stop exception with a message โœ‹ (added in version 1.4.0)
  • error โ€” the error message gets logged, or it throws an error exception with a message if raise_error=1 (added in version 1.4.0)

๐Ÿ’… Description of the get operation ๐Ÿ’…

bestie, get is not one single thing โ€” it's defined separately for a whole bunch of <domain>s, and you gotta pick the right one:

  • conf โ€” a request to read the value of a configuration (global configuration or repository configuration)
  • vars โ€” a request to read the value of variables
  • actions_vars โ€” a request to read the value of action plugin variables โ€” these bad boys exist only in the actions plugin context
  • actions_attrs โ€” lets you get the process ID and the version of the actions plugin
  • packages โ€” a request to list available and installed packages
  • trans_packages โ€” a request to list packages in the transaction
  • cmdline_packages_paths โ€” a request to list command-line packages

๐ŸŽ€ Reading a configuration value ๐ŸŽ€

Request format, literally copy-paste this energy:

{"op":"get", "domain":"conf", "args":{"key":"<name>"}}

{"op":"get", "domain":"conf", "args":{"key":"<repo_id>.<name>"}}

Response format:

{"op":"reply", "requested_op":"get", "domain":"conf", "status":"OK",
  "return":{"keys_val":[{"key":"<name>", "value":"<value>"}]}}

{"op":"reply", "requested_op":"get", "domain":"conf", "status":"OK",
  "return":{"keys_val":[{"key":"<repo_id>.<name>", "value":"<value>"}]}}

{"op":"reply", "requested_op":"get", "domain":"conf", "status":"ERROR",
  "message":"<error message>"}

Description, iykyk:

  • <name> โ€” the name of a global configuration option, or a repository configuration option if <repo_id> is specified
  • <repo_id> โ€” the repository ID; in a request it can contain globs, in which case the value of the configuration option is read for all matching repositories
  • <value> โ€” the read value of the configuration option
  • <error message> โ€” error message
โœจ the way that <repo_id> works? when you use it, the number of keys_val items in the response depends on the number of matching repositories ๐Ÿ‘‘

Example:

{"op":"get", "domain":"conf", "args":{"key":"countme"}}
{"op":"reply", "requested_op":"get", "domain":"conf", "status":"OK",
  "return":{"keys_val":[{"key":"countme", "value":"0"}]}}

{"op": "get", "domain": "conf", "args": {"key": "*.enabled"}}
{"op": "reply", "requested_op": "get", "domain": "conf", "status": "OK",
  "return": {
    "keys_val": [{"key": "dnf-ci-fedora.enabled", "value": "1"},
                 {"key": "dnf-ci-fedora-updates.enabled", "value": "1"},
                 {"key": "dnf-ci-thirdparty.enabled", "value": "0"},
                 {"key": "test-repo.enabled", "value": "0"}]}}

๐Ÿฆ‹ Reading a variable value ๐Ÿฆ‹

Request format:

{"op":"get", "domain":"vars", "args":{"name":"<name>"}}

Response format:

{"op":"reply", "requested_op":"get", "domain":"vars", "status":"OK",
  "return":{"vars":[{"name":"<name>", "value":"<value>"}]}}

Description:

  • <name> โ€” the name of the variable; in a request it can contain globs, in which case the value of all matching variables will be read
  • <value> โ€” the read value of the variable

the number of vars items in the response depends on the number of matching variables, no cap ๐Ÿซถ

Example:

{"op":"get", "domain":"vars", "args":{"name":"test_var*"}}
{"op":"reply", "requested_op":"get", "domain":"vars", "status":"OK",
  "return":{"vars":[{"name":"test_var1", "value":"value1"}]}}

{"op":"get", "domain":"vars", "args":{"name":"nonexist_var"}}
{"op":"reply", "requested_op":"get", "domain":"vars", "status":"OK",
  "return":{"vars":[]}}

๐ŸŒธ Reading an action variable value ๐ŸŒธ

Request format:

{"op":"get", "domain":"actions_vars", "args":{"name":"<name>"}}

Response format:

{"op":"reply", "requested_op":"get", "domain":"actions_vars", "status":"OK",
  "return":{"actions_vars":[{"name":"<name>", "value":"<value>"}]}}

Description:

  • <name> โ€” the name of the action variable; in a request it can contain globs, in which case the value of all matching action variables will be read
  • <value> โ€” the read value of the action variable

the number of actions_vars items in the response depends on the number of matching action variables ๐Ÿ’–

Example:

{"op":"get", "domain":"actions_vars", "args":{"name":"test_actions_var*"}}
{"op":"reply", "requested_op":"get", "domain":"actions_vars", "status":"OK",
  "return":{"actions_vars":[{"name":"test_actions_var1", "value":"value1"}]}}

{"op":"get", "domain":"actions_vars", "args":{"name":"nonexist_var"}}
{"op":"reply", "requested_op":"get", "domain":"actions_vars", "status":"OK",
  "return":{"actions_vars":[]}}

๐Ÿงš Reading an action attribute value ๐Ÿงš

Request format:

{"op":"get", "domain":"actions_attrs", "args":{"key":"<name>"}}

Response format:

{"op":"reply", "requested_op":"get", "domain":"actions_attrs", "status":"OK",
  "return":{"actions_attrs":[{"key":"<name>", "value":"<value>"}]}}

Description:

  • <name> โ€” the name of the action attribute; in a request it can contain globs, in which case the value of all matching actions plugin attributes will be read. Currently, two attributes are supported: pid and version.
    • pid โ€” the process ID of the actions plugin
    • version โ€” the version of the actions plugin, a string in the format MAJOR.MINOR.MICRO
  • <value> โ€” the read value of the action attribute

the number of actions_attrs items in the response depends on the number of matching action attributes ๐ŸŽ€

Example:

{"op":"get", "domain":"actions_attrs", "args":{"key":"*"}}
{"op":"reply", "requested_op":"get", "domain":"actions_attrs", "status":"OK",
  "return":{
    "actions_attrs":[{"key":"pid", "value":"523"},
                     {"key":"version", "value":"1.4.0"}]}}

{"op":"get", "domain":"actions_attrs", "args":{"key":"nonexist_attribute"}}
{"op":"reply", "requested_op":"get", "domain":"actions_attrs", "status":"OK",
  "return":{"actions_attrs":[]}}

๐Ÿ‘‘ Getting available, installed, and transaction packages ๐Ÿ‘‘

okay so quick vibe check on the domains here: the packages domain is what you use to get available and installed packages. The trans_packages domain is what you use to get packages in the transaction. And heads up โ€” the trans_packages domain can only be used in goal_resolved, pre_transaction, and post_transaction callbacks. no exceptions, bestie ๐Ÿ’€

Request format:

{"op":"get", "domain":"packages", "args":{"output":["<pkg_attr>"]}}

{"op":"get", "domain":"packages", "args":{"output":["<pkg_attr>"],
  "params":[{"key":"<param_name>"}]}}

{"op":"get", "domain":"packages", "args":{"output":["<pkg_attr>"],
  "filters":[{"key":"<filter_name>", "value":"<value>", "operator":"<cmp_operator>"}]}}

{"op":"get", "domain":"packages", "args":{"output":["<pkg_attr>"],
  "params":[{"key":"<param_name>"}],
  "filters":[{"key":"<filter_name>", "value":"<value>", "operator":"<cmp_operator>"}]}}

{"op":"get", "domain":"trans_packages", "args":{"output":["<pkg_attr>"]}}

{"op":"get", "domain":"trans_packages", "args":{"output":["<pkg_attr>"],
  "params":[{"key":"<param_name>"}]}}

{"op":"get", "domain":"trans_packages", "args":{"output":["<pkg_attr>"],
  "filters":[{"key":"<filter_name>", "value":"<value>", "operator":"<cmp_operator>"}]}}

{"op":"get", "domain":"trans_packages", "args":{"output":["<pkg_attr>"],
  "params":[{"key":"<param_name>"}],
  "filters":[{"key":"<filter_name>", "value":"<value>", "operator":"<cmp_operator>"}]}}

Response format:

{"op":"reply", "requested_op":"get", "domain":"packages", "status":"OK",
  "return":{"packages":[{"<pkg_attr>":"<attr_value>"}]}}

{"op":"reply", "requested_op":"get", "domain":"trans_packages", "status":"OK",
  "return":{"trans_packages":[{"<pkg_attr>":"<attr_value>"}]}}

{"op":"reply", "requested_op":"get", "domain":"packages", "status":"ERROR",
  "message":"<error message>"}

{"op":"reply", "requested_op":"get", "domain":"trans_packages", "status":"ERROR",
  "message":"<error message>"}

Description:

  • <pkg_attr> โ€” package attribute to return; the array can contain multiple attributes

General attributes ๐ŸŒˆ

  • name โ€” package name
  • arch โ€” package arch
  • version โ€” package version
  • release โ€” package release
  • epoch โ€” package epoch
  • na โ€” package name.architecture
  • evr โ€” package epoch-version-release
  • nevra โ€” package nevra (0 epoch is hidden)
  • full_nevra โ€” package full nevra (epoch is always present) name-epoch:version-release.arch
  • download_size โ€” filesize of package
  • install_size โ€” size the package should occupy after installing on disk
  • repo_id โ€” package repository id
  • license โ€” package license
  • location โ€” package relative path/location from repodata
  • vendor โ€” package vendor

Attributes usable only with the trans_packages domain ๐Ÿ’…

  • action โ€” action performed on the package
    • I โ€” newly installed package
    • U โ€” package installed as an upgrade
    • D โ€” package installed as a downgrade
    • R โ€” package used for reinstallation
    • E โ€” erased (removed) package from the system
    • O โ€” replaced package (was obsoleted/upgraded/downgraded/reinstalled/removed)
    • ? โ€” package with changed installation reason
  • direction โ€” package direction in transaction
    • IN โ€” packages coming to the system (downgrade, install, reinstall, upgrade)
    • OUT โ€” packages going out of the system (upgraded, downgraded, reinstalled, removed, replaced/obsoleted)
  • <param_name> โ€” one of IGNORE_EXCLUDES, IGNORE_MODULAR_EXCLUDES, IGNORE_REGULAR_EXCLUDES, IGNORE_REGULAR_CONFIG_EXCLUDES, IGNORE_REGULAR_USER_EXCLUDES
  • <filter_name> โ€” name of package attribute to filter; the array can contain multiple attributes

General attributes โญ

  • name โ€” filter packages by name, uses <value> and <cmp_operator>
  • arch โ€” filter packages by arch, uses <value> and <cmp_operator>
  • version โ€” filter packages by version, uses <value> and <cmp_operator>
  • release โ€” filter packages by release, uses <value> and <cmp_operator>
  • epoch โ€” filter packages by epoch, uses <value> and <cmp_operator>
  • nevra โ€” filter packages by nevra (0 epoch is hidden), uses <value> and <cmp_operator>
  • repo_id โ€” filter packages by repository id, uses <value> and <cmp_operator>
  • available โ€” filter available packages
  • installed โ€” filter installed packages
  • userinstalled โ€” filter user installed packages
  • installonly โ€” filter installonly packages
  • description โ€” filter packages by description, uses <value> and <cmp_operator>
  • file โ€” filters packages by the files they contain; uses <value> and <cmp_operator> ๐Ÿ—‚๏ธ
  • upgradable โ€” filters installed packages that actually have upgrades available ๐Ÿ”ผ
  • upgrades โ€” filters available packages that count as upgrades to installed packages โฌ†๏ธ
  • downgradable โ€” filters installed packages that have downgrades available ๐Ÿ”ฝ
  • downgrades โ€” filters available packages that count as downgrades to installed packages โฌ‡๏ธ

Attributes usable only with the trans_packages domain โœจ

  • direction โ€” filters packages by their direction in a transaction; uses <value>; here's the supported tea โ˜•:
    • IN โ€” packages coming to the system (downgrade, install, reinstall, upgrade) ๐Ÿ“ฅ
    • OUT โ€” packages going out of the system (upgraded, downgraded, reinstalled, removed, replaced/obsoleted) ๐Ÿ“ค
  • <value> โ€” a value used by some filters; the type of comparison depends on <cmp_operator> ๐Ÿ’ซ
  • <cmp_operator> โ€” the operator the filter uses to evaluate a match against <value>; if you don't specify it, EQ is assumed (the default, no cap) ๐ŸŽ€
  • <attr_value> โ€” the value of the package attribute ๐Ÿท๏ธ
  • <error message> โ€” error message, aka the "oop" text ๐Ÿ˜ญ

The number of packages and trans_packages items in the response depends on the number of matching packages. Like, it flexes based on how much matched ๐Ÿ’…

Example:

{"op":"get", "domain":"packages",
  "args":{
    "params":[{"key":"IGNORE_EXCLUDES"}],
    "filters":[{"key":"name", "value":"lame*", "operator":"GLOB"}],
    "output":["nevra"]}}
{"op":"reply", "requested_op":"get", "domain":"packages", "status":"OK", "return":{"packages":[
  { "nevra":"lame-3.100-5.fc29.src" }, { "nevra":"lame-3.100-5.fc29.x86_64" },
  { "nevra":"lame-libs-3.100-5.fc29.x86_64" }, { "nevra":"lame-3.100-4.fc29.src" },
  { "nevra":"lame-3.100-4.fc29.x86_64" }, { "nevra":"lame-libs-3.100-4.fc29.x86_64"}]}}

{"op":"get", "domain":"packages",
  "args":{
    "params":[{"key":"UNKNOWN"}],
    "filters":[{"key":"name", "value":"lame*", "operator":"GLOB"}],
    "output":["nevra"]}}
{"op":"reply", "requested_op":"get", "domain":"packages", "status":"ERROR",
  "message":"Bad key \"UNKNOWN\" for params"}

{"op":"get", "domain":"trans_packages",
  "args":{
    "filters":[{"key":"direction", "value":"IN"}, {"key":"arch", "value":"x86_64"}],
    "output":["action", "name", "version", "repo_id"]}}
{"op":"reply", "requested_op":"get", "domain":"trans_packages", "status":"OK",
  "return":{
    "trans_packages":[
      {"action":"I", "name":"glibc", "version":"2.28", "repo_id":"dnf-ci-fedora-updates"},
      {"action":"I", "name":"glibc-all-langpacks", "version":"2.28", "repo_id":"dnf-ci-fedora-updates"},
      {"action":"I", "name":"glibc-common", "version":"2.28", "repo_id":"dnf-ci-fedora-updates"},
      {"action":"I", "name":"filesystem", "version":"3.9", "repo_id":"dnf-ci-fedora"}]}}

๐ŸŒธ Getting paths of command-line specified packages ๐ŸŒธ

Okay so this one grabs the paths of the packages you literally typed on the command line ๐Ÿซถ

Request format:

{"op":"get", "domain":"cmdline_packages_paths", "args":{}}

{"op":"get", "domain":"cmdline_packages_paths", "args":{
  "filters":[{"key":"path", "value":"<value>"}]}}

{"op":"get", "domain":"cmdline_packages_paths", "args":{
  "filters":[{"key":"path", "value":"<value>", "operator":"<cmp_operator>"}]}}

Response format:

{"op":"reply", "requested_op":"get", "domain":"cmdline_packages_paths", "status":"OK",
  "return":{"cmdline_packages_paths":["<pkg_path>"]}}

Description:

  • <value> โ€” if you specify it, only paths matching the given pattern come back; the comparison type depends on <cmp_operator> ๐ŸŽฏ
  • <cmp_operator> โ€” the operator that decides whether the path matches the pattern in <value>; if you don't specify it, EQ is assumed ๐Ÿ’ซ
  • <pkg_path> โ€” the path to the package you specified on the command line ๐Ÿ“

The number of cmdline_packages_paths items in the response depends on the number of matching package paths on the command line. It's giving "however many matched, that's how many you get" ๐Ÿ’…

Example:

{"op":"get", "domain":"cmdline_packages_paths",
  "args":{"filters":[{"key":"path", "value":"/local/*", "operator":"GLOB"}]}}
{"op":"reply", "requested_op":"get", "domain":"cmdline_packages_paths", "status":"OK",
  "return":{"cmdline_packages_paths":["/local/packageB.rpm", "/local/packageC.rpm"]}}

๐Ÿฆ‹ Supported compare operators ๐Ÿฆ‹

Some get operations support <cmp_operator>. The actions plugin supports these compare operators, bestie โ€” memorize the vibe โœ…:

  • EQ โ€” exact equality of arguments ๐Ÿ‘ฏ
  • IEQ โ€” string equality, case-insensitive comparison ๐Ÿ™ˆ
  • GT โ€” greater than ๐Ÿ”ผ
  • GTE โ€” greater than or equal to ๐ŸŒŸ
  • LT โ€” less than ๐Ÿ”ฝ
  • LTE โ€” less than or equal to ๐ŸŒ™
  • CONTAINS โ€” contains a substring ๐Ÿงฉ
  • ICONTAINS โ€” contains a substring, case-insensitive search ๐Ÿ”
  • STARTSWITH โ€” starts with a substring ๐Ÿš€
  • ISTARTSWITH โ€” starts with a substring, case-insensitive comparison ๐Ÿ›ซ
  • ENDSWITH โ€” ends with a substring ๐Ÿ
  • IENDSWITH โ€” ends with a substring, case-insensitive comparison ๐ŸŽฌ
  • REGEX โ€” matches a regular expression ๐Ÿงš
  • IREGEX โ€” matches a regular expression, case-insensitive evaluation ๐Ÿช„
  • GLOB โ€” matches a glob pattern โญ
  • IGLOB โ€” matches a glob pattern, case-insensitive evaluation ๐ŸŒˆ

You can invert the meaning of any operator by slapping the NOT_ prefix on it. Examples: NOT_EQ, NOT_GT. Flip the whole vibe, periodt ๐Ÿ’…


๐Ÿ’– Description of the set operation ๐Ÿ’–

The set operation is defined for several <domain>s:

  • conf โ€” request to set a configuration value (global or repository configuration) โš™๏ธ
  • vars โ€” request to set a variable value ๐Ÿงฎ
  • actions_vars โ€” request to set an action plugin variable โ€” these variables exist only in the actions plugin context, iykyk ๐Ÿ‘‘

๐ŸŽ€ Setting a configuration value ๐ŸŽ€

Request format:

{"op":"set", "domain":"conf", "args":{"key":"<name>", "value":"<value>"}}

{"op":"set", "domain":"conf", "args":{"key":"<repo_id>.<name>", "value":"<value>"}}

Response format:

{"op":"reply", "requested_op":"set", "domain":"conf", "status":"OK",
  "return":{"keys_val":[{"key":"<name>", "value":"<value>"}]}}

{"op":"reply", "requested_op":"set", "domain":"conf", "status":"OK",
  "return":{"keys_val":[{"key":"<repo_id>.<name>", "value":"<value>"}]}}

{"op":"reply", "requested_op":"set", "domain":"conf", "status":"ERROR",
  "message":"<error message>"}

Description:

  • <name> โ€” the name of a global configuration option, or a repository configuration option if <repo_id> is specified ๐Ÿท๏ธ
  • <repo_id> โ€” the repository ID; in a request it can contain globs, and in that case the value of the configuration option gets set for all matching repositories ๐ŸŒ
  • <value> โ€” in the request, the value you want to set; in the response, the actual value after it got set โœจ
  • <error message> โ€” error message ๐Ÿ˜ญ

When you use <repo_id>, the number of keys_val items in the response depends on the number of matching repositories. More repos = more keys, simple math ๐Ÿ’…

Example:

{"op":"set", "domain":"conf", "args":{"key":"countme", "value":"1"}}
{"op":"reply", "requested_op":"set", "domain":"conf", "status":"OK",
  "return":{"keys_val":[{"key":"countme", "value":"1"}]}}

{"op":"set", "domain":"conf", "args":{"key":"dnf-ci-fedora*.enabled", "value":"1"}}
{"op":"reply", "requested_op":"set", "domain":"conf", "status":"OK",
  "return":{
    "keys_val":[{"key":"dnf-ci-fedora.enabled", "value":"1"},
                {"key":"dnf-ci-fedora-updates.enabled", "value":"1"}]}}

๐Ÿงš Setting a variable value ๐Ÿงš

Request format:

{"op":"set", "domain":"vars", "args":{"name":"<name>", "value":"<value>"}}

{"op":"set", "domain":"vars", "args":{"name":"<name>"}}

Response format:

{"op":"reply", "requested_op":"set", "domain":"vars", "status":"OK",
  "return":{"vars":[{"name":"<name>", "value":"<value>"}]}}

{"op":"reply", "requested_op":"set", "domain":"vars", "status":"OK",
  "return":{"vars":[{"name":"<name>"}]}}

{"op":"reply", "requested_op":"set", "domain":"vars", "status":"ERROR",
  "message":"<error message>"}

Description:

  • <name> โ€” the name of the variable ๐Ÿท๏ธ
  • <value> โ€” in the request, the value to be set; if you don't specify it, that means the variable should be removed. In the response, the actual value after being set; if it's not specified, the variable didn't exist or it got removed ๐Ÿ‘‹
  • <error message> โ€” error message ๐Ÿ˜ญ

Example:

{"op":"set", "domain":"vars", "args":{"name":"test_var1", "value":"value1"}}
{"op":"reply", "requested_op":"set", "domain":"vars", "status":"OK",
  "return":{"vars":[{"name":"test_var1", "value":"value1"}]}}

๐Ÿ‘‘ Setting an action variable value ๐Ÿ‘‘

Request format:

{"op":"set", "domain":"actions_vars", "args":{"name":"<name>", "value":"<value>"}}

{"op":"set", "domain":"actions_vars", "args":{"name":"<name>"}}

Response format:

{"op":"reply", "requested_op":"set", "domain":"actions_vars", "status":"OK",
  "return":{"vars":[{"name":"<name>", "value":"<value>"}]}}

{"op":"reply", "requested_op":"set", "domain":"actions_vars", "status":"OK",
  "return":{"vars":[{"name":"<name>"}]}}

{"op":"reply", "requested_op":"set", "domain":"actions_vars", "status":"ERROR",
  "message":"<error message>"}

Description:

  • <name> โ€” the name of the action variable ๐Ÿท๏ธ
  • <value> โ€” in the request, the value to be set; if you don't specify it, that means the variable should be removed. In the response, the actual value after being set; if it's not specified, the variable didn't exist or it got removed ๐Ÿซง
  • <error message> โ€” error message ๐Ÿ˜ญ

Example:

{"op":"set", "domain":"actions_vars", "args":{"name":"test_actions_var1", "value":"value1"}}
{"op":"reply", "requested_op":"set", "domain":"actions_vars", "status":"OK",
  "return":{"actions_vars":[{"name":"test_actions_var1", "value":"value1"}]}}

๐ŸŽฌ Description of the new operation ๐ŸŽฌ

The new operation creates a new repository configuration. Heads up though: it can be used only in the repos_configured callback โ€” that's the one and only place it works, no cap โš ๏ธ

Request format:

{"op":"new", "domain":"repoconf",
  "args":{"keys_val":[{"key":"repo_id", "value":"<repo_id>"},
                      {"key":"<repo_opt>", "value":"<opt_value>"}]}

Response format:

{"op":"reply", "requested_op":"new", "domain":"repoconf", "status":"OK",
  "return":{"keys_val":[{"key":"repo_id", "value":"<repo_id>"},
                        {"key":"<repo_opt>", "value":"<opt_value>"}]}}

{"op":"reply", "requested_op":"new", "domain":"repoconf", "status":"ERROR",
  "message":"<error_message>"}

Description:

  • <repo_id> โ€” repository ID ๐Ÿ†”
  • <repo_opt> โ€” any existing repository configuration option โš™๏ธ
  • <opt_value> โ€” the value to be set for the configuration option โœจ

The keys_val field has to have at least one element โ€” setting repo_id is mandatory, no cap. Every other repository configuration option is optional. If the enabled option isn't there, the repository is disabled by default ๐Ÿ’…. And the response comes back with the same keys as the request, plus their actual values.

Example:

{"op":"new", "domain":"repoconf",
  "args":{
    "keys_val":[{"key":"repo_id", "value":"test-repo"},
                {"key":"name", "value":"Test repository"},
                {"key":"enabled", "value":"false"},
                {"key":"baseurl", "value":"https://xyz.com/rpm"}]}}
{"op":"reply", "requested_op":"new", "domain":"repoconf", "status":"OK",
  "return":{
    "keys_val":[{"key":"repo_id", "value":"test-repo"},
                {"key":"name", "value":"Test repository"},
                {"key":"enabled", "value":"0"},
                {"key":"baseurl", "value":"https://xyz.com/rpm"}]}}

๐Ÿชต Description of the log operation

The log operation writes a message to the logger, bestie. That's literally its whole job.

Request format:

{"op":"log", "args":{"level":"<level>", "message":"<message>"}}

Response format:

{"op":"reply", "requested_op":"log", "domain":"log", "status":"OK"}

{"op":"reply", "requested_op":"log", "domain":"log", "status":"ERROR",
  "message":"Unknown log level '<level_from_request>'"}

Description:

  • <level> โ€” CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACE
  • <message> โ€” text to be written to the log
  • <level_from_request> โ€” the level from the request, if it contains an unsupported value

Example:

{"op":"log", "args":{"level":"WARNING", "message":"My warning message"}}
{"op":"reply", "requested_op":"log", "domain":"log", "status":"OK"}

๐Ÿ›‘ Description of the stop operation

The stop operation throws a stop exception with a message. It's giving "we're done here" energy.

Request format:

{"op":"stop", "args":{"message":"<message>"}}

Description:

  • <message> โ€” text to be included in the stop exception

After this request, the application is expected to terminate. The actions plugin does not send a response but closes the communication channel โ€” pipes connected to the standard output and input of the process.

Example:

{"op":"stop", "args":{"message":"I want to stop the task"}}

๐Ÿ’€ Description of the error operation

The error operation logs the error message or throws an error exception with a message if raise_error=1.

Request format:

{"op":"error", "args":{"message":"<message>"}}

Response format:

{"op":"reply", "requested_op":"error", "domain":"error", "status":"OK"}

Description:

  • <message> โ€” text written to the log or included in the error exception if raise_error=1

If raise_error=1, the application is expected to terminate. The actions plugin does not send a response but closes the communication channel โ€” pipes connected to the standard output and input of the process.

Example:

{"op":"error", "args":{"message":"Error in action process 1"}}
{"op":"reply", "requested_op":"error", "domain":"error", "status":"OK"}

๐ŸŒˆ AN EXAMPLE ACTIONS FILE: ๐ŸŒˆ

# Prints header with process id
pre_base_setup::::/usr/bin/sh -c echo\ -------------------------------------\ >>/tmp/actions-trans.log
pre_base_setup::::/usr/bin/sh -c date\ >>/tmp/actions-trans.log
pre_base_setup::::/usr/bin/sh -c echo\ libdnf5\ pre_base_setup\ was\ called.\ Process\ ID\ =\ '${pid}'.\ >>/tmp/actions-trans.log
pre_base_setup:::enabled=installroot-only:/usr/bin/sh -c echo\ run\ in\ alternative\ "installroot":\ installroot\ =\ '${conf.installroot}'\ >>/tmp/actions-trans.log

# Prints the value of the configuration option "defaultyes".
pre_base_setup::::/usr/bin/sh -c echo\ pre_base_setup:\ conf.defaultyes=${conf.defaultyes}\ >>/tmp/actions.log

# Prints a message that the "post_base_setup" callback was called.
post_base_setup::::/usr/bin/sh -c echo\ libdnf5\ post_base_setup\ was\ called.\ >>/tmp/actions-trans.log

# Executes the "add_new_repo" application with json communication.
# This application, for instance, can add new repository configurations.
repos_configured:::mode=json:/usr/local/bin/add_new_repo

# Prints a list of configured repositories with their enable state.
repos_configured::::/usr/bin/sh -c echo\ Repositories:\ ${conf.*.enabled}\ >>/tmp/repos.log

# Prints a list of repositories that use the http protocol in baseurl.
repos_configured::::/usr/bin/sh -c echo\ "${conf.*.baseurl=*http://*}"\ >>/tmp/baseurl_http.log

# Disables all repositories whose id starts with "rpmfusion".
repos_configured::::/usr/bin/sh -c echo\ conf.rpmfusion*.enabled=0

# Executes the "check_transaction" application with json communication, terminating if the application encounters an error.
# This application, for instance, can check for forbidden packages within a transaction and send a stop message.
pre_transaction:::mode=json raise_error=1:/usr/local/bin/check_transaction

# Prints the information about the start of the transaction.
# Since package_filter is empty, it executes the commands once.
pre_transaction::::/usr/bin/sh -c echo\ Transaction\ start.\ Packages\ in\ transaction:\ >>/tmp/actions-trans.log

# Logs all packages (package action, full_nevra, repo id) in transaction into a file.
# Uses the shell command "echo" and redirection to a file.
pre_transaction:*:::/usr/bin/sh -c echo\ '${pkg.action}'\ '${pkg.full_nevra}'\ '${pkg.repo_id}'\ >>/tmp/actions-trans.log

# Prints the date and time and information about the end of the transaction.
# Since package_filter is empty, it executes the commands once.
post_transaction::::/usr/bin/sh -c date\ >>/tmp/actions-trans.log
post_transaction::::/usr/bin/sh -c echo\ Transaction\ end.\ Repositories\ used\ in\ the\ transaction:\ >>/tmp/actions-trans.log

# Logs all the repositories from which packages were used in the transaction to install on the system.
# Each repository will be listed only once, even if multiple packages from the same repository were used.
# The same command (after variables substitution) is executed only once per transaction.
post_transaction:*:in::/usr/bin/sh -c echo\ '${pkg.repo_id}'\ >>/tmp/actions-trans.log

# ==============================================================================================
# The next two actions emulate the DNF4 snapper plugin. It uses the "snapper" command-line program.

# Creates a snapshot description and saves it to the "tmp.snapper_descr" variable.
pre_transaction::::/usr/bin/sh -c echo\ "tmp.snapper_descr=$(ps\ -o\ command\ --no-headers\ -p\ '${pid}')"

# Creates pre snapshot before the transaction and stores the snapshot number in the "tmp.snapper_pre_number" variable.
pre_transaction::::/usr/bin/sh -c echo\ "tmp.snapper_pre_number=$(snapper\ create\ -t\ pre\ -p\ -d\ '${tmp.snapper_descr}')"

# If the variable "tmp.snapper_pre_number" exists, it creates post snapshot after the transaction and removes the used variables.
post_transaction::::/usr/bin/sh -c [\ -n\ "${tmp.snapper_pre_number}"\ ]\ &&\ snapper\ create\ -t\ post\ --pre-number\ "${tmp.snapper_pre_number}"\ -d\ "${tmp.snapper_descr}"\ ;\ echo\ tmp.snapper_pre_number\ ;\ echo\ tmp.snapper_descr

๐Ÿ‘‘ Author

See AUTHORS.md in dnf5 source distribution.

Contributors to the dnf5 project.


๐Ÿ’– And that's the whole page, bestie โ€” source/version LIBDNF5-ACTIONS(8), dated Oct 17, 2026. Periodt. ๐Ÿ’…

๐Ÿ“ฆ where this page comes from