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_virtual_chassis resource #497

Merged
merged 1 commit into from
Nov 9, 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
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
}