Skip to content

Commit

Permalink
Add new resource consul_namespace_role_attachment
Browse files Browse the repository at this point in the history
Discussion: #247
  • Loading branch information
remilapeyre committed Jul 29, 2021
1 parent bc4fb54 commit e3f2d45
Show file tree
Hide file tree
Showing 6 changed files with 362 additions and 13 deletions.
23 changes: 10 additions & 13 deletions consul/resource_consul_namespace_policy_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,13 @@ func resourceConsulNamespacePolicyAttachmentCreate(d *schema.ResourceData, meta
client, qOpts, wOpts := getClient(d, meta)

name := d.Get("namespace").(string)
namespace, _, err := client.Namespaces().Read(name, qOpts)
if err != nil {
return fmt.Errorf("Failed to read namespace %q: %s", name, err)
}
policy := d.Get("policy").(string)

if namespace == nil {
return fmt.Errorf("Namespace %q not found", name)
namespace, err := findNamespace(client, qOpts, name)
if err != nil {
return err
}

policy := d.Get("policy").(string)
for _, p := range namespace.ACLs.PolicyDefaults {
if p.Name == policy {
return fmt.Errorf("Policy %q already attached to the namespace", policy)
Expand All @@ -59,7 +56,7 @@ func resourceConsulNamespacePolicyAttachmentCreate(d *schema.ResourceData, meta

_, _, err = client.Namespaces().Update(namespace, wOpts)
if err != nil {
return fmt.Errorf("Failed to update namespace %q to attach policy %q", name, policy)
return fmt.Errorf("Failed to update namespace %q to attach policy %q: %s", name, policy, err)
}

d.SetId(fmt.Sprintf("%s:%s", name, policy))
Expand All @@ -76,6 +73,9 @@ func resourceConsulNamespacePolicyAttachmentRead(d *schema.ResourceData, meta in
}

namespace, _, err := client.Namespaces().Read(name, qOpts)
if err != nil {
return fmt.Errorf("Failed to read namespace %q: %s", name, err)
}
if namespace == nil {
d.SetId("")
return nil
Expand Down Expand Up @@ -111,12 +111,9 @@ func resourceConsulNamespacePolicyAttachmentDelete(d *schema.ResourceData, meta
return err
}

namespace, _, err := client.Namespaces().Read(name, qOpts)
namespace, err := findNamespace(client, qOpts, name)
if err != nil {
return fmt.Errorf("Failed to get namespace %q: %s", name, err)
}
if namespace == nil {
return fmt.Errorf("Namespace %q not found", name)
return err
}

for i, p := range namespace.ACLs.PolicyDefaults {
Expand Down
148 changes: 148 additions & 0 deletions consul/resource_consul_namespace_role_attachment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package consul

import (
"fmt"

consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceConsulNamespaceRoleAttachment() *schema.Resource {
return &schema.Resource{
Create: resourceConsulNamespaceRoleAttachmentCreate,
Read: resourceConsulNamespaceRoleAttachmentRead,
Delete: resourceConsulNamespaceRoleAttachmentDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"namespace": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
Description: "The namespace to attach the role to.",
},
"role": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
Description: "The role name.",
},
},
}
}

func resourceConsulNamespaceRoleAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
client, qOpts, wOpts := getClient(d, meta)

name := d.Get("namespace").(string)
role := d.Get("role").(string)

namespace, err := findNamespace(client, qOpts, name)
if err != nil {
return err
}

for _, r := range namespace.ACLs.RoleDefaults {
if r.Name == role {
return fmt.Errorf("Role %q already attached to the namespace", role)
}
}

namespace.ACLs.RoleDefaults = append(namespace.ACLs.RoleDefaults, consulapi.ACLLink{
Name: role,
})

_, _, err = client.Namespaces().Update(namespace, wOpts)
if err != nil {
return fmt.Errorf("Failed to update namespace %q to attach role %q: %s", name, role, err)
}

d.SetId(fmt.Sprintf("%s:%s", name, role))

return resourceConsulNamespaceRoleAttachmentRead(d, meta)
}

func resourceConsulNamespaceRoleAttachmentRead(d *schema.ResourceData, meta interface{}) error {
client, qOpts, _ := getClient(d, meta)

name, role, err := parseTwoPartID(d.Id(), "namespace", "role")
if err != nil {
return err
}

namespace, _, err := client.Namespaces().Read(name, qOpts)
if err != nil {
return fmt.Errorf("Failed to read namespace %q: %s", name, err)
}
if namespace == nil {
d.SetId("")
return nil
}

var found bool
for _, l := range namespace.ACLs.RoleDefaults {
if l.Name == role {
found = true
break
}
}

if !found {
d.SetId("")
return nil
}

if err = d.Set("namespace", name); err != nil {
return fmt.Errorf("Failed to set 'namespace': %s", err)
}
if err = d.Set("role", role); err != nil {
return fmt.Errorf("Failed to set 'role': %s", err)
}

return nil
}

func resourceConsulNamespaceRoleAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
client, qOpts, wOpts := getClient(d, meta)
name, role, err := parseTwoPartID(d.Id(), "namespace", "role")
if err != nil {
return err
}

namespace, err := findNamespace(client, qOpts, name)
if err != nil {
return err
}

for i, p := range namespace.ACLs.RoleDefaults {
if p.Name == role {
namespace.ACLs.RoleDefaults = append(
namespace.ACLs.RoleDefaults[:i],
namespace.ACLs.RoleDefaults[i+1:]...,
)
break
}
}

_, _, err = client.Namespaces().Update(namespace, wOpts)
if err != nil {
return fmt.Errorf("Failed to remove role %q from namespace %q", role, name)
}

return nil
}

func findNamespace(client *consulapi.Client, qOpts *consulapi.QueryOptions, name string) (*consulapi.Namespace, error) {
namespace, _, err := client.Namespaces().Read(name, qOpts)
if err != nil {
return nil, fmt.Errorf("Failed to read namespace %q: %s", name, err)
}

if namespace == nil {
return nil, fmt.Errorf("Namespace %q not found", name)
}

return namespace, nil
}
108 changes: 108 additions & 0 deletions consul/resource_consul_namespace_role_attachment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package consul

import (
"fmt"
"testing"

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

func TestAccConsulNamespaceRoleAttachment(t *testing.T) {
testRole := func(name string) func(*terraform.State) error {
return func(s *terraform.State) error {
client := getTestClient(testAccProvider.Meta())
namespace, _, err := client.Namespaces().Read("testroleattachment", nil)
if err != nil {
return fmt.Errorf("failed to read namespace testroleattachment: %s", err)
}
if namespace == nil {
return fmt.Errorf("namespace testroleattachment not found")
}
if len(namespace.ACLs.RoleDefaults) != 1 {
return fmt.Errorf("wrong number of roles: %d", len(namespace.ACLs.RoleDefaults))
}
if namespace.ACLs.RoleDefaults[0].Name != name {
return fmt.Errorf("wrong role, expected %q, found %q", name, namespace.ACLs.RoleDefaults[0].Name)
}
return nil
}
}

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { skipTestOnConsulCommunityEdition(t) },
Steps: []resource.TestStep{
{
Config: testResourceNamespaceRoleConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("consul_namespace_role_attachment.test", "namespace", "testroleattachment"),
resource.TestCheckResourceAttr("consul_namespace_role_attachment.test", "role", "role"),
testRole("role"),
),
},
{
Config: testResourceNamespaceRoleConfigUpdate,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("consul_namespace_role_attachment.test", "namespace", "testroleattachment"),
resource.TestCheckResourceAttr("consul_namespace_role_attachment.test", "role", "role2"),
testRole("role2"),
),
},
{
Config: testResourceNamespaceRoleConfigUpdate,
},
{
ImportState: true,
ImportStateVerify: true,
ResourceName: "consul_namespace_role_attachment.test",
},
},
})
}

