openSUSE:OSC plugins

Jump to: navigation, search
This document shows how to write osc plugins, and how to use the osc module from external python scripts.

Simple example of standalone script

It is probably best shown by example.

Let's say, you want to write a script which looks in the openSUSE:Tools project, and shows metadata of all packages inside this project.

Naive approach by forking osc executable

You could do it like this:

#!/usr/bin/python

import os

project = 'openSUSE:Tools'

def run(command):
    input, output = os.popen2(command)
    lines = output.readlines()
    return lines

lines = run('osc ls ' + project)
for line in lines:
    package = line.rstrip('\n')
    meta = run('osc meta pkg ' + project + ' ' + package)
    for line in meta:
        print line

But this way, you only call osc as external process. This means that osc is initializing each time, and that you cannot access internal capabilities of osc.

Using the osc API

Instead, you could write a script which imports the osc module, and uses that:

#!/usr/bin/python

import os
import osc.conf
import osc.core

project = 'openSUSE:Tools'

# initialize osc configuration
osc.conf.get_config()
packages = osc.core.meta_get_packagelist(osc.conf.config['apiurl'], 
                                       project)
for package in packages:

    m = osc.core.show_package_meta(osc.conf.config['apiurl'], project, package)
    print ''.join(m)

This is easier.

Turning a standalone script into a plugin

Now, let's say you wrote something which could be more generally useful. Could it make sense to integrate the functionality as new osc command? If so, it is easy to do so. Custom osc commands can be thrown into ~/.osc-plugins or /var/lib/osc-plugins, and will be loaded by osc from there. The snippet from above could look like this:

def do_show_packages(self, subcmd, opts, project):
    """${cmd_name}: Show metadata of all packages in an project

    This command shows metadata of all packages in a given project.
    
    ${cmd_usage}
    ${cmd_option_list}
    """

    packages = meta_get_packagelist(conf.config['apiurl'], 
                                        project)

    for package in packages:
        m = show_package_meta(conf.config['apiurl'], project, package)
        print ''.join(m)

The file's name is important; it must end in .py. Other than that, no restrictions on how it's named, although it is of course wise for its name to match the command name it implements.

Debugging a plugin

When writing and debugging osc plugins, and something doesn't work, it is helpful to see where exactly you screw up. osc has a "--trace" option (you can also add "traceback=1" to your ~/.oscrc to activate it permanently). Additionally you may find the "--debugger" and "--post-mortem" options useful.

The method implemented must start with "do_". There is some other magic going on. A python module named cmdln (see pydoc osc.cmdln for more info) automatically takes care of documenting, argument checking, and more. After installing the plugin, if you call 'osc help', you will notice a new command in the long help output:

   show_packages     Show metadata of all packages in an project

The command is automatically documented:

% osc help show_packages     
show_packages: Show metadata of all packages in an project

This command shows metadata of all packages in a given project.

usage:
    osc show_packages PROJECT

and it can be called as "osc show_packages <prj>".

More complex example with CLI options

Here is a more complex example, which also shows command line stuff:

@cmdln.option('-q', '--quiet', action='store_true',
              help='do not show downloading progress')
@cmdln.option('--package', metavar='PACKAGE',
              help='only binaries of this package')
@cmdln.option('-d', '--destdir', default='.', metavar='DIR',
              help='destination directory')
def do_mirror_binaries(self, subcmd, opts, project, repository, architecture):
    """${cmd_name}: Mirror binaries of a project to local directory

    It does download directly from the api server. 
    
    Packages don't need to be "published" to be downloaded.

    ${cmd_usage}
    ${cmd_option_list}
    """

    # Get package list
    filenames = get_binarylist(conf.config['apiurl'],
                               project, repository, architecture,
                               package=opts.package)

    if not os.path.isdir(opts.destdir):
        print "Creating %s" % opts.destdir
        os.makedirs(opts.destdir, 0755)

    for filename in filenames:

        if os.path.exists('%s/%s' % (opts.destdir, filename)):
            continue

        target_filename = '%s/%s' % (opts.destdir, filename)

        get_binary_file(conf.config['apiurl'],
                        project, repository, architecture,
                        filename,
                        target_filename=target_filename,
                        package=opts.package,
                        progress_meter = not opts.quiet)

Help output will look like this:

% osc help mirror_binaries
mirror_binaries: Mirror binaries of a project to local directory

It does download directly from the api server.

Packages don't need to be "published" to be downloaded.

usage:
    osc mirror_binaries PROJECT REPOSITORY ARCHITECTURE 

options:
    -h, --help          show this help message and exit
    -d DIR, --destdir=DIR
                        destination directory
    --package=PACKAGE   only binaries of this package
    -q, --quiet         do not show downloading progress

More information

There's more to it. It is easy to add command line options to your command with python function decorators. This is best done by reviewing existing osc code with similar functionality. Browse the file "osc/commandline.py" found in the source tar-ball or git repository of osc, or inside <python-libdir>

To get an idea of the osc internal functions see

pydoc osc.commandline
pydoc osc.core

for higher and lower level functions.

Plenty other examples can be found by looking inside <python-libdir>/osc/command.py.

This method is very well suited to override, or improve, any existing osc command. If you are not happy with the ls command, just write your own. Or if you have an idea how to improve it or add a new feature, copy and paste the command from osc.command.py to your osc plugin directory and start hacking on it.

There is one difference to note between a command in osc.command.py and a plugged-in command. As indentation matters to Python, you need to indent stuff 4 spaces if you copy it into osc.command.py, or remove 4 spaces the other way round. Other than that, simple copy&paste should do.

GNOME/OscGnome might serve as further example; it describes a more complex system based on osc plugins.

Existing plugins

osc plugins which already exist include:

You may find more by searching for osc-plugin on software.opensuse.org.

Contributing

If you implemented a cool command, which you would like to see integrated in the upstream osc, please write to the buildservice-mailinglist now!

To contribute to osc:

  • Fork https://github.com/openSUSE/osc
  • Post about your patch/idea/bugfix to the buildservice mailinglist (opensuse-buildservice_AT_opensuse.org).
  • Contact adrian_AT_suse.de to get commit access. He'll also arrange commit mails.