From afe33dc344a9e326cf30377380e12806b8829426 Mon Sep 17 00:00:00 2001 From: Diogenes Pelisson Date: Sat, 1 Jun 2024 08:33:22 -0300 Subject: [PATCH] feat: Config contexts -- continue -- continue (#590) * feat: Config Contexts * Make code up-to-date, switch type of config_context to String * go fmt + docs generate * add example * docs * update schema * remove duplicated * tests * fix lint errors * chore: add some newlines to the code --------- Co-authored-by: Mike Frost Co-authored-by: Gennady Lipenkov Co-authored-by: Fabian Breckle --- docs/data-sources/config_context.md | 42 ++ docs/data-sources/devices.md | 2 + docs/resources/config_context.md | 55 +++ example/main.tf | 7 + .../netbox_config_context/resource.tf | 4 + netbox/data_source_netbox_config_context.go | 254 ++++++++++++ .../data_source_netbox_config_context_test.go | 35 ++ netbox/data_source_netbox_devices.go | 20 + netbox/data_source_netbox_devices_test.go | 7 + netbox/provider.go | 2 + netbox/resource_netbox_config_context.go | 375 ++++++++++++++++++ netbox/resource_netbox_config_context_test.go | 205 ++++++++++ 12 files changed, 1008 insertions(+) create mode 100644 docs/data-sources/config_context.md create mode 100644 docs/resources/config_context.md create mode 100644 examples/resources/netbox_config_context/resource.tf create mode 100644 netbox/data_source_netbox_config_context.go create mode 100644 netbox/data_source_netbox_config_context_test.go create mode 100644 netbox/resource_netbox_config_context.go create mode 100644 netbox/resource_netbox_config_context_test.go diff --git a/docs/data-sources/config_context.md b/docs/data-sources/config_context.md new file mode 100644 index 00000000..3bf333ae --- /dev/null +++ b/docs/data-sources/config_context.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/fbreckle/terraform-plugin-docs +page_title: "netbox_config_context Data Source - terraform-provider-netbox" +subcategory: "Extras" +description: |- + +--- + +# netbox_config_context (Data Source) + + + + + + +## Schema + +### Required + +- `name` (String) + +### Read-Only + +- `cluster_groups` (List of Number) +- `cluster_types` (List of Number) +- `clusters` (List of Number) +- `data` (String) +- `description` (String) +- `device_types` (List of Number) +- `id` (String) The ID of this resource. +- `locations` (List of Number) +- `platforms` (List of Number) +- `regions` (List of Number) +- `roles` (List of Number) +- `site_groups` (List of Number) +- `sites` (List of Number) +- `tags` (List of String) +- `tenant_groups` (List of Number) +- `tenants` (List of Number) +- `weight` (Number) + + diff --git a/docs/data-sources/devices.md b/docs/data-sources/devices.md index 9528c396..dd260b68 100644 --- a/docs/data-sources/devices.md +++ b/docs/data-sources/devices.md @@ -43,10 +43,12 @@ Read-Only: - `asset_tag` (String) - `cluster_id` (Number) - `comments` (String) +- `config_context` (String) - `custom_fields` (Map of String) - `description` (String) - `device_id` (Number) - `device_type_id` (Number) +- `local_context_data` (String) - `location_id` (Number) - `manufacturer_id` (Number) - `model` (String) diff --git a/docs/resources/config_context.md b/docs/resources/config_context.md new file mode 100644 index 00000000..f11e9cea --- /dev/null +++ b/docs/resources/config_context.md @@ -0,0 +1,55 @@ +--- +# generated by https://github.com/fbreckle/terraform-plugin-docs +page_title: "netbox_config_context Resource - terraform-provider-netbox" +subcategory: "Extras" +description: |- + From the official documentation https://docs.netbox.dev/en/stable/models/extras/configcontext/: + Context data is made available to devices and/or virtual machines based on their relationships to other objects in NetBox. For example, context data can be associated only with devices assigned to a particular site, or only to virtual machines in a certain cluster. +--- + +# netbox_config_context (Resource) + +From the [official documentation](https://docs.netbox.dev/en/stable/models/extras/configcontext/): + +> Context data is made available to devices and/or virtual machines based on their relationships to other objects in NetBox. For example, context data can be associated only with devices assigned to a particular site, or only to virtual machines in a certain cluster. + +## Example Usage + +```terraform +resource "netbox_config_context" "test" { + name = "%s" + data = jsonencode({"testkey" = "testval"}) +} +``` + + +## Schema + +### Required + +- `data` (String) +- `name` (String) + +### Optional + +- `cluster_groups` (Set of Number) +- `cluster_types` (Set of Number) +- `clusters` (Set of Number) +- `description` (String) +- `device_types` (Set of Number) +- `locations` (Set of Number) +- `platforms` (Set of Number) +- `regions` (Set of Number) +- `roles` (Set of Number) +- `site_groups` (Set of Number) +- `sites` (Set of Number) +- `tags` (Set of String) +- `tenant_groups` (Set of Number) +- `tenants` (Set of Number) +- `weight` (Number) Defaults to `1000`. + +### Read-Only + +- `id` (String) The ID of this resource. + + diff --git a/example/main.tf b/example/main.tf index 16f46321..5f33782d 100644 --- a/example/main.tf +++ b/example/main.tf @@ -124,3 +124,10 @@ resource "netbox_webhook" "testwebhook" { content_types = "dcim.site" body_template = "Sample Body" } + +resource "netbox_config_context" "test" { + name = "My Config Context" + data = jsonencode( + { "testkey" = "testval" } + ) +} diff --git a/examples/resources/netbox_config_context/resource.tf b/examples/resources/netbox_config_context/resource.tf new file mode 100644 index 00000000..c4ec6156 --- /dev/null +++ b/examples/resources/netbox_config_context/resource.tf @@ -0,0 +1,4 @@ +resource "netbox_config_context" "test" { + name = "%s" + data = jsonencode({"testkey" = "testval"}) +} diff --git a/netbox/data_source_netbox_config_context.go b/netbox/data_source_netbox_config_context.go new file mode 100644 index 00000000..9a6b33fe --- /dev/null +++ b/netbox/data_source_netbox_config_context.go @@ -0,0 +1,254 @@ +package netbox + +import ( + "encoding/json" + "errors" + "strconv" + + "github.com/fbreckle/go-netbox/netbox/client" + "github.com/fbreckle/go-netbox/netbox/client/extras" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNetboxConfigContext() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNetboxConfigContextRead, + Description: `:meta:subcategory:Extras:`, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "weight": { + Type: schema.TypeInt, + Computed: true, + }, + "data": { + Type: schema.TypeString, + Computed: true, + }, + "cluster_groups": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "cluster_types": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "clusters": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "device_types": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "locations": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "platforms": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "regions": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "roles": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "site_groups": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "sites": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "tenant_groups": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "tenants": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "tags": { + Type: schema.TypeList, + Computed: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func dataSourceNetboxConfigContextRead(d *schema.ResourceData, m interface{}) error { + api := m.(*client.NetBoxAPI) + + name := d.Get("name").(string) + params := extras.NewExtrasConfigContextsListParams() + params.Name = &name + limit := int64(2) // Limit of 2 is enough + params.Limit = &limit + + res, err := api.Extras.ExtrasConfigContextsList(params, nil) + if err != nil { + return err + } + + if *res.GetPayload().Count > int64(1) { + return errors.New("more than one result. Specify a more narrow filter") + } + if *res.GetPayload().Count == int64(0) { + return errors.New("no result") + } + result := res.GetPayload().Results[0] + d.SetId(strconv.FormatInt(result.ID, 10)) + d.Set("name", result.Name) + d.Set("weight", result.Weight) + + if result.Data != nil { + if jsonArr, err := json.Marshal(result.Data); err == nil { + d.Set("data", string(jsonArr)) + } + } else { + d.Set("data", nil) + } + + clusterGroups := make([]int64, len(result.ClusterGroups)) + for i, v := range result.ClusterGroups { + clusterGroups[i] = int64(v.ID) + } + d.Set("cluster_groups", clusterGroups) + + clusterTypes := make([]int64, len(result.ClusterTypes)) + for i, v := range result.ClusterTypes { + clusterTypes[i] = int64(v.ID) + } + d.Set("cluster_types", clusterTypes) + + clusters := make([]int64, len(result.Clusters)) + for i, v := range result.Clusters { + clusters[i] = int64(v.ID) + } + d.Set("clusters", clusters) + + deviceTypes := make([]int64, len(result.DeviceTypes)) + for i, v := range result.DeviceTypes { + deviceTypes[i] = int64(v.ID) + } + d.Set("device_types", deviceTypes) + + locations := make([]int64, len(result.Locations)) + for i, v := range result.Locations { + locations[i] = int64(v.ID) + } + d.Set("locations", locations) + + platforms := make([]int64, len(result.Platforms)) + for i, v := range result.Platforms { + platforms[i] = int64(v.ID) + } + d.Set("platforms", platforms) + + regions := make([]int64, len(result.Regions)) + for i, v := range result.Regions { + regions[i] = int64(v.ID) + } + d.Set("regions", regions) + + roles := make([]int64, len(result.Roles)) + for i, v := range result.Roles { + roles[i] = int64(v.ID) + } + d.Set("roles", roles) + + siteGroups := make([]int64, len(result.SiteGroups)) + for i, v := range result.SiteGroups { + siteGroups[i] = int64(v.ID) + } + d.Set("site_groups", siteGroups) + + sites := make([]int64, len(result.Sites)) + for i, v := range result.Sites { + sites[i] = int64(v.ID) + } + d.Set("sites", sites) + + tenantGroups := make([]int64, len(result.TenantGroups)) + for i, v := range result.TenantGroups { + tenantGroups[i] = int64(v.ID) + } + d.Set("tenant_groups", tenantGroups) + + tenants := make([]int64, len(result.Tenants)) + for i, v := range result.Tenants { + tenants[i] = int64(v.ID) + } + d.Set("tenants", tenants) + + tags := make([]string, len(result.Tags)) + for i, v := range result.Tags { + tags[i] = string(v) + } + d.Set("tags", tags) + + return nil +} diff --git a/netbox/data_source_netbox_config_context_test.go b/netbox/data_source_netbox_config_context_test.go new file mode 100644 index 00000000..51945548 --- /dev/null +++ b/netbox/data_source_netbox_config_context_test.go @@ -0,0 +1,35 @@ +package netbox + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNetboxConfigContextDataSource_basic(t *testing.T) { + testSlug := "cfct_ds_basic" + testName := testAccGetTestName(testSlug) + resource.ParallelTest(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` +resource "netbox_config_context" "test" { + name = "%[1]s" + weight = 1000 + data = jsonencode({"testkey" = "testval"}) +} +data "netbox_config_context" "test" { + depends_on = [netbox_config_context.test] + name = "%[1]s" +}`, testName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("data.netbox_config_context.test", "id", "netbox_config_context.test", "id"), + resource.TestCheckResourceAttrPair("data.netbox_config_context.test", "weight", "netbox_config_context.test", "weight"), + resource.TestCheckResourceAttrPair("data.netbox_config_context.test", "data", "netbox_config_context.test", "data"), + ), + }, + }, + }) +} diff --git a/netbox/data_source_netbox_devices.go b/netbox/data_source_netbox_devices.go index 13d3463f..04c0b18c 100644 --- a/netbox/data_source_netbox_devices.go +++ b/netbox/data_source_netbox_devices.go @@ -4,7 +4,9 @@ package netbox import ( + "encoding/json" "fmt" + "github.com/fbreckle/go-netbox/netbox/client" "github.com/fbreckle/go-netbox/netbox/client/dcim" "github.com/fbreckle/go-netbox/netbox/models" @@ -63,6 +65,14 @@ func dataSourceNetboxDevices() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "config_context": { + Type: schema.TypeString, + Computed: true, + }, + "local_context_data": { + Type: schema.TypeString, + Computed: true, + }, "custom_fields": { Type: schema.TypeMap, Computed: true, @@ -234,6 +244,16 @@ func dataSourceNetboxDevicesRead(d *schema.ResourceData, m interface{}) error { if device.Description != "" { mapping["description"] = device.Description } + if device.ConfigContext != nil { + if configContext, err := json.Marshal(device.ConfigContext); err == nil { + mapping["config_context"] = string(configContext) + } + } + if device.LocalContextData != nil { + if localContextData, err := json.Marshal(device.LocalContextData); err == nil { + mapping["local_context_data"] = string(localContextData) + } + } mapping["device_id"] = device.ID if device.DeviceType != nil { mapping["device_type_id"] = device.DeviceType.ID diff --git a/netbox/data_source_netbox_devices_test.go b/netbox/data_source_netbox_devices_test.go index 86b17c6d..f23bfe64 100644 --- a/netbox/data_source_netbox_devices_test.go +++ b/netbox/data_source_netbox_devices_test.go @@ -4,6 +4,7 @@ package netbox import ( + "encoding/json" "fmt" "strings" "testing" @@ -14,6 +15,7 @@ import ( func TestAccNetboxDevicesDataSource_basic(t *testing.T) { testSlug := "devices_ds_basic" testName := testAccGetTestName(testSlug) + testLocalContextData, _ := json.Marshal(map[string]string{"testkey0": "testvalue0"}) dependencies := testAccNetboxDeviceDataSourceDependencies(testName) resource.Test(t, resource.TestCase{ Providers: testAccProviders, @@ -37,6 +39,7 @@ func TestAccNetboxDevicesDataSource_basic(t *testing.T) { resource.TestCheckResourceAttr("data.netbox_devices.test", "devices.0.serial", "ABCDEF0"), resource.TestCheckResourceAttr("data.netbox_devices.test", "devices.0.status", "staged"), resource.TestCheckResourceAttr("data.netbox_devices.test", "devices.0.tags.#", "1"), + resource.TestCheckResourceAttr("data.netbox_devices.test", "devices.0.local_context_data", string(testLocalContextData)), ), }, { @@ -198,6 +201,7 @@ resource "netbox_device" "test0" { serial = "ABCDEF0" status = "staged" tags = [netbox_tag.test_a.name] + local_context_data = jsonencode({"testkey0"="testvalue0"}) } resource "netbox_device" "test1" { @@ -210,6 +214,7 @@ resource "netbox_device" "test1" { platform_id = netbox_platform.test.id location_id = netbox_location.test.id serial = "ABCDEF1" + local_context_data = jsonencode({"testkey1"="testvalue1"}) } resource "netbox_device" "test2" { @@ -223,6 +228,7 @@ resource "netbox_device" "test2" { location_id = netbox_location.test.id serial = "ABCDEF2" tags = [netbox_tag.test_b.name, netbox_tag.test_c.name] + local_context_data = jsonencode({"testkey2"="testvalue2"}) } resource "netbox_device" "test3" { @@ -235,6 +241,7 @@ resource "netbox_device" "test3" { platform_id = netbox_platform.test.id location_id = netbox_location.test.id serial = "ABCDEF3" + local_context_data = jsonencode({"testkey3"="testvalue3"}) } `, testName) } diff --git a/netbox/provider.go b/netbox/provider.go index 9bad3ba6..25f39ec4 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -146,6 +146,7 @@ func Provider() *schema.Provider { "netbox_vpn_tunnel_group": resourceNetboxVpnTunnelGroup(), "netbox_vpn_tunnel": resourceNetboxVpnTunnel(), "netbox_vpn_tunnel_termination": resourceNetboxVpnTunnelTermination(), + "netbox_config_context": resourceNetboxConfigContext(), }, DataSourcesMap: map[string]*schema.Resource{ "netbox_asn": dataSourceNetboxAsn(), @@ -187,6 +188,7 @@ func Provider() *schema.Provider { "netbox_site_group": dataSourceNetboxSiteGroup(), "netbox_racks": dataSourceNetboxRacks(), "netbox_rack_role": dataSourceNetboxRackRole(), + "netbox_config_context": dataSourceNetboxConfigContext(), }, Schema: map[string]*schema.Schema{ "server_url": { diff --git a/netbox/resource_netbox_config_context.go b/netbox/resource_netbox_config_context.go new file mode 100644 index 00000000..0098806d --- /dev/null +++ b/netbox/resource_netbox_config_context.go @@ -0,0 +1,375 @@ +package netbox + +import ( + "encoding/json" + "strconv" + + "github.com/fbreckle/go-netbox/netbox/client" + "github.com/fbreckle/go-netbox/netbox/client/extras" + "github.com/fbreckle/go-netbox/netbox/models" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceNetboxConfigContext() *schema.Resource { + return &schema.Resource{ + Create: resourceNetboxConfigContextCreate, + Read: resourceNetboxConfigContextRead, + Update: resourceNetboxConfigContextUpdate, + Delete: resourceNetboxConfigContextDelete, + + Description: `:meta:subcategory:Extras:From the [official documentation](https://docs.netbox.dev/en/stable/models/extras/configcontext/): + +> Context data is made available to devices and/or virtual machines based on their relationships to other objects in NetBox. For example, context data can be associated only with devices assigned to a particular site, or only to virtual machines in a certain cluster.`, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "weight": { + Type: schema.TypeInt, + Optional: true, + Default: 1000, + }, + "data": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsJSON, + }, + "cluster_groups": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "cluster_types": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "clusters": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "device_types": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "locations": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "platforms": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "regions": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "roles": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "site_groups": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "sites": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "tenant_groups": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "tenants": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "tags": { + Type: schema.TypeSet, + Optional: true, + Default: nil, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func resourceNetboxConfigContextCreate(d *schema.ResourceData, m interface{}) error { + api := m.(*client.NetBoxAPI) + data := models.WritableConfigContext{} + data.Name = strToPtr(d.Get("name").(string)) + + dataJSON, ok := d.GetOk("data") + if ok { + var jsonObj any + localContextBA := []byte(dataJSON.(string)) + if err := json.Unmarshal(localContextBA, &jsonObj); err == nil { + data.Data = jsonObj + } + } + data.Description = d.Get("description").(string) + data.ClusterGroups = toInt64List(d.Get("cluster_groups")) + data.ClusterTypes = toInt64List(d.Get("cluster_types")) + data.Clusters = toInt64List(d.Get("clusters")) + data.DeviceTypes = toInt64List(d.Get("device_types")) + data.Locations = toInt64List(d.Get("locations")) + data.Platforms = toInt64List(d.Get("platforms")) + data.Regions = toInt64List(d.Get("regions")) + data.Roles = toInt64List(d.Get("roles")) + data.SiteGroups = toInt64List(d.Get("site_groups")) + data.Sites = toInt64List(d.Get("sites")) + data.TenantGroups = toInt64List(d.Get("tenant_groups")) + data.Tenants = toInt64List(d.Get("tenants")) + data.Tags = toStringList(d.Get("tags")) + data.Weight = int64ToPtr(int64(d.Get("weight").(int))) + + params := extras.NewExtrasConfigContextsCreateParams().WithData(&data) + + res, err := api.Extras.ExtrasConfigContextsCreate(params, nil) + if err != nil { + //return errors.New(getTextFromError(err)) + return err + } + + d.SetId(strconv.FormatInt(res.GetPayload().ID, 10)) + + return resourceNetboxConfigContextRead(d, m) +} + +func resourceNetboxConfigContextRead(d *schema.ResourceData, m interface{}) error { + api := m.(*client.NetBoxAPI) + id, _ := strconv.ParseInt(d.Id(), 10, 64) + params := extras.NewExtrasConfigContextsReadParams().WithID(id) + + res, err := api.Extras.ExtrasConfigContextsRead(params, nil) + + if err != nil { + errorcode := err.(*extras.ExtrasConfigContextsReadDefault).Code() + if errorcode == 404 { + // If the ID is updated to blank, this tells Terraform the resource no longer exists (maybe it was destroyed out of band). Just like the destroy callback, the Read function should gracefully handle this case. https://www.terraform.io/docs/extend/writing-custom-providers.html + d.SetId("") + return nil + } + return err + } + + d.Set("name", res.GetPayload().Name) + d.Set("description", res.GetPayload().Description) + d.Set("weight", res.GetPayload().Weight) + + if res.GetPayload().Data != nil { + if jsonArr, err := json.Marshal(res.GetPayload().Data); err == nil { + d.Set("data", string(jsonArr)) + } + } else { + d.Set("data", nil) + } + + clusterGroups := res.GetPayload().ClusterGroups + clusterGroupsSlice := make([]int64, len(clusterGroups)) + for i, v := range clusterGroups { + clusterGroupsSlice[i] = int64(v.ID) + } + d.Set("cluster_groups", clusterGroupsSlice) + + clusterTypes := res.GetPayload().ClusterTypes + clusterTypesSlice := make([]int64, len(clusterTypes)) + for i, v := range clusterTypes { + clusterTypesSlice[i] = int64(v.ID) + } + d.Set("cluster_types", clusterTypesSlice) + + clusters := res.GetPayload().Clusters + clustersSlice := make([]int64, len(clusters)) + for i, v := range clusters { + clustersSlice[i] = int64(v.ID) + } + d.Set("clusters", clustersSlice) + + deviceTypes := res.GetPayload().DeviceTypes + deviceTypesSlice := make([]int64, len(deviceTypes)) + for i, v := range deviceTypes { + deviceTypesSlice[i] = int64(v.ID) + } + d.Set("device_types", deviceTypesSlice) + + locations := res.GetPayload().Locations + locationsSlice := make([]int64, len(locations)) + for i, v := range locations { + locationsSlice[i] = int64(v.ID) + } + d.Set("locations", locationsSlice) + + platforms := res.GetPayload().Platforms + platformsSlice := make([]int64, len(platforms)) + for i, v := range platforms { + platformsSlice[i] = int64(v.ID) + } + d.Set("platforms", platformsSlice) + + regions := res.GetPayload().Regions + regionsSlice := make([]int64, len(regions)) + for i, v := range regions { + regionsSlice[i] = int64(v.ID) + } + d.Set("regions", regionsSlice) + + roles := res.GetPayload().Roles + rolesSlice := make([]int64, len(roles)) + for i, v := range roles { + rolesSlice[i] = int64(v.ID) + } + d.Set("roles", rolesSlice) + + siteGroups := res.GetPayload().SiteGroups + siteGroupsSlice := make([]int64, len(siteGroups)) + for i, v := range siteGroups { + siteGroupsSlice[i] = int64(v.ID) + } + d.Set("site_groups", siteGroupsSlice) + + sites := res.GetPayload().Sites + sitesSlice := make([]int64, len(sites)) + for i, v := range sites { + sitesSlice[i] = int64(v.ID) + } + d.Set("sites", sitesSlice) + + tags := res.GetPayload().Tags + tagsSlice := make([]string, len(tags)) + for i, v := range tags { + tagsSlice[i] = string(v) + } + d.Set("tags", tagsSlice) + + tenantGroups := res.GetPayload().TenantGroups + tenantGroupsSlice := make([]int64, len(tenantGroups)) + for i, v := range tenantGroups { + tenantGroupsSlice[i] = int64(v.ID) + } + d.Set("tenant_groups", tenantGroupsSlice) + + tenants := res.GetPayload().Tenants + tenantsSlice := make([]int64, len(tenants)) + for i, v := range tenants { + tenantsSlice[i] = int64(v.ID) + } + d.Set("tenants", tenantsSlice) + + return nil +} + +func resourceNetboxConfigContextUpdate(d *schema.ResourceData, m interface{}) error { + api := m.(*client.NetBoxAPI) + + id, _ := strconv.ParseInt(d.Id(), 10, 64) + + data := models.WritableConfigContext{} + + name := d.Get("name").(string) + data.Name = &name + + dataValue, ok := d.GetOk("data") + if ok { + var jsonObj any + localContextBA := []byte(dataValue.(string)) + if err := json.Unmarshal(localContextBA, &jsonObj); err == nil { + data.Data = jsonObj + } + } + data.Description = d.Get("description").(string) + data.ClusterGroups = toInt64List(d.Get("cluster_groups")) + data.ClusterTypes = toInt64List(d.Get("cluster_types")) + data.Clusters = toInt64List(d.Get("clusters")) + data.DeviceTypes = toInt64List(d.Get("device_types")) + data.Locations = toInt64List(d.Get("locations")) + data.Platforms = toInt64List(d.Get("platforms")) + data.Regions = toInt64List(d.Get("regions")) + data.Roles = toInt64List(d.Get("roles")) + data.SiteGroups = toInt64List(d.Get("site_groups")) + data.Sites = toInt64List(d.Get("sites")) + data.TenantGroups = toInt64List(d.Get("tenant_groups")) + data.Tenants = toInt64List(d.Get("tenants")) + data.Tags = toStringList(d.Get("tags")) + data.Weight = int64ToPtr(int64(d.Get("weight").(int))) + + params := extras.NewExtrasConfigContextsPartialUpdateParams().WithID(id).WithData(&data) + + _, err := api.Extras.ExtrasConfigContextsPartialUpdate(params, nil) + if err != nil { + return err + } + + return resourceNetboxConfigContextRead(d, m) +} + +func resourceNetboxConfigContextDelete(d *schema.ResourceData, m interface{}) error { + api := m.(*client.NetBoxAPI) + + id, _ := strconv.ParseInt(d.Id(), 10, 64) + params := extras.NewExtrasConfigContextsDeleteParams().WithID(id) + + _, err := api.Extras.ExtrasConfigContextsDelete(params, nil) + if err != nil { + return err + } + return nil +} diff --git a/netbox/resource_netbox_config_context_test.go b/netbox/resource_netbox_config_context_test.go new file mode 100644 index 00000000..052a542b --- /dev/null +++ b/netbox/resource_netbox_config_context_test.go @@ -0,0 +1,205 @@ +package netbox + +import ( + "fmt" + "log" + "strings" + "testing" + + "github.com/fbreckle/go-netbox/netbox/client" + "github.com/fbreckle/go-netbox/netbox/client/extras" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNetboxConfigContext_basic(t *testing.T) { + testSlug := "config_context_assignments" + testName := testAccGetTestName(testSlug) + resource.ParallelTest(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` +resource "netbox_config_context" "test" { + name = "%s" + description = "test description" + data = jsonencode({"testkey" = "testval"}) +}`, testName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("netbox_config_context.test", "name", testName), + resource.TestCheckResourceAttr("netbox_config_context.test", "data", "{\"testkey\":\"testval\"}"), + ), + }, + { + ResourceName: "netbox_config_context.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetboxConfigContext_defaultWeight(t *testing.T) { + testSlug := "config_context_assignments" + testName := testAccGetTestName(testSlug) + resource.ParallelTest(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` +resource "netbox_config_context" "test" { + name = "%s" + data = jsonencode({"testkey" = "testval"}) +}`, testName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("netbox_config_context.test", "name", testName), + resource.TestCheckResourceAttr("netbox_config_context.test", "weight", "1000"), + ), + }, + }, + }) +} + +func TestAccNetboxConfigContext_assignments(t *testing.T) { + testSlug := "config_context_assignments" + testName := testAccGetTestName(testSlug) + resource.ParallelTest(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + +resource "netbox_tenant_group" "test" { + name = "%[1]s" +} + +resource "netbox_tenant" "test" { + name = "%[1]s" + group_id = netbox_tenant_group.test.id +} + +resource "netbox_platform" "test" { + name = "%[1]s" +} + +resource "netbox_site_group" "test" { + name = "%[1]s" +} + +resource "netbox_site" "test" { + name = "%[1]s" + status = "active" + group_id = netbox_site_group.test.id +} + +resource "netbox_region" "test" { + name = "%[1]s" +} + +resource "netbox_cluster_type" "test" { + name = "%[1]s" +} + +resource "netbox_cluster_group" "test" { + name = "%[1]s" +} + +resource "netbox_cluster" "test" { + name = "%[1]s" + cluster_type_id = netbox_cluster_type.test.id + site_id = netbox_site.test.id + cluster_group_id = netbox_cluster_group.test.id +} + +resource "netbox_location" "test" { + name = "%[1]s" + site_id =netbox_site.test.id +} + +resource "netbox_device_role" "test" { + name = "%[1]s" + color_hex = "123456" +} + +resource "netbox_tag" "test" { + name = "%[1]s" +} + +resource "netbox_manufacturer" "test" { + name = "%[1]s" +} + +resource "netbox_device_type" "test" { + model = "%[1]s" + manufacturer_id = netbox_manufacturer.test.id +} +# Untested: cluster_groups, regions, site_groups, tenant_groups +resource "netbox_config_context" "test" { + name = "%[1]s" + data = jsonencode({"testkey" = "testval"}) + regions = [netbox_region.test.id] + tenant_groups = [netbox_tenant_group.test.id] + tenants = [netbox_tenant.test.id] + platforms = [netbox_platform.test.id] + sites = [netbox_site.test.id] + site_groups = [netbox_site_group.test.id] + cluster_types = [netbox_cluster_type.test.id] + cluster_groups = [netbox_cluster_group.test.id] + clusters = [netbox_cluster.test.id] + locations = [netbox_location.test.id] + roles = [netbox_device_role.test.id] + tags = [netbox_tag.test.name] + device_types = [netbox_device_type.test.id] +}`, testName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("netbox_config_context.test", "name", testName), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "regions.0", "netbox_region.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "tenant_groups.0", "netbox_tenant_group.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "tenants.0", "netbox_tenant.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "platforms.0", "netbox_platform.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "sites.0", "netbox_site.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "site_groups.0", "netbox_site_group.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "cluster_types.0", "netbox_cluster_type.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "cluster_groups.0", "netbox_cluster_group.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "clusters.0", "netbox_cluster.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "locations.0", "netbox_location.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "roles.0", "netbox_device_role.test", "id"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "tags.0", "netbox_tag.test", "name"), + resource.TestCheckResourceAttrPair("netbox_config_context.test", "device_types.0", "netbox_device_type.test", "id"), + ), + }, + }, + }) +} + +func init() { + resource.AddTestSweepers("netbox_config_context", &resource.Sweeper{ + Name: "netbox_config_context", + Dependencies: []string{}, + F: func(region string) error { + m, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("Error getting client: %s", err) + } + api := m.(*client.NetBoxAPI) + params := extras.NewExtrasConfigContextsListParams() + res, err := api.Extras.ExtrasConfigContextsList(params, nil) + if err != nil { + return err + } + for _, configContext := range res.GetPayload().Results { + if strings.HasPrefix(*configContext.Name, testPrefix) { + deleteParams := extras.NewExtrasConfigContextsDeleteParams().WithID(configContext.ID) + _, err := api.Extras.ExtrasConfigContextsDelete(deleteParams, nil) + if err != nil { + return err + } + log.Print("[DEBUG] Deleted a config context") + } + } + return nil + }, + }) +}