Commands are made available for use with Droid through Command Plugins. A
Plugin is a package of Commands under a common namespace. For example, the
droid-mysql
Plugin package provides a number of Commands in the mysql
namespace, including mysql:dump
and mysql:load
.
This document shows how to create a Command Plugin and a Command; how to make the Plugin available to Droid during the development of a Command; and finally how to publish the Plugin so that the Command is made available for remote execution as part of a Droid project.
The Plugin Generation Command may be used to kick-start the development of a Command Plugin: it populates a specified folder to form self-contained project.
We begin by creating a basic Droid Project which will make use of our Command Plugin:-
$ cd /path/to/projects
$ mkdir myproject
$ cd myproject
$ composer init -n && composer require droid/droid
$ echo 'name: "A basic Droid Project"' > droid.yml
$ cd ..
We create a skeleton Command Plugin project which we shall give the name "chuck-norris" - this will be the namespace of the Plugin's Commands:-
$ mkdir chuck-norris
$ myproject/vendor/bin/droid generate:plugin chuck-norris
Generating: chuck-norris in /path/to/projects/chuck-norris
$ cd chuck-norris && composer install
The chuck-norris
folder now contains all that we need for developing Commands
for the Plugin.
The last of our preparatory steps is make the new Plugin available to our basic Droid Project. For this, we will use Autotune to give the Droid project the ability to autoload our Plugin:-
$ cd /path/to/projects/myproject
$ touch autotune.json
We add the following content to the autotune.json
:-
{
"autoload": {
"psr-4": {
"Droid\\Plugin\\ChuckNorris\\": "/path/to/projects/chuck-norris/src"
}
}
}
And our Droid project is now able to load the chuck-norris Plugin and list and execute its Commands:-
$ vendor/bin/droid list chuck-norris
...
Available commands for the "chuck-norris" namespace:
chuck-norris:example This is an example command
The skeleton Plugin includes an example Command chuck-norris:example
which we
can use as the starting point for further development.
We shall create a Droid Command the purpose of which shall be to set-up a Message Of The Day (MOTD) on a Host. It will:-
/etc/motd
, overwriting any previous MOTD.A suitable name for our Command might be jotd
. By convention, the PHP class
name will be ChuckNorrisJotdCommand
, that is, the Command namespace
chuck-norris
, title case, followed by the Command name jotd
, also title
case, followed by Command
.
We make a copy of the example Command:-
$ cd /path/to/projects/chuck-norris
$ cp src/Command/ChuckNorrisExampleCommand.php src/Command/ChuckNorrisJotdCommand.php
Next, edit the ChuckNorrisJotdCommand.php
file to look like the following:-
<?php
// src/Command/ChuckNorrisJotdCommand.php
namespace Droid\Plugin\ChuckNorris\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ChuckNorrisJotdCommand extends Command
{
public function configure()
{
$this
->setName('chuck-norris:jotd')
->setDescription('Install a Chuck Norris Joke of the Day')
->addArgument(
'file',
InputArgument::OPTIONAL,
'Write the JotD to this file',
'/etc/motd'
)
;
}
public function execute(InputInterface $input, OutputInterface $output)
{
$raw = file_get_contents('http://api.icndb.com/jokes/random/');
if ($raw === false) {
$output->writeln(
'Did not get a sensible response from api.icndb.com'
);
return;
}
$response = json_decode($raw, true);
if ($response === null
|| ! array_key_exists('type', $response)
|| $response['type'] !== 'success'
|| ! array_key_exists('value', $response)
|| ! array_key_exists('joke', $response['value'])
) {
$output->writeln(
'Did not get a well-formed json response from api.icndb.com'
);
return;
}
$result = file_put_contents(
$input->getArgument('file'),
html_entity_decode($response['value']['joke']) . PHP_EOL
);
if ($result === false) {
$output->writeln('Failed to write to the <file>.');
return;
}
$output->writeln('Successfully wrote to the <file>.');
}
}
We have:-
ChuckNorrisJotdCommand
configure
method, to set the name of the
Command chuck-norris:jotd
and provide a descriptionfile
which will take
the default value /etc/motd
execute
method with the code to be executed: it will make an
API request and write a joke, if successfully obtained, to the file indicated
by the file
argument.Our Command is now complete. We must now register it with the Plugin. The
src/DroidPlugin.php
contains the main Plugin class DroidProject
. Its
method getCommands
is called by the Droid Console Application which expects a
list of Command objects. We modify the getCommands
method to create an
instance of our Command and add it to the list of objects to be returned by the
method:-
<?php
//src/DroidPlugin.php
namespace Droid\Plugin\ChuckNorris;
use Droid\Plugin\ChuckNorris\Command\ChuckNorrisExampleCommand;
use Droid\Plugin\ChuckNorris\Command\ChuckNorrisJotdCommand;
class DroidPlugin
{
public function __construct($droid)
{
$this->droid = $droid;
}
public function getCommands()
{
$commands = [];
$commands[] = new ChuckNorrisExampleCommand();
$commands[] = new ChuckNorrisJotdCommand();
return $commands;
}
}
In our basic Droid project, Droid should now have two Commands in the
chuck-norris
namespace:-
$ cd /path/to/myproject
$ vendor/bin/droid list chuck-norris
...
Available commands for the "chuck-norris" namespace:
chuck-norris:example This is an example command
chuck-norris:jotd Install a Chuck Norris Joke of the Day
Our Command Plugin is now complete and its Commands are available to our basic Droid Project for execution the local host, either directly at the command line or as part of a Task.
Next we shall publish the Command so that it may be executed on a remote Host.
A Plugin must be published somewhere, accessible to Composer, in order that Droid may execute the Plugin's Commands on a remote Host. Usually, a Droid Project will make use of Command Plugins published on Packagist, but for this example we will publish our Plugin at GitHub and Composer will be instructed to obtain it from GitHub by way of a custom repository.
The first thing to do is create a GitHub repository for the Plugin. We shall pretend that the repository has been created and its URL is:-
https://github.com/someone/chuck-norris.git
Next, we initialise the local git repository:-
$ cd /path/to/projects/chuck-norris
$ git init
$ git config user.name someone
$ git config user.email someone@users.noreply.github.com
$ git remote add origin https://github.com/someone/chuck-norris.git
We remove the chuck-norris:example
Command from the Plugin so that it
provides just the chuck-norris:jotd
Command:-
<?php
//src/DroidPlugin.php
namespace Droid\Plugin\ChuckNorris;
use Droid\Plugin\ChuckNorris\Command\ChuckNorrisJotdCommand;
class DroidPlugin
{
public function __construct($droid)
{
$this->droid = $droid;
}
public function getCommands()
{
$commands = [];
$commands[] = new ChuckNorrisJotdCommand();
return $commands;
}
}
$ rm src/Command/ChuckNorrisExampleCommand.php
Next, we provide some information about the Plugin's Command in its
README.md
:-
Droid plugin: chuck-norris
======================
For more information on Droid, please check out [droidphp.com](http://droidphp.com)
Provides the commands:-
- `chuck-norris:jotd`: Install a Chuck Norris Joke of the Day
And we commit and publish our work:-
$ git add .
$ git commit -m 'Add Droid plugin "chuck-norris".'
$ git push -u origin master
Now that the Plugin is published, we can update our Droid Project to fetch the
Plugin from GitHub. We remove autotune.json
:-
$ cd /path/to/projects/myproject
$ rm autotune.json
add the remote git repository to the Project's composer.json
:-
{
"require": {
"droid/droid": "^1.20",
"droid/droid-chuck-norris": "dev-master as 1.0.x-dev"
},
"require-dev": {
"linkorb/autotune": "^1.2"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/someone/chuck-norris.git"
}
]
}
We can now fetch the Plugin from GitHub, into our Droid Project:-
$ composer update droid/droid-chuck-norris
$ vendor/bin/droid list chuck-norris
...
Available commands for the "chuck-norris" namespace:
chuck-norris:jotd Install a Chuck Norris Joke of the Day
Our Command is now available for use both locally and on remote Hosts.
Let's try it out. We add a remote Host and a Target to our Droid's Project
droid.yml
:-
# /path/to/projects/myproject/droid.yml
project:
name: "A basic Droid Project"
targets:
install_jotd:
hosts: "myhost"
tasks:
-
name: "Install Joke of the Day"
command: "chuck-norris:jotd"
sudo: true
-
name: "Display the installed Joke of the Day"
command: "shell:exec"
sudo: true
arguments:
command-line: "head /etc/motd"
hosts:
myhost:
droid_ip: "198.51.100.1"
Our Droid Project now requires the droid/droid-shell
package because we have
used the shell:exec
Command in the second Task:-
$ composer require droid/droid-shell
We may execute the install_jotd
Target:-
$ vendor/bin/droid install_jotd
We should see something like the following:-
Droid: Running target `install_jotd`
Task `Install Joke of the Day`: chuck-norris:jotd on myhost
Host myhost: file=/etc/motd
myhost Begin droid enablement.
myhost Finished droid enablement. Success.
myhost Successfully wrote to the <file>.
Task `Display the installed Joke of the Day`: shell:exec on myhost
Host myhost: command-line=head /etc/motd
myhost I have successfully executed the given command line.
myhost Someone once tried to tell Chuck Norris that roundhouse kicks aren't the best way to kick someone. This has been recorded by historians as the worst mistake anyone has ever made.
Result: 0
--------------------------------------------
Our new Command Plugin was installed, directly from GitHb, to the remote Host
and the chuck-norris:jotd
Command was successfully executed.