const testResourceNamespaceRoleConfig = `
resource "consul_namespace" "test" {
name = "testroleattachment"
lifecycle {
ignore_changes = [role_defaults]
}
}
resource "consul_acl_role" "test" {
name = "role"
service_identities {
service_name = "foo"
}
}
resource "consul_namespace_role_attachment" "test" {
namespace = consul_namespace.test.name
role = consul_acl_role.test.name
}
`

const testResourceNamespaceRoleConfigUpdate = `
resource "consul_namespace" "test" {
name = "testroleattachment"
lifecycle {
ignore_changes = [role_defaults]
}
}
resource "consul_acl_role" "test2" {
name = "role2"
service_identities {
service_name = "foo"
}
}
resource "consul_namespace_role_attachment" "test" {
namespace = consul_namespace.test.name
role = consul_acl_role.test2.name
}
`
1 change: 1 addition & 0 deletions consul/resource_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ func Provider() terraform.ResourceProvider {
"consul_license": resourceConsulLicense(),
"consul_namespace": resourceConsulNamespace(),
"consul_namespace_policy_attachment": resourceConsulNamespacePolicyAttachment(),
"consul_namespace_role_attachment": resourceConsulNamespaceRoleAttachment(),
"consul_node": resourceConsulNode(),
"consul_prepared_query": resourceConsulPreparedQuery(),
"consul_autopilot_config": resourceConsulAutopilotConfig(),
Expand Down
4 changes: 4 additions & 0 deletions website/consul.erb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@
<a href="/docs/providers/consul/r/namespace_policy_attachment.html">namespace_policy_attachment</a>
</li>

<li<%= sidebar_current("docs-consul-resource-namespace-role-attachment") %>>
<a href="/docs/providers/consul/r/namespace_role_attachment.html">namespace_role_attachment</a>
</li>

<li<%= sidebar_current("docs-consul-resource-network-area") %>>
<a href="/docs/providers/consul/r/network_area.html">consul_network_area</a>
</li>
Expand Down
Loading

0 comments on commit e3f2d45

Please sign in to comment.