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"
)
We are going to add some logic to see how it works, for this in the «Run» function we will add the following lines and then I explain with more details:
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

Original Article:
https://blogs.sap.com/2020/04/28/creating-a-plugin-for-the-cloud-foundry-client/

ASK SAP EXPERTS ONLINE
Related blogs

LEAVE A REPLY

Please enter your comment!
Please enter your name here