Technical Articles
Creating a plugin for the Cloud Foundry client
Hi!
In this post I show how to create a custom plugin for the CF client.
After a few days of work I have published my plugin in the community of cloud foundry, you can see it in聽https://plugins.cloudfoundry.org/#check-before-deploy.
In order to build your plugin you will need to install the GO compiler on your computer. As easy as following the instructions on its official GO install page.
And of course… it’s not necessary be a GO expert 馃槈
Let’s start!
Building plugin (beginner level)
The easiest way to start your plugin is to copy the file from the official GITHUB page.
We start with the basic plugin: basic_plugin.go.
Let’s see all its parts.
Initialization
func main() {
plugin.Start(new(BasicPlugin))
}
The first thing is to add the call so that the client can read the metadata.
One of the parameters is “BasicPlugin”, this variable is declared at the beginning of the file and contains the necessary interface defined in the core of the CF client. Initially we will have it empty.
type BasicPlugin struct{}
Metadata
The next thing to see is the metadata, here the version, the description of the plugin and its commands are reported.
func (c *BasicPlugin) GetMetadata() plugin.PluginMetadata {
return plugin.PluginMetadata{
Name: "MyBasicPlugin",
Version: plugin.VersionType{
Major: 1,
Minor: 0,
Build: 0,
},
MinCliVersion: plugin.VersionType{
Major: 6,
Minor: 7,
Build: 0,
},
Commands: []plugin.Command{
{
Name: "basic-plugin-command",
HelpText: "Basic plugin command's help text",
// UsageDetails is optional
// It is used to show help of usage of each command
UsageDetails: plugin.Usage{
Usage: "basic-plugin-command\n cf basic-plugin-command",
},
},
},
}
}
These are the most important fields:
- Name: The name of plugin
- Version: Plugin version
- MinClientVersion: Minimum version of the plugin
- Commands
- Name: Command in the CLI that identifies the plugin
- HelpText: Description of the plugin when user put “-h”
- UsageDetails: Plugin options description
Running the plugin
In the “Run” function we develop the actions defined in the metadata.
func (c *BasicPlugin) Run(cliConnection plugin.CliConnection, args []string) {
// Ensure that we called the command basic-plugin-command
if args[0] == "basic-plugin-command" {
fmt.Println("Running the basic-plugin-command")
}
}
In this case, when executing the CF client with our plugin, the message “Running the basic-plugin-command” will be displayed on the console.
At this point we have reviewed the basic concepts of the example… We will install and start the plugin
Installing the plugin
With the github example file we will proceed to complete and install the plugin.
As a recommendation, save the file in your <user_directory>/go/<plugin_name>
Compiling the plugin
As easy as going to the terminal and executing the “build” statement:
go build .\plugin.go
In some cases when compiling libraries may be missing, in that case we can import them using the instruction “go get code.foundry.org/cli”
After executing the command, we will see the folder with two files:
At the moment do not worry about having the files in the same folder, the usual thing is a build folder.
Installing the (local) plugin to the CLI CF
We just have to execute the instruction:
cf install-plugin .\plugin.exe -f
It has been installed correctly, we run the CFE client to see the result:
And the result of the execution
Building plugin (add commands)
All the commands of our plugin are reported in the metadata (so the user will know of their existence) and we can develop them in the 芦Run禄 function.
To add a new command we will add a line like the following in the metadata function. In this case we will add two options
We add two new commands, the first is flag type, to activate some functionality … the typical 芦- something禄 the second will have a string value associated as a parameter.
At the moment the code added does not have much use.
For the next steps we will use the 芦flag禄 library, this is useful to obtain the input parameters. We will import the library at the beginning of the file.
import (
"flag"
"fmt"
"code.cloudfoundry.org/cli/plugin"
)
func (c *BasicPlugin) Run(cliConnection plugin.CliConnection, args []string) {
plugin := flag.NewFlagSet("basic-plugin-command", flag.ExitOnError)
flag := plugin.Bool("NewCommandFlag", false, "Description")
string := plugin.String("NewCommandString", "", "Description")
err := plugin.Parse(args[1:])
if err != nil {
fmt.Println("ERROR:>")
fmt.Println(err)
}
if *flag {
fmt.Println("Flag Activate")
}
if *string != "" {
fmt.Println("String Activate",*string)
}
}
The first block we filter is the user calls our plugin. The following two codes allow us to extract the commands that we want to define as Flag and String.
flag := plugin.Bool("NewCommandFlag", false, "Description")
When calling the Bool function, we use the parameters to get:
- the command to obtain (if it exists)
- the default value
- and a description in case it fails.
string := plugin.String("NewCommandString", "", "Description")
In the same way as in the previous case, the String function has:
- the command to obtain (if it exists)
- the default value
- and a description in case it fails
The next block we validate if the parameters have been passed correctly and move the parameter pointer to position 1 where we have the own parameters of our plugin.
err := plugin.Parse(args[1:])
if err != nil {
fmt.Println("ERROR:>")
fmt.Println(err)
}
We can only add the logic of each command, in this case, we only display texts based on the CLI input parameters.
if *flag {
fmt.Println("Flag Activate")
}
if *string != "" {
fmt.Println("String Activate",*string)
}
Now we re-compile and install the plugin. Then we can launch it as follows:
cf basic-plugin-command -h
cf basic-plugin-command -NewCommandString MyString --NewCommandFlag
Building plugin (Call other commands)
Many times it will be necessary to call other CF client commands in order to obtain data without having to implement API calls. There are two ways to proceed.
Launching a CF
Using the cliConnection library, which has the CF client’s connectivity data, we can invoke any of the client’s standard commands as a call.
For example we extract the available services from our CF account:
command_result, errorCliCommand := cliConnection.CliCommandWithoutTerminalOutput("marketplace", "-s", resource.Parameters.Service)
In this case, when calling 芦CliCommandWithoutTerminalOutput禄 with the command we will have in the command_result variable the string with the result. Just as if we did it ourselves from the client.
In order to see how it works, we will add the above instruction to our “Run” function. We will also add a print function to show the result.
And here the result:
Calling a CF client function
Another way to make the call is to search the “cliConnection” library. The documentation has all the possible methods: DOC.md
To see an example. We will obtain the data of a specific service. For this we will use the following function:
service,errorCliCommand = cliConnection.GetService("MyService")
fmt.Println(service.Guid,*string)
In the documentation we can see the output structure that we can use:
If we add this code in the “Run” function, compile and re-run (remember to change the name of the service ?) the plugin will have a result similar to the following:
In this case, we have extracted the service ID as an example.
Now you have no excuse to create and publish your plugins… and of course, you can see an example of the plugin that I created
https://github.com/enric11/cf-cli-check-before-deploy
https://github.com/enric11/cf-cli-check-before-deploy