File: Plugins.doc

package info (click to toggle)
libzypp 17.38.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 27,744 kB
  • sloc: cpp: 132,661; xml: 2,587; sh: 518; python: 266; makefile: 27
file content (234 lines) | stat: -rw-r--r-- 9,077 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/**

\page zypp-plugins Extending ZYpp: Plugins and Hooks

\author Duncan Mac-Vicar P. <dmacvicar@suse.de>
\author Michael Andres <ma@suse.de>

<HR><!-- ====================================================================== -->
\section plugins-intro Introduction

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.

\section plugin-protocols Plugin protocols

Depending on the complexity and need for future extension, plugins talk
to ZYpp using two methods.

\subsection plugin-protocol-stateless Stateless

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).

\subsection plugin-protocol-stateful Stateful

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:

\verbatim
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.
^@
\endverbatim

\note ^@ is a null (control-@ in ASCII) byte.

<HR><!-- ====================================================================== -->
\section plugin-writing Writing plugins

A python class is offered as a helper to handle communication between the plugin and
libzypp. It is available by installing \c zypp-plugin-python.

\verbatim
zypper in -C zypp-plugin-python
\endverbatim

The plugin must define a method for each message it may receive from \ref libzypp. Message header list and body are passed to the method as arguments.

\verbatim
#!/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()
\endverbatim

Two methods \c ack and \c error are available to send back a standard \c ACK or \c ERROR message. You may optionally pass header entries and a multiline body. For sending custom messages use \c answer, which takes the command name as 1st argument, followed by optional header entries and a multiline body.

Plugin closes \c stdin and exits when receiving a \c _DISCONNECT message. Upon an \c ACK response to \c _DISCONNECT libzypp will not attempt to kill the script. An exit value different than \c 0 may be set via an \c 'exit' header in \c ACK.

\verbatim
  def _DISCONNECT( self, headers, body ):
    sys.stding.close()
    self.ack( {'exit':'99'}, 'Famous last words.' )
\endverbatim

<HR><!-- ====================================================================== -->
\section plugins-impl Developers: Implementation

Plugins are implemented in the following classes:

- \ref zypp::PluginScript (Plugin as an external program)
- \ref zypp::PluginScriptException
- \ref zypp::PluginFrame (Message for the stateful protocol)
- \ref zypp::PluginFrameException
- \ref zypp::repo::PluginServices (Finds Service plugins)

The plugins default location is obtained from \ref zypp::ZConfig::pluginsPath()

<HR><!-- ====================================================================== -->
\section plugin-toc Supported plugins

\subpage plugin-commit Escort installation of packages

\subpage plugin-system Receive notification if system content has changed

\subpage plugin-repoverification Repository metadata verification beyond GPG

\ref plugin-services

\ref plugin-url-resolver

\ref plugin-appdata

<HR><!-- ====================================================================== -->
\section plugin-services Service plugins

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 \ref services-remote "Remote Services").

\subsection plugin-services-example1 Example: Management console

You have a custom mass management application that controls the repositories each client muss have. While you can use \ref services-remote "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).

\subsection plugin-services-how How to write a Services plugin

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:

\verbatim
# 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]
...
\endverbatim

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):

\verbatim
#!/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"
...
\endverbatim

<HR><!-- ====================================================================== -->
\section plugin-url-resolver Url Resolver plugins

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:

\verbatim
plugin:foo?param1=val1&param2=val2
\endverbatim

ZYpp tries to execute a plugin named foo (in /usr/lib/zypp/plugins/urlresolver) and call it with the following protocol:

\verbatim
   RESOLVEURL
   param1:val1
   param2:val2
   ...
   ^@
\endverbatim

The plugin answers:

\verbatim
   RESOLVEDURL:
   header1:val1
   header2:val2
   ...
   http://realurl.com?opts=vals
   ^@
\endverbatim

And this url is used instead.

\subsection plugin-urlresolver-example Example

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:

\verbatim
  [distro]
  name=Distribution repository
  baseurl=plugin:lan?repo=distro
  [update]
  name=Update repository
  baseurl=plugin:lan?repo=update
\endverbatim

\note
In this example, the service plugin could have inmediately resolved the urls and returned http://realurl, but the url resolver allows also to add HTTP headers to the request.

<HR><!-- ====================================================================== -->
\section plugin-appdata Appdata refresh plugins (repo change)

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 \b 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:

\verbatim
  -R REPO_ALIAS -t REPO_TYPE -p REPO_METADATA_PATH   -R NEXT_REPO....
\endverbatim

\note REPO_TYPE can be e.g. "rpm-md", "yast2", "plaindir" or "NONE" indicating the repo was not yet probed.

\note REPO_METADATA_PATH can be empty or a not existing directory, indicating valid metadata for the repo are not yet available.

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.
*/