pax_global_header 0000666 0000000 0000000 00000000064 13650476565 0014533 g ustar 00root root 0000000 0000000 52 comment=9a8294627524158f3e39a65fe4d751293659e5a9
names-4.0.0/ 0000775 0000000 0000000 00000000000 13650476565 0012637 5 ustar 00root root 0000000 0000000 names-4.0.0/.gitignore 0000664 0000000 0000000 00000000010 13650476565 0014616 0 ustar 00root root 0000000 0000000 vendor/
names-4.0.0/LICENSE 0000664 0000000 0000000 00000021501 13650476565 0013643 0 ustar 00root root 0000000 0000000 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.
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.
names-4.0.0/Makefile 0000664 0000000 0000000 00000000715 13650476565 0014302 0 ustar 00root root 0000000 0000000 #
# Makefile for juju/names
#
PROJECT := github.com/juju/names/v3
PROJECT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
PROJECT_PACKAGES := $(shell go list $(PROJECT)/... | grep -v /acceptancetests/)
TEST_TIMEOUT := 600s
default: build
build: go-build
# Reformat source files.
format:
gofmt -w -l .
go-build:
@go build $(PROJECT_PACKAGES)
test: build
go test $(CHECK_ARGS) -test.timeout=$(TEST_TIMEOUT) $(PROJECT_PACKAGES) -check.v
names-4.0.0/README.md 0000664 0000000 0000000 00000000127 13650476565 0014116 0 ustar 00root root 0000000 0000000 juju/names
============
This package provides helpers for handling Juju entity names.
names-4.0.0/action.go 0000664 0000000 0000000 00000004646 13650476565 0014455 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"fmt"
"regexp"
"github.com/juju/errors"
"github.com/juju/utils"
)
const ActionTagKind = "action"
// ActionSnippet defines the regexp for a valid Action Id.
// Actions are identified by a unique, incrementing number.
const ActionSnippet = NumberSnippet
var validActionV2 = regexp.MustCompile("^" + ActionSnippet + "$")
type ActionTag struct {
// Tags that are serialized need to have fields exported.
ID string
}
// NewActionTag returns the tag of an action with the given id (UUID).
func NewActionTag(id string) ActionTag {
// Actions v1 use a UUID for the id.
if uuid, err := utils.UUIDFromString(id); err == nil {
return ActionTag{ID: uuid.String()}
}
// Actions v2 use a number.
if !validActionV2.MatchString(id) {
panic(fmt.Sprintf("invalid action id %q", id))
}
return ActionTag{ID: id}
}
// 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 }
// IsValidAction returns whether id is a valid action id.
func IsValidAction(id string) bool {
// UUID is for actions v1
// N is for actions V2.
return utils.IsValidUUIDString(id) ||
validActionV2.MatchString(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)
}
names-4.0.0/action_test.go 0000664 0000000 0000000 00000004600 13650476565 0015502 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type actionSuite struct{}
var _ = gc.Suite(&actionSuite{})
var parseActionTagTests = []struct {
tag string
expected names.Tag
err error
}{
{tag: "", err: names.InvalidTagError("", "")},
{tag: "action-f47ac10b-58cc-4372-a567-0e02b2c3d479", expected: names.NewActionTag("f47ac10b-58cc-4372-a567-0e02b2c3d479")},
{tag: "action-1", expected: names.NewActionTag("1")},
{tag: "action-foo", err: names.InvalidTagError("action-foo", "action")},
{tag: "bob", err: names.InvalidTagError("bob", "")},
{tag: "application-ned", err: names.InvalidTagError("application-ned", names.ActionTagKind)}}
func (s *actionSuite) TestParseActionTag(c *gc.C) {
for i, t := range parseActionTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseActionTag(t.tag)
if t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(err, jc.ErrorIsNil)
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
func (s *actionSuite) TestActionReceiverTag(c *gc.C) {
testCases := []struct {
name string
expected names.Tag
err string
}{
{name: "mysql", err: `invalid actionreceiver name "mysql"`},
{name: "mysql/3", expected: names.NewUnitTag("mysql/3")},
{name: "3", expected: names.NewMachineTag("3")},
}
for _, tcase := range testCases {
tag, err := names.ActionReceiverTag(tcase.name)
if tcase.err != "" {
c.Check(err, gc.ErrorMatches, tcase.err)
c.Check(tag, gc.IsNil)
continue
}
c.Check(err, jc.ErrorIsNil)
c.Check(tag, gc.FitsTypeOf, tcase.expected)
c.Check(tag, gc.Equals, tcase.expected)
}
}
func (s *actionSuite) TestActionReceiverFromTag(c *gc.C) {
for i, test := range []struct {
name string
expected names.Tag
err string
}{
{name: "rambleon", err: `invalid actionreceiver tag "rambleon"`},
{name: "unit-mysql-2", expected: names.NewUnitTag("mysql/2")},
{name: "machine-13", expected: names.NewMachineTag("13")},
} {
c.Logf("test %d", i)
tag, err := names.ActionReceiverFromTag(test.name)
if test.err != "" {
c.Check(err, gc.ErrorMatches, test.err)
c.Check(tag, gc.IsNil)
continue
}
c.Check(tag, gc.Equals, test.expected)
c.Check(err, jc.ErrorIsNil)
}
}
names-4.0.0/application.go 0000664 0000000 0000000 00000002470 13650476565 0015474 0 ustar 00root root 0000000 0000000 // 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
}
names-4.0.0/application_test.go 0000664 0000000 0000000 00000004670 13650476565 0016537 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type applicationSuite struct{}
var _ = gc.Suite(&applicationSuite{})
var applicationNameTests = []struct {
pattern string
valid bool
}{
{pattern: "", valid: false},
{pattern: "wordpress", valid: true},
{pattern: "foo42", valid: true},
{pattern: "doing55in54", valid: true},
{pattern: "%not", valid: false},
{pattern: "42also-not", valid: false},
{pattern: "but-this-works", valid: true},
{pattern: "so-42-far-not-good", valid: false},
{pattern: "foo/42", valid: false},
{pattern: "is-it-", valid: false},
{pattern: "broken2-", valid: false},
{pattern: "foo2", valid: true},
{pattern: "foo-2", valid: false},
}
func (s *applicationSuite) TestApplicationNameFormats(c *gc.C) {
assertApplication := func(s string, expect bool) {
c.Assert(names.IsValidApplication(s), gc.Equals, expect)
// Check that anything that is considered a valid application name
// is also (in)valid if a(n) (in)valid unit designator is added
// to it.
c.Assert(names.IsValidUnit(s+"/0"), gc.Equals, expect)
c.Assert(names.IsValidUnit(s+"/99"), gc.Equals, expect)
c.Assert(names.IsValidUnit(s+"/-1"), gc.Equals, false)
c.Assert(names.IsValidUnit(s+"/blah"), gc.Equals, false)
c.Assert(names.IsValidUnit(s+"/"), gc.Equals, false)
}
for i, test := range applicationNameTests {
c.Logf("test %d: %q", i, test.pattern)
assertApplication(test.pattern, test.valid)
}
}
var parseApplicationTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "application-dave",
expected: names.NewApplicationTag("dave"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "application-dave/0",
err: names.InvalidTagError("application-dave/0", names.ApplicationTagKind),
}, {
tag: "application",
err: names.InvalidTagError("application", ""),
}, {
tag: "user-dave",
err: names.InvalidTagError("user-dave", names.ApplicationTagKind),
}}
func (s *applicationSuite) TestParseApplicationTag(c *gc.C) {
for i, t := range parseApplicationTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseApplicationTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/applicationoffer.go 0000664 0000000 0000000 00000002645 13650476565 0016522 0 ustar 00root root 0000000 0000000 // Copyright 2017 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"regexp"
)
const ApplicationOfferTagKind = "applicationoffer"
const (
ApplicationOfferSnippet = "(?:[a-z][a-z0-9]*(?:-[a-z0-9]*[a-z][a-z0-9]*)*)"
)
var validApplicationOffer = regexp.MustCompile("^" + ApplicationOfferSnippet + "$")
// IsValidApplicationOffer returns whether name is a valid application offer name.
func IsValidApplicationOffer(name string) bool {
return validApplicationOffer.MatchString(name)
}
type ApplicationOfferTag struct {
Name string
}
func (t ApplicationOfferTag) String() string { return t.Kind() + "-" + t.Id() }
func (t ApplicationOfferTag) Kind() string { return ApplicationOfferTagKind }
func (t ApplicationOfferTag) Id() string { return t.Name }
// NewApplicationOfferTag returns the tag for the application with the given name.
func NewApplicationOfferTag(applicationOfferName string) ApplicationOfferTag {
return ApplicationOfferTag{Name: applicationOfferName}
}
// ParseApplicationOfferTag parses a application tag string.
func ParseApplicationOfferTag(applicationOfferTag string) (ApplicationOfferTag, error) {
tag, err := ParseTag(applicationOfferTag)
if err != nil {
return ApplicationOfferTag{}, err
}
st, ok := tag.(ApplicationOfferTag)
if !ok {
return ApplicationOfferTag{}, invalidTagError(applicationOfferTag, ApplicationOfferTagKind)
}
return st, nil
}
names-4.0.0/applicationoffer_test.go 0000664 0000000 0000000 00000002345 13650476565 0017556 0 ustar 00root root 0000000 0000000 // Copyright 2017 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type applicationOfferSuite struct{}
var _ = gc.Suite(&applicationOfferSuite{})
var parseApplicationOfferTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "applicationoffer-dave",
expected: names.NewApplicationOfferTag("dave"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "applicationoffer-dave/0",
err: names.InvalidTagError("applicationoffer-dave/0", names.ApplicationOfferTagKind),
}, {
tag: "applicationoffer",
err: names.InvalidTagError("applicationoffer", ""),
}, {
tag: "user-dave",
err: names.InvalidTagError("user-dave", names.ApplicationOfferTagKind),
}}
func (s *applicationOfferSuite) TestParseApplicationOfferTag(c *gc.C) {
for i, t := range parseApplicationOfferTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseApplicationOfferTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/caasmodel.go 0000664 0000000 0000000 00000002400 13650476565 0015112 0 ustar 00root root 0000000 0000000 // Copyright 2017 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
const CAASModelTagKind = "caasmodel"
// CAASModelTag represents a tag used to describe a model.
type CAASModelTag struct {
uuid string
}
// NewCAASModelTag returns the tag of a CAAS model with the given CAAS model UUID.
func NewCAASModelTag(uuid string) CAASModelTag {
return CAASModelTag{uuid: uuid}
}
// ParseCAASModelTag parses a CAAS model tag string.
func ParseCAASModelTag(caasModelTag string) (CAASModelTag, error) {
tag, err := ParseTag(caasModelTag)
if err != nil {
return CAASModelTag{}, err
}
cmt, ok := tag.(CAASModelTag)
if !ok {
return CAASModelTag{}, invalidTagError(caasModelTag, CAASModelTagKind)
}
return cmt, nil
}
func (t CAASModelTag) String() string { return t.Kind() + "-" + t.Id() }
func (t CAASModelTag) Kind() string { return CAASModelTagKind }
func (t CAASModelTag) Id() string { return t.uuid }
// IsValidCAASModel returns whether id is a valid CAAS model UUID.
func IsValidCAASModel(id string) bool {
return validUUID.MatchString(id)
}
// IsValidCAASModelName returns whether name is a valid string safe for a CAAS model name.
func IsValidCAASModelName(name string) bool {
return validModelName.MatchString(name)
}
names-4.0.0/caasmodel_test.go 0000664 0000000 0000000 00000003371 13650476565 0016161 0 ustar 00root root 0000000 0000000 // Copyright 2017 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type caasModelSuite struct{}
var _ = gc.Suite(&caasModelSuite{})
var parseCAASModelTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "caasmodel-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expected: names.NewCAASModelTag("f47ac10b-58cc-4372-a567-0e02b2c3d479"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "model-f47ac10b-58cc-4372-a567-0e02b2c3d479",
err: names.InvalidTagError("model-f47ac10b-58cc-4372-a567-0e02b2c3d479", names.CAASModelTagKind),
}, {
tag: "application-dave",
err: names.InvalidTagError("application-dave", names.CAASModelTagKind),
}}
func (s *caasModelSuite) TestParseCAASModelTag(c *gc.C) {
for i, t := range parseCAASModelTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseCAASModelTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
var caasModelNameTest = []struct {
test string
name string
expected bool
}{{
test: "Hyphenated true",
name: "foo-bar",
expected: true,
}, {
test: "Whitespace false",
name: "foo bar",
expected: false,
}, {
test: "Capital false",
name: "fooBar",
expected: false,
}, {
test: "At sign false",
name: "foo@bar",
expected: false,
}}
func (s *modelSuite) TestCAASModelName(c *gc.C) {
for i, t := range caasModelNameTest {
c.Logf("test %d: %q", i, t.name)
c.Check(names.IsValidModelName(t.name), gc.Equals, t.expected)
}
}
names-4.0.0/charm.go 0000664 0000000 0000000 00000005750 13650476565 0014267 0 ustar 00root root 0000000 0000000 // 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)
}
names-4.0.0/charm_test.go 0000664 0000000 0000000 00000006020 13650476565 0015315 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"fmt"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type charmSuite struct{}
var _ = gc.Suite(&charmSuite{})
var validCharmURLs = []string{"charm",
// V1 charm urls.
"local:charm",
"local:charm--1",
"local:charm-1",
"local:series/charm",
"local:series/charm-3",
"local:series/charm-0",
"cs:~user/charm",
"cs:~user/charm-1",
"cs:~user/series/charm",
"cs:~user/series/charm-1",
"cs:series/charm",
"cs:series/charm-with-long-name",
"cs:series/charm-3",
"cs:series/charm-0",
"cs:charm",
"cs:charm--1",
"cs:charm-1",
"charm",
"charm-1",
"series/charm",
"series/charm-1",
// V3 charm urls.
"local:charm-with-long2-name/series/2",
"local:charm-with-long2-name/series",
"local:charm-with-long2-name/2",
"cs:user/charm-with-long-name/series/2",
"cs:charm-with-long2-name/series/2",
"cs:user/charm-with-long-name/2",
"cs:user/charm-with-long-name/series",
"cs:charm-with-long-name/2",
"cs:charm-with-long-name/series",
"cs:user/charm-with-long-name",
"user/charm-with-long-name/series/2",
"charm-with-long2-name/series/2",
"user/charm-with-long-name/2",
"user/charm-with-long-name/series",
"charm-with-long-name/2",
"charm-with-long-name/series",
"user/charm-with-long-name",
}
func (s *charmSuite) TestValidCharmURLs(c *gc.C) {
for _, url := range validCharmURLs {
c.Logf("Processing tag %q", url)
c.Assert(names.IsValidCharm(url), jc.IsTrue)
}
}
func (s *charmSuite) TestInvalidCharmURLs(c *gc.C) {
invalidURLs := []string{"",
"local:~user/charm", // false: user on local
"local:~user/series/charm", // false: user on local
"local:~user/series/charm-1", // false: user on local
"local:charm--2", // false: only -1 is a valid negative revision
"blah:charm-2", // false: invalid schema
"local:series/charm-01", // false: revision is funny
"local:user/name/series/2", // false: local charms can't have users
}
for _, url := range invalidURLs {
c.Logf("Processing tag %q", url)
c.Assert(names.IsValidCharm(url), jc.IsFalse)
}
}
func (s *charmSuite) TestParseCharmTagValid(c *gc.C) {
for _, tag := range validCharmURLs {
c.Logf("Processing tag %q", tag)
s.assertParseCharmTagValid(c, fmt.Sprintf("charm-%v", tag), names.NewCharmTag(tag))
}
}
func (s *charmSuite) TestParseCharmTagInvalid(c *gc.C) {
invalidTags := []string{"",
"blah",
"charm",
"user-blah",
}
for _, aTag := range invalidTags {
c.Logf("Processing tag %q", aTag)
s.assertParseCharmTagInvalid(c, aTag)
}
}
func (s *charmSuite) assertParseCharmTagValid(c *gc.C, tag string, expected names.Tag) {
got, err := names.ParseCharmTag(tag)
c.Assert(err, jc.ErrorIsNil)
c.Check(got, gc.FitsTypeOf, expected)
c.Check(got, gc.Equals, expected)
}
func (s *charmSuite) assertParseCharmTagInvalid(c *gc.C, tag string) {
_, err := names.ParseCharmTag(tag)
c.Check(err, gc.ErrorMatches, fmt.Sprintf(".*%q is not a valid.*", tag))
}
names-4.0.0/cloud.go 0000664 0000000 0000000 00000002237 13650476565 0014300 0 ustar 00root root 0000000 0000000 // 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)
}
names-4.0.0/cloud_test.go 0000664 0000000 0000000 00000004300 13650476565 0015330 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type cloudSuite struct{}
var _ = gc.Suite(&cloudSuite{})
func (s *cloudSuite) TestCloudTag(c *gc.C) {
for i, t := range []struct {
input string
string string
}{
{
input: "bob",
string: "cloud-bob",
},
} {
c.Logf("test %d: %s", i, t.input)
cloudTag := names.NewCloudTag(t.input)
c.Check(cloudTag.String(), gc.Equals, t.string)
c.Check(cloudTag.Id(), gc.Equals, t.input)
}
}
func (s *cloudSuite) TestIsValidCloud(c *gc.C) {
for i, t := range []struct {
string string
expect bool
}{
{"", false},
{"bob", true},
{"Bob", true},
{"bOB", true},
{"b^b", false},
{"bob1", true},
{"bob-1", true},
{"bob.1", true},
{"1bob", true},
{"1-bob", true},
{"1+bob", false},
{"1.bob", true},
{"a", true},
{"0foo", true},
{"foo bar", false},
{"bar{}", false},
{"bar+foo", false},
{"bar_foo", true},
{"bar_", true},
{"bar!", false},
{"bar^", false},
{"bar*", false},
{"foo=bar", false},
{"foo?", false},
{"[bar]", false},
{"'foo'", false},
{"%bar", false},
{"&bar", false},
{"#1foo", false},
{"bar@", false},
{"@local", false},
{"not/valid", false},
} {
c.Logf("test %d: %s", i, t.string)
c.Assert(names.IsValidCloud(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
}
}
func (s *cloudSuite) TestParseCloudTag(c *gc.C) {
for i, t := range []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "cloud-aws",
expected: names.NewCloudTag("aws"),
}, {
tag: "aws",
err: names.InvalidTagError("aws", ""),
}, {
tag: "unit-aws",
err: names.InvalidTagError("unit-aws", names.UnitTagKind), // not a valid unit name either
}, {
tag: "application-aws",
err: names.InvalidTagError("application-aws", names.CloudTagKind),
}} {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseCloudTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/cloudcredential.go 0000664 0000000 0000000 00000006213 13650476565 0016331 0 ustar 00root root 0000000 0000000 // 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)
}
names-4.0.0/cloudcredential_test.go 0000664 0000000 0000000 00000010776 13650476565 0017401 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type cloudCredentialSuite struct{}
var _ = gc.Suite(&cloudCredentialSuite{})
func (s *cloudCredentialSuite) TestCloudCredentialTag(c *gc.C) {
for i, t := range []struct {
input string
string string
cloud names.CloudTag
owner names.UserTag
name string
}{
{
input: "aws/bob/foo",
string: "cloudcred-aws_bob_foo",
cloud: names.NewCloudTag("aws"),
owner: names.NewUserTag("bob"),
name: "foo",
}, {
input: "manual_cloud/bob/foo",
string: "cloudcred-manual%5fcloud_bob_foo",
cloud: names.NewCloudTag("manual_cloud"),
owner: names.NewUserTag("bob"),
name: "foo",
}, {
input: "aws/bob@remote/foo",
string: "cloudcred-aws_bob@remote_foo",
cloud: names.NewCloudTag("aws"),
owner: names.NewUserTag("bob@remote"),
name: "foo",
}, {
input: "aws/bob@remote/foo@somewhere.com",
string: "cloudcred-aws_bob@remote_foo@somewhere.com",
cloud: names.NewCloudTag("aws"),
owner: names.NewUserTag("bob@remote"),
name: "foo@somewhere.com",
}, {
input: "aws/bob@remote/foo_bar",
string: `cloudcred-aws_bob@remote_foo%5fbar`,
cloud: names.NewCloudTag("aws"),
owner: names.NewUserTag("bob@remote"),
name: "foo_bar",
}, {
input: "google/bob+bob@remote/foo_bar",
string: `cloudcred-google_bob+bob@remote_foo%5fbar`,
cloud: names.NewCloudTag("google"),
owner: names.NewUserTag("bob+bob@remote"),
name: "foo_bar",
},
} {
c.Logf("test %d: %s", i, t.input)
cloudTag := names.NewCloudCredentialTag(t.input)
c.Check(cloudTag.String(), gc.Equals, t.string)
c.Check(cloudTag.Id(), gc.Equals, t.input)
c.Check(cloudTag.Cloud(), gc.Equals, t.cloud)
c.Check(cloudTag.Owner(), gc.Equals, t.owner)
c.Check(cloudTag.Name(), gc.Equals, t.name)
}
}
func (s *cloudCredentialSuite) TestIsValidCloudCredential(c *gc.C) {
for i, t := range []struct {
string string
expect bool
}{
{"", false},
{"aws/bob/foo", true},
{"manual_cloud/bob/foo", true},
{"aws/bob@local/foo", true},
{"google/bob+bob@local/foo", true},
{"/bob/foo", false},
{"aws//foo", false},
{"aws/bob/", false},
} {
c.Logf("test %d: %s", i, t.string)
c.Assert(names.IsValidCloudCredential(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
}
}
func (s *cloudCredentialSuite) TestIsValidCloudCredentialName(c *gc.C) {
for i, t := range []struct {
string string
expect bool
}{
{"", false},
{"foo", true},
{"f00b4r", true},
{"foo-bar", true},
{"foo@bar", true},
{"foo+foo@bar", true},
{"foo_bar", true},
{"123", false},
{"0foo", false},
} {
c.Logf("test %d: %s", i, t.string)
c.Check(names.IsValidCloudCredentialName(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
}
}
func (s *cloudCredentialSuite) TestParseCloudCredentialTag(c *gc.C) {
for i, t := range []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "cloudcred-aws_bob_foo",
expected: names.NewCloudCredentialTag("aws/bob/foo"),
}, {
tag: "cloudcred-manual%5fcloud_bob_foo",
expected: names.NewCloudCredentialTag("manual_cloud/bob/foo"),
}, {
tag: "cloudcred-aws-china_bob_foo-manchu",
expected: names.NewCloudCredentialTag("aws-china/bob/foo-manchu"),
}, {
tag: "cloudcred-aws-china_bob_foo@somewhere.com",
expected: names.NewCloudCredentialTag("aws-china/bob/foo@somewhere.com"),
}, {
tag: `cloudcred-aws-china_bob_foo%5fbar`,
expected: names.NewCloudCredentialTag("aws-china/bob/foo_bar"),
}, {
tag: "foo",
err: names.InvalidTagError("foo", ""),
}, {
tag: "unit-aws",
err: names.InvalidTagError("unit-aws", names.UnitTagKind), // not a valid unit name either
}} {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseCloudCredentialTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
func (s *cloudCredentialSuite) TestIsZero(c *gc.C) {
c.Assert(names.CloudCredentialTag{}.IsZero(), gc.Equals, true)
c.Assert(names.NewCloudCredentialTag("aws/bob/foo").IsZero(), gc.Equals, false)
}
func (s *cloudCredentialSuite) TestZeroString(c *gc.C) {
c.Assert(names.CloudCredentialTag{}.String(), gc.Equals, "")
}
func (s *cloudCredentialSuite) TestZeroId(c *gc.C) {
c.Assert(names.CloudCredentialTag{}.Id(), gc.Equals, "")
}
names-4.0.0/controller.go 0000664 0000000 0000000 00000003075 13650476565 0015356 0 ustar 00root root 0000000 0000000 // 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)
}
names-4.0.0/controller_test.go 0000664 0000000 0000000 00000004563 13650476565 0016420 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type controllerSuite struct{}
var _ = gc.Suite(&controllerSuite{})
var parseControllerTagTests = []struct {
title string
tag string
expected names.Tag
err error
}{{
title: "empty tag fails",
tag: "",
err: names.InvalidTagError("", ""),
}, {
title: "valid controller- tag",
tag: "controller-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expected: names.NewControllerTag("f47ac10b-58cc-4372-a567-0e02b2c3d479"),
}, {
title: "invalid controller tag one word",
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
title: "invalid controller tag prefix only",
tag: "controller-",
err: names.InvalidTagError("controller-", names.ControllerTagKind),
}, {
title: "invalid controller tag hyphen separated words",
tag: "application-dave",
err: names.InvalidTagError("application-dave", names.ControllerTagKind),
}, {
title: "invalid controller tag non hyphen separated prefix",
tag: "controllerf47ac10b-58cc-4372-a567-0e02b2c3d479",
err: names.InvalidTagError("controllerf47ac10b-58cc-4372-a567-0e02b2c3d479", ""),
}, {
title: "invalid controller tag non hyphen separated terms",
tag: "controllerf47ac10b58cc4372a5670e02b2c3d479",
err: names.InvalidTagError("controllerf47ac10b58cc4372a5670e02b2c3d479", ""),
}}
func (s *controllerSuite) TestParseControllerTag(c *gc.C) {
for i, t := range parseControllerTagTests {
c.Logf("test %d: %q %s", i, t.title, t.tag)
got, err := names.ParseControllerTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
var controllerNameTest = []struct {
test string
name string
expected bool
}{{
test: "Hyphenated true",
name: "foo-bar",
expected: true,
}, {
test: "Whitespsce false",
name: "foo bar",
expected: false,
}, {
test: "Capital false",
name: "fooBar",
expected: false,
}, {
test: "At sign false",
name: "foo@bar",
expected: false,
}}
func (s *controllerSuite) TestControllerName(c *gc.C) {
for i, t := range controllerNameTest {
c.Logf("test %d: %q", i, t.name)
c.Check(names.IsValidControllerName(t.name), gc.Equals, t.expected)
}
}
names-4.0.0/controlleragent.go 0000664 0000000 0000000 00000003335 13650476565 0016374 0 ustar 00root root 0000000 0000000 // Copyright 2019 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"fmt"
"regexp"
"strconv"
)
// ControllerAgentTagKind indicates that a tag belongs to a controller agent.
const ControllerAgentTagKind = "controller"
var validControllerAgentId = regexp.MustCompile("^" + NumberSnippet + "$")
// ControllerAgentTag represents a tag used to describe a controller.
type ControllerAgentTag struct {
id string
}
// NewControllerAgentTag returns the tag of an controller agent with the given id.
func NewControllerAgentTag(id string) ControllerAgentTag {
_, err := strconv.Atoi(id)
if err != nil {
panic(fmt.Sprintf("%q is not a valid controller agent id", id))
}
return ControllerAgentTag{id: id}
}
// ParseControllerAgentTag parses a controller agent tag string.
func ParseControllerAgentTag(controllerAgentTag string) (ControllerAgentTag, error) {
tag, err := ParseTag(controllerAgentTag)
if err != nil {
return ControllerAgentTag{}, err
}
et, ok := tag.(ControllerAgentTag)
if !ok {
return ControllerAgentTag{}, invalidTagError(controllerAgentTag, ControllerAgentTagKind)
}
return et, nil
}
// Number returns the controller agent number.
func (t ControllerAgentTag) Number() int {
n, _ := strconv.Atoi(t.Id())
return n
}
// String implements Tag.
func (t ControllerAgentTag) String() string { return t.Kind() + "-" + t.Id() }
// Kind implements Tag.
func (t ControllerAgentTag) Kind() string { return ControllerAgentTagKind }
// Id implements Tag.
func (t ControllerAgentTag) Id() string { return t.id }
// IsValidControllerAgent returns whether id is a valid controller agent id.
func IsValidControllerAgent(id string) bool {
return validControllerAgentId.MatchString(id)
}
names-4.0.0/controlleragent_test.go 0000664 0000000 0000000 00000003205 13650476565 0017427 0 ustar 00root root 0000000 0000000 // Copyright 2019 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type ControllerAgentSuite struct{}
var _ = gc.Suite(&ControllerAgentSuite{})
func (s *ControllerAgentSuite) TestControllerAgentTag(c *gc.C) {
c.Assert(names.NewControllerAgentTag("123").String(), gc.Equals, "controller-123")
}
func (s *ControllerAgentSuite) TestIdFormats(c *gc.C) {
c.Assert(names.IsValidControllerAgent("123"), jc.IsTrue)
c.Assert(names.IsValidControllerAgent("-123"), jc.IsFalse)
c.Assert(names.IsValidControllerAgent("invalid"), jc.IsFalse)
}
func (s *ControllerAgentSuite) TestNumber(c *gc.C) {
ca := names.ControllerAgentTag{}
c.Assert(ca.Number(), gc.Equals, 0)
ca = names.NewControllerAgentTag("5")
c.Assert(ca.Number(), gc.Equals, 5)
}
var parseControllerAgentTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "controller-0",
expected: names.NewControllerAgentTag("0"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "controller-dave",
err: names.InvalidTagError("controller-dave", names.ControllerAgentTagKind),
}}
func (s *ControllerAgentSuite) TestParseControllerAgentTag(c *gc.C) {
for i, t := range parseControllerAgentTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseControllerAgentTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/environ.go 0000664 0000000 0000000 00000002054 13650476565 0014647 0 ustar 00root root 0000000 0000000 // 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)
}
names-4.0.0/environ_test.go 0000664 0000000 0000000 00000002205 13650476565 0015704 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type environSuite struct{}
var _ = gc.Suite(&environSuite{})
var parseEnvironTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "environment-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expected: names.NewEnvironTag("f47ac10b-58cc-4372-a567-0e02b2c3d479"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
//}, {
// TODO(dfc) passes, but should not
// tag: "environment-",
// err: names.InvalidTagError("environment", ""),
}, {
tag: "application-dave",
err: names.InvalidTagError("application-dave", names.EnvironTagKind),
}}
func (s *environSuite) TestParseEnvironTag(c *gc.C) {
for i, t := range parseEnvironTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseEnvironTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/equality_test.go 0000664 0000000 0000000 00000002312 13650476565 0016060 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
gc "gopkg.in/check.v1"
)
var tagEqualityTests = []struct {
expected Tag
want Tag
}{
{NewMachineTag("0"), MachineTag{id: "0"}},
{NewMachineTag("10/lxc/1"), MachineTag{id: "10-lxc-1"}},
{NewUnitTag("mysql/1"), UnitTag{name: "mysql-1"}},
{NewApplicationTag("ceph"), ApplicationTag{Name: "ceph"}},
{NewRelationTag("wordpress:haproxy"), RelationTag{key: "wordpress.haproxy"}},
{NewEnvironTag("deadbeef-0123-4567-89ab-feedfacebeef"), EnvironTag{uuid: "deadbeef-0123-4567-89ab-feedfacebeef"}},
{NewUserTag("admin"), UserTag{name: "admin"}},
{NewUserTag("admin@local"), UserTag{name: "admin", domain: ""}},
{NewUserTag("admin@foobar"), UserTag{name: "admin", domain: "foobar"}},
{NewActionTag("01234567-aaaa-4bbb-8ccc-012345678901"), ActionTag{ID: "01234567-aaaa-4bbb-8ccc-012345678901"}},
{NewActionTag("1"), ActionTag{ID: "1"}},
{NewControllerAgentTag("1"), ControllerAgentTag{id: "1"}},
}
type equalitySuite struct{}
var _ = gc.Suite(&equalitySuite{})
func (s *equalitySuite) TestTagEquality(c *gc.C) {
for _, tt := range tagEqualityTests {
c.Check(tt.want, gc.Equals, tt.expected)
}
}
names-4.0.0/example_test.go 0000664 0000000 0000000 00000000552 13650476565 0015662 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import "fmt"
func ExampleParseTag() {
tag, err := ParseTag("user-100")
if err != nil {
panic(err)
}
switch tag := tag.(type) {
case UserTag:
fmt.Printf("User tag, id: %s\n", tag.Id())
default:
fmt.Printf("Unknown tag, type %T\n", tag)
}
}
names-4.0.0/export_test.go 0000664 0000000 0000000 00000000223 13650476565 0015543 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
var InvalidTagError = invalidTagError
names-4.0.0/filesystem.go 0000664 0000000 0000000 00000005662 13650476565 0015363 0 ustar 00root root 0000000 0000000 // 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 or unit, meaning that the filesystem cannot
// exist without that machine or unit. We encode this in the tag.
var validFilesystem = regexp.MustCompile("^((" + MachineSnippet + "|" + UnitSnippet + ")/)?" + 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 filesystemOrVolumeTagSuffixToId(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
}
id = id[:pos]
if !IsValidMachine(id) {
return MachineTag{}, false
}
return NewMachineTag(id), true
}
// FilesystemUnit returns the unit component of the filesystem
// tag, and a boolean indicating whether or not there is a
// unit component.
func FilesystemUnit(tag FilesystemTag) (UnitTag, bool) {
id := tag.Id()
pos := strings.LastIndex(id, "/")
if pos == -1 {
return UnitTag{}, false
}
id = id[:pos]
if !IsValidUnit(id) {
return UnitTag{}, false
}
return NewUnitTag(id[:pos]), true
}
func tagFromFilesystemId(id string) (FilesystemTag, bool) {
if !IsValidFilesystem(id) {
return FilesystemTag{}, false
}
id = strings.Replace(id, "/", "-", -1)
return FilesystemTag{id}, true
}
var validMachineSuffix = regexp.MustCompile("^(" + MachineSnippet + "-).*")
func filesystemOrVolumeTagSuffixToId(s string) string {
if validMachineSuffix.MatchString(s) {
return strings.Replace(s, "-", "/", -1)
}
// Replace only the last 2 "-" with "/", as it is valid for unit
// names to contain hyphens
for x := 0; x < 2; x++ {
if i := strings.LastIndex(s, "-"); i > 0 {
s = s[:i] + "/" + s[i+1:]
}
}
return s
}
names-4.0.0/filesystem_test.go 0000664 0000000 0000000 00000007272 13650476565 0016421 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"fmt"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type filesystemSuite struct{}
var _ = gc.Suite(&filesystemSuite{})
func (s *filesystemSuite) TestFilesystemTag(c *gc.C) {
c.Assert(names.NewFilesystemTag("1").String(), gc.Equals, "filesystem-1")
c.Assert(names.NewFilesystemTag("0/lxc/0/0").String(), gc.Equals, "filesystem-0-lxc-0-0")
c.Assert(names.NewFilesystemTag("1/0").String(), gc.Equals, "filesystem-1-0")
c.Assert(names.NewFilesystemTag("some-unit/0/0").String(), gc.Equals, "filesystem-some-unit-0-0")
}
func (s *filesystemSuite) TestFilesystemIdValidity(c *gc.C) {
assertFilesystemIdValid(c, "0")
assertFilesystemIdValid(c, "0/0")
assertFilesystemIdValid(c, "0/lxc/0/0")
assertFilesystemIdValid(c, "1000")
assertFilesystemIdValid(c, "some-unit/0/0")
assertFilesystemIdValid(c, "some-unit/0/1000")
assertFilesystemIdInvalid(c, "-1")
assertFilesystemIdInvalid(c, "")
assertFilesystemIdInvalid(c, "one")
assertFilesystemIdInvalid(c, "#")
assertFilesystemIdInvalid(c, "0/0/0") // 0/0 is not a valid machine or unit ID
}
func (s *filesystemSuite) TestParseFilesystemTag(c *gc.C) {
assertParseFilesystemTag(c, "filesystem-0", names.NewFilesystemTag("0"))
assertParseFilesystemTag(c, "filesystem-0-0", names.NewFilesystemTag("0/0"))
assertParseFilesystemTag(c, "filesystem-88", names.NewFilesystemTag("88"))
assertParseFilesystemTag(c, "filesystem-0-lxc-0-88", names.NewFilesystemTag("0/lxc/0/88"))
assertParseFilesystemTag(c, "filesystem-some-unit-0-88", names.NewFilesystemTag("some-unit/0/88"))
assertParseFilesystemTagInvalid(c, "", names.InvalidTagError("", ""))
assertParseFilesystemTagInvalid(c, "one", names.InvalidTagError("one", ""))
assertParseFilesystemTagInvalid(c, "filesystem-", names.InvalidTagError("filesystem-", names.FilesystemTagKind))
assertParseFilesystemTagInvalid(c, "machine-0", names.InvalidTagError("machine-0", names.FilesystemTagKind))
}
func (s *filesystemSuite) TestFilesystemMachine(c *gc.C) {
assertFilesystemMachine(c, "0/0", names.NewMachineTag("0"))
assertFilesystemMachine(c, "0/lxc/0/0", names.NewMachineTag("0/lxc/0"))
assertFilesystemNoMachine(c, "0")
assertFilesystemNoMachine(c, "some-unit/0/0")
}
func assertFilesystemMachine(c *gc.C, id string, expect names.MachineTag) {
t, ok := names.FilesystemMachine(names.NewFilesystemTag(id))
c.Assert(ok, gc.Equals, true)
c.Assert(t, gc.Equals, expect)
}
func assertFilesystemNoMachine(c *gc.C, id string) {
_, ok := names.FilesystemMachine(names.NewFilesystemTag(id))
c.Assert(ok, gc.Equals, false)
}
func (s *filesystemSuite) TestFilesystemUnit(c *gc.C) {
t, ok := names.FilesystemUnit(names.NewFilesystemTag("some-unit/0/0"))
c.Assert(ok, gc.Equals, true)
c.Assert(t, gc.Equals, names.NewUnitTag("some-unit/0"))
_, ok = names.FilesystemUnit(names.NewFilesystemTag("0/0"))
c.Assert(ok, gc.Equals, false)
}
func assertFilesystemIdValid(c *gc.C, name string) {
c.Assert(names.IsValidFilesystem(name), gc.Equals, true)
names.NewFilesystemTag(name)
}
func assertFilesystemIdInvalid(c *gc.C, name string) {
c.Assert(names.IsValidFilesystem(name), gc.Equals, false)
testFilesystemTag := func() { names.NewFilesystemTag(name) }
expect := fmt.Sprintf("%q is not a valid filesystem id", name)
c.Assert(testFilesystemTag, gc.PanicMatches, expect)
}
func assertParseFilesystemTag(c *gc.C, tag string, expect names.FilesystemTag) {
t, err := names.ParseFilesystemTag(tag)
c.Assert(err, gc.IsNil)
c.Assert(t, gc.Equals, expect)
}
func assertParseFilesystemTagInvalid(c *gc.C, tag string, expect error) {
_, err := names.ParseFilesystemTag(tag)
c.Assert(err, gc.ErrorMatches, expect.Error())
}
names-4.0.0/go.mod 0000664 0000000 0000000 00000001441 13650476565 0013745 0 ustar 00root root 0000000 0000000 module github.com/juju/names/v4
go 1.14
require (
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c // indirect
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
github.com/juju/retry v0.0.0-20180821225755-9058e192b216 // indirect
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2
github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)
names-4.0.0/go.sum 0000664 0000000 0000000 00000006075 13650476565 0014002 0 ustar 00root root 0000000 0000000 github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f h1:MCOvExGLpaSIzLYB4iQXEHP4jYVU6vmzLNQPdMVrxnM=
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU=
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647 h1:wQpkHVbIIpz1PCcLYku9KFWsJ7aEMQXHBBmLy3tRBTk=
github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt/BxiIxjLt9IKGvS+0injMHbag=
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2 h1:+j1SppRob9bAgoYmsdW9NNBdKZfgYuWpqnYHv78Qt8w=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
names-4.0.0/ipaddress.go 0000664 0000000 0000000 00000002275 13650476565 0015152 0 ustar 00root root 0000000 0000000 // 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
}
names-4.0.0/ipaddress_test.go 0000664 0000000 0000000 00000003335 13650476565 0016207 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"github.com/juju/utils"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type ipAddressSuite struct{}
var _ = gc.Suite(&ipAddressSuite{})
func (s *ipAddressSuite) TestNewIPAddressTag(c *gc.C) {
uuid := utils.MustNewUUID()
tag := names.NewIPAddressTag(uuid.String())
parsed, err := names.ParseIPAddressTag(tag.String())
c.Assert(err, gc.IsNil)
c.Assert(parsed.Kind(), gc.Equals, names.IPAddressTagKind)
c.Assert(parsed.Id(), gc.Equals, uuid.String())
c.Assert(parsed.String(), gc.Equals, names.IPAddressTagKind+"-"+uuid.String())
f := func() {
tag = names.NewIPAddressTag("42")
}
c.Assert(f, gc.PanicMatches, `invalid UUID: "42"`)
}
var parseIPAddressTagTests = []struct {
tag string
expected names.Tag
err error
}{
{tag: "", err: names.InvalidTagError("", "")},
{tag: "ipaddress-42424242-1111-2222-3333-0123456789ab", expected: names.NewIPAddressTag("42424242-1111-2222-3333-0123456789ab")},
{tag: "ipaddress-012345678", err: names.InvalidTagError("ipaddress-012345678", names.IPAddressTagKind)},
{tag: "ipaddress-42", err: names.InvalidTagError("ipaddress-42", names.IPAddressTagKind)},
{tag: "foobar", err: names.InvalidTagError("foobar", "")},
{tag: "space-yadda", err: names.InvalidTagError("space-yadda", names.IPAddressTagKind)}}
func (s *ipAddressSuite) TestParseIPAddressTag(c *gc.C) {
for i, t := range parseIPAddressTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseIPAddressTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/machine.go 0000664 0000000 0000000 00000004445 13650476565 0014601 0 ustar 00root root 0000000 0000000 // 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) }
// Parent returns the machineTag for the host of the container if the machineTag
// is a container, otherwise it returns nil.
func (t MachineTag) Parent() Tag {
parts := strings.Split(t.id, "-")
if len(parts) < 3 {
return nil
}
return MachineTag{id: strings.Join(parts[:len(parts)-2], "-")}
}
// ContainerType returns the type of container for this machine.
// If the machine isn't a container, then the empty string is returned.
func (t MachineTag) ContainerType() string {
parts := strings.Split(t.id, "-")
size := len(parts)
if size < 3 {
return ""
}
return parts[size-2]
}
// ChildId returns just the last segment of the ID.
func (t MachineTag) ChildId() string {
parts := strings.Split(t.id, "-")
return parts[len(parts)-1]
}
// 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)
}
names-4.0.0/machine_test.go 0000664 0000000 0000000 00000005761 13650476565 0015642 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
stdtesting "testing"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type machineSuite struct{}
var _ = gc.Suite(&machineSuite{})
func Test(t *stdtesting.T) {
gc.TestingT(t)
}
func (s *machineSuite) TestMachineTag(c *gc.C) {
c.Assert(names.NewMachineTag("10").String(), gc.Equals, "machine-10")
// Check a container id.
c.Assert(names.NewMachineTag("10/lxc/1").String(), gc.Equals, "machine-10-lxc-1")
}
var machineIdTests = []struct {
pattern string
valid bool
container bool
parent names.Tag
containerType string
childId string
}{
{pattern: "42", valid: true, childId: "42"},
{pattern: "042", valid: false},
{pattern: "0", valid: true, childId: "0"},
{pattern: "foo", valid: false},
{pattern: "/", valid: false},
{pattern: "55/", valid: false},
{pattern: "1/foo", valid: false},
{pattern: "2/foo/", valid: false},
{pattern: "3/lxc/42", valid: true, container: true, parent: names.NewMachineTag("3"), containerType: "lxc", childId: "42"},
{pattern: "3/lxc-nodash/42", valid: false},
{pattern: "0/lxc/00", valid: false},
{pattern: "0/lxc/0/", valid: false},
{pattern: "03/lxc/42", valid: false},
{pattern: "3/lxc/042", valid: false},
{pattern: "4/foo/bar", valid: false},
{pattern: "5/lxc/42/foo", valid: false},
{pattern: "6/lxc/42/kvm/0", valid: true, container: true, parent: names.NewMachineTag("6/lxc/42"), containerType: "kvm", childId: "0"},
{pattern: "06/lxc/42/kvm/0", valid: false},
{pattern: "6/lxc/042/kvm/0", valid: false},
{pattern: "6/lxc/42/kvm/00", valid: false},
{pattern: "06/lxc/042/kvm/00", valid: false},
}
func (s *machineSuite) TestMachineIdFormats(c *gc.C) {
for i, test := range machineIdTests {
c.Logf("test %d: %q", i, test.pattern)
c.Check(names.IsValidMachine(test.pattern), gc.Equals, test.valid)
c.Check(names.IsContainerMachine(test.pattern), gc.Equals, test.container)
if test.valid {
machine := names.NewMachineTag(test.pattern)
c.Check(machine.Parent(), gc.Equals, test.parent)
c.Check(machine.ContainerType(), gc.Equals, test.containerType)
c.Check(machine.ChildId(), gc.Equals, test.childId)
}
}
}
var parseMachineTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "machine-0",
expected: names.NewMachineTag("0"),
}, {
tag: "machine-one",
err: names.InvalidTagError("machine-one", names.MachineTagKind),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "user-one",
err: names.InvalidTagError("user-one", names.MachineTagKind),
}}
func (s *machineSuite) TestParseMachineTag(c *gc.C) {
for i, t := range parseMachineTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseMachineTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/model.go 0000664 0000000 0000000 00000002613 13650476565 0014270 0 ustar 00root root 0000000 0000000 // 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)
}
names-4.0.0/model_test.go 0000664 0000000 0000000 00000003170 13650476565 0015326 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type modelSuite struct{}
var _ = gc.Suite(&modelSuite{})
var parseModelTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "model-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expected: names.NewModelTag("f47ac10b-58cc-4372-a567-0e02b2c3d479"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "model-",
err: names.InvalidTagError("model-", names.ModelTagKind),
}, {
tag: "application-dave",
err: names.InvalidTagError("application-dave", names.ModelTagKind),
}}
func (s *modelSuite) TestParseModelTag(c *gc.C) {
for i, t := range parseModelTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseModelTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
var modelNameTest = []struct {
test string
name string
expected bool
}{{
test: "Hyphenated true",
name: "foo-bar",
expected: true,
}, {
test: "Whitespsce false",
name: "foo bar",
expected: false,
}, {
test: "Capital false",
name: "fooBar",
expected: false,
}, {
test: "At sign false",
name: "foo@bar",
expected: false,
}}
func (s *modelSuite) TestModelName(c *gc.C) {
for i, t := range modelNameTest {
c.Logf("test %d: %q", i, t.name)
c.Check(names.IsValidModelName(t.name), gc.Equals, t.expected)
}
}
names-4.0.0/operation.go 0000664 0000000 0000000 00000002635 13650476565 0015174 0 ustar 00root root 0000000 0000000 // Copyright 2020 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"fmt"
"regexp"
)
const OperationTagKind = "operation"
// OperationSnippet defines the regexp for a valid Operation Id.
// Operations are identified by a unique, incrementing number.
const OperationSnippet = NumberSnippet
var validOperation = regexp.MustCompile("^" + OperationSnippet + "$")
type OperationTag struct {
// Tags that are serialized need to have fields exported.
ID string
}
// NewOperationTag returns the tag of an operation with the given id.
func NewOperationTag(id string) OperationTag {
if !validOperation.MatchString(id) {
panic(fmt.Sprintf("invalid operation id %q", id))
}
return OperationTag{ID: id}
}
// ParseOperationTag parses an operation tag string.
func ParseOperationTag(operationTag string) (OperationTag, error) {
tag, err := ParseTag(operationTag)
if err != nil {
return OperationTag{}, err
}
at, ok := tag.(OperationTag)
if !ok {
return OperationTag{}, invalidTagError(operationTag, OperationTagKind)
}
return at, nil
}
func (t OperationTag) String() string { return t.Kind() + "-" + t.Id() }
func (t OperationTag) Kind() string { return OperationTagKind }
func (t OperationTag) Id() string { return t.ID }
// IsValidOperation returns whether id is a valid operation id.
func IsValidOperation(id string) bool {
return validOperation.MatchString(id)
}
names-4.0.0/operation_test.go 0000664 0000000 0000000 00000002323 13650476565 0016225 0 ustar 00root root 0000000 0000000 // Copyright 2020 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
jc "github.com/juju/testing/checkers"
)
type operationSuite struct{}
var _ = gc.Suite(&operationSuite{})
var parseOperationTagTests = []struct {
tag string
expected names.Tag
err error
}{
{tag: "", err: names.InvalidTagError("", "")},
{tag: "operation-1", expected: names.NewOperationTag("1")},
{tag: "operation-foo", err: names.InvalidTagError("operation-foo", "operation")},
{tag: "bob", err: names.InvalidTagError("bob", "")},
{tag: "application-ned", err: names.InvalidTagError("application-ned", names.OperationTagKind)}}
func (s *operationSuite) TestParseOperationTag(c *gc.C) {
for i, t := range parseOperationTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseOperationTag(t.tag)
if t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(err, jc.ErrorIsNil)
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
func (s *operationSuite) TestString(c *gc.C) {
tag := names.NewOperationTag("666")
c.Assert(tag.String(), gc.Equals, "operation-666")
}
names-4.0.0/payload.go 0000664 0000000 0000000 00000003333 13650476565 0014621 0 ustar 00root root 0000000 0000000 // 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)
}
names-4.0.0/payload_test.go 0000664 0000000 0000000 00000004617 13650476565 0015666 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"github.com/juju/names/v4"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
)
var _ = gc.Suite(&payloadSuite{})
type payloadSuite struct{}
type payloadTest struct {
input string
}
func checkPayload(c *gc.C, id string, tag names.PayloadTag) {
c.Check(tag.Kind(), gc.Equals, names.PayloadTagKind)
c.Check(tag.Id(), gc.Equals, id)
c.Check(tag.String(), gc.Equals, names.PayloadTagKind+"-"+id)
}
func (s *payloadSuite) TestPayloadTag(c *gc.C) {
id := "f47ac10b-58cc-4372-a567-0e02b2c3d479"
tag := names.NewPayloadTag(id)
c.Check(tag.Kind(), gc.Equals, names.PayloadTagKind)
c.Check(tag.Id(), gc.Equals, id)
c.Check(tag.String(), gc.Equals, names.PayloadTagKind+"-"+id)
}
func (s *payloadSuite) TestIsValidPayload(c *gc.C) {
for i, test := range []struct {
id string
expect bool
}{
{"", false},
{"spam-", false},
{"spam", true},
{"spam-and-eggs", true},
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", true},
} {
c.Logf("test %d: %s", i, test.id)
ok := names.IsValidPayload(test.id)
c.Check(ok, gc.Equals, test.expect)
}
}
func (s *payloadSuite) TestParsePayloadTag(c *gc.C) {
for i, test := range []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "payload-",
err: names.InvalidTagError("payload-", names.PayloadTagKind),
}, {
tag: "payload-spam",
expected: names.NewPayloadTag("spam"),
}, {
tag: "payload-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expected: names.NewPayloadTag("f47ac10b-58cc-4372-a567-0e02b2c3d479"),
}, {
tag: "spam",
err: names.InvalidTagError("spam", ""),
}, {
tag: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
err: names.InvalidTagError("f47ac10b-58cc-4372-a567-0e02b2c3d479", ""),
}, {
tag: "unit-f47ac10b-58cc-4372-a567-0e02b2c3d479",
err: names.InvalidTagError("unit-f47ac10b-58cc-4372-a567-0e02b2c3d479", names.UnitTagKind),
}, {
tag: "action-f47ac10b-58cc-4372-a567-0e02b2c3d479",
err: names.InvalidTagError("action-f47ac10b-58cc-4372-a567-0e02b2c3d479", names.PayloadTagKind),
}} {
c.Logf("test %d: %s", i, test.tag)
got, err := names.ParsePayloadTag(test.tag)
if test.err != nil {
c.Check(err, jc.DeepEquals, test.err)
} else {
c.Check(err, jc.ErrorIsNil)
c.Check(got, jc.DeepEquals, test.expected)
}
}
}
names-4.0.0/relation.go 0000664 0000000 0000000 00000004206 13650476565 0015005 0 ustar 00root root 0000000 0000000 // 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)
}
names-4.0.0/relation_test.go 0000664 0000000 0000000 00000005223 13650476565 0016044 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type relationSuite struct{}
var _ = gc.Suite(&relationSuite{})
var relationNameTests = []struct {
pattern string
valid bool
}{
{pattern: "", valid: false},
{pattern: "0foo", valid: false},
{pattern: "foo", valid: true},
{pattern: "f1-boo", valid: true},
{pattern: "f-o-o", valid: true},
{pattern: "-foo", valid: false},
{pattern: "fo#o", valid: false},
{pattern: "foo-42", valid: true},
{pattern: "FooBar", valid: false},
{pattern: "foo42-bar1", valid: true},
{pattern: "42", valid: false},
{pattern: "0", valid: false},
{pattern: "%not", valid: false},
{pattern: "42also-not", valid: false},
{pattern: "042", valid: false},
{pattern: "0x42", valid: false},
{pattern: "foo_42", valid: true},
{pattern: "_foo", valid: false},
{pattern: "!foo", valid: false},
{pattern: "foo_bar-baz_boo", valid: true},
{pattern: "foo bar", valid: false},
{pattern: "foo-_", valid: false},
{pattern: "foo-", valid: false},
{pattern: "foo_-a", valid: false},
{pattern: "foo_", valid: false},
}
func (s *relationSuite) TestRelationKeyFormats(c *gc.C) {
// In order to test all possible combinations, we need
// to use the relationNameTests and applicationNameTests
// twice to construct all possible keys.
for i, testRel := range relationNameTests {
for j, testSvc := range applicationNameTests {
peerKey := testSvc.pattern + ":" + testRel.pattern
key := peerKey + " " + peerKey
isValid := testSvc.valid && testRel.valid
c.Logf("test %d: %q -> valid: %v", i*len(applicationNameTests)+j, key, isValid)
c.Assert(names.IsValidRelation(key), gc.Equals, isValid)
c.Assert(names.IsValidRelation(peerKey), gc.Equals, isValid)
}
}
}
var parseRelationTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "relation-wordpress:db mysql:db",
expected: names.NewRelationTag("wordpress:db mysql:db"),
}, {
tag: "relation-wordpress:mysql",
expected: names.NewRelationTag("wordpress:mysql"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "application-dave",
err: names.InvalidTagError("application-dave", names.RelationTagKind),
}}
func (s *relationSuite) TestParseRelationTag(c *gc.C) {
for i, t := range parseRelationTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseRelationTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/set.go 0000664 0000000 0000000 00000007040 13650476565 0013762 0 ustar 00root root 0000000 0000000 // Copyright 2014-2018 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"sort"
"github.com/juju/errors"
)
// Set represents the Set data structure, and contains Tags.
type Set map[Tag]bool
// NewSet creates and initializes a Set and populates it with
// inital values as specified in the parameters.
func NewSet(initial ...Tag) Set {
result := make(Set)
for _, value := range initial {
result.Add(value)
}
return result
}
// NewSetFromStrings creates and initializes a Set and populates it
// by using names.ParseTag on the initial values specified in the parameters.
func NewSetFromStrings(initial ...string) (Set, error) {
result := make(Set)
for _, value := range initial {
tag, err := ParseTag(value)
if err != nil {
return result, errors.Trace(err)
}
result.Add(tag)
}
return result, nil
}
// Size returns the number of elements in the set.
func (t Set) Size() int {
return len(t)
}
// IsEmpty is true for empty or uninitialized sets.
func (t Set) IsEmpty() bool {
return len(t) == 0
}
// Add puts a value into the set.
func (t Set) Add(value Tag) {
if t == nil {
panic("uninitalised set")
}
t[value] = true
}
// Remove takes a value out of the set. If value wasn't in the set to start
// with, this method silently succeeds.
func (t Set) Remove(value Tag) {
delete(t, value)
}
// Contains returns true if the value is in the set, and false otherwise.
func (t Set) Contains(value Tag) bool {
_, exists := t[value]
return exists
}
// Values returns an unordered slice containing all the values in the set.
func (t Set) Values() []Tag {
result := make([]Tag, len(t))
i := 0
for key := range t {
result[i] = key
i++
}
return result
}
// stringValues returns a list of strings that represent a Tag.
// Used internally by the SortedValues method.
func (t Set) stringValues() []string {
result := make([]string, t.Size())
i := 0
for key := range t {
result[i] = key.String()
i++
}
return result
}
// SortedValues returns an ordered slice containing all the values in the set.
func (t Set) SortedValues() []Tag {
values := t.stringValues()
sort.Strings(values)
result := make([]Tag, len(values))
for i, value := range values {
// We already know only good strings can live in the Tags set
// so we can safely ignore the error here.
tag, _ := ParseTag(value)
result[i] = tag
}
return result
}
// Union returns a new Set representing a union of the elments in the
// method target and the parameter.
func (t Set) Union(other Set) Set {
result := make(Set)
// Use the internal map rather than going through the friendlier functions
// to avoid extra allocation of slices.
for value := range t {
result[value] = true
}
for value := range other {
result[value] = true
}
return result
}
// Intersection returns a new Set representing a intersection of the elments in the
// method target and the parameter.
func (t Set) Intersection(other Set) Set {
result := make(Set)
// Use the internal map rather than going through the friendlier functions
// to avoid extra allocation of slices.
for value := range t {
if other.Contains(value) {
result[value] = true
}
}
return result
}
// Difference returns a new Tags representing all the values in the
// target that are not in the parameter.
func (t Set) Difference(other Set) Set {
result := make(Set)
// Use the internal map rather than going through the friendlier functions
// to avoid extra allocation of slices.
for value := range t {
if !other.Contains(value) {
result[value] = true
}
}
return result
}
names-4.0.0/set_test.go 0000664 0000000 0000000 00000010751 13650476565 0015024 0 ustar 00root root 0000000 0000000 // Copyright 2013-2018 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"github.com/juju/testing"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type tagSetSuite struct {
testing.IsolationSuite
foo names.Tag
bar names.Tag
baz names.Tag
bang names.Tag
}
var _ = gc.Suite(&tagSetSuite{})
func (s *tagSetSuite) SetUpTest(c *gc.C) {
s.IsolationSuite.SetUpTest(c)
var err error
s.foo, err = names.ParseTag("unit-wordpress-0")
c.Assert(err, gc.IsNil)
s.bar, err = names.ParseTag("unit-rabbitmq-server-0")
c.Assert(err, gc.IsNil)
s.baz, err = names.ParseTag("unit-mongodb-0")
c.Assert(err, gc.IsNil)
s.bang, err = names.ParseTag("machine-0")
c.Assert(err, gc.IsNil)
}
func (tagSetSuite) TestEmpty(c *gc.C) {
t := names.NewSet()
c.Assert(t.Size(), gc.Equals, 0)
}
func (s tagSetSuite) TestInitialValues(c *gc.C) {
t := names.NewSet(s.foo, s.bar)
c.Assert(t.Size(), gc.Equals, 2)
}
func (tagSetSuite) TestInitialStringValues(c *gc.C) {
t, err := names.NewSetFromStrings("unit-wordpress-0", "unit-rabbitmq-server-0")
c.Assert(err, gc.IsNil)
c.Assert(t.Size(), gc.Equals, 2)
}
func (tagSetSuite) TestInitialStringValuesBad(c *gc.C) {
_, err := names.NewSetFromStrings("not-a-tag")
c.Assert(err, gc.ErrorMatches, `"not-a-tag" is not a valid tag`)
}
func (tagSetSuite) TestSize(c *gc.C) {
// Empty sets are empty.
s := names.NewSet()
c.Assert(s.Size(), gc.Equals, 0)
s, err := names.NewSetFromStrings(
"unit-wordpress-0",
"unit-rabbitmq-server-0",
)
c.Assert(err, gc.IsNil)
c.Assert(s.Size(), gc.Equals, 2)
}
func (tagSetSuite) TestSizeDuplicate(c *gc.C) {
// Empty sets are empty.
s := names.NewSet()
c.Assert(s.Size(), gc.Equals, 0)
// Size returns number of unique values.
s, err := names.NewSetFromStrings(
"unit-wordpress-0",
"unit-rabbitmq-server-0",
"unit-wordpress-0",
)
c.Assert(err, gc.IsNil)
c.Assert(s.Size(), gc.Equals, 2)
}
func (s tagSetSuite) TestIsEmpty(c *gc.C) {
// Empty sets are empty.
t := names.NewSet()
c.Assert(t.IsEmpty(), gc.Equals, true)
// Non-empty sets are not empty.
t = names.NewSet(s.foo)
c.Assert(t.IsEmpty(), gc.Equals, false)
// Newly empty sets work too.
t.Remove(s.foo)
c.Assert(t.IsEmpty(), gc.Equals, true)
}
func (s tagSetSuite) TestAdd(c *gc.C) {
t := names.NewSet()
t.Add(s.foo)
c.Assert(t.Size(), gc.Equals, 1)
c.Assert(t.Contains(s.foo), gc.Equals, true)
}
func (s tagSetSuite) TestAddDuplicate(c *gc.C) {
t := names.NewSet()
t.Add(s.foo)
t.Add(s.bar)
t.Add(s.bar)
c.Assert(t.Size(), gc.Equals, 2)
}
func (s tagSetSuite) TestRemove(c *gc.C) {
t := names.NewSet(s.foo, s.bar)
t.Remove(s.foo)
c.Assert(t.Contains(s.foo), gc.Equals, false)
c.Assert(t.Contains(s.bar), gc.Equals, true)
}
func (s tagSetSuite) TestContains(c *gc.C) {
t, err := names.NewSetFromStrings("unit-wordpress-0", "unit-rabbitmq-server-0")
c.Assert(err, gc.IsNil)
c.Assert(t.Contains(s.foo), gc.Equals, true)
c.Assert(t.Contains(s.bar), gc.Equals, true)
c.Assert(t.Contains(s.baz), gc.Equals, false)
}
func (s tagSetSuite) TestSortedValues(c *gc.C) {
t := names.NewSet(s.foo, s.bang, s.baz, s.bar)
values := t.SortedValues()
c.Assert(values, gc.DeepEquals, []names.Tag{s.bang, s.baz, s.bar, s.foo})
}
func (s tagSetSuite) TestRemoveNonExistent(c *gc.C) {
t := names.NewSet()
t.Remove(s.foo)
c.Assert(t.Size(), gc.Equals, 0)
}
func (s tagSetSuite) TestUnion(c *gc.C) {
t1 := names.NewSet(s.foo, s.bar)
t2 := names.NewSet(s.foo, s.baz, s.bang)
union1 := t1.Union(t2)
union2 := t2.Union(t1)
c.Assert(union1.Size(), gc.Equals, 4)
c.Assert(union2.Size(), gc.Equals, 4)
c.Assert(union1, gc.DeepEquals, union2)
c.Assert(union1, gc.DeepEquals, names.NewSet(s.foo, s.bar, s.baz, s.bang))
}
func (s tagSetSuite) TestIntersection(c *gc.C) {
t1 := names.NewSet(s.foo, s.bar)
t2 := names.NewSet(s.foo, s.baz, s.bang)
int1 := t1.Intersection(t2)
int2 := t2.Intersection(t1)
c.Assert(int1.Size(), gc.Equals, 1)
c.Assert(int2.Size(), gc.Equals, 1)
c.Assert(int1, gc.DeepEquals, int2)
c.Assert(int1, gc.DeepEquals, names.NewSet(s.foo))
}
func (s tagSetSuite) TestDifference(c *gc.C) {
t1 := names.NewSet(s.foo, s.bar)
t2 := names.NewSet(s.foo, s.baz, s.bang)
diff1 := t1.Difference(t2)
diff2 := t2.Difference(t1)
c.Assert(diff1, gc.DeepEquals, names.NewSet(s.bar))
c.Assert(diff2, gc.DeepEquals, names.NewSet(s.baz, s.bang))
}
func (s tagSetSuite) TestUninitializedPanics(c *gc.C) {
f := func() {
var t names.Set
t.Add(s.foo)
}
c.Assert(f, gc.PanicMatches, "uninitalised set")
}
names-4.0.0/snippet_test.go 0000664 0000000 0000000 00000001332 13650476565 0015706 0 ustar 00root root 0000000 0000000 package names
import (
"strings"
gc "gopkg.in/check.v1"
)
var snippets = []struct {
name string
snippet string
}{
{"ContainerTypeSnippet", ContainerTypeSnippet},
{"ContainerSnippet", ContainerSnippet},
{"MachineSnippet", MachineSnippet},
{"NumberSnippet", NumberSnippet},
{"ApplicationSnippet", ApplicationSnippet},
{"RelationSnippet", RelationSnippet},
}
type snippetSuite struct{}
var _ = gc.Suite(&snippetSuite{})
func (s *equalitySuite) TestSnippetsContainNoCapturingGroups(c *gc.C) {
for _, test := range snippets {
for i, ch := range test.snippet {
if ch == '(' && !strings.HasPrefix(test.snippet[i:], "(?:") {
c.Errorf("%s (%q) contains capturing group", test.name, test.snippet)
}
}
}
}
names-4.0.0/space.go 0000664 0000000 0000000 00000002207 13650476565 0014262 0 ustar 00root root 0000000 0000000 // 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
}
names-4.0.0/space_test.go 0000664 0000000 0000000 00000003714 13650476565 0015325 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"fmt"
"regexp"
"github.com/juju/names/v4"
gc "gopkg.in/check.v1"
)
type spaceSuite struct{}
var _ = gc.Suite(&spaceSuite{})
var spaceNameTests = []struct {
pattern string
valid bool
}{
{pattern: "", valid: false},
{pattern: "eth0", valid: true},
{pattern: "-my-net-", valid: false},
{pattern: "42", valid: true},
{pattern: "%not", valid: false},
{pattern: "$PATH", valid: false},
{pattern: "but-this-works", valid: true},
{pattern: "----", valid: false},
{pattern: "oh--no", valid: false},
{pattern: "777", valid: true},
{pattern: "is-it-", valid: false},
{pattern: "also_not", valid: false},
{pattern: "a--", valid: false},
{pattern: "foo-2", valid: true},
}
func (s *spaceSuite) TestSpaceNames(c *gc.C) {
for i, test := range spaceNameTests {
c.Logf("test %d: %q", i, test.pattern)
c.Check(names.IsValidSpace(test.pattern), gc.Equals, test.valid)
if test.valid {
expectTag := fmt.Sprintf("%s-%s", names.SpaceTagKind, test.pattern)
c.Check(names.NewSpaceTag(test.pattern).String(), gc.Equals, expectTag)
} else {
expectErr := fmt.Sprintf("%q is not a valid space name", test.pattern)
testTag := func() { names.NewSpaceTag(test.pattern) }
c.Check(testTag, gc.PanicMatches, regexp.QuoteMeta(expectErr))
}
}
}
var parseSpaceTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "space-1",
expected: names.NewSpaceTag("1"),
}, {
tag: "-space1",
err: names.InvalidTagError("-space1", ""),
}}
func (s *spaceSuite) TestParseSpaceTag(c *gc.C) {
for i, t := range parseSpaceTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseSpaceTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/storage.go 0000664 0000000 0000000 00000004424 13650476565 0014636 0 ustar 00root root 0000000 0000000 // 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
}
names-4.0.0/storage_test.go 0000664 0000000 0000000 00000004763 13650476565 0015703 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"fmt"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type storageSuite struct{}
var _ = gc.Suite(&storageSuite{})
func (s *storageSuite) TestStorageTag(c *gc.C) {
c.Assert(names.NewStorageTag("store/1").String(), gc.Equals, "storage-store-1")
}
func (s *storageSuite) TestStorageNameValidity(c *gc.C) {
assertStorageIdValid(c, "shared-fs/0")
assertStorageIdValid(c, "db-dir/1000")
assertStorageIdInvalid(c, "store/-1")
assertStorageIdInvalid(c, "store-1")
assertStorageIdInvalid(c, "")
assertStorageIdInvalid(c, "store")
assertStorageIdInvalid(c, "store/#")
}
func (s *storageSuite) TestParseStorageTag(c *gc.C) {
assertParseStorageTag(c, "storage-shared-fs-0", names.NewStorageTag("shared-fs/0"))
assertParseStorageTag(c, "storage-store-88", names.NewStorageTag("store/88"))
assertParseStorageTagInvalid(c, "", names.InvalidTagError("", ""))
assertParseStorageTagInvalid(c, "one", names.InvalidTagError("one", ""))
assertParseStorageTagInvalid(c, "storage-", names.InvalidTagError("storage-", names.StorageTagKind))
assertParseStorageTagInvalid(c, "machine-0", names.InvalidTagError("machine-0", names.StorageTagKind))
}
func (s *applicationSuite) TestStorageName(c *gc.C) {
assertStorageNameValid(c, "shared-fs/0", "shared-fs")
assertStorageNameInvalid(c, "storage-shared-fs-0")
}
func assertStorageIdValid(c *gc.C, name string) {
c.Assert(names.IsValidStorage(name), gc.Equals, true)
names.NewStorageTag(name)
}
func assertStorageIdInvalid(c *gc.C, name string) {
c.Assert(names.IsValidStorage(name), gc.Equals, false)
testStorageTag := func() { names.NewStorageTag(name) }
expect := fmt.Sprintf("%q is not a valid storage instance ID", name)
c.Assert(testStorageTag, gc.PanicMatches, expect)
}
func assertStorageNameValid(c *gc.C, id, expect string) {
name, err := names.StorageName(id)
c.Assert(err, gc.IsNil)
c.Assert(name, gc.Equals, expect)
}
func assertStorageNameInvalid(c *gc.C, id string) {
_, err := names.StorageName(id)
expect := fmt.Sprintf("%q is not a valid storage instance ID", id)
c.Assert(err, gc.ErrorMatches, expect)
}
func assertParseStorageTag(c *gc.C, tag string, expect names.StorageTag) {
t, err := names.ParseStorageTag(tag)
c.Assert(err, gc.IsNil)
c.Assert(t, gc.Equals, expect)
}
func assertParseStorageTagInvalid(c *gc.C, tag string, expect error) {
_, err := names.ParseStorageTag(tag)
c.Assert(err, gc.ErrorMatches, expect.Error())
}
names-4.0.0/subnet.go 0000664 0000000 0000000 00000002131 13650476565 0014463 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"fmt"
"regexp"
)
const SubnetTagKind = "subnet"
var validSubnet = regexp.MustCompile("^" + NumberSnippet + "$")
// IsValidSubnet returns whether id is a valid subnet id.
func IsValidSubnet(id string) bool {
return validSubnet.MatchString(id)
}
type SubnetTag struct {
id string
}
func (t SubnetTag) String() string { return t.Kind() + "-" + t.id }
func (t SubnetTag) Kind() string { return SubnetTagKind }
func (t SubnetTag) Id() string { return t.id }
// NewSubnetTag returns the tag for subnet with the given ID.
func NewSubnetTag(id string) SubnetTag {
if !IsValidSubnet(id) {
panic(fmt.Sprintf("%s is not a valid subnet ID", id))
}
return SubnetTag{id: id}
}
// 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
}
names-4.0.0/subnet_test.go 0000664 0000000 0000000 00000003036 13650476565 0015527 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type subnetSuite struct{}
var _ = gc.Suite(&subnetSuite{})
func (s *subnetSuite) TestNewSubnetTag(c *gc.C) {
id := "16"
tag := names.NewSubnetTag(id)
parsed, err := names.ParseSubnetTag(tag.String())
c.Assert(err, gc.IsNil)
c.Assert(parsed.Kind(), gc.Equals, names.SubnetTagKind)
c.Assert(parsed.Id(), gc.Equals, id)
c.Assert(parsed.String(), gc.Equals, names.SubnetTagKind+"-"+id)
f := func() {
tag = names.NewSubnetTag("foo")
}
c.Assert(f, gc.PanicMatches, "foo is not a valid subnet ID")
}
var parseSubnetTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "subnet-16",
expected: names.NewSubnetTag("16"),
}, {
tag: "subnet-foo",
err: names.InvalidTagError("subnet-foo", names.SubnetTagKind),
}, {
tag: "subnet-",
err: names.InvalidTagError("subnet-", names.SubnetTagKind),
}, {
tag: "foobar",
err: names.InvalidTagError("foobar", ""),
}, {
tag: "unit-foo-0",
err: names.InvalidTagError("unit-foo-0", names.SubnetTagKind),
}}
func (s *subnetSuite) TestParseSubnetTag(c *gc.C) {
for i, t := range parseSubnetTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseSubnetTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
names-4.0.0/tag.go 0000664 0000000 0000000 00000015316 13650476565 0013747 0 ustar 00root root 0000000 0000000 // 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, ApplicationOfferTagKind, EnvironTagKind, UserTagKind,
RelationTagKind, ActionTagKind, VolumeTagKind, CharmTagKind, StorageTagKind, OperationTagKind,
FilesystemTagKind, IPAddressTagKind, SpaceTagKind, SubnetTagKind,
PayloadTagKind, ModelTagKind, ControllerTagKind, CloudTagKind, CloudCredentialTagKind, CAASModelTagKind:
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 ApplicationOfferTagKind:
if !IsValidApplicationOffer(id) {
return nil, invalidTagError(tag, kind)
}
return NewApplicationOfferTag(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:
// Controller and ControllerAgent tags use the same "controller" prefix.
// Controller ids are UUIDs, ControllerAgent ids are numbers.
if IsValidController(id) {
return NewControllerTag(id), nil
}
if IsValidControllerAgent(id) {
return NewControllerAgentTag(id), nil
}
return nil, invalidTagError(tag, kind)
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 OperationTagKind:
if !IsValidOperation(id) {
return nil, invalidTagError(tag, kind)
}
return NewOperationTag(id), nil
case VolumeTagKind:
id = filesystemOrVolumeTagSuffixToId(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 = filesystemOrVolumeTagSuffixToId(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
case CAASModelTagKind:
if !IsValidCAASModel(id) {
return nil, invalidTagError(tag, kind)
}
return NewCAASModelTag(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()
}
names-4.0.0/tag_test.go 0000664 0000000 0000000 00000031000 13650476565 0014772 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"strconv"
"github.com/juju/names/v4"
gc "gopkg.in/check.v1"
)
type tagSuite struct{}
var _ = gc.Suite(&tagSuite{})
var tagKindTests = []struct {
tag string
kind string
err string
}{
{tag: "unit-wordpress-42", kind: names.UnitTagKind},
{tag: "machine-42", kind: names.MachineTagKind},
{tag: "application-foo", kind: names.ApplicationTagKind},
{tag: "applicationoffer-foo", kind: names.ApplicationOfferTagKind},
{tag: "environment-42", kind: names.EnvironTagKind},
{tag: "model-42", kind: names.ModelTagKind},
{tag: "user-admin", kind: names.UserTagKind},
{tag: "relation-application1.rel1#other-svc.other-rel2", kind: names.RelationTagKind},
{tag: "relation-application.peerRelation", kind: names.RelationTagKind},
{tag: "foo", err: `"foo" is not a valid tag`},
{tag: "unit", err: `"unit" is not a valid tag`},
{tag: "network", err: `"network" is not a valid tag`},
{tag: "ab01cd23-0123-4edc-9a8b-fedcba987654", err: `"ab01cd23-0123-4edc-9a8b-fedcba987654" is not a valid tag`},
{tag: "action-ab01cd23-0123-4edc-9a8b-fedcba987654", kind: names.ActionTagKind},
{tag: "action-1", kind: names.ActionTagKind},
{tag: "operation-1", kind: names.OperationTagKind},
{tag: "volume-0", kind: names.VolumeTagKind},
{tag: "storage-data-0", kind: names.StorageTagKind},
{tag: "filesystem-0", kind: names.FilesystemTagKind},
{tag: "ipaddress", err: `"ipaddress" is not a valid tag`},
{tag: "ipaddress-42424242-1111-2222-3333-0123456789ab", kind: names.IPAddressTagKind},
{tag: "subnet", err: `"subnet" is not a valid tag`},
{tag: "subnet-16", kind: names.SubnetTagKind},
{tag: "space", err: `"space" is not a valid tag`},
{tag: "space-42", kind: names.SpaceTagKind},
{tag: "cloud", err: `"cloud" is not a valid tag`},
{tag: "cloud-aws", kind: names.CloudTagKind},
{tag: "cloudcred", err: `"cloudcred" is not a valid tag`},
{tag: "cloudcred-aws_admin_foo", kind: names.CloudCredentialTagKind},
{tag: "caasmodel-57", kind: names.CAASModelTagKind},
{tag: "controller-f47ac10b-58cc-4372-a567-0e02b2c3d479", kind: names.ControllerTagKind},
{tag: "controller-123", kind: names.ControllerAgentTagKind},
}
func (*tagSuite) TestTagKind(c *gc.C) {
for i, test := range tagKindTests {
c.Logf("test %d: %q -> %q", i, test.tag, test.kind)
kind, err := names.TagKind(test.tag)
if test.err == "" {
c.Assert(test.kind, gc.Equals, kind)
c.Assert(err, gc.IsNil)
} else {
c.Assert(kind, gc.Equals, "")
c.Assert(err, gc.ErrorMatches, test.err)
}
}
}
var parseTagTests = []struct {
tag string
expectKind string
expectType interface{}
resultId string
resultErr string
}{{
tag: "machine-10",
expectKind: names.MachineTagKind,
expectType: names.MachineTag{},
resultId: "10",
}, {
tag: "machine-10-lxc-1",
expectKind: names.MachineTagKind,
expectType: names.MachineTag{},
resultId: "10/lxc/1",
}, {
tag: "machine-#",
expectKind: names.MachineTagKind,
expectType: names.MachineTag{},
resultErr: `"machine-#" is not a valid machine tag`,
}, {
tag: "unit-wordpress-0",
expectKind: names.UnitTagKind,
expectType: names.UnitTag{},
resultId: "wordpress/0",
}, {
tag: "unit-rabbitmq-server-0",
expectKind: names.UnitTagKind,
expectType: names.UnitTag{},
resultId: "rabbitmq-server/0",
}, {
tag: "unit-#",
expectKind: names.UnitTagKind,
expectType: names.UnitTag{},
resultErr: `"unit-#" is not a valid unit tag`,
}, {
tag: "application-wordpress",
expectKind: names.ApplicationTagKind,
expectType: names.ApplicationTag{},
resultId: "wordpress",
}, {
tag: "application-#",
expectKind: names.ApplicationTagKind,
expectType: names.ApplicationTag{},
resultErr: `"application-#" is not a valid application tag`,
}, {
tag: "applicationoffer-hosted-mysql",
expectKind: names.ApplicationOfferTagKind,
expectType: names.ApplicationOfferTag{},
resultId: "hosted-mysql",
}, {
tag: "applicationoffer-#",
expectKind: names.ApplicationOfferTagKind,
expectType: names.ApplicationOfferTag{},
resultErr: `"applicationoffer-#" is not a valid applicationoffer tag`,
}, {
tag: "environment-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expectKind: names.EnvironTagKind,
expectType: names.EnvironTag{},
resultId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
}, {
tag: "model-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expectKind: names.ModelTagKind,
expectType: names.ModelTag{},
resultId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
}, {
tag: "relation-my-svc1.myrel1#other-svc.other-rel2",
expectKind: names.RelationTagKind,
expectType: names.RelationTag{},
resultId: "my-svc1:myrel1 other-svc:other-rel2",
}, {
tag: "relation-riak.ring",
expectKind: names.RelationTagKind,
expectType: names.RelationTag{},
resultId: "riak:ring",
}, {
tag: "environment-/",
expectKind: names.EnvironTagKind,
expectType: names.EnvironTag{},
resultErr: `"environment-/" is not a valid environment tag`,
}, {
tag: "model-/",
expectKind: names.ModelTagKind,
expectType: names.ModelTag{},
resultErr: `"model-/" is not a valid model tag`,
}, {
tag: "user-foo",
expectKind: names.UserTagKind,
expectType: names.UserTag{},
resultId: "foo",
}, {
tag: "user-foo@remote",
expectKind: names.UserTagKind,
expectType: names.UserTag{},
resultId: "foo@remote",
}, {
tag: "user-/",
expectKind: names.UserTagKind,
expectType: names.UserTag{},
resultErr: `"user-/" is not a valid user tag`,
}, {
tag: "action-00000000-abcd",
expectKind: names.ActionTagKind,
expectType: names.ActionTag{},
resultErr: `"action-00000000-abcd" is not a valid action tag`,
}, {
tag: "action-00000033",
expectKind: names.ActionTagKind,
expectType: names.ActionTag{},
resultErr: `"action-00000033" is not a valid action tag`,
}, {
tag: "action-abedaf33-3212-4fde-aeca-87356432deca",
expectKind: names.ActionTagKind,
expectType: names.ActionTag{},
resultId: "abedaf33-3212-4fde-aeca-87356432deca",
}, {
tag: "action-1",
expectKind: names.ActionTagKind,
expectType: names.ActionTag{},
resultId: "1",
}, {
tag: "operation-1",
expectKind: names.OperationTagKind,
expectType: names.OperationTag{},
resultId: "1",
}, {
tag: "volume-2",
expectKind: names.VolumeTagKind,
expectType: names.VolumeTag{},
resultId: "2",
}, {
tag: "filesystem-3",
expectKind: names.FilesystemTagKind,
expectType: names.FilesystemTag{},
resultId: "3",
}, {
tag: "storage-block-storage-0",
expectKind: names.StorageTagKind,
expectType: names.StorageTag{},
resultId: "block-storage/0",
}, {
tag: "foo",
resultErr: `"foo" is not a valid tag`,
}, {
tag: "ipaddress-",
resultErr: `"ipaddress-" is not a valid ipaddress tag`,
}, {
tag: "ipaddress-42424242-1111-2222-3333-0123456789ab",
expectKind: names.IPAddressTagKind,
expectType: names.IPAddressTag{},
resultId: "42424242-1111-2222-3333-0123456789ab",
}, {
tag: "subnet-",
resultErr: `"subnet-" is not a valid subnet tag`,
}, {
tag: "subnet-16",
expectKind: names.SubnetTagKind,
expectType: names.SubnetTag{},
resultId: "16",
}, {
tag: "space-",
resultErr: `"space-" is not a valid space tag`,
}, {
tag: "space-myspace1",
expectKind: names.SpaceTagKind,
expectType: names.SpaceTag{},
resultId: "myspace1",
}, {
tag: "cloud-aws",
expectKind: names.CloudTagKind,
expectType: names.CloudTag{},
resultId: "aws",
}, {
tag: "cloudcred-aws_admin_foo%5fbar",
expectKind: names.CloudCredentialTagKind,
expectType: names.CloudCredentialTag{},
resultId: "aws/admin/foo_bar",
}, {
tag: "caasmodel-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expectKind: names.CAASModelTagKind,
expectType: names.CAASModelTag{},
resultId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
}, {
tag: "caasmodel-/",
expectKind: names.CAASModelTagKind,
expectType: names.CAASModelTag{},
resultErr: `"caasmodel-/" is not a valid caasmodel tag`,
}, {
tag: "controller-f47ac10b-58cc-4372-a567-0e02b2c3d479",
expectKind: names.ControllerTagKind,
expectType: names.ControllerTag{},
resultId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
}, {
tag: "controller-123",
expectKind: names.ControllerAgentTagKind,
expectType: names.ControllerAgentTag{},
resultId: "123",
}, {
tag: "controller-invalid",
expectKind: names.ControllerTagKind,
expectType: names.ControllerTag{},
resultErr: `"controller-invalid" is not a valid controller tag`,
}}
var makeTag = map[string]func(string) names.Tag{
names.MachineTagKind: func(tag string) names.Tag { return names.NewMachineTag(tag) },
names.UnitTagKind: func(tag string) names.Tag { return names.NewUnitTag(tag) },
names.ApplicationTagKind: func(tag string) names.Tag { return names.NewApplicationTag(tag) },
names.ApplicationOfferTagKind: func(tag string) names.Tag { return names.NewApplicationOfferTag(tag) },
names.RelationTagKind: func(tag string) names.Tag { return names.NewRelationTag(tag) },
names.EnvironTagKind: func(tag string) names.Tag { return names.NewEnvironTag(tag) },
names.ModelTagKind: func(tag string) names.Tag { return names.NewModelTag(tag) },
names.UserTagKind: func(tag string) names.Tag { return names.NewUserTag(tag) },
names.ActionTagKind: func(tag string) names.Tag { return names.NewActionTag(tag) },
names.OperationTagKind: func(tag string) names.Tag { return names.NewOperationTag(tag) },
names.VolumeTagKind: func(tag string) names.Tag { return names.NewVolumeTag(tag) },
names.FilesystemTagKind: func(tag string) names.Tag { return names.NewFilesystemTag(tag) },
names.StorageTagKind: func(tag string) names.Tag { return names.NewStorageTag(tag) },
names.IPAddressTagKind: func(tag string) names.Tag { return names.NewIPAddressTag(tag) },
names.SubnetTagKind: func(tag string) names.Tag { return names.NewSubnetTag(tag) },
names.SpaceTagKind: func(tag string) names.Tag { return names.NewSpaceTag(tag) },
names.CloudTagKind: func(tag string) names.Tag { return names.NewCloudTag(tag) },
names.CloudCredentialTagKind: func(tag string) names.Tag { return names.NewCloudCredentialTag(tag) },
names.CAASModelTagKind: func(tag string) names.Tag { return names.NewCAASModelTag(tag) },
names.ControllerTagKind: func(tag string) names.Tag {
_, err := strconv.Atoi(tag)
if err == nil {
return names.NewControllerAgentTag(tag)
}
return names.NewControllerTag(tag)
},
}
func (*tagSuite) TestParseTag(c *gc.C) {
for i, test := range parseTagTests {
c.Logf("test %d: %q expectKind %q", i, test.tag, test.expectKind)
tag, err := names.ParseTag(test.tag)
if test.resultErr != "" {
c.Assert(err, gc.ErrorMatches, test.resultErr)
c.Assert(tag, gc.IsNil)
// If the tag has a valid kind which matches the
// expected kind, test that using an empty
// expectKind does not change the error message.
if tagKind, err := names.TagKind(test.tag); err == nil && tagKind == test.expectKind {
tag, err := names.ParseTag(test.tag)
c.Assert(err, gc.ErrorMatches, test.resultErr)
c.Assert(tag, gc.IsNil)
}
} else {
c.Assert(err, gc.IsNil)
kind, id := tag.Kind(), tag.Id()
c.Assert(err, gc.IsNil)
c.Assert(id, gc.Equals, test.resultId)
if test.expectKind != "" {
c.Assert(kind, gc.Equals, test.expectKind)
} else {
expectKind, err := names.TagKind(test.tag)
c.Assert(err, gc.IsNil)
c.Assert(kind, gc.Equals, expectKind) // will be removed in the next branch
c.Assert(tag, gc.FitsTypeOf, test.expectType)
}
// Check that it's reversible.
if f := makeTag[kind]; f != nil {
reversed := f(id).String()
c.Assert(reversed, gc.Equals, test.tag)
}
// Check that it parses ok without an expectKind.
tag, err := names.ParseTag(test.tag)
c.Assert(err, gc.IsNil)
c.Assert(tag, gc.FitsTypeOf, test.expectType)
c.Assert(tag.Kind(), gc.Equals, test.expectKind) // will be removed in the next branch
c.Assert(tag.Id(), gc.Equals, id)
}
}
}
func (*tagSuite) TestReadableString(c *gc.C) {
var readableStringTests = []struct {
tag names.Tag
result string
}{{
tag: nil,
result: "",
}, {
tag: names.NewMachineTag("0"),
result: "machine 0",
}, {
tag: names.NewUnitTag("wordpress/2"),
result: "unit wordpress/2",
}}
for i, test := range readableStringTests {
c.Logf("test %d: expected result %q", i, test.result)
resultStr := names.ReadableString(test.tag)
c.Assert(resultStr, gc.Equals, test.result)
}
}
names-4.0.0/unit.go 0000664 0000000 0000000 00000010170 13650476565 0014144 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"fmt"
"hash/crc32"
"regexp"
"strconv"
"strings"
"github.com/juju/errors"
)
const UnitTagKind = "unit"
// minShortenedLength defines minimum size of shortened unit tag, other things depend
// on that value so change it carefully.
const minShortenedLength = 21
// UnitSnippet defines the regexp for a valid Unit Id.
const UnitSnippet = "(" + ApplicationSnippet + ")/(" + NumberSnippet + ")"
var validUnit = regexp.MustCompile("^" + UnitSnippet + "$")
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) }
// Number returns the unit number from the tag, effectively the NumberSnippet from the
// validUnit regular expression.
func (t UnitTag) Number() int {
if i := strings.LastIndex(t.name, "-"); i > 0 {
num, _ := strconv.Atoi(t.name[i+1:])
return num
}
return 0
}
// 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
}
// UnitNumber returns the number of the unit within the
// application. It returns an error if unitName is not a valid unit
// name.
func UnitNumber(unitName string) (int, error) {
s := validUnit.FindStringSubmatch(unitName)
if s == nil {
return 0, fmt.Errorf("%q is not a valid unit name", unitName)
}
num, err := strconv.Atoi(s[2])
if err != nil {
// Shouldn't happen, the regexp checks for digits.
return 0, errors.Trace(err)
}
return num, 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
}
// ShortenedString returns the length-limited string for the tag.
// It can be used in places where there are strict length requirements, e.g. for
// a service name. It uses a hash so the resulting name should be unique.
// It will panic if maxLength is less than minShortenedLength.
func (t UnitTag) ShortenedString(maxLength int) (string, error) {
if maxLength < minShortenedLength {
return "", fmt.Errorf("max length must be at least %d, not %d", minShortenedLength, maxLength)
}
i := strings.LastIndex(t.name, "-")
if i <= 0 {
return "", fmt.Errorf("invalid tag %s", t.name)
}
// To keep unit 'name' the same on all units we reserve 4 chars for ID.
name, id := t.name[:i], t.name[i+1:]
idLen := len(id)
if idLen < 4 {
idLen = 4
}
var hashString string
// 8 for hash, 2 for two dashes
maxNameLength := maxLength - idLen - len(UnitTagKind) - 8 - 2
if len(name) > maxNameLength {
hash := crc32.Checksum([]byte(name), crc32.IEEETable)
hashString = fmt.Sprintf("%0.8x", hash)
name = name[:maxNameLength]
}
return "unit-" + name + hashString + "-" + id, nil
}
names-4.0.0/unit_test.go 0000664 0000000 0000000 00000011532 13650476565 0015206 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"fmt"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type unitSuite struct{}
var _ = gc.Suite(&unitSuite{})
func (s *unitSuite) TestUnitTag(c *gc.C) {
c.Assert(names.NewUnitTag("wordpress/2").String(), gc.Equals, "unit-wordpress-2")
c.Assert(names.NewUnitTag("foo-bar/2").String(), gc.Equals, "unit-foo-bar-2")
}
var unitNameTests = []struct {
pattern string
valid bool
application string
number int
}{
{pattern: "wordpress/42", valid: true, application: "wordpress", number: 42},
{pattern: "rabbitmq-server/123", valid: true, application: "rabbitmq-server", number: 123},
{pattern: "foo", valid: false},
{pattern: "foo/", valid: false},
{pattern: "bar/foo", valid: false},
{pattern: "20/20", valid: false},
{pattern: "foo-55", valid: false},
{pattern: "foo-bar/123", valid: true, application: "foo-bar", number: 123},
{pattern: "foo-bar/123/", valid: false},
{pattern: "foo-bar/123-not", valid: false},
}
func (s *unitSuite) TestUnitNameFormats(c *gc.C) {
for i, test := range unitNameTests {
c.Logf("test %d: %q", i, test.pattern)
c.Assert(names.IsValidUnit(test.pattern), gc.Equals, test.valid)
}
}
func (s *unitSuite) TestInvalidUnitTagFormats(c *gc.C) {
for i, test := range unitNameTests {
if !test.valid {
c.Logf("test %d: %q", i, test.pattern)
expect := fmt.Sprintf("%q is not a valid unit name", test.pattern)
testUnitTag := func() { names.NewUnitTag(test.pattern) }
c.Assert(testUnitTag, gc.PanicMatches, expect)
}
}
}
func (s *unitSuite) TestNumber(c *gc.C) {
u := names.UnitTag{}
c.Assert(u.Number(), gc.Equals, 0)
u = names.NewUnitTag("foo-t4/5")
c.Assert(u.Number(), gc.Equals, 5)
}
func (s *applicationSuite) TestUnitApplication(c *gc.C) {
for i, test := range unitNameTests {
c.Logf("test %d: %q", i, test.pattern)
if !test.valid {
expect := fmt.Sprintf("%q is not a valid unit name", test.pattern)
_, err := names.UnitApplication(test.pattern)
c.Assert(err, gc.ErrorMatches, expect)
} else {
result, err := names.UnitApplication(test.pattern)
c.Assert(err, gc.IsNil)
c.Assert(result, gc.Equals, test.application)
}
}
}
func (s *applicationSuite) TestUnitNumber(c *gc.C) {
for i, test := range unitNameTests {
c.Logf("test %d: %q", i, test.pattern)
if !test.valid {
expect := fmt.Sprintf("%q is not a valid unit name", test.pattern)
_, err := names.UnitNumber(test.pattern)
c.Assert(err, gc.ErrorMatches, expect)
} else {
result, err := names.UnitNumber(test.pattern)
c.Assert(err, gc.IsNil)
c.Assert(result, gc.Equals, test.number)
}
}
}
var parseUnitTagTests = []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "unit-dave-0",
expected: names.NewUnitTag("dave/0"),
}, {
tag: "unit-foo-bar-0",
expected: names.NewUnitTag("foo-bar/0"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "unit-dave",
err: names.InvalidTagError("unit-dave", names.UnitTagKind), // not a valid unit name either
}, {
tag: "application-dave",
err: names.InvalidTagError("application-dave", names.UnitTagKind),
}}
func (s *unitSuite) TestParseUnitTag(c *gc.C) {
for i, t := range parseUnitTagTests {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseUnitTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
var unitTagShortenedStringTests = []struct {
unitName string
maxLength int
errorOutput string
expectedName string
}{{
unitName: "foobar/0",
maxLength: 15,
errorOutput: "max length must be at least 21, not 15",
}, {
unitName: "foobar/0",
maxLength: 30,
expectedName: "unit-foobar-0",
}, {
unitName: "veryveryverylonglongone/0",
maxLength: 25,
expectedName: "unit-veryverf4fa59c6-0",
}, {
unitName: "veryveryverylonglongone/20",
maxLength: 25,
expectedName: "unit-veryverf4fa59c6-20",
}, {
unitName: "veryveryverylonglongone/300",
maxLength: 25,
expectedName: "unit-veryverf4fa59c6-300",
}, {
unitName: "veryveryverylonglongone/4000",
maxLength: 25,
expectedName: "unit-veryverf4fa59c6-4000",
}, {
unitName: "veryveryverylonglongone/50000",
maxLength: 25,
expectedName: "unit-veryvef4fa59c6-50000",
}}
func (s *unitSuite) TestUnitTagShortenedString(c *gc.C) {
for i, t := range unitTagShortenedStringTests {
c.Logf("test %d: %s %d", i, t.unitName, t.maxLength)
tag := names.NewUnitTag(t.unitName)
tagString, err := tag.ShortenedString(t.maxLength)
if t.errorOutput != "" {
c.Check(tagString, gc.Equals, "")
c.Check(err, gc.ErrorMatches, t.errorOutput)
} else {
c.Check(tagString, gc.Equals, t.expectedName)
c.Check(err, gc.IsNil)
}
}
}
names-4.0.0/user.go 0000664 0000000 0000000 00000007222 13650476565 0014147 0 ustar 00root root 0000000 0000000 // 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%s)(?:@(?P%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
}
names-4.0.0/user_test.go 0000664 0000000 0000000 00000012525 13650476565 0015210 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"fmt"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type userSuite struct{}
var _ = gc.Suite(&userSuite{})
func (s *userSuite) TestUserTag(c *gc.C) {
for i, t := range []struct {
input string
string string
name string
domain string
username string
}{
{
input: "bob",
string: "user-bob",
name: "bob",
username: "bob",
}, {
input: "bob@local",
string: "user-bob",
name: "bob",
username: "bob",
}, {
input: "bob@foo",
string: "user-bob@foo",
name: "bob",
domain: "foo",
username: "bob@foo",
},
} {
c.Logf("test %d: %s", i, t.input)
userTag := names.NewUserTag(t.input)
c.Check(userTag.String(), gc.Equals, t.string)
c.Check(userTag.Id(), gc.Equals, t.username)
c.Check(userTag.Name(), gc.Equals, t.name)
c.Check(userTag.Domain(), gc.Equals, t.domain)
c.Check(userTag.IsLocal(), gc.Equals, t.domain == "")
}
}
var withDomainTests = []struct {
id string
domain string
expectId string
}{{
id: "bob",
domain: names.LocalUserDomain,
expectId: "bob",
}, {
id: "bob@local",
domain: "foo",
expectId: "bob@foo",
}, {
id: "bob@local",
domain: "",
}, {
id: "bob@foo",
domain: names.LocalUserDomain,
expectId: "bob",
}, {
id: "bob",
domain: "@foo",
}}
func (s *userSuite) TestWithDomain(c *gc.C) {
for i, test := range withDomainTests {
c.Logf("test %d: id %q; domain %q", i, test.id, test.domain)
tag := names.NewUserTag(test.id)
if test.expectId == "" {
c.Assert(func() {
tag.WithDomain(test.domain)
}, gc.PanicMatches, fmt.Sprintf("invalid user domain %q", test.domain))
} else {
c.Assert(tag.WithDomain(test.domain).Id(), gc.Equals, test.expectId)
}
}
}
func (s *userSuite) TestIsValidUser(c *gc.C) {
for i, t := range []struct {
string string
expect bool
}{
{"", false},
{"bob", true},
{"Bob", true},
{"bOB", true},
{"b^b", false},
{"bob1", true},
{"bob-1", true},
{"bob+1", true},
{"bob+", false},
{"+bob", false},
{"bob.1", true},
{"1bob", true},
{"1-bob", true},
{"1+bob", true},
{"1.bob", true},
{"jim.bob+99-1.", false},
{"a", false},
{"0foo", true},
{"foo bar", false},
{"bar{}", false},
{"bar+foo", true},
{"bar_foo", false},
{"bar!", false},
{"bar^", false},
{"bar*", false},
{"foo=bar", false},
{"foo?", false},
{"[bar]", false},
{"'foo'", false},
{"%bar", false},
{"&bar", false},
{"#1foo", false},
{"bar@ram.u", true},
{"bar@", false},
{"@local", false},
{"not/valid", false},
} {
c.Logf("test %d: %s", i, t.string)
c.Assert(names.IsValidUser(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
}
}
func (s *userSuite) TestIsValidUserNameOrDomain(c *gc.C) {
for i, t := range []struct {
string string
expect bool
}{
{"", false},
{"bob", true},
{"Bob", true},
{"bOB", true},
{"b^b", false},
{"bob1", true},
{"bob-1", true},
{"bob+1", true},
{"bob+", false},
{"+bob", false},
{"bob.1", true},
{"1bob", true},
{"1-bob", true},
{"1+bob", true},
{"1.bob", true},
{"jim.bob+99-1.", false},
{"a", false},
{"0foo", true},
{"foo bar", false},
{"bar{}", false},
{"bar+foo", true},
{"bar_foo", false},
{"bar!", false},
{"bar^", false},
{"bar*", false},
{"foo=bar", false},
{"foo?", false},
{"[bar]", false},
{"'foo'", false},
{"%bar", false},
{"&bar", false},
{"#1foo", false},
{"bar@ram.u", false},
{"bar@local", false},
{"bar@ubuntuone", false},
{"bar@", false},
{"@local", false},
{"not/valid", false},
} {
c.Logf("test %d: %s", i, t.string)
c.Assert(names.IsValidUserName(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
c.Assert(names.IsValidUserDomain(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
}
}
func (s *userSuite) TestParseUserTag(c *gc.C) {
for i, t := range []struct {
tag string
expected names.Tag
err error
}{{
tag: "",
err: names.InvalidTagError("", ""),
}, {
tag: "user-dave",
expected: names.NewUserTag("dave"),
}, {
tag: "user-dave@local",
expected: names.NewUserTag("dave@local"),
}, {
tag: "user-dave@foobar",
expected: names.NewUserTag("dave@foobar"),
}, {
tag: "dave",
err: names.InvalidTagError("dave", ""),
}, {
tag: "unit-dave",
err: names.InvalidTagError("unit-dave", names.UnitTagKind), // not a valid unit name either
}, {
tag: "application-dave",
err: names.InvalidTagError("application-dave", names.UserTagKind),
}} {
c.Logf("test %d: %s", i, t.tag)
got, err := names.ParseUserTag(t.tag)
if err != nil || t.err != nil {
c.Check(err, gc.DeepEquals, t.err)
continue
}
c.Check(got, gc.FitsTypeOf, t.expected)
c.Check(got, gc.Equals, t.expected)
}
}
func (s *userSuite) TestNewLocalUserTag(c *gc.C) {
user := names.NewLocalUserTag("bob")
c.Assert(user.Name(), gc.Equals, "bob")
c.Assert(user.Domain(), gc.Equals, "")
c.Assert(user.IsLocal(), gc.Equals, true)
c.Assert(user.String(), gc.Equals, "user-bob")
c.Assert(func() { names.NewLocalUserTag("bob@local") }, gc.PanicMatches, `invalid user name "bob@local"`)
c.Assert(func() { names.NewLocalUserTag("") }, gc.PanicMatches, `invalid user name ""`)
c.Assert(func() { names.NewLocalUserTag("!@#") }, gc.PanicMatches, `invalid user name "!@#"`)
}
names-4.0.0/volume.go 0000664 0000000 0000000 00000004473 13650476565 0014505 0 ustar 00root root 0000000 0000000 // 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 + "|" + UnitSnippet + ")/)?" + 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 filesystemOrVolumeTagSuffixToId(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
}
id = id[:pos]
if !IsValidMachine(id) {
return MachineTag{}, false
}
return NewMachineTag(id), true
}
// VolumeUnit returns the unit component of the volume
// tag, and a boolean indicating whether or not there is a
// unit component.
func VolumeUnit(tag VolumeTag) (UnitTag, bool) {
id := tag.Id()
pos := strings.LastIndex(id, "/")
if pos == -1 {
return UnitTag{}, false
}
id = id[:pos]
if !IsValidUnit(id) {
return UnitTag{}, false
}
return NewUnitTag(id[:pos]), true
}
func tagFromVolumeId(id string) (VolumeTag, bool) {
if !IsValidVolume(id) {
return VolumeTag{}, false
}
id = strings.Replace(id, "/", "-", -1)
return VolumeTag{id}, true
}
names-4.0.0/volume_test.go 0000664 0000000 0000000 00000006616 13650476565 0015545 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names_test
import (
"fmt"
gc "gopkg.in/check.v1"
"github.com/juju/names/v4"
)
type volumeSuite struct{}
var _ = gc.Suite(&volumeSuite{})
func (s *volumeSuite) TestVolumeTag(c *gc.C) {
c.Assert(names.NewVolumeTag("1").String(), gc.Equals, "volume-1")
c.Assert(names.NewVolumeTag("0/lxc/0/0").String(), gc.Equals, "volume-0-lxc-0-0")
c.Assert(names.NewVolumeTag("1/0").String(), gc.Equals, "volume-1-0")
c.Assert(names.NewVolumeTag("some-unit/0/0").String(), gc.Equals, "volume-some-unit-0-0")
}
func (s *volumeSuite) TestVolumeNameValidity(c *gc.C) {
assertVolumeNameValid(c, "0")
assertVolumeNameValid(c, "0/0")
assertVolumeNameValid(c, "0/lxc/0/0")
assertVolumeNameValid(c, "1000")
assertVolumeNameValid(c, "some-unit/0/0")
assertVolumeNameValid(c, "some-unit/0/1000")
assertVolumeNameInvalid(c, "-1")
assertVolumeNameInvalid(c, "")
assertVolumeNameInvalid(c, "one")
assertVolumeNameInvalid(c, "#")
assertVolumeNameInvalid(c, "0/0/0") // 0/0 is not a valid machine or unit ID
}
func (s *volumeSuite) TestParseVolumeTag(c *gc.C) {
assertParseVolumeTag(c, "volume-0", names.NewVolumeTag("0"))
assertParseVolumeTag(c, "volume-0-0", names.NewVolumeTag("0/0"))
assertParseVolumeTag(c, "volume-88", names.NewVolumeTag("88"))
assertParseVolumeTag(c, "volume-0-lxc-0-88", names.NewVolumeTag("0/lxc/0/88"))
assertParseVolumeTag(c, "volume-some-unit-0-88", names.NewVolumeTag("some-unit/0/88"))
assertParseVolumeTagInvalid(c, "", names.InvalidTagError("", ""))
assertParseVolumeTagInvalid(c, "one", names.InvalidTagError("one", ""))
assertParseVolumeTagInvalid(c, "volume-", names.InvalidTagError("volume-", names.VolumeTagKind))
assertParseVolumeTagInvalid(c, "machine-0", names.InvalidTagError("machine-0", names.VolumeTagKind))
}
func (s *volumeSuite) TestVolumeMachine(c *gc.C) {
assertVolumeMachine(c, "0/0", names.NewMachineTag("0"))
assertVolumeMachine(c, "0/lxc/0/0", names.NewMachineTag("0/lxc/0"))
assertVolumeNoMachine(c, "0")
assertVolumeNoMachine(c, "some-unit/0/0")
}
func assertVolumeMachine(c *gc.C, id string, expect names.MachineTag) {
t, ok := names.VolumeMachine(names.NewVolumeTag(id))
c.Assert(ok, gc.Equals, true)
c.Assert(t, gc.Equals, expect)
}
func assertVolumeNoMachine(c *gc.C, id string) {
_, ok := names.VolumeMachine(names.NewVolumeTag(id))
c.Assert(ok, gc.Equals, false)
}
func (s *volumeSuite) TestVolumeUnit(c *gc.C) {
t, ok := names.VolumeUnit(names.NewVolumeTag("some-unit/0/0"))
c.Assert(ok, gc.Equals, true)
c.Assert(t, gc.Equals, names.NewUnitTag("some-unit/0"))
_, ok = names.VolumeUnit(names.NewVolumeTag("0/0"))
c.Assert(ok, gc.Equals, false)
}
func assertVolumeNameValid(c *gc.C, name string) {
c.Assert(names.IsValidVolume(name), gc.Equals, true)
names.NewVolumeTag(name)
}
func assertVolumeNameInvalid(c *gc.C, name string) {
c.Assert(names.IsValidVolume(name), gc.Equals, false)
testVolumeTag := func() { names.NewVolumeTag(name) }
expect := fmt.Sprintf("%q is not a valid volume ID", name)
c.Assert(testVolumeTag, gc.PanicMatches, expect)
}
func assertParseVolumeTag(c *gc.C, tag string, expect names.VolumeTag) {
t, err := names.ParseVolumeTag(tag)
c.Assert(err, gc.IsNil)
c.Assert(t, gc.Equals, expect)
}
func assertParseVolumeTagInvalid(c *gc.C, tag string, expect error) {
_, err := names.ParseVolumeTag(tag)
c.Assert(err, gc.ErrorMatches, expect.Error())
}