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