diff --git a/cmd/gen-filters/main.go b/cmd/gen-filters/main.go
new file mode 100644
index 0000000..92d4339
--- /dev/null
+++ b/cmd/gen-filters/main.go
@@ -0,0 +1,281 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/format"
+ "go/types"
+ "io"
+ "os"
+ "regexp"
+ "slices"
+ "sort"
+ "strings"
+ "text/template"
+
+ "golang.org/x/tools/go/packages"
+)
+
+var tagRegex = regexp.MustCompile(`([a-z]+):"([^"]+)"\s*`)
+
+type opDef struct {
+ jsonName string
+ goFieldName string
+ goType types.Type
+}
+
+type fieldDef struct {
+ goName string
+ goType types.Type
+ jsonName string
+ operators []opDef
+}
+
+func resolveInnerType(t types.Type) (resolved types.Type, isPointer bool, isSlice bool) {
+ resolved = t
+ pointer, isPointer := resolved.(*types.Pointer)
+ if isPointer {
+ resolved = pointer.Elem()
+ }
+ slice, isSlice := resolved.(*types.Slice)
+ if isSlice {
+ resolved = slice.Elem()
+ }
+ return
+}
+
+func addStringParser(w io.Writer, t types.Type) {
+ defer fmt.Fprintln(w)
+
+ typeName := t.String()
+ switch typeName {
+ case "string":
+ fmt.Fprint(w, `v := value.ValueString()`)
+ return
+ case "time.Time":
+ fmt.Fprint(w, `v, err := time.Parse(time.RFC3339, value.ValueString())`)
+ case "bool":
+ fmt.Fprint(w, `v, err := strconv.ParseBool(value.ValueString())`)
+ case "int64":
+ fmt.Fprint(w, `v, err := strconv.ParseInt(value.ValueString(), 10, 64)`)
+ case "int32":
+ fmt.Fprint(w, `v64, err := strconv.ParseInt(value.ValueString(), 10, 32)`)
+ fmt.Fprintf(w, `
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as %s value: %%s", value.ValueString()),
+ )
+ }`, typeName)
+ fmt.Fprintln(w)
+ fmt.Fprint(w, `v := int32(v64)`)
+ return
+ case "int":
+ fmt.Fprint(w, `v, err := strconv.Atoi(value.ValueString())`)
+ case "github.com/google/uuid.UUID":
+ fmt.Fprint(w, `v, err := uuid.Parse(value.ValueString())`)
+ default:
+ panic(fmt.Errorf("unhandled type: %s", typeName))
+ }
+ fmt.Fprintf(w, `
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as %s value: %%s", value.ValueString()),
+ )
+ }`, typeName)
+}
+
+type wrapperOpts struct {
+ Logic string
+ ShortName string
+ FullName string
+
+ Fields []string
+ Operators []string
+}
+
+var wrapper = template.Must(template.New("wrapper").
+ Funcs(template.FuncMap{"StringsJoin": strings.Join}).
+ Parse(`// Generated code. DO NOT EDIT!
+package provider
+
+var (
+ {{ .ShortName }}Fields = []string{"{{ StringsJoin .Fields "\",\"" }}"}
+ {{ .ShortName }}Operators = []string{"{{ StringsJoin .Operators "\",\"" }}"}
+)
+
+func set{{ .ShortName }}FromFilter(filter Filter, params *client.{{ .FullName }}) diag.Diagnostic {
+ name := filter.Name.ValueString()
+ op := filter.Operator.ValueString()
+ if op == "" {
+ op = "eq"
+ }
+ value := filter.Value
+ {{ .Logic }}
+ return nil
+}
+`))
+
+// these have a special meaning and are set manually.
+var specialFields = []string{"limit", "offset", "ordering"}
+
+func main() {
+ paramsName := os.Args[1]
+
+ cfg := &packages.Config{
+ Mode: packages.NeedTypes,
+ }
+ pkgs, err := packages.Load(cfg, "github.com/ffddorf/terraform-provider-netbox-bgp/client")
+ if err != nil {
+ panic(err)
+ }
+ pkg := pkgs[0]
+ scope := pkg.Types.Scope()
+
+ paramsObj := scope.Lookup(paramsName)
+ if paramsObj == nil {
+ panic(fmt.Errorf("type not found: %s", paramsName))
+ }
+
+ paramsType := paramsObj.(*types.TypeName) //nolint:forcetypeassert
+ paramsNamed := paramsType.Type().(*types.Named) //nolint:forcetypeassert
+ params := paramsNamed.Underlying().(*types.Struct) //nolint:forcetypeassert
+ fields := make(map[string]*fieldDef)
+ for i := range params.NumFields() {
+ f := params.Field(i)
+ tag := params.Tag(i)
+ tagKVs := tagRegex.FindAllStringSubmatch(tag, -1)
+ for _, m := range tagKVs {
+ if m[1] == "json" {
+ jsonBaseName, _, _ := strings.Cut(m[2], ",")
+ jsonName, op, hasSuffix := strings.Cut(jsonBaseName, "__")
+ if !hasSuffix {
+ op = "eq"
+ jsonName = jsonBaseName
+ }
+ def, ok := fields[jsonName]
+ if !ok {
+ def = &fieldDef{
+ jsonName: jsonName,
+ }
+ fields[jsonName] = def
+ }
+ if !hasSuffix {
+ // update field def
+ def.goName = f.Name()
+ def.goType = f.Type()
+ }
+ def.operators = append(def.operators, opDef{
+ jsonName: op,
+ goFieldName: f.Name(),
+ goType: f.Type(),
+ })
+ break
+ }
+ }
+ }
+
+ var output strings.Builder
+ output.WriteString(`switch name {`)
+ output.WriteByte('\n')
+
+ fieldList := make([]*fieldDef, 0, len(fields))
+ for _, field := range fields {
+ fieldList = append(fieldList, field)
+ }
+ slices.SortFunc(fieldList, func(a, b *fieldDef) int {
+ return strings.Compare(a.jsonName, b.jsonName)
+ })
+
+ allFields := make([]string, 0, len(fieldList))
+ operatorSet := make(map[string]struct{})
+ for _, field := range fieldList {
+ if slices.Contains(specialFields, field.jsonName) {
+ continue
+ }
+
+ allFields = append(allFields, field.jsonName)
+
+ fmt.Fprintf(&output, `case "%s":`, field.jsonName)
+ output.WriteByte('\n')
+
+ fieldType, _, _ := resolveInnerType(field.goType)
+ if fieldType == nil {
+ fmt.Printf("type on %s resolved to no type: %#v\n", field.jsonName, field.goType)
+ continue
+ }
+ addStringParser(&output, fieldType)
+
+ output.WriteString(`switch op {`)
+ output.WriteByte('\n')
+
+ for _, op := range field.operators {
+ operatorSet[op.jsonName] = struct{}{}
+
+ fmt.Fprintf(&output, `case "%s":`, op.jsonName)
+ output.WriteByte('\n')
+
+ opType, isPointer, isSlice := resolveInnerType(op.goType)
+ if fieldType != opType {
+ addStringParser(&output, opType)
+ }
+
+ fmt.Fprintf(&output, `params.%s = `, op.goFieldName)
+ if isSlice {
+ if isPointer {
+ output.WriteString("appendPointerSlice(")
+ } else {
+ output.WriteString("append(")
+ }
+ fmt.Fprintf(&output, "params.%s, v)", op.goFieldName)
+ } else {
+ if isPointer {
+ output.WriteString("&v")
+ } else {
+ output.WriteString("v")
+ }
+ }
+
+ output.WriteByte('\n')
+ }
+
+ output.WriteString(` default:
+ return unexpectedOperator(op, name)
+ }`)
+ output.WriteByte('\n')
+ }
+
+ output.WriteString(` default:
+ return diag.NewErrorDiagnostic(
+ "Unexpected filter name",
+ fmt.Sprintf("Did not recognize field name: %s", name),
+ )
+ }`)
+
+ allOperators := make([]string, 0, len(operatorSet))
+ for op := range operatorSet {
+ allOperators = append(allOperators, op)
+ }
+ sort.Strings(allOperators)
+
+ shortName := strings.TrimPrefix(paramsName, "PluginsBgp")
+ var wrapped bytes.Buffer
+ err = wrapper.Execute(&wrapped, wrapperOpts{
+ Logic: output.String(),
+ ShortName: shortName,
+ FullName: paramsName,
+ Fields: allFields,
+ Operators: allOperators,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ formatted, err := format.Source(wrapped.Bytes())
+ if err != nil {
+ panic(err)
+ }
+
+ _, _ = io.Copy(os.Stdout, bytes.NewReader(formatted))
+}
diff --git a/docs/data-sources/sessions.md b/docs/data-sources/sessions.md
new file mode 100644
index 0000000..bfab916
--- /dev/null
+++ b/docs/data-sources/sessions.md
@@ -0,0 +1,177 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "netboxbgp_sessions Data Source - netboxbgp"
+subcategory: ""
+description: |-
+ Data source to query for multiple BGP sessions by arbitrary parameters
+---
+
+# netboxbgp_sessions (Data Source)
+
+Data source to query for multiple BGP sessions by arbitrary parameters
+
+
+
+
+## Schema
+
+### Optional
+
+- `filters` (Attributes List) (see [below for nested schema](#nestedatt--filters))
+- `limit` (Number)
+- `ordering` (String)
+
+### Read-Only
+
+- `sessions` (List of Object) (see [below for nested schema](#nestedatt--sessions))
+
+
+### Nested Schema for `filters`
+
+Required:
+
+- `name` (String)
+- `value` (String)
+
+Optional:
+
+- `operator` (String)
+
+
+
+### Nested Schema for `sessions`
+
+Read-Only:
+
+- `comments` (String)
+- `description` (String)
+- `device` (Object) (see [below for nested schema](#nestedobjatt--sessions--device))
+- `export_policy_ids` (List of Number)
+- `id` (Number)
+- `import_policy_ids` (List of Number)
+- `local_address` (Object) (see [below for nested schema](#nestedobjatt--sessions--local_address))
+- `local_as` (Object) (see [below for nested schema](#nestedobjatt--sessions--local_as))
+- `name` (String)
+- `peer_group` (Object) (see [below for nested schema](#nestedobjatt--sessions--peer_group))
+- `prefix_list_in` (Object) (see [below for nested schema](#nestedobjatt--sessions--prefix_list_in))
+- `prefix_list_out` (Object) (see [below for nested schema](#nestedobjatt--sessions--prefix_list_out))
+- `remote_address` (Object) (see [below for nested schema](#nestedobjatt--sessions--remote_address))
+- `remote_as` (Object) (see [below for nested schema](#nestedobjatt--sessions--remote_as))
+- `site` (Object) (see [below for nested schema](#nestedobjatt--sessions--site))
+- `status` (String)
+- `tags` (List of String)
+- `tenant` (Object) (see [below for nested schema](#nestedobjatt--sessions--tenant))
+
+
+### Nested Schema for `sessions.device`
+
+Read-Only:
+
+- `display` (String)
+- `id` (Number)
+- `name` (String)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.local_address`
+
+Read-Only:
+
+- `address` (String)
+- `display` (String)
+- `family` (Number)
+- `id` (Number)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.local_as`
+
+Read-Only:
+
+- `asn` (Number)
+- `display` (String)
+- `id` (Number)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.peer_group`
+
+Read-Only:
+
+- `description` (String)
+- `display` (String)
+- `id` (Number)
+- `name` (String)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.prefix_list_in`
+
+Read-Only:
+
+- `display` (String)
+- `id` (Number)
+- `name` (String)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.prefix_list_out`
+
+Read-Only:
+
+- `display` (String)
+- `id` (Number)
+- `name` (String)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.remote_address`
+
+Read-Only:
+
+- `address` (String)
+- `display` (String)
+- `family` (Number)
+- `id` (Number)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.remote_as`
+
+Read-Only:
+
+- `asn` (Number)
+- `display` (String)
+- `id` (Number)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.site`
+
+Read-Only:
+
+- `display` (String)
+- `id` (Number)
+- `name` (String)
+- `slug` (String)
+- `url` (String)
+
+
+
+### Nested Schema for `sessions.tenant`
+
+Read-Only:
+
+- `display` (String)
+- `id` (Number)
+- `name` (String)
+- `slug` (String)
+- `url` (String)
diff --git a/go.mod b/go.mod
index d5bc501..22181e7 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0
github.com/oapi-codegen/runtime v1.1.1
github.com/sethvargo/go-envconfig v1.1.0
+ golang.org/x/tools v0.23.0
)
require (
@@ -82,12 +83,11 @@ require (
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect
- golang.org/x/mod v0.17.0 // indirect
- golang.org/x/net v0.25.0 // indirect
+ golang.org/x/mod v0.19.0 // indirect
+ golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
- golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/grpc v1.63.2 // indirect
diff --git a/go.sum b/go.sum
index c4c14cc..d626633 100644
--- a/go.sum
+++ b/go.sum
@@ -246,6 +246,8 @@ golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N0
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
+golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -253,6 +255,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -289,6 +293,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
+golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
diff --git a/internal/provider/bgpsession_datasource.go b/internal/provider/bgpsession_datasource.go
index 3cacd96..bf28eaa 100644
--- a/internal/provider/bgpsession_datasource.go
+++ b/internal/provider/bgpsession_datasource.go
@@ -8,7 +8,6 @@ import (
"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"
)
@@ -40,8 +39,8 @@ type SessionDataSourceModel struct {
RemoteAS *NestedASN `tfsdk:"remote_as"`
PeerGroup *NestedBGPPeerGroup `tfsdk:"peer_group"`
- ImportPolicyIDs types.List `tfsdk:"import_policy_ids"`
- ExportPolicyIDs types.List `tfsdk:"export_policy_ids"`
+ ImportPolicyIDs []types.Int64 `tfsdk:"import_policy_ids"`
+ ExportPolicyIDs []types.Int64 `tfsdk:"export_policy_ids"`
PrefixListIn *NestedPrefixList `tfsdk:"prefix_list_in"`
PrefixListOut *NestedPrefixList `tfsdk:"prefix_list_out"`
@@ -54,18 +53,14 @@ func (m *SessionDataSourceModel) FillFromAPIModel(ctx context.Context, resp *cli
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.ExportPolicies != nil {
+ for _, id := range *resp.ExportPolicies {
+ m.ExportPolicyIDs = append(m.ExportPolicyIDs, types.Int64Value(int64(id)))
}
}
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))
+ for _, id := range *resp.ImportPolicies {
+ m.ImportPolicyIDs = append(m.ImportPolicyIDs, types.Int64Value(int64(id)))
}
}
m.LocalAddress = NestedIPAddressFromAPI(&resp.LocalAddress)
@@ -89,78 +84,79 @@ func (d *SessionDataSource) Metadata(ctx context.Context, req datasource.Metadat
resp.TypeName = req.ProviderTypeName + "_session"
}
+var sessionDataSchema = 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) 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,
- },
+ Attributes: sessionDataSchema,
}
}
diff --git a/internal/provider/bgpsession_resource_test.go b/internal/provider/bgpsession_resource_test.go
index 638d71c..913da1f 100644
--- a/internal/provider/bgpsession_resource_test.go
+++ b/internal/provider/bgpsession_resource_test.go
@@ -29,6 +29,12 @@ func testNum(t *testing.T) uint64 {
return h.Sum64()
}
+func testIP(t *testing.T, offset uint64) string {
+ num := testNum(t)
+ shortNum := num % 250
+ return fmt.Sprintf("203.0.113.%d", shortNum+offset)
+}
+
func baseResources(t *testing.T) string {
num := testNum(t)
shortNum := num % 250
@@ -70,14 +76,14 @@ resource "netbox_device_interface" "test" {
}
resource "netbox_ip_address" "local" {
- ip_address = "203.0.113.%[2]d/24"
+ ip_address = "%[2]s/24"
status = "active"
interface_id = netbox_device_interface.test.id
object_type = "dcim.interface"
}
resource "netbox_ip_address" "remote" {
- ip_address = "203.0.113.%[3]d/24"
+ ip_address = "%[3]s/24"
status = "active"
}
@@ -88,7 +94,7 @@ resource "netbox_rir" "test" {
resource "netbox_asn" "test" {
asn = %[4]d
rir_id = netbox_rir.test.id
-}`, testName(t), shortNum, shortNum+1, shortNum+1337)
+}`, testName(t), testIP(t, 0), testIP(t, 1), shortNum+1337)
}
func TestAccSessionResource(t *testing.T) {
diff --git a/internal/provider/bgpsessions_datasource.go b/internal/provider/bgpsessions_datasource.go
new file mode 100644
index 0000000..b185962
--- /dev/null
+++ b/internal/provider/bgpsessions_datasource.go
@@ -0,0 +1,164 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/ffddorf/terraform-provider-netbox-bgp/client"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "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"
+)
+
+//go:generate sh -c "go run github.com/ffddorf/terraform-provider-netbox-bgp/cmd/gen-filters PluginsBgpBgpsessionListParams > bgpsessions_filters.gen.go && go run golang.org/x/tools/cmd/goimports -w bgpsessions_filters.gen.go"
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ datasource.DataSource = &SessionsDataSource{}
+
+func NewSessionsDataSource() datasource.DataSource {
+ return &SessionsDataSource{}
+}
+
+type SessionsDataSource struct {
+ client *client.Client
+}
+
+type SessionsDataSourceModel struct {
+ Filters Filters `tfsdk:"filters"`
+ Limit types.Int64 `tfsdk:"limit"`
+ Ordering types.String `tfsdk:"ordering"`
+ Sessions []SessionDataSourceModel `tfsdk:"sessions"`
+}
+
+func (d *SessionsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_sessions"
+}
+
+func (d *SessionsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ sessionAttrs := map[string]attr.Type{}
+ for attrName, attrSchema := range sessionDataSchema {
+ sessionAttrs[attrName] = attrSchema.GetType()
+ }
+
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Data source to query for multiple BGP sessions by arbitrary parameters",
+ Attributes: map[string]schema.Attribute{
+ "filters": schema.ListNestedAttribute{
+ NestedObject: FiltersSchema(BgpsessionListParamsFields, BgpsessionListParamsOperators),
+ Optional: true,
+ },
+ "limit": schema.Int64Attribute{
+ Optional: true,
+ },
+ "ordering": schema.StringAttribute{
+ Optional: true,
+ },
+ "sessions": schema.ListAttribute{
+ ElementType: types.ObjectType{
+ AttrTypes: sessionAttrs,
+ },
+ Computed: true,
+ },
+ },
+ }
+}
+
+func (d *SessionsDataSource) 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 unexpectedOperator(op, name string) diag.Diagnostic {
+ return diag.NewErrorDiagnostic(
+ "Unexpected operator",
+ fmt.Sprintf(`The operator "%s" does not work with the field name "%s"`, op, name),
+ )
+}
+
+func (d *SessionsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var data SessionsDataSourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // construct filters
+ var params client.PluginsBgpBgpsessionListParams
+ for i, filter := range data.Filters {
+ if d := setBgpsessionListParamsFromFilter(filter, ¶ms); d != nil {
+ resp.Diagnostics.Append(diag.WithPath(path.Root("filters").AtListIndex(i), d))
+ }
+ }
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ params.Limit = fromInt64Value(data.Limit)
+ params.Ordering = data.Ordering.ValueStringPointer()
+
+ nextHTTPReq, err := client.NewPluginsBgpBgpsessionListRequest(d.client.Server, ¶ms)
+ for nextHTTPReq != nil {
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to create session list request: %s", err))
+ return
+ }
+
+ var httpRes *http.Response
+ httpRes, err = doPlainReq(ctx, nextHTTPReq, d.client)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to retrieve sessions: %s", err))
+ return
+ }
+ var res *client.PluginsBgpBgpsessionListResponse
+ res, err = client.ParsePluginsBgpBgpsessionListResponse(httpRes)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to parse sessions: %s", err))
+ return
+ }
+ if res.JSON200 == nil {
+ resp.Diagnostics.AddError("Client Error", httpError(httpRes, res.Body))
+ return
+ }
+ if res.JSON200.Results != nil {
+ for _, sess := range *res.JSON200.Results {
+ m := SessionDataSourceModel{}
+ m.FillFromAPIModel(ctx, &sess, resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ data.Sessions = append(data.Sessions, m)
+ }
+ }
+
+ // if there was a limit configured, only return elements up to the limit
+ if !data.Limit.IsNull() && len(data.Sessions) >= int(data.Limit.ValueInt64()) {
+ break
+ }
+
+ if res.JSON200.Next == nil || *res.JSON200.Next == "" {
+ break
+ }
+
+ // handle pagination, query next results
+ nextHTTPReq, err = http.NewRequest(http.MethodGet, *res.JSON200.Next, nil)
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/provider/bgpsessions_datasource_test.go b/internal/provider/bgpsessions_datasource_test.go
new file mode 100644
index 0000000..3fd3b81
--- /dev/null
+++ b/internal/provider/bgpsessions_datasource_test.go
@@ -0,0 +1,119 @@
+package provider
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/knownvalue"
+ "github.com/hashicorp/terraform-plugin-testing/statecheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
+)
+
+func testSessions(t *testing.T) string {
+ return fmt.Sprintf(`%s
+ resource "netboxbgp_session" "test1" {
+ name = "Session 1"
+ 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
+ }
+
+ resource "netbox_ip_address" "remote2" {
+ ip_address = "%s/24"
+ status = "active"
+ }
+
+ resource "netboxbgp_session" "test2" {
+ name = "Session 2"
+ status = "planned"
+ device_id = netbox_device.test.id
+ local_address_id = netbox_ip_address.local.id
+ remote_address_id = netbox_ip_address.remote2.id
+ local_as_id = netbox_asn.test.id
+ remote_as_id = netbox_asn.test.id
+ }
+
+ resource "netbox_ip_address" "remote3" {
+ ip_address = "%s/24"
+ status = "active"
+ }
+
+ resource "netboxbgp_session" "test3" {
+ name = "Session 3"
+ status = "active"
+ device_id = netbox_device.test.id
+ local_address_id = netbox_ip_address.local.id
+ remote_address_id = netbox_ip_address.remote3.id
+ local_as_id = netbox_asn.test.id
+ remote_as_id = netbox_asn.test.id
+ }`, baseResources(t), testIP(t, 2), testIP(t, 3))
+}
+
+func TestAccSessionsDataSource(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
+ data "netboxbgp_sessions" "test_active" {
+ depends_on = [
+ netboxbgp_session.test1,
+ netboxbgp_session.test2,
+ netboxbgp_session.test3,
+ ]
+
+ filters = [
+ { name: "status", value: "active" }
+ ]
+
+ ordering = "name"
+ }
+
+ data "netboxbgp_sessions" "test_limit" {
+ depends_on = [
+ netboxbgp_session.test1,
+ netboxbgp_session.test2,
+ netboxbgp_session.test3,
+ ]
+
+ limit = 2
+ ordering = "name"
+ }
+ `, testSessions(t)),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue(
+ "data.netboxbgp_sessions.test_active",
+ tfjsonpath.New("sessions"),
+ knownvalue.ListExact([]knownvalue.Check{
+ knownvalue.ObjectPartial(map[string]knownvalue.Check{
+ "name": knownvalue.StringExact("Session 1"),
+ }),
+ knownvalue.ObjectPartial(map[string]knownvalue.Check{
+ "name": knownvalue.StringExact("Session 3"),
+ }),
+ }),
+ ),
+ statecheck.ExpectKnownValue(
+ "data.netboxbgp_sessions.test_limit",
+ tfjsonpath.New("sessions"),
+ knownvalue.ListExact([]knownvalue.Check{
+ knownvalue.ObjectPartial(map[string]knownvalue.Check{
+ "name": knownvalue.StringExact("Session 1"),
+ }),
+ knownvalue.ObjectPartial(map[string]knownvalue.Check{
+ "name": knownvalue.StringExact("Session 2"),
+ }),
+ }),
+ ),
+ },
+ },
+ },
+ })
+}
diff --git a/internal/provider/bgpsessions_filters.gen.go b/internal/provider/bgpsessions_filters.gen.go
new file mode 100644
index 0000000..09fbc40
--- /dev/null
+++ b/internal/provider/bgpsessions_filters.gen.go
@@ -0,0 +1,508 @@
+// Generated code. DO NOT EDIT!
+package provider
+
+import (
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/ffddorf/terraform-provider-netbox-bgp/client"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+)
+
+var (
+ BgpsessionListParamsFields = []string{"by_local_address", "by_remote_address", "created", "created_by_request", "description", "device", "device_id", "export_policies", "id", "import_policies", "last_updated", "local_address", "local_address_id", "local_as", "local_as_id", "modified_by_request", "name", "peer_group", "q", "remote_address", "remote_address_id", "remote_as", "remote_as_id", "site", "site_id", "status", "tag", "tenant", "updated_by_request"}
+ BgpsessionListParamsOperators = []string{"empty", "eq", "gt", "gte", "ic", "ie", "iew", "isw", "lt", "lte", "n", "nic", "nie", "niew", "nisw"}
+)
+
+func setBgpsessionListParamsFromFilter(filter Filter, params *client.PluginsBgpBgpsessionListParams) diag.Diagnostic {
+ name := filter.Name.ValueString()
+ op := filter.Operator.ValueString()
+ if op == "" {
+ op = "eq"
+ }
+ value := filter.Value
+ switch name {
+ case "by_local_address":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.ByLocalAddress = &v
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "by_remote_address":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.ByRemoteAddress = &v
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "created":
+ v, err := time.Parse(time.RFC3339, value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as time.Time value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.Created = appendPointerSlice(params.Created, v)
+ case "empty":
+ params.CreatedEmpty = appendPointerSlice(params.CreatedEmpty, v)
+ case "gt":
+ params.CreatedGt = appendPointerSlice(params.CreatedGt, v)
+ case "gte":
+ params.CreatedGte = appendPointerSlice(params.CreatedGte, v)
+ case "lt":
+ params.CreatedLt = appendPointerSlice(params.CreatedLt, v)
+ case "lte":
+ params.CreatedLte = appendPointerSlice(params.CreatedLte, v)
+ case "n":
+ params.CreatedN = appendPointerSlice(params.CreatedN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "created_by_request":
+ v, err := uuid.Parse(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as github.com/google/uuid.UUID value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.CreatedByRequest = &v
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "description":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.Description = appendPointerSlice(params.Description, v)
+ case "empty":
+ v, err := strconv.ParseBool(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as bool value: %s", value.ValueString()),
+ )
+ }
+ params.DescriptionEmpty = &v
+ case "ic":
+ params.DescriptionIc = appendPointerSlice(params.DescriptionIc, v)
+ case "ie":
+ params.DescriptionIe = appendPointerSlice(params.DescriptionIe, v)
+ case "iew":
+ params.DescriptionIew = appendPointerSlice(params.DescriptionIew, v)
+ case "isw":
+ params.DescriptionIsw = appendPointerSlice(params.DescriptionIsw, v)
+ case "n":
+ params.DescriptionN = appendPointerSlice(params.DescriptionN, v)
+ case "nic":
+ params.DescriptionNic = appendPointerSlice(params.DescriptionNic, v)
+ case "nie":
+ params.DescriptionNie = appendPointerSlice(params.DescriptionNie, v)
+ case "niew":
+ params.DescriptionNiew = appendPointerSlice(params.DescriptionNiew, v)
+ case "nisw":
+ params.DescriptionNisw = appendPointerSlice(params.DescriptionNisw, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "device":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.Device = appendPointerSlice(params.Device, v)
+ case "n":
+ params.DeviceN = appendPointerSlice(params.DeviceN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "device_id":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.DeviceId = appendPointerSlice(params.DeviceId, v)
+ case "n":
+ params.DeviceIdN = appendPointerSlice(params.DeviceIdN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "export_policies":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.ExportPolicies = appendPointerSlice(params.ExportPolicies, v)
+ case "n":
+ params.ExportPoliciesN = appendPointerSlice(params.ExportPoliciesN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "id":
+ v64, err := strconv.ParseInt(value.ValueString(), 10, 32)
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int32 value: %s", value.ValueString()),
+ )
+ }
+ v := int32(v64)
+ switch op {
+ case "eq":
+ params.Id = appendPointerSlice(params.Id, v)
+ case "empty":
+ v, err := strconv.ParseBool(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as bool value: %s", value.ValueString()),
+ )
+ }
+ params.IdEmpty = &v
+ case "gt":
+ params.IdGt = appendPointerSlice(params.IdGt, v)
+ case "gte":
+ params.IdGte = appendPointerSlice(params.IdGte, v)
+ case "lt":
+ params.IdLt = appendPointerSlice(params.IdLt, v)
+ case "lte":
+ params.IdLte = appendPointerSlice(params.IdLte, v)
+ case "n":
+ params.IdN = appendPointerSlice(params.IdN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "import_policies":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.ImportPolicies = appendPointerSlice(params.ImportPolicies, v)
+ case "n":
+ params.ImportPoliciesN = appendPointerSlice(params.ImportPoliciesN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "last_updated":
+ v, err := time.Parse(time.RFC3339, value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as time.Time value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.LastUpdated = appendPointerSlice(params.LastUpdated, v)
+ case "empty":
+ params.LastUpdatedEmpty = appendPointerSlice(params.LastUpdatedEmpty, v)
+ case "gt":
+ params.LastUpdatedGt = appendPointerSlice(params.LastUpdatedGt, v)
+ case "gte":
+ params.LastUpdatedGte = appendPointerSlice(params.LastUpdatedGte, v)
+ case "lt":
+ params.LastUpdatedLt = appendPointerSlice(params.LastUpdatedLt, v)
+ case "lte":
+ params.LastUpdatedLte = appendPointerSlice(params.LastUpdatedLte, v)
+ case "n":
+ params.LastUpdatedN = appendPointerSlice(params.LastUpdatedN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "local_address":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.LocalAddress = appendPointerSlice(params.LocalAddress, v)
+ case "n":
+ params.LocalAddressN = appendPointerSlice(params.LocalAddressN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "local_address_id":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.LocalAddressId = appendPointerSlice(params.LocalAddressId, v)
+ case "n":
+ params.LocalAddressIdN = appendPointerSlice(params.LocalAddressIdN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "local_as":
+ v, err := strconv.ParseInt(value.ValueString(), 10, 64)
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int64 value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.LocalAs = appendPointerSlice(params.LocalAs, v)
+ case "n":
+ params.LocalAsN = appendPointerSlice(params.LocalAsN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "local_as_id":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.LocalAsId = appendPointerSlice(params.LocalAsId, v)
+ case "n":
+ params.LocalAsIdN = appendPointerSlice(params.LocalAsIdN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "modified_by_request":
+ v, err := uuid.Parse(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as github.com/google/uuid.UUID value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.ModifiedByRequest = &v
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "name":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.Name = appendPointerSlice(params.Name, v)
+ case "empty":
+ v, err := strconv.ParseBool(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as bool value: %s", value.ValueString()),
+ )
+ }
+ params.NameEmpty = &v
+ case "ic":
+ params.NameIc = appendPointerSlice(params.NameIc, v)
+ case "ie":
+ params.NameIe = appendPointerSlice(params.NameIe, v)
+ case "iew":
+ params.NameIew = appendPointerSlice(params.NameIew, v)
+ case "isw":
+ params.NameIsw = appendPointerSlice(params.NameIsw, v)
+ case "n":
+ params.NameN = appendPointerSlice(params.NameN, v)
+ case "nic":
+ params.NameNic = appendPointerSlice(params.NameNic, v)
+ case "nie":
+ params.NameNie = appendPointerSlice(params.NameNie, v)
+ case "niew":
+ params.NameNiew = appendPointerSlice(params.NameNiew, v)
+ case "nisw":
+ params.NameNisw = appendPointerSlice(params.NameNisw, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "peer_group":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.PeerGroup = appendPointerSlice(params.PeerGroup, v)
+ case "n":
+ params.PeerGroupN = appendPointerSlice(params.PeerGroupN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "q":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.Q = &v
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "remote_address":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.RemoteAddress = appendPointerSlice(params.RemoteAddress, v)
+ case "n":
+ params.RemoteAddressN = appendPointerSlice(params.RemoteAddressN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "remote_address_id":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.RemoteAddressId = appendPointerSlice(params.RemoteAddressId, v)
+ case "n":
+ params.RemoteAddressIdN = appendPointerSlice(params.RemoteAddressIdN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "remote_as":
+ v, err := strconv.ParseInt(value.ValueString(), 10, 64)
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int64 value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.RemoteAs = appendPointerSlice(params.RemoteAs, v)
+ case "n":
+ params.RemoteAsN = appendPointerSlice(params.RemoteAsN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "remote_as_id":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.RemoteAsId = appendPointerSlice(params.RemoteAsId, v)
+ case "n":
+ params.RemoteAsIdN = appendPointerSlice(params.RemoteAsIdN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "site":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.Site = appendPointerSlice(params.Site, v)
+ case "n":
+ params.SiteN = appendPointerSlice(params.SiteN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "site_id":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.SiteId = appendPointerSlice(params.SiteId, v)
+ case "n":
+ params.SiteIdN = appendPointerSlice(params.SiteIdN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "status":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.Status = &v
+ case "n":
+ params.StatusN = &v
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "tag":
+ v := value.ValueString()
+ switch op {
+ case "eq":
+ params.Tag = appendPointerSlice(params.Tag, v)
+ case "n":
+ params.TagN = appendPointerSlice(params.TagN, v)
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "tenant":
+ v, err := strconv.Atoi(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as int value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.Tenant = &v
+ case "n":
+ params.TenantN = &v
+ default:
+ return unexpectedOperator(op, name)
+ }
+ case "updated_by_request":
+ v, err := uuid.Parse(value.ValueString())
+ if err != nil {
+ return diag.NewErrorDiagnostic(
+ "Value Parse Error",
+ fmt.Sprintf("failed to parse as github.com/google/uuid.UUID value: %s", value.ValueString()),
+ )
+ }
+ switch op {
+ case "eq":
+ params.UpdatedByRequest = &v
+ default:
+ return unexpectedOperator(op, name)
+ }
+ default:
+ return diag.NewErrorDiagnostic(
+ "Unexpected filter name",
+ fmt.Sprintf("Did not recognize field name: %s", name),
+ )
+ }
+ return nil
+}
diff --git a/internal/provider/filter.go b/internal/provider/filter.go
new file mode 100644
index 0000000..c0b8f23
--- /dev/null
+++ b/internal/provider/filter.go
@@ -0,0 +1,38 @@
+package provider
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+type Filters []Filter
+
+type Filter struct {
+ Name types.String `tfsdk:"name"`
+ Operator types.String `tfsdk:"operator"`
+ Value types.String `tfsdk:"value"`
+}
+
+func FiltersSchema(fields, ops []string) schema.NestedAttributeObject {
+ return schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(fields...),
+ },
+ },
+ "operator": schema.StringAttribute{
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(ops...),
+ },
+ },
+ "value": schema.StringAttribute{
+ Required: true,
+ },
+ },
+ }
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index c1c4eeb..e512f9c 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -177,6 +177,7 @@ func (p *NetboxBGPProvider) Resources(ctx context.Context) []func() resource.Res
func (p *NetboxBGPProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewSessionDataSource,
+ NewSessionsDataSource,
}
}
diff --git a/internal/provider/utils.go b/internal/provider/utils.go
index 03a3710..33cbcdc 100644
--- a/internal/provider/utils.go
+++ b/internal/provider/utils.go
@@ -6,6 +6,7 @@ import (
"net/http"
"strconv"
+ "github.com/ffddorf/terraform-provider-netbox-bgp/client"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -71,3 +72,23 @@ func importByInt64ID(ctx context.Context, req resource.ImportStateRequest, resp
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...)
}
+
+func appendPointerSlice[T any](s *[]T, vals ...T) *[]T {
+ if s == nil {
+ val := make([]T, 0, len(vals))
+ s = &val
+ }
+ newS := append(*s, vals...)
+ return &newS
+}
+
+func doPlainReq(ctx context.Context, req *http.Request, c *client.Client) (*http.Response, error) {
+ req = req.WithContext(ctx)
+ for _, e := range c.RequestEditors {
+ if err := e(ctx, req); err != nil {
+ return nil, err
+ }
+ }
+
+ return c.Client.Do(req)
+}
diff --git a/tools/tools.go b/tools/tools.go
index d447da4..5b0fdb4 100644
--- a/tools/tools.go
+++ b/tools/tools.go
@@ -11,4 +11,7 @@ import (
// API client generation
_ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen"
+
+ // For generated code
+ _ "golang.org/x/tools/cmd/goimports"
)