Skip to content

Commit

Permalink
Use filter framework for linode_networking_ips data source (#1708)
Browse files Browse the repository at this point in the history
* Translate IPs data source

* Update fixture

* Fix resource
  • Loading branch information
lgarber-akamai authored Jan 9, 2025
1 parent 35546d1 commit 0b2a918
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 151 deletions.
132 changes: 66 additions & 66 deletions linode/networkingips/famework_datasource_schema.go
Original file line number Diff line number Diff line change
@@ -1,82 +1,82 @@
package networkingips

import (
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/linode/terraform-provider-linode/v2/linode/helper/frameworkfilter"
)

var updatedIPObjectType = types.ObjectType{
AttrTypes: map[string]attr.Type{
"address": types.StringType,
"region": types.StringType,
"gateway": types.StringType,
"subnet_mask": types.StringType,
"prefix": types.Int64Type,
"type": types.StringType,
"public": types.BoolType,
"rdns": types.StringType,
"linode_id": types.Int64Type,
"reserved": types.BoolType,
},
var filterConfig = frameworkfilter.Config{
"type": {APIFilterable: true, TypeFunc: frameworkfilter.FilterTypeString},
"region": {APIFilterable: true, TypeFunc: frameworkfilter.FilterTypeString},
"rdns": {APIFilterable: true, TypeFunc: frameworkfilter.FilterTypeString},
"address": {APIFilterable: true, TypeFunc: frameworkfilter.FilterTypeString},
"prefix": {APIFilterable: true, TypeFunc: frameworkfilter.FilterTypeInt},

"gateway": {APIFilterable: false, TypeFunc: frameworkfilter.FilterTypeString},
"subnet_mask": {APIFilterable: false, TypeFunc: frameworkfilter.FilterTypeString},
"public": {APIFilterable: false, TypeFunc: frameworkfilter.FilterTypeString},
"linode_id": {APIFilterable: false, TypeFunc: frameworkfilter.FilterTypeInt},
"reserved": {APIFilterable: false, TypeFunc: frameworkfilter.FilterTypeBool},
}

var frameworkDatasourceSchema = schema.Schema{
Attributes: map[string]schema.Attribute{
"address": schema.StringAttribute{
Description: "The IP address.",
// Required: true,
Optional: true,
},
"gateway": schema.StringAttribute{
Description: "The default gateway for this address.",
Computed: true,
},
"subnet_mask": schema.StringAttribute{
Description: "The mask that separates host bits from network bits for this address.",
Computed: true,
},
"prefix": schema.Int64Attribute{
Description: "The number of bits set in the subnet mask.",
Computed: true,
},
"type": schema.StringAttribute{
Description: "The type of address this is (ipv4, ipv6, ipv6/pool, ipv6/range).",
Computed: true,
},
"public": schema.BoolAttribute{
Description: "Whether this is a public or private IP address.",
Computed: true,
},
"rdns": schema.StringAttribute{
Description: "The reverse DNS assigned to this address. For public IPv4 addresses, this will be set to " +
"a default value provided by Linode if not explicitly set.",
Computed: true,
},
"linode_id": schema.Int64Attribute{
Description: "The ID of the Linode this address currently belongs to.",
Computed: true,
},
"region": schema.StringAttribute{
Description: "The Region this IP address resides in.",
Computed: true,
},
"id": schema.StringAttribute{
Description: "A unique identifier for this datasource.",
Description: "The data source's unique ID.",
Computed: true,
},
"reserved": schema.BoolAttribute{
Computed: true,
Description: "Whether this IP is reserved or not.",
},
"ip_addresses": schema.ListAttribute{
Description: "A list of all IPs.",
Computed: true,
ElementType: updatedIPObjectType,
},
"filter_reserved": schema.BoolAttribute{
Description: "Filter IPs by reserved status.",
Optional: true,
"order": filterConfig.OrderSchema(),
"order_by": filterConfig.OrderBySchema(),
},
Blocks: map[string]schema.Block{
"filter": filterConfig.Schema(),
"ip_addresses": schema.ListNestedBlock{
Description: "The returned list of Images.",
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"address": schema.StringAttribute{
Description: "The IP address.",
Computed: true,
},
"gateway": schema.StringAttribute{
Description: "The default gateway for this address.",
Computed: true,
},
"subnet_mask": schema.StringAttribute{
Description: "The mask that separates host bits from network bits for this address.",
Computed: true,
},
"prefix": schema.Int64Attribute{
Description: "The number of bits set in the subnet mask.",
Computed: true,
},
"type": schema.StringAttribute{
Description: "The type of address this is (ipv4, ipv6, ipv6/pool, ipv6/range).",
Computed: true,
},
"public": schema.BoolAttribute{
Description: "Whether this is a public or private IP address.",
Computed: true,
},
"rdns": schema.StringAttribute{
Description: "The reverse DNS assigned to this address. For public IPv4 addresses, this will be set to " +
"a default value provided by Linode if not explicitly set.",
Computed: true,
},
"linode_id": schema.Int64Attribute{
Description: "The ID of the Linode this address currently belongs to.",
Computed: true,
},
"region": schema.StringAttribute{
Description: "The Region this IP address resides in.",
Computed: true,
},
"reserved": schema.BoolAttribute{
Computed: true,
Description: "Whether this IP is reserved or not.",
},
},
},
},
},
}
120 changes: 36 additions & 84 deletions linode/networkingips/framework_datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ package networkingips

import (
"context"
"encoding/json"
"fmt"

"github.com/hashicorp/terraform-plugin-log/tflog"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/linode/linodego"
"github.com/linode/terraform-provider-linode/v2/linode/helper"
)

type DataSource struct {
helper.BaseDataSource
}

func NewDataSource() datasource.DataSource {
return &DataSource{
BaseDataSource: helper.NewBaseDataSource(
Expand All @@ -26,104 +25,57 @@ func NewDataSource() datasource.DataSource {
}
}

type DataSource struct {
helper.BaseDataSource
}

type DataSourceModel struct {
Address types.String `tfsdk:"address"`
Gateway types.String `tfsdk:"gateway"`
SubnetMask types.String `tfsdk:"subnet_mask"`
Prefix types.Int64 `tfsdk:"prefix"`
Type types.String `tfsdk:"type"`
Public types.Bool `tfsdk:"public"`
RDNS types.String `tfsdk:"rdns"`
LinodeID types.Int64 `tfsdk:"linode_id"`
Region types.String `tfsdk:"region"`
ID types.String `tfsdk:"id"`
Reserved types.Bool `tfsdk:"reserved"`
IPAddresses types.List `tfsdk:"ip_addresses"`
FilterReserved types.Bool `tfsdk:"filter_reserved"`
}

func (d *DataSource) Read(
ctx context.Context,
req datasource.ReadRequest,
resp *datasource.ReadResponse,
) {
tflog.Debug(ctx, "Read data.linode_networking_ip")
tflog.Debug(ctx, "Read data.linode_networking_ips")

var data DataSourceModel
var data FilterModel

resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

if data.Address.IsNull() {

// List all IP addresses with filter on reservation status

filter, err := buildFilter(data)
if err != nil {
resp.Diagnostics.AddError("Unable to build filter", err.Error())
return
}

tflog.Debug(ctx, "Generated filter", map[string]interface{}{
"filter": filter,
})

opts := &linodego.ListOptions{Filter: filter}
ips, err := d.Meta.Client.ListIPAddresses(ctx, opts)
if err != nil {
resp.Diagnostics.AddError("Unable to list IP Addresses", err.Error())
return
}

ipList := make([]attr.Value, len(ips))
for i, ip := range ips {
ipObj := map[string]attr.Value{
"address": types.StringValue(ip.Address),
"region": types.StringValue(ip.Region),
"gateway": types.StringValue(ip.Gateway),
"subnet_mask": types.StringValue(ip.SubnetMask),
"prefix": types.Int64Value(int64(ip.Prefix)),
"type": types.StringValue(string(ip.Type)),
"public": types.BoolValue(ip.Public),
"rdns": types.StringValue(ip.RDNS),
"linode_id": types.Int64Value(int64(ip.LinodeID)),
"reserved": types.BoolValue(ip.Reserved),
}
ipList[i] = types.ObjectValueMust(updatedIPObjectType.AttrTypes, ipObj)
}

var diags diag.Diagnostics
data.IPAddresses, diags = types.ListValue(updatedIPObjectType, ipList)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
id, diag := filterConfig.GenerateID(data.Filters)
if diag != nil {
resp.Diagnostics.Append(diag)
return
}
data.ID = id

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func buildFilter(data DataSourceModel) (string, error) {
filters := make(map[string]string)

if !data.FilterReserved.IsNull() {
filters["reserved"] = fmt.Sprintf("%t", data.FilterReserved.ValueBool())
result, diag := filterConfig.GetAndFilter(
ctx, d.Meta.Client, data.Filters, listFunc,
data.Order, data.OrderBy)
if diag != nil {
resp.Diagnostics.Append(diag)
return
}

if len(filters) == 0 {
return "", nil
resp.Diagnostics.Append(data.parseIPAddresses(helper.AnySliceToTyped[linodego.InstanceIP](result))...)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

jsonFilter, err := json.Marshal(filters)
func listFunc(
ctx context.Context,
client *linodego.Client,
filter string,
) ([]any, error) {
tflog.Trace(ctx, "client.ListIPAddresses(...)", map[string]any{
"filter": filter,
})

images, err := client.ListIPAddresses(ctx, &linodego.ListOptions{
Filter: filter,
})
if err != nil {
return "", fmt.Errorf("error creating filter: %v", err)
return nil, err
}

return string(jsonFilter), nil
return helper.TypedSliceToAny(images), nil
}
60 changes: 60 additions & 0 deletions linode/networkingips/framework_datasource_models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package networkingips

import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/linode/linodego"
"github.com/linode/terraform-provider-linode/v2/linode/helper/frameworkfilter"
)

type IPAddressModel struct {
Address types.String `tfsdk:"address"`
Type types.String `tfsdk:"type"`
Region types.String `tfsdk:"region"`
RDNS types.String `tfsdk:"rdns"`
Prefix types.Int64 `tfsdk:"prefix"`
Gateway types.String `tfsdk:"gateway"`
SubnetMask types.String `tfsdk:"subnet_mask"`
Public types.Bool `tfsdk:"public"`
LinodeID types.Int64 `tfsdk:"linode_id"`
Reserved types.Bool `tfsdk:"reserved"`
}

func (m *IPAddressModel) ParseIP(ip linodego.InstanceIP) {
m.Address = types.StringValue(ip.Address)
m.Type = types.StringValue(string(ip.Type))
m.Region = types.StringValue(ip.Region)
m.RDNS = types.StringValue(ip.RDNS)
m.Prefix = types.Int64Value(int64(ip.Prefix))
m.Gateway = types.StringValue(ip.Gateway)
m.SubnetMask = types.StringValue(ip.SubnetMask)
m.Public = types.BoolValue(ip.Public)
m.LinodeID = types.Int64Value(int64(ip.LinodeID))
m.Reserved = types.BoolValue(ip.Reserved)
}

// FilterModel describes the Terraform resource data model to match the
// resource schema.
type FilterModel struct {
ID types.String `tfsdk:"id"`
Filters frameworkfilter.FiltersModelType `tfsdk:"filter"`
Order types.String `tfsdk:"order"`
OrderBy types.String `tfsdk:"order_by"`
IPAddresses []IPAddressModel `tfsdk:"ip_addresses"`
}

func (data *FilterModel) parseIPAddresses(
ips []linodego.InstanceIP,
) diag.Diagnostics {
result := make([]IPAddressModel, len(ips))

for i := range ips {
var data IPAddressModel
data.ParseIP(ips[i])
result[i] = data
}

data.IPAddresses = result

return nil
}
14 changes: 13 additions & 1 deletion linode/networkingips/tmpl/data_filter.gotf
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
{{ define "networking_ip_data_filtered" }}

resource "linode_networking_ip" "test" {
type = "ipv4"
region = "us-mia"
reserved = true
public = true
}

data "linode_networking_ips" "filtered" {
filter_reserved = true
depends_on = [linode_networking_ip.test]

filter {
name = "reserved"
values = ["true"]
}
}

{{ end }}
Loading

0 comments on commit 0b2a918

Please sign in to comment.