Skip to content

Commit

Permalink
add role to parse tag (#1439)
Browse files Browse the repository at this point in the history
* add role to parse tag

* add parsetag tests
  • Loading branch information
SimoneDutto authored Nov 18, 2024
1 parent 33003cf commit 428222b
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pkg/names/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ func ParseTag(tag string) (names.Tag, error) {
return nil, invalidTagError(tag, kind)
}
return NewGroupTag(id), nil
case RoleTagKind:
if !IsValidRoleId(id) {
return nil, invalidTagError(tag, kind)
}
return NewRoleTag(id), nil
case ServiceAccountTagKind:
if !IsValidServiceAccountId(id) {
return nil, invalidTagError(tag, kind)
Expand Down
57 changes: 57 additions & 0 deletions pkg/names/names_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2024 Canonical.
package names_test

import (
"fmt"
"testing"

qt "github.com/frankban/quicktest"
"github.com/google/uuid"
jujunames "github.com/juju/names/v5"

"github.com/canonical/jimm/v3/pkg/names"
)

func TestParseTag(t *testing.T) {
c := qt.New(t)
uuid := uuid.NewString()
tests := []struct {
tagString string
expectedTag jujunames.Tag
expectedValid bool
}{
{
tagString: fmt.Sprintf("group-%s", uuid),
expectedTag: names.NewGroupTag(uuid),
expectedValid: true,
},
{
tagString: fmt.Sprintf("role-%s", uuid),
expectedTag: names.NewRoleTag(uuid),
expectedValid: true,
},
{
tagString: fmt.Sprintf("serviceaccount-%s@serviceaccount", uuid),
expectedTag: names.NewServiceAccountTag(fmt.Sprintf("%s@serviceaccount", uuid)),
expectedValid: true,
},
{
tagString: fmt.Sprintf("not-exisintg-%s@serviceaccount", uuid),
expectedTag: names.NewServiceAccountTag(fmt.Sprintf("%s@serviceaccount", uuid)),
expectedValid: false,
},
{
tagString: "group1",
expectedValid: false,
},
}
for _, test := range tests {
tag, err := names.ParseTag(test.tagString)
if test.expectedValid {
c.Assert(tag, qt.Equals, test.expectedTag)
} else {
c.Assert(err, qt.IsNotNil)
}

}
}
74 changes: 74 additions & 0 deletions pkg/names/role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2024 Canonical.

package names

import (
"fmt"
"regexp"
)

const (
RoleTagKind = "role"
)

var (
validRoleName = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9._-]+[a-zA-Z0-9]$")
validRoleIdSnippet = `^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}((#|\z)[a-z]+)?$`
validRoleId = regexp.MustCompile(validRoleIdSnippet)
)

// RoleTag represents a role.
// Implements juju names.Tag
type RoleTag struct {
id string
}

// Id implements juju names.Tag
func (t RoleTag) Id() string { return t.id }

// Kind implements juju names.Tag
func (t RoleTag) Kind() string { return RoleTagKind }

// String implements juju names.Tag
func (t RoleTag) String() string { return RoleTagKind + "-" + t.Id() }

// NewRoleTag creates a valid RoleTag if it is possible to parse
// the provided tag.
func NewRoleTag(roleId string) RoleTag {
id := validRoleId.FindString(roleId)

if id == "" {
panic(fmt.Sprintf("invalid role tag %q", roleId))
}

return RoleTag{id: id}
}

// ParseRoleTag parses a user role string.
func ParseRoleTag(tag string) (RoleTag, error) {
t, err := ParseTag(tag)
if err != nil {
return RoleTag{}, err
}
gt, ok := t.(RoleTag)
if !ok {
return RoleTag{}, invalidTagError(tag, RoleTagKind)
}
return gt, nil
}

// IsValidRoleId verifies the id of the tag is valid according to a regex internally.
func IsValidRoleId(id string) bool {
return validRoleId.MatchString(id)
}

// IsValidRoleName verifies the name of the role is valid
// according to the role name regexp.
// A valid role name:
// - starts with an upper- or lower-case character
// - ends with an upper- or lower-case character or a number
// - may contain ., _, or -
// - must at least 6 characters long.
func IsValidRoleName(name string) bool {
return validRoleName.MatchString(name)
}
149 changes: 149 additions & 0 deletions pkg/names/role_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2024 Canonical.

package names_test

import (
"fmt"
"testing"

qt "github.com/frankban/quicktest"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"

"github.com/canonical/jimm/v3/pkg/names"
)

func TestParseRoleTag(t *testing.T) {
c := qt.New(t)
uuid := uuid.NewString()

tests := []struct {
tag string
expectedError string
expectedTag string
expectedId string
}{{
tag: fmt.Sprintf("role-%s", uuid),
expectedId: uuid,
expectedTag: fmt.Sprintf("role-%s", uuid),
}, {
tag: fmt.Sprintf("role-%s#member", uuid),
expectedId: fmt.Sprintf("%s#member", uuid),
expectedTag: fmt.Sprintf("role-%s#member", uuid),
}, {
tag: "pokemon-diglett",
expectedError: "\"pokemon-diglett\" is not a valid tag",
}}

for i, test := range tests {
test := test
c.Run(fmt.Sprintf("test case %d", i), func(c *qt.C) {
gt, err := names.ParseRoleTag(test.tag)
if test.expectedError == "" {
c.Assert(err, qt.IsNil)
c.Assert(gt.Id(), qt.Equals, test.expectedId)
c.Assert(gt.Kind(), qt.Equals, "role")
c.Assert(gt.String(), qt.Equals, test.expectedTag)
} else {
c.Assert(err, qt.ErrorMatches, test.expectedError)
}
})
}
}

func TestParseRoleTagDeniesBadKinds(t *testing.T) {
_, err := names.ParseRoleTag("pokemon-diglett")
assert.Error(t, err)
assert.ErrorContains(t, err, "\"pokemon-diglett\" is not a valid tag")
}

func TestIsValidRoleId(t *testing.T) {
uuid := uuid.NewString()
tests := []struct {
id string
expectedValid bool
}{{
id: uuid,
expectedValid: true,
}, {
id: fmt.Sprintf("%s#member", uuid),
expectedValid: true,
}, {
id: fmt.Sprintf("%s#member#member", uuid),
expectedValid: false,
}, {
id: fmt.Sprintf("%s#", uuid),
expectedValid: false,
}, {
id: "0#member",
expectedValid: false,
}, {
id: "0",
expectedValid: false,
}}
for _, test := range tests {
assert.Equal(t, names.IsValidRoleId(test.id), test.expectedValid)
}
}

func TestIsValidRoleName(t *testing.T) {
tests := []struct {
name string
expectedValidity bool
}{{
name: "role-1",
expectedValidity: true,
}, {
name: "Role1",
expectedValidity: true,
}, {
name: "1role",
expectedValidity: false,
}, {
name: ".role",
expectedValidity: false,
}, {
name: "role.A",
expectedValidity: true,
}, {
name: "role.A1",
expectedValidity: true,
}, {
name: "role_test_a_1",
expectedValidity: true,
}, {
name: "role+a",
expectedValidity: false,
}, {
name: "Test.Role.1.A",
expectedValidity: true,
}, {
name: "",
expectedValidity: false,
}, {
name: "no",
expectedValidity: false,
}, {
name: "foo",
expectedValidity: true,
}, {
name: "short",
expectedValidity: true,
}, {
name: "short1",
expectedValidity: true,
}, {
name: "short_",
expectedValidity: false,
}, {
name: "role.A#member",
expectedValidity: false,
}}

for _, test := range tests {
t.Logf("testing role name %q, expected validity %v", test.name, test.expectedValidity)

valid := names.IsValidRoleName(test.name)
assert.Equal(t, valid, test.expectedValidity)
}
}

0 comments on commit 428222b

Please sign in to comment.