From 0f87e74f55b7c5dd0d4d615c5ff252dc67e91f66 Mon Sep 17 00:00:00 2001 From: Marcus Weiner Date: Sat, 20 Jul 2024 20:35:44 +0200 Subject: [PATCH] Add session data source (#6) --- internal/provider/bgpsession_datasource.go | 212 +++++++++++ .../provider/bgpsession_datasource_test.go | 41 +++ internal/provider/bgpsession_resource.go | 92 ++--- internal/provider/bgpsession_resource_test.go | 31 +- internal/provider/common.go | 346 ++++++++++++++++++ internal/provider/provider.go | 4 +- internal/provider/utils.go | 31 +- 7 files changed, 680 insertions(+), 77 deletions(-) create mode 100644 internal/provider/bgpsession_datasource.go create mode 100644 internal/provider/bgpsession_datasource_test.go create mode 100644 internal/provider/common.go diff --git a/internal/provider/bgpsession_datasource.go b/internal/provider/bgpsession_datasource.go new file mode 100644 index 0000000..3cacd96 --- /dev/null +++ b/internal/provider/bgpsession_datasource.go @@ -0,0 +1,212 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/ffddorf/terraform-provider-netbox-bgp/client" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &SessionDataSource{} + +func NewSessionDataSource() datasource.DataSource { + return &SessionDataSource{} +} + +type SessionDataSource struct { + client *client.Client +} + +type SessionDataSourceModel struct { + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Comments types.String `tfsdk:"comments"` + Status types.String `tfsdk:"status"` + + Site *NestedSite `tfsdk:"site"` + Tenant *NestedTenant `tfsdk:"tenant"` + Device *NestedDevice `tfsdk:"device"` + + LocalAddress *NestedIPAddress `tfsdk:"local_address"` + RemoteAddress *NestedIPAddress `tfsdk:"remote_address"` + LocalAS *NestedASN `tfsdk:"local_as"` + RemoteAS *NestedASN `tfsdk:"remote_as"` + PeerGroup *NestedBGPPeerGroup `tfsdk:"peer_group"` + + ImportPolicyIDs types.List `tfsdk:"import_policy_ids"` + ExportPolicyIDs types.List `tfsdk:"export_policy_ids"` + + PrefixListIn *NestedPrefixList `tfsdk:"prefix_list_in"` + PrefixListOut *NestedPrefixList `tfsdk:"prefix_list_out"` + + Tags types.List `tfsdk:"tags"` +} + +func (m *SessionDataSourceModel) FillFromAPIModel(ctx context.Context, resp *client.BGPSession, diags diag.Diagnostics) { + m.ID = maybeInt64Value(resp.Id) + m.Comments = maybeStringValue(resp.Comments) + m.Description = maybeStringValue(resp.Description) + m.Device = NestedDeviceFromAPI(resp.Device) + if resp.ExportPolicies != nil && len(*resp.ExportPolicies) > 0 { + var ds diag.Diagnostics + m.ExportPolicyIDs, ds = types.ListValueFrom(ctx, types.Int64Type, resp.ExportPolicies) + for _, d := range ds { + diags.Append(diag.WithPath(path.Root("export_policy_ids"), d)) + } + } + if resp.ImportPolicies != nil && len(*resp.ImportPolicies) > 0 { + var ds diag.Diagnostics + m.ImportPolicyIDs, ds = types.ListValueFrom(ctx, types.Int64Type, resp.ImportPolicies) + for _, d := range ds { + diags.Append(diag.WithPath(path.Root("import_policy_ids"), d)) + } + } + m.LocalAddress = NestedIPAddressFromAPI(&resp.LocalAddress) + m.LocalAS = NestedASNFromAPI(&resp.LocalAs) + m.Name = maybeStringValue(resp.Name) + m.PeerGroup = NestedBGPPeerGroupFromAPI(resp.PeerGroup) + m.PrefixListIn = NestedPrefixListFromAPI(resp.PrefixListIn) + m.PrefixListOut = NestedPrefixListFromAPI(resp.PrefixListOut) + m.RemoteAddress = NestedIPAddressFromAPI(&resp.RemoteAddress) + m.RemoteAS = NestedASNFromAPI(&resp.RemoteAs) + m.Site = NestedSiteFromAPI(resp.Site) + m.Status = maybeStringValue((*string)(resp.Status.Value)) + m.Tenant = NestedTenantFromAPI(resp.Tenant) + + m.Tags = TagsFromAPI(ctx, resp.Tags, diags) + + // todo: custom fields +} + +func (d *SessionDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_session" +} + +func (d *SessionDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "BGP Session data source", + + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + MarkdownDescription: "ID of the resource in Netbox to use for lookup", + Required: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + "description": schema.StringAttribute{ + Computed: true, + }, + "comments": schema.StringAttribute{ + Computed: true, + }, + "status": schema.StringAttribute{ + Computed: true, + MarkdownDescription: `One of: "active", "failed", "offline", "planned"`, + }, + "site": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedSite)(nil).SchemaAttributes(), + }, + "tenant": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedTenant)(nil).SchemaAttributes(), + }, + "device": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedDevice)(nil).SchemaAttributes(), + }, + "local_address": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedIPAddress)(nil).SchemaAttributes(), + }, + "remote_address": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedIPAddress)(nil).SchemaAttributes(), + }, + "local_as": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedASN)(nil).SchemaAttributes(), + }, + "remote_as": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedASN)(nil).SchemaAttributes(), + }, + "peer_group": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedBGPPeerGroup)(nil).SchemaAttributes(), + }, + "import_policy_ids": schema.ListAttribute{ + ElementType: types.Int64Type, + Computed: true, + }, + "export_policy_ids": schema.ListAttribute{ + ElementType: types.Int64Type, + Computed: true, + }, + "prefix_list_in": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedPrefixList)(nil).SchemaAttributes(), + }, + "prefix_list_out": schema.SingleNestedAttribute{ + Computed: true, + Attributes: (*NestedPrefixList)(nil).SchemaAttributes(), + }, + TagFieldName: TagSchema, + }, + } +} + +func (d *SessionDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + data, ok := req.ProviderData.(*configuredProvider) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *configuredProvider, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = data.Client +} + +func (d *SessionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SessionDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + httpRes, err := d.client.PluginsBgpBgpsessionRetrieve(ctx, int(data.ID.ValueInt64())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to retrieve session: %s", err)) + return + } + res, err := client.ParsePluginsBgpSessionRetrieveResponse(httpRes) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to parse session: %s", err)) + return + } + if res.JSON200 == nil { + resp.Diagnostics.AddError("Client Error", httpError(httpRes, res.Body)) + return + } + + data.FillFromAPIModel(ctx, res.JSON200, resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/bgpsession_datasource_test.go b/internal/provider/bgpsession_datasource_test.go new file mode 100644 index 0000000..bb9fd7d --- /dev/null +++ b/internal/provider/bgpsession_datasource_test.go @@ -0,0 +1,41 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccSessionDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ExternalProviders: testExternalProviders, + Steps: []resource.TestStep{ + // Read testing + { + Config: fmt.Sprintf(`%s + resource "netboxbgp_session" "test" { + name = "My session" + status = "active" + device_id = netbox_device.test.id + local_address_id = netbox_ip_address.local.id + remote_address_id = netbox_ip_address.remote.id + local_as_id = netbox_asn.test.id + remote_as_id = netbox_asn.test.id + } + + data "netboxbgp_session" "test" { + depends_on = [netboxbgp_session.test] + id = netboxbgp_session.test.id + } + `, baseResources(t)), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.netboxbgp_session.test", "name", "My session"), + resource.TestCheckResourceAttrPair("data.netboxbgp_session.test", "device.name", "netbox_device.test", "name"), + ), + }, + }, + }) +} diff --git a/internal/provider/bgpsession_resource.go b/internal/provider/bgpsession_resource.go index f69492c..cef49ae 100644 --- a/internal/provider/bgpsession_resource.go +++ b/internal/provider/bgpsession_resource.go @@ -63,43 +63,21 @@ type SessionResourceModel struct { func (m *SessionResourceModel) ToAPIModel(ctx context.Context, diags diag.Diagnostics) client.WritableBGPSessionRequest { p := client.WritableBGPSessionRequest{} - if !m.Name.IsNull() { - p.Name = m.Name.ValueStringPointer() - } - if !m.Description.IsNull() { - p.Description = m.Description.ValueStringPointer() - } - if !m.Comments.IsNull() { - p.Comments = m.Comments.ValueStringPointer() - } + p.Name = m.Name.ValueStringPointer() + p.Description = m.Description.ValueStringPointer() + p.Comments = m.Comments.ValueStringPointer() if !m.Status.IsNull() { status := client.WritableBGPSessionRequestStatus(m.Status.ValueString()) p.Status = &status } - if !m.SiteID.IsNull() { - p.Site = toIntPointer(m.SiteID.ValueInt64()) - } - if !m.TenantID.IsNull() { - p.Tenant = toIntPointer(m.TenantID.ValueInt64()) - } - if !m.DeviceID.IsNull() { - p.Device = toIntPointer(m.DeviceID.ValueInt64()) - } - if !m.LocalAddressID.IsNull() { - p.LocalAddress = int(m.LocalAddressID.ValueInt64()) - } - if !m.RemoteAddressID.IsNull() { - p.RemoteAddress = int(m.RemoteAddressID.ValueInt64()) - } - if !m.LocalASID.IsNull() { - p.LocalAs = int(m.LocalASID.ValueInt64()) - } - if !m.RemoteASID.IsNull() { - p.RemoteAs = int(m.RemoteASID.ValueInt64()) - } - if !m.PeerGroupID.IsNull() { - p.PeerGroup = toIntPointer(m.PeerGroupID.ValueInt64()) - } + p.Site = fromInt64Value(m.SiteID) + p.Tenant = fromInt64Value(m.TenantID) + p.Device = fromInt64Value(m.DeviceID) + p.LocalAddress = *fromInt64Value(m.LocalAddressID) + p.RemoteAddress = *fromInt64Value(m.RemoteAddressID) + p.LocalAs = *fromInt64Value(m.LocalASID) + p.RemoteAs = *fromInt64Value(m.RemoteASID) + p.PeerGroup = fromInt64Value(m.PeerGroupID) if !m.ImportPolicyIDs.IsNull() { policies, ds := toIntListPointer(ctx, m.ImportPolicyIDs) for _, d := range ds { @@ -114,12 +92,8 @@ func (m *SessionResourceModel) ToAPIModel(ctx context.Context, diags diag.Diagno } p.ExportPolicies = &policies } - if !m.PrefixListInID.IsNull() { - p.PrefixListIn = toIntPointer(m.PrefixListInID.ValueInt64()) - } - if !m.PrefixListOutID.IsNull() { - p.PrefixListOut = toIntPointer(m.PrefixListOutID.ValueInt64()) - } + p.PrefixListIn = fromInt64Value(m.PrefixListInID) + p.PrefixListOut = fromInt64Value(m.PrefixListOutID) p.Tags = TagsForAPIModel(ctx, m.Tags, diags) @@ -129,17 +103,11 @@ func (m *SessionResourceModel) ToAPIModel(ctx context.Context, diags diag.Diagno } func (m *SessionResourceModel) FillFromAPIModel(ctx context.Context, resp *client.BGPSession, diags diag.Diagnostics) { - if resp.Id != nil { - m.ID = types.Int64Value(int64(*resp.Id)) - } - if resp.Comments != nil && *resp.Comments != "" { - m.Comments = types.StringPointerValue(resp.Comments) - } - if resp.Description != nil && *resp.Description != "" { - m.Description = types.StringPointerValue(resp.Description) - } + m.ID = maybeInt64Value(resp.Id) + m.Comments = maybeStringValue(resp.Comments) + m.Description = maybeStringValue(resp.Description) if resp.Device != nil { - m.DeviceID = types.Int64Value(int64(*resp.Device.Id)) + m.DeviceID = maybeInt64Value(resp.Device.Id) } if resp.ExportPolicies != nil && len(*resp.ExportPolicies) > 0 { var ds diag.Diagnostics @@ -155,30 +123,26 @@ func (m *SessionResourceModel) FillFromAPIModel(ctx context.Context, resp *clien diags.Append(diag.WithPath(path.Root("import_policy_ids"), d)) } } - m.LocalAddressID = types.Int64Value(int64(*resp.LocalAddress.Id)) - m.LocalASID = types.Int64Value(int64(*resp.LocalAs.Id)) - if resp.Name != nil { - m.Name = types.StringPointerValue(resp.Name) - } + m.LocalAddressID = maybeInt64Value(resp.LocalAddress.Id) + m.LocalASID = maybeInt64Value(resp.LocalAs.Id) + m.Name = maybeStringValue(resp.Name) if resp.PeerGroup != nil { - m.PeerGroupID = types.Int64Value(int64(*resp.PeerGroup.Id)) + m.PeerGroupID = maybeInt64Value(resp.PeerGroup.Id) } if resp.PrefixListIn != nil { - m.PrefixListInID = types.Int64Value(int64(*resp.PrefixListIn.Id)) + m.PrefixListInID = maybeInt64Value(resp.PrefixListIn.Id) } if resp.PrefixListOut != nil { - m.PrefixListOutID = types.Int64Value(int64(*resp.PrefixListOut.Id)) + m.PrefixListOutID = maybeInt64Value(resp.PrefixListOut.Id) } - m.RemoteAddressID = types.Int64Value(int64(*resp.RemoteAddress.Id)) - m.RemoteASID = types.Int64Value(int64(*resp.RemoteAs.Id)) + m.RemoteAddressID = maybeInt64Value(resp.RemoteAddress.Id) + m.RemoteASID = maybeInt64Value(resp.RemoteAs.Id) if resp.Site != nil { - m.SiteID = types.Int64Value(int64(*resp.Site.Id)) - } - if resp.Status != nil { - m.Status = types.StringPointerValue((*string)(resp.Status.Value)) + m.SiteID = maybeInt64Value(resp.Site.Id) } + m.Status = maybeStringValue((*string)(resp.Status.Value)) if resp.Tenant != nil { - m.TenantID = types.Int64Value(int64(*resp.Tenant.Id)) + m.TenantID = maybeInt64Value(resp.Tenant.Id) } m.Tags = TagsFromAPI(ctx, resp.Tags, diags) diff --git a/internal/provider/bgpsession_resource_test.go b/internal/provider/bgpsession_resource_test.go index 6c38747..638d71c 100644 --- a/internal/provider/bgpsession_resource_test.go +++ b/internal/provider/bgpsession_resource_test.go @@ -2,6 +2,7 @@ package provider import ( "fmt" + "hash/fnv" "testing" "github.com/google/uuid" @@ -11,11 +12,26 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) +var testExternalProviders = map[string]resource.ExternalProvider{ + "netbox": { + VersionConstraint: "~> 3.8.7", + Source: "registry.terraform.io/e-breuninger/netbox", + }, +} + func testName(t *testing.T) string { return t.Name() + "_" + uuid.NewString() } +func testNum(t *testing.T) uint64 { + h := fnv.New64() + fmt.Fprint(h, testName(t)) + return h.Sum64() +} + func baseResources(t *testing.T) string { + num := testNum(t) + shortNum := num % 250 return fmt.Sprintf(` resource "netbox_tag" "test" { name = "%[1]s" @@ -54,14 +70,14 @@ resource "netbox_device_interface" "test" { } resource "netbox_ip_address" "local" { - ip_address = "203.0.113.10/24" + ip_address = "203.0.113.%[2]d/24" status = "active" interface_id = netbox_device_interface.test.id object_type = "dcim.interface" } resource "netbox_ip_address" "remote" { - ip_address = "203.0.113.11/24" + ip_address = "203.0.113.%[3]d/24" status = "active" } @@ -70,21 +86,16 @@ resource "netbox_rir" "test" { } resource "netbox_asn" "test" { - asn = 1337 + asn = %[4]d rir_id = netbox_rir.test.id -}`, testName(t)) +}`, testName(t), shortNum, shortNum+1, shortNum+1337) } func TestAccSessionResource(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - ExternalProviders: map[string]resource.ExternalProvider{ - "netbox": { - VersionConstraint: "~> 3.8.7", - Source: "registry.terraform.io/e-breuninger/netbox", - }, - }, + ExternalProviders: testExternalProviders, Steps: []resource.TestStep{ { Config: fmt.Sprintf(`%s diff --git a/internal/provider/common.go b/internal/provider/common.go new file mode 100644 index 0000000..619c1ef --- /dev/null +++ b/internal/provider/common.go @@ -0,0 +1,346 @@ +package provider + +import ( + "github.com/ffddorf/terraform-provider-netbox-bgp/client" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type NestedSite struct { + Display types.String `tfsdk:"display"` + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Slug types.String `tfsdk:"slug"` + URL types.String `tfsdk:"url"` +} + +type NestedASN struct { + ASN types.Int64 `tfsdk:"asn"` + Display types.String `tfsdk:"display"` + ID types.Int64 `tfsdk:"id"` + URL types.String `tfsdk:"url"` +} + +type NestedIPAddress struct { + Address types.String `tfsdk:"address"` + Display types.String `tfsdk:"display"` + Family types.Int64 `tfsdk:"family"` + ID types.Int64 `tfsdk:"id"` + URL types.String `tfsdk:"url"` +} + +type NestedDevice struct { + Display types.String `tfsdk:"display"` + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + URL types.String `tfsdk:"url"` +} + +type NestedBGPPeerGroup struct { + Description types.String `tfsdk:"description"` + Display types.String `tfsdk:"display"` + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + URL types.String `tfsdk:"url"` +} + +type NestedPrefixList struct { + Display types.String `tfsdk:"display"` + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + URL types.String `tfsdk:"url"` +} + +type NestedTenant struct { + Display types.String `tfsdk:"display"` + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Slug types.String `tfsdk:"slug"` + URL types.String `tfsdk:"url"` +} + +func (tfo NestedSite) ToAPIModel() client.NestedSite { + return client.NestedSite{ + Id: toIntPointer(tfo.ID.ValueInt64Pointer()), + Url: tfo.URL.ValueStringPointer(), + Display: tfo.Display.ValueStringPointer(), + Name: tfo.Name.ValueString(), + Slug: tfo.Slug.ValueString(), + } +} + +func (tfo NestedASN) ToAPIModel() client.NestedASN { + return client.NestedASN{ + Id: toIntPointer(tfo.ID.ValueInt64Pointer()), + Url: tfo.URL.ValueStringPointer(), + Display: tfo.Display.ValueStringPointer(), + Asn: tfo.ASN.ValueInt64(), + } +} + +func (tfo NestedIPAddress) ToAPIModel() client.NestedIPAddress { + return client.NestedIPAddress{ + Id: toIntPointer(tfo.ID.ValueInt64Pointer()), + Url: tfo.URL.ValueStringPointer(), + Display: tfo.Display.ValueStringPointer(), + Family: toIntPointer(tfo.Family.ValueInt64Pointer()), + Address: tfo.Address.ValueString(), + } +} + +func (tfo NestedDevice) ToAPIModel() client.NestedDevice { + return client.NestedDevice{ + Id: toIntPointer(tfo.ID.ValueInt64Pointer()), + Url: tfo.URL.ValueStringPointer(), + Display: tfo.Display.ValueStringPointer(), + Name: tfo.Name.ValueStringPointer(), + } +} + +func (tfo NestedBGPPeerGroup) ToAPIModel() client.NestedBGPPeerGroup { + return client.NestedBGPPeerGroup{ + Id: toIntPointer(tfo.ID.ValueInt64Pointer()), + Url: tfo.URL.ValueStringPointer(), + Display: tfo.Display.ValueStringPointer(), + Name: tfo.Name.ValueString(), + Description: tfo.Description.ValueStringPointer(), + } +} + +func (tfo NestedPrefixList) ToAPIModel() client.NestedPrefixList { + return client.NestedPrefixList{ + Id: toIntPointer(tfo.ID.ValueInt64Pointer()), + Url: tfo.URL.ValueStringPointer(), + Display: tfo.Display.ValueStringPointer(), + Name: tfo.Name.ValueString(), + } +} + +func (tfo NestedTenant) ToAPIModel() client.NestedTenant { + return client.NestedTenant{ + Id: toIntPointer(tfo.ID.ValueInt64Pointer()), + Url: tfo.URL.ValueStringPointer(), + Display: tfo.Display.ValueStringPointer(), + Name: tfo.Name.ValueString(), + Slug: tfo.Slug.ValueString(), + } +} + +func NestedSiteFromAPI(resp *client.NestedSite) *NestedSite { + if resp == nil { + return nil + } + tfo := &NestedSite{} + tfo.ID = types.Int64Value(int64(*resp.Id)) + tfo.URL = maybeStringValue(resp.Url) + tfo.Display = maybeStringValue(resp.Display) + tfo.Name = types.StringValue(resp.Name) + tfo.Slug = types.StringValue(resp.Slug) + return tfo +} + +func NestedASNFromAPI(resp *client.NestedASN) *NestedASN { + if resp == nil { + return nil + } + tfo := &NestedASN{} + tfo.ID = types.Int64Value(int64(*resp.Id)) + tfo.URL = maybeStringValue(resp.Url) + tfo.Display = maybeStringValue(resp.Display) + tfo.ASN = types.Int64Value(resp.Asn) + return tfo +} + +func NestedIPAddressFromAPI(resp *client.NestedIPAddress) *NestedIPAddress { + if resp == nil { + return nil + } + tfo := &NestedIPAddress{} + tfo.ID = types.Int64Value(int64(*resp.Id)) + tfo.URL = maybeStringValue(resp.Url) + tfo.Display = maybeStringValue(resp.Display) + tfo.Family = maybeInt64Value(resp.Family) + tfo.Address = types.StringValue(resp.Address) + return tfo +} + +func NestedDeviceFromAPI(resp *client.NestedDevice) *NestedDevice { + if resp == nil { + return nil + } + tfo := &NestedDevice{} + tfo.ID = types.Int64Value(int64(*resp.Id)) + tfo.URL = maybeStringValue(resp.Url) + tfo.Display = maybeStringValue(resp.Display) + tfo.Name = maybeStringValue(resp.Name) + return tfo +} + +func NestedBGPPeerGroupFromAPI(resp *client.NestedBGPPeerGroup) *NestedBGPPeerGroup { + if resp == nil { + return nil + } + tfo := &NestedBGPPeerGroup{} + tfo.ID = types.Int64Value(int64(*resp.Id)) + tfo.URL = maybeStringValue(resp.Url) + tfo.Display = maybeStringValue(resp.Display) + tfo.Name = types.StringValue(resp.Name) + tfo.Description = maybeStringValue(resp.Description) + return tfo +} + +func NestedPrefixListFromAPI(resp *client.NestedPrefixList) *NestedPrefixList { + if resp == nil { + return nil + } + tfo := &NestedPrefixList{} + tfo.ID = types.Int64Value(int64(*resp.Id)) + tfo.URL = maybeStringValue(resp.Url) + tfo.Display = maybeStringValue(resp.Display) + tfo.Name = types.StringValue(resp.Name) + return tfo +} + +func NestedTenantFromAPI(resp *client.NestedTenant) *NestedTenant { + if resp == nil { + return nil + } + tfo := &NestedTenant{} + tfo.ID = types.Int64Value(int64(*resp.Id)) + tfo.URL = maybeStringValue(resp.Url) + tfo.Display = maybeStringValue(resp.Display) + tfo.Name = types.StringValue(resp.Name) + tfo.Slug = types.StringValue(resp.Slug) + return tfo +} + +func (*NestedSite) SchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + }, + "display": schema.StringAttribute{ + Computed: true, + }, + "url": schema.StringAttribute{ + Optional: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "slug": schema.StringAttribute{ + Required: true, + }, + } +} + +func (*NestedASN) SchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + }, + "display": schema.StringAttribute{ + Computed: true, + }, + "url": schema.StringAttribute{ + Optional: true, + }, + "asn": schema.Int64Attribute{ + Required: true, + }, + } +} + +func (*NestedIPAddress) SchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + }, + "display": schema.StringAttribute{ + Computed: true, + }, + "url": schema.StringAttribute{ + Optional: true, + }, + "family": schema.Int64Attribute{ + Optional: true, + }, + "address": schema.StringAttribute{ + Required: true, + }, + } +} + +func (*NestedDevice) SchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + }, + "display": schema.StringAttribute{ + Computed: true, + }, + "url": schema.StringAttribute{ + Optional: true, + }, + "name": schema.StringAttribute{ + Optional: true, + }, + } +} + +func (*NestedBGPPeerGroup) SchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + }, + "display": schema.StringAttribute{ + Computed: true, + }, + "url": schema.StringAttribute{ + Optional: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "description": schema.StringAttribute{ + Optional: true, + }, + } +} + +func (*NestedPrefixList) SchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + }, + "display": schema.StringAttribute{ + Computed: true, + }, + "url": schema.StringAttribute{ + Optional: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + } +} + +func (*NestedTenant) SchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + }, + "display": schema.StringAttribute{ + Computed: true, + }, + "url": schema.StringAttribute{ + Optional: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "slug": schema.StringAttribute{ + Required: true, + }, + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 99c55cd..883b583 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -168,7 +168,9 @@ func (p *NetboxBGPProvider) Resources(ctx context.Context) []func() resource.Res } func (p *NetboxBGPProvider) DataSources(ctx context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} + return []func() datasource.DataSource{ + NewSessionDataSource, + } } func New(version string) func() provider.Provider { diff --git a/internal/provider/utils.go b/internal/provider/utils.go index 0323660..03a3710 100644 --- a/internal/provider/utils.go +++ b/internal/provider/utils.go @@ -12,8 +12,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -func toIntPointer(from int64) *int { - val := int(from) +func toIntPointer(from *int64) *int { + if from == nil { + return nil + } + val := int(*from) return &val } @@ -31,6 +34,30 @@ func toIntListPointer(ctx context.Context, from types.List) ([]int, diag.Diagnos return out, diags } +func maybeStringValue(in *string) types.String { + if in == nil { + return types.StringNull() + } + if *in == "" { + return types.StringNull() + } + return types.StringPointerValue(in) +} + +func maybeInt64Value(in *int) types.Int64 { + if in == nil { + return types.Int64Null() + } + return types.Int64Value(int64(*in)) +} + +func fromInt64Value(in types.Int64) *int { + if in.IsNull() { + return nil + } + return toIntPointer(in.ValueInt64Pointer()) +} + func httpError(res *http.Response, body []byte) string { return fmt.Sprintf("Bad response: Status %d with content type \"%s\"\n%s", res.StatusCode, res.Header.Get("Content-Type"), string(body)) }