generated from hashicorp/terraform-provider-scaffolding
-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Netbox User Permission Resource (#390)
This feature allows management of netbox user permissions with terraform. It follows the pattern of testing and design similar to that of other resources. A unique feature of this resource is the handling of the `constraints` field. Since this field is a JSON blob in the netbox API, we had to find a nice way to convert a JSON string into a JSON blob. And since this field can be either a JSON list or a JSON object, we had to handle this accordingly. By using `json.Unmarshal()` on the string, and then doing a type switch, we could detect the correct type, and set that in the struct for sending it to the API. Ref: #387
- Loading branch information
Showing
5 changed files
with
367 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters