Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

team: add organization_access #155

Merged
merged 17 commits into from
May 20, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.6.0
github.com/hashicorp/go-tfe v0.7.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.6.0 h1:d5W5GYyu4cl1J3+widz2lCyO0ZHp3dxHEu7mhEYP4Ho=
github.com/hashicorp/go-tfe v0.6.0/go.mod h1:XAV72S4O1iP8BDaqiaPLmL2B4EE6almocnOn8E8stHc=
github.com/hashicorp/go-tfe v0.7.0 h1:mmjHnqPQXu3jG7uycU7kW4irxzs/0VqIbA5BQKgT5Nc=
github.com/hashicorp/go-tfe v0.7.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
89 changes: 87 additions & 2 deletions tfe/resource_tfe_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"log"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/helper/validation"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)
Expand All @@ -13,6 +15,7 @@ func resourceTFETeam() *schema.Resource {
return &schema.Resource{
Create: resourceTFETeamCreate,
Read: resourceTFETeamRead,
Update: resourceTFETeamUpdate,
Delete: resourceTFETeamDelete,
Importer: &schema.ResourceImporter{
State: resourceTFETeamImporter,
Expand All @@ -24,20 +27,49 @@ func resourceTFETeam() *schema.Resource {
Required: true,
ForceNew: true,
},

"organization": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"organization_access": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"manage_policies": {
Type: schema.TypeBool,
Optional: true,
},
"manage_workspaces": {
Type: schema.TypeBool,
Optional: true,
},
"manage_vcs_settings": {
Type: schema.TypeBool,
Optional: true,
bendrucker marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
},
"visibility": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I'm not sure we want this to be marked as a computed value. Computed is used to represent values that are not user configurable or aren't known at time of an apply or plan operation, which this shouldn't be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set this based on the following comment from the plugin SDK:

// If Computed is true, then the result of this value is computed
// (unless specified by config) on creation.

I take that to mean that any with a server set default could be "computed," though this probably moot when it's an optional field where the zero value of the type is equal to the server value. If the server were capable of defaulting to true, I think this might need to be set though? I can remove it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, was mis-remembering what values visibility could have. I think this needs to be computed since the API will respond with visibility = "secret" regardless of whether the user has set a value and we'll persist that value to state. I would expect Terraform to present a diff from "secret" -> null on subsequent runs if we don't account for this. I think computed takes care of this, but correct me if I'm wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the API will respond with visibility = "secret" regardless of whether the user has set a value

Nope, the API should respond with whatever value the user set - and valid options are "secret" or "organization". The default is "secret".

  • Computed is very much for unknown values at plan-time that are not user-configurable - things like dates and UUIDs.
  • Default is for known values to be used when something isn't explicitly set within the configuration.

For our case here, these are all values that have known defaults that we can provide at plan-time; so they can all be defaulted appropriately. (So this visibility section should not be computed, and should have a default of "secret").

When that's all finished, given a configuration like this:

resource "tfe_team" "test-1" {
  name         = "my-team-name"
  organization = var.organization
  organization_access {
    manage_policies = true
    manage_workspaces = false
  }
}

We should see a plan output like this:

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # tfe_team.test-1 will be created
  + resource "tfe_team" "test-1" {
      + id           = (known after apply)
      + name         = "my-team-name"
      + organization = "my-cool-organization"
      + visibility   = "secret"

      + organization_access {
          + manage_policies     = true
          + manage_vcs_settings = false
          + manage_workspaces   = false
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

See the Schema Behaviors documentation for more info on both of these behaviors.

ValidateFunc: validation.StringInSlice([]string{
"secret",
"organization",
}, false),
},
},
}
}

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

// Get the name and organization.
// Get team attributes.
name := d.Get("name").(string)
organization := d.Get("organization").(string)

Expand All @@ -46,6 +78,20 @@ func resourceTFETeamCreate(d *schema.ResourceData, meta interface{}) error {
Name: tfe.String(name),
}

if v, ok := d.GetOk("organization_access"); ok {
organizationAccess := v.([]interface{})[0].(map[string]interface{})

options.OrganizationAccess = &tfe.OrganizationAccessOptions{
ManagePolicies: tfe.Bool(organizationAccess["manage_policies"].(bool)),
ManageWorkspaces: tfe.Bool(organizationAccess["manage_workspaces"].(bool)),
ManageVCSSettings: tfe.Bool(organizationAccess["manage_vcs_settings"].(bool)),
}
}

if v, ok := d.GetOk("visibility"); ok {
options.Visibility = tfe.String(v.(string))
}

log.Printf("[DEBUG] Create team %s for organization: %s", name, organization)
team, err := tfeClient.Teams.Create(ctx, organization, options)
if err != nil {
Expand Down Expand Up @@ -74,6 +120,45 @@ func resourceTFETeamRead(d *schema.ResourceData, meta interface{}) error {

// Update the config.
d.Set("name", team.Name)
d.Set("organization_access.0.manage_policies", team.OrganizationAccess.ManagePolicies)
d.Set("organization_access.0.manage_workspaces", team.OrganizationAccess.ManageWorkspaces)
d.Set("organization_access.0.manage_vcs_settings", team.OrganizationAccess.ManageVCSSettings)
d.Set("visibility", team.Visibility)

return nil
}

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

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

// create an options struct
options := tfe.TeamUpdateOptions{
Name: tfe.String(name),
}

if v, ok := d.GetOk("organization_access"); ok {
organizationAccess := v.([]interface{})[0].(map[string]interface{})

options.OrganizationAccess = &tfe.OrganizationAccessOptions{
ManagePolicies: tfe.Bool(organizationAccess["manage_policies"].(bool)),
ManageWorkspaces: tfe.Bool(organizationAccess["manage_workspaces"].(bool)),
ManageVCSSettings: tfe.Bool(organizationAccess["manage_vcs_settings"].(bool)),
}
}

if v, ok := d.GetOk("visibility"); ok {
options.Visibility = tfe.String(v.(string))
}

log.Printf("[DEBUG] Update team: %s", d.Id())
_, err := tfeClient.Teams.Update(ctx, d.Id(), options)
if err != nil {
return fmt.Errorf(
"Error updating team %s: %v", d.Id(), err)
}

return nil
}
Expand Down
170 changes: 168 additions & 2 deletions tfe/resource_tfe_team_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestAccTFETeam_basic(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckTFETeamExists(
"tfe_team.foobar", team),
testAccCheckTFETeamAttributes(team),
testAccCheckTFETeamAttributes_basic(team),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "name", "team-test"),
),
Expand All @@ -31,6 +31,84 @@ func TestAccTFETeam_basic(t *testing.T) {
})
}

