diff --git a/docs/data-sources/interface.md b/docs/data-sources/interface.md
new file mode 100644
index 0000000..4bb1347
--- /dev/null
+++ b/docs/data-sources/interface.md
@@ -0,0 +1,57 @@
+---
+page_title: "opnsense_interface Data Source - terraform-provider-opnsense"
+subcategory: Interfaces
+description: |-
+ Interfaces can be used to get configurations of OPNsense interfaces.
+---
+
+# opnsense_interface (Data Source)
+
+Interfaces can be used to get configurations of OPNsense interfaces.
+
+
+## Schema
+
+### Required
+
+- `device` (String) Name of the interface device.
+
+### Read-Only
+
+- `capabilities` (Set of String) List of capabilities the interface supports.
+- `flags` (Set of String) List of flags configured on the interface (equiv. to flags=xxxx in output of ifconfig).
+- `groups` (Set of String) List of groups the interface is a member of.
+- `ipv4` (Attributes List) (see [below for nested schema](#nestedatt--ipv4))
+- `ipv6` (Attributes List) (see [below for nested schema](#nestedatt--ipv6))
+- `is_physical` (Boolean) Whether the interface is physical or virtual.
+- `macaddr` (String) MAC address assigned to the interface.
+- `media` (String) Interface media type settings (see https://man.openbsd.org/ifmedia.4).
+- `media_raw` (String) User-friendly interface media type.
+- `mtu` (Number) Maximum Transmission Unit for the interface. This is typically 1500 bytes but can vary in some circumstances.
+- `options` (Set of String) List of options configured on the interface (equiv. to options=xx in output of ifconfig).
+- `status` (String) Status of the interface (e.g. `"active"`).
+- `supported_media` (Set of String) List of supported media type settings (see https://man.openbsd.org/ifmedia.4).
+
+
+### Nested Schema for `ipv4`
+
+Read-Only:
+
+- `ipaddr` (String) IPv4 address assigned to the interface.
+- `subnetbits` (Number) Number of subnet bits (i.e. CIDR).
+- `tunnel` (Boolean) Whether IPv4 tunnelling is enabled.
+
+
+
+### Nested Schema for `ipv6`
+
+Read-Only:
+
+- `autoconf` (Boolean) Whether auto-configuration is enabled for the address.
+- `deprecated` (Boolean) Whether the address is deprecated.
+- `ipaddr` (String) IPv6 address assigned to the interface.
+- `link_local` (Boolean) Whether the address is link-local.
+- `subnetbits` (Number) Number of subnet bits (i.e. CIDR).
+- `tentative` (Boolean) Whether the address is tentative.
+- `tunnel` (Boolean) Whether IPv6 tunnelling is enabled.
+
diff --git a/go.mod b/go.mod
index 5c75ce7..c6a4745 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module terraform-provider-opnsense
go 1.20
require (
- github.com/browningluke/opnsense-go v0.7.0
+ github.com/browningluke/opnsense-go v0.8.0
github.com/hashicorp/terraform-plugin-docs v0.14.1
github.com/hashicorp/terraform-plugin-framework v1.2.0
github.com/hashicorp/terraform-plugin-framework-validators v0.10.0
diff --git a/go.sum b/go.sum
index 945b4c4..8c7d3a7 100644
--- a/go.sum
+++ b/go.sum
@@ -21,8 +21,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/browningluke/opnsense-go v0.7.0 h1:y6nhqpYnqTvlKx8bZk66n3Bx0mnlR5BfJ8K3sSMW+Jc=
-github.com/browningluke/opnsense-go v0.7.0/go.mod h1:u8di5DaP7igyIBl02jjrAbXYKRtEelnRi3Tqp5aj7g0=
+github.com/browningluke/opnsense-go v0.8.0 h1:i2d1evSXAWTGbe4O24UJAOydQd89YnG4qDbq3fMXRqk=
+github.com/browningluke/opnsense-go v0.8.0/go.mod h1:8TRAG1xjiFzRUEuaosv4mHSXwBJd+R35o6QSEKGVwSA=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 1641000..ae74994 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -140,6 +140,7 @@ func (p *OPNsenseProvider) DataSources(ctx context.Context) []func() datasource.
return []func() datasource.DataSource{
// Interfaces
service.NewInterfacesVlanDataSource,
+ service.NewInterfaceDataSource,
// Routes
service.NewRouteDataSource,
// Unbound
diff --git a/internal/service/interface_data_source.go b/internal/service/interface_data_source.go
new file mode 100644
index 0000000..921ac5a
--- /dev/null
+++ b/internal/service/interface_data_source.go
@@ -0,0 +1,77 @@
+package service
+
+import (
+ "context"
+ "fmt"
+ "github.com/browningluke/opnsense-go/pkg/api"
+ "github.com/browningluke/opnsense-go/pkg/opnsense"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ datasource.DataSource = &InterfaceDataSource{}
+
+func NewInterfaceDataSource() datasource.DataSource {
+ return &InterfaceDataSource{}
+}
+
+// InterfaceDataSource defines the data source implementation.
+type InterfaceDataSource struct {
+ client opnsense.Client
+}
+
+func (d *InterfaceDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_interface"
+}
+
+func (d *InterfaceDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = InterfaceDataSourceSchema()
+}
+
+func (d *InterfaceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+
+ apiClient, ok := req.ProviderData.(*api.Client)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+ return
+ }
+
+ d.client = opnsense.NewClient(apiClient)
+}
+
+func (d *InterfaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var data *InterfaceDataSourceModel
+
+ // Read Terraform configuration data into the model
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Get resource from OPNsense API
+ resource, err := d.client.Diagnostics().GetInterface(ctx, data.Device.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error",
+ fmt.Sprintf("Unable to read interface, got error: %s", err))
+ return
+ }
+
+ // Convert OPNsense struct to TF schema
+ model, err := convertInterfaceConfigStructToSchema(resource)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error",
+ fmt.Sprintf("Unable to read interface, got error: %s", err))
+ return
+ }
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &model)...)
+}
diff --git a/internal/service/interface_schema.go b/internal/service/interface_schema.go
new file mode 100644
index 0000000..62df1e7
--- /dev/null
+++ b/internal/service/interface_schema.go
@@ -0,0 +1,229 @@
+package service
+
+import (
+ "context"
+ "github.com/browningluke/opnsense-go/pkg/diagnostics"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "terraform-provider-opnsense/internal/tools"
+)
+
+type InterfaceDataSourceModel struct {
+ Device types.String `tfsdk:"device"`
+ Media types.String `tfsdk:"media"`
+ MediaRaw types.String `tfsdk:"media_raw"`
+ MacAddr types.String `tfsdk:"macaddr"`
+ IsPhysical types.Bool `tfsdk:"is_physical"`
+ MTU types.Int64 `tfsdk:"mtu"`
+ Status types.String `tfsdk:"status"`
+
+ Flags types.Set `tfsdk:"flags"`
+ Capabilities types.Set `tfsdk:"capabilities"`
+ Options types.Set `tfsdk:"options"`
+ SupportedMedia types.Set `tfsdk:"supported_media"`
+ Groups types.Set `tfsdk:"groups"`
+
+ Ipv4 types.List `tfsdk:"ipv4"`
+ Ipv6 types.List `tfsdk:"ipv6"`
+}
+
+type Ipv4Model struct {
+ Ipaddr types.String `tfsdk:"ipaddr"`
+ SubnetBits types.Int64 `tfsdk:"subnetbits"`
+ Tunnel types.Bool `tfsdk:"tunnel"`
+}
+
+type Ipv6Model struct {
+ Ipaddr types.String `tfsdk:"ipaddr"`
+ SubnetBits types.Int64 `tfsdk:"subnetbits"`
+ Tunnel types.Bool `tfsdk:"tunnel"`
+ Autoconf types.Bool `tfsdk:"autoconf"`
+ Deprecated types.Bool `tfsdk:"deprecated"`
+ LinkLocal types.Bool `tfsdk:"link_local"`
+ Tentative types.Bool `tfsdk:"tentative"`
+}
+
+var ipv4AttrTypes = map[string]attr.Type{
+ "ipaddr": types.StringType,
+ "subnetbits": types.Int64Type,
+ "tunnel": types.BoolType,
+}
+
+var ipv6AttrTypes = map[string]attr.Type{
+ "ipaddr": types.StringType,
+ "subnetbits": types.Int64Type,
+ "tunnel": types.BoolType,
+ "autoconf": types.BoolType,
+ "deprecated": types.BoolType,
+ "link_local": types.BoolType,
+ "tentative": types.BoolType,
+}
+
+func InterfaceDataSourceSchema() schema.Schema {
+ return schema.Schema{
+ MarkdownDescription: "Interfaces can be used to get configurations of OPNsense interfaces.",
+
+ Attributes: map[string]schema.Attribute{
+ "device": schema.StringAttribute{
+ MarkdownDescription: "Name of the interface device.",
+ Required: true,
+ },
+ "media": schema.StringAttribute{
+ MarkdownDescription: "Interface media type settings (see https://man.openbsd.org/ifmedia.4).",
+ Computed: true,
+ },
+ "media_raw": schema.StringAttribute{
+ MarkdownDescription: "User-friendly interface media type.",
+ Computed: true,
+ },
+ "macaddr": schema.StringAttribute{
+ MarkdownDescription: "MAC address assigned to the interface.",
+ Computed: true,
+ },
+ "is_physical": schema.BoolAttribute{
+ MarkdownDescription: "Whether the interface is physical or virtual.",
+ Computed: true,
+ },
+ "mtu": schema.Int64Attribute{
+ MarkdownDescription: "Maximum Transmission Unit for the interface. This is typically 1500 bytes but can vary in some circumstances.",
+ Computed: true,
+ },
+ "status": schema.StringAttribute{
+ MarkdownDescription: "Status of the interface (e.g. `\"active\"`).",
+ Computed: true,
+ },
+ "flags": schema.SetAttribute{
+ MarkdownDescription: "List of flags configured on the interface (equiv. to flags=xxxx in output of ifconfig).",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "capabilities": schema.SetAttribute{
+ MarkdownDescription: "List of capabilities the interface supports.",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "options": schema.SetAttribute{
+ MarkdownDescription: "List of options configured on the interface (equiv. to options=xx in output of ifconfig).",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "supported_media": schema.SetAttribute{
+ MarkdownDescription: "List of supported media type settings (see https://man.openbsd.org/ifmedia.4).",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "groups": schema.SetAttribute{
+ MarkdownDescription: "List of groups the interface is a member of.",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "ipv4": schema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "ipaddr": schema.StringAttribute{
+ MarkdownDescription: "IPv4 address assigned to the interface.",
+ Computed: true,
+ },
+ "subnetbits": schema.Int64Attribute{
+ MarkdownDescription: "Number of subnet bits (i.e. CIDR).",
+ Computed: true,
+ },
+ "tunnel": schema.BoolAttribute{
+ MarkdownDescription: "Whether IPv4 tunnelling is enabled.",
+ Computed: true,
+ },
+ },
+ }},
+ "ipv6": schema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "ipaddr": schema.StringAttribute{
+ MarkdownDescription: "IPv6 address assigned to the interface.",
+ Computed: true,
+ },
+ "subnetbits": schema.Int64Attribute{
+ MarkdownDescription: "Number of subnet bits (i.e. CIDR).",
+ Computed: true,
+ },
+ "tunnel": schema.BoolAttribute{
+ MarkdownDescription: "Whether IPv6 tunnelling is enabled.",
+ Computed: true,
+ },
+ "autoconf": schema.BoolAttribute{
+ MarkdownDescription: "Whether auto-configuration is enabled for the address.",
+ Computed: true,
+ },
+ "deprecated": schema.BoolAttribute{
+ MarkdownDescription: "Whether the address is deprecated.",
+ Computed: true,
+ },
+ "link_local": schema.BoolAttribute{
+ MarkdownDescription: "Whether the address is link-local.",
+ Computed: true,
+ },
+ "tentative": schema.BoolAttribute{
+ MarkdownDescription: "Whether the address is tentative.",
+ Computed: true,
+ },
+ },
+ }},
+ },
+ }
+}
+
+func convertInterfaceConfigStructToSchema(d *diagnostics.Interface) (*InterfaceDataSourceModel, error) {
+ model := &InterfaceDataSourceModel{
+ Device: types.StringValue(d.Device),
+ Media: types.StringValue(d.Media),
+ MediaRaw: types.StringValue(d.MediaRaw),
+ MacAddr: types.StringValue(d.MacAddr),
+ IsPhysical: types.BoolValue(d.IsPhysical),
+ MTU: tools.StringToInt64Null(d.MTU),
+ Status: types.StringValue(d.Status),
+ Flags: tools.StringSliceToSet(d.Flags),
+ Capabilities: tools.StringSliceToSet(d.Capabilities),
+ Options: tools.StringSliceToSet(d.Options),
+ SupportedMedia: tools.StringSliceToSet(d.SupportedMedia),
+ Groups: tools.StringSliceToSet(d.Groups),
+ }
+
+ // Creating an empty slice results in `[]` rather than `null` if OPNsense API returned an empty list.
+ ipv4s := []Ipv4Model{}
+ for _, elem := range d.Ipv4 {
+ ipv4s = append(ipv4s, Ipv4Model{
+ Ipaddr: types.StringValue(elem.IpAddr),
+ SubnetBits: types.Int64Value(elem.SubnetBits),
+ Tunnel: types.BoolValue(elem.Tunnel),
+ })
+ }
+
+ ipv6s := []Ipv6Model{}
+ for _, elem := range d.Ipv6 {
+ ipv6s = append(ipv6s, Ipv6Model{
+ Ipaddr: types.StringValue(elem.IpAddr),
+ SubnetBits: types.Int64Value(elem.SubnetBits),
+ Tunnel: types.BoolValue(elem.Tunnel),
+ Autoconf: types.BoolValue(elem.Autoconf),
+ Deprecated: types.BoolValue(elem.Deprecated),
+ LinkLocal: types.BoolValue(elem.LinkLocal),
+ Tentative: types.BoolValue(elem.Tentative),
+ })
+ }
+
+ model.Ipv4, _ = types.ListValueFrom(
+ context.Background(),
+ types.ObjectType{}.WithAttributeTypes(ipv4AttrTypes),
+ ipv4s,
+ )
+
+ model.Ipv6, _ = types.ListValueFrom(
+ context.Background(),
+ types.ObjectType{}.WithAttributeTypes(ipv6AttrTypes),
+ ipv6s,
+ )
+
+ return model, nil
+}
diff --git a/templates/data-sources/interface.md.tmpl b/templates/data-sources/interface.md.tmpl
new file mode 100644
index 0000000..eb5e19f
--- /dev/null
+++ b/templates/data-sources/interface.md.tmpl
@@ -0,0 +1,20 @@
+---
+page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}"
+subcategory: Interfaces
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} ({{.Type}})
+
+{{ .Description | trimspace }}
+
+{{ .SchemaMarkdown | trimspace }}
+
+{{ if .HasImport -}}
+## Import
+
+Import is supported using the following syntax:
+
+{{ printf "{{codefile \"shell\" %q}}" .ImportFile }}
+{{- end }}
\ No newline at end of file