-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Terraform Support for Managing Reserved IP Addresses #1598
base: dev
Are you sure you want to change the base?
Changes from 5 commits
c0451aa
3d09656
c9196b8
950ab3b
b6638d8
2b18460
be57ac7
38195df
24493f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
//go:build integration || networkreservedips | ||
|
||
package networkreservedips_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/linode/terraform-provider-linode/v2/linode/acceptance" | ||
"github.com/linode/terraform-provider-linode/v2/linode/networkreservedips/tmpl" | ||
) | ||
|
||
func TestAccDataSource_reservedIP(t *testing.T) { | ||
t.Parallel() | ||
|
||
resourceName := "data.linode_reserved_ip.test" | ||
region, _ := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { acceptance.PreCheck(t) }, | ||
ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: tmpl.DataBasic(t, region), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrSet(resourceName, "address"), | ||
resource.TestCheckResourceAttrSet(resourceName, "region"), | ||
resource.TestCheckResourceAttrSet(resourceName, "gateway"), | ||
resource.TestCheckResourceAttrSet(resourceName, "subnet_mask"), | ||
resource.TestCheckResourceAttrSet(resourceName, "prefix"), | ||
resource.TestCheckResourceAttrSet(resourceName, "type"), | ||
resource.TestCheckResourceAttrSet(resourceName, "public"), | ||
resource.TestCheckResourceAttrSet(resourceName, "rdns"), | ||
resource.TestCheckResourceAttrSet(resourceName, "linode_id"), | ||
resource.TestCheckResourceAttrSet(resourceName, "reserved"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func TestAccDataSource_reservedIPList(t *testing.T) { | ||
t.Parallel() | ||
|
||
resourceName := "data.linode_reserved_ip.test" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { acceptance.PreCheck(t) }, | ||
ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: tmpl.DataList(t), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrSet(resourceName, "reserved_ips.#"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package networkreservedips | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/datasource" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-log/tflog" | ||
"github.com/linode/linodego" | ||
"github.com/linode/terraform-provider-linode/v2/linode/helper" | ||
) | ||
|
||
func NewDataSource() datasource.DataSource { | ||
return &DataSource{ | ||
BaseDataSource: helper.NewBaseDataSource( | ||
helper.BaseDataSourceConfig{ | ||
Name: "linode_reserved_ip", | ||
Schema: &frameworkDataSourceSchema, | ||
}, | ||
), | ||
} | ||
} | ||
|
||
type DataSource struct { | ||
helper.BaseDataSource | ||
} | ||
|
||
type DataSourceModel struct { | ||
ID types.String `tfsdk:"id"` | ||
Address types.String `tfsdk:"address"` | ||
Region types.String `tfsdk:"region"` | ||
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"` | ||
Reserved types.Bool `tfsdk:"reserved"` | ||
ReservedIPs types.List `tfsdk:"reserved_ips"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may need to build another data source for listing reserved IPs, instead of having it here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I will be having a new datasource for this |
||
} | ||
|
||
func (data *DataSourceModel) parseIP(ip *linodego.InstanceIP) { | ||
data.ID = types.StringValue(ip.Address) | ||
data.Address = types.StringValue(ip.Address) | ||
data.Region = types.StringValue(ip.Region) | ||
data.Gateway = types.StringValue(ip.Gateway) | ||
data.SubnetMask = types.StringValue(ip.SubnetMask) | ||
data.Prefix = types.Int64Value(int64(ip.Prefix)) | ||
data.Type = types.StringValue(string(ip.Type)) | ||
data.Public = types.BoolValue(ip.Public) | ||
data.RDNS = types.StringValue(ip.RDNS) | ||
data.LinodeID = types.Int64Value(int64(ip.LinodeID)) | ||
data.Reserved = types.BoolValue(ip.Reserved) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you move this data source model and parsing function to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I will be doing it the next commit |
||
|
||
func (d *DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { | ||
tflog.Debug(ctx, "Read data.linode_reserved_ip") | ||
|
||
var data DataSourceModel | ||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
if !data.Address.IsNull() { | ||
// Fetch a specific reserved IP | ||
ip, err := d.Meta.Client.GetReservedIPAddress(ctx, data.Address.ValueString()) | ||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"Unable to get Reserved IP Address", | ||
err.Error(), | ||
) | ||
return | ||
} | ||
data.parseIP(ip) | ||
} else { | ||
// List all reserved IPs | ||
filter := "" | ||
if !data.Region.IsNull() { | ||
filter = fmt.Sprintf("{\"region\":\"%s\"}", data.Region.ValueString()) | ||
} | ||
ips, err := d.Meta.Client.ListReservedIPAddresses(ctx, &linodego.ListOptions{Filter: filter}) | ||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"Unable to list Reserved IP Addresses", | ||
err.Error(), | ||
) | ||
return | ||
} | ||
|
||
reservedIPs := make([]ReservedIPObject, len(ips)) | ||
for i, ip := range ips { | ||
reservedIPs[i] = ReservedIPObject{ | ||
ID: types.StringValue(ip.Address), | ||
Address: types.StringValue(ip.Address), | ||
Region: types.StringValue(ip.Region), | ||
Gateway: types.StringValue(ip.Gateway), | ||
SubnetMask: 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), | ||
LinodeID: types.Int64Value(int64(ip.LinodeID)), | ||
Reserved: types.BoolValue(ip.Reserved), | ||
} | ||
} | ||
|
||
reservedIPsValue, diags := types.ListValueFrom(ctx, reservedIPObjectType, reservedIPs) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
data.ReservedIPs = reservedIPsValue | ||
|
||
// If there are IPs, populate the first one's details for backwards compatibility | ||
if len(ips) > 0 { | ||
data.parseIP(&ips[0]) | ||
} | ||
} | ||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package networkreservedips | ||
|
||
import ( | ||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
) | ||
|
||
type ReservedIPObject struct { | ||
ID types.String `tfsdk:"id"` | ||
Address types.String `tfsdk:"address"` | ||
Region types.String `tfsdk:"region"` | ||
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"` | ||
Reserved types.Bool `tfsdk:"reserved"` | ||
} | ||
|
||
var reservedIPObjectType = types.ObjectType{ | ||
AttrTypes: map[string]attr.Type{ | ||
"id": types.StringType, | ||
"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 frameworkDataSourceSchema = schema.Schema{ | ||
Attributes: map[string]schema.Attribute{ | ||
"region": schema.StringAttribute{ | ||
Description: "The Region in which to reserve the IP address.", | ||
Optional: true, | ||
}, | ||
"address": schema.StringAttribute{ | ||
Description: "The reserved IP address.", | ||
Optional: true, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if I understand the endpoint correctly, but it looks like address is a required attribute to get this data source? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have made it optional since I am implementing both fetch and list endpoints in the same datasource. But now it seems like it is indeed a better idea to separate the functionalities into individual datasources. I will be doing that in the next commit. |
||
"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.", | ||
Computed: true, | ||
}, | ||
"linode_id": schema.Int64Attribute{ | ||
Description: "The ID of the Linode this address currently belongs to.", | ||
Computed: true, | ||
}, | ||
"reserved": schema.BoolAttribute{ | ||
Description: "Whether this IP is reserved or not.", | ||
Computed: true, | ||
}, | ||
"id": schema.StringAttribute{ | ||
Description: "The unique ID of the reserved IP address.", | ||
Computed: true, | ||
}, | ||
"reserved_ips": schema.ListAttribute{ | ||
Description: "A list of all reserved IPs.", | ||
Computed: true, | ||
ElementType: reservedIPObjectType, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like I mentioned above, we can remove this list to its own data source. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah will be separating it into a new datasource |
||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package networkreservedips | ||
|
||
import ( | ||
"context" | ||
|
||
"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 ReservedIPModel struct { | ||
ID types.String `tfsdk:"id"` | ||
Region types.String `tfsdk:"region"` | ||
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"` | ||
Reserved types.Bool `tfsdk:"reserved"` | ||
} | ||
|
||
func (m *ReservedIPModel) FlattenReservedIP( | ||
ctx context.Context, | ||
ip linodego.InstanceIP, | ||
preserveKnown bool, | ||
) diag.Diagnostics { | ||
var diags diag.Diagnostics | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like no diags is raised from this function. You can just remove it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will look into it. I will remove it if it is not being triggered. |
||
|
||
m.ID = helper.KeepOrUpdateString(m.ID, ip.Address, preserveKnown) | ||
m.Region = helper.KeepOrUpdateString(m.Region, ip.Region, preserveKnown) | ||
m.Address = helper.KeepOrUpdateString(m.Address, ip.Address, preserveKnown) | ||
m.Gateway = helper.KeepOrUpdateString(m.Gateway, ip.Gateway, preserveKnown) | ||
m.SubnetMask = helper.KeepOrUpdateString(m.SubnetMask, ip.SubnetMask, preserveKnown) | ||
m.Prefix = helper.KeepOrUpdateInt64(m.Prefix, int64(ip.Prefix), preserveKnown) | ||
m.Type = helper.KeepOrUpdateString(m.Type, string(ip.Type), preserveKnown) | ||
m.Public = helper.KeepOrUpdateBool(m.Public, ip.Public, preserveKnown) | ||
m.RDNS = helper.KeepOrUpdateString(m.RDNS, ip.RDNS, preserveKnown) | ||
m.LinodeID = helper.KeepOrUpdateInt64(m.LinodeID, int64(ip.LinodeID), preserveKnown) | ||
m.Reserved = helper.KeepOrUpdateBool(m.Reserved, ip.Reserved, preserveKnown) | ||
|
||
return diags | ||
} | ||
|
||
func (m *ReservedIPModel) CopyFrom( | ||
ctx context.Context, | ||
other ReservedIPModel, | ||
preserveKnown bool, | ||
) diag.Diagnostics { | ||
var diags diag.Diagnostics | ||
|
||
m.ID = helper.KeepOrUpdateValue(m.ID, other.ID, preserveKnown) | ||
m.Region = helper.KeepOrUpdateValue(m.Region, other.Region, preserveKnown) | ||
m.Address = helper.KeepOrUpdateValue(m.Address, other.Address, preserveKnown) | ||
m.Gateway = helper.KeepOrUpdateValue(m.Gateway, other.Gateway, preserveKnown) | ||
m.SubnetMask = helper.KeepOrUpdateValue(m.SubnetMask, other.SubnetMask, preserveKnown) | ||
m.Prefix = helper.KeepOrUpdateValue(m.Prefix, other.Prefix, preserveKnown) | ||
m.Type = helper.KeepOrUpdateValue(m.Type, other.Type, preserveKnown) | ||
m.Public = helper.KeepOrUpdateValue(m.Public, other.Public, preserveKnown) | ||
m.RDNS = helper.KeepOrUpdateValue(m.RDNS, other.RDNS, preserveKnown) | ||
m.LinodeID = helper.KeepOrUpdateValue(m.LinodeID, other.LinodeID, preserveKnown) | ||
m.Reserved = helper.KeepOrUpdateValue(m.Reserved, other.Reserved, preserveKnown) | ||
|
||
return diags | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can remove the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait for next linodego release