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

feat: Add Netbox User Permission Resource #390

Merged
merged 1 commit into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
41 changes: 41 additions & 0 deletions docs/resources/user_permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
# generated by https://github.com/fbreckle/terraform-plugin-docs
page_title: "netbox_user_permissions Resource - terraform-provider-netbox"
subcategory: "Authentication"
description: |-
This resource manages the object-based permissions for Netbox users, built into the application.
Object-based permissions enable an administrator to grant users or groups the ability to perform an action on arbitrary subsets of objects in NetBox, rather than all objects of a certain type.
For more information, see the Netbox Object-Based Permissions Docs. https://docs.netbox.dev/en/stable/administration/permissions/
---

# netbox_user_permissions (Resource)

This resource manages the object-based permissions for Netbox users, built into the application.

> Object-based permissions enable an administrator to grant users or groups the ability to perform an action on arbitrary subsets of objects in NetBox, rather than all objects of a certain type.
> For more information, see the [Netbox Object-Based Permissions Docs.](https://docs.netbox.dev/en/stable/administration/permissions/)



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `actions` (Set of Number) A list actions that are allowed on the object types. Acceptable values are `view`, `add`, `change`, or `delete`.
- `name` (String) The name of the permissions object.
- `object_types` (Set of String) A list of object types that the permission object allows access to. Should be in a form the API can accept. For example: `circuits.provider`, `dcim.inventoryitem`, etc.

### Optional

- `constraints` (String) A JSON string of an arbitrary filter used to limit the granted action(s) to a specific subset of objects. For more information on correct syntax, see https://docs.netbox.dev/en/stable/administration/permissions/#constraints. Defaults to `""`.
- `description` (String) The description of the permissions object.
- `enabled` (Boolean) Whether the permissions object is enabled or not. Defaults to `true`.
- `groups` (Set of Number) A list of group IDs that have been assigned to this permissions object.
- `users` (Set of Number) A list of user IDs that have been assigned to this permissions object.

### Read-Only

- `id` (String) The ID of this resource.


1 change: 1 addition & 0 deletions netbox/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func Provider() *schema.Provider {
"netbox_circuit_provider": resourceNetboxCircuitProvider(),
"netbox_circuit_termination": resourceNetboxCircuitTermination(),
"netbox_user": resourceNetboxUser(),
"netbox_user_permissions": resourceNetboxUserPermissions(),
"netbox_token": resourceNetboxToken(),
"netbox_custom_field": resourceCustomField(),
"netbox_asn": resourceNetboxAsn(),
Expand Down
231 changes: 231 additions & 0 deletions netbox/resource_netbox_user_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package netbox

import (
"encoding/json"
"strconv"

"github.com/fbreckle/go-netbox/netbox/client"
"github.com/fbreckle/go-netbox/netbox/client/users"
"github.com/fbreckle/go-netbox/netbox/models"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

// a
func resourceNetboxUserPermissions() *schema.Resource {
return &schema.Resource{
Create: resourceNetboxUserPermissionsCreate,
Read: resourceNetboxUserPermissionsRead,
Update: resourceNetboxUserPermissionsUpdate,
Delete: resourceNetboxUserPermissionsDelete,
Description: `:meta:subcategory:Authentication:This resource manages the object-based permissions for Netbox users, built into the application.

> Object-based permissions enable an administrator to grant users or groups the ability to perform an action on arbitrary subsets of objects in NetBox, rather than all objects of a certain type.
> For more information, see the [Netbox Object-Based Permissions Docs.](https://docs.netbox.dev/en/stable/administration/permissions/)`,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "The name of the permissions object.",
Required: true,
},
"description": {
Type: schema.TypeString,
Description: "The description of the permissions object.",
Optional: true,
},
"enabled": {
Type: schema.TypeBool,
Description: "Whether the permissions object is enabled or not.",
Optional: true,
Default: true,
},
"object_types": {
Type: schema.TypeSet,
Description: "A list of object types that the permission object allows access to. Should be in a form " +
"the API can accept. For example: `circuits.provider`, `dcim.inventoryitem`, etc.",
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"groups": {
Type: schema.TypeSet,
Optional: true,
Description: "A list of group IDs that have been assigned to this permissions object.",
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
"users": {
Type: schema.TypeSet,
Optional: true,
Description: "A list of user IDs that have been assigned to this permissions object.",
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
"actions": {
Type: schema.TypeSet,
Required: true,
Description: "A list actions that are allowed on the object types. Acceptable values are `view`, `add`, `change`, or `delete`.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"constraints": {
Type: schema.TypeString,
Description: "A JSON string of an arbitrary filter used to limit the granted action(s) to a specific subset of objects. " +
"For more information on correct syntax, see https://docs.netbox.dev/en/stable/administration/permissions/#constraints ",
Optional: true,
Default: nil,
ValidateFunc: validation.StringIsJSON,
},
},
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}
func resourceNetboxUserPermissionsCreate(d *schema.ResourceData, m interface{}) error {
api := m.(*client.NetBoxAPI)
data := models.WritableObjectPermission{}

name := d.Get("name").(string)
data.Name = &name
data.Description = d.Get("description").(string)
data.Enabled = d.Get("enabled").(bool)

data.ObjectTypes = toStringList(d.Get("object_types"))
data.Groups = toInt64List(d.Get("groups"))
data.Users = toInt64List(d.Get("users"))
data.Actions = toStringList(d.Get("actions"))

var constraints interface{}
c := d.Get("constraints").(string)
if c == "" {
data.Constraints = nil
} else {
err := json.Unmarshal([]byte(c), &constraints)
if err != nil {
return err
}
switch v := constraints.(type) {
case []interface{}:
data.Constraints = v
case map[string]interface{}:
data.Constraints = v

}
}

params := users.NewUsersPermissionsCreateParams().WithData(&data)
res, err := api.Users.UsersPermissionsCreate(params, nil)
if err != nil {
return err
}
d.SetId(strconv.FormatInt(res.GetPayload().ID, 10))

return resourceNetboxUserPermissionsRead(d, m)
}

func resourceNetboxUserPermissionsRead(d *schema.ResourceData, m interface{}) error {
api := m.(*client.NetBoxAPI)
id, _ := strconv.ParseInt(d.Id(), 10, 64)
params := users.NewUsersPermissionsReadParams().WithID(id)

res, err := api.Users.UsersPermissionsRead(params, nil)
if err != nil {
errorcode := err.(*users.UsersPermissionsReadDefault).Code()
if errorcode == 404 {
// If the ID is updated to blank, this tells Terraform the resource no longer exists (maybe it was destroyed out of band). Just like the destroy callback, the Read function should gracefully handle this case. https://www.terraform.io/docs/extend/writing-custom-providers.html
d.SetId("")
return nil
}
return err
}

d.Set("name", res.GetPayload().Name)
d.Set("description", res.GetPayload().Description)
d.Set("enabled", res.GetPayload().Enabled)
d.Set("object_types", res.GetPayload().ObjectTypes)

var groups []int
for _, v := range res.GetPayload().Groups {
groups = append(groups, int(v.ID))
}
d.Set("groups", groups)

var users []int
for _, v := range res.GetPayload().Users {
users = append(users, int(v.ID))
}
d.Set("users", users)

d.Set("actions", res.GetPayload().Actions)

b, err := json.Marshal(res.GetPayload().Constraints)
if err != nil {
return err
}
d.Set("constraints", string(b))

return nil
}

func resourceNetboxUserPermissionsUpdate(d *schema.ResourceData, m interface{}) error {
api := m.(*client.NetBoxAPI)
id, _ := strconv.ParseInt(d.Id(), 10, 64)
data := models.WritableObjectPermission{}

name := d.Get("name").(string)
data.Name = &name
data.Description = d.Get("description").(string)
data.Enabled = d.Get("enabled").(bool)

data.ObjectTypes = toStringList(d.Get("object_types"))
data.Groups = toInt64List(d.Get("groups"))
data.Users = toInt64List(d.Get("users"))
data.Actions = toStringList(d.Get("actions"))

var constraints interface{}
c := d.Get("constraints").(string)
if c == "" {
data.Constraints = nil
} else {
err := json.Unmarshal([]byte(c), &constraints)
if err != nil {
return err
}
switch v := constraints.(type) {
case []interface{}:
data.Constraints = v
case map[string]interface{}:
data.Constraints = v
}
}
params := users.NewUsersPermissionsUpdateParams().WithID(id).WithData(&data)
_, err := api.Users.UsersPermissionsUpdate(params, nil)
if err != nil {
return err
}
return resourceNetboxUserPermissionsRead(d, m)
}

func resourceNetboxUserPermissionsDelete(d *schema.ResourceData, m interface{}) error {
api := m.(*client.NetBoxAPI)
id, _ := strconv.ParseInt(d.Id(), 10, 64)
params := users.NewUsersPermissionsDeleteParams().WithID(id)
_, err := api.Users.UsersPermissionsDelete(params, nil)
if err != nil {
if errresp, ok := err.(*users.UsersPermissionsDeleteDefault); ok {
if errresp.Code() == 404 {
d.SetId("")
return nil
}
}
return err
}
d.SetId("")
return nil
}
86 changes: 86 additions & 0 deletions netbox/resource_netbox_user_permissions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package netbox

import (
"fmt"
"log"
"strings"
"testing"

"github.com/fbreckle/go-netbox/netbox/client"
"github.com/fbreckle/go-netbox/netbox/client/users"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccNetboxUsePermissions_basic(t *testing.T) {
testSlug := "user_permissions"
testName := testAccGetTestName(testSlug)
resource.ParallelTest(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
resource "netbox_user_permissions" "test_basic" {
name = "%s"
description = "This is a terraform test."
enabled = true
object_types = ["ipam.prefix"]
actions = ["add", "change"]
users = [1]
constraints = jsonencode([{
"status" = "active"
}]
)
}`, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "name", testName),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "description", "This is a terraform test."),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "enabled", "true"),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "object_types.#", "1"),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "object_types.0", "ipam.prefix"),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "actions.#", "2"),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "actions.0", "add"),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "actions.1", "change"),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "users.#", "1"),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "users.0", "1"),
resource.TestCheckResourceAttr("netbox_user_permissions.test_basic", "constraints", "[{\"status\":\"active\"}]"),
),
},
{
ResourceName: "netbox_user_permissions.test_basic",
ImportState: true,
ImportStateVerify: false,
},
},
})
}

func init() {
resource.AddTestSweepers("netbox_user_permissions", &resource.Sweeper{
Name: "netbox_user_permissions",
Dependencies: []string{},
F: func(region string) error {
m, err := sharedClientForRegion(region)
if err != nil {
return fmt.Errorf("Error getting client: %s", err)
}
api := m.(*client.NetBoxAPI)
params := users.NewUsersPermissionsListParams()
res, err := api.Users.UsersPermissionsList(params, nil)
if err != nil {
return err
}
for _, perm := range res.GetPayload().Results {
if strings.HasPrefix(*perm.Name, testPrefix) {
deleteParams := users.NewUsersPermissionsDeleteParams().WithID(perm.ID)
_, err := api.Users.UsersPermissionsDelete(deleteParams, nil)
if err != nil {
return err
}
log.Print("[DEBUG] Deleted a user")
}
}
return nil
},
})
}
8 changes: 8 additions & 0 deletions netbox/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ func float64ToPtr(i float64) *float64 {
return &i
}

func toStringList(a interface{}) []string {
strList := []string{}
for _, str := range a.(*schema.Set).List() {
strList = append(strList, str.(string))
}
return strList
}

func toInt64List(a interface{}) []int64 {
intList := []int64{}
for _, number := range a.(*schema.Set).List() {
Expand Down