libzypp 17.31.23
|
Plugins allow one to extend the ZYpp package manager without the need to change code. Plugins are designed as external programs so that they can be written in any language.
Depending on the complexity and need for future extension, plugins talk to ZYpp using two methods.
This type of plugin receive input reading the standard input, and answer ZYpp writing to the standard output. This means the plugin is executed once per hook and they are stateless (unless the plugin keeps the state out of process).
This type of plugin is called by ZYpp and a conversation using a simple protocol. The protocol is based on STOMP https://stomp.github.io (Streaming Text Orientated Messaging Protocol). Messages (called "frames") look like the following:
COMMAND param1:val1 param2:val2 ... Thus a COMMAND followed by key:value header lines and a multiline body separated from header by an empty line and terminated by NUL. ^@
A python class is offered as a helper to handle communication between the plugin and libzypp. It is available by installing zypp-plugin-python
.
zypper in -C zypp-plugin-python
The plugin must define a method for each message it may receive from libzypp. Message header list and body are passed to the method as arguments.
#!/usr/bin/env python # # zypp plugin # import os import sys from zypp_plugin import Plugin class MyPlugin(Plugin): def SAMPLE( self, headers, body ): # called upon revieving a SAMPLE message ... if ( ok ): self.ack() else: self.error( { "aheader":"header value" }, "body\n(multiline text ok)" ) plugin = MyPlugin() plugin.main()
Two methods ack
and error
are available to send back a standard ACK
or ERROR
message. You may optionally pass header entries and a multiline body. For sending custom messages use answer
, which takes the command name as 1st argument, followed by optional header entries and a multiline body.
Plugin closes stdin
and exits when receiving a _DISCONNECT
message. Upon an ACK
response to _DISCONNECT
libzypp will not attempt to kill the script. An exit value different than 0
may be set via an 'exit'
header in ACK
.
def _DISCONNECT( self, headers, body ): sys.stding.close() self.ack( {'exit':'99'}, 'Famous last words.' )
Plugins are implemented in the following classes:
The plugins default location is obtained from zypp::ZConfig::pluginsPath()
Commit plugin Escort installation of packages
System plugin Receive notification if system content has changed
Repository metadata verification plugin Repository metadata verification beyond GPG
Appdata refresh plugins (repo change)
ZYpp will find a subscribed service for each executable located in /usr/lib/zypp/plugins/services and will set the alias as the executable name. The type will be set to "plugin".
Service plugins are used to provide a client a list of repositories from a central location where more complex logic is needed than a simple remote xml index accessible via http (in that case you can use Remote Services).
You have a custom mass management application that controls the repositories each client muss have. While you can use Remote Services and subscribe each client to an url in the server providing a dynamic repoindex.xml based on the client, if you need to pass custom information in order for the server to calculate the repository list (e.g. number of CPUs) or the protocol that the client and the server and client speak is proprietary, you may implement the service locally, as an executable that will be installed in each client /usr/lib/zypp/plugins/services directory (it may be installed from a package).
When listing services, ZYpp will find each plugin service as a subscribed service.
Service plugins are Stateless. When services are refreshed, ZYpp will call each plugin and the repository list will be taken from the output of the script in INI format (same as how they are stored in /etc/zypp/repos.d).
For our example:
# example plugin output # comments are ignored [repo1] name=Base repository summary=Standard repository baseurl=http://server.com/repo1 type=rpm-md # multiple repositories can be present in the output [repo2] ...
The repositories will be added on service refresh with the alias present in the output, prefixed by the service alias (in this case, the executable name).
To protect your code from e.g python modules writing their own messages to stdout, you should use a copy of stdout and redirect stdout to stderr (Python3 may need a slightly different print syntax):
#!/usr/bin/python import sys sendback = sys.stdout sys.stdout = sys.stderr print >>sendback, "# example plugin output" print >>sendback, "# comments are ignored" print >>sendback, "[repo1]" print >>sendback, "name=Base repository" ...
Url resolver plugins convert urls of scheme "plugin" into the output of the plugin named $name using the protocol. Thanks to the protocol, each header returned is also added as HTTP headers. The current protocol sequence is:
ZYpp sees a repository whose url has the format:
plugin:foo?param1=val1¶m2=val2
ZYpp tries to execute a plugin named foo (in /usr/lib/zypp/plugins/urlresolver) and call it with the following protocol:
RESOLVEURL param1:val1 param2:val2 ... ^@
The plugin answers:
RESOLVEDURL: header1:val1 header2:val2 ... http://realurl.com?opts=vals ^@
And this url is used instead.
You have a repository with url:
plugin:lan
The script looks which distribution you have installed, and via SLP finds the right repositories in the lan and selects the update one and returns it url. But in addition, it adds a header with the update status that can be collected on the server side.
This type of plugin can be combined with service plugins, because a local service could return a list of repos like this:
[distro] name=Distribution repository baseurl=plugin:lan?repo=distro [update] name=Update repository baseurl=plugin:lan?repo=update
Stateless plugins found in /usr/lib/zypp/plugins/appdata are called whenever any of the system repos has changed (added/removed/renamed/modified) or has been refreshed. Detailed information what exactly has changed is not available. (scripts are executed IFF euid is '0' and –root is not used). For every enabled system repo we pass alias type and metadata path on the commandline like this:
-R REPO_ALIAS -t REPO_TYPE -p REPO_METADATA_PATH -R NEXT_REPO....
Scripts are executed 'fire and forget' whenever a RepoManager instance that performed changes goes out of scope. So it's up to the script to protect against concurrency.