From cdaa29f4b9d93a418c47e28ae603288c91d5ce29 Mon Sep 17 00:00:00 2001 From: smutel Date: Sat, 19 Dec 2020 19:14:27 +0100 Subject: [PATCH] feat: Add resource IPAM aggregate --- README.md | 5 +- docs/resources/ipam_aggregate.md | 51 ++++ examples/main.tf | 56 +++-- netbox/provider.go | 1 + netbox/resource_netbox_ipam_aggregate.go | 305 +++++++++++++++++++++++ 5 files changed, 401 insertions(+), 17 deletions(-) create mode 100644 docs/resources/ipam_aggregate.md create mode 100644 netbox/resource_netbox_ipam_aggregate.go diff --git a/README.md b/README.md index 2f83e6415..3c5c657e5 100644 --- a/README.md +++ b/README.md @@ -118,5 +118,6 @@ $ terraform init & terraform apply ``` ## Known bugs in external project which can impact this provider -* [Issue 85 in project go-netbox](https://github.com/netbox-community/go-netbox/issues/85) -* [Issue 54 in project go-netbox](https://github.com/netbox-community/go-netbox/issues/54) +* *Closed* - [Issue 85 in project go-netbox](https://github.com/netbox-community/go-netbox/issues/85) +* *Closed* - [Issue 54 in project go-netbox](https://github.com/netbox-community/go-netbox/issues/54) +* *Open* - [Issue 115 in project go-netbox](https://github.com/netbox-community/go-netbox/issues/115) diff --git a/docs/resources/ipam_aggregate.md b/docs/resources/ipam_aggregate.md new file mode 100644 index 000000000..2b6823f9c --- /dev/null +++ b/docs/resources/ipam_aggregate.md @@ -0,0 +1,51 @@ +# netbox\_ipam\_aggregate Resource + +Manage an aggregate within Netbox. + +## Example Usage + +```hcl +resource "netbox_ipam_aggregate" "aggregate_test" { + prefix = "192.168.56.0/24" + rir_id = 1 + date_created = "2020-12-21" + + tag { + name = "tag1" + slug = "tag1" + } + + custom_fields = { + cf_boolean = "true" + cf_date = "2020-12-25" + cf_integer = "10" + cf_selection = "1" + cf_text = "Some text" + cf_url = "https://github.com" + } +} +``` + +## Argument Reference + +The following arguments are supported: +* ``custom_fields`` - (Optional) Custom Field Keys and Values for this object + * For boolean, use the string value "true" or "false" + * For data, use the string format "YYYY-MM-DD" + * For integer, use the value between double quote "10" + * For selection, use the level id + * For text, use the string value + * For URL, use the URL as string +* ``date_added`` - (Optional) Date when this aggregate was added. Format *YYYY-MM-DD*. +* ``description`` - (Optional) The description of this object. +* ``prefix`` - (Required) The prefix (with mask) used for this object. +* ``rir_id`` - (Required) The RIR id linked to this object. + +The ``tag`` block supports: +* ``name`` - (Required) Name of the existing tag to associate with this resource. +* ``slug`` - (Required) Slug of the existing tag to associate with this resource. + +## Attributes Reference + +In addition to the above arguments, the following attributes are exported: +* ``id`` - The id (ref in Netbox) of this object. diff --git a/examples/main.tf b/examples/main.tf index f9d89ee81..7f7c25a82 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -15,12 +15,12 @@ resource "netbox_tenancy_tenant" "tenant_test" { } custom_fields = { - cf_boolean = "true" - cf_date = "2020-12-25" - cf_integer = "10" + cf_boolean = "true" + cf_date = "2020-12-25" + cf_integer = "10" cf_selection = "1" - cf_text = "Some text" - cf_url = "https://github.com" + cf_text = "Some text" + cf_url = "https://github.com" } } @@ -67,12 +67,12 @@ resource "netbox_ipam_vlan" "vlan_test" { } custom_fields = { - cf_boolean = "true" - cf_date = "2020-12-25" - cf_integer = "10" + cf_boolean = "true" + cf_date = "2020-12-25" + cf_integer = "10" cf_selection = "1" - cf_text = "Some text" - cf_url = "https://github.com" + cf_text = "Some text" + cf_url = "https://github.com" } } @@ -136,12 +136,12 @@ resource "netbox_virtualization_vm" "vm_test" { } custom_fields = { - cf_boolean = "true" - cf_date = "2020-12-25" - cf_integer = "10" + cf_boolean = "true" + cf_date = "2020-12-25" + cf_integer = "10" cf_selection = "1" - cf_text = "Some text" - cf_url = "https://github.com" + cf_text = "Some text" + cf_url = "https://github.com" } } @@ -151,3 +151,29 @@ resource "netbox_virtualization_interface" "interface_test" { mac_address = "AA:AA:AA:AA:AA:AA" description = "Interface de test" } + +resource "netbox_ipam_aggregate" "aggregate_test" { + prefix = "192.167.0.0/24" + rir_id = 1 + date_added = "2020-12-21" + description = "Aggregate created by terraform" + + tag { + name = "tag1" + slug = "tag1" + } + + tag { + name = "tag2" + slug = "tag2" + } + + custom_fields = { + cf_boolean = "true" + cf_date = "2020-12-25" + cf_integer = "10" + cf_selection = "1" + cf_text = "Some text" + cf_url = "https://github.com" + } +} diff --git a/netbox/provider.go b/netbox/provider.go index 7fde8d438..c4e79a447 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -47,6 +47,7 @@ func Provider() *schema.Provider { "netbox_virtualization_cluster": dataNetboxVirtualizationCluster(), }, ResourcesMap: map[string]*schema.Resource{ + "netbox_ipam_aggregate": resourceNetboxIpamAggregate(), "netbox_ipam_prefix": resourceNetboxIpamPrefix(), "netbox_ipam_ip_addresses": resourceNetboxIpamIPAddresses(), "netbox_ipam_vlan": resourceNetboxIpamVlan(), diff --git a/netbox/resource_netbox_ipam_aggregate.go b/netbox/resource_netbox_ipam_aggregate.go new file mode 100644 index 000000000..3061bd303 --- /dev/null +++ b/netbox/resource_netbox_ipam_aggregate.go @@ -0,0 +1,305 @@ +package netbox + +import ( + "fmt" + "strconv" + "time" + + "github.com/go-openapi/strfmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + netboxclient "github.com/netbox-community/go-netbox/netbox/client" + "github.com/netbox-community/go-netbox/netbox/client/ipam" + "github.com/netbox-community/go-netbox/netbox/models" +) + +func resourceNetboxIpamAggregate() *schema.Resource { + return &schema.Resource{ + Create: resourceNetboxIpamAggregateCreate, + Read: resourceNetboxIpamAggregateRead, + Update: resourceNetboxIpamAggregateUpdate, + Delete: resourceNetboxIpamAggregateDelete, + Exists: resourceNetboxIpamAggregateExists, + + Schema: map[string]*schema.Schema{ + "custom_fields": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + // terraform default behavior sees a difference between null and an empty string + // therefore we override the default, because null in terraform results in empty string in netbox + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // function is called for each member of map + // including additional call on the amount of entries + // we ignore the count, because the actual state always returns the amount of existing custom_fields and all are optional in terraform + if k == CustomFieldsRegex { + return true + } + return old == new + }, + }, + "date_added": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { + v := val.(string) + _, err := time.Parse("2006-01-02", v) + + if err != nil { + errs = append(errs, fmt.Errorf("date_added in not in the good format YYYY-MM-DD")) + } + return + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Default: " ", + ValidateFunc: validation.StringLenBetween(1, 200), + }, + "prefix": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsCIDRNetwork(0, 256), + }, + "rir_id": { + Type: schema.TypeInt, + Required: true, + }, + "tag": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "slug": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + } +} + +func resourceNetboxIpamAggregateCreate(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + resourceCustomFields := d.Get("custom_fields").(map[string]interface{}) + customFields := convertCustomFieldsFromTerraformToAPICreate(resourceCustomFields) + dateAdded := d.Get("date_added").(string) + description := d.Get("description").(string) + prefix := d.Get("prefix").(string) + rirID := int64(d.Get("rir_id").(int)) + tags := d.Get("tag").(*schema.Set).List() + + newResource := &models.WritableAggregate{ + CustomFields: &customFields, + Description: description, + Prefix: &prefix, + Tags: convertTagsToNestedTags(tags), + } + + if rirID != 0 { + newResource.Rir = &rirID + } + + if dateAdded != "" { + dateAddedTime, err := time.Parse("2006-01-02", dateAdded) + if err != nil { + return err + } + + dateAddedFmt := strfmt.Date(dateAddedTime) + newResource.DateAdded = &dateAddedFmt + } + + resource := ipam.NewIpamAggregatesCreateParams().WithData(newResource) + + resourceCreated, err := client.Ipam.IpamAggregatesCreate(resource, nil) + if err != nil { + return err + } + + d.SetId(strconv.FormatInt(resourceCreated.Payload.ID, 10)) + + return resourceNetboxIpamAggregateRead(d, m) +} + +func resourceNetboxIpamAggregateRead(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + resourceID := d.Id() + params := ipam.NewIpamAggregatesListParams().WithID(&resourceID) + resources, err := client.Ipam.IpamAggregatesList(params, nil) + if err != nil { + return err + } + + for _, resource := range resources.Payload.Results { + if strconv.FormatInt(resource.ID, 10) == d.Id() { + customFields := convertCustomFieldsFromAPIToTerraform(resource.CustomFields) + + if err = d.Set("custom_fields", customFields); err != nil { + return err + } + + var dateAdded string + if resource.DateAdded == nil { + dateAdded = "" + } else { + dateAdded = resource.DateAdded.String() + } + + if err = d.Set("date_added", dateAdded); err != nil { + return err + } + + var description string + if resource.Description == "" { + description = " " + } else { + description = resource.Description + } + + if err = d.Set("description", description); err != nil { + return err + } + + if err = d.Set("prefix", resource.Prefix); err != nil { + return err + } + + if err = d.Set("tag", convertNestedTagsToTags(resource.Tags)); err != nil { + return err + } + + if resource.Rir == nil { + if err = d.Set("rir_id", nil); err != nil { + return err + } + } else { + if err = d.Set("rir_id", resource.Rir.ID); err != nil { + return err + } + } + + return nil + } + } + + d.SetId("") + return nil +} + +func resourceNetboxIpamAggregateUpdate(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + params := &models.WritableAggregate{} + + // Required parameters + prefix := d.Get("prefix").(string) + params.Prefix = &prefix + + rirID := int64(d.Get("rir_id").(int)) + params.Rir = &rirID + + if d.HasChange("custom_fields") { + stateCustomFields, resourceCustomFields := d.GetChange("custom_fields") + customFields := convertCustomFieldsFromTerraformToAPIUpdate(stateCustomFields, resourceCustomFields) + params.CustomFields = &customFields + } + + if d.HasChange("date_added") { + dateAdded := d.Get("date_added").(string) + + if dateAdded != "" { + dateAddedTime, err := time.Parse("2006-01-02", dateAdded) + if err != nil { + return err + } + + dateAddedFmt := strfmt.Date(dateAddedTime) + params.DateAdded = &dateAddedFmt + } + } + + if d.HasChange("description") { + description := d.Get("description").(string) + params.Description = description + } + + tags := d.Get("tag").(*schema.Set).List() + params.Tags = convertTagsToNestedTags(tags) + + resource := ipam.NewIpamAggregatesPartialUpdateParams().WithData(params) + + resourceID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return fmt.Errorf("Unable to convert ID into int64") + } + + resource.SetID(resourceID) + + _, err = client.Ipam.IpamAggregatesPartialUpdate(resource, nil) + if err != nil { + return err + } + + return resourceNetboxIpamAggregateRead(d, m) +} + +func resourceNetboxIpamAggregateDelete(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + resourceExists, err := resourceNetboxIpamAggregateExists(d, m) + if err != nil { + return err + } + + if !resourceExists { + return nil + } + + id, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return fmt.Errorf("Unable to convert ID into int64") + } + + resource := ipam.NewIpamAggregatesDeleteParams().WithID(id) + if _, err := client.Ipam.IpamAggregatesDelete(resource, nil); err != nil { + return err + } + + return nil +} + +func resourceNetboxIpamAggregateExists(d *schema.ResourceData, + m interface{}) (b bool, e error) { + client := m.(*netboxclient.NetBoxAPI) + resourceExist := false + + resourceID := d.Id() + params := ipam.NewIpamAggregatesListParams().WithID(&resourceID) + resources, err := client.Ipam.IpamAggregatesList(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 +}