---
title: Extending Juju, Plugin basics in Go
description: This is a quick introduction on extending Juju with plugins written in
  Go. What we’ll cover:Setting up your Go environmentGetting the Juju source codeWriting
  a basic plugin named juju-lyaplugin short for (juju-learnyouaplugin)End result will
  be a […]
url: https://canonical.com/blog/extending-juju-plugin-basics-in-go?format=md
---

1. [Blog](https://canonical.com/blog)
2. Article

---

[Adam Stokes](https://canonical.com/blog/author/adam-stokes "More about Adam Stokes")

17 November 2015

# Extending Juju, Plugin basics in Go

[golang](https://canonical.com/blog/tag/golang)
[Juju](https://canonical.com/blog/tag/juju)

---

Share the article

This is a quick introduction on extending Juju with plugins written in Go. What we’ll cover:

* Setting up your Go environment
* Getting the Juju source code
* Writing a basic plugin named **juju-lyaplugin** short for (juju-learnyouaplugin)
* End result will be a plugin that closely resembles what **juju run** would do.

## Prerequisites

* Running on Ubuntu 14.04 or above
* Go 1.2.1 or above (Article written using Go 1.2.1)
* A basic understanding of the Go language, package imports, etc.

## Setting up your Go environment

This is all a matter of preference but for the sake of this article we’ll do it
my way 🙂

### Install Go

On Trusty and above:

```
sudo apt-get install golang
```

### Go dependency management

2 projects I use are:

* <https://github.com/pote/gpm> – Barebones dependency manager for Go
* <https://github.com/pote/gvp> – Go Versioning Packager

#### Install

```
$ cd /tmp && git clone https://github.com/pote/gvp.git && cd gvp && sudo ./configure && sudo make install
$ cd /tmp && git clone https://github.com/pote/gpm.git && cd gpm && sudo ./configure && sudo make install
```

Feel free to check out their project pages for additional uses.

### Create your project directory

```
$ mkdir ~/Projects/juju-learnyouaplugin
$ cd ~/Projects/juju-learnyouaplugin
```

### Setup the project specific Go paths

```
$ source gvp in
```

This will setup your **$GOPATH** and **$GOBIN** variables for use when resolving imports, compiling, etc.

```
$ echo $GOPATH
/home/adam/Projects/juju-learnyouaplugin/.godeps
$ echo $GOBIN
/home/adam/Projects/juju-learnyouaplugin/.godeps/bin
```

From this point on all package dependencies will be stored in the project’s **.godeps** directory.

## Get the Juju code

From your project’s directory run:

```
$ go get -d -v github.com/juju/juju/...
```

## Writing the plugin

Now that all the preparatory tasks are complete we can begin the fun stuff. Using your favorite editor open up a new file **main.go**. Within this file we need to define a few package imports that are necessary for the plugin.

```
import (
    "fmt"
    "github.com/juju/cmd"
    "github.com/juju/juju/apiserver/params"
    "github.com/juju/juju/cmd/envcmd"
    "github.com/juju/juju/juju"
    "github.com/juju/loggo"
    "github.com/juju/names"
    "launchpad.net/gnuflag"
    "os"
    "time"

    _ "github.com/juju/juju/provider/all"
)
```

Let’s go through the imports and list why they are required.

* **github.com/juju/cmd** – This import gives us access to the run context of a command ***[DefaultContext](https://godoc.org/github.com/juju/cmd#Context)***
* **github.com/juju/juju/cmd/envcmd** – Provides ***[EnvCommandBase](https://godoc.org/github.com/juju/juju/cmd/envcmd#EnvCommandBase)*** for creating new commands and giving us access to the
  [API Client](https://godoc.org/github.com/juju/juju/cmd/envcmd#EnvCommandBase.NewAPIClient) for making queries against the Juju state server.
* **github.com/juju/juju/apiserver/params** – Provides access to 2 types [RunParams](https://godoc.org/github.com/juju/juju/apiserver/params#RunParams) and [RunResults](https://godoc.org/github.com/juju/juju/apiserver/params#RunResult) for executing the api call to [Run](https://godoc.org/github.com/juju/juju/api#Client.Run) and return the executed results.
* **github.com/juju/juju/juju** – Provides access to ***[InitJujuHome](https://godoc.org/github.com/juju/juju/juju#InitJujuHome)*** for initializing the necessary bits like charm cache and environment. Required before running any juju cli command.
* **github.com/juju/loggo** – Provides access to juju’s logging api
* **github.com/juju/names** – This package provides some convenience functions in particular we’ll use ***[IsValidMachine](https://godoc.org/github.com/juju/names#IsValidMachine)***
* **launchpad.net/gnuflag** – Provides the interface for our command definition like setting arguments, usage information, and execution.
* **github.com/juju/juju/provider/all** – Registers all known providers (amazon, maas, local, etc)

With that said let’s spec out the plugin type. This will hold our embedded command base and cli arguments.

```
type LYAPluginCommand struct {
    envcmd.EnvCommandBase
    out      cmd.Output
    timeout  time.Duration
    machines []string
    services []string
    units    []string
    commands string
    envName string
    description bool
}
```

Once defined we can spec out our cli command and its functions.

#### The info function

First part of the command is the ***[Info()](https://godoc.org/github.com/juju/cmd#SuperCommand.Info)*** function which returns
information about the particular subcommand, in our case that is **lyaplugin**

```
var doc = `Run a command on target machine(s)

This example plugin mimics what "juju run" does.

eg.

juju lyaplugin -m 1 -e local "touch /tmp/testfile"
`

func (c *LYAPluginCommand) Info() *cmd.Info {
    return &cmd.Info{
        Name:    "lyaplugin",
        Args:    "<commands>",
        Purpose: "Run a command on remote target",
        Doc:     doc,
    }
}
```

#### SetFlags function

Next we’ll define what arguments are available to this new subcommand (**lyaplugin**).

```
func (c *LYAPluginCommand) SetFlags(f *gnuflag.FlagSet) {
    f.BoolVar(&c.description, "description", false, "Plugin Description")
    f.Var(cmd.NewStringsValue(nil, &c.machines), "machine", "one or more machine ids")
    f.Var(cmd.NewStringsValue(nil, &c.machines), "m", "")
    f.StringVar(&c.envName, "e", "local", "Juju environment")
    f.StringVar(&c.envName, "environment", "local", "")
}
```

Here we are providing a ***–description*** argument to satisfy a [Juju plugin requirement](https://github.com/juju/plugins#plugin-requirements). In addition a target argument **-m/–machine MACHINEID** and the ability to define which juju environment to execute this in **-e/–environment** defaults to **local** environment.

#### Init function

Here we’ll parse the cli arguments, do some basic sanity checking to make sure the passed arguments validate to our liking.

```
func (c *LYAPluginCommand) Init(args []string) error {
    if c.description {
        fmt.Println(doc)
        os.Exit(0)
    }
    if len(args) == 0 {
        return fmt.Errorf("no commands specified")
    }
    if c.envName == "" {
        return fmt.Errorf("Juju environment must be specified.")
    }
    c.commands, args = args[0], args[1:]
    if len(c.machines) == 0 {
        return fmt.Errorf("You must specify a target with --machine, -m")
    }

    for _, machineId := range c.machines {
        if !names.IsValidMachine(machineId) {
            return fmt.Errorf("(%s) not a valid machine id.", machineId)
        }
    }
    return cmd.CheckEmpty(args)
}
```

Notice the **names.IsValidMachine(machineId)** which was imported above as this is the only place where we make use of that particular package.

#### Run function

To the heart of the command where the execution based on the cli arguments take place. I’ll describe inline what is happening:

```
func (c *LYAPluginCommand) Run(ctx *cmd.Context) error {
    c.SetEnvName(c.envName)
```

Set the environment name pulled from our arguments list so we known which environment to run our command against.

```
    client, err := c.NewAPIClient()
    if err != nil {
        return fmt.Errorf("Failed to load api client: %s", err)
    }
    defer client.Close()
```

Grab the api client for the current environment.

```
    var runResults []params.RunResult

    logger.Infof("Running cmd: %s on machine: %s", c.commands, c.machines[0])
    params := params.RunParams{
        Commands: c.commands,
        Timeout:  c.timeout,
        Machines: c.machines,
        Services: c.services,
        Units:    c.units,
    }
```

Prepare the **RunParams** for passing to the api’s **Run** function.

```
    runResults, err = client.Run(params)
    if err != nil {
        fmt.Errorf("An error occurred: %s", err)
    }
    if len(runResults) == 1 {
        result := runResults[0]
        logger.Infof("Result: out(%s), err(%s), code(%d)", result.Stdout, result.Stderr, result.Code)
    }
    return nil
}
```

Execute the api **Run** function and return the results from the executed command on the machine.

#### Entrypoint

The last bit of code is our **main** function which ties everything together.

```
func main() {
    loggo.ConfigureLoggers("<root>=INFO")
    err := juju.InitJujuHome()
```

Initialize the Juju environment based on the default paths or if **$JUJU\_HOME** is defined.

```
    if err != nil {
        panic(err)
    }

    ctx, err := cmd.DefaultContext()
    if err != nil {
        panic(err)
    }
```

Set the proper command context

```
    c := &LYAPluginCommand{}
    cmd.Main(c, ctx, os.Args[1:])
}
```

Pass our plugin type/command into the supplied command Context and off you go.

## Finish

With the code written, build and run the command.

```
$ go build -o juju-lyaplugin -v main.go
```

Place the executable somewhere in your **$PATH**

```
$ mv juju-lyaplugin ~/bin
```

See if Juju picks it up

```
$ juju help lyaplugin
usage: lyaplugin [options] <commands>
purpose: Run a command on remote target

options:
--description  (= false)
    Plugin Description
-e, --environment (= "local")
    Juju environment
-m, --machine  (= )

Run a command on target machine(s)

This example plugin mimics what "juju run" does.

eg.

juju lyaplugin -m 1 -e local "touch /tmp/testfile"
```

See it in your list of plugins, requires [juju-plugins](https://github.com/juju/plugins) to be installed:

```
$ juju help plugins
Juju Plugins

Plugins are implemented as stand-alone executable files somewhere in the user's PATH.
The executable command must be of the format juju-<plugin name>.

...
git-charm        Clone and keep up-to-date a git repository containing a Juju charm for easy source managing.
kill             Destroy a juju object and reap the environment.
lyaplugin        Run a command on target machine(s)
...
```

This should hopefully give you a better idea where to start when you decide to dive into writing a juju plugin 🙂

[Full source code for juju-learnyouaplugin](https://github.com/battlemidget/juju-learnyouaplugin)

[Read original article](http://blog.astokes.org/extending-juju-plugin-basics-in-go/)

[Get in touch

Interested in running Ubuntu in your organization?](https://ubuntu.com/about/contact-us/form)

## Sign up for our newsletter

Get the latest Canonical news and updates in your inbox.

Work email:

\*I agree to receive information about Canonical's
products and services.

By submitting this form, I confirm that I have read and agree to [Canonical's Privacy Policy](https://canonical.com/legal/dataprivacy).

Sign up

## Share on

---

## Related posts

[### Deploy your Spring Boot application to production](https://canonical.com/blog/deploy-spring-application-to-production)

In this article we walk through the steps required to deploy a Spring Boot application to production using Juju and Kubernetes. The goal is to showcase the integration of the...

[Javier de la Puente](https://canonical.com/blog/author/javierdelapuente)

13 January 2026

[### Cut data center energy costs with bare metal automation](https://canonical.com/blog/cut-data-center-energy-costs-with-bare-metal-automation)

Data centers don’t have to be power-hungry monsters. With smart automation using tools like MAAS, you can reduce energy waste and operational costs, and make your...

[David Beamonte](https://canonical.com/blog/author/dbeamonte)

26 June 2025

[### The hitchhiker’s guide to infrastructure modernization](https://canonical.com/blog/hitchhikers-guide-to-infrastructure-modernization)

One of my favourite authors, Douglas Adams, once said that “we are stuck with technology when what we really want is just stuff that works.” Whilst Adams is right about a lot...

[Isobel Kate Maxwell](https://canonical.com/blog/author/iso-maxwell)

24 April 2025

[### Effortless observability for Django applications](https://canonical.com/blog/effortless-observability-for-django-applications)

Observability is critical for web operations to ensure that the application is working as expected and to identify any potential issues. However, setting up observability has...

[jdkandersson](https://canonical.com/blog/author/jdkandersson)

13 March 2025
