Add managed group resource (#118)

* feat: add managed group resource

* feat: add tests for managed group resource

* update go.sum

* update tests

* simplify the managed group resource

* use WithDefaultPasswordAuthMethodId

* Managed groups (#129)

* docs: update host catalog resource with correct value for type attribute

* build: add Makefile target for auto-generating docs

* bugfix: make OIDC account attribute for subject ForceNew

* docs: update auto-generated docs

* Add credential library resource for Vault (#114)

* Integrate with Vault to retrieve and manage per session credentials

Co-authored-by: Louis Ruch <louisruch@gmail.com>

* Update token/clientKey on hmac change (#125)

* Update token/clientKey on hmac change

* Update changelog

* Update docs

* Prep for 1.0.3

* v1.0.3

* Update terraform-plugin-sdk

* Fix Typo

* Adopt American spelling

* fix the parameter that is actually being sent as the auth method id

Co-authored-by: Jeff Malnick <malnick@gmail.com>
Co-authored-by: Louis Ruch <louisruch@gmail.com>
Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
Co-authored-by: tf-release-bot <terraform@hashicorp.com>
Co-authored-by: Lewis Cowper <593574+lewiscowper@users.noreply.github.com>

* nits and updates

* OIDC auth method + get filter from attributes

* Add changelog

* Fix schema description

Co-authored-by: Riley Guerin <rileytg@users.noreply.github.com>
Co-authored-by: Louis Ruch <louisruch@gmail.com>
Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
Co-authored-by: tf-release-bot <terraform@hashicorp.com>
Co-authored-by: Lewis Cowper <593574+lewiscowper@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2021-08-17 07:22:09 +02:00
1 parent 77eda49baa
commit 6cefb8e376
4 files changed
+368

No files matched your search

+7
View File
@@ -1,3 +1,10 @@
## 1.0.4 (Unreleased)
### New and Improved
* Adds managed groups resource
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/118)).
## 1.0.3 (June 30, 2021)
### New and Improved
+1
View File
@@ -62,6 +62,7 @@ func New() *schema.Provider {
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
"boundary_credential_store_vault": resourceCredentialStoreVault(),
"boundary_managed_group": resourceManagedGroup(),
"boundary_group": resourceGroup(),
"boundary_host": resourceHost(),
"boundary_host_catalog": resourceHostCatalog(),
+227
View File
@@ -0,0 +1,227 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/managedgroups"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
managedGroupFilterKey = "filter"
)
func resourceManagedGroup() *schema.Resource {
return &schema.Resource{
Description: "The managed group resource allows you to configure a Boundary group.",
CreateContext: resourceManagedGroupCreate,
ReadContext: resourceManagedGroupRead,
UpdateContext: resourceManagedGroupUpdate,
DeleteContext: resourceManagedGroupDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the group.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The managed group name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The managed group description.",
Type: schema.TypeString,
Optional: true,
},
AuthMethodIdKey: {
Description: "The resource ID for the auth method.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
managedGroupFilterKey: {
Description: "Boolean expression to filter the workers for this managed group.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
func setFromManagedGroupResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw[NameKey]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
return err
}
if err := d.Set(AuthMethodIdKey, raw[AuthMethodIdKey]); err != nil {
return err
}
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
if v, ok := attrs[managedGroupFilterKey]; ok {
if err := d.Set(managedGroupFilterKey, v); err != nil {
return err
}
}
}
d.SetId(raw[IDKey].(string))
return nil
}
func resourceManagedGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
grpClient := managedgroups.NewClient(md.client)
var authMethodId string
authMethodVal, ok := d.GetOk(AuthMethodIdKey)
if !ok {
return diag.Errorf("no auth method ID provided")
}
authMethodId = authMethodVal.(string)
var opts []managedgroups.Option
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, managedgroups.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, managedgroups.WithDescription(descStr))
}
v, ok := d.GetOk(managedGroupFilterKey)
if ok {
str := v.(string)
opts = append(opts, managedgroups.WithOidcManagedGroupFilter(str))
}
grp, err := grpClient.Create(ctx, authMethodId, opts...)
if err != nil {
return diag.Errorf("error creating managed group: %v", err)
}
if grp == nil {
return diag.Errorf("managed group nil after create")
}
raw := grp.GetResponse().Map
if err := setFromManagedGroupResponseMap(d, raw); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceManagedGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
grpClient := managedgroups.NewClient(md.client)
grp, err := grpClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading managed group: %v", err)
}
if grp == nil {
return diag.Errorf("managed group nil after read")
}
if err := setFromManagedGroupResponseMap(d, grp.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceManagedGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
grpClient := managedgroups.NewClient(md.client)
var opts []managedgroups.Option
var name *string
if d.HasChange(NameKey) {
opts = append(opts, managedgroups.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
name = &nameStr
opts = append(opts, managedgroups.WithName(nameStr))
}
}
var desc *string
if d.HasChange(DescriptionKey) {
opts = append(opts, managedgroups.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
desc = &descStr
opts = append(opts, managedgroups.WithDescription(descStr))
}
}
var filter *string
if d.HasChange(managedGroupFilterKey) {
if f, ok := d.GetOk(managedGroupFilterKey); ok {
filterStr := f.(string)
filter = &filterStr
opts = append(opts, managedgroups.WithOidcManagedGroupFilter(filterStr))
}
}
if len(opts) > 0 {
opts = append(opts, managedgroups.WithAutomaticVersioning(true))
_, err := grpClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating managed group: %v", err)
}
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
}
if d.HasChange(managedGroupFilterKey) {
if err := d.Set(managedGroupFilterKey, filter); err != nil {
return diag.FromErr(err)
}
}
return nil
}
func resourceManagedGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
grpClient := managedgroups.NewClient(md.client)
_, err := grpClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error calling delete managed group: %s", err.Error())
}
return nil
}
@@ -0,0 +1,133 @@
package provider
import (
"context"
"fmt"
"net/http"
"strings"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/managedgroups"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/cap/oidc"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
const (
managedGroupName = "test"
managedGroupDescription = "test managed group"
managedGroupUpdate = "_update"
)
var (
fooManagedGroup = fmt.Sprintf(`
resource "boundary_managed_group" "foo" {
name = "%s"
description = "%s"
auth_method_id = boundary_auth_method_oidc.foo.id
filter = "name == \"foo\""
}`, managedGroupName, managedGroupDescription)
fooManagedGroupUpdate = fmt.Sprintf(`
resource "boundary_managed_group" "foo" {
name = "%s"
description = "%s"
auth_method_id = boundary_auth_method_oidc.foo.id
filter = "name == \"bar\""
}`, managedGroupName+managedGroupUpdate, managedGroupDescription+managedGroupUpdate)
)
func TestAccManagedGroup(t *testing.T) {
wrapper := testWrapper(t, tcRecoveryKey)
tp := oidc.StartTestProvider(t)
tc := controller.NewTestController(t, append(tcConfig, controller.WithRecoveryKms(wrapper))...)
tpCert := strings.TrimSpace(tp.CACert())
createConfig := fmt.Sprintf(fooAuthMethodOidc, fooAuthMethodOidcDesc, tp.Addr(), tpCert)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckManagedGroupResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// test create
Config: testConfig(url, fooOrg, createConfig, fooManagedGroup),
Check: resource.ComposeTestCheckFunc(
testAccCheckManagedGroupResourceExists(provider, "boundary_managed_group.foo"),
resource.TestCheckResourceAttr("boundary_managed_group.foo", DescriptionKey, managedGroupDescription),
resource.TestCheckResourceAttr("boundary_managed_group.foo", NameKey, managedGroupName),
resource.TestCheckResourceAttr("boundary_managed_group.foo", managedGroupFilterKey, `name == "foo"`),
),
},
importStep("boundary_managed_group.foo"),
{
// test update
Config: testConfig(url, fooOrg, createConfig, fooManagedGroupUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckManagedGroupResourceExists(provider, "boundary_managed_group.foo"),
resource.TestCheckResourceAttr("boundary_managed_group.foo", DescriptionKey, managedGroupDescription+managedGroupUpdate),
resource.TestCheckResourceAttr("boundary_managed_group.foo", NameKey, managedGroupName+managedGroupUpdate),
resource.TestCheckResourceAttr("boundary_managed_group.foo", managedGroupFilterKey, `name == "bar"`),
),
},
},
})
}
func testAccCheckManagedGroupResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("No ID is set")
}
md := testProvider.Meta().(*metaData)
grpClient := managedgroups.NewClient(md.client)
_, err := grpClient.Read(context.Background(), id)
if err != nil {
return fmt.Errorf("Got an error when reading group %q: %v", id, err)
}
return nil
}
}
func testAccCheckManagedGroupResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
}
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_managed_group":
grpClient := managedgroups.NewClient(md.client)
id := rs.Primary.ID
_, err := grpClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed resource %q: %v", id, apiErr)
}
default:
continue
}
}
return nil
}
}