Given I made a comment about the difficulty in getting dependencies set up for an external client, I figure I might as well share the code that I wrote. It is intended to fake being a Juju Unit Agent and call some of the APIs. You have to supply the IP address of the controller, the UUID of the model you are connecting to, and the unit name and password.
I generally use
juju status -m controller
juju models --uuid
juju ssh MACHINE grep apipassword /var/log/juju/agents/unit*/agent.conf
To get the various bits of information that I want to pass in.
I actually found the size of the script to be quite manageable, and it means you can easily poke at whatever APIs we’re trying to expose, without going through all the difficulty of injecting it into a real agent.
Here is the script:
package main
import (
"fmt"
"math/rand"
"net"
"os"
"strings"
"time"
"github.com/juju/errors"
"github.com/juju/gnuflag"
"github.com/juju/loggo"
"gopkg.in/juju/names.v2"
"github.com/juju/juju/api"
"github.com/juju/juju/api/leadership"
coreleadership "github.com/juju/juju/core/leadership"
)
var unit = gnuflag.String("unit", "ubuntu-lite/0", "set the unit name that we will connect as")
var password = gnuflag.String("password", "", "the password for this agent")
var host = gnuflag.String("host", "localhost", "the host to connect to")
var port = gnuflag.Int("port", 17070, "the apiserver port")
var uuid = gnuflag.String("uuid", "", "model-uuid to connect to")
var claimtime = gnuflag.String("claimtime", "10s", "time that we will request to hold the lease")
var renewtime = gnuflag.String("renewtime", "", "how often we will renew the lease (default 1/2 the claim time)")
var quiet = gnuflag.Bool("quiet", false, "print only when the leases are claimed")
func main() {
loggo.GetLogger("").SetLogLevel(loggo.DEBUG)
now := time.Now()
rand.Seed(int64(now.Nanosecond() + os.Getpid()))
gnuflag.Parse(true)
claimDuration, err := time.ParseDuration(*claimtime)
if err != nil {
panic(err)
}
renewDuration := claimDuration / 2
if *renewtime != "" {
renewDuration, err = time.ParseDuration(*renewtime)
if err != nil {
panic(err)
}
}
if !names.IsValidUnit(*unit) {
panic(fmt.Sprintf("must supply a valid unit name, not: %q", *unit))
}
modelTag := names.NewModelTag(*uuid)
unitTag := names.NewUnitTag(*unit)
lease, err := names.UnitApplication(*unit)
if err != nil {
panic(err)
}
info := &api.Info{
Addrs: []string{net.JoinHostPort(*host, fmt.Sprint(*port))},
ModelTag: modelTag,
Tag: unitTag,
Password: *password,
}
opts := api.DefaultDialOpts()
opts.InsecureSkipVerify = true
conn, err := api.Open(info, opts)
if err != nil {
panic(err)
}
leadershipClient := leadership.NewClient(conn)
next := time.After(0)
for {
select {
case <-next:
err := leadershipClient.ClaimLeadership(lease, unitTag.Id(), claimDuration)
if err == nil {
next = time.After(renewDuration)
if *quiet {
fmt.Fprintf(os.Stderr, "claimed %q for %q\n", lease, unitTag.Id())
} else {
fmt.Fprintf(os.Stderr, "claimed leadership of %q for %q for %v, renewing after %v\n", lease, unitTag.Id(), claimDuration, renewDuration)
}
} else {
if errors.Cause(err) == coreleadership.ErrClaimDenied {
if !*quiet {
fmt.Fprintf(os.Stderr, "claim of %q for %q denied, blocking until released\n", lease, unitTag.Id())
}
// Note: the 'cancel' channel does nothing
err := leadershipClient.BlockUntilLeadershipReleased(lease, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "blocking for leadership of %q for %q failed with %v\n", lease, unitTag.Id(), err)
return
}
if !*quiet {
fmt.Fprintf(os.Stderr, "blocking of %q for %q returned, attempting to claim\n", lease, unitTag.Id())
}
next = time.After(0)
} else {
fmt.Fprintf(os.Stderr, "claim of %q for %q failed: %v\n", lease, unitTag.Id(), err)
return
}
}
}
}
}