Skip to content

Commit

Permalink
feat: Add netbox_virtual_chassis resource
Browse files Browse the repository at this point in the history
This adds support to manage virtual chassis. Adding members and setting
the master will be done on the device resource itself, otherwise this
would introduce circular dependencies.
  • Loading branch information
Ikke authored and fbreckle committed Nov 9, 2023
1 parent ae74579 commit 8559ff6
Show file tree
Hide file tree
Showing 5 changed files with 340 additions and 0 deletions.
45 changes: 45 additions & 0 deletions docs/resources/virtual_chassis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
# generated by https://github.com/fbreckle/terraform-plugin-docs
page_title: "netbox_virtual_chassis Resource - terraform-provider-netbox"
subcategory: "Data Center Inventory Management (DCIM)"
description: |-
From the official documentation https://docs.netbox.dev/en/stable/features/devices-cabling/#virtual-chassis:
> Sometimes it is necessary to model a set of physical devices as sharing a single management plane. Perhaps the most common example of such a scenario is stackable switches. These can be modeled as virtual chassis in NetBox, with one device acting as the chassis master and the rest as members. All components of member devices will appear on the master.
---

# netbox_virtual_chassis (Resource)

From the [official documentation](https://docs.netbox.dev/en/stable/features/devices-cabling/#virtual-chassis):

> Sometimes it is necessary to model a set of physical devices as sharing a single management plane. Perhaps the most common example of such a scenario is stackable switches. These can be modeled as virtual chassis in NetBox, with one device acting as the chassis master and the rest as members. All components of member devices will appear on the master.

## Example Usage

```terraform
resource "netbox_virtual_chassis" "example" {
name = "chassis"
domain = "domain"
description = "virtual chassis"
}
```

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

### Required

- `name` (String)

### Optional

- `comments` (String)
- `custom_fields` (Map of String)
- `description` (String)
- `domain` (String)
- `tags` (Set of String)

### Read-Only

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


5 changes: 5 additions & 0 deletions examples/resources/netbox_virtual_chassis/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resource "netbox_virtual_chassis" "example" {
name = "chassis"
domain = "domain"
description = "virtual chassis"
}
1 change: 1 addition & 0 deletions netbox/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func Provider() *schema.Provider {
"netbox_inventory_item": resourceNetboxInventoryItem(),
"netbox_webhook": resourceNetboxWebhook(),
"netbox_custom_field_choice_set": resourceNetboxCustomFieldChoiceSet(),
"netbox_virtual_chassis": resourceNetboxVirtualChassis(),
},
DataSourcesMap: map[string]*schema.Resource{
"netbox_asn": dataSourceNetboxAsn(),
Expand Down
198 changes: 198 additions & 0 deletions netbox/resource_netbox_virtual_chassis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package netbox

import (
"context"
"strconv"

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

func resourceNetboxVirtualChassis() *schema.Resource {
return &schema.Resource{
CreateContext: resourceNetboxVirtualChassisCreate,
ReadContext: resourceNetboxVirtualChassisRead,
UpdateContext: resourceNetboxVirtualChassisUpdate,
DeleteContext: resourceNetboxVirtualChassisDelete,
Description: `:meta:subcategory:Data Center Inventory Management (DCIM):From the [official documentation](https://docs.netbox.dev/en/stable/features/devices-cabling/#virtual-chassis):
> Sometimes it is necessary to model a set of physical devices as sharing a single management plane. Perhaps the most common example of such a scenario is stackable switches. These can be modeled as virtual chassis in NetBox, with one device acting as the chassis master and the rest as members. All components of member devices will appear on the master.`,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"domain": {
Type: schema.TypeString,
Optional: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"comments": {
Type: schema.TypeString,
Optional: true,
},
tagsKey: tagsSchema,
customFieldsKey: customFieldsSchema,
},
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

func resourceNetboxVirtualChassisCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api := m.(*client.NetBoxAPI)

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

data := models.WritableVirtualChassis{
Name: &name,
}

domainValue, ok := d.GetOk("domain")
if ok {
domain := domainValue.(string)
data.Domain = domain
}

descriptionValue, ok := d.GetOk("description")
if ok {
description := descriptionValue.(string)
data.Description = description
}

commentsValue, ok := d.GetOk("comments")
if ok {
comments := commentsValue.(string)
data.Comments = comments
}

ct, ok := d.GetOk(customFieldsKey)
if ok {
data.CustomFields = ct
}

data.Tags, _ = getNestedTagListFromResourceDataSet(api, d.Get(tagsKey))

params := dcim.NewDcimVirtualChassisCreateParams().WithData(&data)

res, err := api.Dcim.DcimVirtualChassisCreate(params, nil)
if err != nil {
return diag.FromErr(err)
}

d.SetId(strconv.FormatInt(res.GetPayload().ID, 10))

return resourceNetboxVirtualChassisRead(ctx, d, m)
}

func resourceNetboxVirtualChassisRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api := m.(*client.NetBoxAPI)

id, _ := strconv.ParseInt(d.Id(), 10, 64)

params := dcim.NewDcimVirtualChassisReadParams().WithID(id)

res, err := api.Dcim.DcimVirtualChassisRead(params, nil)
if err != nil {
if errresp, ok := err.(*dcim.DcimVirtualChassisReadDefault); ok {
errorcode := errresp.Code()
if errorcode == 404 {
d.SetId("")
return nil
}
}
return diag.FromErr(err)
}

virtualChassis := res.GetPayload()

d.Set("name", virtualChassis.Name)
d.Set("domain", virtualChassis.Domain)
d.Set("description", virtualChassis.Description)
d.Set("comments", virtualChassis.Comments)

cf := getCustomFields(res.GetPayload().CustomFields)
if cf != nil {
d.Set(customFieldsKey, cf)
}

d.Set(tagsKey, getTagListFromNestedTagList(virtualChassis.Tags))
return nil
}

func resourceNetboxVirtualChassisUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api := m.(*client.NetBoxAPI)

id, _ := strconv.ParseInt(d.Id(), 10, 64)
data := models.WritableVirtualChassis{}

name := d.Get("name").(string)
data.Name = &name

domainValue, ok := d.GetOk("domain")
if ok {
domain := domainValue.(string)
data.Domain = domain
}

ct, ok := d.GetOk(customFieldsKey)
if ok {
data.CustomFields = ct
}

data.Tags, _ = getNestedTagListFromResourceDataSet(api, d.Get(tagsKey))

if d.HasChanges("comments") {
// check if comment is set
if commentsValue, ok := d.GetOk("comments"); ok {
data.Comments = commentsValue.(string)
} else {
data.Comments = " "
}
}
if d.HasChanges("description") {
// check if description is set
if descriptionValue, ok := d.GetOk("description"); ok {
data.Description = descriptionValue.(string)
} else {
data.Description = " "
}
}

params := dcim.NewDcimVirtualChassisUpdateParams().WithID(id).WithData(&data)

_, err := api.Dcim.DcimVirtualChassisUpdate(params, nil)
if err != nil {
return diag.FromErr(err)
}

return resourceNetboxVirtualChassisRead(ctx, d, m)
}

func resourceNetboxVirtualChassisDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api := m.(*client.NetBoxAPI)

id, _ := strconv.ParseInt(d.Id(), 10, 64)
params := dcim.NewDcimVirtualChassisDeleteParams().WithID(id)

_, err := api.Dcim.DcimVirtualChassisDelete(params, nil)
if err != nil {
if errresp, ok := err.(*dcim.DcimVirtualChassisDeleteDefault); ok {
if errresp.Code() == 404 {
d.SetId("")
return nil
}
}
return diag.FromErr(err)
}

return nil
}
91 changes: 91 additions & 0 deletions netbox/resource_netbox_virtual_chassis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package netbox

import (
"fmt"
"strconv"
"testing"

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

func TestAccNetboxVirtualChassis_basic(t *testing.T) {
testSlug := "virtual_chassis"
testName := testAccGetTestName(testSlug)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVirtualChassisDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
resource "netbox_tag" "tag_a" {
name = "[%[1]s_a]"
color_hex = "123456"
}
resource "netbox_virtual_chassis" "test" {
name = "%[1]s"
domain = "domain"
description = "description"
comments = "comment"
tags = [netbox_tag.tag_a.name]
}
`, testName),
},
{
Config: fmt.Sprintf(`
resource "netbox_tag" "tag_a" {
name = "[%[1]s_a]"
color_hex = "123456"
}
resource "netbox_virtual_chassis" "test" {
name = "%[1]s_updated"
domain = "domain_updated"
description = "description updated"
comments = "comment updated"
tags = [netbox_tag.tag_a.name]
}
`, testName),
},
{
ResourceName: "netbox_virtual_chassis.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckVirtualChassisDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*client.NetBoxAPI)

// loop through the resources in state, verifying each virtual machine
// is destroyed
for _, rs := range s.RootModule().Resources {
if rs.Type != "netbox_virtual_chassis" {
continue
}

stateID, _ := strconv.ParseInt(rs.Primary.ID, 10, 64)
params := dcim.NewDcimVirtualChassisReadParams().WithID(stateID)
_, err := conn.Dcim.DcimVirtualChassisRead(params, nil)

if err == nil {
return fmt.Errorf("virtual chassis (%s) still exists", rs.Primary.ID)
}

if err != nil {
if errresp, ok := err.(*dcim.DcimVirtualChassisReadDefault); ok {
errorcode := errresp.Code()
if errorcode == 404 {
return nil
}
}
return err
}
}
return nil
}

0 comments on commit 8559ff6

Please sign in to comment.