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:
4 files changed
+368
No files matched your search
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user