diff --git a/examples/resources/netbox_ipam_ip_range/resource.tf b/examples/resources/netbox_ipam_ip_range/resource.tf new file mode 100644 index 000000000..e185ce91d --- /dev/null +++ b/examples/resources/netbox_ipam_ip_range/resource.tf @@ -0,0 +1,54 @@ +resource "netbox_ipam_ip_range" "range_test" { + start_address = "192.168.56.1/24" + end_address = "192.168.56.100/24" + description = "Range created by terraform" + role_id = data.netbox_ipam_roles.vlan_role_production.id + status = "active" + + 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 = "selection" + value = "1" + } + + custom_field { + name = "cf_url" + type = "url" + value = "https://github.com" + } + + custom_field { + name = "cf_multiple_selection" + type = "multiple" + value = "0,1" + } +} diff --git a/netbox/provider.go b/netbox/provider.go index 316924151..0fb549ddd 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -158,6 +158,7 @@ func Provider() *schema.Provider { ResourcesMap: map[string]*schema.Resource{ "netbox_ipam_aggregate": resourceNetboxIpamAggregate(), "netbox_ipam_ip_addresses": resourceNetboxIpamIPAddresses(), + "netbox_ipam_ip_range": resourceNetboxIpamIPRange(), "netbox_ipam_prefix": resourceNetboxIpamPrefix(), "netbox_ipam_service": resourceNetboxIpamService(), "netbox_ipam_vlan": resourceNetboxIpamVlan(), diff --git a/netbox/resource_netbox_ipam_ip_range.go b/netbox/resource_netbox_ipam_ip_range.go new file mode 100644 index 000000000..a7e59939e --- /dev/null +++ b/netbox/resource_netbox_ipam_ip_range.go @@ -0,0 +1,392 @@ +package netbox + +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/netbox/client" + "github.com/smutel/go-netbox/netbox/client/ipam" + "github.com/smutel/go-netbox/netbox/models" +) + +func resourceNetboxIpamIPRange() *schema.Resource { + return &schema.Resource{ + Description: "Manage an ip range (ipam module) within Netbox.", + CreateContext: resourceNetboxIpamIPRangeCreate, + ReadContext: resourceNetboxIpamIPRangeRead, + UpdateContext: resourceNetboxIpamIPRangeUpdate, + DeleteContext: resourceNetboxIpamIPRangeDelete, + Exists: resourceNetboxIpamIPRangeExists, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "content_type": { + Type: schema.TypeString, + Computed: true, + Description: "The content type of this prefix (ipam module).", + }, + "custom_field": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the existing custom field.", + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"text", "integer", "boolean", + "date", "url", "selection", "multiple"}, false), + Description: "Type of the existing custom field (text, integer, boolean, url, selection, multiple).", + }, + "value": { + Type: schema.TypeString, + Required: true, + Description: "Value of the existing custom field.", + }, + }, + }, + Description: "Existing custom fields to associate to this prefix (ipam module).", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Default: nil, + ValidateFunc: validation.StringLenBetween(1, 100), + Description: "The description of this prefix (ipam module).", + }, + "start_address": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsCIDR, + Description: "The first address of the ip range", + }, + "end_address": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsCIDR, + Description: "The last address of the ip range", + }, + "size": { + Type: schema.TypeInt, + Computed: true, + Description: "Number of addresses in the ip range", + }, + "role_id": { + Type: schema.TypeInt, + Optional: true, + Description: "ID of the role attached to this prefix (ipam module).", + }, + "status": { + Type: schema.TypeString, + Optional: true, + Default: "active", + ValidateFunc: validation.StringInSlice([]string{"active", + "reserved", "deprecated"}, false), + Description: "Status among active, reserved, deprecated (active by default).", + }, + "tag": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the existing tag.", + }, + "slug": { + Type: schema.TypeString, + Required: true, + Description: "Slug of the existing tag.", + }, + }, + }, + Description: "Existing tag to associate to this prefix (ipam module).", + }, + "tenant_id": { + Type: schema.TypeInt, + Optional: true, + Description: "ID of the tenant where this prefix (ipam module) is attached.", + }, + "vrf_id": { + Type: schema.TypeInt, + Optional: true, + Description: "ID of the vrf attached to this prefix (ipam module).", + }, + }, + } +} + +func resourceNetboxIpamIPRangeCreate(ctx context.Context, d *schema.ResourceData, + m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + + startAddress := d.Get("start_address").(string) + endAddress := d.Get("end_address").(string) + + resourceCustomFields := d.Get("custom_field").(*schema.Set).List() + customFields := convertCustomFieldsFromTerraformToAPI(nil, resourceCustomFields) + description := d.Get("description").(string) + roleID := int64(d.Get("role_id").(int)) + status := d.Get("status").(string) + tags := d.Get("tag").(*schema.Set).List() + tenantID := int64(d.Get("tenant_id").(int)) + vrfID := int64(d.Get("vrf_id").(int)) + + newResource := &models.WritableIPRange{ + CustomFields: &customFields, + Description: description, + EndAddress: &endAddress, + StartAddress: &startAddress, + Status: status, + Tags: convertTagsToNestedTags(tags), + } + + if roleID != 0 { + newResource.Role = &roleID + } + + if tenantID != 0 { + newResource.Tenant = &tenantID + } + + if vrfID != 0 { + newResource.Vrf = &vrfID + } + + resource := ipam.NewIpamIPRangesCreateParams().WithData(newResource) + + resourceCreated, err := client.Ipam.IpamIPRangesCreate(resource, nil) + if err != nil { + return diag.FromErr(err) + } + + rangeid := &resourceCreated.Payload.ID + d.SetId(strconv.FormatInt(*rangeid, 10)) + + return resourceNetboxIpamIPRangeRead(ctx, d, m) +} + +func resourceNetboxIpamIPRangeRead(ctx context.Context, d *schema.ResourceData, + m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + + resourceID := d.Id() + params := ipam.NewIpamIPRangesListParams().WithID(&resourceID) + resources, err := client.Ipam.IpamIPRangesList(params, nil) + if err != nil { + return diag.FromErr(err) + } + + for _, resource := range resources.Payload.Results { + if strconv.FormatInt(resource.ID, 10) == d.Id() { + if err = d.Set("content_type", convertURIContentType(resource.URL)); err != nil { + return diag.FromErr(err) + } + + resourceCustomFields := d.Get("custom_field").(*schema.Set).List() + customFields := updateCustomFieldsFromAPI(resourceCustomFields, resource.CustomFields) + + if err = d.Set("custom_field", customFields); err != nil { + return diag.FromErr(err) + } + + var description interface{} + if resource.Description == "" { + description = nil + } else { + description = resource.Description + } + + if err = d.Set("description", description); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("start_address", resource.StartAddress); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("end_address", resource.EndAddress); err != nil { + return diag.FromErr(err) + } + if err = d.Set("size", resource.Size); err != nil { + return diag.FromErr(err) + } + + if resource.Role == nil { + if err = d.Set("role_id", nil); err != nil { + return diag.FromErr(err) + } + } else { + if err = d.Set("role_id", resource.Role.ID); err != nil { + return diag.FromErr(err) + } + } + + if resource.Status == nil { + if err = d.Set("status", nil); err != nil { + return diag.FromErr(err) + } + } else { + if err = d.Set("status", resource.Status.Value); err != nil { + return diag.FromErr(err) + } + } + + if err = d.Set("tag", convertNestedTagsToTags(resource.Tags)); err != nil { + return diag.FromErr(err) + } + + if resource.Tenant == nil { + if err = d.Set("tenant_id", nil); err != nil { + return diag.FromErr(err) + } + } else { + if err = d.Set("tenant_id", resource.Tenant.ID); err != nil { + return diag.FromErr(err) + } + } + + if resource.Vrf == nil { + if err = d.Set("vrf_id", nil); err != nil { + return diag.FromErr(err) + } + } else { + if err = d.Set("vrf_id", resource.Vrf.ID); err != nil { + return diag.FromErr(err) + } + } + + return nil + } + } + + d.SetId("") + return nil +} + +func resourceNetboxIpamIPRangeUpdate(ctx context.Context, d *schema.ResourceData, + m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + params := &models.WritableIPRange{} + + // Required parameters + startAddress := d.Get("start_address").(string) + endAddress := d.Get("end_address").(string) + params.StartAddress = &startAddress + params.EndAddress = &endAddress + + if d.HasChange("custom_field") { + stateCustomFields, resourceCustomFields := d.GetChange("custom_field") + customFields := convertCustomFieldsFromTerraformToAPI(stateCustomFields.(*schema.Set).List(), resourceCustomFields.(*schema.Set).List()) + params.CustomFields = &customFields + } + + if d.HasChange("description") { + if description, exist := d.GetOk("description"); exist { + params.Description = description.(string) + } else { + params.Description = " " + } + } + + if d.HasChange("role_id") { + roleID := int64(d.Get("role_id").(int)) + if roleID != 0 { + params.Role = &roleID + } + } + + if d.HasChange("status") { + params.Status = d.Get("status").(string) + } + + tags := d.Get("tag").(*schema.Set).List() + params.Tags = convertTagsToNestedTags(tags) + + if d.HasChange("tenant_id") { + tenantID := int64(d.Get("tenant_id").(int)) + if tenantID != 0 { + params.Tenant = &tenantID + } + } + + if d.HasChange("vrf_id") { + vrfID := int64(d.Get("vrf_id").(int)) + if vrfID != 0 { + params.Vrf = &vrfID + } + } + + resource := ipam.NewIpamIPRangesPartialUpdateParams().WithData(params) + + resourceID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return diag.Errorf("Unable to convert ID into int64") + } + + resource.SetID(resourceID) + + _, err = client.Ipam.IpamIPRangesPartialUpdate(resource, nil) + if err != nil { + return diag.FromErr(err) + } + + return resourceNetboxIpamIPRangeRead(ctx, d, m) +} + +func resourceNetboxIpamIPRangeDelete(ctx context.Context, d *schema.ResourceData, + m interface{}) diag.Diagnostics { + client := m.(*netboxclient.NetBoxAPI) + + resourceExists, err := resourceNetboxIpamIPRangeExists(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.NewIpamIPRangesDeleteParams().WithID(id) + if _, err := client.Ipam.IpamIPRangesDelete(resource, nil); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceNetboxIpamIPRangeExists(d *schema.ResourceData, + m interface{}) (b bool, e error) { + client := m.(*netboxclient.NetBoxAPI) + resourceExist := false + + resourceID := d.Id() + params := ipam.NewIpamIPRangesListParams().WithID(&resourceID) + resources, err := client.Ipam.IpamIPRangesList(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 +}