cord-776 create build / runtime containers for autmation uservices

Change-Id: I246973192adef56a250ffe93a5f65fff488840c1
diff --git a/automation/vendor/gopkg.in/juju/names.v2/LICENSE b/automation/vendor/gopkg.in/juju/names.v2/LICENSE
new file mode 100644
index 0000000..ade9307
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/LICENSE
@@ -0,0 +1,191 @@
+All files in this repository are licensed as follows. If you contribute
+to this repository, it is assumed that you license your contribution
+under the same license unless you state otherwise.
+
+All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
+
+This software is licensed under the LGPLv3, included below.
+
+As a special exception to the GNU Lesser General Public License version 3
+("LGPL3"), the copyright holders of this Library give you permission to
+convey to a third party a Combined Work that links statically or dynamically
+to this Library without providing any Minimal Corresponding Source or
+Minimal Application Code as set out in 4d or providing the installation
+information set out in section 4e, provided that you comply with the other
+provisions of LGPL3 and provided that you meet, for the Application the
+terms and conditions of the license(s) which apply to the Application.
+
+Except as stated in this special exception, the provisions of LGPL3 will
+continue to comply in full to this Library. If you modify this Library, you
+may apply this exception to your version of this Library, but you are not
+obliged to do so. If you do not wish to do so, delete this exception
+statement from your version. This exception does not (and cannot) modify any
+license terms which apply to the Application, with which you must still
+comply.
+
+
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/automation/vendor/gopkg.in/juju/names.v2/README.md b/automation/vendor/gopkg.in/juju/names.v2/README.md
new file mode 100644
index 0000000..ecacdc7
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/README.md
@@ -0,0 +1,4 @@
+juju/names
+============
+
+This package provides helpers for handling Juju entity names.
diff --git a/automation/vendor/gopkg.in/juju/names.v2/action.go b/automation/vendor/gopkg.in/juju/names.v2/action.go
new file mode 100644
index 0000000..3211881
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/action.go
@@ -0,0 +1,79 @@
+// Copyright 2014 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+
+	"github.com/juju/errors"
+	"github.com/juju/utils"
+)
+
+const ActionTagKind = "action"
+
+type ActionTag struct {
+	// Tags that are serialized need to have fields exported.
+	ID utils.UUID
+}
+
+// NewActionTag returns the tag of an action with the given id (UUID).
+func NewActionTag(id string) ActionTag {
+	uuid, err := utils.UUIDFromString(id)
+	if err != nil {
+		panic(err)
+	}
+	return ActionTag{ID: uuid}
+}
+
+// ParseActionTag parses an action tag string.
+func ParseActionTag(actionTag string) (ActionTag, error) {
+	tag, err := ParseTag(actionTag)
+	if err != nil {
+		return ActionTag{}, err
+	}
+	at, ok := tag.(ActionTag)
+	if !ok {
+		return ActionTag{}, invalidTagError(actionTag, ActionTagKind)
+	}
+	return at, nil
+}
+
+func (t ActionTag) String() string { return t.Kind() + "-" + t.Id() }
+func (t ActionTag) Kind() string   { return ActionTagKind }
+func (t ActionTag) Id() string     { return t.ID.String() }
+
+// IsValidAction returns whether id is a valid action id (UUID).
+func IsValidAction(id string) bool {
+	return utils.IsValidUUIDString(id)
+}
+
+// ActionReceiverTag returns an ActionReceiver Tag from a
+// machine or unit name.
+func ActionReceiverTag(name string) (Tag, error) {
+	if IsValidUnit(name) {
+		return NewUnitTag(name), nil
+	}
+	if IsValidApplication(name) {
+		// TODO(jcw4) enable when leader elections complete
+		//return NewApplicationTag(name), nil
+	}
+	if IsValidMachine(name) {
+		return NewMachineTag(name), nil
+	}
+	return nil, fmt.Errorf("invalid actionreceiver name %q", name)
+}
+
+// ActionReceiverFrom Tag returns an ActionReceiver tag from
+// a machine or unit tag.
+func ActionReceiverFromTag(tag string) (Tag, error) {
+	unitTag, err := ParseUnitTag(tag)
+	if err == nil {
+		return unitTag, nil
+	}
+	machineTag, err := ParseMachineTag(tag)
+	if err == nil {
+		return machineTag, nil
+	}
+	return nil, errors.Errorf("invalid actionreceiver tag %q", tag)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/application.go b/automation/vendor/gopkg.in/juju/names.v2/application.go
new file mode 100644
index 0000000..aa8985e
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/application.go
@@ -0,0 +1,48 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"regexp"
+)
+
+const ApplicationTagKind = "application"
+
+const (
+	ApplicationSnippet = "(?:[a-z][a-z0-9]*(?:-[a-z0-9]*[a-z][a-z0-9]*)*)"
+	NumberSnippet      = "(?:0|[1-9][0-9]*)"
+)
+
+var validApplication = regexp.MustCompile("^" + ApplicationSnippet + "$")
+
+// IsValidApplication returns whether name is a valid application name.
+func IsValidApplication(name string) bool {
+	return validApplication.MatchString(name)
+}
+
+type ApplicationTag struct {
+	Name string
+}
+
+func (t ApplicationTag) String() string { return t.Kind() + "-" + t.Id() }
+func (t ApplicationTag) Kind() string   { return ApplicationTagKind }
+func (t ApplicationTag) Id() string     { return t.Name }
+
+// NewApplicationTag returns the tag for the application with the given name.
+func NewApplicationTag(applicationName string) ApplicationTag {
+	return ApplicationTag{Name: applicationName}
+}
+
+// ParseApplicationTag parses a application tag string.
+func ParseApplicationTag(applicationTag string) (ApplicationTag, error) {
+	tag, err := ParseTag(applicationTag)
+	if err != nil {
+		return ApplicationTag{}, err
+	}
+	st, ok := tag.(ApplicationTag)
+	if !ok {
+		return ApplicationTag{}, invalidTagError(applicationTag, ApplicationTagKind)
+	}
+	return st, nil
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/charm.go b/automation/vendor/gopkg.in/juju/names.v2/charm.go
new file mode 100644
index 0000000..3984426
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/charm.go
@@ -0,0 +1,105 @@
+// Copyright 2014 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+)
+
+// CharmTagKind specifies charm tag kind
+const CharmTagKind = "charm"
+
+// Valid charm url can be either in V1 or V3 format. (V2 is a
+// charmstore web URL like https://jujucharms.com/postgresql/105, but
+// that's not valid as a tag.)
+//
+// V1 is of the form:
+// schema:~user/series/name-revision
+// where
+//     schema    is optional and can be either "local" or "cs".
+//               When not supplied, "cs" is implied.
+//     user      is optional and is only applicable for "cs" schema
+//     series    is optional and is a valid series name
+//     name      is mandatory and is the name of the charm
+//     revision  is optional and can be -1 if revision is unset
+//
+// V3 is of the form
+// schema:user/name/series/revision
+// with the same fields and constraints as the V1 format.
+
+var (
+	// SeriesSnippet is a regular expression representing series
+	SeriesSnippet = "[a-z]+([a-z0-9]+)?"
+
+	// CharmNameSnippet is a regular expression representing charm name
+	CharmNameSnippet = "[a-z][a-z0-9]*(-[a-z0-9]*[a-z][a-z0-9]*)*"
+
+	localSchemaSnippet        = "local:"
+	v1CharmStoreSchemaSnippet = "cs:(~" + validUserNameSnippet + "/)?"
+	revisionSnippet           = "(-1|0|[1-9][0-9]*)"
+
+	validV1CharmRegEx = regexp.MustCompile("^(" +
+		localSchemaSnippet + "|" +
+		v1CharmStoreSchemaSnippet + ")?(" +
+		SeriesSnippet + "/)?" +
+		CharmNameSnippet + "(-" +
+		revisionSnippet + ")?$")
+
+	v3CharmStoreSchemaSnippet = "(cs:)?(" + validUserNameSnippet + "/)?"
+
+	validV3CharmRegEx = regexp.MustCompile("^(" +
+		localSchemaSnippet + "|" +
+		v3CharmStoreSchemaSnippet + ")" +
+		CharmNameSnippet + "(/" +
+		SeriesSnippet + ")?(/" +
+		revisionSnippet + ")?$")
+)
+
+// CharmTag represents tag for charm
+// using charm's URL
+type CharmTag struct {
+	url string
+}
+
+// String satisfies Tag interface.
+// Produces string representation of charm tag.
+func (t CharmTag) String() string { return t.Kind() + "-" + t.Id() }
+
+// Kind satisfies Tag interface.
+// Returns Charm tag kind.
+func (t CharmTag) Kind() string { return CharmTagKind }
+
+// Id satisfies Tag interface.
+// Returns charm URL.
+func (t CharmTag) Id() string { return t.url }
+
+// NewCharmTag returns the tag for the charm with the given url.
+// It will panic if the given charm url is not valid.
+func NewCharmTag(charmURL string) CharmTag {
+	if !IsValidCharm(charmURL) {
+		panic(fmt.Sprintf("%q is not a valid charm name", charmURL))
+	}
+	return CharmTag{url: charmURL}
+}
+
+var emptyTag = CharmTag{}
+
+// ParseCharmTag parses a charm tag string.
+func ParseCharmTag(charmTag string) (CharmTag, error) {
+	tag, err := ParseTag(charmTag)
+	if err != nil {
+		return emptyTag, err
+	}
+	ct, ok := tag.(CharmTag)
+	if !ok {
+		return emptyTag, invalidTagError(charmTag, CharmTagKind)
+	}
+	return ct, nil
+}
+
+// IsValidCharm returns whether name is a valid charm url.
+func IsValidCharm(url string) bool {
+	return validV1CharmRegEx.MatchString(url) || validV3CharmRegEx.MatchString(url)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/cloud.go b/automation/vendor/gopkg.in/juju/names.v2/cloud.go
new file mode 100644
index 0000000..78a3c8b
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/cloud.go
@@ -0,0 +1,51 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+)
+
+const CloudTagKind = "cloud"
+
+var (
+	cloudSnippet = "[a-zA-Z0-9][a-zA-Z0-9.-]*"
+	validCloud   = regexp.MustCompile("^" + cloudSnippet + "$")
+)
+
+type CloudTag struct {
+	id string
+}
+
+func (t CloudTag) String() string { return t.Kind() + "-" + t.id }
+func (t CloudTag) Kind() string   { return CloudTagKind }
+func (t CloudTag) Id() string     { return t.id }
+
+// NewCloudTag returns the tag for the cloud with the given ID.
+// It will panic if the given cloud ID is not valid.
+func NewCloudTag(id string) CloudTag {
+	if !IsValidCloud(id) {
+		panic(fmt.Sprintf("%q is not a valid cloud ID", id))
+	}
+	return CloudTag{id}
+}
+
+// ParseCloudTag parses a cloud tag string.
+func ParseCloudTag(cloudTag string) (CloudTag, error) {
+	tag, err := ParseTag(cloudTag)
+	if err != nil {
+		return CloudTag{}, err
+	}
+	dt, ok := tag.(CloudTag)
+	if !ok {
+		return CloudTag{}, invalidTagError(cloudTag, CloudTagKind)
+	}
+	return dt, nil
+}
+
+// IsValidCloud returns whether id is a valid cloud ID.
+func IsValidCloud(id string) bool {
+	return validCloud.MatchString(id)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/cloudcredential.go b/automation/vendor/gopkg.in/juju/names.v2/cloudcredential.go
new file mode 100644
index 0000000..1d7252d
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/cloudcredential.go
@@ -0,0 +1,120 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"net/url"
+	"regexp"
+	"strings"
+)
+
+const CloudCredentialTagKind = "cloudcred"
+
+var (
+	cloudCredentialNameSnippet = "[a-zA-Z][a-zA-Z0-9.@_-]*"
+	validCloudCredentialName   = regexp.MustCompile("^" + cloudCredentialNameSnippet + "$")
+	validCloudCredential       = regexp.MustCompile(
+		"^" +
+			"(" + cloudSnippet + ")" +
+			"/(" + validUserSnippet + ")" + // credential owner
+			"/(" + cloudCredentialNameSnippet + ")" +
+			"$",
+	)
+)
+
+type CloudCredentialTag struct {
+	cloud CloudTag
+	owner UserTag
+	name  string
+}
+
+// IsZero reports whether t is zero.
+func (t CloudCredentialTag) IsZero() bool {
+	return t == CloudCredentialTag{}
+}
+
+// Kind is part of the Tag interface.
+func (t CloudCredentialTag) Kind() string { return CloudCredentialTagKind }
+
+// Id implements Tag.Id. It returns the empty string
+// if t is zero.
+func (t CloudCredentialTag) Id() string {
+	if t.IsZero() {
+		return ""
+	}
+	return fmt.Sprintf("%s/%s/%s", t.cloud.Id(), t.owner.Id(), t.name)
+}
+
+func quoteCredentialSeparator(in string) string {
+	return strings.Replace(in, "_", `%5f`, -1)
+}
+
+// String implements Tag.String. It returns the empty
+// string if t is zero.
+func (t CloudCredentialTag) String() string {
+	if t.IsZero() {
+		return ""
+	}
+	return fmt.Sprintf("%s-%s_%s_%s", t.Kind(),
+		quoteCredentialSeparator(t.cloud.Id()),
+		quoteCredentialSeparator(t.owner.Id()),
+		quoteCredentialSeparator(t.name))
+}
+
+// Cloud returns the tag of the cloud to which the credential pertains.
+func (t CloudCredentialTag) Cloud() CloudTag {
+	return t.cloud
+}
+
+// Owner returns the tag of the user that owns the credential.
+func (t CloudCredentialTag) Owner() UserTag {
+	return t.owner
+}
+
+// Name returns the cloud credential name, excluding the
+// cloud and owner qualifiers.
+func (t CloudCredentialTag) Name() string {
+	return t.name
+}
+
+// NewCloudCredentialTag returns the tag for the cloud with the given ID.
+// It will panic if the given cloud ID is not valid.
+func NewCloudCredentialTag(id string) CloudCredentialTag {
+	parts := validCloudCredential.FindStringSubmatch(id)
+	if len(parts) != 4 {
+		panic(fmt.Sprintf("%q is not a valid cloud credential ID", id))
+	}
+	cloud := NewCloudTag(parts[1])
+	owner := NewUserTag(parts[2])
+	return CloudCredentialTag{cloud, owner, parts[3]}
+}
+
+// ParseCloudCredentialTag parses a cloud tag string.
+func ParseCloudCredentialTag(s string) (CloudCredentialTag, error) {
+	tag, err := ParseTag(s)
+	if err != nil {
+		return CloudCredentialTag{}, err
+	}
+	dt, ok := tag.(CloudCredentialTag)
+	if !ok {
+		return CloudCredentialTag{}, invalidTagError(s, CloudCredentialTagKind)
+	}
+	return dt, nil
+}
+
+// IsValidCloudCredential returns whether id is a valid cloud credential ID.
+func IsValidCloudCredential(id string) bool {
+	return validCloudCredential.MatchString(id)
+}
+
+// IsValidCloudCredentialName returns whether name is a valid cloud credential name.
+func IsValidCloudCredentialName(name string) bool {
+	return validCloudCredentialName.MatchString(name)
+}
+
+func cloudCredentialTagSuffixToId(s string) (string, error) {
+	s = strings.Replace(s, "_", "/", -1)
+	return url.QueryUnescape(s)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/controller.go b/automation/vendor/gopkg.in/juju/names.v2/controller.go
new file mode 100644
index 0000000..c48c326
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/controller.go
@@ -0,0 +1,56 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"regexp"
+)
+
+// ControllerTagKind indicates that a tag belongs to a controller.
+const ControllerTagKind = "controller"
+
+// ControllerTag represents a tag used to describe a controller.
+type ControllerTag struct {
+	uuid string
+}
+
+// Lowercase letters, digits and (non-leading) hyphens.
+var validControllerName = regexp.MustCompile(`^[a-z0-9]+[a-z0-9-]*$`)
+
+// NewControllerTag returns the tag of an controller with the given controller UUID.
+func NewControllerTag(uuid string) ControllerTag {
+	return ControllerTag{uuid: uuid}
+}
+
+// ParseControllerTag parses an environ tag string.
+func ParseControllerTag(controllerTag string) (ControllerTag, error) {
+	tag, err := ParseTag(controllerTag)
+	if err != nil {
+		return ControllerTag{}, err
+	}
+	et, ok := tag.(ControllerTag)
+	if !ok {
+		return ControllerTag{}, invalidTagError(controllerTag, ControllerTagKind)
+	}
+	return et, nil
+}
+
+// String implements Tag.
+func (t ControllerTag) String() string { return t.Kind() + "-" + t.Id() }
+
+// Kind implements Tag.
+func (t ControllerTag) Kind() string { return ControllerTagKind }
+
+// Id implements Tag.
+func (t ControllerTag) Id() string { return t.uuid }
+
+// IsValidController returns whether id is a valid controller UUID.
+func IsValidController(id string) bool {
+	return validUUID.MatchString(id)
+}
+
+// IsValidControllerName returns whether name is a valid string safe for a controller name.
+func IsValidControllerName(name string) bool {
+	return validControllerName.MatchString(name)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/environ.go b/automation/vendor/gopkg.in/juju/names.v2/environ.go
new file mode 100644
index 0000000..db02d00
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/environ.go
@@ -0,0 +1,38 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+// EnvironTagKind is DEPRECATED: model tags are used instead.
+const EnvironTagKind = "environment"
+
+type EnvironTag struct {
+	uuid string
+}
+
+// NewEnvironTag returns the tag of an environment with the given environment UUID.
+func NewEnvironTag(uuid string) EnvironTag {
+	return EnvironTag{uuid: uuid}
+}
+
+// ParseEnvironTag parses an environ tag string.
+func ParseEnvironTag(environTag string) (EnvironTag, error) {
+	tag, err := ParseTag(environTag)
+	if err != nil {
+		return EnvironTag{}, err
+	}
+	et, ok := tag.(EnvironTag)
+	if !ok {
+		return EnvironTag{}, invalidTagError(environTag, EnvironTagKind)
+	}
+	return et, nil
+}
+
+func (t EnvironTag) String() string { return t.Kind() + "-" + t.Id() }
+func (t EnvironTag) Kind() string   { return EnvironTagKind }
+func (t EnvironTag) Id() string     { return t.uuid }
+
+// IsValidEnvironment returns whether id is a valid environment UUID.
+func IsValidEnvironment(id string) bool {
+	return validUUID.MatchString(id)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/filesystem.go b/automation/vendor/gopkg.in/juju/names.v2/filesystem.go
new file mode 100644
index 0000000..44ecc96
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/filesystem.go
@@ -0,0 +1,76 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+)
+
+const FilesystemTagKind = "filesystem"
+
+// Filesystems may be bound to a machine, meaning that the filesystem cannot
+// exist without that machine. We encode this in the tag.
+var validFilesystem = regexp.MustCompile("^(" + MachineSnippet + "/)?" + NumberSnippet + "$")
+
+type FilesystemTag struct {
+	id string
+}
+
+func (t FilesystemTag) String() string { return t.Kind() + "-" + t.id }
+func (t FilesystemTag) Kind() string   { return FilesystemTagKind }
+func (t FilesystemTag) Id() string     { return filesystemTagSuffixToId(t.id) }
+
+// NewFilesystemTag returns the tag for the filesystem with the given name.
+// It will panic if the given filesystem name is not valid.
+func NewFilesystemTag(id string) FilesystemTag {
+	tag, ok := tagFromFilesystemId(id)
+	if !ok {
+		panic(fmt.Sprintf("%q is not a valid filesystem id", id))
+	}
+	return tag
+}
+
+// ParseFilesystemTag parses a filesystem tag string.
+func ParseFilesystemTag(filesystemTag string) (FilesystemTag, error) {
+	tag, err := ParseTag(filesystemTag)
+	if err != nil {
+		return FilesystemTag{}, err
+	}
+	fstag, ok := tag.(FilesystemTag)
+	if !ok {
+		return FilesystemTag{}, invalidTagError(filesystemTag, FilesystemTagKind)
+	}
+	return fstag, nil
+}
+
+// IsValidFilesystem returns whether id is a valid filesystem id.
+func IsValidFilesystem(id string) bool {
+	return validFilesystem.MatchString(id)
+}
+
+// FilesystemMachine returns the machine component of the filesystem
+// tag, and a boolean indicating whether or not there is a
+// machine component.
+func FilesystemMachine(tag FilesystemTag) (MachineTag, bool) {
+	id := tag.Id()
+	pos := strings.LastIndex(id, "/")
+	if pos == -1 {
+		return MachineTag{}, false
+	}
+	return NewMachineTag(id[:pos]), true
+}
+
+func tagFromFilesystemId(id string) (FilesystemTag, bool) {
+	if !IsValidFilesystem(id) {
+		return FilesystemTag{}, false
+	}
+	id = strings.Replace(id, "/", "-", -1)
+	return FilesystemTag{id}, true
+}
+
+func filesystemTagSuffixToId(s string) string {
+	return strings.Replace(s, "-", "/", -1)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/ipaddress.go b/automation/vendor/gopkg.in/juju/names.v2/ipaddress.go
new file mode 100644
index 0000000..b92a389
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/ipaddress.go
@@ -0,0 +1,46 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"github.com/juju/utils"
+)
+
+const IPAddressTagKind = "ipaddress"
+
+// IsValidIPAddress returns whether id is a valid IP address ID.
+// Here it simply is checked if it is a valid UUID.
+func IsValidIPAddress(id string) bool {
+	return utils.IsValidUUIDString(id)
+}
+
+type IPAddressTag struct {
+	id utils.UUID
+}
+
+func (t IPAddressTag) String() string { return t.Kind() + "-" + t.id.String() }
+func (t IPAddressTag) Kind() string   { return IPAddressTagKind }
+func (t IPAddressTag) Id() string     { return t.id.String() }
+
+// NewIPAddressTag returns the tag for the IP address with the given ID (UUID).
+func NewIPAddressTag(id string) IPAddressTag {
+	uuid, err := utils.UUIDFromString(id)
+	if err != nil {
+		panic(err)
+	}
+	return IPAddressTag{id: uuid}
+}
+
+// ParseIPAddressTag parses an IP address tag string.
+func ParseIPAddressTag(ipAddressTag string) (IPAddressTag, error) {
+	tag, err := ParseTag(ipAddressTag)
+	if err != nil {
+		return IPAddressTag{}, err
+	}
+	ipat, ok := tag.(IPAddressTag)
+	if !ok {
+		return IPAddressTag{}, invalidTagError(ipAddressTag, IPAddressTagKind)
+	}
+	return ipat, nil
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/machine.go b/automation/vendor/gopkg.in/juju/names.v2/machine.go
new file mode 100644
index 0000000..867aa72
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/machine.go
@@ -0,0 +1,60 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"regexp"
+	"strings"
+)
+
+const MachineTagKind = "machine"
+
+const (
+	ContainerTypeSnippet = "[a-z]+"
+	ContainerSnippet     = "/" + ContainerTypeSnippet + "/" + NumberSnippet + ""
+	MachineSnippet       = NumberSnippet + "(?:" + ContainerSnippet + ")*"
+)
+
+var validMachine = regexp.MustCompile("^" + MachineSnippet + "$")
+
+// IsValidMachine returns whether id is a valid machine id.
+func IsValidMachine(id string) bool {
+	return validMachine.MatchString(id)
+}
+
+// IsContainerMachine returns whether id is a valid container machine id.
+func IsContainerMachine(id string) bool {
+	return validMachine.MatchString(id) && strings.Contains(id, "/")
+}
+
+type MachineTag struct {
+	id string
+}
+
+func (t MachineTag) String() string { return t.Kind() + "-" + t.id }
+func (t MachineTag) Kind() string   { return MachineTagKind }
+func (t MachineTag) Id() string     { return machineTagSuffixToId(t.id) }
+
+// NewMachineTag returns the tag for the machine with the given id.
+func NewMachineTag(id string) MachineTag {
+	id = strings.Replace(id, "/", "-", -1)
+	return MachineTag{id: id}
+}
+
+// ParseMachineTag parses a machine tag string.
+func ParseMachineTag(machineTag string) (MachineTag, error) {
+	tag, err := ParseTag(machineTag)
+	if err != nil {
+		return MachineTag{}, err
+	}
+	mt, ok := tag.(MachineTag)
+	if !ok {
+		return MachineTag{}, invalidTagError(machineTag, MachineTagKind)
+	}
+	return mt, nil
+}
+
+func machineTagSuffixToId(s string) string {
+	return strings.Replace(s, "-", "/", -1)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/model.go b/automation/vendor/gopkg.in/juju/names.v2/model.go
new file mode 100644
index 0000000..60e0db2
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/model.go
@@ -0,0 +1,52 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"regexp"
+)
+
+const ModelTagKind = "model"
+
+// ModelTag represents a tag used to describe a model.
+type ModelTag struct {
+	uuid string
+}
+
+var validUUID = regexp.MustCompile(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`)
+
+// Lowercase letters, digits and (non-leading) hyphens, as per LP:1568944 #5.
+var validModelName = regexp.MustCompile(`^[a-z0-9]+[a-z0-9-]*$`)
+
+// NewModelTag returns the tag of an model with the given model UUID.
+func NewModelTag(uuid string) ModelTag {
+	return ModelTag{uuid: uuid}
+}
+
+// ParseModelTag parses an environ tag string.
+func ParseModelTag(modelTag string) (ModelTag, error) {
+	tag, err := ParseTag(modelTag)
+	if err != nil {
+		return ModelTag{}, err
+	}
+	et, ok := tag.(ModelTag)
+	if !ok {
+		return ModelTag{}, invalidTagError(modelTag, ModelTagKind)
+	}
+	return et, nil
+}
+
+func (t ModelTag) String() string { return t.Kind() + "-" + t.Id() }
+func (t ModelTag) Kind() string   { return ModelTagKind }
+func (t ModelTag) Id() string     { return t.uuid }
+
+// IsValidModel returns whether id is a valid model UUID.
+func IsValidModel(id string) bool {
+	return validUUID.MatchString(id)
+}
+
+// IsValidModelName returns whether name is a valid string safe for a model name.
+func IsValidModelName(name string) bool {
+	return validModelName.MatchString(name)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/payload.go b/automation/vendor/gopkg.in/juju/names.v2/payload.go
new file mode 100644
index 0000000..c911ffd
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/payload.go
@@ -0,0 +1,74 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"regexp"
+
+	"github.com/juju/utils"
+)
+
+const (
+	// PayloadTagKind is used as the prefix for the string
+	// representation of payload tags.
+	PayloadTagKind = "payload"
+
+	// This can be expanded later, as needed.
+	payloadClass = "([a-zA-Z](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)"
+)
+
+var validPayload = regexp.MustCompile("^" + payloadClass + "$")
+
+// IsValidPayload returns whether id is a valid Juju ID for
+// a charm payload. The ID must be a valid alpha-numeric (plus hyphens).
+func IsValidPayload(id string) bool {
+	return validPayload.MatchString(id)
+}
+
+// For compatibility with Juju 1.25, UUIDs are also supported.
+func isValidPayload(id string) bool {
+	return IsValidPayload(id) || utils.IsValidUUIDString(id)
+}
+
+// PayloadTag represents a charm payload.
+type PayloadTag struct {
+	id string
+}
+
+// NewPayloadTag returns the tag for a charm's payload with the given id.
+func NewPayloadTag(id string) PayloadTag {
+	return PayloadTag{
+		id: id,
+	}
+}
+
+// ParsePayloadTag parses a payload tag string.
+// So ParsePayloadTag(tag.String()) === tag.
+func ParsePayloadTag(tag string) (PayloadTag, error) {
+	t, err := ParseTag(tag)
+	if err != nil {
+		return PayloadTag{}, err
+	}
+	pt, ok := t.(PayloadTag)
+	if !ok {
+		return PayloadTag{}, invalidTagError(tag, PayloadTagKind)
+	}
+	return pt, nil
+}
+
+// Kind implements Tag.
+func (t PayloadTag) Kind() string {
+	return PayloadTagKind
+}
+
+// Id implements Tag.Id. It always returns the same ID with which
+// it was created. So NewPayloadTag(x).Id() == x for all valid x.
+func (t PayloadTag) Id() string {
+	return t.id
+}
+
+// String implements Tag.
+func (t PayloadTag) String() string {
+	return tagString(t)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/relation.go b/automation/vendor/gopkg.in/juju/names.v2/relation.go
new file mode 100644
index 0000000..b61696c
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/relation.go
@@ -0,0 +1,67 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+)
+
+const RelationTagKind = "relation"
+
+const RelationSnippet = "[a-z][a-z0-9]*(?:[_-][a-z0-9]+)*"
+
+// Relation keys have the format "application1:relName1 application2:relName2".
+// Except the peer relations, which have the format "application:relName"
+// Relation tags have the format "relation-application1.rel1#application2.rel2".
+// For peer relations, the format is "relation-application.rel"
+
+var (
+	validRelation     = regexp.MustCompile("^" + ApplicationSnippet + ":" + RelationSnippet + " " + ApplicationSnippet + ":" + RelationSnippet + "$")
+	validPeerRelation = regexp.MustCompile("^" + ApplicationSnippet + ":" + RelationSnippet + "$")
+)
+
+// IsValidRelation returns whether key is a valid relation key.
+func IsValidRelation(key string) bool {
+	return validRelation.MatchString(key) || validPeerRelation.MatchString(key)
+}
+
+type RelationTag struct {
+	key string
+}
+
+func (t RelationTag) String() string { return t.Kind() + "-" + t.key }
+func (t RelationTag) Kind() string   { return RelationTagKind }
+func (t RelationTag) Id() string     { return relationTagSuffixToKey(t.key) }
+
+// NewRelationTag returns the tag for the relation with the given key.
+func NewRelationTag(relationKey string) RelationTag {
+	if !IsValidRelation(relationKey) {
+		panic(fmt.Sprintf("%q is not a valid relation key", relationKey))
+	}
+	// Replace both ":" with "." and the " " with "#".
+	relationKey = strings.Replace(relationKey, ":", ".", 2)
+	relationKey = strings.Replace(relationKey, " ", "#", 1)
+	return RelationTag{key: relationKey}
+}
+
+// ParseRelationTag parses a relation tag string.
+func ParseRelationTag(relationTag string) (RelationTag, error) {
+	tag, err := ParseTag(relationTag)
+	if err != nil {
+		return RelationTag{}, err
+	}
+	rt, ok := tag.(RelationTag)
+	if !ok {
+		return RelationTag{}, invalidTagError(relationTag, RelationTagKind)
+	}
+	return rt, nil
+}
+
+func relationTagSuffixToKey(s string) string {
+	// Replace both "." with ":" and the "#" with " ".
+	s = strings.Replace(s, ".", ":", 2)
+	return strings.Replace(s, "#", " ", 1)
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/space.go b/automation/vendor/gopkg.in/juju/names.v2/space.go
new file mode 100644
index 0000000..049e1c7
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/space.go
@@ -0,0 +1,50 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+)
+
+const (
+	SpaceTagKind = "space"
+	SpaceSnippet = "(?:[a-z0-9]+(?:-[a-z0-9]+)*)"
+)
+
+var validSpace = regexp.MustCompile("^" + SpaceSnippet + "$")
+
+// IsValidSpace reports whether name is a valid space name.
+func IsValidSpace(name string) bool {
+	return validSpace.MatchString(name)
+}
+
+type SpaceTag struct {
+	name string
+}
+
+func (t SpaceTag) String() string { return t.Kind() + "-" + t.Id() }
+func (t SpaceTag) Kind() string   { return SpaceTagKind }
+func (t SpaceTag) Id() string     { return t.name }
+
+// NewSpaceTag returns the tag of a space with the given name.
+func NewSpaceTag(name string) SpaceTag {
+	if !IsValidSpace(name) {
+		panic(fmt.Sprintf("%q is not a valid space name", name))
+	}
+	return SpaceTag{name: name}
+}
+
+// ParseSpaceTag parses a space tag string.
+func ParseSpaceTag(spaceTag string) (SpaceTag, error) {
+	tag, err := ParseTag(spaceTag)
+	if err != nil {
+		return SpaceTag{}, err
+	}
+	nt, ok := tag.(SpaceTag)
+	if !ok {
+		return SpaceTag{}, invalidTagError(spaceTag, SpaceTagKind)
+	}
+	return nt, nil
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/storage.go b/automation/vendor/gopkg.in/juju/names.v2/storage.go
new file mode 100644
index 0000000..2314f87
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/storage.go
@@ -0,0 +1,86 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+)
+
+const (
+	StorageTagKind = "storage"
+
+	// StorageNameSnippet is the regular expression that describes valid
+	// storage names (without the storage instance sequence number).
+	StorageNameSnippet = "(?:[a-z][a-z0-9]*(?:-[a-z0-9]*[a-z][a-z0-9]*)*)"
+)
+
+var validStorage = regexp.MustCompile("^(" + StorageNameSnippet + ")/" + NumberSnippet + "$")
+
+type StorageTag struct {
+	id string
+}
+
+func (t StorageTag) String() string { return t.Kind() + "-" + t.id }
+func (t StorageTag) Kind() string   { return StorageTagKind }
+func (t StorageTag) Id() string     { return storageTagSuffixToId(t.id) }
+
+// NewStorageTag returns the tag for the storage instance with the given ID.
+// It will panic if the given string is not a valid storage instance Id.
+func NewStorageTag(id string) StorageTag {
+	tag, ok := tagFromStorageId(id)
+	if !ok {
+		panic(fmt.Sprintf("%q is not a valid storage instance ID", id))
+	}
+	return tag
+}
+
+// ParseStorageTag parses a storage tag string.
+func ParseStorageTag(s string) (StorageTag, error) {
+	tag, err := ParseTag(s)
+	if err != nil {
+		return StorageTag{}, err
+	}
+	st, ok := tag.(StorageTag)
+	if !ok {
+		return StorageTag{}, invalidTagError(s, StorageTagKind)
+	}
+	return st, nil
+}
+
+// IsValidStorage returns whether id is a valid storage instance ID.
+func IsValidStorage(id string) bool {
+	return validStorage.MatchString(id)
+}
+
+// StorageName returns the storage name from a storage instance ID.
+// StorageName returns an error if "id" is not a valid storage
+// instance ID.
+func StorageName(id string) (string, error) {
+	s := validStorage.FindStringSubmatch(id)
+	if s == nil {
+		return "", fmt.Errorf("%q is not a valid storage instance ID", id)
+	}
+	return s[1], nil
+}
+
+func tagFromStorageId(id string) (StorageTag, bool) {
+	// replace only the last "/" with "-".
+	i := strings.LastIndex(id, "/")
+	if i <= 0 || !IsValidStorage(id) {
+		return StorageTag{}, false
+	}
+	id = id[:i] + "-" + id[i+1:]
+	return StorageTag{id}, true
+}
+
+func storageTagSuffixToId(s string) string {
+	// Replace only the last "-" with "/", as it is valid for storage
+	// names to contain hyphens.
+	if i := strings.LastIndex(s, "-"); i > 0 {
+		s = s[:i] + "/" + s[i+1:]
+	}
+	return s
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/subnet.go b/automation/vendor/gopkg.in/juju/names.v2/subnet.go
new file mode 100644
index 0000000..281eb0f
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/subnet.go
@@ -0,0 +1,49 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"net"
+)
+
+const SubnetTagKind = "subnet"
+
+// IsValidSubnet returns whether cidr is a valid subnet CIDR.
+func IsValidSubnet(cidr string) bool {
+	_, ipNet, err := net.ParseCIDR(cidr)
+	if err == nil && ipNet.String() == cidr {
+		return true
+	}
+	return false
+}
+
+type SubnetTag struct {
+	cidr string
+}
+
+func (t SubnetTag) String() string { return t.Kind() + "-" + t.cidr }
+func (t SubnetTag) Kind() string   { return SubnetTagKind }
+func (t SubnetTag) Id() string     { return t.cidr }
+
+// NewSubnetTag returns the tag for subnet with the given CIDR.
+func NewSubnetTag(cidr string) SubnetTag {
+	if !IsValidSubnet(cidr) {
+		panic(fmt.Sprintf("%s is not a valid subnet CIDR", cidr))
+	}
+	return SubnetTag{cidr: cidr}
+}
+
+// ParseSubnetTag parses a subnet tag string.
+func ParseSubnetTag(subnetTag string) (SubnetTag, error) {
+	tag, err := ParseTag(subnetTag)
+	if err != nil {
+		return SubnetTag{}, err
+	}
+	subt, ok := tag.(SubnetTag)
+	if !ok {
+		return SubnetTag{}, invalidTagError(subnetTag, SubnetTagKind)
+	}
+	return subt, nil
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/tag.go b/automation/vendor/gopkg.in/juju/names.v2/tag.go
new file mode 100644
index 0000000..9feae00
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/tag.go
@@ -0,0 +1,216 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/juju/errors"
+	"github.com/juju/utils"
+)
+
+// A Tag tags things that are taggable. Its purpose is to uniquely
+// identify some resource and provide a consistent representation of
+// that identity in both a human-readable and a machine-friendly format.
+// The latter benefits use of the tag in over-the-wire transmission
+// (e.g. in HTTP RPC calls) and in filename paths. The human-readable
+// tag "name" is available through the Id method. The machine-friendly
+// representation is provided by the String method.
+//
+// The ParseTag function may be used to build a tag from the machine-
+// formatted string. As well each kind of tag has its own Parse* method.
+// Each kind also has a New* method (e.g. NewMachineTag) which produces
+// a tag from the human-readable tag "ID".
+//
+// In the context of juju, the API *must* use tags to represent the
+// various juju entities. This contrasts with user-facing code, where
+// tags *must not* be used. Internal to juju the use of tags is a
+// judgement call based on the situation.
+type Tag interface {
+	// Kind returns the kind of the tag.
+	// This method is for legacy compatibility, callers should
+	// use equality or type assertions to verify the Kind, or type
+	// of a Tag.
+	Kind() string
+
+	// Id returns an identifier for this Tag.
+	// The contents and format of the identifier are specific
+	// to the implementer of the Tag.
+	Id() string
+
+	fmt.Stringer // all Tags should be able to print themselves
+}
+
+// tagString returns the canonical string representation of a tag.
+// It round-trips with splitTag().
+func tagString(tag Tag) string {
+	return tag.Kind() + "-" + tag.Id()
+}
+
+// TagKind returns one of the *TagKind constants for the given tag, or
+// an error if none matches.
+func TagKind(tag string) (string, error) {
+	i := strings.Index(tag, "-")
+	if i <= 0 || !validKinds(tag[:i]) {
+		return "", fmt.Errorf("%q is not a valid tag", tag)
+	}
+	return tag[:i], nil
+}
+
+func validKinds(kind string) bool {
+	switch kind {
+	case UnitTagKind, MachineTagKind, ApplicationTagKind, EnvironTagKind, UserTagKind,
+		RelationTagKind, ActionTagKind, VolumeTagKind, CharmTagKind, StorageTagKind,
+		FilesystemTagKind, IPAddressTagKind, SpaceTagKind, SubnetTagKind,
+		PayloadTagKind, ModelTagKind, ControllerTagKind, CloudTagKind, CloudCredentialTagKind:
+		return true
+	}
+	return false
+}
+
+func splitTag(tag string) (string, string, error) {
+	kind, err := TagKind(tag)
+	if err != nil {
+		return "", "", err
+	}
+	return kind, tag[len(kind)+1:], nil
+}
+
+// ParseTag parses a string representation into a Tag.
+func ParseTag(tag string) (Tag, error) {
+	kind, id, err := splitTag(tag)
+	if err != nil {
+		return nil, invalidTagError(tag, "")
+	}
+	switch kind {
+	case UnitTagKind:
+		id = unitTagSuffixToId(id)
+		if !IsValidUnit(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewUnitTag(id), nil
+	case MachineTagKind:
+		id = machineTagSuffixToId(id)
+		if !IsValidMachine(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewMachineTag(id), nil
+	case ApplicationTagKind:
+		if !IsValidApplication(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewApplicationTag(id), nil
+	case UserTagKind:
+		if !IsValidUser(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewUserTag(id), nil
+	case EnvironTagKind:
+		if !IsValidEnvironment(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewEnvironTag(id), nil
+	case ModelTagKind:
+		if !IsValidModel(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewModelTag(id), nil
+	case ControllerTagKind:
+		if !IsValidController(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewControllerTag(id), nil
+
+	case RelationTagKind:
+		id = relationTagSuffixToKey(id)
+		if !IsValidRelation(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewRelationTag(id), nil
+	case ActionTagKind:
+		if !IsValidAction(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewActionTag(id), nil
+	case VolumeTagKind:
+		id = volumeTagSuffixToId(id)
+		if !IsValidVolume(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewVolumeTag(id), nil
+	case CharmTagKind:
+		if !IsValidCharm(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewCharmTag(id), nil
+	case StorageTagKind:
+		id = storageTagSuffixToId(id)
+		if !IsValidStorage(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewStorageTag(id), nil
+	case FilesystemTagKind:
+		id = filesystemTagSuffixToId(id)
+		if !IsValidFilesystem(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewFilesystemTag(id), nil
+	case IPAddressTagKind:
+		uuid, err := utils.UUIDFromString(id)
+		if err != nil {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewIPAddressTag(uuid.String()), nil
+	case SubnetTagKind:
+		if !IsValidSubnet(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewSubnetTag(id), nil
+	case SpaceTagKind:
+		if !IsValidSpace(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewSpaceTag(id), nil
+	case PayloadTagKind:
+		if !isValidPayload(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewPayloadTag(id), nil
+	case CloudTagKind:
+		if !IsValidCloud(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewCloudTag(id), nil
+	case CloudCredentialTagKind:
+		id, err = cloudCredentialTagSuffixToId(id)
+		if err != nil {
+			return nil, errors.Wrap(err, invalidTagError(tag, kind))
+		}
+		if !IsValidCloudCredential(id) {
+			return nil, invalidTagError(tag, kind)
+		}
+		return NewCloudCredentialTag(id), nil
+	default:
+		return nil, invalidTagError(tag, "")
+	}
+}
+
+func invalidTagError(tag, kind string) error {
+	if kind != "" {
+		return fmt.Errorf("%q is not a valid %s tag", tag, kind)
+	}
+	return fmt.Errorf("%q is not a valid tag", tag)
+}
+
+// ReadableString returns a human-readable string from the tag passed in.
+// It currently supports unit and machine tags. Support for additional types
+// can be added in as needed.
+func ReadableString(tag Tag) string {
+	if tag == nil {
+		return ""
+	}
+
+	return tag.Kind() + " " + tag.Id()
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/unit.go b/automation/vendor/gopkg.in/juju/names.v2/unit.go
new file mode 100644
index 0000000..3f3a026
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/unit.go
@@ -0,0 +1,79 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+)
+
+const UnitTagKind = "unit"
+
+var validUnit = regexp.MustCompile("^(" + ApplicationSnippet + ")/" + NumberSnippet + "$")
+
+type UnitTag struct {
+	name string
+}
+
+func (t UnitTag) String() string { return t.Kind() + "-" + t.name }
+func (t UnitTag) Kind() string   { return UnitTagKind }
+func (t UnitTag) Id() string     { return unitTagSuffixToId(t.name) }
+
+// NewUnitTag returns the tag for the unit with the given name.
+// It will panic if the given unit name is not valid.
+func NewUnitTag(unitName string) UnitTag {
+	tag, ok := tagFromUnitName(unitName)
+	if !ok {
+		panic(fmt.Sprintf("%q is not a valid unit name", unitName))
+	}
+	return tag
+}
+
+// ParseUnitTag parses a unit tag string.
+func ParseUnitTag(unitTag string) (UnitTag, error) {
+	tag, err := ParseTag(unitTag)
+	if err != nil {
+		return UnitTag{}, err
+	}
+	ut, ok := tag.(UnitTag)
+	if !ok {
+		return UnitTag{}, invalidTagError(unitTag, UnitTagKind)
+	}
+	return ut, nil
+}
+
+// IsValidUnit returns whether name is a valid unit name.
+func IsValidUnit(name string) bool {
+	return validUnit.MatchString(name)
+}
+
+// UnitApplication returns the name of the application that the unit is
+// associated with. It returns an error if unitName is not a valid unit name.
+func UnitApplication(unitName string) (string, error) {
+	s := validUnit.FindStringSubmatch(unitName)
+	if s == nil {
+		return "", fmt.Errorf("%q is not a valid unit name", unitName)
+	}
+	return s[1], nil
+}
+
+func tagFromUnitName(unitName string) (UnitTag, bool) {
+	// Replace only the last "/" with "-".
+	i := strings.LastIndex(unitName, "/")
+	if i <= 0 || !IsValidUnit(unitName) {
+		return UnitTag{}, false
+	}
+	unitName = unitName[:i] + "-" + unitName[i+1:]
+	return UnitTag{name: unitName}, true
+}
+
+func unitTagSuffixToId(s string) string {
+	// Replace only the last "-" with "/", as it is valid for application
+	// names to contain hyphens.
+	if i := strings.LastIndex(s, "-"); i > 0 {
+		s = s[:i] + "/" + s[i+1:]
+	}
+	return s
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/user.go b/automation/vendor/gopkg.in/juju/names.v2/user.go
new file mode 100644
index 0000000..332ee27
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/user.go
@@ -0,0 +1,130 @@
+// Copyright 2013 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+)
+
+const (
+	UserTagKind     = "user"
+	LocalUserDomain = "local"
+)
+
+var (
+	// TODO this does not allow single character usernames or
+	// domains. Is that deliberate?
+	// https://github.com/juju/names/issues/54
+	validUserNameSnippet = "[a-zA-Z0-9][a-zA-Z0-9.+-]*[a-zA-Z0-9]"
+	validUserSnippet     = fmt.Sprintf("(?:%s(?:@%s)?)", validUserNameSnippet, validUserNameSnippet)
+	validName            = regexp.MustCompile(fmt.Sprintf("^(?P<name>%s)(?:@(?P<domain>%s))?$", validUserNameSnippet, validUserNameSnippet))
+	validUserName        = regexp.MustCompile("^" + validUserNameSnippet + "$")
+)
+
+// IsValidUser returns whether id is a valid user id.
+// Valid users may or may not be qualified with an
+// @domain suffix. Examples of valid users include
+// bob, bob@local, bob@somewhere-else, 0-a-f@123.
+func IsValidUser(id string) bool {
+	return validName.MatchString(id)
+}
+
+// IsValidUserName returns whether the given
+// name is a valid name part of a user. That is,
+// usernames with a domain suffix will return
+// false.
+func IsValidUserName(name string) bool {
+	return validUserName.MatchString(name)
+}
+
+// IsValidUserDomain returns whether the given user
+// domain is valid.
+func IsValidUserDomain(domain string) bool {
+	return validUserName.MatchString(domain)
+}
+
+// UserTag represents a user that may be stored locally
+// or associated with some external domain.
+type UserTag struct {
+	name   string
+	domain string
+}
+
+func (t UserTag) Kind() string   { return UserTagKind }
+func (t UserTag) String() string { return UserTagKind + "-" + t.Id() }
+
+// Id implements Tag.Id. Local users will always have
+// an Id value without any domain.
+func (t UserTag) Id() string {
+	if t.domain == "" || t.domain == LocalUserDomain {
+		return t.name
+	}
+	return t.name + "@" + t.domain
+}
+
+// Name returns the name part of the user name
+// without its associated domain.
+func (t UserTag) Name() string { return t.name }
+
+// IsLocal returns true if the tag represents a local user.
+// Users without an explicit domain are considered local.
+func (t UserTag) IsLocal() bool {
+	return t.Domain() == LocalUserDomain || t.Domain() == ""
+}
+
+// Domain returns the user domain. Users in the local database
+// are from the LocalDomain. Other users are considered 'remote' users.
+func (t UserTag) Domain() string {
+	return t.domain
+}
+
+// WithDomain returns a copy of the user tag with the
+// domain changed to the given argument.
+// The domain must satisfy IsValidUserDomain
+// or this function will panic.
+func (t UserTag) WithDomain(domain string) UserTag {
+	if !IsValidUserDomain(domain) {
+		panic(fmt.Sprintf("invalid user domain %q", domain))
+	}
+	return UserTag{
+		name:   t.name,
+		domain: domain,
+	}
+}
+
+// NewUserTag returns the tag for the user with the given name.
+// It panics if the user name does not satisfy IsValidUser.
+func NewUserTag(userName string) UserTag {
+	parts := validName.FindStringSubmatch(userName)
+	if len(parts) != 3 {
+		panic(fmt.Sprintf("invalid user tag %q", userName))
+	}
+	domain := parts[2]
+	if domain == LocalUserDomain {
+		domain = ""
+	}
+	return UserTag{name: parts[1], domain: domain}
+}
+
+// NewLocalUserTag returns the tag for a local user with the given name.
+func NewLocalUserTag(name string) UserTag {
+	if !IsValidUserName(name) {
+		panic(fmt.Sprintf("invalid user name %q", name))
+	}
+	return UserTag{name: name}
+}
+
+// ParseUserTag parses a user tag string.
+func ParseUserTag(tag string) (UserTag, error) {
+	t, err := ParseTag(tag)
+	if err != nil {
+		return UserTag{}, err
+	}
+	ut, ok := t.(UserTag)
+	if !ok {
+		return UserTag{}, invalidTagError(tag, UserTagKind)
+	}
+	return ut, nil
+}
diff --git a/automation/vendor/gopkg.in/juju/names.v2/volume.go b/automation/vendor/gopkg.in/juju/names.v2/volume.go
new file mode 100644
index 0000000..2c1fd1b
--- /dev/null
+++ b/automation/vendor/gopkg.in/juju/names.v2/volume.go
@@ -0,0 +1,76 @@
+// Copyright 2014 Canonical Ltd.
+// Licensed under the LGPLv3, see LICENCE file for details.
+
+package names
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+)
+
+const VolumeTagKind = "volume"
+
+// Volumes may be bound to a machine, meaning that the volume cannot
+// exist without that machine. We encode this in the tag to allow
+var validVolume = regexp.MustCompile("^(" + MachineSnippet + "/)?" + NumberSnippet + "$")
+
+type VolumeTag struct {
+	id string
+}
+
+func (t VolumeTag) String() string { return t.Kind() + "-" + t.id }
+func (t VolumeTag) Kind() string   { return VolumeTagKind }
+func (t VolumeTag) Id() string     { return volumeTagSuffixToId(t.id) }
+
+// NewVolumeTag returns the tag for the volume with the given ID.
+// It will panic if the given volume ID is not valid.
+func NewVolumeTag(id string) VolumeTag {
+	tag, ok := tagFromVolumeId(id)
+	if !ok {
+		panic(fmt.Sprintf("%q is not a valid volume ID", id))
+	}
+	return tag
+}
+
+// ParseVolumeTag parses a volume tag string.
+func ParseVolumeTag(volumeTag string) (VolumeTag, error) {
+	tag, err := ParseTag(volumeTag)
+	if err != nil {
+		return VolumeTag{}, err
+	}
+	dt, ok := tag.(VolumeTag)
+	if !ok {
+		return VolumeTag{}, invalidTagError(volumeTag, VolumeTagKind)
+	}
+	return dt, nil
+}
+
+// IsValidVolume returns whether id is a valid volume ID.
+func IsValidVolume(id string) bool {
+	return validVolume.MatchString(id)
+}
+
+// VolumeMachine returns the machine component of the volume
+// tag, and a boolean indicating whether or not there is a
+// machine component.
+func VolumeMachine(tag VolumeTag) (MachineTag, bool) {
+	id := tag.Id()
+	pos := strings.LastIndex(id, "/")
+	if pos == -1 {
+		return MachineTag{}, false
+	}
+	return NewMachineTag(id[:pos]), true
+}
+
+func tagFromVolumeId(id string) (VolumeTag, bool) {
+	if !IsValidVolume(id) {
+		return VolumeTag{}, false
+	}
+	id = strings.Replace(id, "/", "-", -1)
+	return VolumeTag{id}, true
+}
+
+func volumeTagSuffixToId(s string) string {
+	return strings.Replace(s, "-", "/", -1)
+}