Skip to content

Commit

Permalink
Merge pull request #154 from terraform-providers/leetrout/use-org-mem…
Browse files Browse the repository at this point in the history
…berships-for-teams

Use org memberships for teams
  • Loading branch information
beekus committed Apr 14, 2020
2 parents 0692d84 + f391240 commit 3a8177d
Show file tree
Hide file tree
Showing 21 changed files with 864 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:

workflows:
version: 2
my-workflow:
build-and-test:
jobs:
- run-tests:
context: terraform-provider-tfe build access
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
## 0.16.0 (Unreleased)

FEATURES:

- **New Resource**: `tfe_organization_membership` ([#154](https://github.com/terraform-providers/terraform-provider-tfe/pull/154))
- **New Resource**: `tfe_team_organization_member` ([#154](https://github.com/terraform-providers/terraform-provider-tfe/pull/154))

## 0.15.1 (March 25, 2020)
ENHANCEMENTS:
* r/tfe_workspace: Migrate ID from <organization>/<workspace> to opaque external_id ([#106](https://github.com/terraform-providers/terraform-provider-tfe/pull/106))
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/terraform-providers/terraform-provider-tfe

require (
github.com/hashicorp/go-tfe v0.5.0
github.com/hashicorp/go-tfe v0.6.0
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce
github.com/hashicorp/terraform-plugin-sdk v1.0.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhE
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZqc=
github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
github.com/hashicorp/go-tfe v0.5.0 h1:8fKVNGdCziwZy0VtZkKmurYL7RJRPvGL9R72glwk6F8=
github.com/hashicorp/go-tfe v0.5.0/go.mod h1:DVPSW2ogH+M9W1/i50ASgMht8cHP7NxxK0nrY9aFikQ=
github.com/hashicorp/go-tfe v0.6.0 h1:d5W5GYyu4cl1J3+widz2lCyO0ZHp3dxHEu7mhEYP4Ho=
github.com/hashicorp/go-tfe v0.6.0/go.mod h1:XAV72S4O1iP8BDaqiaPLmL2B4EE6almocnOn8E8stHc=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
Expand Down
2 changes: 2 additions & 0 deletions tfe/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func Provider() terraform.ResourceProvider {
"tfe_notification_configuration": resourceTFENotificationConfiguration(),
"tfe_oauth_client": resourceTFEOAuthClient(),
"tfe_organization": resourceTFEOrganization(),
"tfe_organization_membership": resourceTFEOrganizationMembership(),
"tfe_organization_token": resourceTFEOrganizationToken(),
"tfe_policy_set": resourceTFEPolicySet(),
"tfe_policy_set_parameter": resourceTFEPolicySetParameter(),
Expand All @@ -81,6 +82,7 @@ func Provider() terraform.ResourceProvider {
"tfe_ssh_key": resourceTFESSHKey(),
"tfe_team": resourceTFETeam(),
"tfe_team_access": resourceTFETeamAccess(),
"tfe_team_organization_member": resourceTFETeamOrganizationMember(),
"tfe_team_member": resourceTFETeamMember(),
"tfe_team_members": resourceTFETeamMembers(),
"tfe_team_token": resourceTFETeamToken(),
Expand Down
92 changes: 92 additions & 0 deletions tfe/resource_tfe_organization_membership.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package tfe

import (
"fmt"
"log"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceTFEOrganizationMembership() *schema.Resource {
return &schema.Resource{
Create: resourceTFEOrganizationMembershipCreate,
Read: resourceTFEOrganizationMembershipRead,
Delete: resourceTFEOrganizationMembershipDelete,

Schema: map[string]*schema.Schema{
"email": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"organization": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceTFEOrganizationMembershipCreate(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

// Get the email and organization.
email := d.Get("email").(string)
organization := d.Get("organization").(string)

// Create a new options struct.
options := tfe.OrganizationMembershipCreateOptions{
Email: tfe.String(email),
}

log.Printf("[DEBUG] Create membership %s for organization: %s", email, organization)
membership, err := tfeClient.OrganizationMemberships.Create(ctx, organization, options)
if err != nil {
return fmt.Errorf(
"Error creating membership %s for organization %s: %v", email, organization, err)
}

d.SetId(membership.ID)

return resourceTFEOrganizationMembershipRead(d, meta)
}

func resourceTFEOrganizationMembershipRead(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

log.Printf("[DEBUG] Read configuration of membership: %s", d.Id())
membership, err := tfeClient.OrganizationMemberships.Read(ctx, d.Id())

if err != nil {
if err == tfe.ErrResourceNotFound {
log.Printf("[DEBUG] Membership %s does no longer exist", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Error reading configuration of membership %s: %v", d.Id(), err)
}

// Update the config.
log.Printf("[INFO] User = %#v", membership.User)
d.Set("email", membership.Email)

return nil
}

func resourceTFEOrganizationMembershipDelete(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

log.Printf("[DEBUG] Delete membership: %s", d.Id())
err := tfeClient.OrganizationMemberships.Delete(ctx, d.Id())
if err != nil {
if err == tfe.ErrResourceNotFound {
return nil
}
return fmt.Errorf("Error deleting membership %s: %v", d.Id(), err)
}

return nil
}
107 changes: 107 additions & 0 deletions tfe/resource_tfe_organization_membership_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package tfe

import (
"fmt"
"testing"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccTFEOrganizationMembership_basic(t *testing.T) {
mem := &tfe.OrganizationMembership{}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckTFEOrganizationMembershipDestroy,
Steps: []resource.TestStep{
{
Config: testAccTFEOrganizationMembership_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckTFEOrganizationMembershipExists(
"tfe_organization_membership.foobar", mem),
testAccCheckTFEOrganizationMembershipAttributes(mem),
resource.TestCheckResourceAttr(
"tfe_organization_membership.foobar", "email", "example@hashicorp.com"),
),
},
},
})
}

func testAccCheckTFEOrganizationMembershipExists(
n string, membership *tfe.OrganizationMembership) resource.TestCheckFunc {
return func(s *terraform.State) error {
tfeClient := testAccProvider.Meta().(*tfe.Client)

rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}

options := tfe.OrganizationMembershipReadOptions{
Include: "user",
}

m, err := tfeClient.OrganizationMemberships.ReadWithOptions(ctx, rs.Primary.ID, options)
if err != nil {
return err
}

if m == nil {
return fmt.Errorf("Membership not found")
}

*membership = *m

return nil
}
}

func testAccCheckTFEOrganizationMembershipAttributes(
membership *tfe.OrganizationMembership) resource.TestCheckFunc {
return func(s *terraform.State) error {
if membership.User.Email != "example@hashicorp.com" {
return fmt.Errorf("Bad email: %s", membership.User.Email)
}
return nil
}
}

func testAccCheckTFEOrganizationMembershipDestroy(s *terraform.State) error {
tfeClient := testAccProvider.Meta().(*tfe.Client)

for _, rs := range s.RootModule().Resources {
if rs.Type != "tfe_organization_membership" {
continue
}

if rs.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}

_, err := tfeClient.OrganizationMemberships.Read(ctx, rs.Primary.ID)
if err == nil {
return fmt.Errorf("Membership %s still exists", rs.Primary.ID)
}
}

return nil
}

const testAccTFEOrganizationMembership_basic = `
resource "tfe_organization" "foobar" {
name = "tst-terraform"
email = "admin@company.com"
}
resource "tfe_organization_membership" "foobar" {
email = "example@hashicorp.com"
organization = "${tfe_organization.foobar.id}"
}`
134 changes: 134 additions & 0 deletions tfe/resource_tfe_team_organization_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package tfe

import (
"fmt"
"log"
"strings"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceTFETeamOrganizationMember() *schema.Resource {
return &schema.Resource{
Create: resourceTFETeamOrganizationMemberCreate,
Read: resourceTFETeamOrganizationMemberRead,
Delete: resourceTFETeamOrganizationMemberDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"team_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"organization_membership_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceTFETeamOrganizationMemberCreate(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

// Get the team ID and username..
teamID := d.Get("team_id").(string)
organizationMembershipID := d.Get("organization_membership_id").(string)

// Create a new options struct.
options := tfe.TeamMemberAddOptions{
OrganizationMembershipIDs: []string{organizationMembershipID},
}

log.Printf("[DEBUG] Add organization membership %q to team: %s", organizationMembershipID, teamID)
err := tfeClient.TeamMembers.Add(ctx, teamID, options)
if err != nil {
return fmt.Errorf("Error adding organization membership %q to team %s: %v", organizationMembershipID, teamID, err)
}

d.SetId(packTeamOrganizationMemberID(teamID, organizationMembershipID))

return nil
}

func resourceTFETeamOrganizationMemberRead(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

// Get the team ID and organization membership id.
teamID, organizationMembershipID, err := unpackTeamOrganizationMemberID(d.Id())
if err != nil {
return fmt.Errorf("Error unpacking team member ID: %v", err)
}

log.Printf("[DEBUG] Read organization membership from team: %s", teamID)
organizationMemberships, err := tfeClient.TeamMembers.ListOrganizationMemberships(ctx, teamID)
if err != nil {
if err == tfe.ErrResourceNotFound {
log.Printf("[DEBUG] Organization membership %q does no longer exist", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Error reading organization memberships from team %s: %v", teamID, err)
}

found := false
for _, organizationMembership := range organizationMemberships {
if organizationMembership.ID == organizationMembershipID {
d.Set("team_id", teamID)
d.Set("organization_membership_id", organizationMembershipID)

found = true
break
}
}

if !found {
log.Printf("[DEBUG] Organization membership %q no longer exists", d.Id())
d.SetId("")
}

return nil
}

func resourceTFETeamOrganizationMemberDelete(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

// Get the team ID and organization membership id.
teamID, organizationMembershipID, err := unpackTeamOrganizationMemberID(d.Id())
if err != nil {
return fmt.Errorf("Error unpacking team member ID: %v", err)
}

// Create a new options struct.
options := tfe.TeamMemberRemoveOptions{
OrganizationMembershipIDs: []string{organizationMembershipID},
}

log.Printf("[DEBUG] Remove organization membership %q from team: %s", organizationMembershipID, teamID)
err = tfeClient.TeamMembers.Remove(ctx, teamID, options)
if err != nil {
return fmt.Errorf("Error removing organization membership %q to team %s: %v", organizationMembershipID, teamID, err)
}

return nil
}

func packTeamOrganizationMemberID(teamID, organizationMembershipID string) string {
return teamID + "/" + organizationMembershipID
}

func unpackTeamOrganizationMemberID(id string) (teamID, organizationMembershipID string, err error) {
s := strings.SplitN(id, "/", 2)
if len(s) != 2 {
return "", "", fmt.Errorf(
"invalid team organization member ID format: %s (expected <TEAM ID>/<ORGANIZATION MEMBERSHIP ID>)", id)
}

return s[0], s[1], nil
}
Loading

0 comments on commit 3a8177d

Please sign in to comment.