diff --git a/docs/resources/virtual_chassis.md b/docs/resources/virtual_chassis.md new file mode 100644 index 00000000..7add77b1 --- /dev/null +++ b/docs/resources/virtual_chassis.md @@ -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 + +### 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. + + diff --git a/examples/resources/netbox_virtual_chassis/resource.tf b/examples/resources/netbox_virtual_chassis/resource.tf new file mode 100644 index 00000000..9a4c6b67 --- /dev/null +++ b/examples/resources/netbox_virtual_chassis/resource.tf @@ -0,0 +1,5 @@ +resource "netbox_virtual_chassis" "example" { + name = "chassis" + domain = "domain" + description = "virtual chassis" +} diff --git a/netbox/provider.go b/netbox/provider.go index 393b9ce4..ee930be1 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -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(), diff --git a/netbox/resource_netbox_virtual_chassis.go b/netbox/resource_netbox_virtual_chassis.go new file mode 100644 index 00000000..da578599 --- /dev/null +++ b/netbox/resource_netbox_virtual_chassis.go @@ -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 +} diff --git a/netbox/resource_netbox_virtual_chassis_test.go b/netbox/resource_netbox_virtual_chassis_test.go new file mode 100644 index 00000000..a4d78a9b --- /dev/null +++ b/netbox/resource_netbox_virtual_chassis_test.go @@ -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 +}