pax_global_header00006660000000000000000000000064136504765650014533gustar00rootroot0000000000000052 comment=9a8294627524158f3e39a65fe4d751293659e5a9 names-4.0.0/000077500000000000000000000000001365047656500126375ustar00rootroot00000000000000names-4.0.0/.gitignore000066400000000000000000000000101365047656500146160ustar00rootroot00000000000000vendor/ names-4.0.0/LICENSE000066400000000000000000000215011365047656500136430ustar00rootroot00000000000000All 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/Makefile000066400000000000000000000007151365047656500143020ustar00rootroot00000000000000# # 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.md000066400000000000000000000001271365047656500141160ustar00rootroot00000000000000juju/names ============ This package provides helpers for handling Juju entity names. names-4.0.0/action.go000066400000000000000000000046461365047656500144550ustar00rootroot00000000000000// 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.go000066400000000000000000000046001365047656500155020ustar00rootroot00000000000000// 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.go000066400000000000000000000024701365047656500154740ustar00rootroot00000000000000// 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.go000066400000000000000000000046701365047656500165370ustar00rootroot00000000000000// 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.go000066400000000000000000000026451365047656500165220ustar00rootroot00000000000000// 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.go000066400000000000000000000023451365047656500175560ustar00rootroot00000000000000// 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.go000066400000000000000000000024001365047656500151120ustar00rootroot00000000000000// 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.go000066400000000000000000000033711365047656500161610ustar00rootroot00000000000000// 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.go000066400000000000000000000057501365047656500142670ustar00rootroot00000000000000// 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.go000066400000000000000000000060201365047656500153150ustar00rootroot00000000000000// 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.go000066400000000000000000000022371365047656500143000ustar00rootroot00000000000000// 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.go000066400000000000000000000043001365047656500153300ustar00rootroot00000000000000// 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.go000066400000000000000000000062131365047656500163310ustar00rootroot00000000000000// 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.go000066400000000000000000000107761365047656500174010ustar00rootroot00000000000000// 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.go000066400000000000000000000030751365047656500153560ustar00rootroot00000000000000// 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.go000066400000000000000000000045631365047656500164200ustar00rootroot00000000000000// 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.go000066400000000000000000000033351365047656500163740ustar00rootroot00000000000000// 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.go000066400000000000000000000032051365047656500174270ustar00rootroot00000000000000// 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.go000066400000000000000000000020541365047656500146470ustar00rootroot00000000000000// 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.go000066400000000000000000000022051365047656500157040ustar00rootroot00000000000000// 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.go000066400000000000000000000023121365047656500160600ustar00rootroot00000000000000// 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.go000066400000000000000000000005521365047656500156620ustar00rootroot00000000000000// 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.go000066400000000000000000000002231365047656500155430ustar00rootroot00000000000000// Copyright 2014 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package names var InvalidTagError = invalidTagError names-4.0.0/filesystem.go000066400000000000000000000056621365047656500153630ustar00rootroot00000000000000// 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.go000066400000000000000000000072721365047656500164210ustar00rootroot00000000000000// 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.mod000066400000000000000000000014411365047656500137450ustar00rootroot00000000000000module 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.sum000066400000000000000000000060751365047656500140020ustar00rootroot00000000000000github.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.go000066400000000000000000000022751365047656500151520ustar00rootroot00000000000000// 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.go000066400000000000000000000033351365047656500162070ustar00rootroot00000000000000// 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.go000066400000000000000000000044451365047656500146010ustar00rootroot00000000000000// 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.go000066400000000000000000000057611365047656500156420ustar00rootroot00000000000000// 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.go000066400000000000000000000026131365047656500142700ustar00rootroot00000000000000// 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.go000066400000000000000000000031701365047656500153260ustar00rootroot00000000000000// 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.go000066400000000000000000000026351365047656500151740ustar00rootroot00000000000000// 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.go000066400000000000000000000023231365047656500162250ustar00rootroot00000000000000// 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.go000066400000000000000000000033331365047656500146210ustar00rootroot00000000000000// 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.go000066400000000000000000000046171365047656500156660ustar00rootroot00000000000000// 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.go000066400000000000000000000042061365047656500150050ustar00rootroot00000000000000// 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.go000066400000000000000000000052231365047656500160440ustar00rootroot00000000000000// 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.go000066400000000000000000000070401365047656500137620ustar00rootroot00000000000000// 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.go000066400000000000000000000107511365047656500150240ustar00rootroot00000000000000// 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.go000066400000000000000000000013321365047656500157060ustar00rootroot00000000000000package 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.go000066400000000000000000000022071365047656500142620ustar00rootroot00000000000000// 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.go000066400000000000000000000037141365047656500153250ustar00rootroot00000000000000// 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.go000066400000000000000000000044241365047656500146360ustar00rootroot00000000000000// 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.go000066400000000000000000000047631365047656500157030ustar00rootroot00000000000000// 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.go000066400000000000000000000021311365047656500144630ustar00rootroot00000000000000// 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.go000066400000000000000000000030361365047656500155270ustar00rootroot00000000000000// 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.go000066400000000000000000000153161365047656500137470ustar00rootroot00000000000000// 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.go000066400000000000000000000310001365047656500147720ustar00rootroot00000000000000// 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.go000066400000000000000000000101701365047656500141440ustar00rootroot00000000000000// 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.go000066400000000000000000000115321365047656500152060ustar00rootroot00000000000000// 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.go000066400000000000000000000072221365047656500141470ustar00rootroot00000000000000// 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.go000066400000000000000000000125251365047656500152100ustar00rootroot00000000000000// 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.go000066400000000000000000000044731365047656500145050ustar00rootroot00000000000000// 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.go000066400000000000000000000066161365047656500155450ustar00rootroot00000000000000// 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()) }