James Thomas

Notes on JavaScript

Creating CF CLI Plugins

Since the v.6.7 release of the Cloud Foundry Command Line Interface (CF CLI), users have been to create and install plugins to provide custom commands.

There’s now a whole community of third-party plugins to help make you more productive developing Cloud Foundry applications.

Installing Plugins

Plugins can be installed directly from the platform binary.

1
2
$ go get github.com/sample_user/sample_plugin
$ cf install-plugin $GOPATH/bin/sample_plugin

…or discovered and installed directly from plugin repositories.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cf add-plugin-repo cf-plugins http://plugins.cloudfoundry.org/
$ cf list-plugin-repos
OK

Repo Name    Url
cf-plugins   http://plugins.cloudfoundry.org/

$ cf repo-plugins
Getting plugins from all repositories ...

Repository: cf-plugins
name                   version   description
CLI-Recorder           1.0.1     Records and playbacks CLI commands.
Live Stats             1.0.0     Monitor CPU and Memory usage on an app via the browser.
Console                1.0.0     Start a tmate session on an application container
Diego-Beta             1.3.0     Enables Diego-specific commands and functionality
Open                   1.1.0     Open app url in browser
autopilot              0.0.1     zero downtime deploy plugin for cf applications
Brooklyn               0.1.1     Interact with Service Broker for Apache Brooklyn
kibana-me-logs         0.3.0     Launches the Kibana UI (from kibana-me-logs) for an application.
Buildpack Usage        1.0.0     View all buildpacks used in the current CLI target context.
CF App Stack Changer   1.0.0     Allows admins to list and update applications with outdated lucid64 stacks.

Once a repository has been registered, we can search and install the available plugins.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ cf install-plugin open -r cf-plugins
Looking up 'open' from repository 'cf-plugins'
  7998292 bytes downloaded...
Installing plugin /var/folders/db/9y12sh3n0kdg4v3zxnn8dbg80000gn/T/ filename=cf-plugin-open_darwin_amd64...
OK
Plugin open v1.1.0 successfully installed.

$ cf plugins
Listing Installed Plugins...
OK

Plugin Name   Version   Command Name   Command Help
open          1.1.0     open           open app url in browser

$ cf open
NAME:
   open - open app url in browser

USAGE:
   open <appname>

How about creating your own plugins? Here I’ll show you how by walking through the steps used to create my first plugin, copyenv.

Creating New Plugins

Plugins are Go binaries, implenting a common interface defined by the CF CLI project.

There’s a Run() function to implement that acts as a callback when the user issues the plugin command along with a GetMetadata() function to provide the metadata for the new command.

There’s a list of example plugins to start with in the CF CLI repository.

For our plugin, we’re starting with the basic_plugin code. This file contains a skeleton outline for a basic plugin implementation that you can modify.

Plugin Structure

Reviewing the basic_plugin example, plugins follow a simple structure.

First, we declare the Go package “main” as this code will be compiled into an executable command. Application dependencies are registered with the “import” definition. We link to the CF CLI Plugin package to access the common interface that defines a runnable plugin. BasicPlugin is the name of our struct that will implement the Plugin Interface.

1
2
3
4
5
6
7
8
package main

import (
  "fmt"
  "github.com/cloudfoundry/cli/plugin"
)

type BasicPlugin struct{}

The “Run” function will be executed each time a user calls our custom plugin command. We are passed a reference to the CF CLI, for running additional commands, along with the command line arguments.

1
2
3
4
5
6
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")
  }
}

Returning metadata to install the plugin is implemented via the “GetMetadata” function. We can specify the plugin version number, help documentation and command identifiers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (c *BasicPlugin) GetMetadata() plugin.PluginMetadata {
  return plugin.PluginMetadata{
    Name: "MyBasicPlugin",
    Version: plugin.VersionType{
      Major: 1,
      Minor: 0,
      Build: 0,
    },
    Commands: []plugin.Command{
      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",
        },
      },
    },
  }
}

