Skip to content
Permalink
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:

#### example_third_party_plugin.py

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
`__init__.py` file to expose them at the module level).


#### setup.py

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](https://setuptools.readthedocs.io/en/latest/index.html)
for all available options.

## Installation

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

```bash
python setup.py install
```

### Registration and Invocation

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

```bash
linode-cli register-plugin example_third_party_plugin
```

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


```bash
linode-cli example-plugin
```

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

## Development

To begin working from this base, simply edit `example_third_party_plugin.py` and
add whatever features you need. When it comes time to distribute your plugin,
copy this entire directory elsewhere and modify the `setup.py` 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
information.
.. _the plugin docs: https://github.com/linode/linode-cli/blob/master/linodecli/plugins/README.md
"""

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

setup(
# replace this with the module name you want your plugin to install as
name="example_third_party_plugin",
# replace with your plugin's version - use semantic versioning if possible
version=1,
# 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
author="linode",
author_email='developers@linode.com',
# 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
py_modules=[
'example_third_party_plugin',
],
)
@@ -170,10 +170,37 @@ def main():
print('{} is not a valid Linode CLI plugin - missing call'.format(module))
exit(11)

reregistering = False
# check for naming conflicts
if plugin_name in cli.ops:
print('Plugin name conflicts with CLI operation - registration failed.')
exit(12)
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.')
exit(13)
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))
print("")
answer = input_helper("Allow re-registration of {}? [y/N] ".format(plugin_name))

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

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:
already_registered.remove(plugin_name)
cli.config.config.remove_option('DEFAULT', 'plugin-name-{}'.format(plugin_name))

already_registered.append(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
conditions:

* 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, `echo.py.example` and
`regionstats.py.example`. To run these, simply remove the `.example` at the end
of the file and build the CLI as described above.

[This directory](https://github.com/linode/linode-cli/tree/master/examples/third-party-plugin)
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 != '__init__.py']
available_local = [f[:-3] for f in _available_files if f.endswith('.py') and f != '__init__.py']


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)
plugin.call(args, 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.