func TestAccTFETeam_full(t *testing.T) {
team := &tfe.Team{}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckTFETeamDestroy,
Steps: []resource.TestStep{
{
Config: testAccTFETeam_full,
Check: resource.ComposeTestCheckFunc(
testAccCheckTFETeamExists(
"tfe_team.foobar", team),
testAccCheckTFETeamAttributes_full(team),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "name", "team-test"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "visibility", "organization"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_policies", "true"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_workspaces", "true"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_vcs_settings", "true"),
),
},
},
})
}

func TestAccTFETeam_full_update(t *testing.T) {
team := &tfe.Team{}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckTFETeamDestroy,
Steps: []resource.TestStep{
{
Config: testAccTFETeam_full,
Check: resource.ComposeTestCheckFunc(
testAccCheckTFETeamExists(
"tfe_team.foobar", team),
testAccCheckTFETeamAttributes_full(team),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "name", "team-test"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "visibility", "organization"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_policies", "true"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_workspaces", "true"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_vcs_settings", "true"),
),
},
{
Config: testAccTFETeam_full_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckTFETeamExists(
"tfe_team.foobar", team),
testAccCheckTFETeamAttributes_full_update(team),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "name", "team-test-1"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "visibility", "secret"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_policies", "false"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_workspaces", "false"),
resource.TestCheckResourceAttr(
"tfe_team.foobar", "organization_access.0.manage_vcs_settings", "false"),
),
},
},
})
}

func TestAccTFETeam_import(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -80,12 +158,62 @@ func testAccCheckTFETeamExists(
}
}

func testAccCheckTFETeamAttributes(
func testAccCheckTFETeamAttributes_basic(
team *tfe.Team) resource.TestCheckFunc {
return func(s *terraform.State) error {
if team.Name != "team-test" {
return fmt.Errorf("Bad name: %s", team.Name)
}
return nil
}
}

func testAccCheckTFETeamAttributes_full(
team *tfe.Team) resource.TestCheckFunc {
return func(s *terraform.State) error {
if team.Name != "team-test" {
return fmt.Errorf("Bad name: %s", team.Name)
}

if team.Visibility != "organization" {
return fmt.Errorf("Bad visibility: %s", team.Visibility)
}

if !team.OrganizationAccess.ManagePolicies {
return fmt.Errorf("OrganizationAccess.ManagePolicies should be true")
}
if !team.OrganizationAccess.ManageVCSSettings {
return fmt.Errorf("OrganizationAccess.ManageVCSSettings should be true")
}
if !team.OrganizationAccess.ManageWorkspaces {
return fmt.Errorf("OrganizationAccess.ManageWorkspaces should be true")
}

return nil
}
}

func testAccCheckTFETeamAttributes_full_update(
team *tfe.Team) resource.TestCheckFunc {
return func(s *terraform.State) error {
if team.Name != "team-test-1" {
return fmt.Errorf("Bad name: %s", team.Name)
}

if team.Visibility != "secret" {
return fmt.Errorf("Bad visibility: %s", team.Visibility)
}

if team.OrganizationAccess.ManagePolicies {
return fmt.Errorf("OrganizationAccess.ManagePolicies should be false")
}
if team.OrganizationAccess.ManageVCSSettings {
return fmt.Errorf("OrganizationAccess.ManageVCSSettings should be false")
}
if team.OrganizationAccess.ManageWorkspaces {
return fmt.Errorf("OrganizationAccess.ManageWorkspaces should be false")
}

return nil
}
}
Expand Down Expand Up @@ -121,3 +249,41 @@ resource "tfe_team" "foobar" {
name = "team-test"
organization = "${tfe_organization.foobar.id}"
}`

const testAccTFETeam_full = `
resource "tfe_organization" "foobar" {
name = "tst-terraform"
email = "admin@company.com"
}

resource "tfe_team" "foobar" {
name = "team-test"
organization = "${tfe_organization.foobar.id}"

visibility = "organization"

organization_access {
manage_policies = true
manage_workspaces = true
manage_vcs_settings = true
}
}`

const testAccTFETeam_full_update = `
resource "tfe_organization" "foobar" {
name = "tst-terraform"
email = "admin@company.com"
}

resource "tfe_team" "foobar" {
name = "team-test-1"
organization = "${tfe_organization.foobar.id}"

visibility = "secret"

organization_access {
manage_policies = false
manage_workspaces = false
manage_vcs_settings = false
}
}`
Loading