Finally, the “main” function will the entry point when executing the compiled binary. Calling “plugin.Start” with a pointer to the struct implementing the Plugin interace will register our plugin.

1
2
3
func main() {
  plugin.Start(new(BasicPlugin))
}

CopyEnv Plugin

Cloud Foundry CLI plugin to export application VCAP_SERVICES onto the local machine.

Applications running on Cloud Foundry rely on the VCAP_SERVICES environment variable to provide service credentials.

When running applications locally for development and testing, it’s useful to have the same VCAP_SERVICES values available in the local environment to simulate running on the host platform.

This plugin will export the remote application environment variables, available using cf env, into a format that makes it simple to expose those same values locally.

Modifying the Sample Plugin

For the new plugin, we will need to get an application name from the user, access the remote VCAP_SERVICES environment variable and then export this into the user’s local environment.

Accessing an application’s environment variables can be retrieved using the existing cf env command. The “plugin.CliConnection” reference passed into the Run function has methods for executing CLI commands from within the plugin.

We’re following the convention of the “cf env” command by having the application name as a command line argument. This means we can modify the existing “args” value to set up the CLI command to retrieve the VCAP_SERVICES value.

1
2
3
4
5
6
7
8
func (c *CopyEnv) Run(cliConnection plugin.CliConnection, args []string) {
  if len(args) < 2 {
    fmt.Println("ERROR: Missing application name")
     os.Exit(1)
  }

  args[0] = "env"
  output, err := cliConnection.CliCommandWithoutTerminalOutput(args...)

Now we have an array of strings, output, containing the text output from cf env APP_NAME command. Iterating through this list, we search for the line which contains the VCAP_SERVICES definition. This value will be a JSON object with a VCAP_SERVICES attribute defining the service credentials.

Exporting this JSON object to the local environment, we need to convert the VCAP_SERVICES object into a shell environment variable definition. Go has built in support for the JSON language. We decode the parent JSON to a Map interface and then export the VCAP_SERVICES attribute as JSON. This text is then wrapped within a shell variable definition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for _, val := range output {
  if (strings.Contains(val, "VCAP_SERVICES")) {
    var f interface{}
    err := json.Unmarshal([]byte(val), &f)
    if err != nil {
      fmt.Println(err)
      os.Exit(1)
    }

    m := f.(map[string]interface{})
    b, err := json.Marshal(m["VCAP_SERVICES"])
    if err != nil {
      fmt.Println(err)
      os.Exit(1)
    }

    vcap_services := "export VCAP_SERVICES='" + string(b[:]) + "';"
    fmt.Println(vcap_services)
  }
}

Once we’ve finished the code, install the compiled binary using the CF CLI.

1
2
$ go build copyenv.go
$ cf install-plugin copyenv

Making plugin available for other users

Exporting out plugin into an external Git repository will allow users to use the Go package manager to retrieve and compile the plugin for installation with the CF CLI.

1
2
$ go get github.com/sample_user/sample_plugin
$ cf install-plugin $GOPATH/bin/sample_plugin

We can also include the plugin in the official Cloud Foundry Plugin Repository by forking the source project, adding their plugin definition to the repo-index.yml file and submitting a pull request.

For maximum compatibility, plugin authors are encouraged to include platform binaries for their plugins.

Go makes it extremely easy to cross-compile your source code for different platforms.

On Mac OS X, if you used Brew to install Go, you can set up cross-compilation with the following commands:

1
2
$ brew reinstall go --with-cc-common
$ GOOS=windows GOARCH=386 go build appname.go

For the full list of supported platforms, see the Go documentation

Using the Plugin

With the CopyEnv plugin installed, we can now run the following command to export an application’s VCAP_SERVICES into our local environment.

1
2
$ cf copyenv APP_NAME
export VCAP_SERVICES='{...}';

Writing a new plugin for the CF CLI was extremely straightforward. It’s a great feature to that enables people to contribute new plugins with minimal effort. I’m looking forward to seeing what plugins the community comes up with!

You can see the plugin in action below…

Comments