From 43d308959e63ae32aa927ccfbe70e48961ce1d25 Mon Sep 17 00:00:00 2001 From: Samuel Mutel <12967891+smutel@users.noreply.github.com> Date: Thu, 19 May 2022 15:59:08 +0200 Subject: [PATCH] feat: Add contact resource --- docs/data-sources/tenancy_contact.md | 21 ++ docs/resources/tenancy_contact.md | 95 ++++++ examples/main.tf | 61 ++++ netbox/data_netbox_tenancy_contact.go | 50 +++ netbox/provider.go | 2 + netbox/resource_netbox_tenancy_contact.go | 385 ++++++++++++++++++++++ 6 files changed, 614 insertions(+) create mode 100644 docs/data-sources/tenancy_contact.md create mode 100644 docs/resources/tenancy_contact.md create mode 100644 netbox/data_netbox_tenancy_contact.go create mode 100644 netbox/resource_netbox_tenancy_contact.go diff --git a/docs/data-sources/tenancy_contact.md b/docs/data-sources/tenancy_contact.md new file mode 100644 index 000000000..dcb571b81 --- /dev/null +++ b/docs/data-sources/tenancy_contact.md @@ -0,0 +1,21 @@ +# netbox\_tenancy\_contact Data Source + +Get info about tenancy contact from netbox. + +## Example Usage + +```hcl +data "netbox_tenancy_contact" "contact_test" { + name = "John Doe" +} +``` + +## Argument Reference + +The following arguments are supported: +* ``name`` - (Required) The name of the contact. + +## 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/docs/resources/tenancy_contact.md b/docs/resources/tenancy_contact.md new file mode 100644 index 000000000..d0113a908 --- /dev/null +++ b/docs/resources/tenancy_contact.md @@ -0,0 +1,95 @@ +# netbox\_tenancy\_contact Resource + +Manage a contact within Netbox. + +## Example Usage + +```hcl +resource "netbox_tenancy_contact" "contact_test" { + name = "John Doe" + title = "Someone in the world" + phone = "+330123456789" + email = "john.doe@unknown.com" + address = "Somewhere in the world" + comments = "Good contact" + + 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" + } +} +``` + +## Argument Reference + +The following arguments are supported: +* ``name`` - (Required) The name for this object. +* ``title`` - (Optional) The title for this object. +* ``phone`` - (Optional) The phone for this object. +* ``email`` - (Optional) The e-mail for this object. +* ``address`` - (Optional) The address for this object. +* ``comments`` - (Optional) Comments for this object. + +The ``custom_field`` block (optional) supports: +* ``name`` - (Required) Name of the existing custom resource to associate with this resource. +* ``type`` - (Required) Type of the existing custom resource to associate with this resource (text, integer, boolean, url, selection, multiple). +* ``value`` - (Required) Value of the existing custom resource to associate with this resource. + +The ``tag`` block (optional) 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. + +## Import + +Tenants can be imported by `id` e.g. + +``` +$ terraform import netbox_tenancy_contact.contact_test id +``` diff --git a/examples/main.tf b/examples/main.tf index 7c9775200..19363d275 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -473,3 +473,64 @@ resource "netbox_ipam_aggregate" "aggregate_test" { value = "0,1" } } + +resource "netbox_tenancy_contact" "contact" { + name = "John Doe" + title = "Someone in the world" + phone = "+330123456789" + email = "john.doe@unknown.com" + address = "Somewhere in the world" + comments = "Good contact" + + tag { + name = "tag1" + slug = "tag1" + } + + tag { + name = "tag2" + slug = "tag2" + } + + 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/data_netbox_tenancy_contact.go b/netbox/data_netbox_tenancy_contact.go new file mode 100644 index 000000000..5432010c9 --- /dev/null +++ b/netbox/data_netbox_tenancy_contact.go @@ -0,0 +1,50 @@ +package netbox + +import ( + "fmt" + "strconv" + + "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/tenancy" +) + +func dataNetboxTenancyContact() *schema.Resource { + return &schema.Resource{ + Read: dataNetboxTenancyContactRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 100), + }, + }, + } +} + +func dataNetboxTenancyContactRead(d *schema.ResourceData, m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + name := d.Get("name").(string) + + p := tenancy.NewTenancyContactsListParams().WithName(&name) + + list, err := client.Tenancy.TenancyContactsList(p, nil) + if err != nil { + return err + } + + if *list.Payload.Count < 1 { + return fmt.Errorf("Your query returned no results. " + + "Please change your search criteria and try again.") + } else if *list.Payload.Count > 1 { + return fmt.Errorf("Your query returned more than one result. " + + "Please try a more specific search criteria.") + } + + d.SetId(strconv.FormatInt(list.Payload.Results[0].ID, 10)) + + return nil +} diff --git a/netbox/provider.go b/netbox/provider.go index 2b70a21c7..21581fc3b 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -129,6 +129,7 @@ func Provider() *schema.Provider { "netbox_ipam_service": dataNetboxIpamService(), "netbox_ipam_vlan": dataNetboxIpamVlan(), "netbox_ipam_vlan_group": dataNetboxIpamVlanGroup(), + "netbox_tenancy_contact": dataNetboxTenancyContact(), "netbox_tenancy_tenant": dataNetboxTenancyTenant(), "netbox_tenancy_tenant_group": dataNetboxTenancyTenantGroup(), "netbox_virtualization_cluster": dataNetboxVirtualizationCluster(), @@ -140,6 +141,7 @@ func Provider() *schema.Provider { "netbox_ipam_service": resourceNetboxIpamService(), "netbox_ipam_vlan": resourceNetboxIpamVlan(), "netbox_ipam_vlan_group": resourceNetboxIpamVlanGroup(), + "netbox_tenancy_contact": resourceNetboxTenancyContact(), "netbox_tenancy_tenant": resourceNetboxTenancyTenant(), "netbox_tenancy_tenant_group": resourceNetboxTenancyTenantGroup(), "netbox_virtualization_interface": resourceNetboxVirtualizationInterface(), diff --git a/netbox/resource_netbox_tenancy_contact.go b/netbox/resource_netbox_tenancy_contact.go new file mode 100644 index 000000000..16355bff1 --- /dev/null +++ b/netbox/resource_netbox_tenancy_contact.go @@ -0,0 +1,385 @@ +package netbox + +import ( + "fmt" + "strconv" + + "github.com/go-openapi/strfmt" + "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/tenancy" + "github.com/smutel/go-netbox/netbox/models" +) + +func resourceNetboxTenancyContact() *schema.Resource { + return &schema.Resource{ + Create: resourceNetboxTenancyContactCreate, + Read: resourceNetboxTenancyContactRead, + Update: resourceNetboxTenancyContactUpdate, + Delete: resourceNetboxTenancyContactDelete, + Exists: resourceNetboxTenancyContactExists, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Optional: true, + Default: nil, + ValidateFunc: validation.StringLenBetween(1, 200), + }, + "comments": { + Type: schema.TypeString, + Optional: true, + Default: nil, + }, + "contact_group_id": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + }, + "custom_field": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"text", "integer", "boolean", + "date", "url", "selection", "multiple"}, false), + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "email": { + Type: schema.TypeString, + Optional: true, + Default: nil, + ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { + email := val.(string) + if len(email) >= 254 { + errs = append(errs, fmt.Errorf("Length of %q must be lower than 254, got: %d", key, len(email))) + } else if !strfmt.IsEmail(email) { + errs = append(errs, fmt.Errorf("%q is not a valid email", key)) + } + return + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 100), + }, + "phone": { + Type: schema.TypeString, + Optional: true, + Default: nil, + ValidateFunc: validation.StringLenBetween(1, 50), + }, + "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, + }, + }, + }, + }, + "title": { + Type: schema.TypeString, + Optional: true, + Default: nil, + ValidateFunc: validation.StringLenBetween(1, 100), + }, + }, + } +} + +func resourceNetboxTenancyContactCreate(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + address := d.Get("address").(string) + comments := d.Get("comments").(string) + groupID := int64(d.Get("contact_group_id").(int)) + resourceCustomFields := d.Get("custom_field").(*schema.Set).List() + customFields := convertCustomFieldsFromTerraformToAPI(nil, resourceCustomFields) + email := strfmt.Email(d.Get("email").(string)) + name := d.Get("name").(string) + phone := d.Get("phone").(string) + tags := d.Get("tag").(*schema.Set).List() + title := d.Get("title").(string) + + newResource := &models.WritableContact{ + Address: address, + Comments: comments, + CustomFields: &customFields, + Email: email, + Name: &name, + Phone: phone, + Tags: convertTagsToNestedTags(tags), + Title: title, + } + + if groupID != 0 { + newResource.Group = &groupID + } + + resource := tenancy.NewTenancyContactsCreateParams().WithData(newResource) + + resourceCreated, err := client.Tenancy.TenancyContactsCreate(resource, nil) + if err != nil { + return err + } + + d.SetId(strconv.FormatInt(resourceCreated.Payload.ID, 10)) + + return resourceNetboxTenancyContactRead(d, m) +} + +func resourceNetboxTenancyContactRead(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + resourceID := d.Id() + params := tenancy.NewTenancyContactsListParams().WithID(&resourceID) + resources, err := client.Tenancy.TenancyContactsList(params, nil) + if err != nil { + return err + } + + for _, resource := range resources.Payload.Results { + if strconv.FormatInt(resource.ID, 10) == d.Id() { + var address interface{} + if resource.Address == "" { + address = nil + } else { + address = resource.Address + } + + if err = d.Set("address", address); err != nil { + return err + } + + var comments interface{} + if resource.Comments == "" { + comments = nil + } else { + comments = resource.Comments + } + + if err = d.Set("comments", comments); err != nil { + return err + } + + if resource.Group == nil { + if err = d.Set("contact_group_id", 0); err != nil { + return err + } + } else { + if err = d.Set("contact_group_id", resource.Group.ID); err != nil { + return err + } + } + + resourceCustomFields := d.Get("custom_field").(*schema.Set).List() + customFields := updateCustomFieldsFromAPI(resourceCustomFields, resource.CustomFields) + + if err = d.Set("custom_field", customFields); err != nil { + return err + } + + var email interface{} + if resource.Email.String() == "" { + email = nil + } else { + email = resource.Email.String() + } + + if err = d.Set("email", email); err != nil { + return err + } + + if err = d.Set("name", resource.Name); err != nil { + return err + } + + var phone interface{} + if resource.Phone == "" { + phone = nil + } else { + phone = resource.Phone + } + + if err = d.Set("phone", phone); err != nil { + return err + } + + if err = d.Set("tag", convertNestedTagsToTags(resource.Tags)); err != nil { + return err + } + + var title interface{} + if resource.Title == "" { + title = nil + } else { + title = resource.Title + } + + if err = d.Set("title", title); err != nil { + return err + } + + return nil + } + } + + d.SetId("") + return nil +} + +func resourceNetboxTenancyContactUpdate(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + params := &models.WritableContact{} + + // Required parameters + name := d.Get("name").(string) + params.Name = &name + + if d.HasChange("address") { + if address, exist := d.GetOk("address"); exist { + params.Address = address.(string) + } else { + params.Address = " " + } + } + + if d.HasChange("comments") { + if comments, exist := d.GetOk("comments"); exist { + params.Comments = comments.(string) + } else { + params.Comments = " " + } + } + + if d.HasChange("contact_group_id") { + groupID := int64(d.Get("contact_group_id").(int)) + params.Group = &groupID + } + + 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("email") { + if email, exist := d.GetOk("email"); exist { + params.Email = strfmt.Email(email.(string)) + } else { + params.Email = strfmt.Email(" ") + } + } + + if d.HasChange("phone") { + if phone, exist := d.GetOk("phone"); exist { + params.Phone = phone.(string) + } else { + params.Phone = " " + } + } + + tags := d.Get("tag").(*schema.Set).List() + params.Tags = convertTagsToNestedTags(tags) + + if d.HasChange("title") { + if title, exist := d.GetOk("title"); exist { + params.Title = title.(string) + } else { + params.Title = " " + } + } + + resource := tenancy.NewTenancyContactsPartialUpdateParams().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.Tenancy.TenancyContactsPartialUpdate(resource, nil) + if err != nil { + return err + } + + return resourceNetboxTenancyContactRead(d, m) +} + +func resourceNetboxTenancyContactDelete(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + resourceExists, err := resourceNetboxTenancyContactExists(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") + } + + p := tenancy.NewTenancyContactsDeleteParams().WithID(id) + if _, err := client.Tenancy.TenancyContactsDelete(p, nil); err != nil { + return err + } + + return nil +} + +func resourceNetboxTenancyContactExists(d *schema.ResourceData, + m interface{}) (b bool, + e error) { + client := m.(*netboxclient.NetBoxAPI) + resourceExist := false + + resourceID := d.Id() + params := tenancy.NewTenancyContactsListParams().WithID(&resourceID) + resources, err := client.Tenancy.TenancyContactsList(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 +}