Skip to content
Browse files

Added examples, handled registration edge cases

  • Loading branch information...
Dorthu committed Apr 4, 2019
1 parent 4ae593f commit a4a94b372775470533c4b22aaea771d4b1486f2d
@@ -0,0 +1,63 @@
# Example Third Party Plugin

This is included as an example of how to develop a third party plugin for the
Linode CLI. There are only two files:


This file contains the python source code for your plugin. Notably, it is a valid
plugin because it exposes two attributes at the module level:

* `PLUGIN_NAME` - a constant whose value is the string used to invoke the plugin
once it's registered
* `call(args, context)` - a function called when the plugin is invoked

While this example is a single file, a module that exposes those two attributes
at the top level is also a valid CLI plugin (define or import them in the module's
`` file to expose them at the module level).


This file is used by setuptools to create a python module. This example is very
sparse, but is enough to install the module locally and get you started. Please
see the [setuptools docs](
for all available options.

## Installation

To install this example plugin, run the following in this directory:

python install

### Registration and Invocation

Once installed, you have to register the plugin with the Linode CLI by python
module name (as defined in ``):

linode-cli register-plugin example_third_party_plugin

The CLI will print out the command to invoke this plugin, which in this example

linode-cli example-plugin

Doing so will print `Hello world!` and exit.

## Development

To begin working from this base, simply edit `` and
add whatever features you need. When it comes time to distribute your plugin,
copy this entire directory elsewhere and modify the `` file as described
within it to create your own module.

To test your changes, simply reinstall the plugin as described above. This
_does not_ require reregistering it, as it references the installed module and
will invoke the updated code next time it's called.
@@ -0,0 +1,18 @@
This file is an example third-party plugin. See `the plugin docs`_ for more
.. _the plugin docs:

#: This is the name the plugin will be invoked with once it's registered. Note
#: that this name is different than the module name, which is what's used to
#: register it. This is required for all third party plugins.
PLUGIN_NAME = "example-plugin"

def call(args, context):
This is the entrypoint for the plugin when invoked through the CLI. See the
docs linked above for more information.
print('Hello world!')
@@ -0,0 +1,23 @@
This file allows installation of this plugin as a python module. See the docs
for setuptools for more information.
from setuptools import setup

# replace this with the module name you want your plugin to install as
# replace with your plugin's version - use semantic versioning if possible
# this is used in pip to show details about your plugin
description="Example third party plugin for the Linode CLI",
# replace these fields with information about yourself or your organization
# in this case, the plugin is a single file, so that file is listed here
# replace with the name of your plugin file, or use ``packages=[]`` to list
# whole python modules to include
@@ -170,10 +170,37 @@ def main():
print('{} is not a valid Linode CLI plugin - missing call'.format(module))

reregistering = False
# check for naming conflicts
if plugin_name in cli.ops:
print('Plugin name conflicts with CLI operation - registration failed.')
elif plugin_name in plugins.available_local:
# conflicts with an internal plugin - can't do that
print('Plugin name conflicts with internal CLI plugin - registration failed.')
elif plugin_name in plugins.available(cli.config):
from linodecli.configuration import input_helper

# this isn't an internal plugin, so warn that we're re-registering it
print("WARNING: Plugin {} is already registered.".format(plugin_name))
answer = input_helper("Allow re-registration of {}? [y/N] ".format(plugin_name))

if not answer or answer not in 'yY':
print('Registration aborted.')

reregistering = True

# looks good - register it
already_registered = []
if cli.config.config.has_option('DEFAULT', 'registered-plugins'):
already_registered = cli.config.config.get('DEFAULT', 'registered-plugins')
already_registered = cli.config.config.get('DEFAULT', 'registered-plugins').split(',')

if reregistering:
cli.config.config.remove_option('DEFAULT', 'plugin-name-{}'.format(plugin_name))

cli.config.config.set('DEFAULT', 'registered-plugins', ','.join(already_registered))
@@ -6,15 +6,30 @@ CLI as other features are. All plugins are found in this directory.

## Creating a Plugin

To create a plugin, simply drop a new python file into this directory. The
plugin must meet the following conditions:
To create a plugin, simply drop a new python file into this directory or write a
module that presents the interface described below.

Plugins in this directory are called "Internal Plugins," and must meet the
following conditions:

* It must be compatible with python 2 and 3
* Its name must be unique, both with the other plugins and with all commands
offered through the generated CLI
* Its name must not contain special characters, and should be easily enter able
* Its name must not contain special characters, and should be easily to enter
on the command line
* It must contain a `call(args, context)` function for invocation
* It must support a `--help` command as all other CLI commands do.

Plugins that are installed separately and registered with the `register-plugin`
command are called "Third Party Plugins," and must meet the following

* It should be compatible with python 2 and 3
* Its name must be unique, both with the internal plugins and all CLI operations
* It must contain a `call(args, context)` function for invocation
* It must contain a `PLUGIN_NAME` constant whose value is a string that does not
contain special characters, and should be easy to enter on the command line.
* It should support a `--help` command as all other CLI commands do.

## The Plugin Interface

@@ -108,8 +123,16 @@ root directory of the project (this installs the code without generating new
baked data, and will only work if you've installed the CLI via `make install`
at least once, however it's a lot faster).

To develop a third party plugin, simply create and install your module and register
it to the CLI. As long as the `PLUGIN_NAME` doesn't change, updated installations
should invoke the new code.

### Examples

This directory contains two example plugins, `` and
``. To run these, simply remove the `.example` at the end
of the file and build the CLI as described above.

[This directory](
contains an example Third Party Plugin module. This module is installable and
can be registered to the CLI.
@@ -5,7 +5,7 @@

_available_files = listdir(dirname(__file__))
_available_local = [f[:-3] for f in _available_files if f.endswith('.py') and f != '']
available_local = [f[:-3] for f in _available_files if f.endswith('.py') and f != '']

def available(config):
@@ -18,7 +18,7 @@ def available(config):

additional = registered_plugins.split(',')

return _available_local + additional
return available_local + additional

def invoke(name, args, context):
@@ -28,7 +28,7 @@ def invoke(name, args, context):
# setup config to know what plugin is running
context.client.config.running_plugin = name

if name in _available_local:
if name in available_local:
plugin = import_module('linodecli.plugins.'+name), context)
elif name in available(context.client.config):

0 comments on commit a4a94b3

Please sign in to comment.
You can’t perform that action at this time.