From ab11014df641a170b5a4e05662204a7766673ae0 Mon Sep 17 00:00:00 2001 From: Samuel Mutel <12967891+smutel@users.noreply.github.com> Date: Mon, 21 Aug 2023 09:14:16 +0200 Subject: [PATCH] feat: Add VRF object --- .../netbox_ipam_vrf/data-source.tf | 3 + examples/resources/netbox_ipam_vrf/import.sh | 2 + .../resources/netbox_ipam_vrf/resource.tf | 88 ++++ netbox/ipam/data_netbox_ipam_vrf.go | 62 +++ .../resource_netbox_ipam_ip_addresses_test.go | 9 +- netbox/ipam/resource_netbox_ipam_vrf.go | 392 ++++++++++++++++++ netbox/ipam/resource_netbox_ipam_vrf_test.go | 142 +++++++ netbox/provider.go | 2 + 8 files changed, 695 insertions(+), 5 deletions(-) create mode 100644 examples/data-sources/netbox_ipam_vrf/data-source.tf create mode 100644 examples/resources/netbox_ipam_vrf/import.sh create mode 100644 examples/resources/netbox_ipam_vrf/resource.tf create mode 100644 netbox/ipam/data_netbox_ipam_vrf.go create mode 100644 netbox/ipam/resource_netbox_ipam_vrf.go create mode 100644 netbox/ipam/resource_netbox_ipam_vrf_test.go diff --git a/examples/data-sources/netbox_ipam_vrf/data-source.tf b/examples/data-sources/netbox_ipam_vrf/data-source.tf new file mode 100644 index 000000000..3ace98e70 --- /dev/null +++ b/examples/data-sources/netbox_ipam_vrf/data-source.tf @@ -0,0 +1,3 @@ +data "netbox_ipam_vrf" "vrf_test" { + vrf_id = 15 +} diff --git a/examples/resources/netbox_ipam_vrf/import.sh b/examples/resources/netbox_ipam_vrf/import.sh new file mode 100644 index 000000000..04c5bd491 --- /dev/null +++ b/examples/resources/netbox_ipam_vrf/import.sh @@ -0,0 +1,2 @@ +# VRFs can be imported by id +terraform import netbox_ipam_vrf.vrf_test 1 diff --git a/examples/resources/netbox_ipam_vrf/resource.tf b/examples/resources/netbox_ipam_vrf/resource.tf new file mode 100644 index 000000000..921b2c056 --- /dev/null +++ b/examples/resources/netbox_ipam_vrf/resource.tf @@ -0,0 +1,88 @@ +resource "netbox_ipam_vrf" "vrf_test" { + name = "Test VRF" + enforce_unique = false + export_targets = [ netbox_ipam_route_targets.rt_export_test.id ] + import_targets = [ netbox_ipam_route_targets.rt_import_test.id ] + rd = "test-vrf" + description = "Test VRF" + comments = <<-EOT + Test Vrf + EOT + + tag { + name = "tag1" + slug = "tag1" + } + + custom_field { + name = "cf_boolean" + type = "boolean" + value = "true" + } + + custom_field { + name = "cf_date" + type = "date" + value = "2020-12-25" + } + + custom_field { + name = "cf_text" + type = "text" + value = "some text" + } + + custom_field { + name = "cf_integer" + type = "integer" + value = "10" + } + + custom_field { + name = "cf_selection" + type = "select" + value = "1" + } + + custom_field { + name = "cf_url" + type = "url" + value = "https://github.com" + } + + custom_field { + name = "cf_multi_selection" + type = "multiselect" + value = jsonencode([ + "0", + "1" + ]) + } + + custom_field { + name = "cf_json" + type = "json" + value = jsonencode({ + stringvalue = "string" + boolvalue = false + dictionary = { + numbervalue = 5 + } + }) + } + + custom_field { + name = "cf_object" + type = "object" + value = 1 + } + + custom_field { + name = "cf_multi_object" + type = "multiobject" + value = jsonencode([ + 1, + 2 + ]) + } +} diff --git a/netbox/ipam/data_netbox_ipam_vrf.go b/netbox/ipam/data_netbox_ipam_vrf.go new file mode 100644 index 000000000..e9ae6dc75 --- /dev/null +++ b/netbox/ipam/data_netbox_ipam_vrf.go @@ -0,0 +1,62 @@ +package ipam + +import ( + "context" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + netboxclient "github.com/smutel/go-netbox/v3/netbox/client" + "github.com/smutel/go-netbox/v3/netbox/client/ipam" + "github.com/smutel/terraform-provider-netbox/v7/netbox/internal/util" +) + +func DataNetboxIpamVrf() *schema.Resource { + return &schema.Resource{ + Description: "Get info about vrf (ipam module) from netbox.", + ReadContext: dataNetboxIpamVrfRead, + + Schema: map[string]*schema.Schema{ + "content_type": { + Type: schema.TypeString, + Computed: true, + Description: "The content type of this vrf (ipam module).", + }, + "vrf_id": { + Type: schema.TypeInt, + Required: true, + Description: "The ID of the vrf (ipam module).", + }, + }, + } +} + +func dataNetboxIpamVrfRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + + id := int64(d.Get("vrf_id").(int)) + idStr := strconv.FormatInt(id, 10) + + p := ipam.NewIpamVrfsListParams().WithID(&idStr) + + list, err := client.Ipam.IpamVrfsList(p, nil) + if err != nil { + return diag.FromErr(err) + } + + if *list.Payload.Count < 1 { + return diag.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } else if *list.Payload.Count > 1 { + return diag.Errorf("Your query returned more than one result. " + + "Please try a more specific search criteria.") + } + + r := list.Payload.Results[0] + d.SetId(strconv.FormatInt(r.ID, 10)) + if err = d.Set("content_type", util.ConvertURIContentType(r.URL)); err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/netbox/ipam/resource_netbox_ipam_ip_addresses_test.go b/netbox/ipam/resource_netbox_ipam_ip_addresses_test.go index c00dc3ba7..9628b149c 100644 --- a/netbox/ipam/resource_netbox_ipam_ip_addresses_test.go +++ b/netbox/ipam/resource_netbox_ipam_ip_addresses_test.go @@ -130,9 +130,9 @@ func testAccCheckNetboxIpamIPAddressConfig(nameSuffix string, resourceFull, extr address = "${cidrhost("10.0.0.0/8", {{ .ipnum }} + 2 )}/24" } - #resource "netbox_ipam_vrf" "test" { - # name = "test-{{ .namesuffix }}" - #} + resource "netbox_ipam_vrf" "test" { + name = "test-{{ .namesuffix }}" + } {{ end }} resource "netbox_ipam_ip_addresses" "test" { @@ -143,8 +143,7 @@ func testAccCheckNetboxIpamIPAddressConfig(nameSuffix string, resourceFull, extr dns_name = "test.example.local" role = "vip" status = "reserved" - # vrf_id = netbox_ipam_vrf.test.id - vrf_id = 1 + vrf_id = netbox_ipam_vrf.test.id tenant_id = netbox_tenancy_tenant.test.id nat_inside_id = netbox_ipam_ip_addresses.nat.id diff --git a/netbox/ipam/resource_netbox_ipam_vrf.go b/netbox/ipam/resource_netbox_ipam_vrf.go new file mode 100644 index 000000000..859549fcc --- /dev/null +++ b/netbox/ipam/resource_netbox_ipam_vrf.go @@ -0,0 +1,392 @@ +package ipam + +import ( + "context" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + netboxclient "github.com/smutel/go-netbox/v3/netbox/client" + "github.com/smutel/go-netbox/v3/netbox/client/ipam" + "github.com/smutel/go-netbox/v3/netbox/models" + "github.com/smutel/terraform-provider-netbox/v7/netbox/internal/customfield" + "github.com/smutel/terraform-provider-netbox/v7/netbox/internal/requestmodifier" + "github.com/smutel/terraform-provider-netbox/v7/netbox/internal/tag" + "github.com/smutel/terraform-provider-netbox/v7/netbox/internal/util" +) + +func ResourceNetboxIpamVrf() *schema.Resource { + return &schema.Resource{ + Description: "Manage a vrf (ipam module) within Netbox.", + CreateContext: resourceNetboxIpamVrfCreate, + ReadContext: resourceNetboxIpamVrfRead, + UpdateContext: resourceNetboxIpamVrfUpdate, + DeleteContext: resourceNetboxIpamVrfDelete, + // Exists: resourceNetboxIpamVrfExists, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "content_type": { + Type: schema.TypeString, + Computed: true, + Description: "The content type of this VRF (ipam module).", + }, + "created": { + Type: schema.TypeString, + Computed: true, + Description: "Date when this VRF was created.", + }, + "comments": { + Type: schema.TypeString, + Optional: true, + Default: nil, + StateFunc: util.TrimString, + Description: "Comments for this VRF (ipam module).", + }, + "custom_field": &customfield.CustomFieldSchema, + "description": { + Type: schema.TypeString, + Optional: true, + Default: nil, + ValidateFunc: validation.StringLenBetween(1, 200), + Description: "The description of this VRF (ipam module).", + }, + "enforce_unique": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Prevent duplicate prefixes/IP addresses within this VRF (ipam module)", + }, + "export_targets": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + Description: "Array of ID of exported vrf targets attached to this VRF (ipam module).", + }, + "import_targets": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + Description: "Array of ID of imported vrf targets attached to this VRF (ipam module).", + }, + "last_updated": { + Type: schema.TypeString, + Computed: true, + Description: "Date when this VRF was created.", + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 100), + Description: "The name of this VRF (ipam module).", + }, + "rd": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 21), + Description: "The Route Distinguisher (RFC 4364) of this VRF (ipam module).", + }, + "tag": &tag.TagSchema, + "tenant_id": { + Type: schema.TypeInt, + Optional: true, + Default: nil, + Description: "ID of the tenant where this VRF (ipam module) is attached.", + }, + "url": { + Type: schema.TypeString, + Computed: true, + Description: "The link to this VRF (ipam module).", + }, + }, + } +} + +var vrfRequiredFields = []string{ + "created", + "enforce_unique", + "last_updated", + "name", + "tags", +} + +func resourceNetboxIpamVrfCreate(ctx context.Context, d *schema.ResourceData, + m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + + resourceCustomFields := d.Get("custom_field").(*schema.Set).List() + customFields := customfield.ConvertCustomFieldsFromTerraformToAPI(nil, resourceCustomFields) + + name := d.Get("name").(string) + rd := d.Get("rd").(string) + tags := d.Get("tag").(*schema.Set).List() + exportTargets := d.Get("export_targets").([]interface{}) + exportTargetsID64 := []int64{} + importTargets := d.Get("import_targets").([]interface{}) + importTargetsID64 := []int64{} + + for _, id := range exportTargets { + exportTargetsID64 = append(exportTargetsID64, int64(id.(int))) + } + + for _, id := range importTargets { + importTargetsID64 = append(importTargetsID64, int64(id.(int))) + } + + newResource := &models.WritableVRF{ + Comments: d.Get("comments").(string), + CustomFields: &customFields, + Description: d.Get("description").(string), + EnforceUnique: d.Get("enforce_unique").(bool), + ExportTargets: exportTargetsID64, + ImportTargets: importTargetsID64, + Name: &name, + Rd: &rd, + Tags: tag.ConvertTagsToNestedTags(tags), + } + + if tenantID := int64(d.Get("tenant_id").(int)); tenantID != 0 { + newResource.Tenant = &tenantID + } + + resource := ipam.NewIpamVrfsCreateParams().WithData(newResource) + + resourceCreated, err := client.Ipam.IpamVrfsCreate(resource, nil) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.FormatInt(resourceCreated.Payload.ID, 10)) + + return resourceNetboxIpamVrfRead(ctx, d, m) +} + +func resourceNetboxIpamVrfRead(ctx context.Context, d *schema.ResourceData, + m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + + resourceID := d.Id() + params := ipam.NewIpamVrfsListParams().WithID(&resourceID) + resources, err := client.Ipam.IpamVrfsList(params, nil) + if err != nil { + return diag.FromErr(err) + } + + if len(resources.Payload.Results) != 1 { + d.SetId("") + return nil + } + + resource := resources.Payload.Results[0] + + if err = d.Set("comments", resource.Comments); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("content_type", util.ConvertURIContentType(resource.URL)); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("created", resource.Created.String()); err != nil { + return diag.FromErr(err) + } + + resourceCustomFields := d.Get("custom_field").(*schema.Set).List() + customFields := customfield.UpdateCustomFieldsFromAPI(resourceCustomFields, resource.CustomFields) + if err = d.Set("custom_field", customFields); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("description", resource.Description); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("enforce_unique", resource.EnforceUnique); err != nil { + return diag.FromErr(err) + } + + exportTargetsObject := resource.ExportTargets + exportTargetsInt := []int64{} + for _, ip := range exportTargetsObject { + exportTargetsInt = append(exportTargetsInt, ip.ID) + } + if err = d.Set("export_targets", exportTargetsInt); err != nil { + return diag.FromErr(err) + } + + importTargetsObject := resource.ImportTargets + importTargetsInt := []int64{} + for _, ip := range importTargetsObject { + importTargetsInt = append(importTargetsInt, ip.ID) + } + if err = d.Set("import_targets", importTargetsInt); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("last_updated", resource.LastUpdated.String()); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("name", resource.Name); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("rd", resource.Rd); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("tag", tag.ConvertNestedTagsToTags(resource.Tags)); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("tenant_id", util.GetNestedTenantID(resource.Tenant)); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("url", resource.URL); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceNetboxIpamVrfUpdate(ctx context.Context, d *schema.ResourceData, + m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + modiefiedFields := make(map[string]interface{}) + + resourceID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return diag.Errorf("Unable to convert ID into int64") + } + params := &models.WritableVRF{} + + if d.HasChange("comments") { + comments := d.Get("comments") + if comments != "" { + params.Comments = comments.(string) + } else { + modiefiedFields["comments"] = "" + } + } + + if d.HasChange("custom_field") { + stateCustomFields, resourceCustomFields := d.GetChange("custom_field") + customFields := customfield.ConvertCustomFieldsFromTerraformToAPI(stateCustomFields.(*schema.Set).List(), resourceCustomFields.(*schema.Set).List()) + params.CustomFields = &customFields + } + if d.HasChange("description") { + params.Description = d.Get("description").(string) + modiefiedFields["description"] = params.Description + } + + if d.HasChange("enforce_unique") { + params.EnforceUnique = d.Get("enforce_unique").(bool) + modiefiedFields["enforce_unique"] = params.EnforceUnique + } + + exportTargets := d.Get("export_targets").([]interface{}) + exportTargetsID64 := []int64{} + for _, id := range exportTargets { + exportTargetsID64 = append(exportTargetsID64, int64(id.(int))) + } + + params.ExportTargets = exportTargetsID64 + + importTargets := d.Get("import_targets").([]interface{}) + importTargetsID64 := []int64{} + for _, id := range importTargets { + importTargetsID64 = append(importTargetsID64, int64(id.(int))) + } + + params.ImportTargets = importTargetsID64 + + if d.HasChange("name") { + name := d.Get("name").(string) + params.Name = &name + } + + if d.HasChange("rd") { + rd := d.Get("rd").(string) + params.Rd = &rd + } + + if d.HasChange("tag") { + tags := d.Get("tag").(*schema.Set).List() + params.Tags = tag.ConvertTagsToNestedTags(tags) + } + + if d.HasChange("tenant_id") { + tenantID := int64(d.Get("tenant_id").(int)) + if tenantID != 0 { + params.Tenant = &tenantID + } else { + modiefiedFields["tenant"] = nil + } + } + + resource := ipam.NewIpamVrfsPartialUpdateParams().WithData(params) + + resource.SetID(resourceID) + + _, err = client.Ipam.IpamVrfsPartialUpdate(resource, nil, requestmodifier.NewNetboxRequestModifier(modiefiedFields, vrfRequiredFields)) + if err != nil { + return diag.FromErr(err) + } + + return resourceNetboxIpamVrfRead(ctx, d, m) +} + +func resourceNetboxIpamVrfDelete(ctx context.Context, d *schema.ResourceData, + m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + + resourceExists, err := resourceNetboxIpamVrfExists(d, m) + if err != nil { + return diag.FromErr(err) + } + + if !resourceExists { + return nil + } + + id, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return diag.Errorf("Unable to convert ID into int64") + } + + resource := ipam.NewIpamVrfsDeleteParams().WithID(id) + if _, err := client.Ipam.IpamVrfsDelete(resource, nil); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceNetboxIpamVrfExists(d *schema.ResourceData, + m interface{}) (b bool, e error) { + client := m.(*netboxclient.NetBoxAPI) + resourceExist := false + + resourceID := d.Id() + params := ipam.NewIpamVrfsListParams().WithID(&resourceID) + resources, err := client.Ipam.IpamVrfsList(params, nil) + if err != nil { + return resourceExist, err + } + + for _, resource := range resources.Payload.Results { + if strconv.FormatInt(resource.ID, 10) == d.Id() { + resourceExist = true + } + } + + return resourceExist, nil +} diff --git a/netbox/ipam/resource_netbox_ipam_vrf_test.go b/netbox/ipam/resource_netbox_ipam_vrf_test.go new file mode 100644 index 000000000..9674896e3 --- /dev/null +++ b/netbox/ipam/resource_netbox_ipam_vrf_test.go @@ -0,0 +1,142 @@ +package ipam_test + +import ( + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/smutel/terraform-provider-netbox/v7/netbox/internal/util" +) + +const resourceNameIpamVrf = "netbox_ipam_vrf.test" + +func TestAccNetboxIpamVrfMinimal(t *testing.T) { + + nameSuffix := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckNetboxIPAMVrfConfig(nameSuffix, false, false), + Check: resource.ComposeTestCheckFunc( + util.TestAccResourceExists(resourceNameIpamVrf), + ), + }, + { + ResourceName: resourceNameIpamVrf, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetboxIpamVrfFull(t *testing.T) { + nameSuffix := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckNetboxIPAMVrfConfig(nameSuffix, true, true), + Check: resource.ComposeTestCheckFunc( + util.TestAccResourceExists(resourceNameIpamVrf), + ), + }, + { + ResourceName: resourceNameIpamVrf, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetboxIpamVrfMininmalFullMinimal(t *testing.T) { + nameSuffix := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckNetboxIPAMVrfConfig(nameSuffix, false, false), + Check: resource.ComposeTestCheckFunc( + util.TestAccResourceExists(resourceNameIpamVrf), + ), + }, + { + Config: testAccCheckNetboxIPAMVrfConfig(nameSuffix, true, true), + Check: resource.ComposeTestCheckFunc( + util.TestAccResourceExists(resourceNameIpamVrf), + ), + }, + { + Config: testAccCheckNetboxIPAMVrfConfig(nameSuffix, false, true), + Check: resource.ComposeTestCheckFunc( + util.TestAccResourceExists(resourceNameIpamVrf), + ), + }, + { + Config: testAccCheckNetboxIPAMVrfConfig(nameSuffix, false, false), + Check: resource.ComposeTestCheckFunc( + util.TestAccResourceExists(resourceNameIpamVrf), + ), + }, + }, + }) +} + +func testAccCheckNetboxIPAMVrfConfig(nameSuffix string, resourceFull, extraResources bool) string { + template := ` + {{ if eq .extraresources "true" }} + resource "netbox_extras_tag" "test" { + name = "test-{{ .namesuffix }}" + slug = "test-{{ .namesuffix }}" + } + + resource "netbox_tenancy_tenant" "test" { + name = "test-{{ .namesuffix }}" + slug = "test-{{ .namesuffix }}" + } + + resource "netbox_ipam_route_targets" "rt_export_test" { + name = "test1-{{ .namesuffix }}" + } + + resource "netbox_ipam_route_targets" "rt_import_test" { + name = "test2-{{ .namesuffix }}" + } + {{ end }} + + resource "netbox_ipam_vrf" "test" { + name = "test-{{ .namesuffix }}" + {{ if eq .resourcefull "true" }} + # enforce_unique = false + export_targets = [ netbox_ipam_route_targets.rt_export_test.id ] + import_targets = [ netbox_ipam_route_targets.rt_import_test.id ] + rd = "test-{{ .namesuffix }}" + description = "Test Vrf" + comments = <<-EOT + Test Vrf + EOT + + tag { + name = netbox_extras_tag.test.name + slug = netbox_extras_tag.test.slug + } + tenant_id = netbox_tenancy_tenant.test.id + {{ end }} + } + ` + data := map[string]string{ + "namesuffix": nameSuffix, + "resourcefull": strconv.FormatBool(resourceFull), + "extraresources": strconv.FormatBool(extraResources), + } + return util.RenderTemplate(template, data) +} diff --git a/netbox/provider.go b/netbox/provider.go index 153af777a..44f1597c4 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -162,6 +162,7 @@ func Provider() *schema.Provider { "netbox_ipam_service": ipam.DataNetboxIpamService(), "netbox_ipam_vlan": ipam.DataNetboxIpamVlan(), "netbox_ipam_vlan_group": ipam.DataNetboxIpamVlanGroup(), + "netbox_ipam_vrf": ipam.DataNetboxIpamVrf(), "netbox_tenancy_contact": tenancy.DataNetboxTenancyContact(), "netbox_tenancy_contact_group": tenancy.DataNetboxTenancyContactGroup(), "netbox_tenancy_contact_role": tenancy.DataNetboxTenancyContactRole(), @@ -190,6 +191,7 @@ func Provider() *schema.Provider { "netbox_ipam_service": ipam.ResourceNetboxIpamService(), "netbox_ipam_vlan": ipam.ResourceNetboxIpamVlan(), "netbox_ipam_vlan_group": ipam.ResourceNetboxIpamVlanGroup(), + "netbox_ipam_vrf": ipam.ResourceNetboxIpamVrf(), "netbox_tenancy_contact": tenancy.ResourceNetboxTenancyContact(), "netbox_tenancy_contact_assignment": tenancy.ResourceNetboxTenancyContactAssignment(), "netbox_tenancy_contact_group": tenancy.ResourceNetboxTenancyContactGroup(),