Skip to content

Commit

Permalink
Merge pull request #393 from hashicorp/sebasslash/filter-teams-org-me…
Browse files Browse the repository at this point in the history
…mbers

Filter teams by name, org members by email
  • Loading branch information
sebasslash committed May 5, 2022
2 parents 4335e10 + 1f33aab commit 836895d
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 3 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
@@ -1,10 +1,13 @@
# v1.2.0 (Unreleased)

## Enhancements
* Adds support for reading current state version outputs to StateVersionOutputs, which can be useful for reading outputs when users don't have the necessary permissions to read the entire state.
* Adds Variable Set methods for `ApplyToWorkspaces` and `RemoveFromWorkspaces` [#375](https://github.com/hashicorp/go-tfe/pull/375)
* Adds support for reading current state version outputs to StateVersionOutputs, which can be useful for reading outputs when users don't have the necessary permissions to read the entire state by @brandonc [#370](https://github.com/hashicorp/go-tfe/pull/370)
* Adds Variable Set methods for `ApplyToWorkspaces` and `RemoveFromWorkspaces` by @byronwolfman [#375](https://github.com/hashicorp/go-tfe/pull/375)
* Adds `Names` query param field to `TeamListOptions` by @sebasslash [#393](https://github.com/hashicorp/go-tfe/pull/393)
* Adds `Emails` query param field to `OrganizationMembershipListOptions` by @sebasslash [#393](https://github.com/hashicorp/go-tfe/pull/393)
* Adds Run Tasks API support by @glennsarti [#381](https://github.com/hashicorp/go-tfe/pull/381), [#382](https://github.com/hashicorp/go-tfe/pull/382) and [#383](https://github.com/hashicorp/go-tfe/pull/383)


## Bug fixes
* Fixes ignored comment when performing apply, discard, cancel, and force-cancel run actions [#388](https://github.com/hashicorp/go-tfe/pull/388)

Expand Down
4 changes: 4 additions & 0 deletions errors.go
Expand Up @@ -262,4 +262,8 @@ var (
ErrRequiredWorkspacesList = errors.New("no workspaces list provided")

ErrCommentBody = errors.New("comment body is required")

ErrEmptyTeamName = errors.New("team name can not be empty")

ErrInvalidEmail = errors.New("email is invalid")
)
17 changes: 17 additions & 0 deletions organization_membership.go
Expand Up @@ -77,6 +77,9 @@ type OrganizationMembershipListOptions struct {
// Optional: A list of relations to include. See available resources
// https://www.terraform.io/cloud-docs/api-docs/organization-memberships#available-related-resources
Include []OrgMembershipIncludeOpt `url:"include,omitempty"`

// Optional: A list of organization member emails to filter by.
Emails []string `url:"filter[email],omitempty"`
}

// OrganizationMembershipCreateOptions represents the options for creating an organization membership.
Expand Down Expand Up @@ -206,6 +209,10 @@ func (o *OrganizationMembershipListOptions) valid() error {
return err
}

if err := validateOrgMembershipEmailParams(o.Emails); err != nil {
return err
}

return nil
}

Expand All @@ -229,3 +236,13 @@ func validateOrgMembershipIncludeParams(params []OrgMembershipIncludeOpt) error

return nil
}

func validateOrgMembershipEmailParams(emails []string) error {
for _, email := range emails {
if !validEmail(email) {
return ErrInvalidEmail
}
}

return nil
}
29 changes: 29 additions & 0 deletions organization_membership_integration_test.go
Expand Up @@ -72,6 +72,35 @@ func TestOrganizationMembershipsList(t *testing.T) {
assert.Contains(t, ml.Items, memTest2)
})

t.Run("with email filter option", func(t *testing.T) {
_, memTest1Cleanup := createOrganizationMembership(t, client, orgTest)
defer memTest1Cleanup()
memTest2, memTest2Cleanup := createOrganizationMembership(t, client, orgTest)
defer memTest2Cleanup()

memTest3, memTest3Cleanup := createOrganizationMembership(t, client, orgTest)
defer memTest3Cleanup()

memTest2.User = &User{ID: memTest2.User.ID}
memTest3.User = &User{ID: memTest3.User.ID}

ml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{
Emails: []string{memTest2.Email, memTest3.Email},
})
require.NoError(t, err)

assert.Len(t, ml.Items, 2)
assert.Contains(t, ml.Items, memTest2)
assert.Contains(t, ml.Items, memTest3)

t.Run("with invalid email", func(t *testing.T) {
ml, err = client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{
Emails: []string{"foobar"},
})
assert.Equal(t, err, ErrInvalidEmail)
})
})

t.Run("without a valid organization", func(t *testing.T) {
ml, err := client.OrganizationMemberships.List(ctx, badIdentifier, nil)
assert.Nil(t, ml)
Expand Down
17 changes: 17 additions & 0 deletions team.go
Expand Up @@ -88,6 +88,9 @@ type TeamListOptions struct {
// Optional: A list of relations to include.
// https://www.terraform.io/docs/cloud/api/teams.html#available-related-resources
Include []TeamIncludeOpt `url:"include,omitempty"`

// Optional: A list of team names to filter by.
Names []string `url:"filter[names],omitempty"`
}

// TeamCreateOptions represents the options for creating a team.
Expand Down Expand Up @@ -263,6 +266,10 @@ func (o *TeamListOptions) valid() error {
return err
}

if err := validateTeamNames(o.Names); err != nil {
return err
}

return nil
}

Expand All @@ -278,3 +285,13 @@ func validateTeamIncludeParams(params []TeamIncludeOpt) error {

return nil
}

func validateTeamNames(names []string) error {
for _, name := range names {
if name == "" {
return ErrEmptyTeamName
}
}

return nil
}
18 changes: 18 additions & 0 deletions team_integration_test.go
Expand Up @@ -27,6 +27,8 @@ func TestTeamsList(t *testing.T) {
defer tmTest1Cleanup()
tmTest2, tmTest2Cleanup := createTeam(t, client, orgTest)
defer tmTest2Cleanup()
tmTest3, tmTest3Cleanup := createTeam(t, client, orgTest)
defer tmTest3Cleanup()

t.Run("without list options", func(t *testing.T) {
tl, err := client.Teams.List(ctx, orgTest.Name, nil)
Expand Down Expand Up @@ -54,6 +56,22 @@ func TestTeamsList(t *testing.T) {
assert.Empty(t, tl.Items)
assert.Equal(t, 999, tl.CurrentPage)
assert.Equal(t, 2, tl.TotalCount)

tl, err = client.Teams.List(ctx, orgTest.Name, &TeamListOptions{
Names: []string{tmTest2.Name, tmTest3.Name},
})

assert.Equal(t, tl.Items, 2)
assert.Contains(t, tl.Items, tmTest2)
assert.Contains(t, tl.Items, tmTest3)

t.Run("with invalid names query param", func(t *testing.T) {
// should return an error because we've included an empty string
tl, err = client.Teams.List(ctx, orgTest.Name, &TeamListOptions{
Names: []string{tmTest2.Name, ""},
})
assert.Equal(t, err, ErrEmptyTeamName)
})
})

t.Run("without a valid organization", func(t *testing.T) {
Expand Down
6 changes: 5 additions & 1 deletion tfe.go
Expand Up @@ -551,7 +551,7 @@ func encodeQueryParams(v url.Values) string {
sort.Strings(keys)
for _, k := range keys {
vs := v[k]
if len(vs) > 1 && k == _includeQueryParam {
if len(vs) > 1 && validSliceKey(k) {
val := strings.Join(vs, ",")
vs = vs[:0]
vs = append(vs, val)
Expand Down Expand Up @@ -909,3 +909,7 @@ func packContents(path string) (*bytes.Buffer, error) {

return body, nil
}

func validSliceKey(key string) bool {
return key == _includeQueryParam || strings.Contains(key, "filter[")
}
7 changes: 7 additions & 0 deletions validations.go
@@ -1,6 +1,7 @@
package tfe

import (
"net/mail"
"regexp"
)

Expand All @@ -20,3 +21,9 @@ func validString(v *string) bool {
func validStringID(v *string) bool {
return v != nil && reStringID.MatchString(*v)
}

// validEmail checks if the given input is a correct email
func validEmail(v string) bool {
_, err := mail.ParseAddress(v)
return err == nil
}

0 comments on commit 836895d

Please sign in to comment.