From 807b55cc888ce5dae2ee228b51115c6a14c8a242 Mon Sep 17 00:00:00 2001 From: Kevin Daudt Date: Mon, 4 Mar 2024 11:53:35 +0100 Subject: [PATCH] Add resource to manage virtual disks (#558) * feat(netbox_virtual_machine): make disk_size_gb computed Since the addition of virtual disks, this field represents the aggregate size of all disks. When adding virtual disks, netbox automatically updates this field, so it must be a `computed` field to let terraform know that there can be changes from outside of terraform. * chore(go.mod): upgrade github.com/fbreckle/go-netbox to v0.0.0-20240229093931-1ddc00b277c1 * feat(netbox_virtual_disk): add new resource Add support to manage [virtual disks][0], which were added in netbox 3.7.0. [0]:https://docs.netbox.dev/en/stable/models/virtualization/virtualdisk/ --- docs/resources/virtual_disk.md | 56 ++++++ .../resources/netbox_virtual_disk/resource.tf | 16 ++ go.mod | 2 +- go.sum | 2 + netbox/provider.go | 1 + netbox/resource_netbox_virtual_disk.go | 186 ++++++++++++++++++ netbox/resource_netbox_virtual_disk_test.go | 105 ++++++++++ netbox/resource_netbox_virtual_machine.go | 1 + .../resource_netbox_virtual_machine_test.go | 1 - 9 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 docs/resources/virtual_disk.md create mode 100644 examples/resources/netbox_virtual_disk/resource.tf create mode 100644 netbox/resource_netbox_virtual_disk.go create mode 100644 netbox/resource_netbox_virtual_disk_test.go diff --git a/docs/resources/virtual_disk.md b/docs/resources/virtual_disk.md new file mode 100644 index 00000000..a8b20ebe --- /dev/null +++ b/docs/resources/virtual_disk.md @@ -0,0 +1,56 @@ +--- +# generated by https://github.com/fbreckle/terraform-plugin-docs +page_title: "netbox_virtual_disk Resource - terraform-provider-netbox" +subcategory: "Virtualization" +description: |- + From the official documentation https://docs.netbox.dev/en/stable/models/virtualization/virtualdisk/: + > A virtual disk is used to model discrete virtual hard disks assigned to virtual machines. +--- + +# netbox_virtual_disk (Resource) + +From the [official documentation](https://docs.netbox.dev/en/stable/models/virtualization/virtualdisk/): + + > A virtual disk is used to model discrete virtual hard disks assigned to virtual machines. + +## Example Usage + +```terraform +// Assumes vmw-cluster-01 exists in Netbox +data "netbox_cluster" "vmw_cluster_01" { + name = "vmw-cluster-01" +} + +resource "netbox_virtual_machine" "base_vm" { + cluster_id = data.netbox_cluster.vmw_cluster_01.id + name = "myvm-1" +} + +resource "netbox_virtual_disk" "example" { + name = "disk-01" + description = "Main disk" + size = 50 + virtual_machine_id = netbox_virtual_machine.base_vm.id +} +``` + + +## Schema + +### Required + +- `name` (String) +- `size` (Number) +- `virtual_machine_id` (Number) + +### Optional + +- `custom_fields` (Map of String) +- `description` (String) +- `tags` (Set of String) + +### Read-Only + +- `id` (String) The ID of this resource. + + diff --git a/examples/resources/netbox_virtual_disk/resource.tf b/examples/resources/netbox_virtual_disk/resource.tf new file mode 100644 index 00000000..9eef2873 --- /dev/null +++ b/examples/resources/netbox_virtual_disk/resource.tf @@ -0,0 +1,16 @@ +// Assumes vmw-cluster-01 exists in Netbox +data "netbox_cluster" "vmw_cluster_01" { + name = "vmw-cluster-01" +} + +resource "netbox_virtual_machine" "base_vm" { + cluster_id = data.netbox_cluster.vmw_cluster_01.id + name = "myvm-1" +} + +resource "netbox_virtual_disk" "example" { + name = "disk-01" + description = "Main disk" + size = 50 + virtual_machine_id = netbox_virtual_machine.base_vm.id +} diff --git a/go.mod b/go.mod index 94d2a937..9516876d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 toolchain go1.21.3 require ( - github.com/fbreckle/go-netbox v0.0.0-20240216144714-1060d37403a2 + github.com/fbreckle/go-netbox v0.0.0-20240229093931-1ddc00b277c1 github.com/fbreckle/terraform-plugin-docs v0.0.0-20220812121758-a828466500d3 github.com/go-openapi/runtime v0.27.1 github.com/go-openapi/strfmt v0.22.1 diff --git a/go.sum b/go.sum index cc68b6df..48ea335b 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fbreckle/go-netbox v0.0.0-20240216144714-1060d37403a2 h1:8NCjFRDamuUYQvX/JmSkp2JTx0pW5EZMHQTjPdgIhS8= github.com/fbreckle/go-netbox v0.0.0-20240216144714-1060d37403a2/go.mod h1:3U3/m/hna9Ntd3sbHBYwZ1IqbP2+coRzoXw3mCfu3kM= +github.com/fbreckle/go-netbox v0.0.0-20240229093931-1ddc00b277c1 h1:xyVf34wJpiY4PCDlZau6hzwtbIUc1pHdaI/2B+c4+FI= +github.com/fbreckle/go-netbox v0.0.0-20240229093931-1ddc00b277c1/go.mod h1:3U3/m/hna9Ntd3sbHBYwZ1IqbP2+coRzoXw3mCfu3kM= github.com/fbreckle/terraform-plugin-docs v0.0.0-20220812121758-a828466500d3 h1:DMSpM0btVedE2Tt1vfDHWQhf2obzjAe1F0/j8/CyfW4= github.com/fbreckle/terraform-plugin-docs v0.0.0-20220812121758-a828466500d3/go.mod h1:j3HmJySEjx6hOAOPDjGzmzpVNDQq9SNnnF+Vm22d2rs= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= diff --git a/netbox/provider.go b/netbox/provider.go index bdcc81e2..dc82eed6 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -139,6 +139,7 @@ func Provider() *schema.Provider { "netbox_webhook": resourceNetboxWebhook(), "netbox_custom_field_choice_set": resourceNetboxCustomFieldChoiceSet(), "netbox_virtual_chassis": resourceNetboxVirtualChassis(), + "netbox_virtual_disk": resourceNetboxVirtualDisks(), "netbox_event_rule": resourceNetboxEventRule(), "netbox_vpn_tunnel_group": resourceNetboxVpnTunnelGroup(), "netbox_vpn_tunnel": resourceNetboxVpnTunnel(), diff --git a/netbox/resource_netbox_virtual_disk.go b/netbox/resource_netbox_virtual_disk.go new file mode 100644 index 00000000..463ecdf2 --- /dev/null +++ b/netbox/resource_netbox_virtual_disk.go @@ -0,0 +1,186 @@ +package netbox + +import ( + "context" + "strconv" + + "github.com/fbreckle/go-netbox/netbox/client" + "github.com/fbreckle/go-netbox/netbox/client/virtualization" + "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 resourceNetboxVirtualDisks() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceNetboxVirtualDisksCreate, + ReadContext: resourceNetboxVirtualDisksRead, + UpdateContext: resourceNetboxVirtualDisksUpdate, + DeleteContext: resourceNetboxVirtualDisksDelete, + Description: `:meta:subcategory:Virtualization:From the [official documentation](https://docs.netbox.dev/en/stable/models/virtualization/virtualdisk/): + + > A virtual disk is used to model discrete virtual hard disks assigned to virtual machines.`, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "size": { + Type: schema.TypeInt, + Required: true, + }, + "virtual_machine_id": { + Type: schema.TypeInt, + Required: true, + }, + tagsKey: tagsSchema, + customFieldsKey: customFieldsSchema, + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func resourceNetboxVirtualDisksCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + api := m.(*client.NetBoxAPI) + + name := d.Get("name").(string) + size := d.Get("size").(int) + virtualMachineID := d.Get("virtual_machine_id").(int) + + data := models.WritableVirtualDisk{ + Name: &name, + Size: int64ToPtr(int64(size)), + VirtualMachine: int64ToPtr(int64(virtualMachineID)), + } + + descriptionValue, ok := d.GetOk("description") + if ok { + description := descriptionValue.(string) + data.Description = description + } + + ct, ok := d.GetOk(customFieldsKey) + if ok { + data.CustomFields = ct + } + + data.Tags, _ = getNestedTagListFromResourceDataSet(api, d.Get(tagsKey)) + + params := virtualization.NewVirtualizationVirtualDisksCreateParams().WithData(&data) + + res, err := api.Virtualization.VirtualizationVirtualDisksCreate(params, nil) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.FormatInt(res.GetPayload().ID, 10)) + + return resourceNetboxVirtualDisksRead(ctx, d, m) +} + +func resourceNetboxVirtualDisksRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + api := m.(*client.NetBoxAPI) + + id, _ := strconv.ParseInt(d.Id(), 10, 64) + + params := virtualization.NewVirtualizationVirtualDisksReadParams().WithID(id) + + res, err := api.Virtualization.VirtualizationVirtualDisksRead(params, nil) + if err != nil { + if errresp, ok := err.(*virtualization.VirtualizationVirtualDisksReadDefault); ok { + errorcode := errresp.Code() + if errorcode == 404 { + d.SetId("") + return nil + } + } + return diag.FromErr(err) + } + + VirtualDisks := res.GetPayload() + + d.Set("name", VirtualDisks.Name) + d.Set("description", VirtualDisks.Description) + + if VirtualDisks.Size != nil { + d.Set("size", *VirtualDisks.Size) + } + if VirtualDisks.VirtualMachine != nil { + d.Set("virtual_machine_id", VirtualDisks.VirtualMachine.ID) + } + + cf := getCustomFields(res.GetPayload().CustomFields) + if cf != nil { + d.Set(customFieldsKey, cf) + } + + d.Set(tagsKey, getTagListFromNestedTagList(VirtualDisks.Tags)) + return nil +} + +func resourceNetboxVirtualDisksUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + api := m.(*client.NetBoxAPI) + + id, _ := strconv.ParseInt(d.Id(), 10, 64) + data := models.WritableVirtualDisk{} + + name := d.Get("name").(string) + size := int64(d.Get("size").(int)) + virtualMachineID := int64(d.Get("virtual_machine_id").(int)) + + data.Name = &name + data.Size = &size + data.VirtualMachine = &virtualMachineID + + ct, ok := d.GetOk(customFieldsKey) + if ok { + data.CustomFields = ct + } + + data.Tags, _ = getNestedTagListFromResourceDataSet(api, d.Get(tagsKey)) + + if d.HasChanges("description") { + // check if description is set + if descriptionValue, ok := d.GetOk("description"); ok { + data.Description = descriptionValue.(string) + } else { + data.Description = " " + } + } + + params := virtualization.NewVirtualizationVirtualDisksUpdateParams().WithID(id).WithData(&data) + + _, err := api.Virtualization.VirtualizationVirtualDisksUpdate(params, nil) + if err != nil { + return diag.FromErr(err) + } + + return resourceNetboxVirtualDisksRead(ctx, d, m) +} + +func resourceNetboxVirtualDisksDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + api := m.(*client.NetBoxAPI) + + id, _ := strconv.ParseInt(d.Id(), 10, 64) + params := virtualization.NewVirtualizationVirtualDisksDeleteParams().WithID(id) + + _, err := api.Virtualization.VirtualizationVirtualDisksDelete(params, nil) + if err != nil { + if errresp, ok := err.(*virtualization.VirtualizationVirtualDisksDeleteDefault); ok { + if errresp.Code() == 404 { + d.SetId("") + return nil + } + } + return diag.FromErr(err) + } + + return nil +} diff --git a/netbox/resource_netbox_virtual_disk_test.go b/netbox/resource_netbox_virtual_disk_test.go new file mode 100644 index 00000000..eb363626 --- /dev/null +++ b/netbox/resource_netbox_virtual_disk_test.go @@ -0,0 +1,105 @@ +package netbox + +import ( + "fmt" + "strconv" + "testing" + + "github.com/fbreckle/go-netbox/netbox/client" + "github.com/fbreckle/go-netbox/netbox/client/virtualization" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccNetboxVirtualDisk_basic(t *testing.T) { + testSlug := "virtual_disk" + testName := testAccGetTestName(testSlug) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVirtualDiskDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` +resource "netbox_tag" "tag_a" { + name = "[%[1]s_a]" + color_hex = "123456" +} +resource "netbox_site" "test" { + name = "%[1]s" + status = "active" +} +resource "netbox_virtual_machine" "test" { + name = "%[1]s" + site_id = netbox_site.test.id +} +resource "netbox_virtual_disk" "test" { + name = "%[1]s" + description = "description" + size = 30 + virtual_machine_id = netbox_virtual_machine.test.id + tags = [netbox_tag.tag_a.name] +} + `, testName), + }, + { + Config: fmt.Sprintf(` +resource "netbox_tag" "tag_a" { + name = "[%[1]s_a]" + color_hex = "123456" +} +resource "netbox_site" "test" { + name = "%[1]s" + status = "active" +} +resource "netbox_virtual_machine" "test" { + name = "%[1]s" + site_id = netbox_site.test.id +} +resource "netbox_virtual_disk" "test" { + name = "%[1]s_updated" + description = "description updated" + size = 60 + virtual_machine_id = netbox_virtual_machine.test.id + tags = [netbox_tag.tag_a.name] +} + `, testName), + }, + { + ResourceName: "netbox_virtual_disk.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckVirtualDiskDestroy(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_disk" { + continue + } + + stateID, _ := strconv.ParseInt(rs.Primary.ID, 10, 64) + params := virtualization.NewVirtualizationVirtualDisksReadParams().WithID(stateID) + _, err := conn.Virtualization.VirtualizationVirtualDisksRead(params, nil) + + if err == nil { + return fmt.Errorf("virtual disk (%s) still exists", rs.Primary.ID) + } + + if errresp, ok := err.(*virtualization.VirtualizationVirtualDisksReadDefault); ok { + errorcode := errresp.Code() + if errorcode == 404 { + return nil + } + } + return err + } + return nil +} diff --git a/netbox/resource_netbox_virtual_machine.go b/netbox/resource_netbox_virtual_machine.go index 45dc1dbc..2d1ae4c7 100644 --- a/netbox/resource_netbox_virtual_machine.go +++ b/netbox/resource_netbox_virtual_machine.go @@ -76,6 +76,7 @@ func resourceNetboxVirtualMachine() *schema.Resource { "disk_size_gb": { Type: schema.TypeInt, Optional: true, + Computed: true, }, "status": { Type: schema.TypeString, diff --git a/netbox/resource_netbox_virtual_machine_test.go b/netbox/resource_netbox_virtual_machine_test.go index c7d949f7..4d6746bc 100644 --- a/netbox/resource_netbox_virtual_machine_test.go +++ b/netbox/resource_netbox_virtual_machine_test.go @@ -262,7 +262,6 @@ resource "netbox_virtual_machine" "test" { resource.TestCheckResourceAttrPair("netbox_virtual_machine.test", "cluster_id", "netbox_cluster.test", "id"), resource.TestCheckResourceAttr("netbox_virtual_machine.test", "vcpus", "0"), resource.TestCheckResourceAttr("netbox_virtual_machine.test", "memory_mb", "0"), - resource.TestCheckResourceAttr("netbox_virtual_machine.test", "disk_size_gb", "0"), resource.TestCheckResourceAttr("netbox_virtual_machine.test", "comments", ""), resource.TestCheckResourceAttr("netbox_virtual_machine.test", "description", ""), ),