diff --git a/docs/data-sources/tenancy_contact_group.md b/docs/data-sources/tenancy_contact_group.md new file mode 100644 index 000000000..be21b271c --- /dev/null +++ b/docs/data-sources/tenancy_contact_group.md @@ -0,0 +1,21 @@ +# netbox\_tenancy\_contact\_group Data Source + +Get info about tenancy contact groups from netbox. + +## Example Usage + +```hcl +data "netbox_tenancy_contact_group" "contact_group_test" { + slug = "TestContactGroup" +} +``` + +## Argument Reference + +The following arguments are supported: +* ``slug`` - (Required) The slug of the tenancy contact groups. + +## 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 index d0113a908..92c721dfc 100644 --- a/docs/resources/tenancy_contact.md +++ b/docs/resources/tenancy_contact.md @@ -12,6 +12,7 @@ resource "netbox_tenancy_contact" "contact_test" { email = "john.doe@unknown.com" address = "Somewhere in the world" comments = "Good contact" + contact_group_id = netbox_tenancy_contact_group.contact_group_02.id tag { name = "tag1" @@ -71,6 +72,7 @@ The following arguments are supported: * ``email`` - (Optional) The e-mail for this object. * ``address`` - (Optional) The address for this object. * ``comments`` - (Optional) Comments for this object. +* ``contact_group_id`` - (Optional) ID of the group where this object belongs to. The ``custom_field`` block (optional) supports: * ``name`` - (Required) Name of the existing custom resource to associate with this resource. diff --git a/docs/resources/tenancy_contact_group.md b/docs/resources/tenancy_contact_group.md new file mode 100644 index 000000000..4219fb05c --- /dev/null +++ b/docs/resources/tenancy_contact_group.md @@ -0,0 +1,91 @@ +# netbox\_tenancy\_contact\_group Resource + +Manage a contact group within Netbox. + +## Example Usage + +```hcl +resource "netbox_tenancy_contact_group" "contact_group_test" { + description = "Contact group created by terraform" + name = "TestContactGroup" + parent_id = 10 + slug = "TestContactGroup" + + 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: +* ``description`` - (Optional) Description for this object. +* ``name`` - (Required) The name for this object. +* ``parent_id`` - (Optional) ID of the parent. +* ``slug`` - (Required) The slug 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 + +Contact groups can be imported by `id` e.g. + +``` +$ terraform import netbox_tenancy_contact_group.contact_group_test id +``` diff --git a/examples/main.tf b/examples/main.tf index 19363d275..5011ac868 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -481,6 +481,7 @@ resource "netbox_tenancy_contact" "contact" { email = "john.doe@unknown.com" address = "Somewhere in the world" comments = "Good contact" + contact_group_id = netbox_tenancy_contact_group.contact_group_02.id tag { name = "tag1" @@ -534,3 +535,68 @@ resource "netbox_tenancy_contact" "contact" { value = "0,1" } } + +resource "netbox_tenancy_contact_group" "contact_group_01" { + name = "A contact group" + slug = "cg01" + description = "A contact group" + + 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" + } +} + +resource "netbox_tenancy_contact_group" "contact_group_02" { + name = "Another contact group" + slug = "cg02" + description = "Another contact group" + parent_id = netbox_tenancy_contact_group.contact_group_01.id +} diff --git a/netbox/data_netbox_tenancy_contact_group.go b/netbox/data_netbox_tenancy_contact_group.go new file mode 100644 index 000000000..1504fcdaa --- /dev/null +++ b/netbox/data_netbox_tenancy_contact_group.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 dataNetboxTenancyContactGroup() *schema.Resource { + return &schema.Resource{ + Read: dataNetboxTenancyContactGroupRead, + + Schema: map[string]*schema.Schema{ + "slug": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 100), + }, + }, + } +} + +func dataNetboxTenancyContactGroupRead(d *schema.ResourceData, m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + slug := d.Get("slug").(string) + + p := tenancy.NewTenancyContactGroupsListParams().WithSlug(&slug) + + list, err := client.Tenancy.TenancyContactGroupsList(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 21581fc3b..205211ae0 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -130,6 +130,7 @@ func Provider() *schema.Provider { "netbox_ipam_vlan": dataNetboxIpamVlan(), "netbox_ipam_vlan_group": dataNetboxIpamVlanGroup(), "netbox_tenancy_contact": dataNetboxTenancyContact(), + "netbox_tenancy_contact_group": dataNetboxTenancyContactGroup(), "netbox_tenancy_tenant": dataNetboxTenancyTenant(), "netbox_tenancy_tenant_group": dataNetboxTenancyTenantGroup(), "netbox_virtualization_cluster": dataNetboxVirtualizationCluster(), @@ -142,6 +143,7 @@ func Provider() *schema.Provider { "netbox_ipam_vlan": resourceNetboxIpamVlan(), "netbox_ipam_vlan_group": resourceNetboxIpamVlanGroup(), "netbox_tenancy_contact": resourceNetboxTenancyContact(), + "netbox_tenancy_contact_group": resourceNetboxTenancyContactGroup(), "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 index 16355bff1..8ee33e26d 100644 --- a/netbox/resource_netbox_tenancy_contact.go +++ b/netbox/resource_netbox_tenancy_contact.go @@ -283,7 +283,11 @@ func resourceNetboxTenancyContactUpdate(d *schema.ResourceData, if d.HasChange("contact_group_id") { groupID := int64(d.Get("contact_group_id").(int)) - params.Group = &groupID + if groupID == 0 { + params.Group = nil + } else { + params.Group = &groupID + } } if d.HasChange("custom_field") { diff --git a/netbox/resource_netbox_tenancy_contact_group.go b/netbox/resource_netbox_tenancy_contact_group.go new file mode 100644 index 000000000..b2de5fbc1 --- /dev/null +++ b/netbox/resource_netbox_tenancy_contact_group.go @@ -0,0 +1,284 @@ +package netbox + +import ( + "fmt" + "regexp" + "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" + "github.com/smutel/go-netbox/netbox/models" +) + +func resourceNetboxTenancyContactGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceNetboxTenancyContactGroupCreate, + Read: resourceNetboxTenancyContactGroupRead, + Update: resourceNetboxTenancyContactGroupUpdate, + Delete: resourceNetboxTenancyContactGroupDelete, + Exists: resourceNetboxTenancyContactGroupExists, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "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, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Default: nil, + ValidateFunc: validation.StringLenBetween(1, 100), + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 100), + }, + "parent_id": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + }, + "slug": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile("^[-a-zA-Z0-9_]{1,50}$"), + "Must be like ^[-a-zA-Z0-9_]{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, + }, + }, + }, + }, + }, + } +} + +func resourceNetboxTenancyContactGroupCreate(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + resourceCustomFields := d.Get("custom_field").(*schema.Set).List() + customFields := convertCustomFieldsFromTerraformToAPI(nil, resourceCustomFields) + description := d.Get("description").(string) + name := d.Get("name").(string) + parentID := int64(d.Get("parent_id").(int)) + slug := d.Get("slug").(string) + tags := d.Get("tag").(*schema.Set).List() + + newResource := &models.WritableContactGroup{ + CustomFields: &customFields, + Description: description, + Name: &name, + Slug: &slug, + Tags: convertTagsToNestedTags(tags), + } + + if parentID != 0 { + newResource.Parent = &parentID + } + + resource := tenancy.NewTenancyContactGroupsCreateParams().WithData(newResource) + + resourceCreated, err := client.Tenancy.TenancyContactGroupsCreate(resource, nil) + if err != nil { + return err + } + + d.SetId(strconv.FormatInt(resourceCreated.Payload.ID, 10)) + + return resourceNetboxTenancyContactGroupRead(d, m) +} + +func resourceNetboxTenancyContactGroupRead(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + resourceID := d.Id() + params := tenancy.NewTenancyContactGroupsListParams().WithID(&resourceID) + resources, err := client.Tenancy.TenancyContactGroupsList(params, nil) + if err != nil { + return err + } + + for _, resource := range resources.Payload.Results { + if strconv.FormatInt(resource.ID, 10) == d.Id() { + 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 description interface{} + if resource.Description == "" { + description = nil + } else { + description = resource.Description + } + + if err = d.Set("description", description); err != nil { + return err + } + + if err = d.Set("name", resource.Name); err != nil { + return err + } + + if resource.Parent == nil { + if err = d.Set("parent_id", 0); err != nil { + return err + } + } else { + if err = d.Set("parent_id", resource.Parent.ID); err != nil { + return err + } + } + + if err = d.Set("slug", resource.Slug); err != nil { + return err + } + + if err = d.Set("tag", convertNestedTagsToTags(resource.Tags)); err != nil { + return err + } + + return nil + } + } + + d.SetId("") + return nil +} + +func resourceNetboxTenancyContactGroupUpdate(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + params := &models.WritableContactGroup{} + + // Required parameters + name := d.Get("name").(string) + params.Name = &name + + slug := d.Get("slug").(string) + params.Slug = &slug + + parentID := int64(d.Get("parent_id").(int)) + params.Parent = &parentID + + 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 = " " + } + } + + tags := d.Get("tag").(*schema.Set).List() + params.Tags = convertTagsToNestedTags(tags) + + resource := tenancy.NewTenancyContactGroupsPartialUpdateParams().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.TenancyContactGroupsPartialUpdate(resource, nil) + if err != nil { + return err + } + + return resourceNetboxTenancyContactGroupRead(d, m) +} + +func resourceNetboxTenancyContactGroupDelete(d *schema.ResourceData, + m interface{}) error { + client := m.(*netboxclient.NetBoxAPI) + + resourceExists, err := resourceNetboxTenancyContactGroupExists(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.NewTenancyContactGroupsDeleteParams().WithID(id) + if _, err := client.Tenancy.TenancyContactGroupsDelete(p, nil); err != nil { + return err + } + + return nil +} + +func resourceNetboxTenancyContactGroupExists(d *schema.ResourceData, + m interface{}) (b bool, + e error) { + client := m.(*netboxclient.NetBoxAPI) + resourceExist := false + + resourceID := d.Id() + params := tenancy.NewTenancyContactGroupsListParams().WithID(&resourceID) + resources, err := client.Tenancy.TenancyContactGroupsList(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 +}