From 9bd4ae9ff6f3e6db9ddebd46e54eedb6013ebacf Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 23 Sep 2024 10:11:12 -0400 Subject: [PATCH 01/49] Added support for creating a linode with a reserved IP + adding additional reserved IP to existing linode --- linode/instance/resource.go | 8 ++ linode/instance/resource_test.go | 34 ++++++++ linode/instance/schema_resource.go | 1 + linode/instance/tmpl/template.go | 18 +++++ .../templates/instance_with_reserved_ip.gotf | 15 ++++ linode/instanceip/framework_resource.go | 80 ++++++++++++++++--- linode/instanceip/framework_schema.go | 1 + linode/instanceip/resource_test.go | 68 ++++++++++++++++ .../tmpl/AddReservedIPToInstance.gotf | 17 ++++ linode/instanceip/tmpl/template.go | 10 +++ 10 files changed, 240 insertions(+), 12 deletions(-) create mode 100644 linode/instance/tmpl/templates/instance_with_reserved_ip.gotf create mode 100644 linode/instanceip/tmpl/AddReservedIPToInstance.gotf diff --git a/linode/instance/resource.go b/linode/instance/resource.go index 76c6c240f..3c68cb3d9 100644 --- a/linode/instance/resource.go +++ b/linode/instance/resource.go @@ -188,6 +188,14 @@ func createResource(ctx context.Context, d *schema.ResourceData, meta interface{ ), } + // Add this new section to handle IPv4 addresses + if ipv4Raw, ok := d.GetOk("ipv4"); ok { + ipv4Set := ipv4Raw.(*schema.Set) + for _, ip := range ipv4Set.List() { + createOpts.Ipv4 = append(createOpts.Ipv4, ip.(string)) + } + } + if tagsRaw, tagsOk := d.GetOk("tags"); tagsOk { for _, tag := range tagsRaw.(*schema.Set).List() { createOpts.Tags = append(createOpts.Tags, tag.(string)) diff --git a/linode/instance/resource_test.go b/linode/instance/resource_test.go index 61083a8f0..3ac894b6b 100644 --- a/linode/instance/resource_test.go +++ b/linode/instance/resource_test.go @@ -2883,3 +2883,37 @@ func checkComputeInstanceDisk(instance *linodego.Instance, label string, size in return fmt.Errorf("Disk not found: %s", label) } } + +func TestAccResourceInstance_withReservedIP(t *testing.T) { + t.Parallel() + + var instance linodego.Instance + resourceName := "linode_instance.foobar" + testRegion = "us-east" + instanceName := acctest.RandomWithPrefix("tf_test") + rootPass := acctest.RandString(16) + reservedIP := "50.116.51.242" // Use a test IP or fetch a real reserved IP + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.WithReservedIP(t, instanceName, acceptance.PublicKeyMaterial, testRegion, rootPass, reservedIP), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "label", instanceName), + resource.TestCheckResourceAttr(resourceName, "ipv4.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ipv4.0", reservedIP), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"root_pass", "authorized_keys", "image", "migration_type", "resize_disk"}, + }, + }, + }) +} diff --git a/linode/instance/schema_resource.go b/linode/instance/schema_resource.go index cb65f77ae..37a1c0356 100644 --- a/linode/instance/schema_resource.go +++ b/linode/instance/schema_resource.go @@ -327,6 +327,7 @@ var resourceSchema = map[string]*schema.Schema{ Description: "This Linode's IPv4 Addresses. Each Linode is assigned a single public IPv4 address upon " + "creation, and may get a single private IPv4 address if needed. You may need to open a support ticket " + "to get additional IPv4 addresses.", + Optional: true, Computed: true, }, diff --git a/linode/instance/tmpl/template.go b/linode/instance/tmpl/template.go index 2dd51a57d..1b4f7b62e 100644 --- a/linode/instance/tmpl/template.go +++ b/linode/instance/tmpl/template.go @@ -28,6 +28,7 @@ type TemplateData struct { AssignedGroup string DiskEncryption *linodego.InstanceDiskEncryption + IPv4 []string } func Basic(t *testing.T, label, pubKey, region string, rootPass string) string { @@ -732,3 +733,20 @@ func WithPG(t *testing.T, label, region, assignedGroup string, groups []string) AssignedGroup: assignedGroup, }) } + +func WithReservedIP(t *testing.T, label, pubKey, region, rootPass string, reservedIP string) string { + generatedConfig := acceptance.ExecuteTemplate(t, + "instance_with_reserved_ip", TemplateData{ + Label: label, + PubKey: pubKey, + Image: acceptance.TestImageLatest, + Region: region, + RootPass: rootPass, + IPv4: []string{reservedIP}, + }) + + // Add this debug logging + t.Logf("Generated Terraform config for WithReservedIP:\n%s", generatedConfig) + + return generatedConfig +} diff --git a/linode/instance/tmpl/templates/instance_with_reserved_ip.gotf b/linode/instance/tmpl/templates/instance_with_reserved_ip.gotf new file mode 100644 index 000000000..efdd435cd --- /dev/null +++ b/linode/instance/tmpl/templates/instance_with_reserved_ip.gotf @@ -0,0 +1,15 @@ +{{ define "instance_with_reserved_ip" }} + +resource "linode_instance" "foobar" { + label = "{{ .Label }}" + type = "g6-nanode-1" + region = "{{ .Region }}" + image = "{{ .Image }}" + + root_pass = "{{ .RootPass }}" + authorized_keys = ["{{ .PubKey }}"] + + ipv4 = [{{ range $index, $ip := .IPv4 }}{{ if $index }}, {{ end }}"{{ $ip }}"{{ end }}] +} + +{{ end }} \ No newline at end of file diff --git a/linode/instanceip/framework_resource.go b/linode/instanceip/framework_resource.go index 2ad1fed5a..85fcd7ff1 100644 --- a/linode/instanceip/framework_resource.go +++ b/linode/instanceip/framework_resource.go @@ -54,13 +54,59 @@ func (r *Resource) Create( isPublic := plan.Public.ValueBool() client := r.Meta.Client - ip, err := client.AddInstanceIPAddress(ctx, linodeID, isPublic) - if err != nil { - resp.Diagnostics.AddError( - fmt.Sprintf("Failed to created instance (%d) IP", linodeID), - err.Error(), - ) - return + var ip *linodego.InstanceIP + var err error + + if !plan.Address.IsNull() && !plan.Address.IsUnknown() { + // Assign a reserved IP + createOpts := linodego.InstanceReserveIPOptions{ + Type: "ipv4", + Public: isPublic, + Address: plan.Address.ValueString(), + } + _, err = client.AddReservedIPToInstance(ctx, linodeID, createOpts) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to assign reserved IP to instance (%d)", linodeID), + err.Error(), + ) + return + } + + // Fetch the IP information after assigning + instanceIPs, err := client.GetInstanceIPAddresses(ctx, linodeID) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to fetch IP addresses for instance (%d)", linodeID), + err.Error(), + ) + return + } + + // Find the assigned IP in the instance's IP addresses + for _, instanceIP := range instanceIPs.IPv4.Public { + if instanceIP.Address == plan.Address.ValueString() { + ip = instanceIP + break + } + } + if ip == nil { + resp.Diagnostics.AddError( + "Failed to find assigned IP", + fmt.Sprintf("Could not find the assigned IP %s in instance (%d) IP addresses", plan.Address.ValueString(), linodeID), + ) + return + } + } else { + // Allocate a new IP + ip, err = client.AddInstanceIPAddress(ctx, linodeID, isPublic) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to allocate new IP for instance (%d)", linodeID), + err.Error(), + ) + return + } } if !plan.RDNS.IsNull() && !plan.RDNS.IsUnknown() { @@ -86,11 +132,6 @@ func (r *Resource) Create( } } - if ip == nil { - resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the instance ip") - return - } - resp.Diagnostics.Append(plan.FlattenInstanceIP(ctx, *ip, true)...) if resp.Diagnostics.HasError() { return @@ -295,3 +336,18 @@ func populateLogAttributes(ctx context.Context, data *InstanceIPModel) context.C "address": data.ID.ValueString(), }) } + +func addReservedIPToInstance(ctx context.Context, client linodego.Client, instanceID int, ip string) error { + opts := linodego.InstanceReserveIPOptions{ + Type: "ipv4", + Public: true, + Address: ip, + } + + _, err := client.AddReservedIPToInstance(ctx, instanceID, opts) + if err != nil { + return fmt.Errorf("failed to add IP %s to Linode instance %d: %s", ip, instanceID, err) + } + + return nil +} diff --git a/linode/instanceip/framework_schema.go b/linode/instanceip/framework_schema.go index 93a0ce8b5..f29d624ea 100644 --- a/linode/instanceip/framework_schema.go +++ b/linode/instanceip/framework_schema.go @@ -41,6 +41,7 @@ var frameworkResourceSchema = schema.Schema{ "address": schema.StringAttribute{ Description: "The resulting IPv4 address.", + Optional: true, Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), diff --git a/linode/instanceip/resource_test.go b/linode/instanceip/resource_test.go index 5019e306a..1140a2582 100644 --- a/linode/instanceip/resource_test.go +++ b/linode/instanceip/resource_test.go @@ -127,3 +127,71 @@ func TestAccInstanceIP_noApply(t *testing.T) { }, }) } + +// func TestAccInstanceIP_addReservedIP(t *testing.T) { +// t.Parallel() + +// var instance linodego.Instance +// name := acctest.RandomWithPrefix("tf_test") +// reservedIP := "45.33.74.65" // Replace with an actual reserved IP address + +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { acceptance.PreCheck(t) }, +// ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, +// CheckDestroy: acceptance.CheckInstanceDestroy, +// Steps: []resource.TestStep{ +// { +// Config: tmpl.Basic(t, name, testRegion, false), +// Check: resource.ComposeTestCheckFunc( +// acceptance.CheckInstanceExists("linode_instance.foobar", &instance), +// ), +// }, +// { +// Config: tmpl.AddReservedIP(t, name, testRegion, reservedIP), +// Check: resource.ComposeTestCheckFunc( +// acceptance.CheckInstanceExists("linode_instance.foobar", &instance), +// resource.TestCheckResourceAttr(testInstanceIPResName, "address", reservedIP), +// resource.TestCheckResourceAttr(testInstanceIPResName, "public", "true"), +// resource.TestCheckResourceAttrSet(testInstanceIPResName, "linode_id"), +// resource.TestCheckResourceAttrSet(testInstanceIPResName, "gateway"), +// resource.TestCheckResourceAttrSet(testInstanceIPResName, "subnet_mask"), +// resource.TestCheckResourceAttrSet(testInstanceIPResName, "prefix"), +// resource.TestCheckResourceAttrSet(testInstanceIPResName, "rdns"), +// resource.TestCheckResourceAttr(testInstanceIPResName, "region", testRegion), +// resource.TestCheckResourceAttr(testInstanceIPResName, "type", "ipv4"), +// ), +// }, +// }, +// }) +// } + +func TestAccInstanceIP_addReservedIP(t *testing.T) { + t.Parallel() + + var instance linodego.Instance + name := acctest.RandomWithPrefix("tf_test") + reservedIP := "50.116.51.242" // Replace with your actual reserved IP address + testRegion = "us-east" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.AddReservedIP(t, name, testRegion, reservedIP), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists("linode_instance.foobar", &instance), + resource.TestCheckResourceAttr(testInstanceIPResName, "address", reservedIP), + resource.TestCheckResourceAttr(testInstanceIPResName, "public", "true"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "linode_id"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "gateway"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "subnet_mask"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "prefix"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "rdns"), + resource.TestCheckResourceAttr(testInstanceIPResName, "region", testRegion), + resource.TestCheckResourceAttr(testInstanceIPResName, "type", "ipv4"), + ), + }, + }, + }) +} diff --git a/linode/instanceip/tmpl/AddReservedIPToInstance.gotf b/linode/instanceip/tmpl/AddReservedIPToInstance.gotf new file mode 100644 index 000000000..617afb23d --- /dev/null +++ b/linode/instanceip/tmpl/AddReservedIPToInstance.gotf @@ -0,0 +1,17 @@ +{{ define "instance_ip_add_reservedIP" }} + +resource "linode_instance" "foobar" { + label = "{{.Label}}" + group = "tf_test" + type = "g6-nanode-1" + region = "{{ .Region }}" + image = "linode/debian12" +} + +resource "linode_instance_ip" "test" { + linode_id = linode_instance.foobar.id + public = true + address = "{{ .Address}}" +} + +{{ end }} \ No newline at end of file diff --git a/linode/instanceip/tmpl/template.go b/linode/instanceip/tmpl/template.go index c3ecfe8af..b1f2d00cb 100644 --- a/linode/instanceip/tmpl/template.go +++ b/linode/instanceip/tmpl/template.go @@ -10,6 +10,7 @@ type TemplateData struct { Label string ApplyImmediately bool Region string + Address string } func Basic(t *testing.T, instanceLabel, region string, applyImmediately bool) string { @@ -29,3 +30,12 @@ func NoBoot(t *testing.T, instanceLabel, region string, applyImmediately bool) s Region: region, }) } + +func AddReservedIP(t *testing.T, instanceLabel, region string, address string) string { + return acceptance.ExecuteTemplate(t, + "instance_ip_add_reservedIP", TemplateData{ + Label: instanceLabel, + Region: region, + Address: address, + }) +} From c0451aa3615a1c73d2ec9a02fcb9ae6c2c6d77a5 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 26 Sep 2024 10:13:43 -0400 Subject: [PATCH 02/49] Registered the resource and data source within the provider --- linode/framework_provider.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 22cab83ff..0b123c5ae 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -3,6 +3,7 @@ package linode import ( "context" + "github.com/linode/terraform-provider-linode/v2/linode/networkreservedips" "github.com/linode/terraform-provider-linode/v2/linode/vpcips" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -222,6 +223,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res firewall.NewResource, placementgroup.NewResource, placementgroupassignment.NewResource, + networkreservedips.NewResource, } } @@ -286,5 +288,6 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource placementgroups.NewDataSource, childaccount.NewDataSource, childaccounts.NewDataSource, + networkreservedips.NewDataSource, } } From 3d09656a38720e1ac2a24860d5138165dbd5ff39 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 26 Sep 2024 10:16:35 -0400 Subject: [PATCH 03/49] Implemented framework_resource, framework_datasource along with their respective tests --- linode/networkreservedips/datasource_test.go | 57 ++++++ .../framework_datasource.go | 124 ++++++++++++ .../framework_datasource_schema.go | 91 +++++++++ linode/networkreservedips/framework_models.go | 69 +++++++ .../networkreservedips/framework_resource.go | 181 ++++++++++++++++++ linode/networkreservedips/framework_schema.go | 60 ++++++ linode/networkreservedips/resource_test.go | 59 ++++++ .../networkreservedips/tmpl/reserved_ip.gotf | 17 ++ .../tmpl/reserved_ip_basic.gotf | 7 + linode/networkreservedips/tmpl/template.go | 31 +++ 10 files changed, 696 insertions(+) create mode 100644 linode/networkreservedips/datasource_test.go create mode 100644 linode/networkreservedips/framework_datasource.go create mode 100644 linode/networkreservedips/framework_datasource_schema.go create mode 100644 linode/networkreservedips/framework_models.go create mode 100644 linode/networkreservedips/framework_resource.go create mode 100644 linode/networkreservedips/framework_schema.go create mode 100644 linode/networkreservedips/resource_test.go create mode 100644 linode/networkreservedips/tmpl/reserved_ip.gotf create mode 100644 linode/networkreservedips/tmpl/reserved_ip_basic.gotf create mode 100644 linode/networkreservedips/tmpl/template.go diff --git a/linode/networkreservedips/datasource_test.go b/linode/networkreservedips/datasource_test.go new file mode 100644 index 000000000..0826e3663 --- /dev/null +++ b/linode/networkreservedips/datasource_test.go @@ -0,0 +1,57 @@ +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.#"), + ), + }, + }, + }) +} diff --git a/linode/networkreservedips/framework_datasource.go b/linode/networkreservedips/framework_datasource.go new file mode 100644 index 000000000..1c862841a --- /dev/null +++ b/linode/networkreservedips/framework_datasource.go @@ -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"` +} + +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) +} + +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)...) +} diff --git a/linode/networkreservedips/framework_datasource_schema.go b/linode/networkreservedips/framework_datasource_schema.go new file mode 100644 index 000000000..20f5099e8 --- /dev/null +++ b/linode/networkreservedips/framework_datasource_schema.go @@ -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, + }, + "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, + }, + }, +} diff --git a/linode/networkreservedips/framework_models.go b/linode/networkreservedips/framework_models.go new file mode 100644 index 000000000..4d6be3808 --- /dev/null +++ b/linode/networkreservedips/framework_models.go @@ -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 + + 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 +} diff --git a/linode/networkreservedips/framework_resource.go b/linode/networkreservedips/framework_resource.go new file mode 100644 index 000000000..88407eaee --- /dev/null +++ b/linode/networkreservedips/framework_resource.go @@ -0,0 +1,181 @@ +package networkreservedips + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "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 NewResource() resource.Resource { + return &Resource{ + BaseResource: helper.NewBaseResource( + helper.BaseResourceConfig{ + Name: "linode_reserved_ip", + IDType: types.StringType, + Schema: &frameworkResourceSchema, + }, + ), + } +} + +type Resource struct { + helper.BaseResource +} + +func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "Starting Create for linode_reserved_ip") + var plan ReservedIPModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + tflog.Error(ctx, "Error getting plan", map[string]interface{}{ + "error": resp.Diagnostics.Errors(), + }) + return + } + + ctx = populateLogAttributes(ctx, &plan) + + client := r.Meta.Client + reserveIP, err := client.ReserveIPAddress(ctx, linodego.ReserveIPOptions{ + Region: plan.Region.ValueString(), + }) + if err != nil { + tflog.Error(ctx, "Failed to reserve IP address", map[string]interface{}{ + "error": err.Error(), + }) + resp.Diagnostics.AddError( + "Failed to reserve IP address", + err.Error(), + ) + return + } + + if reserveIP == nil { + tflog.Error(ctx, "Received nil pointer for reserved IP") + resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the reserved ip") + return + } + + tflog.Debug(ctx, "Successfully reserved IP address", map[string]interface{}{ + "address": reserveIP.Address, + "region": reserveIP.Region, + }) + + plan.ID = types.StringValue(reserveIP.Address) + tflog.Debug(ctx, "Setting ID for reserved IP", map[string]interface{}{ + "id": plan.ID.ValueString(), + }) + + diags := plan.FlattenReservedIP(ctx, *reserveIP, true) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + tflog.Error(ctx, "Error flattening reserved IP", map[string]interface{}{ + "error": resp.Diagnostics.Errors(), + }) + return + } + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + tflog.Error(ctx, "Error setting state for reserved IP", map[string]interface{}{ + "error": resp.Diagnostics.Errors(), + }) + } else { + tflog.Debug(ctx, "Successfully set state for reserved IP", map[string]interface{}{ + "id": plan.ID.ValueString(), + }) + } +} + +func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + tflog.Debug(ctx, "Read linode_reserved_ip") + var state ReservedIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if helper.FrameworkAttemptRemoveResourceForEmptyID(ctx, state.ID, resp) { + return + } + + ctx = populateLogAttributes(ctx, &state) + + client := r.Meta.Client + address := state.Address.ValueString() + + reservedIP, err := client.GetReservedIPAddress(ctx, address) + if err != nil { + if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { + resp.Diagnostics.AddWarning( + "Reserved IP No Longer Exists", + fmt.Sprintf( + "Removing reserved IP %s from state because it no longer exists", + state.ID.ValueString(), + ), + ) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + "Unable to Refresh the Reserved IP", + fmt.Sprintf( + "Error finding the specified Reserved IP: %s", + err.Error(), + ), + ) + return + } + + if reservedIP == nil { + resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the reserved ip") + return + } + + resp.Diagnostics.Append(state.FlattenReservedIP(ctx, *reservedIP, false)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Reserved IPs cannot be updated, so this method is left empty +} + +func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state ReservedIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + address := state.Address.ValueString() + + tflog.Debug(ctx, "client.DeleteReservedIPAddress(...)") + if err := client.DeleteReservedIPAddress(ctx, address); err != nil { + if lErr, ok := err.(*linodego.Error); (ok && lErr.Code != 404) || !ok { + resp.Diagnostics.AddError( + "Failed to Delete Reserved IP", + fmt.Sprintf( + "failed to delete reserved ip (%s): %s", + address, err.Error(), + ), + ) + } + } +} + +func populateLogAttributes(ctx context.Context, data *ReservedIPModel) context.Context { + return helper.SetLogFieldBulk(ctx, map[string]any{ + "region": data.Region.ValueString(), + "address": data.ID.ValueString(), + }) +} diff --git a/linode/networkreservedips/framework_schema.go b/linode/networkreservedips/framework_schema.go new file mode 100644 index 000000000..d4e03121b --- /dev/null +++ b/linode/networkreservedips/framework_schema.go @@ -0,0 +1,60 @@ +package networkreservedips + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" +) + +var frameworkResourceSchema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "region": schema.StringAttribute{ + Description: "The Region in which to reserve the IP address.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "address": schema.StringAttribute{ + Description: "The reserved 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, + }, + "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, + }, + }, +} diff --git a/linode/networkreservedips/resource_test.go b/linode/networkreservedips/resource_test.go new file mode 100644 index 000000000..5827d7f33 --- /dev/null +++ b/linode/networkreservedips/resource_test.go @@ -0,0 +1,59 @@ +//go:build integration || instance + +package networkreservedips_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" + "github.com/linode/terraform-provider-linode/v2/linode/networkreservedips/tmpl" +) + +var testRegion string + +func init() { + region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") + if err != nil { + panic(fmt.Sprintf("Error getting random region: %s", err)) + } + testRegion = region + fmt.Println(testRegion) +} + +func TestAccResource_reserveIP(t *testing.T) { + t.Parallel() + + resName := "linode_reserved_ip.test" + instanceName := acctest.RandomWithPrefix("tf_test") + + t.Logf("Starting TestAccResource_reserveIP with resName: %s, instanceName: %s, region: %s", resName, instanceName, testRegion) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + t.Log("Running PreCheck") + acceptance.PreCheck(t) + }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.ReserveIP(t, instanceName, testRegion), + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + t.Log("Running Check function") + return nil + }, + resource.TestCheckResourceAttrSet(resName, "id"), + resource.TestCheckResourceAttrSet(resName, "address"), + resource.TestCheckResourceAttr(resName, "region", testRegion), + ), + }, + }, + }) + + t.Log("Finished TestAccResource_reserveIP") +} diff --git a/linode/networkreservedips/tmpl/reserved_ip.gotf b/linode/networkreservedips/tmpl/reserved_ip.gotf new file mode 100644 index 000000000..cd54c26e4 --- /dev/null +++ b/linode/networkreservedips/tmpl/reserved_ip.gotf @@ -0,0 +1,17 @@ +{{ define "reserved_ip_data_basic" }} + +resource "linode_reserved_ip" "test" { + region = "{{ .Region }}" +} + +data "linode_reserved_ip" "test" { + address = linode_reserved_ip.test.address +} + +{{ end }} + +{{ define "reserved_ip_data_list" }} + +data "linode_reserved_ip" "test" {} + +{{ end }} diff --git a/linode/networkreservedips/tmpl/reserved_ip_basic.gotf b/linode/networkreservedips/tmpl/reserved_ip_basic.gotf new file mode 100644 index 000000000..23e617dcf --- /dev/null +++ b/linode/networkreservedips/tmpl/reserved_ip_basic.gotf @@ -0,0 +1,7 @@ +{{ define "reserved_ip_basic" }} + +resource "linode_reserved_ip" "test" { + region = "{{ .Region }}" +} + +{{ end }} \ No newline at end of file diff --git a/linode/networkreservedips/tmpl/template.go b/linode/networkreservedips/tmpl/template.go new file mode 100644 index 000000000..d2cc09e33 --- /dev/null +++ b/linode/networkreservedips/tmpl/template.go @@ -0,0 +1,31 @@ +package tmpl + +import ( + "testing" + + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" +) + +type TemplateData struct { + Region string + Address string +} + +func DataBasic(t *testing.T, region string) string { + return acceptance.ExecuteTemplate(t, + "reserved_ip_data_basic", TemplateData{ + Region: region, + }) +} + +func DataList(t *testing.T) string { + return acceptance.ExecuteTemplate(t, "reserved_ip_data_list", nil) +} + +// ReserveIP generates the Terraform configuration for reserving an IP address +func ReserveIP(t *testing.T, name, region string) string { + return acceptance.ExecuteTemplate(t, + "reserved_ip_basic", TemplateData{ + Region: region, + }) +} From aca9fc693b1aaf49dc555bf90ca72811a767d3ca Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 26 Sep 2024 10:48:45 -0400 Subject: [PATCH 04/49] Made changes for maintaining consistency and readability --- linode/instance/resource.go | 2 +- linode/instanceip/framework_resource.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/linode/instance/resource.go b/linode/instance/resource.go index 3c68cb3d9..831113a99 100644 --- a/linode/instance/resource.go +++ b/linode/instance/resource.go @@ -192,7 +192,7 @@ func createResource(ctx context.Context, d *schema.ResourceData, meta interface{ if ipv4Raw, ok := d.GetOk("ipv4"); ok { ipv4Set := ipv4Raw.(*schema.Set) for _, ip := range ipv4Set.List() { - createOpts.Ipv4 = append(createOpts.Ipv4, ip.(string)) + createOpts.IPv4 = append(createOpts.IPv4, ip.(string)) } } diff --git a/linode/instanceip/framework_resource.go b/linode/instanceip/framework_resource.go index 85fcd7ff1..e56a188a2 100644 --- a/linode/instanceip/framework_resource.go +++ b/linode/instanceip/framework_resource.go @@ -64,7 +64,7 @@ func (r *Resource) Create( Public: isPublic, Address: plan.Address.ValueString(), } - _, err = client.AddReservedIPToInstance(ctx, linodeID, createOpts) + _, err = client.AssignInstanceReservedIP(ctx, linodeID, createOpts) if err != nil { resp.Diagnostics.AddError( fmt.Sprintf("Failed to assign reserved IP to instance (%d)", linodeID), @@ -344,7 +344,7 @@ func addReservedIPToInstance(ctx context.Context, client linodego.Client, instan Address: ip, } - _, err := client.AddReservedIPToInstance(ctx, instanceID, opts) + _, err := client.AssignInstanceReservedIP(ctx, instanceID, opts) if err != nil { return fmt.Errorf("failed to add IP %s to Linode instance %d: %s", ip, instanceID, err) } From 88701c131193b18f9aba8689f24efd7d38bc8583 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 26 Sep 2024 10:50:45 -0400 Subject: [PATCH 05/49] Removed commented function --- linode/instanceip/resource_test.go | 37 ------------------------------ 1 file changed, 37 deletions(-) diff --git a/linode/instanceip/resource_test.go b/linode/instanceip/resource_test.go index 1140a2582..2ce6185f0 100644 --- a/linode/instanceip/resource_test.go +++ b/linode/instanceip/resource_test.go @@ -128,43 +128,6 @@ func TestAccInstanceIP_noApply(t *testing.T) { }) } -// func TestAccInstanceIP_addReservedIP(t *testing.T) { -// t.Parallel() - -// var instance linodego.Instance -// name := acctest.RandomWithPrefix("tf_test") -// reservedIP := "45.33.74.65" // Replace with an actual reserved IP address - -// resource.Test(t, resource.TestCase{ -// PreCheck: func() { acceptance.PreCheck(t) }, -// ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, -// CheckDestroy: acceptance.CheckInstanceDestroy, -// Steps: []resource.TestStep{ -// { -// Config: tmpl.Basic(t, name, testRegion, false), -// Check: resource.ComposeTestCheckFunc( -// acceptance.CheckInstanceExists("linode_instance.foobar", &instance), -// ), -// }, -// { -// Config: tmpl.AddReservedIP(t, name, testRegion, reservedIP), -// Check: resource.ComposeTestCheckFunc( -// acceptance.CheckInstanceExists("linode_instance.foobar", &instance), -// resource.TestCheckResourceAttr(testInstanceIPResName, "address", reservedIP), -// resource.TestCheckResourceAttr(testInstanceIPResName, "public", "true"), -// resource.TestCheckResourceAttrSet(testInstanceIPResName, "linode_id"), -// resource.TestCheckResourceAttrSet(testInstanceIPResName, "gateway"), -// resource.TestCheckResourceAttrSet(testInstanceIPResName, "subnet_mask"), -// resource.TestCheckResourceAttrSet(testInstanceIPResName, "prefix"), -// resource.TestCheckResourceAttrSet(testInstanceIPResName, "rdns"), -// resource.TestCheckResourceAttr(testInstanceIPResName, "region", testRegion), -// resource.TestCheckResourceAttr(testInstanceIPResName, "type", "ipv4"), -// ), -// }, -// }, -// }) -// } - func TestAccInstanceIP_addReservedIP(t *testing.T) { t.Parallel() From c9bb10d2636e30c5227821e9086e45d012a77022 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 26 Sep 2024 12:48:57 -0400 Subject: [PATCH 06/49] temporarily changed linodego to point to the latest commit on main branch before next release --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9367812a1..709ffe91f 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 - github.com/linode/linodego v1.40.0 + github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0 github.com/linode/linodego/k8s v1.25.2 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.27.0 @@ -101,7 +101,7 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect golang.org/x/mod v0.19.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/term v0.24.0 // indirect golang.org/x/text v0.18.0 // indirect diff --git a/go.sum b/go.sum index 8210e3d73..d8004321b 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.40.0 h1:7ESY0PwK94hoggoCtIroT1Xk6b1flrFBNZ6KwqbTqlI= -github.com/linode/linodego v1.40.0/go.mod h1:NsUw4l8QrLdIofRg1NYFBbW5ZERnmbZykVBszPZLORM= +github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0 h1:FbA+CGk47kdAm2XmVEm1rVCLFlo98uJ+5jnbTLjVkv8= +github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0/go.mod h1:Ow4/XZ0yvWBzt3iAHwchvhSx30AyLintsSMvvQ2/SJY= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -298,8 +298,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 5fcc625950aae42622ebfa792f09f34da4ac58f0 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 26 Sep 2024 13:13:48 -0400 Subject: [PATCH 07/49] removed unused addReservedIPToInstance func --- linode/instanceip/framework_resource.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/linode/instanceip/framework_resource.go b/linode/instanceip/framework_resource.go index e56a188a2..4065e8955 100644 --- a/linode/instanceip/framework_resource.go +++ b/linode/instanceip/framework_resource.go @@ -336,18 +336,3 @@ func populateLogAttributes(ctx context.Context, data *InstanceIPModel) context.C "address": data.ID.ValueString(), }) } - -func addReservedIPToInstance(ctx context.Context, client linodego.Client, instanceID int, ip string) error { - opts := linodego.InstanceReserveIPOptions{ - Type: "ipv4", - Public: true, - Address: ip, - } - - _, err := client.AssignInstanceReservedIP(ctx, instanceID, opts) - if err != nil { - return fmt.Errorf("failed to add IP %s to Linode instance %d: %s", ip, instanceID, err) - } - - return nil -} From c9196b8b0a6bea95a48103d9977251deaeb07221 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 26 Sep 2024 13:59:00 -0400 Subject: [PATCH 08/49] temporarily changed linodego to point to the latest commit on main branch before next release --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9367812a1..709ffe91f 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 - github.com/linode/linodego v1.40.0 + github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0 github.com/linode/linodego/k8s v1.25.2 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.27.0 @@ -101,7 +101,7 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect golang.org/x/mod v0.19.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/term v0.24.0 // indirect golang.org/x/text v0.18.0 // indirect diff --git a/go.sum b/go.sum index 8210e3d73..d8004321b 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.40.0 h1:7ESY0PwK94hoggoCtIroT1Xk6b1flrFBNZ6KwqbTqlI= -github.com/linode/linodego v1.40.0/go.mod h1:NsUw4l8QrLdIofRg1NYFBbW5ZERnmbZykVBszPZLORM= +github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0 h1:FbA+CGk47kdAm2XmVEm1rVCLFlo98uJ+5jnbTLjVkv8= +github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0/go.mod h1:Ow4/XZ0yvWBzt3iAHwchvhSx30AyLintsSMvvQ2/SJY= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -298,8 +298,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From b6638d8b43fdf3e8e699bc9b79d26dfaf2ce74c3 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 26 Sep 2024 17:11:51 -0400 Subject: [PATCH 09/49] added go buil inegration || networkreservedips to prevent integration tests from being run by unit test targets --- linode/networkreservedips/datasource_test.go | 2 ++ linode/networkreservedips/resource_test.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/linode/networkreservedips/datasource_test.go b/linode/networkreservedips/datasource_test.go index 0826e3663..49b461443 100644 --- a/linode/networkreservedips/datasource_test.go +++ b/linode/networkreservedips/datasource_test.go @@ -1,3 +1,5 @@ +//go:build integration || networkreservedips + package networkreservedips_test import ( diff --git a/linode/networkreservedips/resource_test.go b/linode/networkreservedips/resource_test.go index 5827d7f33..5b4307e66 100644 --- a/linode/networkreservedips/resource_test.go +++ b/linode/networkreservedips/resource_test.go @@ -1,4 +1,4 @@ -//go:build integration || instance +//go:build integration || networkreservedips package networkreservedips_test From be57ac73b2b06cb6863444d482dd14aa2e168de9 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 8 Oct 2024 11:20:47 -0400 Subject: [PATCH 10/49] removed unused import statement and changed linodego version to point to the latest release --- go.mod | 2 +- go.sum | 4 ++-- linode/framework_provider.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 5a510ea2d..9a23c4b9f 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 - github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0 + github.com/linode/linodego v1.41.0 github.com/linode/linodego/k8s v1.25.2 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.28.0 diff --git a/go.sum b/go.sum index d9a52a6de..a1f50f3e6 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0 h1:FbA+CGk47kdAm2XmVEm1rVCLFlo98uJ+5jnbTLjVkv8= -github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0/go.mod h1:Ow4/XZ0yvWBzt3iAHwchvhSx30AyLintsSMvvQ2/SJY= +github.com/linode/linodego v1.41.0 h1:GcP7JIBr9iLRJ9FwAtb9/WCT1DuPJS/xUApapfdjtiY= +github.com/linode/linodego v1.41.0/go.mod h1:Ow4/XZ0yvWBzt3iAHwchvhSx30AyLintsSMvvQ2/SJY= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 718213eed..e52acd9f0 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -2,8 +2,7 @@ package linode import ( "context" - "github.com/linode/terraform-provider-linode/v2/linode/networkreservedips" - "github.com/linode/terraform-provider-linode/v2/linode/vpcips" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" @@ -54,6 +53,7 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/nbs" "github.com/linode/terraform-provider-linode/v2/linode/nbtypes" "github.com/linode/terraform-provider-linode/v2/linode/networkingip" + "github.com/linode/terraform-provider-linode/v2/linode/networkreservedips" "github.com/linode/terraform-provider-linode/v2/linode/networktransferprices" "github.com/linode/terraform-provider-linode/v2/linode/objbucket" "github.com/linode/terraform-provider-linode/v2/linode/objcluster" From 38195dfed7703ea2bc737af6ace42370cba4d9d4 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 14 Oct 2024 12:35:48 -0400 Subject: [PATCH 11/49] removed unnecessary region filter from datasource --- linode/networkreservedips/framework_datasource.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/linode/networkreservedips/framework_datasource.go b/linode/networkreservedips/framework_datasource.go index 1c862841a..86e097ee1 100644 --- a/linode/networkreservedips/framework_datasource.go +++ b/linode/networkreservedips/framework_datasource.go @@ -2,7 +2,6 @@ package networkreservedips import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/types" @@ -78,9 +77,6 @@ func (d *DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp } 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( From 3267bec8e6cbeca7e29927fd5682c37d646ea39a Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 15 Oct 2024 12:15:35 -0400 Subject: [PATCH 12/49] formatting change --- linode/image/resource_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/linode/image/resource_test.go b/linode/image/resource_test.go index 8184c5d7b..f40af614b 100644 --- a/linode/image/resource_test.go +++ b/linode/image/resource_test.go @@ -36,8 +36,10 @@ var testImageBytesNew = []byte{ 0xe4, 0x02, 0x00, 0x7a, 0x7a, 0x6f, 0xed, 0x03, 0x00, 0x00, 0x00, } -var testRegion string -var testRegions []string +var ( + testRegion string + testRegions []string +) func init() { resource.AddTestSweepers("linode_image", &resource.Sweeper{ From 9c67fcfe5e0dc2949494e15a9505038a0d886591 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 15 Oct 2024 12:17:08 -0400 Subject: [PATCH 13/49] added check for addition of ipv4 field post creation --- linode/instance/resource.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/linode/instance/resource.go b/linode/instance/resource.go index 831113a99..a072267ed 100644 --- a/linode/instance/resource.go +++ b/linode/instance/resource.go @@ -188,7 +188,6 @@ func createResource(ctx context.Context, d *schema.ResourceData, meta interface{ ), } - // Add this new section to handle IPv4 addresses if ipv4Raw, ok := d.GetOk("ipv4"); ok { ipv4Set := ipv4Raw.(*schema.Set) for _, ip := range ipv4Set.List() { @@ -826,6 +825,17 @@ func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{ } } + if d.HasChange("ipv4") { + fmt.Println("Change detected") + oldIPv4, newIPv4 := d.GetChange("ipv4") + oldSet := oldIPv4.(*schema.Set) + newSet := newIPv4.(*schema.Set) + if len(newSet.Difference(oldSet).List()) > 0 { + // Log a warning that direct IP changes are not supported + return diag.Errorf("Changing IPv4 addresses after instance creation is not directly supported. The attempted change will be ignored.") + } + } + // Don't reboot if the Linode should be powered off if !bootedNull && !booted { rebootInstance = false From 47854606a7c81f493ca3ff225313f33255277d94 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 15 Oct 2024 16:47:13 -0400 Subject: [PATCH 14/49] Removed debug logic and statements --- linode/instance/resource.go | 10 ++-------- linode/instance/tmpl/template.go | 4 ---- linode/instanceip/framework_resource.go | 25 ------------------------- 3 files changed, 2 insertions(+), 37 deletions(-) diff --git a/linode/instance/resource.go b/linode/instance/resource.go index a072267ed..75357b2f8 100644 --- a/linode/instance/resource.go +++ b/linode/instance/resource.go @@ -826,14 +826,8 @@ func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{ } if d.HasChange("ipv4") { - fmt.Println("Change detected") - oldIPv4, newIPv4 := d.GetChange("ipv4") - oldSet := oldIPv4.(*schema.Set) - newSet := newIPv4.(*schema.Set) - if len(newSet.Difference(oldSet).List()) > 0 { - // Log a warning that direct IP changes are not supported - return diag.Errorf("Changing IPv4 addresses after instance creation is not directly supported. The attempted change will be ignored.") - } + // Log a warning that direct IP changes are not supported + return diag.Errorf("Changing IPv4 addresses after instance creation is not directly supported. The attempted change will be ignored.") } // Don't reboot if the Linode should be powered off diff --git a/linode/instance/tmpl/template.go b/linode/instance/tmpl/template.go index 1b4f7b62e..101591a13 100644 --- a/linode/instance/tmpl/template.go +++ b/linode/instance/tmpl/template.go @@ -744,9 +744,5 @@ func WithReservedIP(t *testing.T, label, pubKey, region, rootPass string, reserv RootPass: rootPass, IPv4: []string{reservedIP}, }) - - // Add this debug logging - t.Logf("Generated Terraform config for WithReservedIP:\n%s", generatedConfig) - return generatedConfig } diff --git a/linode/instanceip/framework_resource.go b/linode/instanceip/framework_resource.go index 4065e8955..24b70ab38 100644 --- a/linode/instanceip/framework_resource.go +++ b/linode/instanceip/framework_resource.go @@ -72,31 +72,6 @@ func (r *Resource) Create( ) return } - - // Fetch the IP information after assigning - instanceIPs, err := client.GetInstanceIPAddresses(ctx, linodeID) - if err != nil { - resp.Diagnostics.AddError( - fmt.Sprintf("Failed to fetch IP addresses for instance (%d)", linodeID), - err.Error(), - ) - return - } - - // Find the assigned IP in the instance's IP addresses - for _, instanceIP := range instanceIPs.IPv4.Public { - if instanceIP.Address == plan.Address.ValueString() { - ip = instanceIP - break - } - } - if ip == nil { - resp.Diagnostics.AddError( - "Failed to find assigned IP", - fmt.Sprintf("Could not find the assigned IP %s in instance (%d) IP addresses", plan.Address.ValueString(), linodeID), - ) - return - } } else { // Allocate a new IP ip, err = client.AddInstanceIPAddress(ctx, linodeID, isPublic) From 24493f8ed55b185f42234a06aec690c315f9f014 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Wed, 16 Oct 2024 04:07:48 -0400 Subject: [PATCH 15/49] Created a separate datasource for listing reserved IPs, added the corresponding test. Removed unnessary logs --- linode/framework_provider.go | 6 +- linode/image/resource_test.go | 6 +- .../datasource_fetch_model.go | 56 ++++++++ .../datasource_fetch_schema.go} | 10 +- .../datasource_test.go | 27 +--- .../framework_datasource_fetch.go | 90 +++++++++++++ .../framework_models.go | 40 +++--- .../framework_resource.go | 68 ++++------ .../framework_schema.go | 2 +- .../resource_test.go | 11 +- .../tmpl/reserved_ip.gotf | 7 +- .../tmpl/reserved_ip_basic.gotf | 0 linode/networkreservedip/tmpl/template.go | 27 ++++ .../datasource_list_model.go | 9 ++ .../datasource_list_schema.go | 47 +++++++ .../datasource_list_test.go | 30 +++++ .../framework_datasource.go | 120 ------------------ .../framework_datasource_list.go | 72 +++++++++++ .../tmpl/reserved_ip_list.gotf | 9 ++ linode/networkreservedips/tmpl/template.go | 20 --- 20 files changed, 403 insertions(+), 254 deletions(-) create mode 100644 linode/networkreservedip/datasource_fetch_model.go rename linode/{networkreservedips/framework_datasource_schema.go => networkreservedip/datasource_fetch_schema.go} (92%) rename linode/{networkreservedips => networkreservedip}/datasource_test.go (67%) create mode 100644 linode/networkreservedip/framework_datasource_fetch.go rename linode/{networkreservedips => networkreservedip}/framework_models.go (62%) rename linode/{networkreservedips => networkreservedip}/framework_resource.go (65%) rename linode/{networkreservedips => networkreservedip}/framework_schema.go (98%) rename linode/{networkreservedips => networkreservedip}/resource_test.go (82%) rename linode/{networkreservedips => networkreservedip}/tmpl/reserved_ip.gotf (58%) rename linode/{networkreservedips => networkreservedip}/tmpl/reserved_ip_basic.gotf (100%) create mode 100644 linode/networkreservedip/tmpl/template.go create mode 100644 linode/networkreservedips/datasource_list_model.go create mode 100644 linode/networkreservedips/datasource_list_schema.go create mode 100644 linode/networkreservedips/datasource_list_test.go delete mode 100644 linode/networkreservedips/framework_datasource.go create mode 100644 linode/networkreservedips/framework_datasource_list.go create mode 100644 linode/networkreservedips/tmpl/reserved_ip_list.gotf diff --git a/linode/framework_provider.go b/linode/framework_provider.go index e52acd9f0..25d5f03a4 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -53,6 +53,7 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/nbs" "github.com/linode/terraform-provider-linode/v2/linode/nbtypes" "github.com/linode/terraform-provider-linode/v2/linode/networkingip" + "github.com/linode/terraform-provider-linode/v2/linode/networkreservedip" "github.com/linode/terraform-provider-linode/v2/linode/networkreservedips" "github.com/linode/terraform-provider-linode/v2/linode/networktransferprices" "github.com/linode/terraform-provider-linode/v2/linode/objbucket" @@ -226,7 +227,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res firewall.NewResource, placementgroup.NewResource, placementgroupassignment.NewResource, - networkreservedips.NewResource, + networkreservedip.NewResource, } } @@ -295,6 +296,7 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource placementgroups.NewDataSource, childaccount.NewDataSource, childaccounts.NewDataSource, - networkreservedips.NewDataSource, + networkreservedip.NewDataSourceFetch, + networkreservedips.NewDataSourceList, } } diff --git a/linode/image/resource_test.go b/linode/image/resource_test.go index 8184c5d7b..f40af614b 100644 --- a/linode/image/resource_test.go +++ b/linode/image/resource_test.go @@ -36,8 +36,10 @@ var testImageBytesNew = []byte{ 0xe4, 0x02, 0x00, 0x7a, 0x7a, 0x6f, 0xed, 0x03, 0x00, 0x00, 0x00, } -var testRegion string -var testRegions []string +var ( + testRegion string + testRegions []string +) func init() { resource.AddTestSweepers("linode_image", &resource.Sweeper{ diff --git a/linode/networkreservedip/datasource_fetch_model.go b/linode/networkreservedip/datasource_fetch_model.go new file mode 100644 index 000000000..cb6d9197e --- /dev/null +++ b/linode/networkreservedip/datasource_fetch_model.go @@ -0,0 +1,56 @@ +package networkreservedip + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/linode/linodego" +) + +type DataSourceFetchModel 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"` +} + +func (data *DataSourceFetchModel) 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) +} + +// 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 +// } diff --git a/linode/networkreservedips/framework_datasource_schema.go b/linode/networkreservedip/datasource_fetch_schema.go similarity index 92% rename from linode/networkreservedips/framework_datasource_schema.go rename to linode/networkreservedip/datasource_fetch_schema.go index 20f5099e8..efc1e58dd 100644 --- a/linode/networkreservedips/framework_datasource_schema.go +++ b/linode/networkreservedip/datasource_fetch_schema.go @@ -1,4 +1,4 @@ -package networkreservedips +package networkreservedip import ( "github.com/hashicorp/terraform-plugin-framework/attr" @@ -36,7 +36,7 @@ var reservedIPObjectType = types.ObjectType{ }, } -var frameworkDataSourceSchema = schema.Schema{ +var frameworkDataSourceFetchSchema = schema.Schema{ Attributes: map[string]schema.Attribute{ "region": schema.StringAttribute{ Description: "The Region in which to reserve the IP address.", @@ -44,6 +44,7 @@ var frameworkDataSourceSchema = schema.Schema{ }, "address": schema.StringAttribute{ Description: "The reserved IP address.", + Computed: true, Optional: true, }, "gateway": schema.StringAttribute{ @@ -82,10 +83,5 @@ var frameworkDataSourceSchema = schema.Schema{ 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, - }, }, } diff --git a/linode/networkreservedips/datasource_test.go b/linode/networkreservedip/datasource_test.go similarity index 67% rename from linode/networkreservedips/datasource_test.go rename to linode/networkreservedip/datasource_test.go index 49b461443..5520d9fa0 100644 --- a/linode/networkreservedips/datasource_test.go +++ b/linode/networkreservedip/datasource_test.go @@ -1,19 +1,19 @@ -//go:build integration || networkreservedips +//go:build integration || networkreservedip -package networkreservedips_test +package networkreservedip_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" + "github.com/linode/terraform-provider-linode/v2/linode/networkreservedip/tmpl" ) func TestAccDataSource_reservedIP(t *testing.T) { t.Parallel() - resourceName := "data.linode_reserved_ip.test" + resourceName := "data.linode_reserved_ip_fetch.test" region, _ := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") resource.Test(t, resource.TestCase{ @@ -38,22 +38,3 @@ func TestAccDataSource_reservedIP(t *testing.T) { }, }) } - -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.#"), - ), - }, - }, - }) -} diff --git a/linode/networkreservedip/framework_datasource_fetch.go b/linode/networkreservedip/framework_datasource_fetch.go new file mode 100644 index 000000000..59ff5f163 --- /dev/null +++ b/linode/networkreservedip/framework_datasource_fetch.go @@ -0,0 +1,90 @@ +package networkreservedip + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/linode/terraform-provider-linode/v2/linode/helper" +) + +func NewDataSourceFetch() datasource.DataSource { + return &DataSource{ + BaseDataSource: helper.NewBaseDataSource( + helper.BaseDataSourceConfig{ + Name: "linode_reserved_ip_fetch", + Schema: &frameworkDataSourceFetchSchema, + }, + ), + } +} + +type DataSource struct { + helper.BaseDataSource +} + +func (d *DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "Read data.linode_reserved_ip") + + var data DataSourceFetchModel + 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 := "" + // 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)...) +} diff --git a/linode/networkreservedips/framework_models.go b/linode/networkreservedip/framework_models.go similarity index 62% rename from linode/networkreservedips/framework_models.go rename to linode/networkreservedip/framework_models.go index 4d6be3808..9d4accabc 100644 --- a/linode/networkreservedips/framework_models.go +++ b/linode/networkreservedip/framework_models.go @@ -1,4 +1,4 @@ -package networkreservedips +package networkreservedip import ( "context" @@ -46,24 +46,24 @@ func (m *ReservedIPModel) FlattenReservedIP( return diags } -func (m *ReservedIPModel) CopyFrom( - ctx context.Context, - other ReservedIPModel, - preserveKnown bool, -) diag.Diagnostics { - var diags diag.Diagnostics +// 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) +// 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 -} +// return diags +// } diff --git a/linode/networkreservedips/framework_resource.go b/linode/networkreservedip/framework_resource.go similarity index 65% rename from linode/networkreservedips/framework_resource.go rename to linode/networkreservedip/framework_resource.go index 88407eaee..cf85923e6 100644 --- a/linode/networkreservedips/framework_resource.go +++ b/linode/networkreservedip/framework_resource.go @@ -1,4 +1,4 @@ -package networkreservedips +package networkreservedip import ( "context" @@ -29,26 +29,20 @@ type Resource struct { func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { tflog.Debug(ctx, "Starting Create for linode_reserved_ip") - var plan ReservedIPModel + var data ReservedIPModel - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { - tflog.Error(ctx, "Error getting plan", map[string]interface{}{ - "error": resp.Diagnostics.Errors(), - }) return } - ctx = populateLogAttributes(ctx, &plan) + ctx = populateLogAttributes(ctx, &data) client := r.Meta.Client reserveIP, err := client.ReserveIPAddress(ctx, linodego.ReserveIPOptions{ - Region: plan.Region.ValueString(), + Region: data.Region.ValueString(), }) if err != nil { - tflog.Error(ctx, "Failed to reserve IP address", map[string]interface{}{ - "error": err.Error(), - }) resp.Diagnostics.AddError( "Failed to reserve IP address", err.Error(), @@ -57,60 +51,41 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp } if reserveIP == nil { - tflog.Error(ctx, "Received nil pointer for reserved IP") resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the reserved ip") return } - tflog.Debug(ctx, "Successfully reserved IP address", map[string]interface{}{ - "address": reserveIP.Address, - "region": reserveIP.Region, - }) - - plan.ID = types.StringValue(reserveIP.Address) + data.ID = types.StringValue(reserveIP.Address) tflog.Debug(ctx, "Setting ID for reserved IP", map[string]interface{}{ - "id": plan.ID.ValueString(), + "id": data.ID.ValueString(), }) - diags := plan.FlattenReservedIP(ctx, *reserveIP, true) + diags := data.FlattenReservedIP(ctx, *reserveIP, true) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { - tflog.Error(ctx, "Error flattening reserved IP", map[string]interface{}{ - "error": resp.Diagnostics.Errors(), - }) return } - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - tflog.Error(ctx, "Error setting state for reserved IP", map[string]interface{}{ - "error": resp.Diagnostics.Errors(), - }) - } else { - tflog.Debug(ctx, "Successfully set state for reserved IP", map[string]interface{}{ - "id": plan.ID.ValueString(), - }) - } + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { tflog.Debug(ctx, "Read linode_reserved_ip") - var state ReservedIPModel + var data ReservedIPModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } - if helper.FrameworkAttemptRemoveResourceForEmptyID(ctx, state.ID, resp) { + if helper.FrameworkAttemptRemoveResourceForEmptyID(ctx, data.ID, resp) { return } - ctx = populateLogAttributes(ctx, &state) + ctx = populateLogAttributes(ctx, &data) client := r.Meta.Client - address := state.Address.ValueString() + address := data.Address.ValueString() reservedIP, err := client.GetReservedIPAddress(ctx, address) if err != nil { @@ -119,7 +94,7 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res "Reserved IP No Longer Exists", fmt.Sprintf( "Removing reserved IP %s from state because it no longer exists", - state.ID.ValueString(), + data.ID.ValueString(), ), ) resp.State.RemoveResource(ctx) @@ -135,13 +110,16 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res return } - if reservedIP == nil { - resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the reserved ip") + // if reservedIP == nil { + // resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the reserved ip") + // return + // } + + resp.Diagnostics.Append(data.FlattenReservedIP(ctx, *reservedIP, false)...) + if resp.Diagnostics.HasError() { return } - - resp.Diagnostics.Append(state.FlattenReservedIP(ctx, *reservedIP, false)...) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { diff --git a/linode/networkreservedips/framework_schema.go b/linode/networkreservedip/framework_schema.go similarity index 98% rename from linode/networkreservedips/framework_schema.go rename to linode/networkreservedip/framework_schema.go index d4e03121b..7ab010669 100644 --- a/linode/networkreservedips/framework_schema.go +++ b/linode/networkreservedip/framework_schema.go @@ -1,4 +1,4 @@ -package networkreservedips +package networkreservedip import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" diff --git a/linode/networkreservedips/resource_test.go b/linode/networkreservedip/resource_test.go similarity index 82% rename from linode/networkreservedips/resource_test.go rename to linode/networkreservedip/resource_test.go index 5b4307e66..3ec18081b 100644 --- a/linode/networkreservedips/resource_test.go +++ b/linode/networkreservedip/resource_test.go @@ -1,6 +1,6 @@ -//go:build integration || networkreservedips +//go:build integration || networkreservedip -package networkreservedips_test +package networkreservedip_test import ( "fmt" @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/linode/terraform-provider-linode/v2/linode/acceptance" - "github.com/linode/terraform-provider-linode/v2/linode/networkreservedips/tmpl" + "github.com/linode/terraform-provider-linode/v2/linode/networkreservedip/tmpl" ) var testRegion string @@ -29,12 +29,8 @@ func TestAccResource_reserveIP(t *testing.T) { resName := "linode_reserved_ip.test" instanceName := acctest.RandomWithPrefix("tf_test") - - t.Logf("Starting TestAccResource_reserveIP with resName: %s, instanceName: %s, region: %s", resName, instanceName, testRegion) - resource.Test(t, resource.TestCase{ PreCheck: func() { - t.Log("Running PreCheck") acceptance.PreCheck(t) }, ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, @@ -44,7 +40,6 @@ func TestAccResource_reserveIP(t *testing.T) { Config: tmpl.ReserveIP(t, instanceName, testRegion), Check: resource.ComposeTestCheckFunc( func(s *terraform.State) error { - t.Log("Running Check function") return nil }, resource.TestCheckResourceAttrSet(resName, "id"), diff --git a/linode/networkreservedips/tmpl/reserved_ip.gotf b/linode/networkreservedip/tmpl/reserved_ip.gotf similarity index 58% rename from linode/networkreservedips/tmpl/reserved_ip.gotf rename to linode/networkreservedip/tmpl/reserved_ip.gotf index cd54c26e4..efc2ff330 100644 --- a/linode/networkreservedips/tmpl/reserved_ip.gotf +++ b/linode/networkreservedip/tmpl/reserved_ip.gotf @@ -4,14 +4,9 @@ resource "linode_reserved_ip" "test" { region = "{{ .Region }}" } -data "linode_reserved_ip" "test" { +data "linode_reserved_ip_fetch" "test" { address = linode_reserved_ip.test.address } {{ end }} -{{ define "reserved_ip_data_list" }} - -data "linode_reserved_ip" "test" {} - -{{ end }} diff --git a/linode/networkreservedips/tmpl/reserved_ip_basic.gotf b/linode/networkreservedip/tmpl/reserved_ip_basic.gotf similarity index 100% rename from linode/networkreservedips/tmpl/reserved_ip_basic.gotf rename to linode/networkreservedip/tmpl/reserved_ip_basic.gotf diff --git a/linode/networkreservedip/tmpl/template.go b/linode/networkreservedip/tmpl/template.go new file mode 100644 index 000000000..80f1f70d9 --- /dev/null +++ b/linode/networkreservedip/tmpl/template.go @@ -0,0 +1,27 @@ +package tmpl + +import ( + "testing" + + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" +) + +type TemplateData struct { + Region string + Address string +} + +func DataBasic(t *testing.T, region string) string { + return acceptance.ExecuteTemplate(t, + "reserved_ip_data_basic", TemplateData{ + Region: region, + }) +} + +// ReserveIP generates the Terraform configuration for reserving an IP address +func ReserveIP(t *testing.T, name, region string) string { + return acceptance.ExecuteTemplate(t, + "reserved_ip_basic", TemplateData{ + Region: region, + }) +} diff --git a/linode/networkreservedips/datasource_list_model.go b/linode/networkreservedips/datasource_list_model.go new file mode 100644 index 000000000..79656ad51 --- /dev/null +++ b/linode/networkreservedips/datasource_list_model.go @@ -0,0 +1,9 @@ +package networkreservedips + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type DataSourceListModel struct { + ReservedIPs types.List `tfsdk:"reserved_ips"` +} diff --git a/linode/networkreservedips/datasource_list_schema.go b/linode/networkreservedips/datasource_list_schema.go new file mode 100644 index 000000000..7d8d38d81 --- /dev/null +++ b/linode/networkreservedips/datasource_list_schema.go @@ -0,0 +1,47 @@ +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 frameworkDataSourceListSchema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "reserved_ips": schema.ListAttribute{ + Description: "A list of all reserved IPs.", + Computed: true, + ElementType: reservedIPObjectType, + }, + }, +} diff --git a/linode/networkreservedips/datasource_list_test.go b/linode/networkreservedips/datasource_list_test.go new file mode 100644 index 000000000..97cfe8052 --- /dev/null +++ b/linode/networkreservedips/datasource_list_test.go @@ -0,0 +1,30 @@ +//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_reservedIPList(t *testing.T) { + t.Parallel() + + resourceName := "data.linode_reserved_ip_list.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.#"), + ), + }, + }, + }) +} diff --git a/linode/networkreservedips/framework_datasource.go b/linode/networkreservedips/framework_datasource.go deleted file mode 100644 index 86e097ee1..000000000 --- a/linode/networkreservedips/framework_datasource.go +++ /dev/null @@ -1,120 +0,0 @@ -package networkreservedips - -import ( - "context" - - "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"` -} - -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) -} - -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 := "" - 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)...) -} diff --git a/linode/networkreservedips/framework_datasource_list.go b/linode/networkreservedips/framework_datasource_list.go new file mode 100644 index 000000000..2320d3119 --- /dev/null +++ b/linode/networkreservedips/framework_datasource_list.go @@ -0,0 +1,72 @@ +package networkreservedips + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/linode/terraform-provider-linode/v2/linode/helper" +) + +func NewDataSourceList() datasource.DataSource { + return &DataSourceList{ + BaseDataSource: helper.NewBaseDataSource( + helper.BaseDataSourceConfig{ + Name: "linode_reserved_ip_list", + Schema: &frameworkDataSourceListSchema, + }, + ), + } +} + +type DataSourceList struct { + helper.BaseDataSource +} + +func (d *DataSourceList) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "Read data.linode_reserved_ip_list") + + var data DataSourceListModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + client := d.Meta.Client + + // List all reserved IPs + ips, err := client.ListReservedIPAddresses(ctx, nil) + 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(true), // All IPs in this list are reserved + } + } + + reservedIPsValue, diags := types.ListValueFrom(ctx, reservedIPObjectType, reservedIPs) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + data.ReservedIPs = reservedIPsValue + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/linode/networkreservedips/tmpl/reserved_ip_list.gotf b/linode/networkreservedips/tmpl/reserved_ip_list.gotf new file mode 100644 index 000000000..e2357dc13 --- /dev/null +++ b/linode/networkreservedips/tmpl/reserved_ip_list.gotf @@ -0,0 +1,9 @@ +{{ define "reserved_ip_data_list" }} + +data "linode_reserved_ip_list" "test" {} + +output "reserved_ips" { + value = data.linode_reserved_ip_list.test.reserved_ips +} + +{{ end }} \ No newline at end of file diff --git a/linode/networkreservedips/tmpl/template.go b/linode/networkreservedips/tmpl/template.go index d2cc09e33..68826c4ea 100644 --- a/linode/networkreservedips/tmpl/template.go +++ b/linode/networkreservedips/tmpl/template.go @@ -6,26 +6,6 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/acceptance" ) -type TemplateData struct { - Region string - Address string -} - -func DataBasic(t *testing.T, region string) string { - return acceptance.ExecuteTemplate(t, - "reserved_ip_data_basic", TemplateData{ - Region: region, - }) -} - func DataList(t *testing.T) string { return acceptance.ExecuteTemplate(t, "reserved_ip_data_list", nil) } - -// ReserveIP generates the Terraform configuration for reserving an IP address -func ReserveIP(t *testing.T, name, region string) string { - return acceptance.ExecuteTemplate(t, - "reserved_ip_basic", TemplateData{ - Region: region, - }) -} From 66ce8040be81baaed0cef2bca59fffd123166350 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 21 Oct 2024 19:55:16 -0400 Subject: [PATCH 16/49] Fix for nil pointer dereference --- linode/instanceip/framework_resource.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/linode/instanceip/framework_resource.go b/linode/instanceip/framework_resource.go index 24b70ab38..dde7ae2a7 100644 --- a/linode/instanceip/framework_resource.go +++ b/linode/instanceip/framework_resource.go @@ -64,7 +64,7 @@ func (r *Resource) Create( Public: isPublic, Address: plan.Address.ValueString(), } - _, err = client.AssignInstanceReservedIP(ctx, linodeID, createOpts) + ip, err = client.AssignInstanceReservedIP(ctx, linodeID, createOpts) if err != nil { resp.Diagnostics.AddError( fmt.Sprintf("Failed to assign reserved IP to instance (%d)", linodeID), @@ -72,6 +72,7 @@ func (r *Resource) Create( ) return } + } else { // Allocate a new IP ip, err = client.AddInstanceIPAddress(ctx, linodeID, isPublic) From 1e97bea8b4e43d29a86e0bfb7840baf95803667b Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 22 Oct 2024 11:01:37 -0400 Subject: [PATCH 17/49] Update go.mod to point to latest release of linodego --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5a510ea2d..ea92aa174 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 - github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0 + github.com/linode/linodego v1.42.0 github.com/linode/linodego/k8s v1.25.2 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.28.0 diff --git a/go.sum b/go.sum index d9a52a6de..1c731765e 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0 h1:FbA+CGk47kdAm2XmVEm1rVCLFlo98uJ+5jnbTLjVkv8= -github.com/linode/linodego v1.41.1-0.20240925173015-b20be2e986e0/go.mod h1:Ow4/XZ0yvWBzt3iAHwchvhSx30AyLintsSMvvQ2/SJY= +github.com/linode/linodego v1.42.0 h1:ZSbi4MtvwrfB9Y6bknesorvvueBGGilcmh2D5dq76RM= +github.com/linode/linodego v1.42.0/go.mod h1:2yzmY6pegPBDgx2HDllmt0eIk2IlzqcgK6NR0wFCFRY= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= From 865a471677810c74f47c8780d49fee68a750eb54 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 22 Oct 2024 15:18:55 -0400 Subject: [PATCH 18/49] Added reserved field to linode_instance_ip schema --- linode/instanceip/framework_models.go | 4 +++- linode/instanceip/framework_schema.go | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/linode/instanceip/framework_models.go b/linode/instanceip/framework_models.go index 21a527751..b5fa2174f 100644 --- a/linode/instanceip/framework_models.go +++ b/linode/instanceip/framework_models.go @@ -31,6 +31,7 @@ type InstanceIPModel struct { Type types.String `tfsdk:"type"` ApplyImmediately types.Bool `tfsdk:"apply_immediately"` IPVPCNAT1To1 types.List `tfsdk:"vpc_nat_1_1"` + Reserved types.Bool `tfsdk:"reserved"` } func (m *InstanceIPModel) FlattenInstanceIP( @@ -48,6 +49,7 @@ func (m *InstanceIPModel) FlattenInstanceIP( m.Prefix = helper.KeepOrUpdateInt64(m.Prefix, int64(ip.Prefix), preserveKnown) m.RDNS = helper.KeepOrUpdateString(m.RDNS, ip.RDNS, preserveKnown) + m.Reserved = helper.KeepOrUpdateBool(m.Reserved, ip.Reserved, preserveKnown) m.Region = helper.KeepOrUpdateString(m.Region, ip.Region, preserveKnown) m.SubnetMask = helper.KeepOrUpdateString(m.SubnetMask, ip.SubnetMask, preserveKnown) @@ -97,7 +99,7 @@ func (m *InstanceIPModel) CopyFrom( m.Prefix = helper.KeepOrUpdateValue(m.Prefix, other.Prefix, preserveKnown) m.RDNS = helper.KeepOrUpdateValue(m.RDNS, other.RDNS, preserveKnown) - + m.Reserved = helper.KeepOrUpdateValue(m.Reserved, other.Reserved, preserveKnown) m.Region = helper.KeepOrUpdateValue(m.Region, other.Region, preserveKnown) m.SubnetMask = helper.KeepOrUpdateValue(m.SubnetMask, other.SubnetMask, preserveKnown) m.Type = helper.KeepOrUpdateValue(m.Type, other.Type, preserveKnown) diff --git a/linode/instanceip/framework_schema.go b/linode/instanceip/framework_schema.go index f29d624ea..89ef7c2df 100644 --- a/linode/instanceip/framework_schema.go +++ b/linode/instanceip/framework_schema.go @@ -106,5 +106,10 @@ var frameworkResourceSchema = schema.Schema{ Computed: true, Default: booldefault.StaticBool(false), }, + + "reserved": schema.BoolAttribute{ + Description: "The reservation status of the IP address", + Computed: true, + }, }, } From dbca9353140e3264949867256d8f41fc431d0ac6 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Wed, 23 Oct 2024 11:13:10 -0400 Subject: [PATCH 19/49] committing branch --- linode/instanceip/framework_resource.go | 1 - 1 file changed, 1 deletion(-) diff --git a/linode/instanceip/framework_resource.go b/linode/instanceip/framework_resource.go index 24b70ab38..9f02578dd 100644 --- a/linode/instanceip/framework_resource.go +++ b/linode/instanceip/framework_resource.go @@ -169,7 +169,6 @@ func (r *Resource) Read( if resp.Diagnostics.HasError() { return } - ip, err := client.GetInstanceIPAddress(ctx, linodeID, address) if err != nil { if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { From 021e2101fa536c189619a3480714091f8a71b39a Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Wed, 23 Oct 2024 13:29:05 -0400 Subject: [PATCH 20/49] fix for plugin crash --- linode/instanceip/framework_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linode/instanceip/framework_resource.go b/linode/instanceip/framework_resource.go index 9f02578dd..14a065eaf 100644 --- a/linode/instanceip/framework_resource.go +++ b/linode/instanceip/framework_resource.go @@ -64,7 +64,7 @@ func (r *Resource) Create( Public: isPublic, Address: plan.Address.ValueString(), } - _, err = client.AssignInstanceReservedIP(ctx, linodeID, createOpts) + ip, err = client.AssignInstanceReservedIP(ctx, linodeID, createOpts) if err != nil { resp.Diagnostics.AddError( fmt.Sprintf("Failed to assign reserved IP to instance (%d)", linodeID), From f0eb534dc71ce63cc31a58913968442b210258de Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 24 Oct 2024 11:04:35 -0400 Subject: [PATCH 21/49] Removed unused reservedIPObjectType struct, copyFrom function --- .../datasource_fetch_model.go | 22 ------------------- .../datasource_fetch_schema.go | 17 -------------- .../networkreservedip/framework_resource.go | 5 ----- 3 files changed, 44 deletions(-) diff --git a/linode/networkreservedip/datasource_fetch_model.go b/linode/networkreservedip/datasource_fetch_model.go index cb6d9197e..bd2a575c7 100644 --- a/linode/networkreservedip/datasource_fetch_model.go +++ b/linode/networkreservedip/datasource_fetch_model.go @@ -32,25 +32,3 @@ func (data *DataSourceFetchModel) parseIP(ip *linodego.InstanceIP) { data.LinodeID = types.Int64Value(int64(ip.LinodeID)) data.Reserved = types.BoolValue(ip.Reserved) } - -// 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 -// } diff --git a/linode/networkreservedip/datasource_fetch_schema.go b/linode/networkreservedip/datasource_fetch_schema.go index efc1e58dd..ea334a69a 100644 --- a/linode/networkreservedip/datasource_fetch_schema.go +++ b/linode/networkreservedip/datasource_fetch_schema.go @@ -1,7 +1,6 @@ package networkreservedip import ( - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -20,22 +19,6 @@ type ReservedIPObject struct { 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 frameworkDataSourceFetchSchema = schema.Schema{ Attributes: map[string]schema.Attribute{ "region": schema.StringAttribute{ diff --git a/linode/networkreservedip/framework_resource.go b/linode/networkreservedip/framework_resource.go index cf85923e6..d19cc7ec8 100644 --- a/linode/networkreservedip/framework_resource.go +++ b/linode/networkreservedip/framework_resource.go @@ -110,11 +110,6 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res return } - // if reservedIP == nil { - // resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the reserved ip") - // return - // } - resp.Diagnostics.Append(data.FlattenReservedIP(ctx, *reservedIP, false)...) if resp.Diagnostics.HasError() { return From f76bfbd4786e23019c03f1f3cb9c9e03a3f6c845 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 24 Oct 2024 14:39:59 -0400 Subject: [PATCH 22/49] Separated the reserved IP allocation into linode_instance_reserved_ip resource --- linode/framework_provider.go | 2 + linode/instanceip/framework_resource.go | 40 +-- linode/instanceip/framework_schema.go | 1 - linode/instanceip/resource_test.go | 31 -- linode/instanceip/tmpl/template.go | 9 - linode/instancereservedip/framework_models.go | 111 +++++++ .../instancereservedip/framework_resource.go | 303 ++++++++++++++++++ linode/instancereservedip/framework_schema.go | 110 +++++++ linode/instancereservedip/resource_test.go | 58 ++++ .../tmpl/AddReservedIPToInstance.gotf | 2 +- linode/instancereservedip/tmpl/template.go | 23 ++ 11 files changed, 620 insertions(+), 70 deletions(-) create mode 100644 linode/instancereservedip/framework_models.go create mode 100644 linode/instancereservedip/framework_resource.go create mode 100644 linode/instancereservedip/framework_schema.go create mode 100644 linode/instancereservedip/resource_test.go rename linode/{instanceip => instancereservedip}/tmpl/AddReservedIPToInstance.gotf (86%) create mode 100644 linode/instancereservedip/tmpl/template.go diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 7dc261184..7c0a502ec 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -34,6 +34,7 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/instancedisk" "github.com/linode/terraform-provider-linode/v2/linode/instanceip" "github.com/linode/terraform-provider-linode/v2/linode/instancenetworking" + "github.com/linode/terraform-provider-linode/v2/linode/instancereservedip" "github.com/linode/terraform-provider-linode/v2/linode/instancesharedips" "github.com/linode/terraform-provider-linode/v2/linode/instancetype" "github.com/linode/terraform-provider-linode/v2/linode/instancetypes" @@ -225,6 +226,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res firewall.NewResource, placementgroup.NewResource, placementgroupassignment.NewResource, + instancereservedip.NewResource, } } diff --git a/linode/instanceip/framework_resource.go b/linode/instanceip/framework_resource.go index 24b70ab38..2ad1fed5a 100644 --- a/linode/instanceip/framework_resource.go +++ b/linode/instanceip/framework_resource.go @@ -54,34 +54,13 @@ func (r *Resource) Create( isPublic := plan.Public.ValueBool() client := r.Meta.Client - var ip *linodego.InstanceIP - var err error - - if !plan.Address.IsNull() && !plan.Address.IsUnknown() { - // Assign a reserved IP - createOpts := linodego.InstanceReserveIPOptions{ - Type: "ipv4", - Public: isPublic, - Address: plan.Address.ValueString(), - } - _, err = client.AssignInstanceReservedIP(ctx, linodeID, createOpts) - if err != nil { - resp.Diagnostics.AddError( - fmt.Sprintf("Failed to assign reserved IP to instance (%d)", linodeID), - err.Error(), - ) - return - } - } else { - // Allocate a new IP - ip, err = client.AddInstanceIPAddress(ctx, linodeID, isPublic) - if err != nil { - resp.Diagnostics.AddError( - fmt.Sprintf("Failed to allocate new IP for instance (%d)", linodeID), - err.Error(), - ) - return - } + ip, err := client.AddInstanceIPAddress(ctx, linodeID, isPublic) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to created instance (%d) IP", linodeID), + err.Error(), + ) + return } if !plan.RDNS.IsNull() && !plan.RDNS.IsUnknown() { @@ -107,6 +86,11 @@ func (r *Resource) Create( } } + if ip == nil { + resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the instance ip") + return + } + resp.Diagnostics.Append(plan.FlattenInstanceIP(ctx, *ip, true)...) if resp.Diagnostics.HasError() { return diff --git a/linode/instanceip/framework_schema.go b/linode/instanceip/framework_schema.go index f29d624ea..93a0ce8b5 100644 --- a/linode/instanceip/framework_schema.go +++ b/linode/instanceip/framework_schema.go @@ -41,7 +41,6 @@ var frameworkResourceSchema = schema.Schema{ "address": schema.StringAttribute{ Description: "The resulting IPv4 address.", - Optional: true, Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), diff --git a/linode/instanceip/resource_test.go b/linode/instanceip/resource_test.go index 2ce6185f0..5019e306a 100644 --- a/linode/instanceip/resource_test.go +++ b/linode/instanceip/resource_test.go @@ -127,34 +127,3 @@ func TestAccInstanceIP_noApply(t *testing.T) { }, }) } - -func TestAccInstanceIP_addReservedIP(t *testing.T) { - t.Parallel() - - var instance linodego.Instance - name := acctest.RandomWithPrefix("tf_test") - reservedIP := "50.116.51.242" // Replace with your actual reserved IP address - testRegion = "us-east" - resource.Test(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, - CheckDestroy: acceptance.CheckInstanceDestroy, - Steps: []resource.TestStep{ - { - Config: tmpl.AddReservedIP(t, name, testRegion, reservedIP), - Check: resource.ComposeTestCheckFunc( - acceptance.CheckInstanceExists("linode_instance.foobar", &instance), - resource.TestCheckResourceAttr(testInstanceIPResName, "address", reservedIP), - resource.TestCheckResourceAttr(testInstanceIPResName, "public", "true"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "linode_id"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "gateway"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "subnet_mask"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "prefix"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "rdns"), - resource.TestCheckResourceAttr(testInstanceIPResName, "region", testRegion), - resource.TestCheckResourceAttr(testInstanceIPResName, "type", "ipv4"), - ), - }, - }, - }) -} diff --git a/linode/instanceip/tmpl/template.go b/linode/instanceip/tmpl/template.go index b1f2d00cb..463a50bac 100644 --- a/linode/instanceip/tmpl/template.go +++ b/linode/instanceip/tmpl/template.go @@ -30,12 +30,3 @@ func NoBoot(t *testing.T, instanceLabel, region string, applyImmediately bool) s Region: region, }) } - -func AddReservedIP(t *testing.T, instanceLabel, region string, address string) string { - return acceptance.ExecuteTemplate(t, - "instance_ip_add_reservedIP", TemplateData{ - Label: instanceLabel, - Region: region, - Address: address, - }) -} diff --git a/linode/instancereservedip/framework_models.go b/linode/instancereservedip/framework_models.go new file mode 100644 index 000000000..92d8570d0 --- /dev/null +++ b/linode/instancereservedip/framework_models.go @@ -0,0 +1,111 @@ +package instancereservedip + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "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" + "github.com/linode/terraform-provider-linode/v2/linode/instancenetworking" +) + +type IPVPCNAT1To1Model struct { + Address types.String `tfsdk:"address"` + SubnetID types.Int64 `tfsdk:"subnet_id"` + VPCID types.Int64 `tfsdk:"vpc_id"` +} + +type InstanceIPModel struct { + ID types.String `tfsdk:"id"` + LinodeID types.Int64 `tfsdk:"linode_id"` + Public types.Bool `tfsdk:"public"` + Address types.String `tfsdk:"address"` + Gateway types.String `tfsdk:"gateway"` + Prefix types.Int64 `tfsdk:"prefix"` + RDNS types.String `tfsdk:"rdns"` + Region types.String `tfsdk:"region"` + SubnetMask types.String `tfsdk:"subnet_mask"` + Type types.String `tfsdk:"type"` + ApplyImmediately types.Bool `tfsdk:"apply_immediately"` + IPVPCNAT1To1 types.List `tfsdk:"vpc_nat_1_1"` +} + +func (m *InstanceIPModel) FlattenInstanceIP( + ctx context.Context, + ip linodego.InstanceIP, + preserveKnown bool, +) diag.Diagnostics { + var diags diag.Diagnostics + + m.ID = helper.KeepOrUpdateString(m.ID, ip.Address, preserveKnown) + m.LinodeID = helper.KeepOrUpdateInt64(m.LinodeID, int64(ip.LinodeID), preserveKnown) + m.Public = helper.KeepOrUpdateBool(m.Public, ip.Public, preserveKnown) + m.Address = helper.KeepOrUpdateString(m.Address, ip.Address, preserveKnown) + m.Gateway = helper.KeepOrUpdateString(m.Gateway, ip.Gateway, preserveKnown) + m.Prefix = helper.KeepOrUpdateInt64(m.Prefix, int64(ip.Prefix), preserveKnown) + + m.RDNS = helper.KeepOrUpdateString(m.RDNS, ip.RDNS, preserveKnown) + + m.Region = helper.KeepOrUpdateString(m.Region, ip.Region, preserveKnown) + m.SubnetMask = helper.KeepOrUpdateString(m.SubnetMask, ip.SubnetMask, preserveKnown) + m.Type = helper.KeepOrUpdateString(m.Type, string(ip.Type), preserveKnown) + + var resultList types.List + if ip.VPCNAT1To1 == nil { + resultList = types.ListNull(instancenetworking.VPCNAT1To1Type) + } else { + vpcNAT1To1, diags := instancenetworking.FlattenIPVPCNAT1To1(ip.VPCNAT1To1) + diags.Append(diags...) + if diags.HasError() { + return diags + } + + var listDiags diag.Diagnostics + resultList, listDiags = types.ListValue( + instancenetworking.VPCNAT1To1Type, + []attr.Value{vpcNAT1To1}, + ) + diags.Append(listDiags...) + if diags.HasError() { + return diags + } + } + m.IPVPCNAT1To1 = helper.KeepOrUpdateValue( + m.IPVPCNAT1To1, + resultList, + preserveKnown, + ) + + return diags +} + +func (m *InstanceIPModel) CopyFrom( + ctx context.Context, + other InstanceIPModel, + preserveKnown bool, +) diag.Diagnostics { + var diags diag.Diagnostics + + m.ID = helper.KeepOrUpdateValue(m.ID, other.Address, preserveKnown) + m.LinodeID = helper.KeepOrUpdateValue(m.LinodeID, other.LinodeID, preserveKnown) + m.Public = helper.KeepOrUpdateValue(m.Public, other.Public, preserveKnown) + m.Address = helper.KeepOrUpdateValue(m.Address, other.Address, preserveKnown) + m.Gateway = helper.KeepOrUpdateValue(m.Gateway, other.Gateway, preserveKnown) + m.Prefix = helper.KeepOrUpdateValue(m.Prefix, other.Prefix, preserveKnown) + + m.RDNS = helper.KeepOrUpdateValue(m.RDNS, other.RDNS, preserveKnown) + + m.Region = helper.KeepOrUpdateValue(m.Region, other.Region, preserveKnown) + m.SubnetMask = helper.KeepOrUpdateValue(m.SubnetMask, other.SubnetMask, preserveKnown) + m.Type = helper.KeepOrUpdateValue(m.Type, other.Type, preserveKnown) + m.IPVPCNAT1To1 = helper.KeepOrUpdateValue( + m.IPVPCNAT1To1, + other.IPVPCNAT1To1, + preserveKnown, + ) + + return diags +} diff --git a/linode/instancereservedip/framework_resource.go b/linode/instancereservedip/framework_resource.go new file mode 100644 index 000000000..88a916fcb --- /dev/null +++ b/linode/instancereservedip/framework_resource.go @@ -0,0 +1,303 @@ +package instancereservedip + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "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 NewResource() resource.Resource { + return &Resource{ + BaseResource: helper.NewBaseResource( + helper.BaseResourceConfig{ + Name: "linode_instance_reserved_ip", + IDType: types.StringType, + Schema: &frameworkResourceSchema, + }, + ), + } +} + +type Resource struct { + helper.BaseResource +} + +func (r *Resource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + tflog.Debug(ctx, "Create linode_instance_ip") + var plan InstanceIPModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + ctx = populateLogAttributes(ctx, &plan) + + linodeID := helper.FrameworkSafeInt64ToInt( + plan.LinodeID.ValueInt64(), + &resp.Diagnostics, + ) + if resp.Diagnostics.HasError() { + return + } + + isPublic := plan.Public.ValueBool() + + client := r.Meta.Client + var ip *linodego.InstanceIP + var err error + + if !plan.Address.IsNull() && !plan.Address.IsUnknown() { + // Assign a reserved IP + createOpts := linodego.InstanceReserveIPOptions{ + Type: "ipv4", + Public: isPublic, + Address: plan.Address.ValueString(), + } + ip, err = client.AssignInstanceReservedIP(ctx, linodeID, createOpts) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to assign reserved IP to instance (%d)", linodeID), + err.Error(), + ) + return + } + } + + if !plan.RDNS.IsNull() && !plan.RDNS.IsUnknown() { + rdns := plan.RDNS.ValueString() + + options := linodego.IPAddressUpdateOptions{ + RDNS: &rdns, + } + + tflog.Debug(ctx, "client.UpdateIPAddress(...)", map[string]any{ + "options": options, + }) + + if _, err := client.UpdateIPAddress(ctx, ip.Address, options); err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf( + "failed to set RDNS for instance (%d) ip (%s)", + linodeID, ip.Address, + ), + err.Error(), + ) + return + } + } + + resp.Diagnostics.Append(plan.FlattenInstanceIP(ctx, *ip, true)...) + if resp.Diagnostics.HasError() { + return + } + + // IDs should always be overridden during creation (see #1085) + // TODO: Remove when Crossplane empty string ID issue is resolved + plan.ID = types.StringValue(ip.Address) + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if plan.ApplyImmediately.ValueBool() { + tflog.Debug(ctx, "Attempting apply_immediately") + + instance, err := client.GetInstance(ctx, linodeID) + if err != nil { + resp.Diagnostics.AddError("Failed to Get Linode Instance", err.Error()) + return + } + + if instance.Status == linodego.InstanceRunning { + tflog.Info(ctx, "detected instance in running status, rebooting instance") + ctx, cancel := context.WithTimeout(ctx, time.Duration(600)*time.Second) + resp.Diagnostics.Append(helper.FrameworkRebootInstance(ctx, linodeID, client, 0)...) + cancel() + } else { + tflog.Info(ctx, "Detected instance not in running status, can't perform a reboot.") + } + } +} + +func (r *Resource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + tflog.Debug(ctx, "Read linode_instance_ip") + var state InstanceIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if helper.FrameworkAttemptRemoveResourceForEmptyID(ctx, state.ID, resp) { + return + } + + ctx = populateLogAttributes(ctx, &state) + + client := r.Meta.Client + address := state.Address.ValueString() + linodeID := helper.FrameworkSafeInt64ToInt( + state.LinodeID.ValueInt64(), + &resp.Diagnostics, + ) + if resp.Diagnostics.HasError() { + return + } + + ip, err := client.GetInstanceIPAddress(ctx, linodeID, address) + if err != nil { + if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { + resp.Diagnostics.AddWarning( + "Instance IP No Longer Exists", + fmt.Sprintf( + "Removing instance IP %s from state because it no longer exists", + state.ID.ValueString(), + ), + ) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + "Unable to Refresh the Instance IP", + fmt.Sprintf( + "Error finding the specified Instance IP: %s", + err.Error(), + ), + ) + return + } + + if ip == nil { + resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the instance ip") + return + } + + resp.Diagnostics.Append(state.FlattenInstanceIP(ctx, *ip, false)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *Resource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan InstanceIPModel + var state InstanceIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + + if resp.Diagnostics.HasError() { + return + } + + if !plan.RDNS.Equal(state.RDNS) { + rdns := plan.RDNS.ValueStringPointer() + updateOptions := linodego.IPAddressUpdateOptions{ + RDNS: rdns, + } + + client := r.Meta.Client + address := plan.Address.ValueString() + linodeID := plan.LinodeID.ValueInt64() + + tflog.Debug(ctx, "client.UpdateIPAddress(...)", map[string]any{ + "options": updateOptions, + }) + + ip, err := client.UpdateIPAddress(ctx, address, updateOptions) + if err != nil { + resp.Diagnostics.AddError( + "Failed to Update RDNS", + fmt.Sprintf( + "failed to update RDNS for instance (%d) ip (%s): %s", + linodeID, address, err, + ), + ) + return + } + if ip == nil { + resp.Diagnostics.AddError( + "Failed to Get Updated IP", + fmt.Sprintf( + "ip is a nil pointer after update operation for instance (%d) ip (%s): %s", + linodeID, address, err, + ), + ) + return + } + resp.Diagnostics.Append(plan.FlattenInstanceIP(ctx, *ip, true)...) + if resp.Diagnostics.HasError() { + return + } + } + plan.CopyFrom(ctx, state, true) + + // Workaround for Crossplane issue where ID is not + // properly populated in plan + // See TPT-2865 for more details + if plan.ID.ValueString() == "" { + plan.ID = state.ID + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *Resource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state InstanceIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + address := state.Address.ValueString() + linodeID := helper.FrameworkSafeInt64ToInt( + state.LinodeID.ValueInt64(), + &resp.Diagnostics, + ) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "client.DeleteInstanceIPAddress(...)") + if err := client.DeleteInstanceIPAddress(ctx, linodeID, address); err != nil { + if lErr, ok := err.(*linodego.Error); (ok && lErr.Code != 404) || !ok { + resp.Diagnostics.AddError( + "Failed to Delete IP", + fmt.Sprintf( + "failed to delete instance (%d) ip (%s): %s", + linodeID, address, err.Error(), + ), + ) + } + } +} + +func populateLogAttributes(ctx context.Context, data *InstanceIPModel) context.Context { + return helper.SetLogFieldBulk(ctx, map[string]any{ + "linode_id": data.LinodeID.ValueInt64(), + "address": data.ID.ValueString(), + }) +} diff --git a/linode/instancereservedip/framework_schema.go b/linode/instancereservedip/framework_schema.go new file mode 100644 index 000000000..362aa1555 --- /dev/null +++ b/linode/instancereservedip/framework_schema.go @@ -0,0 +1,110 @@ +package instancereservedip + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/linode/terraform-provider-linode/v2/linode/instancenetworking" +) + +var frameworkResourceSchema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the IPv4 address, which will be IPv4 address itself.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "linode_id": schema.Int64Attribute{ + Description: "The ID of the Linode to allocate an IPv4 address for.", + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "public": schema.BoolAttribute{ + Description: "Whether the IPv4 address is public or private.", + Default: booldefault.StaticBool(true), + Computed: true, + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + + "address": schema.StringAttribute{ + Description: "The resulting IPv4 address.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "gateway": schema.StringAttribute{ + Description: "The default gateway for this address", + Computed: true, + }, + "prefix": schema.Int64Attribute{ + Description: "The number of bits set in the subnet mask.", + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "rdns": schema.StringAttribute{ + Description: "The reverse DNS assigned to this address.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "region": schema.StringAttribute{ + Description: "The region this IP resides in.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "subnet_mask": schema.StringAttribute{ + Description: "The mask that separates host bits from network bits for this address.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "type": schema.StringAttribute{ + Description: "The type of IP address.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "vpc_nat_1_1": schema.ListAttribute{ + Description: "Contains information about the NAT 1:1 mapping of a public IP address to a VPC subnet.", + Computed: true, + ElementType: instancenetworking.VPCNAT1To1Type, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + + "apply_immediately": schema.BoolAttribute{ + Description: "If true, the instance will be rebooted to update network interfaces. " + + "This functionality is not affected by the `skip_implicit_reboots` provider argument.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + }, +} diff --git a/linode/instancereservedip/resource_test.go b/linode/instancereservedip/resource_test.go new file mode 100644 index 000000000..3f8a9488f --- /dev/null +++ b/linode/instancereservedip/resource_test.go @@ -0,0 +1,58 @@ +//go:build integration || instancereservedip + +package instancereservedip_test + +import ( + "log" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/linode/linodego" + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" + "github.com/linode/terraform-provider-linode/v2/linode/instancereservedip/tmpl" +) + +const testInstanceIPResName = "linode_instance_ip.test" + +var testRegion string + +func init() { + region, err := acceptance.GetRandomRegionWithCaps(nil, "core") + if err != nil { + log.Fatal(err) + } + + testRegion = region +} + +func TestAccInstanceIP_addReservedIP(t *testing.T) { + t.Parallel() + + var instance linodego.Instance + name := acctest.RandomWithPrefix("tf_test") + reservedIP := "50.116.51.242" // Replace with your actual reserved IP address + testRegion = "us-east" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.AddReservedIP(t, name, testRegion, reservedIP), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists("linode_instance.foobar", &instance), + resource.TestCheckResourceAttr(testInstanceIPResName, "address", reservedIP), + resource.TestCheckResourceAttr(testInstanceIPResName, "public", "true"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "linode_id"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "gateway"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "subnet_mask"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "prefix"), + resource.TestCheckResourceAttrSet(testInstanceIPResName, "rdns"), + resource.TestCheckResourceAttr(testInstanceIPResName, "region", testRegion), + resource.TestCheckResourceAttr(testInstanceIPResName, "type", "ipv4"), + ), + }, + }, + }) +} diff --git a/linode/instanceip/tmpl/AddReservedIPToInstance.gotf b/linode/instancereservedip/tmpl/AddReservedIPToInstance.gotf similarity index 86% rename from linode/instanceip/tmpl/AddReservedIPToInstance.gotf rename to linode/instancereservedip/tmpl/AddReservedIPToInstance.gotf index 617afb23d..02496633e 100644 --- a/linode/instanceip/tmpl/AddReservedIPToInstance.gotf +++ b/linode/instancereservedip/tmpl/AddReservedIPToInstance.gotf @@ -8,7 +8,7 @@ resource "linode_instance" "foobar" { image = "linode/debian12" } -resource "linode_instance_ip" "test" { +resource "linode_instance_reserved_ip" "test" { linode_id = linode_instance.foobar.id public = true address = "{{ .Address}}" diff --git a/linode/instancereservedip/tmpl/template.go b/linode/instancereservedip/tmpl/template.go new file mode 100644 index 000000000..2746e7d05 --- /dev/null +++ b/linode/instancereservedip/tmpl/template.go @@ -0,0 +1,23 @@ +package tmpl + +import ( + "testing" + + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" +) + +type TemplateData struct { + Label string + ApplyImmediately bool + Region string + Address string +} + +func AddReservedIP(t *testing.T, instanceLabel, region string, address string) string { + return acceptance.ExecuteTemplate(t, + "instance_ip_add_reservedIP", TemplateData{ + Label: instanceLabel, + Region: region, + Address: address, + }) +} From da79151ef8a11539669cb5c7c2f7d3a710b0ed05 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 24 Oct 2024 15:12:24 -0400 Subject: [PATCH 23/49] Made changes to add reserved field in the IP object of linode_instance_reserved_ip resource --- linode/instanceip/framework_models.go | 4 +--- linode/instanceip/framework_schema.go | 5 ----- linode/instancereservedip/framework_models.go | 3 +++ linode/instancereservedip/framework_schema.go | 4 ++++ 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/linode/instanceip/framework_models.go b/linode/instanceip/framework_models.go index b5fa2174f..21a527751 100644 --- a/linode/instanceip/framework_models.go +++ b/linode/instanceip/framework_models.go @@ -31,7 +31,6 @@ type InstanceIPModel struct { Type types.String `tfsdk:"type"` ApplyImmediately types.Bool `tfsdk:"apply_immediately"` IPVPCNAT1To1 types.List `tfsdk:"vpc_nat_1_1"` - Reserved types.Bool `tfsdk:"reserved"` } func (m *InstanceIPModel) FlattenInstanceIP( @@ -49,7 +48,6 @@ func (m *InstanceIPModel) FlattenInstanceIP( m.Prefix = helper.KeepOrUpdateInt64(m.Prefix, int64(ip.Prefix), preserveKnown) m.RDNS = helper.KeepOrUpdateString(m.RDNS, ip.RDNS, preserveKnown) - m.Reserved = helper.KeepOrUpdateBool(m.Reserved, ip.Reserved, preserveKnown) m.Region = helper.KeepOrUpdateString(m.Region, ip.Region, preserveKnown) m.SubnetMask = helper.KeepOrUpdateString(m.SubnetMask, ip.SubnetMask, preserveKnown) @@ -99,7 +97,7 @@ func (m *InstanceIPModel) CopyFrom( m.Prefix = helper.KeepOrUpdateValue(m.Prefix, other.Prefix, preserveKnown) m.RDNS = helper.KeepOrUpdateValue(m.RDNS, other.RDNS, preserveKnown) - m.Reserved = helper.KeepOrUpdateValue(m.Reserved, other.Reserved, preserveKnown) + m.Region = helper.KeepOrUpdateValue(m.Region, other.Region, preserveKnown) m.SubnetMask = helper.KeepOrUpdateValue(m.SubnetMask, other.SubnetMask, preserveKnown) m.Type = helper.KeepOrUpdateValue(m.Type, other.Type, preserveKnown) diff --git a/linode/instanceip/framework_schema.go b/linode/instanceip/framework_schema.go index 793718c81..93a0ce8b5 100644 --- a/linode/instanceip/framework_schema.go +++ b/linode/instanceip/framework_schema.go @@ -105,10 +105,5 @@ var frameworkResourceSchema = schema.Schema{ Computed: true, Default: booldefault.StaticBool(false), }, - - "reserved": schema.BoolAttribute{ - Description: "The reservation status of the IP address", - Computed: true, - }, }, } diff --git a/linode/instancereservedip/framework_models.go b/linode/instancereservedip/framework_models.go index 92d8570d0..cce7479f6 100644 --- a/linode/instancereservedip/framework_models.go +++ b/linode/instancereservedip/framework_models.go @@ -31,6 +31,7 @@ type InstanceIPModel struct { Type types.String `tfsdk:"type"` ApplyImmediately types.Bool `tfsdk:"apply_immediately"` IPVPCNAT1To1 types.List `tfsdk:"vpc_nat_1_1"` + Reserved types.Bool `tfsdk:"reserved"` } func (m *InstanceIPModel) FlattenInstanceIP( @@ -48,6 +49,7 @@ func (m *InstanceIPModel) FlattenInstanceIP( m.Prefix = helper.KeepOrUpdateInt64(m.Prefix, int64(ip.Prefix), preserveKnown) m.RDNS = helper.KeepOrUpdateString(m.RDNS, ip.RDNS, preserveKnown) + m.Reserved = helper.KeepOrUpdateBool(m.Reserved, ip.Reserved, preserveKnown) m.Region = helper.KeepOrUpdateString(m.Region, ip.Region, preserveKnown) m.SubnetMask = helper.KeepOrUpdateString(m.SubnetMask, ip.SubnetMask, preserveKnown) @@ -97,6 +99,7 @@ func (m *InstanceIPModel) CopyFrom( m.Prefix = helper.KeepOrUpdateValue(m.Prefix, other.Prefix, preserveKnown) m.RDNS = helper.KeepOrUpdateValue(m.RDNS, other.RDNS, preserveKnown) + m.Reserved = helper.KeepOrUpdateValue(m.Reserved, other.Reserved, preserveKnown) m.Region = helper.KeepOrUpdateValue(m.Region, other.Region, preserveKnown) m.SubnetMask = helper.KeepOrUpdateValue(m.SubnetMask, other.SubnetMask, preserveKnown) diff --git a/linode/instancereservedip/framework_schema.go b/linode/instancereservedip/framework_schema.go index 362aa1555..c552bd4d1 100644 --- a/linode/instancereservedip/framework_schema.go +++ b/linode/instancereservedip/framework_schema.go @@ -106,5 +106,9 @@ var frameworkResourceSchema = schema.Schema{ Computed: true, Default: booldefault.StaticBool(false), }, + "reserved": schema.BoolAttribute{ + Description: "The reservation status of the IP address", + Computed: true, + }, }, } From e1c17cb4a0db17c7c2e6da8c8f9e97a2fea06099 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 24 Oct 2024 16:22:02 -0400 Subject: [PATCH 24/49] changed the resource name to linode_instance_reserved_ip.test in the resource_test file --- linode/instancereservedip/resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linode/instancereservedip/resource_test.go b/linode/instancereservedip/resource_test.go index 3f8a9488f..7efcc89aa 100644 --- a/linode/instancereservedip/resource_test.go +++ b/linode/instancereservedip/resource_test.go @@ -13,7 +13,7 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/instancereservedip/tmpl" ) -const testInstanceIPResName = "linode_instance_ip.test" +const testInstanceIPResName = "linode_instance_reserved_ip.test" var testRegion string From 8d6cb69a515049f27d69f2ef4db10bde6c7069d2 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Fri, 25 Oct 2024 12:39:39 -0400 Subject: [PATCH 25/49] added test for checking reserved status after deletion --- linode/instance/resource_test.go | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/linode/instance/resource_test.go b/linode/instance/resource_test.go index 1067ce07a..4ddc614f9 100644 --- a/linode/instance/resource_test.go +++ b/linode/instance/resource_test.go @@ -2927,3 +2927,54 @@ func TestAccResourceInstance_withReservedIP(t *testing.T) { }, }) } + +func TestAccResourceInstance_deleteWithReservedIP(t *testing.T) { + t.Parallel() + var instance linodego.Instance + resourceName := "linode_instance.foobar" + testRegion := "us-east" + instanceName := acctest.RandomWithPrefix("tf_test") + rootPass := acctest.RandString(16) + reservedIP := "172.104.17.36" // Use a test IP or fetch a real reserved IP + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.WithReservedIP(t, instanceName, acceptance.PublicKeyMaterial, testRegion, rootPass, reservedIP), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "label", instanceName), + resource.TestCheckResourceAttr(resourceName, "ipv4.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ipv4.0", reservedIP), + ), + }, + { + Config: " ", // Empty config to trigger instance deletion + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + client := acceptance.TestAccProvider.Meta().(*helper.ProviderMeta).Client + // Check if the instance is deleted + _, err := client.GetInstance(context.Background(), instance.ID) + if err == nil { + return fmt.Errorf("Linode instance %d still exists", instance.ID) + } + if apiErr, ok := err.(*linodego.Error); ok && apiErr.Code != 404 { + return fmt.Errorf("Error requesting Linode instance %d: %s", instance.ID, err) + } + // Check if the Reserved IP still exists and is reserved + ip, err := client.GetIPAddress(context.Background(), reservedIP) + if err != nil { + return fmt.Errorf("Error checking if Reserved IP still exists: %s", err) + } + if !ip.Reserved { + return fmt.Errorf("Reserved IP %s is no longer reserved after instance deletion", reservedIP) + } + return nil + }, + ), + }, + }, + }) +} From e986a316fb38fa49191243cc131c03ab6573f633 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 28 Oct 2024 07:08:11 -0400 Subject: [PATCH 26/49] Created datasource for listing IP instance IP address --- linode/framework_provider.go | 2 + linode/instanceips/datasource_test.go | 57 ++++++++ linode/instanceips/framework_datasource.go | 129 ++++++++++++++++++ .../instanceips/framework_datasource_model.go | 11 ++ .../framework_datasource_schema.go | 36 +++++ linode/instanceips/tmpl/basic.gotf | 15 ++ linode/instanceips/tmpl/template.go | 20 +++ 7 files changed, 270 insertions(+) create mode 100644 linode/instanceips/datasource_test.go create mode 100644 linode/instanceips/framework_datasource.go create mode 100644 linode/instanceips/framework_datasource_model.go create mode 100644 linode/instanceips/framework_datasource_schema.go create mode 100644 linode/instanceips/tmpl/basic.gotf create mode 100644 linode/instanceips/tmpl/template.go diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 89ac94672..32022c098 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -33,6 +33,7 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/images" "github.com/linode/terraform-provider-linode/v2/linode/instancedisk" "github.com/linode/terraform-provider-linode/v2/linode/instanceip" + "github.com/linode/terraform-provider-linode/v2/linode/instanceips" "github.com/linode/terraform-provider-linode/v2/linode/instancenetworking" "github.com/linode/terraform-provider-linode/v2/linode/instancereservedip" "github.com/linode/terraform-provider-linode/v2/linode/instancesharedips" @@ -300,5 +301,6 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource childaccounts.NewDataSource, networkreservedip.NewDataSourceFetch, networkreservedips.NewDataSourceList, + instanceips.NewDataSource, } } diff --git a/linode/instanceips/datasource_test.go b/linode/instanceips/datasource_test.go new file mode 100644 index 000000000..02dce9372 --- /dev/null +++ b/linode/instanceips/datasource_test.go @@ -0,0 +1,57 @@ +//go:build integration || instanceips + +package instanceips_test + +import ( + "log" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/linode/linodego" + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" + "github.com/linode/terraform-provider-linode/v2/linode/instanceips/tmpl" +) + +const testInstanceIPsDataName = "data.linode_instance_ips.test" + +var testRegion string + +func init() { + region, err := acceptance.GetRandomRegionWithCaps(nil, "core") + if err != nil { + log.Fatal(err) + } + + testRegion = region +} + +func TestAccDataSourceInstanceIPs_basic(t *testing.T) { + t.Parallel() + + var instance linodego.Instance + + name := acctest.RandomWithPrefix("tf_test") + resourceName := "linode_instance.foobar" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.Basic(t, name, testRegion), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "id"), + resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "ipv4.public.0"), + resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "ipv4.private.#"), + resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "ipv6.slaac"), + resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "ipv6.link_local"), + resource.TestCheckResourceAttr(testInstanceIPsDataName, "ipv6.global.#", "0"), + resource.TestCheckResourceAttrPair(testInstanceIPsDataName, "id", resourceName, "id"), + ), + }, + }, + }) +} diff --git a/linode/instanceips/framework_datasource.go b/linode/instanceips/framework_datasource.go new file mode 100644 index 000000000..dea2a1dc5 --- /dev/null +++ b/linode/instanceips/framework_datasource.go @@ -0,0 +1,129 @@ +package instanceips + +import ( + "context" + "fmt" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types" + "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_instance_ips", + Schema: &frameworkDataSourceSchema, + }, + ), + } +} + +type DataSource struct { + helper.BaseDataSource +} + +func (d *DataSource) Read( + ctx context.Context, + req datasource.ReadRequest, + resp *datasource.ReadResponse, +) { + var data InstanceIPDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + client := d.Meta.Client + linodeID, err := strconv.Atoi(data.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Invalid Linode ID", err.Error()) + return + } + + ips, err := client.GetInstanceIPAddresses(ctx, linodeID) + if err != nil { + resp.Diagnostics.AddError( + "Failed to get instance IP addresses", + fmt.Sprintf("Error getting IP addresses for Linode %d: %s", linodeID, err), + ) + return + } + + // Populate IPv4 addresses + ipv4Value, diags := types.ObjectValue( + map[string]attr.Type{ + "public": types.ListType{ElemType: types.StringType}, + "private": types.ListType{ElemType: types.StringType}, + "shared": types.ListType{ElemType: types.StringType}, + "reserved": types.ListType{ElemType: types.StringType}, + "vpc": types.ListType{ElemType: types.StringType}, + }, + map[string]attr.Value{ + "public": types.ListValueMust(types.StringType, getIPAddresses(ips.IPv4.Public)), + "private": types.ListValueMust(types.StringType, getIPAddresses(ips.IPv4.Private)), + "shared": types.ListValueMust(types.StringType, getIPAddresses(ips.IPv4.Shared)), + "reserved": types.ListValueMust(types.StringType, getIPAddresses(ips.IPv4.Reserved)), + "vpc": types.ListValueMust(types.StringType, getVPCIPAddresses(ips.IPv4.VPC)), + }, + ) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + data.IPv4 = ipv4Value + + // Populate IPv6 addresses + ipv6Value, diags := types.ObjectValue( + map[string]attr.Type{ + "link_local": types.StringType, + "slaac": types.StringType, + "global": types.ListType{ElemType: types.StringType}, + }, + map[string]attr.Value{ + "link_local": types.StringValue(ips.IPv6.LinkLocal.Address), + "slaac": types.StringValue(ips.IPv6.SLAAC.Address), + "global": types.ListValueMust(types.StringType, getIPv6Ranges(ips.IPv6.Global)), + }, + ) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + data.IPv6 = ipv6Value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func getIPAddresses(ips []*linodego.InstanceIP) []attr.Value { + addresses := make([]attr.Value, len(ips)) + for i, ip := range ips { + addresses[i] = types.StringValue(ip.Address) + } + return addresses +} + +func getVPCIPAddresses(ips []*linodego.VPCIP) []attr.Value { + addresses := make([]attr.Value, len(ips)) + for i, ip := range ips { + if ip.Address != nil { + addresses[i] = types.StringValue(*ip.Address) + } else { + addresses[i] = types.StringNull() + } + } + return addresses +} + +func getIPv6Ranges(ranges []linodego.IPv6Range) []attr.Value { + result := make([]attr.Value, len(ranges)) + for i, r := range ranges { + result[i] = types.StringValue(r.Range) + } + return result +} diff --git a/linode/instanceips/framework_datasource_model.go b/linode/instanceips/framework_datasource_model.go new file mode 100644 index 000000000..acc8a41a9 --- /dev/null +++ b/linode/instanceips/framework_datasource_model.go @@ -0,0 +1,11 @@ +package instanceips + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type InstanceIPDataSourceModel struct { + ID types.String `tfsdk:"id"` + IPv4 types.Object `tfsdk:"ipv4"` + IPv6 types.Object `tfsdk:"ipv6"` +} diff --git a/linode/instanceips/framework_datasource_schema.go b/linode/instanceips/framework_datasource_schema.go new file mode 100644 index 000000000..b6ac8cb5b --- /dev/null +++ b/linode/instanceips/framework_datasource_schema.go @@ -0,0 +1,36 @@ +package instanceips + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var frameworkDataSourceSchema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the Linode instance.", + Required: true, + }, + "ipv4": schema.ObjectAttribute{ + Computed: true, + AttributeTypes: map[string]attr.Type{ + "public": types.ListType{ElemType: types.StringType}, + "private": types.ListType{ElemType: types.StringType}, + "shared": types.ListType{ElemType: types.StringType}, + "reserved": types.ListType{ElemType: types.StringType}, + "vpc": types.ListType{ElemType: types.StringType}, + }, + Description: "The IPv4 addresses of the instance.", + }, + "ipv6": schema.ObjectAttribute{ + Computed: true, + AttributeTypes: map[string]attr.Type{ + "link_local": types.StringType, + "slaac": types.StringType, + "global": types.ListType{ElemType: types.StringType}, + }, + Description: "The IPv6 addresses of the instance.", + }, + }, +} diff --git a/linode/instanceips/tmpl/basic.gotf b/linode/instanceips/tmpl/basic.gotf new file mode 100644 index 000000000..f1a0e29b3 --- /dev/null +++ b/linode/instanceips/tmpl/basic.gotf @@ -0,0 +1,15 @@ +{{ define "instance_ips_basic" }} + +resource "linode_instance" "foobar" { + label = "{{.Label}}" + group = "tf_test" + type = "g6-nanode-1" + region = "{{ .Region }}" + image = "linode/debian12" +} + +data "linode_instance_ips" "test" { + id = linode_instance.foobar.id +} + +{{ end }} \ No newline at end of file diff --git a/linode/instanceips/tmpl/template.go b/linode/instanceips/tmpl/template.go new file mode 100644 index 000000000..bc8e55bfc --- /dev/null +++ b/linode/instanceips/tmpl/template.go @@ -0,0 +1,20 @@ +package tmpl + +import ( + "testing" + + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" +) + +type TemplateData struct { + Label string + Region string +} + +func Basic(t *testing.T, instanceLabel, region string) string { + return acceptance.ExecuteTemplate(t, + "instance_ips_basic", TemplateData{ + Label: instanceLabel, + Region: region, + }) +} From 9369bd783f4c1464cb89a41b73086231c931f1b0 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 28 Oct 2024 10:51:22 -0400 Subject: [PATCH 27/49] Removing the linode_instance_ips data source since it is already implemented in instancenetworking --- linode/instanceips/datasource_test.go | 57 -------- linode/instanceips/framework_datasource.go | 129 ------------------ .../instanceips/framework_datasource_model.go | 11 -- .../framework_datasource_schema.go | 36 ----- linode/instanceips/tmpl/basic.gotf | 15 -- linode/instanceips/tmpl/template.go | 20 --- 6 files changed, 268 deletions(-) delete mode 100644 linode/instanceips/datasource_test.go delete mode 100644 linode/instanceips/framework_datasource.go delete mode 100644 linode/instanceips/framework_datasource_model.go delete mode 100644 linode/instanceips/framework_datasource_schema.go delete mode 100644 linode/instanceips/tmpl/basic.gotf delete mode 100644 linode/instanceips/tmpl/template.go diff --git a/linode/instanceips/datasource_test.go b/linode/instanceips/datasource_test.go deleted file mode 100644 index 02dce9372..000000000 --- a/linode/instanceips/datasource_test.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build integration || instanceips - -package instanceips_test - -import ( - "log" - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/linode/linodego" - "github.com/linode/terraform-provider-linode/v2/linode/acceptance" - "github.com/linode/terraform-provider-linode/v2/linode/instanceips/tmpl" -) - -const testInstanceIPsDataName = "data.linode_instance_ips.test" - -var testRegion string - -func init() { - region, err := acceptance.GetRandomRegionWithCaps(nil, "core") - if err != nil { - log.Fatal(err) - } - - testRegion = region -} - -func TestAccDataSourceInstanceIPs_basic(t *testing.T) { - t.Parallel() - - var instance linodego.Instance - - name := acctest.RandomWithPrefix("tf_test") - resourceName := "linode_instance.foobar" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, - CheckDestroy: acceptance.CheckInstanceDestroy, - Steps: []resource.TestStep{ - { - Config: tmpl.Basic(t, name, testRegion), - Check: resource.ComposeTestCheckFunc( - acceptance.CheckInstanceExists(resourceName, &instance), - resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "id"), - resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "ipv4.public.0"), - resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "ipv4.private.#"), - resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "ipv6.slaac"), - resource.TestCheckResourceAttrSet(testInstanceIPsDataName, "ipv6.link_local"), - resource.TestCheckResourceAttr(testInstanceIPsDataName, "ipv6.global.#", "0"), - resource.TestCheckResourceAttrPair(testInstanceIPsDataName, "id", resourceName, "id"), - ), - }, - }, - }) -} diff --git a/linode/instanceips/framework_datasource.go b/linode/instanceips/framework_datasource.go deleted file mode 100644 index dea2a1dc5..000000000 --- a/linode/instanceips/framework_datasource.go +++ /dev/null @@ -1,129 +0,0 @@ -package instanceips - -import ( - "context" - "fmt" - "strconv" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/types" - "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_instance_ips", - Schema: &frameworkDataSourceSchema, - }, - ), - } -} - -type DataSource struct { - helper.BaseDataSource -} - -func (d *DataSource) Read( - ctx context.Context, - req datasource.ReadRequest, - resp *datasource.ReadResponse, -) { - var data InstanceIPDataSourceModel - - resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { - return - } - - client := d.Meta.Client - linodeID, err := strconv.Atoi(data.ID.ValueString()) - if err != nil { - resp.Diagnostics.AddError("Invalid Linode ID", err.Error()) - return - } - - ips, err := client.GetInstanceIPAddresses(ctx, linodeID) - if err != nil { - resp.Diagnostics.AddError( - "Failed to get instance IP addresses", - fmt.Sprintf("Error getting IP addresses for Linode %d: %s", linodeID, err), - ) - return - } - - // Populate IPv4 addresses - ipv4Value, diags := types.ObjectValue( - map[string]attr.Type{ - "public": types.ListType{ElemType: types.StringType}, - "private": types.ListType{ElemType: types.StringType}, - "shared": types.ListType{ElemType: types.StringType}, - "reserved": types.ListType{ElemType: types.StringType}, - "vpc": types.ListType{ElemType: types.StringType}, - }, - map[string]attr.Value{ - "public": types.ListValueMust(types.StringType, getIPAddresses(ips.IPv4.Public)), - "private": types.ListValueMust(types.StringType, getIPAddresses(ips.IPv4.Private)), - "shared": types.ListValueMust(types.StringType, getIPAddresses(ips.IPv4.Shared)), - "reserved": types.ListValueMust(types.StringType, getIPAddresses(ips.IPv4.Reserved)), - "vpc": types.ListValueMust(types.StringType, getVPCIPAddresses(ips.IPv4.VPC)), - }, - ) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - data.IPv4 = ipv4Value - - // Populate IPv6 addresses - ipv6Value, diags := types.ObjectValue( - map[string]attr.Type{ - "link_local": types.StringType, - "slaac": types.StringType, - "global": types.ListType{ElemType: types.StringType}, - }, - map[string]attr.Value{ - "link_local": types.StringValue(ips.IPv6.LinkLocal.Address), - "slaac": types.StringValue(ips.IPv6.SLAAC.Address), - "global": types.ListValueMust(types.StringType, getIPv6Ranges(ips.IPv6.Global)), - }, - ) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - data.IPv6 = ipv6Value - - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func getIPAddresses(ips []*linodego.InstanceIP) []attr.Value { - addresses := make([]attr.Value, len(ips)) - for i, ip := range ips { - addresses[i] = types.StringValue(ip.Address) - } - return addresses -} - -func getVPCIPAddresses(ips []*linodego.VPCIP) []attr.Value { - addresses := make([]attr.Value, len(ips)) - for i, ip := range ips { - if ip.Address != nil { - addresses[i] = types.StringValue(*ip.Address) - } else { - addresses[i] = types.StringNull() - } - } - return addresses -} - -func getIPv6Ranges(ranges []linodego.IPv6Range) []attr.Value { - result := make([]attr.Value, len(ranges)) - for i, r := range ranges { - result[i] = types.StringValue(r.Range) - } - return result -} diff --git a/linode/instanceips/framework_datasource_model.go b/linode/instanceips/framework_datasource_model.go deleted file mode 100644 index acc8a41a9..000000000 --- a/linode/instanceips/framework_datasource_model.go +++ /dev/null @@ -1,11 +0,0 @@ -package instanceips - -import ( - "github.com/hashicorp/terraform-plugin-framework/types" -) - -type InstanceIPDataSourceModel struct { - ID types.String `tfsdk:"id"` - IPv4 types.Object `tfsdk:"ipv4"` - IPv6 types.Object `tfsdk:"ipv6"` -} diff --git a/linode/instanceips/framework_datasource_schema.go b/linode/instanceips/framework_datasource_schema.go deleted file mode 100644 index b6ac8cb5b..000000000 --- a/linode/instanceips/framework_datasource_schema.go +++ /dev/null @@ -1,36 +0,0 @@ -package instanceips - -import ( - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -var frameworkDataSourceSchema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "The ID of the Linode instance.", - Required: true, - }, - "ipv4": schema.ObjectAttribute{ - Computed: true, - AttributeTypes: map[string]attr.Type{ - "public": types.ListType{ElemType: types.StringType}, - "private": types.ListType{ElemType: types.StringType}, - "shared": types.ListType{ElemType: types.StringType}, - "reserved": types.ListType{ElemType: types.StringType}, - "vpc": types.ListType{ElemType: types.StringType}, - }, - Description: "The IPv4 addresses of the instance.", - }, - "ipv6": schema.ObjectAttribute{ - Computed: true, - AttributeTypes: map[string]attr.Type{ - "link_local": types.StringType, - "slaac": types.StringType, - "global": types.ListType{ElemType: types.StringType}, - }, - Description: "The IPv6 addresses of the instance.", - }, - }, -} diff --git a/linode/instanceips/tmpl/basic.gotf b/linode/instanceips/tmpl/basic.gotf deleted file mode 100644 index f1a0e29b3..000000000 --- a/linode/instanceips/tmpl/basic.gotf +++ /dev/null @@ -1,15 +0,0 @@ -{{ define "instance_ips_basic" }} - -resource "linode_instance" "foobar" { - label = "{{.Label}}" - group = "tf_test" - type = "g6-nanode-1" - region = "{{ .Region }}" - image = "linode/debian12" -} - -data "linode_instance_ips" "test" { - id = linode_instance.foobar.id -} - -{{ end }} \ No newline at end of file diff --git a/linode/instanceips/tmpl/template.go b/linode/instanceips/tmpl/template.go deleted file mode 100644 index bc8e55bfc..000000000 --- a/linode/instanceips/tmpl/template.go +++ /dev/null @@ -1,20 +0,0 @@ -package tmpl - -import ( - "testing" - - "github.com/linode/terraform-provider-linode/v2/linode/acceptance" -) - -type TemplateData struct { - Label string - Region string -} - -func Basic(t *testing.T, instanceLabel, region string) string { - return acceptance.ExecuteTemplate(t, - "instance_ips_basic", TemplateData{ - Label: instanceLabel, - Region: region, - }) -} From f420bbcffa850f829284d8c9d51002d3de2fc093 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 28 Oct 2024 10:52:17 -0400 Subject: [PATCH 28/49] de-registering the datasource from framework_provider --- linode/framework_provider.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 32022c098..89ac94672 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -33,7 +33,6 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/images" "github.com/linode/terraform-provider-linode/v2/linode/instancedisk" "github.com/linode/terraform-provider-linode/v2/linode/instanceip" - "github.com/linode/terraform-provider-linode/v2/linode/instanceips" "github.com/linode/terraform-provider-linode/v2/linode/instancenetworking" "github.com/linode/terraform-provider-linode/v2/linode/instancereservedip" "github.com/linode/terraform-provider-linode/v2/linode/instancesharedips" @@ -301,6 +300,5 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource childaccounts.NewDataSource, networkreservedip.NewDataSourceFetch, networkreservedips.NewDataSourceList, - instanceips.NewDataSource, } } From 47941897d9972d0871feb330e2794a7cbbb59471 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 28 Oct 2024 11:22:24 -0400 Subject: [PATCH 29/49] Separated out the list networking ips (paginated response) to a different datasource called linode_networking_ips --- linode/networkingips/datasource_test.go | 116 ++++++++++++++ .../famework_datasource_schema.go | 82 ++++++++++ linode/networkingips/framework_datasource.go | 145 ++++++++++++++++++ 3 files changed, 343 insertions(+) create mode 100644 linode/networkingips/datasource_test.go create mode 100644 linode/networkingips/famework_datasource_schema.go create mode 100644 linode/networkingips/framework_datasource.go diff --git a/linode/networkingips/datasource_test.go b/linode/networkingips/datasource_test.go new file mode 100644 index 000000000..357081ce5 --- /dev/null +++ b/linode/networkingips/datasource_test.go @@ -0,0 +1,116 @@ +//go:build integration || networkingip + +package networkingips_test + +import ( + "log" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "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/networkingip/tmpl" +) + +var testRegion string + +func init() { + region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") + if err != nil { + log.Fatal(err) + } + + testRegion = region +} + +func TestAccDataSourceNetworkingIP_basic(t *testing.T) { + t.Parallel() + + resourceName := "linode_instance.foobar" + dataResourceName := "data.linode_networking_ip.foobar" + + label := acctest.RandomWithPrefix("tf-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + + Steps: []resource.TestStep{ + { + Config: tmpl.DataBasic(t, label, testRegion), + }, + { + Config: tmpl.DataBasic(t, label, testRegion), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataResourceName, "address", resourceName, "ip_address"), + resource.TestCheckResourceAttrPair(dataResourceName, "linode_id", resourceName, "id"), + resource.TestCheckResourceAttrPair(dataResourceName, "region", resourceName, "region"), + resource.TestMatchResourceAttr(dataResourceName, "gateway", regexp.MustCompile(`\.1$`)), + resource.TestCheckResourceAttr(dataResourceName, "type", "ipv4"), + resource.TestCheckResourceAttr(dataResourceName, "public", "true"), + resource.TestCheckResourceAttr(dataResourceName, "prefix", "24"), + resource.TestMatchResourceAttr(dataResourceName, "rdns", regexp.MustCompile(`.ip.linodeusercontent.com$`)), + ), + }, + }, + }) +} + +func TestAccDataSourceNetworkingIP_list(t *testing.T) { + t.Parallel() + + dataResourceName := "data.linode_networking_ip.list" + + 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(dataResourceName, "ip_addresses.#"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.address"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.linode_id"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.region"), + resource.TestMatchResourceAttr(dataResourceName, "ip_addresses.0.gateway", regexp.MustCompile(`\.1$`)), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.type", "ipv4"), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.public", "true"), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.prefix", "24"), + resource.TestMatchResourceAttr(dataResourceName, "ip_addresses.0.rdns", regexp.MustCompile(`.ip.linodeusercontent.com$`)), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.subnet_mask"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.reserved"), + ), + }, + }, + }) +} + +func TestAccDataSourceNetworkingIP_filterReserved(t *testing.T) { + t.Parallel() + + dataResourceName := "data.linode_networking_ip.filtered" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: tmpl.DataFilterReserved(t), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.#"), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.reserved", "true"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.address"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.linode_id"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.region"), + resource.TestMatchResourceAttr(dataResourceName, "ip_addresses.0.gateway", regexp.MustCompile(`\.1$`)), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.type", "ipv4"), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.public", "true"), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.prefix", "24"), + resource.TestMatchResourceAttr(dataResourceName, "ip_addresses.0.rdns", regexp.MustCompile(`.ip.linodeusercontent.com$`)), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.subnet_mask"), + ), + }, + }, + }) +} diff --git a/linode/networkingips/famework_datasource_schema.go b/linode/networkingips/famework_datasource_schema.go new file mode 100644 index 000000000..2aefea03d --- /dev/null +++ b/linode/networkingips/famework_datasource_schema.go @@ -0,0 +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" +) + +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 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.", + 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, + }, + }, +} diff --git a/linode/networkingips/framework_datasource.go b/linode/networkingips/framework_datasource.go new file mode 100644 index 000000000..6fecf15c5 --- /dev/null +++ b/linode/networkingips/framework_datasource.go @@ -0,0 +1,145 @@ +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" +) + +func NewDataSource() datasource.DataSource { + return &DataSource{ + BaseDataSource: helper.NewBaseDataSource( + helper.BaseDataSourceConfig{ + Name: "linode_networking_ip", + Schema: &frameworkDatasourceSchema, + }, + ), + } +} + +type DataSource struct { + helper.BaseDataSource +} + +func (data *DataSourceModel) parseIP(ip *linodego.InstanceIP) { + data.Address = types.StringValue(ip.Address) + 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.Region = types.StringValue(ip.Region) + + id, _ := json.Marshal(ip) + data.Reserved = types.BoolValue(ip.Reserved) + data.ID = types.StringValue(string(id)) +} + +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") + + var data DataSourceModel + + 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 + } + } + + 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()) + } + + if len(filters) == 0 { + return "", nil + } + + jsonFilter, err := json.Marshal(filters) + if err != nil { + return "", fmt.Errorf("error creating filter: %v", err) + } + + return string(jsonFilter), nil +} From 2cb26e81e248cdbdd8276f4899028748366322ae Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 28 Oct 2024 11:40:41 -0400 Subject: [PATCH 30/49] modified the name of the datasource and registered it with the framework_provider.go --- linode/framework_provider.go | 2 ++ linode/networkingips/framework_datasource.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 89ac94672..0a8f2f057 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -54,6 +54,7 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/nbs" "github.com/linode/terraform-provider-linode/v2/linode/nbtypes" "github.com/linode/terraform-provider-linode/v2/linode/networkingip" + "github.com/linode/terraform-provider-linode/v2/linode/networkingips" "github.com/linode/terraform-provider-linode/v2/linode/networkreservedip" "github.com/linode/terraform-provider-linode/v2/linode/networkreservedips" "github.com/linode/terraform-provider-linode/v2/linode/networktransferprices" @@ -300,5 +301,6 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource childaccounts.NewDataSource, networkreservedip.NewDataSourceFetch, networkreservedips.NewDataSourceList, + networkingips.NewDataSource, } } diff --git a/linode/networkingips/framework_datasource.go b/linode/networkingips/framework_datasource.go index 6fecf15c5..38082c18c 100644 --- a/linode/networkingips/framework_datasource.go +++ b/linode/networkingips/framework_datasource.go @@ -19,7 +19,7 @@ func NewDataSource() datasource.DataSource { return &DataSource{ BaseDataSource: helper.NewBaseDataSource( helper.BaseDataSourceConfig{ - Name: "linode_networking_ip", + Name: "linode_networking_ips", Schema: &frameworkDatasourceSchema, }, ), From 86d9e3c6f0b2fdf47ff1b6ffa19f05f86830b606 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 28 Oct 2024 13:57:43 -0400 Subject: [PATCH 31/49] created linode_networking_ip resource for allocating/creating reserved IPs --- go.mod | 2 + go.sum | 2 - linode/framework_provider.go | 1 + linode/networkingip/framework_resource.go | 173 ++++++++++++++++++ .../networkingip/framework_resource_model.go | 30 +++ .../networkingip/framework_resource_schema.go | 49 +++++ 6 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 linode/networkingip/framework_resource.go create mode 100644 linode/networkingip/framework_resource_model.go create mode 100644 linode/networkingip/framework_resource_schema.go diff --git a/go.mod b/go.mod index ea92aa174..e9529bfb4 100644 --- a/go.mod +++ b/go.mod @@ -125,3 +125,5 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) + +replace github.com/linode/linodego => /home/ajagadis/ReservedIP/linodego diff --git a/go.sum b/go.sum index 1c731765e..788fcdddb 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.42.0 h1:ZSbi4MtvwrfB9Y6bknesorvvueBGGilcmh2D5dq76RM= -github.com/linode/linodego v1.42.0/go.mod h1:2yzmY6pegPBDgx2HDllmt0eIk2IlzqcgK6NR0wFCFRY= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 0a8f2f057..6622f103d 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -231,6 +231,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res placementgroupassignment.NewResource, instancereservedip.NewResource, networkreservedip.NewResource, + networkingip.NewResource, } } diff --git a/linode/networkingip/framework_resource.go b/linode/networkingip/framework_resource.go new file mode 100644 index 000000000..a6addc6e6 --- /dev/null +++ b/linode/networkingip/framework_resource.go @@ -0,0 +1,173 @@ +package networkingip + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "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 NewResource() resource.Resource { + return &Resource{ + BaseResource: helper.NewBaseResource( + helper.BaseResourceConfig{ + Name: "linode_networking_ip", + IDType: types.StringType, + Schema: &frameworkResourceSchema, + }, + ), + } +} + +type Resource struct { + helper.BaseResource +} + +func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "Create linode_networking_ip") + var plan NetworkingIPModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + + if !plan.Public.IsNull() { + // Handle IP creation + createOpts := linodego.LinodeReserveIPOptions{ + Type: "ipv4", + Public: plan.Public.ValueBool(), + } + + if !plan.LinodeID.IsNull() { + createOpts.LinodeID = int(plan.LinodeID.ValueInt64()) + } + + if !plan.Reserved.IsNull() { + createOpts.Reserved = plan.Reserved.ValueBool() + } + + if !plan.Region.IsNull() { + createOpts.Region = plan.Region.ValueString() + } + + ip, err := client.AllocateReserveIP(ctx, createOpts) + if err != nil { + resp.Diagnostics.AddError( + "Error creating IP Address", + fmt.Sprintf("Could not create IP address: %s", err), + ) + return + } + + // Only set the necessary fields after creation + plan.ID = types.StringValue(ip.Address) + plan.Address = types.StringValue(ip.Address) + plan.LinodeID = types.Int64Value(int64(ip.LinodeID)) + plan.Public = types.BoolValue(ip.Public) + plan.Reserved = types.BoolValue(ip.Reserved) + plan.Region = types.StringValue(ip.Region) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + tflog.Debug(ctx, "Read linode_networking_ip") + var state NetworkingIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + + // For created/reserved IPs + ip, err := client.GetIPAddress(ctx, state.ID.ValueString()) + if err != nil { + if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + "Error reading IP Address", + fmt.Sprintf("Could not read IP address %s: %s", state.ID.ValueString(), err), + ) + return + } + + tflog.Debug(ctx, fmt.Sprintf("API Response for IP read: %+v", ip)) + tflog.Debug(ctx, fmt.Sprintf("API Response Reserved status: %t", ip.Reserved)) + + state.FlattenIPAddress(ip) + + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + tflog.Debug(ctx, "Update linode_networking_ip") + var plan, state NetworkingIPModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Debug(ctx, "Delete linode_networking_ip") + var state NetworkingIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + + // For created/reserved IPs + if !state.Reserved.ValueBool() { + // This is a regular IP address + linodeID := helper.FrameworkSafeInt64ToInt(state.LinodeID.ValueInt64(), &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + err := client.DeleteInstanceIPAddress(ctx, linodeID, state.Address.ValueString()) + if err != nil { + if lErr, ok := err.(*linodego.Error); (ok && lErr.Code != 404) || !ok { + resp.Diagnostics.AddError( + "Failed to Delete IP", + fmt.Sprintf( + "failed to delete instance (%d) ip (%s): %s", + linodeID, state.Address.ValueString(), err.Error(), + ), + ) + } + } + } else { + // This is a reserved IP address + err := client.DeleteReservedIPAddress(ctx, state.Address.ValueString()) + if err != nil { + if lErr, ok := err.(*linodego.Error); (ok && lErr.Code != 404) || !ok { + resp.Diagnostics.AddError( + "Failed to Delete Reserved IP", + fmt.Sprintf( + "failed to delete reserved ip (%s): %s", + state.Address.ValueString(), err.Error(), + ), + ) + } + } + } +} diff --git a/linode/networkingip/framework_resource_model.go b/linode/networkingip/framework_resource_model.go new file mode 100644 index 000000000..24e5c150f --- /dev/null +++ b/linode/networkingip/framework_resource_model.go @@ -0,0 +1,30 @@ +package networkingip + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/linode/linodego" +) + +type NetworkingIPModel struct { + ID types.String `tfsdk:"id"` + LinodeID types.Int64 `tfsdk:"linode_id"` + Reserved types.Bool `tfsdk:"reserved"` + Region types.String `tfsdk:"region"` + Public types.Bool `tfsdk:"public"` + Address types.String `tfsdk:"address"` + Type types.String `tfsdk:"type"` +} + +func (m *NetworkingIPModel) FlattenIPAddress(ip *linodego.InstanceIP) { + m.ID = types.StringValue(ip.Address) + if ip.LinodeID != 0 { + m.LinodeID = types.Int64Value(int64(ip.LinodeID)) + } else { + m.LinodeID = types.Int64Null() + } + m.Reserved = types.BoolValue(ip.Reserved) + m.Region = types.StringValue(ip.Region) + m.Public = types.BoolValue(ip.Public) + m.Address = types.StringValue(ip.Address) + m.Type = types.StringValue(string(ip.Type)) +} diff --git a/linode/networkingip/framework_resource_schema.go b/linode/networkingip/framework_resource_schema.go new file mode 100644 index 000000000..bacdf8db4 --- /dev/null +++ b/linode/networkingip/framework_resource_schema.go @@ -0,0 +1,49 @@ +package networkingip + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" +) + +var frameworkResourceSchema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the IPv4 address.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "linode_id": schema.Int64Attribute{ + Description: "The ID of the Linode to allocate an IPv4 address for. Required when reserved is false or not set.", + Optional: true, + Computed: true, + }, + "reserved": schema.BoolAttribute{ + Description: "Whether the IPv4 address should be reserved.", + Optional: true, + Computed: true, + }, + "region": schema.StringAttribute{ + Description: "The region for the reserved IPv4 address. Required when reserved is true and linode_id is not set.", + Optional: true, + Computed: true, + }, + "public": schema.BoolAttribute{ + Description: "Whether the IPv4 address is public or private.", + Optional: true, + Computed: true, + }, + "address": schema.StringAttribute{ + Description: "The allocated IPv4 address.", + Computed: true, + Optional: true, + }, + "type": schema.StringAttribute{ + Description: "The type of IP address (ipv4).", + Computed: true, + Optional: true, + }, + }, +} From 8dc5759009004aa309c0e9a4ea1f8f61f730969a Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 28 Oct 2024 14:59:54 -0400 Subject: [PATCH 32/49] Fixed import bug for linode_reserved_ip resource --- linode/networkreservedip/framework_models.go | 22 ------------------- .../networkreservedip/framework_resource.go | 2 +- linode/networkreservedip/framework_schema.go | 11 ++++++---- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/linode/networkreservedip/framework_models.go b/linode/networkreservedip/framework_models.go index 9d4accabc..a716b37bd 100644 --- a/linode/networkreservedip/framework_models.go +++ b/linode/networkreservedip/framework_models.go @@ -45,25 +45,3 @@ func (m *ReservedIPModel) FlattenReservedIP( 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 -// } diff --git a/linode/networkreservedip/framework_resource.go b/linode/networkreservedip/framework_resource.go index cf85923e6..4016c67a3 100644 --- a/linode/networkreservedip/framework_resource.go +++ b/linode/networkreservedip/framework_resource.go @@ -85,7 +85,7 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res ctx = populateLogAttributes(ctx, &data) client := r.Meta.Client - address := data.Address.ValueString() + address := data.ID.ValueString() reservedIP, err := client.GetReservedIPAddress(ctx, address) if err != nil { diff --git a/linode/networkreservedip/framework_schema.go b/linode/networkreservedip/framework_schema.go index 7ab010669..0f1c4d352 100644 --- a/linode/networkreservedip/framework_schema.go +++ b/linode/networkreservedip/framework_schema.go @@ -8,6 +8,13 @@ import ( var frameworkResourceSchema = schema.Schema{ Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the reserved IP address, which will be the IP address itself.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, "region": schema.StringAttribute{ Description: "The Region in which to reserve the IP address.", Required: true, @@ -52,9 +59,5 @@ var frameworkResourceSchema = schema.Schema{ Description: "Whether this IP is reserved or not.", Computed: true, }, - "id": schema.StringAttribute{ - Description: "The unique ID of the reserved IP address.", - Computed: true, - }, }, } From 5ef370ec9efe274ad6ceca6427c048198338b9a4 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 28 Oct 2024 15:12:04 -0400 Subject: [PATCH 33/49] Fixed import bug for linode_reserved_ip resource --- linode/networkreservedip/framework_models.go | 22 ------------------- .../networkreservedip/framework_resource.go | 2 +- linode/networkreservedip/framework_schema.go | 11 ++++++---- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/linode/networkreservedip/framework_models.go b/linode/networkreservedip/framework_models.go index 9d4accabc..a716b37bd 100644 --- a/linode/networkreservedip/framework_models.go +++ b/linode/networkreservedip/framework_models.go @@ -45,25 +45,3 @@ func (m *ReservedIPModel) FlattenReservedIP( 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 -// } diff --git a/linode/networkreservedip/framework_resource.go b/linode/networkreservedip/framework_resource.go index d19cc7ec8..f19d9b594 100644 --- a/linode/networkreservedip/framework_resource.go +++ b/linode/networkreservedip/framework_resource.go @@ -85,7 +85,7 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res ctx = populateLogAttributes(ctx, &data) client := r.Meta.Client - address := data.Address.ValueString() + address := data.ID.ValueString() reservedIP, err := client.GetReservedIPAddress(ctx, address) if err != nil { diff --git a/linode/networkreservedip/framework_schema.go b/linode/networkreservedip/framework_schema.go index 7ab010669..0f1c4d352 100644 --- a/linode/networkreservedip/framework_schema.go +++ b/linode/networkreservedip/framework_schema.go @@ -8,6 +8,13 @@ import ( var frameworkResourceSchema = schema.Schema{ Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the reserved IP address, which will be the IP address itself.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, "region": schema.StringAttribute{ Description: "The Region in which to reserve the IP address.", Required: true, @@ -52,9 +59,5 @@ var frameworkResourceSchema = schema.Schema{ Description: "Whether this IP is reserved or not.", Computed: true, }, - "id": schema.StringAttribute{ - Description: "The unique ID of the reserved IP address.", - Computed: true, - }, }, } From 131ca7140427c1006ea12d72f1e4e03d508b6bb3 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 29 Oct 2024 11:33:18 -0400 Subject: [PATCH 34/49] The functionality of network ip assignment is now moved to a new resource called linode_networking_assign_ip --- linode/framework_provider.go | 2 + .../networkipassignment/framework_resource.go | 237 ++++++++++++++++++ .../framework_resource_model.go | 36 +++ .../framework_resource_schema.go | 58 +++++ 4 files changed, 333 insertions(+) create mode 100644 linode/networkipassignment/framework_resource.go create mode 100644 linode/networkipassignment/framework_resource_model.go create mode 100644 linode/networkipassignment/framework_resource_schema.go diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 6622f103d..e61588a03 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -55,6 +55,7 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/nbtypes" "github.com/linode/terraform-provider-linode/v2/linode/networkingip" "github.com/linode/terraform-provider-linode/v2/linode/networkingips" + "github.com/linode/terraform-provider-linode/v2/linode/networkipassignment" "github.com/linode/terraform-provider-linode/v2/linode/networkreservedip" "github.com/linode/terraform-provider-linode/v2/linode/networkreservedips" "github.com/linode/terraform-provider-linode/v2/linode/networktransferprices" @@ -232,6 +233,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res instancereservedip.NewResource, networkreservedip.NewResource, networkingip.NewResource, + networkipassignment.NewResource, } } diff --git a/linode/networkipassignment/framework_resource.go b/linode/networkipassignment/framework_resource.go new file mode 100644 index 000000000..06b25cb5a --- /dev/null +++ b/linode/networkipassignment/framework_resource.go @@ -0,0 +1,237 @@ +package networkipassignment + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "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 NewResource() resource.Resource { + return &Resource{ + BaseResource: helper.NewBaseResource( + helper.BaseResourceConfig{ + Name: "linode_networking_assign_ip", + IDType: types.StringType, + Schema: &frameworkResourceSchema, + }, + ), + } +} + +type Resource struct { + helper.BaseResource +} + +func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "Create linode_networking_ip") + var plan NetworkingIPModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + + if len(plan.Assignments) > 0 { + // Handle IP assignment + tflog.Info(ctx, "Assigning IP addresses", map[string]interface{}{ + "assignments": plan.Assignments, + }) + + apiAssignments := make([]linodego.LinodeIPAssignment, len(plan.Assignments)) + for i, assignment := range plan.Assignments { + apiAssignments[i] = linodego.LinodeIPAssignment{ + Address: assignment.Address.ValueString(), + LinodeID: int(assignment.LinodeID.ValueInt64()), + } + } + + assignOpts := linodego.LinodesAssignIPsOptions{ + Region: plan.Region.ValueString(), + Assignments: apiAssignments, + } + + err := client.InstancesAssignIPs(ctx, assignOpts) + if err != nil { + resp.Diagnostics.AddError( + "Error assigning IP Addresses", + fmt.Sprintf("Could not assign IP addresses: %s", err), + ) + return + } + + // Only set the necessary fields for IP assignment + plan.ID = types.StringValue(plan.Assignments[0].Address.ValueString()) + + plan.Assignments = plan.Assignments + plan.Reserved = plan.Reserved + // plan.Address = plan.Assignments[0].Address + plan.LinodeID = plan.Assignments[0].LinodeID + plan.Region = types.StringValue(assignOpts.Region) + + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + tflog.Debug(ctx, "Read linode_networking_assign_ip") + var state NetworkingIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + + // Fetch the IP address details + ip, err := client.GetIPAddress(ctx, state.ID.ValueString()) + if err != nil { + if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { + resp.Diagnostics.AddWarning( + "IP Address No Longer Exists", + fmt.Sprintf("Removing IP address %s from state because it no longer exists", state.ID.ValueString()), + ) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + "Error reading IP Address", + fmt.Sprintf("Could not read IP address %s: %s", state.ID.ValueString(), err), + ) + return + } + + // Update state with the latest information + state.ID = types.StringValue(ip.Address) + state.Address = types.StringValue(ip.Address) + state.LinodeID = types.Int64Value(int64(ip.LinodeID)) + state.Region = types.StringValue(ip.Region) + state.Public = types.BoolValue(ip.Public) + state.Type = types.StringValue(string(ip.Type)) + state.Reserved = types.BoolValue(ip.Reserved) + + // Handle assignments + if ip.LinodeID != 0 { + state.Assignments = []AssignmentModel{ + { + Address: types.StringValue(ip.Address), + LinodeID: types.Int64Value(int64(ip.LinodeID)), + }, + } + } else { + state.Assignments = []AssignmentModel{} + } + + // Ensure all computed fields are set, even if they're empty or zero values + if state.Region.IsNull() { + state.Region = types.StringValue("") + } + if state.Public.IsNull() { + state.Public = types.BoolValue(false) + } + if state.Type.IsNull() { + state.Type = types.StringValue("") + } + if state.Reserved.IsNull() { + state.Reserved = types.BoolValue(false) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + tflog.Debug(ctx, "Update linode_networking_ip") + var plan, state NetworkingIPModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + + if len(plan.Assignments) > 0 { + // Handle IP assignment updates + apiAssignments := make([]linodego.LinodeIPAssignment, len(plan.Assignments)) + for i, assignment := range plan.Assignments { + apiAssignments[i] = linodego.LinodeIPAssignment{ + Address: assignment.Address.ValueString(), + LinodeID: int(assignment.LinodeID.ValueInt64()), + } + } + + assignOpts := linodego.LinodesAssignIPsOptions{ + Region: plan.Region.ValueString(), + Assignments: apiAssignments, + } + + err := client.InstancesAssignIPs(ctx, assignOpts) + if err != nil { + resp.Diagnostics.AddError( + "Error updating IP Assignments", + fmt.Sprintf("Could not update IP assignments: %s", err), + ) + return + } + + // Update plan with new assignment details + plan.ID = types.StringValue(plan.Assignments[0].Address.ValueString()) + plan.Address = plan.Assignments[0].Address + } + + // Re-read the IP address to get the latest state + ip, err := client.GetIPAddress(ctx, plan.Address.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error reading updated IP Address", + fmt.Sprintf("Could not read updated IP address %s: %s", plan.Address.ValueString(), err), + ) + return + } + plan.FlattenIPAddress(ip) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Debug(ctx, "Delete linode_networking_ip") + var state NetworkingIPModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.Client + + // Check if this is an assigned IP + if len(state.Assignments) > 0 { + // For assigned IPs, we need to unassign them + for _, assignment := range state.Assignments { + linodeID := int(assignment.LinodeID.ValueInt64()) + ipAddress := assignment.Address.ValueString() + + err := client.DeleteInstanceIPAddress(ctx, linodeID, ipAddress) + if err != nil { + if lErr, ok := err.(*linodego.Error); (ok && lErr.Code != 404) || !ok { + resp.Diagnostics.AddError( + "Failed to Unassign IP", + fmt.Sprintf( + "failed to unassign ip (%s) from instance (%d): %s", + ipAddress, linodeID, err.Error(), + ), + ) + } + } + } + } +} diff --git a/linode/networkipassignment/framework_resource_model.go b/linode/networkipassignment/framework_resource_model.go new file mode 100644 index 000000000..ebc5c8244 --- /dev/null +++ b/linode/networkipassignment/framework_resource_model.go @@ -0,0 +1,36 @@ +package networkipassignment + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/linode/linodego" +) + +type NetworkingIPModel struct { + ID types.String `tfsdk:"id"` + LinodeID types.Int64 `tfsdk:"linode_id"` + Reserved types.Bool `tfsdk:"reserved"` + Region types.String `tfsdk:"region"` + Public types.Bool `tfsdk:"public"` + Address types.String `tfsdk:"address"` + Type types.String `tfsdk:"type"` + Assignments []AssignmentModel `tfsdk:"assignments"` +} + +type AssignmentModel struct { + Address types.String `tfsdk:"address"` + LinodeID types.Int64 `tfsdk:"linode_id"` +} + +func (m *NetworkingIPModel) FlattenIPAddress(ip *linodego.InstanceIP) { + m.ID = types.StringValue(ip.Address) + if ip.LinodeID != 0 { + m.LinodeID = types.Int64Value(int64(ip.LinodeID)) + } else { + m.LinodeID = types.Int64Null() + } + m.Reserved = types.BoolValue(ip.Reserved) + m.Region = types.StringValue(ip.Region) + m.Public = types.BoolValue(ip.Public) + m.Address = types.StringValue(ip.Address) + m.Type = types.StringValue(string(ip.Type)) +} diff --git a/linode/networkipassignment/framework_resource_schema.go b/linode/networkipassignment/framework_resource_schema.go new file mode 100644 index 000000000..df7195ca2 --- /dev/null +++ b/linode/networkipassignment/framework_resource_schema.go @@ -0,0 +1,58 @@ +package networkipassignment + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var frameworkResourceSchema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the IPv4 address.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "linode_id": schema.Int64Attribute{ + Description: "The ID of the Linode to allocate an IPv4 address for. Required when reserved is false or not set.", + Optional: true, + Computed: true, + }, + "reserved": schema.BoolAttribute{ + Description: "Whether the IPv4 address should be reserved.", + Optional: true, + }, + "region": schema.StringAttribute{ + Description: "The region for the reserved IPv4 address. Required when reserved is true and linode_id is not set.", + Optional: true, + Computed: true, + }, + "public": schema.BoolAttribute{ + Description: "Whether the IPv4 address is public or private.", + Optional: true, + // Computed: true, + }, + "address": schema.StringAttribute{ + Description: "The allocated IPv4 address.", + Optional: true, + }, + "type": schema.StringAttribute{ + Description: "The type of IP address (ipv4).", + // Computed: true, + Optional: true, + }, + "assignments": schema.ListAttribute{ + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "address": types.StringType, + "linode_id": types.Int64Type, + }, + }, + Optional: true, + }, + }, +} From 2ea86e6cf78397a8e83d598ae36b370ef6b68215 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 29 Oct 2024 13:35:31 -0400 Subject: [PATCH 35/49] added functionality to update RDNS and reserved status of an IP Address --- linode/rdns/framework_helper.go | 12 ++-- linode/rdns/framework_models.go | 2 + linode/rdns/framework_resource.go | 75 +++++++++++++++++++----- linode/rdns/framework_resource_schema.go | 9 ++- 4 files changed, 74 insertions(+), 24 deletions(-) diff --git a/linode/rdns/framework_helper.go b/linode/rdns/framework_helper.go index d18302130..19238de1e 100644 --- a/linode/rdns/framework_helper.go +++ b/linode/rdns/framework_helper.go @@ -15,22 +15,18 @@ func updateIPAddress( ctx context.Context, client *linodego.Client, address string, - desiredRDNS *string, + opts linodego.IPAddressUpdateOptions, waitForAvailable bool, ) (*linodego.InstanceIP, error) { - updateOpts := linodego.IPAddressUpdateOptions{ - RDNS: desiredRDNS, - } - if waitForAvailable { - return updateIPAddressWithRetries(ctx, client, address, updateOpts, time.Second*5) + return updateIPAddressWithRetries(ctx, client, address, opts, time.Second*5) } tflog.Debug(ctx, "client.UpdateIPAddress(...)", map[string]any{ - "options": updateOpts, + "options": opts, }) - return client.UpdateIPAddress(ctx, address, updateOpts) + return client.UpdateIPAddress(ctx, address, opts) } func updateIPAddressWithRetries(ctx context.Context, client *linodego.Client, address string, diff --git a/linode/rdns/framework_models.go b/linode/rdns/framework_models.go index add47ef30..c5fe9e7e9 100644 --- a/linode/rdns/framework_models.go +++ b/linode/rdns/framework_models.go @@ -11,6 +11,7 @@ import ( type ResourceModel struct { Address customtypes.IPAddrStringValue `tfsdk:"address"` RDNS types.String `tfsdk:"rdns"` + Reserved types.Bool `tfsdk:"reserved"` WaitForAvailable types.Bool `tfsdk:"wait_for_available"` ID types.String `tfsdk:"id"` Timeouts timeouts.Value `tfsdk:"timeouts"` @@ -22,6 +23,7 @@ func (rm *ResourceModel) FlattenInstanceIP(ip *linodego.InstanceIP, preserveKnow ) rm.ID = helper.KeepOrUpdateString(rm.ID, ip.Address, preserveKnown) rm.RDNS = helper.KeepOrUpdateString(rm.RDNS, ip.RDNS, preserveKnown) + rm.Reserved = helper.KeepOrUpdateValue(rm.Reserved, types.BoolValue(ip.Reserved), preserveKnown) } func (rm *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { diff --git a/linode/rdns/framework_resource.go b/linode/rdns/framework_resource.go index 9cc8a72d2..fb01a7588 100644 --- a/linode/rdns/framework_resource.go +++ b/linode/rdns/framework_resource.go @@ -90,22 +90,59 @@ func (r *Resource) Create( ) } - ip, err = updateIPAddress( - ctx, - client, - plan.Address.ValueString(), - plan.RDNS.ValueStringPointer(), - plan.WaitForAvailable.ValueBool(), - ) - if err != nil { - resp.Diagnostics.AddError( - "Failed to create Linode RDNS", - err.Error(), + updateOpts := linodego.IPAddressUpdateOptions{} + + if !plan.RDNS.IsNull() { + rdnsValue := plan.RDNS.ValueString() + if rdnsValue != "" { + updateOpts.RDNS = &rdnsValue + } + } + + if !plan.Reserved.IsNull() && plan.Reserved.ValueBool() { + updateOpts.Reserved = plan.Reserved.ValueBool() + } + + // Only call updateIPAddress if there are options to update + if updateOpts.RDNS != nil || !plan.Reserved.IsNull() { + // Log updateOpts before calling updateIPAddress + tflog.Info(ctx, "UpdateOpts before calling updateIPAddress", map[string]interface{}{ + "RDNS": updateOpts.RDNS, + "Reserved": updateOpts.Reserved, + }) + ip, err = updateIPAddress( + ctx, + client, + plan.Address.ValueString(), + updateOpts, + plan.WaitForAvailable.ValueBool(), ) - return + if err != nil { + resp.Diagnostics.AddError( + "Failed to update Linode IP", + err.Error(), + ) + return + } } + // Log IP fields before FlattenInstanceIP + tflog.Info(ctx, "IP details before FlattenInstanceIP", map[string]interface{}{ + "Address": ip.Address, + "Gateway": ip.Gateway, + "SubnetMask": ip.SubnetMask, + "Prefix": ip.Prefix, + "Type": ip.Type, + "Public": ip.Public, + "LinodeID": ip.LinodeID, + "Region": ip.Region, + "RDNS": ip.RDNS, + "Reserved": ip.Reserved, + }) + + // Update the plan with the latest IP information plan.FlattenInstanceIP(ip, true) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -187,7 +224,15 @@ func (r *Resource) Update( resourceUpdated := false if !state.RDNS.Equal(plan.RDNS) { - updateOpts.RDNS = plan.RDNS.ValueStringPointer() + rdnsValue := plan.RDNS.ValueString() + if rdnsValue != "" { + updateOpts.RDNS = &rdnsValue + resourceUpdated = true + } + } + + if !state.Reserved.Equal(plan.Reserved) { + updateOpts.Reserved = plan.Reserved.ValueBool() resourceUpdated = true } @@ -196,12 +241,12 @@ func (r *Resource) Update( ctx, client, plan.Address.ValueString(), - plan.RDNS.ValueStringPointer(), + updateOpts, plan.WaitForAvailable.ValueBool(), ) if err != nil { resp.Diagnostics.AddError( - "Failed to update the Linode RDNS", + "Failed to update the Linode IP", err.Error(), ) return diff --git a/linode/rdns/framework_resource_schema.go b/linode/rdns/framework_resource_schema.go index 62c9c4444..79277875f 100644 --- a/linode/rdns/framework_resource_schema.go +++ b/linode/rdns/framework_resource_schema.go @@ -23,11 +23,18 @@ var frameworkResourceSchema = schema.Schema{ "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.", - Required: true, + // Required: true, + Optional: true, + Computed: true, Validators: []validator.String{ stringvalidator.LengthBetween(3, 254), }, }, + "reserved": schema.BoolAttribute{ + Description: "Whether the IP address is reserved.", + Optional: true, + Computed: true, + }, "wait_for_available": schema.BoolAttribute{ Description: "If true, the RDNS assignment will be retried within the operation timeout period.", Optional: true, From 79aec4ee589c222d7677e7903eff63dc855fbbaa Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 29 Oct 2024 19:43:46 -0400 Subject: [PATCH 36/49] Removing the update reserved status from linode_rdns to move it to new resource --- linode/rdns/framework_helper.go | 12 ++-- linode/rdns/framework_models.go | 2 - linode/rdns/framework_resource.go | 75 +++++------------------- linode/rdns/framework_resource_schema.go | 9 +-- 4 files changed, 24 insertions(+), 74 deletions(-) diff --git a/linode/rdns/framework_helper.go b/linode/rdns/framework_helper.go index 19238de1e..d18302130 100644 --- a/linode/rdns/framework_helper.go +++ b/linode/rdns/framework_helper.go @@ -15,18 +15,22 @@ func updateIPAddress( ctx context.Context, client *linodego.Client, address string, - opts linodego.IPAddressUpdateOptions, + desiredRDNS *string, waitForAvailable bool, ) (*linodego.InstanceIP, error) { + updateOpts := linodego.IPAddressUpdateOptions{ + RDNS: desiredRDNS, + } + if waitForAvailable { - return updateIPAddressWithRetries(ctx, client, address, opts, time.Second*5) + return updateIPAddressWithRetries(ctx, client, address, updateOpts, time.Second*5) } tflog.Debug(ctx, "client.UpdateIPAddress(...)", map[string]any{ - "options": opts, + "options": updateOpts, }) - return client.UpdateIPAddress(ctx, address, opts) + return client.UpdateIPAddress(ctx, address, updateOpts) } func updateIPAddressWithRetries(ctx context.Context, client *linodego.Client, address string, diff --git a/linode/rdns/framework_models.go b/linode/rdns/framework_models.go index c5fe9e7e9..add47ef30 100644 --- a/linode/rdns/framework_models.go +++ b/linode/rdns/framework_models.go @@ -11,7 +11,6 @@ import ( type ResourceModel struct { Address customtypes.IPAddrStringValue `tfsdk:"address"` RDNS types.String `tfsdk:"rdns"` - Reserved types.Bool `tfsdk:"reserved"` WaitForAvailable types.Bool `tfsdk:"wait_for_available"` ID types.String `tfsdk:"id"` Timeouts timeouts.Value `tfsdk:"timeouts"` @@ -23,7 +22,6 @@ func (rm *ResourceModel) FlattenInstanceIP(ip *linodego.InstanceIP, preserveKnow ) rm.ID = helper.KeepOrUpdateString(rm.ID, ip.Address, preserveKnown) rm.RDNS = helper.KeepOrUpdateString(rm.RDNS, ip.RDNS, preserveKnown) - rm.Reserved = helper.KeepOrUpdateValue(rm.Reserved, types.BoolValue(ip.Reserved), preserveKnown) } func (rm *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { diff --git a/linode/rdns/framework_resource.go b/linode/rdns/framework_resource.go index fb01a7588..9cc8a72d2 100644 --- a/linode/rdns/framework_resource.go +++ b/linode/rdns/framework_resource.go @@ -90,59 +90,22 @@ func (r *Resource) Create( ) } - updateOpts := linodego.IPAddressUpdateOptions{} - - if !plan.RDNS.IsNull() { - rdnsValue := plan.RDNS.ValueString() - if rdnsValue != "" { - updateOpts.RDNS = &rdnsValue - } - } - - if !plan.Reserved.IsNull() && plan.Reserved.ValueBool() { - updateOpts.Reserved = plan.Reserved.ValueBool() - } - - // Only call updateIPAddress if there are options to update - if updateOpts.RDNS != nil || !plan.Reserved.IsNull() { - // Log updateOpts before calling updateIPAddress - tflog.Info(ctx, "UpdateOpts before calling updateIPAddress", map[string]interface{}{ - "RDNS": updateOpts.RDNS, - "Reserved": updateOpts.Reserved, - }) - ip, err = updateIPAddress( - ctx, - client, - plan.Address.ValueString(), - updateOpts, - plan.WaitForAvailable.ValueBool(), + ip, err = updateIPAddress( + ctx, + client, + plan.Address.ValueString(), + plan.RDNS.ValueStringPointer(), + plan.WaitForAvailable.ValueBool(), + ) + if err != nil { + resp.Diagnostics.AddError( + "Failed to create Linode RDNS", + err.Error(), ) - if err != nil { - resp.Diagnostics.AddError( - "Failed to update Linode IP", - err.Error(), - ) - return - } + return } - // Log IP fields before FlattenInstanceIP - tflog.Info(ctx, "IP details before FlattenInstanceIP", map[string]interface{}{ - "Address": ip.Address, - "Gateway": ip.Gateway, - "SubnetMask": ip.SubnetMask, - "Prefix": ip.Prefix, - "Type": ip.Type, - "Public": ip.Public, - "LinodeID": ip.LinodeID, - "Region": ip.Region, - "RDNS": ip.RDNS, - "Reserved": ip.Reserved, - }) - - // Update the plan with the latest IP information plan.FlattenInstanceIP(ip, true) - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -224,15 +187,7 @@ func (r *Resource) Update( resourceUpdated := false if !state.RDNS.Equal(plan.RDNS) { - rdnsValue := plan.RDNS.ValueString() - if rdnsValue != "" { - updateOpts.RDNS = &rdnsValue - resourceUpdated = true - } - } - - if !state.Reserved.Equal(plan.Reserved) { - updateOpts.Reserved = plan.Reserved.ValueBool() + updateOpts.RDNS = plan.RDNS.ValueStringPointer() resourceUpdated = true } @@ -241,12 +196,12 @@ func (r *Resource) Update( ctx, client, plan.Address.ValueString(), - updateOpts, + plan.RDNS.ValueStringPointer(), plan.WaitForAvailable.ValueBool(), ) if err != nil { resp.Diagnostics.AddError( - "Failed to update the Linode IP", + "Failed to update the Linode RDNS", err.Error(), ) return diff --git a/linode/rdns/framework_resource_schema.go b/linode/rdns/framework_resource_schema.go index 79277875f..62c9c4444 100644 --- a/linode/rdns/framework_resource_schema.go +++ b/linode/rdns/framework_resource_schema.go @@ -23,18 +23,11 @@ var frameworkResourceSchema = schema.Schema{ "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.", - // Required: true, - Optional: true, - Computed: true, + Required: true, Validators: []validator.String{ stringvalidator.LengthBetween(3, 254), }, }, - "reserved": schema.BoolAttribute{ - Description: "Whether the IP address is reserved.", - Optional: true, - Computed: true, - }, "wait_for_available": schema.BoolAttribute{ Description: "If true, the RDNS assignment will be retried within the operation timeout period.", Optional: true, From 2001cf56f9c5f079b0d23dc2b0425f0b855f9f41 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Wed, 30 Oct 2024 16:04:28 -0400 Subject: [PATCH 37/49] Adding the updated behvior of the rdns resource back to linode_rdns --- linode/rdns/framework_helper.go | 6 +- linode/rdns/framework_models.go | 3 + linode/rdns/framework_resource.go | 211 +++++------------------ linode/rdns/framework_resource_schema.go | 12 +- 4 files changed, 58 insertions(+), 174 deletions(-) diff --git a/linode/rdns/framework_helper.go b/linode/rdns/framework_helper.go index d18302130..bf1c6add1 100644 --- a/linode/rdns/framework_helper.go +++ b/linode/rdns/framework_helper.go @@ -15,13 +15,9 @@ func updateIPAddress( ctx context.Context, client *linodego.Client, address string, - desiredRDNS *string, + updateOpts linodego.IPAddressUpdateOptions, waitForAvailable bool, ) (*linodego.InstanceIP, error) { - updateOpts := linodego.IPAddressUpdateOptions{ - RDNS: desiredRDNS, - } - if waitForAvailable { return updateIPAddressWithRetries(ctx, client, address, updateOpts, time.Second*5) } diff --git a/linode/rdns/framework_models.go b/linode/rdns/framework_models.go index add47ef30..a133b919c 100644 --- a/linode/rdns/framework_models.go +++ b/linode/rdns/framework_models.go @@ -11,6 +11,7 @@ import ( type ResourceModel struct { Address customtypes.IPAddrStringValue `tfsdk:"address"` RDNS types.String `tfsdk:"rdns"` + Reserved types.Bool `tfsdk:"reserved"` WaitForAvailable types.Bool `tfsdk:"wait_for_available"` ID types.String `tfsdk:"id"` Timeouts timeouts.Value `tfsdk:"timeouts"` @@ -22,10 +23,12 @@ func (rm *ResourceModel) FlattenInstanceIP(ip *linodego.InstanceIP, preserveKnow ) rm.ID = helper.KeepOrUpdateString(rm.ID, ip.Address, preserveKnown) rm.RDNS = helper.KeepOrUpdateString(rm.RDNS, ip.RDNS, preserveKnown) + rm.Reserved = helper.KeepOrUpdateValue(rm.Reserved, types.BoolValue(ip.Reserved), preserveKnown) } func (rm *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { rm.Address = helper.KeepOrUpdateValue(rm.Address, other.Address, preserveKnown) rm.ID = helper.KeepOrUpdateValue(rm.ID, other.ID, preserveKnown) rm.RDNS = helper.KeepOrUpdateValue(rm.RDNS, other.RDNS, preserveKnown) + rm.Reserved = helper.KeepOrUpdateValue(rm.Reserved, other.Reserved, preserveKnown) } diff --git a/linode/rdns/framework_resource.go b/linode/rdns/framework_resource.go index 9cc8a72d2..903bcb530 100644 --- a/linode/rdns/framework_resource.go +++ b/linode/rdns/framework_resource.go @@ -2,9 +2,6 @@ package rdns import ( "context" - "fmt" - "strings" - "time" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -14,11 +11,6 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/helper" ) -const ( - DefaultVolumeCreateTimeout = 15 * time.Minute - DefaultVolumeUpdateTimeout = 15 * time.Minute -) - func NewResource() resource.Resource { return &Resource{ BaseResource: helper.NewBaseResource( @@ -39,69 +31,36 @@ type Resource struct { helper.BaseResource } -func (r *Resource) Create( - ctx context.Context, - req resource.CreateRequest, - resp *resource.CreateResponse, -) { +func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { tflog.Debug(ctx, "Create linode_rdns") var plan ResourceModel - client := r.Meta.Client - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } - ctx = populateLogAttributes(ctx, plan) + client := r.Meta.Client + updateOpts := linodego.IPAddressUpdateOptions{} - createTimeout, diags := plan.Timeouts.Create(ctx, DefaultVolumeCreateTimeout) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return + if !plan.RDNS.IsNull() { + updateOpts.RDNS = plan.RDNS.ValueStringPointer() } - ctx, cancel := context.WithTimeout(ctx, createTimeout) - defer cancel() - - address := plan.Address.ValueString() - - ip, err := client.GetIPAddress(ctx, address) - if err != nil { - resp.Diagnostics.AddError( - "Failed to get the ip address associated with this RDNS", - err.Error(), - ) - return + if !plan.Reserved.IsNull() { + reserved := plan.Reserved.ValueBool() + updateOpts.Reserved = reserved } - defaultRdns := strings.Replace( - plan.Address.ValueString(), - ".", - "-", - -1, - ) + ".ip.linodeusercontent.com" - - if ip.RDNS != defaultRdns { - resp.Diagnostics.AddWarning( - "Pre-modified RDNS Address", - "RDNS was already configured before the creation of this RDNS resource", - ) - } - - ip, err = updateIPAddress( + ip, err := updateIPAddress( ctx, client, plan.Address.ValueString(), - plan.RDNS.ValueStringPointer(), + updateOpts, plan.WaitForAvailable.ValueBool(), ) if err != nil { - resp.Diagnostics.AddError( - "Failed to create Linode RDNS", - err.Error(), - ) + resp.Diagnostics.AddError("Failed to create/update IP Address", err.Error()) return } @@ -109,166 +68,88 @@ func (r *Resource) Create( resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } -func (r *Resource) Read( - ctx context.Context, - req resource.ReadRequest, - resp *resource.ReadResponse, -) { +func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { tflog.Debug(ctx, "Read linode_rdns") - client := r.Meta.Client - - var data ResourceModel - - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + var state ResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } - ctx = populateLogAttributes(ctx, data) - - if helper.FrameworkAttemptRemoveResourceForEmptyID(ctx, data.ID, resp) { - return - } - - ip, err := client.GetIPAddress(ctx, data.ID.ValueString()) + client := r.Meta.Client + ip, err := client.GetIPAddress(ctx, state.ID.ValueString()) if err != nil { if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { - resp.Diagnostics.AddWarning( - "RDNS No Longer Exists", - fmt.Sprintf( - "Removing Linode RDNS with IP %v from state because it no longer exists", - data.ID.ValueString(), - ), - ) resp.State.RemoveResource(ctx) - } else { - resp.Diagnostics.AddError( - "Failed to read the Linode RDNS", err.Error(), - ) + return } + resp.Diagnostics.AddError("Failed to read IP Address", err.Error()) return } - data.FlattenInstanceIP(ip, false) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + state.FlattenInstanceIP(ip, false) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } -func (r *Resource) Update( - ctx context.Context, - req resource.UpdateRequest, - resp *resource.UpdateResponse, -) { +func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { tflog.Debug(ctx, "Update linode_rdns") - var state, plan ResourceModel - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + var plan, state ResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } - ctx = populateLogAttributes(ctx, state) - - updateTimeout, diags := plan.Timeouts.Update(ctx, DefaultVolumeUpdateTimeout) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - ctx, cancel := context.WithTimeout(ctx, updateTimeout) - defer cancel() - client := r.Meta.Client + updateOpts := linodego.IPAddressUpdateOptions{} - var updateOpts linodego.IPAddressUpdateOptions - - resourceUpdated := false - - if !state.RDNS.Equal(plan.RDNS) { + if !plan.RDNS.IsNull() { updateOpts.RDNS = plan.RDNS.ValueStringPointer() - resourceUpdated = true } - if resourceUpdated { - ip, err := updateIPAddress( - ctx, - client, - plan.Address.ValueString(), - plan.RDNS.ValueStringPointer(), - plan.WaitForAvailable.ValueBool(), - ) - if err != nil { - resp.Diagnostics.AddError( - "Failed to update the Linode RDNS", - err.Error(), - ) - return - } - plan.FlattenInstanceIP(ip, true) + if !plan.Reserved.IsNull() { + reserved := plan.Reserved.ValueBool() + updateOpts.Reserved = reserved } - plan.CopyFrom(state, true) - - // Workaround for Crossplane issue where ID is not - // properly populated in plan - // See TPT-2865 for more details - if plan.ID.ValueString() == "" { - plan.ID = state.ID + ip, err := updateIPAddress( + ctx, + client, + plan.Address.ValueString(), + updateOpts, + plan.WaitForAvailable.ValueBool(), + ) + if err != nil { + resp.Diagnostics.AddError("Failed to update IP Address", err.Error()) + return } + plan.FlattenInstanceIP(ip, true) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } -func (r *Resource) Delete( - ctx context.Context, - req resource.DeleteRequest, - resp *resource.DeleteResponse, -) { +func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { tflog.Debug(ctx, "Delete linode_rdns") - var data ResourceModel - - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + var state ResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } client := r.Meta.Client - updateOpts := linodego.IPAddressUpdateOptions{ - RDNS: nil, + RDNS: nil, + Reserved: false, } - tflog.Debug(ctx, "client.UpdateIPAddress(...)", map[string]any{ - "options": updateOpts, - }) - _, err := client.UpdateIPAddress(ctx, data.Address.ValueString(), updateOpts) + _, err := client.UpdateIPAddress(ctx, state.Address.ValueString(), updateOpts) if err != nil { if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { - resp.Diagnostics.AddWarning( - "Target IP for RDNS resetting no longer exists.", - fmt.Sprintf( - "The given IP Address (%s) for RDNS resetting no longer exists.", - data.Address, - ), - ) return } - - resp.Diagnostics.AddError( - "Unable to delete the Linode IP address RDNS", - fmt.Sprintf( - "Error deleting the Linode IP address RDNS: %s", - err.Error(), - ), - ) + resp.Diagnostics.AddError("Failed to delete IP Address reservation", err.Error()) } } - -func populateLogAttributes(ctx context.Context, model ResourceModel) context.Context { - return helper.SetLogFieldBulk(ctx, map[string]any{ - "address": model.Address.ValueString(), - }) -} diff --git a/linode/rdns/framework_resource_schema.go b/linode/rdns/framework_resource_schema.go index 62c9c4444..a10d5d8a9 100644 --- a/linode/rdns/framework_resource_schema.go +++ b/linode/rdns/framework_resource_schema.go @@ -23,11 +23,16 @@ var frameworkResourceSchema = schema.Schema{ "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.", - Required: true, + Optional: true, Validators: []validator.String{ stringvalidator.LengthBetween(3, 254), }, }, + "reserved": schema.BoolAttribute{ + Description: "Whether the IP address is reserved.", + Optional: true, + Computed: true, + }, "wait_for_available": schema.BoolAttribute{ Description: "If true, the RDNS assignment will be retried within the operation timeout period.", Optional: true, @@ -35,9 +40,8 @@ var frameworkResourceSchema = schema.Schema{ Default: booldefault.StaticBool(false), }, "id": schema.StringAttribute{ - Description: "Unique identification field for this RDNS Resource. " + - "The public Linode IPv4 or IPv6 address to operate on. ", - Computed: true, + Description: "The ID of the IP address resource.", + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, From e28442bb6d47deca09c412d16932c104ae005bbb Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Wed, 30 Oct 2024 21:06:02 -0400 Subject: [PATCH 38/49] added test for checking updated behavior while listing linode ip addresses --- linode/instancenetworking/datasource_test.go | 31 +++++++++ .../tmpl/data_basic_reserved.gotf | 22 +++++++ linode/instancenetworking/tmpl/template.go | 8 +++ linode/networkingips/datasource_test.go | 63 ------------------- 4 files changed, 61 insertions(+), 63 deletions(-) create mode 100644 linode/instancenetworking/tmpl/data_basic_reserved.gotf diff --git a/linode/instancenetworking/datasource_test.go b/linode/instancenetworking/datasource_test.go index d09925106..0bc61fbc8 100644 --- a/linode/instancenetworking/datasource_test.go +++ b/linode/instancenetworking/datasource_test.go @@ -80,3 +80,34 @@ func TestAccDataSourceInstanceNetworking_vpc(t *testing.T) { }, }) } + +func TestAccDataSourceInstanceNetworking_basicwithReseved(t *testing.T) { + t.Parallel() + + var instance linodego.Instance + + name := acctest.RandomWithPrefix("tf_test") + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.DataBasic_withReservedField(t, name, testRegion), + }, + { + Config: tmpl.DataBasic_withReservedField(t, name, testRegion), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists("linode_instance.foobar", &instance), + resource.TestCheckResourceAttrSet(testInstanceNetworkResName, "ipv4.0.private.#"), + resource.TestCheckResourceAttrSet(testInstanceNetworkResName, "ipv4.0.public.#"), + resource.TestCheckResourceAttrSet(testInstanceNetworkResName, "ipv4.0.reserved.#"), + resource.TestCheckResourceAttrSet(testInstanceNetworkResName, "ipv4.0.shared.#"), + resource.TestCheckResourceAttrSet(testInstanceNetworkResName, "ipv6.0.global.#"), + resource.TestCheckResourceAttrSet(testInstanceNetworkResName, "ipv6.0.link_local.%"), + resource.TestCheckResourceAttrSet(testInstanceNetworkResName, "ipv6.0.slaac.%"), + ), + }, + }, + }) +} diff --git a/linode/instancenetworking/tmpl/data_basic_reserved.gotf b/linode/instancenetworking/tmpl/data_basic_reserved.gotf new file mode 100644 index 000000000..0081c7edf --- /dev/null +++ b/linode/instancenetworking/tmpl/data_basic_reserved.gotf @@ -0,0 +1,22 @@ +{{ define "instance_networking_data_basic_with_reserved" }} + +resource "linode_instance" "foobar" { + label = "{{.Label}}" + type = "g6-nanode-1" + region = "{{ .Region }}" + image = "linode/debian12" +} + +resource "linode_networking_ip" "reserved_ip" { + region = "{{ .Region }}" + public = true + type = "ipv4" + reserved = true + linode_id = linode_instance.foobar.id +} + +data "linode_instance_networking" "test" { + linode_id = linode_instance.foobar.id +} + +{{ end }} diff --git a/linode/instancenetworking/tmpl/template.go b/linode/instancenetworking/tmpl/template.go index 73a46cd68..cb1828eda 100644 --- a/linode/instancenetworking/tmpl/template.go +++ b/linode/instancenetworking/tmpl/template.go @@ -30,3 +30,11 @@ func DataVPC(t *testing.T, label, region, subnetIPv4, interfaceIPv4 string) stri InterfaceIPv4: interfaceIPv4, }) } + +func DataBasic_withReservedField(t *testing.T, instanceLabel, region string) string { + return acceptance.ExecuteTemplate(t, + "instance_networking_data_basic_with_reserved", TemplateData{ + Label: instanceLabel, + Region: region, + }) +} diff --git a/linode/networkingips/datasource_test.go b/linode/networkingips/datasource_test.go index 357081ce5..e4ae86156 100644 --- a/linode/networkingips/datasource_test.go +++ b/linode/networkingips/datasource_test.go @@ -7,7 +7,6 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "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/networkingip/tmpl" @@ -24,68 +23,6 @@ func init() { testRegion = region } -func TestAccDataSourceNetworkingIP_basic(t *testing.T) { - t.Parallel() - - resourceName := "linode_instance.foobar" - dataResourceName := "data.linode_networking_ip.foobar" - - label := acctest.RandomWithPrefix("tf-test") - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, - - Steps: []resource.TestStep{ - { - Config: tmpl.DataBasic(t, label, testRegion), - }, - { - Config: tmpl.DataBasic(t, label, testRegion), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair(dataResourceName, "address", resourceName, "ip_address"), - resource.TestCheckResourceAttrPair(dataResourceName, "linode_id", resourceName, "id"), - resource.TestCheckResourceAttrPair(dataResourceName, "region", resourceName, "region"), - resource.TestMatchResourceAttr(dataResourceName, "gateway", regexp.MustCompile(`\.1$`)), - resource.TestCheckResourceAttr(dataResourceName, "type", "ipv4"), - resource.TestCheckResourceAttr(dataResourceName, "public", "true"), - resource.TestCheckResourceAttr(dataResourceName, "prefix", "24"), - resource.TestMatchResourceAttr(dataResourceName, "rdns", regexp.MustCompile(`.ip.linodeusercontent.com$`)), - ), - }, - }, - }) -} - -func TestAccDataSourceNetworkingIP_list(t *testing.T) { - t.Parallel() - - dataResourceName := "data.linode_networking_ip.list" - - 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(dataResourceName, "ip_addresses.#"), - resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.address"), - resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.linode_id"), - resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.region"), - resource.TestMatchResourceAttr(dataResourceName, "ip_addresses.0.gateway", regexp.MustCompile(`\.1$`)), - resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.type", "ipv4"), - resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.public", "true"), - resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.prefix", "24"), - resource.TestMatchResourceAttr(dataResourceName, "ip_addresses.0.rdns", regexp.MustCompile(`.ip.linodeusercontent.com$`)), - resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.subnet_mask"), - resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.reserved"), - ), - }, - }, - }) -} - func TestAccDataSourceNetworkingIP_filterReserved(t *testing.T) { t.Parallel() From 3fa103a5d1475bc4516437cdbdba50d2b867d13c Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Wed, 30 Oct 2024 21:18:49 -0400 Subject: [PATCH 39/49] Added test for filterig list of ip address on the reserved field --- linode/networkingips/datasource_test.go | 29 ++++++++++++++++++++++ linode/networkingips/tmpl/data_filter.gotf | 7 ++++++ linode/networkingips/tmpl/data_list.gotf | 6 +++++ linode/networkingips/tmpl/template.go | 15 +++++++++++ 4 files changed, 57 insertions(+) create mode 100644 linode/networkingips/tmpl/data_filter.gotf create mode 100644 linode/networkingips/tmpl/data_list.gotf create mode 100644 linode/networkingips/tmpl/template.go diff --git a/linode/networkingips/datasource_test.go b/linode/networkingips/datasource_test.go index e4ae86156..3ef1edf91 100644 --- a/linode/networkingips/datasource_test.go +++ b/linode/networkingips/datasource_test.go @@ -23,6 +23,35 @@ func init() { testRegion = region } +func TestAccDataSourceNetworkingIP_list(t *testing.T) { + t.Parallel() + + dataResourceName := "data.linode_networking_ip.list" + + 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(dataResourceName, "ip_addresses.#"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.address"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.linode_id"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.region"), + resource.TestMatchResourceAttr(dataResourceName, "ip_addresses.0.gateway", regexp.MustCompile(`\.1$`)), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.type", "ipv4"), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.public", "true"), + resource.TestCheckResourceAttr(dataResourceName, "ip_addresses.0.prefix", "24"), + resource.TestMatchResourceAttr(dataResourceName, "ip_addresses.0.rdns", regexp.MustCompile(`.ip.linodeusercontent.com$`)), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.subnet_mask"), + resource.TestCheckResourceAttrSet(dataResourceName, "ip_addresses.0.reserved"), + ), + }, + }, + }) +} + func TestAccDataSourceNetworkingIP_filterReserved(t *testing.T) { t.Parallel() diff --git a/linode/networkingips/tmpl/data_filter.gotf b/linode/networkingips/tmpl/data_filter.gotf new file mode 100644 index 000000000..963b5a9ae --- /dev/null +++ b/linode/networkingips/tmpl/data_filter.gotf @@ -0,0 +1,7 @@ +{{ define "networking_ip_data_filtered" }} + +data "linode_networking_ip" "filtered" { + filter_reserved = true +} + +{{ end }} \ No newline at end of file diff --git a/linode/networkingips/tmpl/data_list.gotf b/linode/networkingips/tmpl/data_list.gotf new file mode 100644 index 000000000..1121bdcb2 --- /dev/null +++ b/linode/networkingips/tmpl/data_list.gotf @@ -0,0 +1,6 @@ +{{ define "networking_ip_data_list" }} + +data "linode_networking_ip" "list" { +} + +{{ end }} \ No newline at end of file diff --git a/linode/networkingips/tmpl/template.go b/linode/networkingips/tmpl/template.go new file mode 100644 index 000000000..9050c98e6 --- /dev/null +++ b/linode/networkingips/tmpl/template.go @@ -0,0 +1,15 @@ +package tmpl + +import ( + "testing" + + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" +) + +func DataList(t *testing.T) string { + return acceptance.ExecuteTemplate(t, "networking_ip_data_list", nil) +} + +func DataFilterReserved(t *testing.T) string { + return acceptance.ExecuteTemplate(t, "networking_ip_data_filtered", nil) +} From 7e831bbbaaf686b1866846e9b42d98900515baa4 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Wed, 30 Oct 2024 21:31:42 -0400 Subject: [PATCH 40/49] made changes to TestAccDataSourceNetworkingIP_list, TestAccDataSourceNetworkingIP_filterReserved tests --- linode/networkingips/datasource_test.go | 6 +++--- linode/networkingips/tmpl/data_filter.gotf | 2 +- linode/networkingips/tmpl/data_list.gotf | 2 +- linode/networkingips/tmpl/template.go | 5 +++++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/linode/networkingips/datasource_test.go b/linode/networkingips/datasource_test.go index 3ef1edf91..a0c7f72b7 100644 --- a/linode/networkingips/datasource_test.go +++ b/linode/networkingips/datasource_test.go @@ -9,7 +9,7 @@ import ( "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/networkingip/tmpl" + "github.com/linode/terraform-provider-linode/v2/linode/networkingips/tmpl" ) var testRegion string @@ -26,7 +26,7 @@ func init() { func TestAccDataSourceNetworkingIP_list(t *testing.T) { t.Parallel() - dataResourceName := "data.linode_networking_ip.list" + dataResourceName := "data.linode_networking_ips.list" resource.Test(t, resource.TestCase{ PreCheck: func() { acceptance.PreCheck(t) }, @@ -55,7 +55,7 @@ func TestAccDataSourceNetworkingIP_list(t *testing.T) { func TestAccDataSourceNetworkingIP_filterReserved(t *testing.T) { t.Parallel() - dataResourceName := "data.linode_networking_ip.filtered" + dataResourceName := "data.linode_networking_ips.filtered" resource.Test(t, resource.TestCase{ PreCheck: func() { acceptance.PreCheck(t) }, diff --git a/linode/networkingips/tmpl/data_filter.gotf b/linode/networkingips/tmpl/data_filter.gotf index 963b5a9ae..bb982222e 100644 --- a/linode/networkingips/tmpl/data_filter.gotf +++ b/linode/networkingips/tmpl/data_filter.gotf @@ -1,6 +1,6 @@ {{ define "networking_ip_data_filtered" }} -data "linode_networking_ip" "filtered" { +data "linode_networking_ips" "filtered" { filter_reserved = true } diff --git a/linode/networkingips/tmpl/data_list.gotf b/linode/networkingips/tmpl/data_list.gotf index 1121bdcb2..9258eb4dd 100644 --- a/linode/networkingips/tmpl/data_list.gotf +++ b/linode/networkingips/tmpl/data_list.gotf @@ -1,6 +1,6 @@ {{ define "networking_ip_data_list" }} -data "linode_networking_ip" "list" { +data "linode_networking_ips" "list" { } {{ end }} \ No newline at end of file diff --git a/linode/networkingips/tmpl/template.go b/linode/networkingips/tmpl/template.go index 9050c98e6..56749df79 100644 --- a/linode/networkingips/tmpl/template.go +++ b/linode/networkingips/tmpl/template.go @@ -6,6 +6,11 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/acceptance" ) +type TemplateData struct { + Label string + Region string +} + func DataList(t *testing.T) string { return acceptance.ExecuteTemplate(t, "networking_ip_data_list", nil) } From e82cc76956f43279191714c2863aa8c125198bb2 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 31 Oct 2024 12:11:34 -0400 Subject: [PATCH 41/49] Added test for reserved IP creation and allocation --- linode/networkingip/resource_test.go | 49 +++++++++++++++++++ .../tmpl/resource_reserved_assigned.gotf | 18 +++++++ linode/networkingip/tmpl/template.go | 35 +++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 linode/networkingip/resource_test.go create mode 100644 linode/networkingip/tmpl/resource_reserved_assigned.gotf diff --git a/linode/networkingip/resource_test.go b/linode/networkingip/resource_test.go new file mode 100644 index 000000000..616e392f3 --- /dev/null +++ b/linode/networkingip/resource_test.go @@ -0,0 +1,49 @@ +//go:build integration || networkingip + +package networkingip_test + +import ( + "log" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "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/networkingip/tmpl" +) + +func init() { + region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") + if err != nil { + log.Fatal(err) + } + + testRegion = region +} + +func TestAccResourceNetworkingIP_reserved(t *testing.T) { + t.Parallel() + + label := acctest.RandomWithPrefix("tf-test") + + resourceName := "linode_networking_ip.reserved_ip" + instanceResourceName := "linode_instance.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: tmpl.NetworkingIPReservedAssigned(t, label, testRegion), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "reserved", "true"), + resource.TestCheckResourceAttr(resourceName, "public", "true"), + resource.TestCheckResourceAttr(resourceName, "type", "ipv4"), + resource.TestCheckResourceAttrSet(resourceName, "address"), + resource.TestCheckResourceAttrSet(resourceName, "region"), + resource.TestCheckResourceAttrPair(resourceName, "linode_id", instanceResourceName, "id"), + ), + }, + }, + }) +} diff --git a/linode/networkingip/tmpl/resource_reserved_assigned.gotf b/linode/networkingip/tmpl/resource_reserved_assigned.gotf new file mode 100644 index 000000000..9062950dd --- /dev/null +++ b/linode/networkingip/tmpl/resource_reserved_assigned.gotf @@ -0,0 +1,18 @@ +{{ define "networking_ip_reserved_assigned" }} + +resource "linode_instance" "test" { + label = "{{.Label}}" + group = "tf_test" + type = "g6-nanode-1" + region = "{{ .Region }}" + image = "linode/debian12" +} + +resource "linode_networking_ip" "reserved_ip" { + linode_id = linode_instance.test.id + public = true + type = "ipv4" + reserved = true +} + +{{ end }} \ No newline at end of file diff --git a/linode/networkingip/tmpl/template.go b/linode/networkingip/tmpl/template.go index b0e78e177..2c14ae962 100644 --- a/linode/networkingip/tmpl/template.go +++ b/linode/networkingip/tmpl/template.go @@ -15,3 +15,38 @@ func DataBasic(t *testing.T, label, region string) string { return acceptance.ExecuteTemplate(t, "networking_ip_data_basic", TemplateData{Label: label, Region: region}) } + +func NetworkingIPReservedAssigned(t *testing.T, label string, region string) string { + return acceptance.ExecuteTemplate(t, + "networking_ip_reserved_assigned", + TemplateData{ + Label: label, + Region: region, + }) +} + +func NetworkingIPReserved(t *testing.T, region string) string { + return acceptance.ExecuteTemplate(t, + "networking_ip_reserved", + TemplateData{ + Region: region, + }) +} + +func NetworkingIPEphemeral(t *testing.T, label string, region string) string { + return acceptance.ExecuteTemplate(t, + "networking_ip_ephemeral", + TemplateData{ + Label: label, + Region: region, + }) +} + +func NetworkingIPEphemeralAssigned(t *testing.T, label string, region string) string { + return acceptance.ExecuteTemplate(t, + "networking_ip_ephemeral_assigned", + TemplateData{ + Label: label, + Region: region, + }) +} From 09617296470f848d16564e3fc15c9cc92d0e5725 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 31 Oct 2024 12:13:27 -0400 Subject: [PATCH 42/49] made change to template function --- linode/networkingip/tmpl/template.go | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/linode/networkingip/tmpl/template.go b/linode/networkingip/tmpl/template.go index 2c14ae962..32a4f43bf 100644 --- a/linode/networkingip/tmpl/template.go +++ b/linode/networkingip/tmpl/template.go @@ -24,29 +24,3 @@ func NetworkingIPReservedAssigned(t *testing.T, label string, region string) str Region: region, }) } - -func NetworkingIPReserved(t *testing.T, region string) string { - return acceptance.ExecuteTemplate(t, - "networking_ip_reserved", - TemplateData{ - Region: region, - }) -} - -func NetworkingIPEphemeral(t *testing.T, label string, region string) string { - return acceptance.ExecuteTemplate(t, - "networking_ip_ephemeral", - TemplateData{ - Label: label, - Region: region, - }) -} - -func NetworkingIPEphemeralAssigned(t *testing.T, label string, region string) string { - return acceptance.ExecuteTemplate(t, - "networking_ip_ephemeral_assigned", - TemplateData{ - Label: label, - Region: region, - }) -} From 92fc8160ad0e5fa11197d37ccd9d4d5f7b9ae8dd Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 4 Nov 2024 11:17:56 -0500 Subject: [PATCH 43/49] added test for network_ip_assign resource --- linode/networkipassignment/resource_test.go | 96 +++++++++++++++++++ .../tmpl/network_ip_assign.gotf | 0 linode/networkipassignment/tmpl/template.go | 21 ++++ 3 files changed, 117 insertions(+) create mode 100644 linode/networkipassignment/resource_test.go create mode 100644 linode/networkipassignment/tmpl/network_ip_assign.gotf create mode 100644 linode/networkipassignment/tmpl/template.go diff --git a/linode/networkipassignment/resource_test.go b/linode/networkipassignment/resource_test.go new file mode 100644 index 000000000..c66121b3c --- /dev/null +++ b/linode/networkipassignment/resource_test.go @@ -0,0 +1,96 @@ +package networkipassignment_test + +import ( + "context" + "fmt" + "log" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/linode/linodego" + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" + "github.com/linode/terraform-provider-linode/v2/linode/helper" + "github.com/linode/terraform-provider-linode/v2/linode/networkipassignment/tmpl" +) + +var testRegion string + +func init() { + region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") + if err != nil { + log.Fatal(err) + } + + testRegion = region +} + +func TestAccResourceNetworkingIPsAssign(t *testing.T) { + t.Parallel() + + resourceName := "linode_networking_ips_assign.test" + instanceName := acctest.RandomWithPrefix("tf_test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: checkNetworkingIPsAssignDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.NetworkingIPsAssign(t, instanceName, testRegion), + Check: resource.ComposeTestCheckFunc( + checkNetworkingIPsAssignExists, + resource.TestCheckResourceAttrSet(resourceName, "region"), + resource.TestCheckResourceAttr(resourceName, "assignments.#", "2"), + resource.TestCheckResourceAttrSet(resourceName, "assignments.0.linode_id"), + resource.TestCheckResourceAttrSet(resourceName, "assignments.0.address"), + resource.TestCheckResourceAttrSet(resourceName, "assignments.1.linode_id"), + resource.TestCheckResourceAttrSet(resourceName, "assignments.1.address"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func checkNetworkingIPsAssignExists(s *terraform.State) error { + client := acceptance.TestAccProvider.Meta().(*helper.ProviderMeta).Client + + for _, rs := range s.RootModule().Resources { + if rs.Type != "linode_networking_ips_assign" { + continue + } + + _, err := client.GetIPAddress(context.Background(), rs.Primary.ID) + if err != nil { + return fmt.Errorf("Error retrieving state of Networking IPs Assign %s: %s", rs.Primary.Attributes["id"], err) + } + } + + return nil +} + +func checkNetworkingIPsAssignDestroy(s *terraform.State) error { + client := acceptance.TestAccProvider.Meta().(*helper.ProviderMeta).Client + for _, rs := range s.RootModule().Resources { + if rs.Type != "linode_networking_ips_assign" { + continue + } + + _, err := client.GetIPAddress(context.Background(), rs.Primary.ID) + if err == nil { + return fmt.Errorf("Networking IPs Assign with id %s still exists", rs.Primary.ID) + } + + if apiErr, ok := err.(*linodego.Error); ok && apiErr.Code != 404 { + return fmt.Errorf("Error requesting Networking IPs Assign with id %s", rs.Primary.ID) + } + } + + return nil +} diff --git a/linode/networkipassignment/tmpl/network_ip_assign.gotf b/linode/networkipassignment/tmpl/network_ip_assign.gotf new file mode 100644 index 000000000..e69de29bb diff --git a/linode/networkipassignment/tmpl/template.go b/linode/networkipassignment/tmpl/template.go new file mode 100644 index 000000000..740700045 --- /dev/null +++ b/linode/networkipassignment/tmpl/template.go @@ -0,0 +1,21 @@ +package tmpl + +import ( + "testing" + + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" +) + +type TemplateData struct { + Label string + Region string +} + +func NetworkingIPsAssign(t *testing.T, label string, region string) string { + return acceptance.ExecuteTemplate(t, + "networking_ips_assign", + TemplateData{ + Label: label, + Region: region, + }) +} From 0c8e68db21f1f2fdbbc75094e14ecdfa53f1a656 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Mon, 4 Nov 2024 17:15:34 -0500 Subject: [PATCH 44/49] made changes to .gotf file for the resource_test --- .../tmpl/network_ip_assign.gotf | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/linode/networkipassignment/tmpl/network_ip_assign.gotf b/linode/networkipassignment/tmpl/network_ip_assign.gotf index e69de29bb..8d6d08821 100644 --- a/linode/networkipassignment/tmpl/network_ip_assign.gotf +++ b/linode/networkipassignment/tmpl/network_ip_assign.gotf @@ -0,0 +1,43 @@ +{{ define "networking_ips_assign" }} + +resource "linode_instance" "test1" { + label = "{{ .Label }}-1" + type = "g6-nanode-1" + region = "{{ .Region }}" + image = "linode/alpine3.19" +} + +resource "linode_instance" "test2" { + label = "{{ .Label }}-2" + type = "g6-nanode-1" + region = "{{ .Region }}" + image = "linode/alpine3.19" +} + +resource "linode_networking_ip" "reserved_ip1" { + linode_id = linode_instance.test1.id + public = true + type = "ipv4" + reserved = true +} + +resource "linode_networking_ip" "reserved_ip2" { + linode_id = linode_instance.test2.id + public = true + type = "ipv4" + reserved = true +} + +resource "linode_networking_assign_ip" "test" { + region = "{{ .Region }}" + assignments { + linode_id = linode_instance.test1.id + address = linode_networking_ip.reserved_ip1.address + } + assignments { + linode_id = linode_instance.test2.id + address = linode_networking_ip.reserved_ip2.address + } +} + +{{ end }} \ No newline at end of file From 95619aeb6089993e1275982956faf034fc3f377f Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 5 Nov 2024 10:18:04 -0500 Subject: [PATCH 45/49] added more conditions to the existing multiple IP assign test --- .../networkipassignment/framework_resource.go | 104 +++++++----------- .../framework_resource_schema.go | 1 - linode/networkipassignment/resource_test.go | 33 ++++-- .../tmpl/network_ip_assign.gotf | 34 +++--- 4 files changed, 80 insertions(+), 92 deletions(-) diff --git a/linode/networkipassignment/framework_resource.go b/linode/networkipassignment/framework_resource.go index 06b25cb5a..07b57f0c6 100644 --- a/linode/networkipassignment/framework_resource.go +++ b/linode/networkipassignment/framework_resource.go @@ -28,7 +28,7 @@ type Resource struct { } func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - tflog.Debug(ctx, "Create linode_networking_ip") + tflog.Debug(ctx, "Create linode_networking_assign_ip") var plan NetworkingIPModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) @@ -39,7 +39,6 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp client := r.Meta.Client if len(plan.Assignments) > 0 { - // Handle IP assignment tflog.Info(ctx, "Assigning IP addresses", map[string]interface{}{ "assignments": plan.Assignments, }) @@ -66,15 +65,8 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp return } - // Only set the necessary fields for IP assignment plan.ID = types.StringValue(plan.Assignments[0].Address.ValueString()) - - plan.Assignments = plan.Assignments - plan.Reserved = plan.Reserved - // plan.Address = plan.Assignments[0].Address - plan.LinodeID = plan.Assignments[0].LinodeID plan.Region = types.StringValue(assignOpts.Region) - } resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) @@ -91,64 +83,50 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res client := r.Meta.Client - // Fetch the IP address details - ip, err := client.GetIPAddress(ctx, state.ID.ValueString()) - if err != nil { - if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { - resp.Diagnostics.AddWarning( - "IP Address No Longer Exists", - fmt.Sprintf("Removing IP address %s from state because it no longer exists", state.ID.ValueString()), + // Initialize a slice to store all assignments + allAssignments := []AssignmentModel{} + + // Iterate through all assignments in the current state + for _, assignment := range state.Assignments { + ip, err := client.GetIPAddress(ctx, assignment.Address.ValueString()) + if err != nil { + if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { + // If the IP is not found, skip it + continue + } + resp.Diagnostics.AddError( + "Error reading IP Address", + fmt.Sprintf("Could not read IP address %s: %s", assignment.Address.ValueString(), err), ) - resp.State.RemoveResource(ctx) return } - resp.Diagnostics.AddError( - "Error reading IP Address", - fmt.Sprintf("Could not read IP address %s: %s", state.ID.ValueString(), err), - ) - return - } - // Update state with the latest information - state.ID = types.StringValue(ip.Address) - state.Address = types.StringValue(ip.Address) - state.LinodeID = types.Int64Value(int64(ip.LinodeID)) - state.Region = types.StringValue(ip.Region) - state.Public = types.BoolValue(ip.Public) - state.Type = types.StringValue(string(ip.Type)) - state.Reserved = types.BoolValue(ip.Reserved) - - // Handle assignments - if ip.LinodeID != 0 { - state.Assignments = []AssignmentModel{ - { - Address: types.StringValue(ip.Address), - LinodeID: types.Int64Value(int64(ip.LinodeID)), - }, + // Add the assignment to our slice + allAssignments = append(allAssignments, AssignmentModel{ + Address: types.StringValue(ip.Address), + LinodeID: types.Int64Value(int64(ip.LinodeID)), + }) + + // If this is the first assignment, use it to set the main resource attributes + if len(allAssignments) == 1 { + state.ID = types.StringValue(ip.Address) + state.Address = types.StringValue(ip.Address) + state.LinodeID = types.Int64Value(int64(ip.LinodeID)) + state.Region = types.StringValue(ip.Region) + state.Public = types.BoolValue(ip.Public) + state.Type = types.StringValue(string(ip.Type)) + state.Reserved = types.BoolValue(ip.Reserved) } - } else { - state.Assignments = []AssignmentModel{} } - // Ensure all computed fields are set, even if they're empty or zero values - if state.Region.IsNull() { - state.Region = types.StringValue("") - } - if state.Public.IsNull() { - state.Public = types.BoolValue(false) - } - if state.Type.IsNull() { - state.Type = types.StringValue("") - } - if state.Reserved.IsNull() { - state.Reserved = types.BoolValue(false) - } + // Update the state with all assignments + state.Assignments = allAssignments resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - tflog.Debug(ctx, "Update linode_networking_ip") + tflog.Debug(ctx, "Update linode_networking_assign_ip") var plan, state NetworkingIPModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) @@ -188,22 +166,16 @@ func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp plan.Address = plan.Assignments[0].Address } - // Re-read the IP address to get the latest state - ip, err := client.GetIPAddress(ctx, plan.Address.ValueString()) - if err != nil { - resp.Diagnostics.AddError( - "Error reading updated IP Address", - fmt.Sprintf("Could not read updated IP address %s: %s", plan.Address.ValueString(), err), - ) + // Re-read the state to get the latest information + readResp := resource.ReadResponse{State: resp.State} + if readResp.Diagnostics.HasError() { + resp.Diagnostics.Append(readResp.Diagnostics...) return } - plan.FlattenIPAddress(ip) - - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) } func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - tflog.Debug(ctx, "Delete linode_networking_ip") + tflog.Debug(ctx, "Delete linode_networking_assign_ip") var state NetworkingIPModel resp.Diagnostics.Append(req.State.Get(ctx, &state)...) diff --git a/linode/networkipassignment/framework_resource_schema.go b/linode/networkipassignment/framework_resource_schema.go index df7195ca2..518e1fa09 100644 --- a/linode/networkipassignment/framework_resource_schema.go +++ b/linode/networkipassignment/framework_resource_schema.go @@ -20,7 +20,6 @@ var frameworkResourceSchema = schema.Schema{ "linode_id": schema.Int64Attribute{ Description: "The ID of the Linode to allocate an IPv4 address for. Required when reserved is false or not set.", Optional: true, - Computed: true, }, "reserved": schema.BoolAttribute{ Description: "Whether the IPv4 address should be reserved.", diff --git a/linode/networkipassignment/resource_test.go b/linode/networkipassignment/resource_test.go index c66121b3c..73936d84c 100644 --- a/linode/networkipassignment/resource_test.go +++ b/linode/networkipassignment/resource_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "testing" + "time" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -29,7 +30,7 @@ func init() { func TestAccResourceNetworkingIPsAssign(t *testing.T) { t.Parallel() - resourceName := "linode_networking_ips_assign.test" + resourceName := "linode_networking_assign_ip.test" instanceName := acctest.RandomWithPrefix("tf_test") resource.Test(t, resource.TestCase{ @@ -40,13 +41,15 @@ func TestAccResourceNetworkingIPsAssign(t *testing.T) { { Config: tmpl.NetworkingIPsAssign(t, instanceName, testRegion), Check: resource.ComposeTestCheckFunc( - checkNetworkingIPsAssignExists, resource.TestCheckResourceAttrSet(resourceName, "region"), - resource.TestCheckResourceAttr(resourceName, "assignments.#", "2"), + resource.TestCheckResourceAttrSet(resourceName, "assignments.#"), + func(*terraform.State) error { + time.Sleep(30 * time.Second) // Add a delay to allow for API propagation + return nil + }, + checkNetworkingIPsAssignExists, resource.TestCheckResourceAttrSet(resourceName, "assignments.0.linode_id"), resource.TestCheckResourceAttrSet(resourceName, "assignments.0.address"), - resource.TestCheckResourceAttrSet(resourceName, "assignments.1.linode_id"), - resource.TestCheckResourceAttrSet(resourceName, "assignments.1.address"), ), }, { @@ -62,13 +65,25 @@ func checkNetworkingIPsAssignExists(s *terraform.State) error { client := acceptance.TestAccProvider.Meta().(*helper.ProviderMeta).Client for _, rs := range s.RootModule().Resources { - if rs.Type != "linode_networking_ips_assign" { + if rs.Type != "linode_networking_assign_ip" { continue } - _, err := client.GetIPAddress(context.Background(), rs.Primary.ID) + filter := fmt.Sprintf("{\"region\": \"%s\"}", rs.Primary.Attributes["region"]) + ips, err := client.ListIPAddresses(context.Background(), &linodego.ListOptions{Filter: filter}) if err != nil { - return fmt.Errorf("Error retrieving state of Networking IPs Assign %s: %s", rs.Primary.Attributes["id"], err) + return fmt.Errorf("Error listing IP addresses: %s", err) + } + + assignmentCount := 0 + for _, ip := range ips { + if ip.LinodeID != 0 { + assignmentCount++ + } + } + + if assignmentCount == 0 { + return fmt.Errorf("No IP assignments found") } } @@ -78,7 +93,7 @@ func checkNetworkingIPsAssignExists(s *terraform.State) error { func checkNetworkingIPsAssignDestroy(s *terraform.State) error { client := acceptance.TestAccProvider.Meta().(*helper.ProviderMeta).Client for _, rs := range s.RootModule().Resources { - if rs.Type != "linode_networking_ips_assign" { + if rs.Type != "linode_networking_assign_ip" { continue } diff --git a/linode/networkipassignment/tmpl/network_ip_assign.gotf b/linode/networkipassignment/tmpl/network_ip_assign.gotf index 8d6d08821..23a243c01 100644 --- a/linode/networkipassignment/tmpl/network_ip_assign.gotf +++ b/linode/networkipassignment/tmpl/network_ip_assign.gotf @@ -15,29 +15,31 @@ resource "linode_instance" "test2" { } resource "linode_networking_ip" "reserved_ip1" { - linode_id = linode_instance.test1.id - public = true - type = "ipv4" - reserved = true + public = true + type = "ipv4" + region = "{{ .Region }}" + reserved = true } resource "linode_networking_ip" "reserved_ip2" { - linode_id = linode_instance.test2.id - public = true - type = "ipv4" - reserved = true + public = true + type = "ipv4" + region = "{{ .Region }}" + reserved = true } resource "linode_networking_assign_ip" "test" { region = "{{ .Region }}" - assignments { - linode_id = linode_instance.test1.id - address = linode_networking_ip.reserved_ip1.address - } - assignments { - linode_id = linode_instance.test2.id - address = linode_networking_ip.reserved_ip2.address - } + assignments = [ + { + linode_id = linode_instance.test1.id + address = linode_networking_ip.reserved_ip1.address + }, + { + linode_id = linode_instance.test2.id + address = linode_networking_ip.reserved_ip2.address + } + ] } {{ end }} \ No newline at end of file From 3bccbf75bc3c74d3060f885aeb8fe42cd6a1840a Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Thu, 7 Nov 2024 18:47:56 -0500 Subject: [PATCH 46/49] removing duplicate resource for adding reserved IP to linodes --- linode/framework_provider.go | 2 - linode/instancereservedip/framework_models.go | 114 ------- .../instancereservedip/framework_resource.go | 303 ------------------ linode/instancereservedip/framework_schema.go | 114 ------- linode/instancereservedip/resource_test.go | 58 ---- .../tmpl/AddReservedIPToInstance.gotf | 17 - linode/instancereservedip/tmpl/template.go | 23 -- 7 files changed, 631 deletions(-) delete mode 100644 linode/instancereservedip/framework_models.go delete mode 100644 linode/instancereservedip/framework_resource.go delete mode 100644 linode/instancereservedip/framework_schema.go delete mode 100644 linode/instancereservedip/resource_test.go delete mode 100644 linode/instancereservedip/tmpl/AddReservedIPToInstance.gotf delete mode 100644 linode/instancereservedip/tmpl/template.go diff --git a/linode/framework_provider.go b/linode/framework_provider.go index 82e8560a6..7077df18f 100644 --- a/linode/framework_provider.go +++ b/linode/framework_provider.go @@ -237,10 +237,8 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res volume.NewResource, vpc.NewResource, vpcsubnet.NewResource, - networkreservedip.NewResource, networkingip.NewResource, networkipassignment.NewResource, - } } diff --git a/linode/instancereservedip/framework_models.go b/linode/instancereservedip/framework_models.go deleted file mode 100644 index cce7479f6..000000000 --- a/linode/instancereservedip/framework_models.go +++ /dev/null @@ -1,114 +0,0 @@ -package instancereservedip - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "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" - "github.com/linode/terraform-provider-linode/v2/linode/instancenetworking" -) - -type IPVPCNAT1To1Model struct { - Address types.String `tfsdk:"address"` - SubnetID types.Int64 `tfsdk:"subnet_id"` - VPCID types.Int64 `tfsdk:"vpc_id"` -} - -type InstanceIPModel struct { - ID types.String `tfsdk:"id"` - LinodeID types.Int64 `tfsdk:"linode_id"` - Public types.Bool `tfsdk:"public"` - Address types.String `tfsdk:"address"` - Gateway types.String `tfsdk:"gateway"` - Prefix types.Int64 `tfsdk:"prefix"` - RDNS types.String `tfsdk:"rdns"` - Region types.String `tfsdk:"region"` - SubnetMask types.String `tfsdk:"subnet_mask"` - Type types.String `tfsdk:"type"` - ApplyImmediately types.Bool `tfsdk:"apply_immediately"` - IPVPCNAT1To1 types.List `tfsdk:"vpc_nat_1_1"` - Reserved types.Bool `tfsdk:"reserved"` -} - -func (m *InstanceIPModel) FlattenInstanceIP( - ctx context.Context, - ip linodego.InstanceIP, - preserveKnown bool, -) diag.Diagnostics { - var diags diag.Diagnostics - - m.ID = helper.KeepOrUpdateString(m.ID, ip.Address, preserveKnown) - m.LinodeID = helper.KeepOrUpdateInt64(m.LinodeID, int64(ip.LinodeID), preserveKnown) - m.Public = helper.KeepOrUpdateBool(m.Public, ip.Public, preserveKnown) - m.Address = helper.KeepOrUpdateString(m.Address, ip.Address, preserveKnown) - m.Gateway = helper.KeepOrUpdateString(m.Gateway, ip.Gateway, preserveKnown) - m.Prefix = helper.KeepOrUpdateInt64(m.Prefix, int64(ip.Prefix), preserveKnown) - - m.RDNS = helper.KeepOrUpdateString(m.RDNS, ip.RDNS, preserveKnown) - m.Reserved = helper.KeepOrUpdateBool(m.Reserved, ip.Reserved, preserveKnown) - - m.Region = helper.KeepOrUpdateString(m.Region, ip.Region, preserveKnown) - m.SubnetMask = helper.KeepOrUpdateString(m.SubnetMask, ip.SubnetMask, preserveKnown) - m.Type = helper.KeepOrUpdateString(m.Type, string(ip.Type), preserveKnown) - - var resultList types.List - if ip.VPCNAT1To1 == nil { - resultList = types.ListNull(instancenetworking.VPCNAT1To1Type) - } else { - vpcNAT1To1, diags := instancenetworking.FlattenIPVPCNAT1To1(ip.VPCNAT1To1) - diags.Append(diags...) - if diags.HasError() { - return diags - } - - var listDiags diag.Diagnostics - resultList, listDiags = types.ListValue( - instancenetworking.VPCNAT1To1Type, - []attr.Value{vpcNAT1To1}, - ) - diags.Append(listDiags...) - if diags.HasError() { - return diags - } - } - m.IPVPCNAT1To1 = helper.KeepOrUpdateValue( - m.IPVPCNAT1To1, - resultList, - preserveKnown, - ) - - return diags -} - -func (m *InstanceIPModel) CopyFrom( - ctx context.Context, - other InstanceIPModel, - preserveKnown bool, -) diag.Diagnostics { - var diags diag.Diagnostics - - m.ID = helper.KeepOrUpdateValue(m.ID, other.Address, preserveKnown) - m.LinodeID = helper.KeepOrUpdateValue(m.LinodeID, other.LinodeID, preserveKnown) - m.Public = helper.KeepOrUpdateValue(m.Public, other.Public, preserveKnown) - m.Address = helper.KeepOrUpdateValue(m.Address, other.Address, preserveKnown) - m.Gateway = helper.KeepOrUpdateValue(m.Gateway, other.Gateway, preserveKnown) - m.Prefix = helper.KeepOrUpdateValue(m.Prefix, other.Prefix, preserveKnown) - - m.RDNS = helper.KeepOrUpdateValue(m.RDNS, other.RDNS, preserveKnown) - m.Reserved = helper.KeepOrUpdateValue(m.Reserved, other.Reserved, preserveKnown) - - m.Region = helper.KeepOrUpdateValue(m.Region, other.Region, preserveKnown) - m.SubnetMask = helper.KeepOrUpdateValue(m.SubnetMask, other.SubnetMask, preserveKnown) - m.Type = helper.KeepOrUpdateValue(m.Type, other.Type, preserveKnown) - m.IPVPCNAT1To1 = helper.KeepOrUpdateValue( - m.IPVPCNAT1To1, - other.IPVPCNAT1To1, - preserveKnown, - ) - - return diags -} diff --git a/linode/instancereservedip/framework_resource.go b/linode/instancereservedip/framework_resource.go deleted file mode 100644 index 88a916fcb..000000000 --- a/linode/instancereservedip/framework_resource.go +++ /dev/null @@ -1,303 +0,0 @@ -package instancereservedip - -import ( - "context" - "fmt" - "time" - - "github.com/hashicorp/terraform-plugin-framework/resource" - "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 NewResource() resource.Resource { - return &Resource{ - BaseResource: helper.NewBaseResource( - helper.BaseResourceConfig{ - Name: "linode_instance_reserved_ip", - IDType: types.StringType, - Schema: &frameworkResourceSchema, - }, - ), - } -} - -type Resource struct { - helper.BaseResource -} - -func (r *Resource) Create( - ctx context.Context, - req resource.CreateRequest, - resp *resource.CreateResponse, -) { - tflog.Debug(ctx, "Create linode_instance_ip") - var plan InstanceIPModel - - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - - ctx = populateLogAttributes(ctx, &plan) - - linodeID := helper.FrameworkSafeInt64ToInt( - plan.LinodeID.ValueInt64(), - &resp.Diagnostics, - ) - if resp.Diagnostics.HasError() { - return - } - - isPublic := plan.Public.ValueBool() - - client := r.Meta.Client - var ip *linodego.InstanceIP - var err error - - if !plan.Address.IsNull() && !plan.Address.IsUnknown() { - // Assign a reserved IP - createOpts := linodego.InstanceReserveIPOptions{ - Type: "ipv4", - Public: isPublic, - Address: plan.Address.ValueString(), - } - ip, err = client.AssignInstanceReservedIP(ctx, linodeID, createOpts) - if err != nil { - resp.Diagnostics.AddError( - fmt.Sprintf("Failed to assign reserved IP to instance (%d)", linodeID), - err.Error(), - ) - return - } - } - - if !plan.RDNS.IsNull() && !plan.RDNS.IsUnknown() { - rdns := plan.RDNS.ValueString() - - options := linodego.IPAddressUpdateOptions{ - RDNS: &rdns, - } - - tflog.Debug(ctx, "client.UpdateIPAddress(...)", map[string]any{ - "options": options, - }) - - if _, err := client.UpdateIPAddress(ctx, ip.Address, options); err != nil { - resp.Diagnostics.AddError( - fmt.Sprintf( - "failed to set RDNS for instance (%d) ip (%s)", - linodeID, ip.Address, - ), - err.Error(), - ) - return - } - } - - resp.Diagnostics.Append(plan.FlattenInstanceIP(ctx, *ip, true)...) - if resp.Diagnostics.HasError() { - return - } - - // IDs should always be overridden during creation (see #1085) - // TODO: Remove when Crossplane empty string ID issue is resolved - plan.ID = types.StringValue(ip.Address) - - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - - if plan.ApplyImmediately.ValueBool() { - tflog.Debug(ctx, "Attempting apply_immediately") - - instance, err := client.GetInstance(ctx, linodeID) - if err != nil { - resp.Diagnostics.AddError("Failed to Get Linode Instance", err.Error()) - return - } - - if instance.Status == linodego.InstanceRunning { - tflog.Info(ctx, "detected instance in running status, rebooting instance") - ctx, cancel := context.WithTimeout(ctx, time.Duration(600)*time.Second) - resp.Diagnostics.Append(helper.FrameworkRebootInstance(ctx, linodeID, client, 0)...) - cancel() - } else { - tflog.Info(ctx, "Detected instance not in running status, can't perform a reboot.") - } - } -} - -func (r *Resource) Read( - ctx context.Context, - req resource.ReadRequest, - resp *resource.ReadResponse, -) { - tflog.Debug(ctx, "Read linode_instance_ip") - var state InstanceIPModel - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - if helper.FrameworkAttemptRemoveResourceForEmptyID(ctx, state.ID, resp) { - return - } - - ctx = populateLogAttributes(ctx, &state) - - client := r.Meta.Client - address := state.Address.ValueString() - linodeID := helper.FrameworkSafeInt64ToInt( - state.LinodeID.ValueInt64(), - &resp.Diagnostics, - ) - if resp.Diagnostics.HasError() { - return - } - - ip, err := client.GetInstanceIPAddress(ctx, linodeID, address) - if err != nil { - if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { - resp.Diagnostics.AddWarning( - "Instance IP No Longer Exists", - fmt.Sprintf( - "Removing instance IP %s from state because it no longer exists", - state.ID.ValueString(), - ), - ) - resp.State.RemoveResource(ctx) - return - } - resp.Diagnostics.AddError( - "Unable to Refresh the Instance IP", - fmt.Sprintf( - "Error finding the specified Instance IP: %s", - err.Error(), - ), - ) - return - } - - if ip == nil { - resp.Diagnostics.AddError("nil Pointer", "received nil pointer of the instance ip") - return - } - - resp.Diagnostics.Append(state.FlattenInstanceIP(ctx, *ip, false)...) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} - -func (r *Resource) Update( - ctx context.Context, - req resource.UpdateRequest, - resp *resource.UpdateResponse, -) { - var plan InstanceIPModel - var state InstanceIPModel - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - - if resp.Diagnostics.HasError() { - return - } - - if !plan.RDNS.Equal(state.RDNS) { - rdns := plan.RDNS.ValueStringPointer() - updateOptions := linodego.IPAddressUpdateOptions{ - RDNS: rdns, - } - - client := r.Meta.Client - address := plan.Address.ValueString() - linodeID := plan.LinodeID.ValueInt64() - - tflog.Debug(ctx, "client.UpdateIPAddress(...)", map[string]any{ - "options": updateOptions, - }) - - ip, err := client.UpdateIPAddress(ctx, address, updateOptions) - if err != nil { - resp.Diagnostics.AddError( - "Failed to Update RDNS", - fmt.Sprintf( - "failed to update RDNS for instance (%d) ip (%s): %s", - linodeID, address, err, - ), - ) - return - } - if ip == nil { - resp.Diagnostics.AddError( - "Failed to Get Updated IP", - fmt.Sprintf( - "ip is a nil pointer after update operation for instance (%d) ip (%s): %s", - linodeID, address, err, - ), - ) - return - } - resp.Diagnostics.Append(plan.FlattenInstanceIP(ctx, *ip, true)...) - if resp.Diagnostics.HasError() { - return - } - } - plan.CopyFrom(ctx, state, true) - - // Workaround for Crossplane issue where ID is not - // properly populated in plan - // See TPT-2865 for more details - if plan.ID.ValueString() == "" { - plan.ID = state.ID - } - - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} - -func (r *Resource) Delete( - ctx context.Context, - req resource.DeleteRequest, - resp *resource.DeleteResponse, -) { - var state InstanceIPModel - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - client := r.Meta.Client - address := state.Address.ValueString() - linodeID := helper.FrameworkSafeInt64ToInt( - state.LinodeID.ValueInt64(), - &resp.Diagnostics, - ) - if resp.Diagnostics.HasError() { - return - } - - tflog.Debug(ctx, "client.DeleteInstanceIPAddress(...)") - if err := client.DeleteInstanceIPAddress(ctx, linodeID, address); err != nil { - if lErr, ok := err.(*linodego.Error); (ok && lErr.Code != 404) || !ok { - resp.Diagnostics.AddError( - "Failed to Delete IP", - fmt.Sprintf( - "failed to delete instance (%d) ip (%s): %s", - linodeID, address, err.Error(), - ), - ) - } - } -} - -func populateLogAttributes(ctx context.Context, data *InstanceIPModel) context.Context { - return helper.SetLogFieldBulk(ctx, map[string]any{ - "linode_id": data.LinodeID.ValueInt64(), - "address": data.ID.ValueString(), - }) -} diff --git a/linode/instancereservedip/framework_schema.go b/linode/instancereservedip/framework_schema.go deleted file mode 100644 index c552bd4d1..000000000 --- a/linode/instancereservedip/framework_schema.go +++ /dev/null @@ -1,114 +0,0 @@ -package instancereservedip - -import ( - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/linode/terraform-provider-linode/v2/linode/instancenetworking" -) - -var frameworkResourceSchema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "The ID of the IPv4 address, which will be IPv4 address itself.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "linode_id": schema.Int64Attribute{ - Description: "The ID of the Linode to allocate an IPv4 address for.", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "public": schema.BoolAttribute{ - Description: "Whether the IPv4 address is public or private.", - Default: booldefault.StaticBool(true), - Computed: true, - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - - "address": schema.StringAttribute{ - Description: "The resulting IPv4 address.", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "gateway": schema.StringAttribute{ - Description: "The default gateway for this address", - Computed: true, - }, - "prefix": schema.Int64Attribute{ - Description: "The number of bits set in the subnet mask.", - Computed: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.UseStateForUnknown(), - }, - }, - "rdns": schema.StringAttribute{ - Description: "The reverse DNS assigned to this address.", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "region": schema.StringAttribute{ - Description: "The region this IP resides in.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "subnet_mask": schema.StringAttribute{ - Description: "The mask that separates host bits from network bits for this address.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "type": schema.StringAttribute{ - Description: "The type of IP address.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "vpc_nat_1_1": schema.ListAttribute{ - Description: "Contains information about the NAT 1:1 mapping of a public IP address to a VPC subnet.", - Computed: true, - ElementType: instancenetworking.VPCNAT1To1Type, - Validators: []validator.List{ - listvalidator.SizeAtMost(1), - }, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, - }, - - "apply_immediately": schema.BoolAttribute{ - Description: "If true, the instance will be rebooted to update network interfaces. " + - "This functionality is not affected by the `skip_implicit_reboots` provider argument.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), - }, - "reserved": schema.BoolAttribute{ - Description: "The reservation status of the IP address", - Computed: true, - }, - }, -} diff --git a/linode/instancereservedip/resource_test.go b/linode/instancereservedip/resource_test.go deleted file mode 100644 index 7efcc89aa..000000000 --- a/linode/instancereservedip/resource_test.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build integration || instancereservedip - -package instancereservedip_test - -import ( - "log" - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/linode/linodego" - "github.com/linode/terraform-provider-linode/v2/linode/acceptance" - "github.com/linode/terraform-provider-linode/v2/linode/instancereservedip/tmpl" -) - -const testInstanceIPResName = "linode_instance_reserved_ip.test" - -var testRegion string - -func init() { - region, err := acceptance.GetRandomRegionWithCaps(nil, "core") - if err != nil { - log.Fatal(err) - } - - testRegion = region -} - -func TestAccInstanceIP_addReservedIP(t *testing.T) { - t.Parallel() - - var instance linodego.Instance - name := acctest.RandomWithPrefix("tf_test") - reservedIP := "50.116.51.242" // Replace with your actual reserved IP address - testRegion = "us-east" - resource.Test(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, - CheckDestroy: acceptance.CheckInstanceDestroy, - Steps: []resource.TestStep{ - { - Config: tmpl.AddReservedIP(t, name, testRegion, reservedIP), - Check: resource.ComposeTestCheckFunc( - acceptance.CheckInstanceExists("linode_instance.foobar", &instance), - resource.TestCheckResourceAttr(testInstanceIPResName, "address", reservedIP), - resource.TestCheckResourceAttr(testInstanceIPResName, "public", "true"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "linode_id"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "gateway"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "subnet_mask"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "prefix"), - resource.TestCheckResourceAttrSet(testInstanceIPResName, "rdns"), - resource.TestCheckResourceAttr(testInstanceIPResName, "region", testRegion), - resource.TestCheckResourceAttr(testInstanceIPResName, "type", "ipv4"), - ), - }, - }, - }) -} diff --git a/linode/instancereservedip/tmpl/AddReservedIPToInstance.gotf b/linode/instancereservedip/tmpl/AddReservedIPToInstance.gotf deleted file mode 100644 index 02496633e..000000000 --- a/linode/instancereservedip/tmpl/AddReservedIPToInstance.gotf +++ /dev/null @@ -1,17 +0,0 @@ -{{ define "instance_ip_add_reservedIP" }} - -resource "linode_instance" "foobar" { - label = "{{.Label}}" - group = "tf_test" - type = "g6-nanode-1" - region = "{{ .Region }}" - image = "linode/debian12" -} - -resource "linode_instance_reserved_ip" "test" { - linode_id = linode_instance.foobar.id - public = true - address = "{{ .Address}}" -} - -{{ end }} \ No newline at end of file diff --git a/linode/instancereservedip/tmpl/template.go b/linode/instancereservedip/tmpl/template.go deleted file mode 100644 index 2746e7d05..000000000 --- a/linode/instancereservedip/tmpl/template.go +++ /dev/null @@ -1,23 +0,0 @@ -package tmpl - -import ( - "testing" - - "github.com/linode/terraform-provider-linode/v2/linode/acceptance" -) - -type TemplateData struct { - Label string - ApplyImmediately bool - Region string - Address string -} - -func AddReservedIP(t *testing.T, instanceLabel, region string, address string) string { - return acceptance.ExecuteTemplate(t, - "instance_ip_add_reservedIP", TemplateData{ - Label: instanceLabel, - Region: region, - Address: address, - }) -} From e1e5539a130a2dadccf43e0296c72c34563e3f8f Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Fri, 8 Nov 2024 17:34:06 -0500 Subject: [PATCH 47/49] modified test for assigning multiple reserved IP address to linodes in a region and fetching the updated IP address model --- linode/networkingip/datasource_test.go | 1 + linode/networkingip/framework_datasource.go | 2 + .../framework_datasource_schema.go | 5 + .../networkipassignment/framework_resource.go | 145 ++++++------------ .../framework_resource_model.go | 20 --- .../framework_resource_schema.go | 34 +--- linode/networkipassignment/resource_test.go | 13 +- 7 files changed, 61 insertions(+), 159 deletions(-) diff --git a/linode/networkingip/datasource_test.go b/linode/networkingip/datasource_test.go index ae4b2e013..1b7f9b924 100644 --- a/linode/networkingip/datasource_test.go +++ b/linode/networkingip/datasource_test.go @@ -49,6 +49,7 @@ func TestAccDataSourceNetworkingIP_basic(t *testing.T) { resource.TestMatchResourceAttr(dataResourceName, "gateway", regexp.MustCompile(`\.1$`)), resource.TestCheckResourceAttr(dataResourceName, "type", "ipv4"), resource.TestCheckResourceAttr(dataResourceName, "public", "true"), + resource.TestCheckResourceAttrSet(dataResourceName, "reserved"), resource.TestCheckResourceAttr(dataResourceName, "prefix", "24"), resource.TestMatchResourceAttr(dataResourceName, "rdns", regexp.MustCompile(`.ip.linodeusercontent.com$`)), ), diff --git a/linode/networkingip/framework_datasource.go b/linode/networkingip/framework_datasource.go index e80ebbd75..ee5d2e2ae 100644 --- a/linode/networkingip/framework_datasource.go +++ b/linode/networkingip/framework_datasource.go @@ -37,6 +37,7 @@ func (data *DataSourceModel) parseIP(ip *linodego.InstanceIP) { data.RDNS = types.StringValue(ip.RDNS) data.LinodeID = types.Int64Value(int64(ip.LinodeID)) data.Region = types.StringValue(ip.Region) + data.Reserved = types.BoolValue(ip.Reserved) id, _ := json.Marshal(ip) @@ -51,6 +52,7 @@ type DataSourceModel struct { Type types.String `tfsdk:"type"` Public types.Bool `tfsdk:"public"` RDNS types.String `tfsdk:"rdns"` + Reserved types.Bool `tfsdk:"reserved"` LinodeID types.Int64 `tfsdk:"linode_id"` Region types.String `tfsdk:"region"` ID types.String `tfsdk:"id"` diff --git a/linode/networkingip/framework_datasource_schema.go b/linode/networkingip/framework_datasource_schema.go index 5299994ea..c97a7a891 100644 --- a/linode/networkingip/framework_datasource_schema.go +++ b/linode/networkingip/framework_datasource_schema.go @@ -43,6 +43,11 @@ var frameworkDatasourceSchema = schema.Schema{ Description: "The Region this IP address resides in.", Computed: true, }, + "reserved": schema.BoolAttribute{ + Description: "Whether the IPv4 address should be reserved.", + Computed: true, + }, + "id": schema.StringAttribute{ Description: "A unique identifier for this datasource.", Computed: true, diff --git a/linode/networkipassignment/framework_resource.go b/linode/networkipassignment/framework_resource.go index 07b57f0c6..a5943af97 100644 --- a/linode/networkipassignment/framework_resource.go +++ b/linode/networkipassignment/framework_resource.go @@ -38,37 +38,31 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp client := r.Meta.Client - if len(plan.Assignments) > 0 { - tflog.Info(ctx, "Assigning IP addresses", map[string]interface{}{ - "assignments": plan.Assignments, - }) - - apiAssignments := make([]linodego.LinodeIPAssignment, len(plan.Assignments)) - for i, assignment := range plan.Assignments { - apiAssignments[i] = linodego.LinodeIPAssignment{ - Address: assignment.Address.ValueString(), - LinodeID: int(assignment.LinodeID.ValueInt64()), - } - } - - assignOpts := linodego.LinodesAssignIPsOptions{ - Region: plan.Region.ValueString(), - Assignments: apiAssignments, + apiAssignments := make([]linodego.LinodeIPAssignment, len(plan.Assignments)) + for i, assignment := range plan.Assignments { + apiAssignments[i] = linodego.LinodeIPAssignment{ + Address: assignment.Address.ValueString(), + LinodeID: int(assignment.LinodeID.ValueInt64()), } + } - err := client.InstancesAssignIPs(ctx, assignOpts) - if err != nil { - resp.Diagnostics.AddError( - "Error assigning IP Addresses", - fmt.Sprintf("Could not assign IP addresses: %s", err), - ) - return - } + assignOpts := linodego.LinodesAssignIPsOptions{ + Region: plan.Region.ValueString(), + Assignments: apiAssignments, + } - plan.ID = types.StringValue(plan.Assignments[0].Address.ValueString()) - plan.Region = types.StringValue(assignOpts.Region) + err := client.InstancesAssignIPs(ctx, assignOpts) + if err != nil { + resp.Diagnostics.AddError( + "Error assigning IP Addresses", + fmt.Sprintf("Could not assign IP addresses: %s", err), + ) + return } + // Generate a unique ID for this resource + plan.ID = types.StringValue(fmt.Sprintf("%s-%d", plan.Region.ValueString(), len(plan.Assignments))) + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) } @@ -83,15 +77,12 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res client := r.Meta.Client - // Initialize a slice to store all assignments - allAssignments := []AssignmentModel{} - - // Iterate through all assignments in the current state - for _, assignment := range state.Assignments { + for i, assignment := range state.Assignments { ip, err := client.GetIPAddress(ctx, assignment.Address.ValueString()) if err != nil { if lerr, ok := err.(*linodego.Error); ok && lerr.Code == 404 { - // If the IP is not found, skip it + // IP not found, remove it from state + state.Assignments = append(state.Assignments[:i], state.Assignments[i+1:]...) continue } resp.Diagnostics.AddError( @@ -101,27 +92,12 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res return } - // Add the assignment to our slice - allAssignments = append(allAssignments, AssignmentModel{ + state.Assignments[i] = AssignmentModel{ Address: types.StringValue(ip.Address), LinodeID: types.Int64Value(int64(ip.LinodeID)), - }) - - // If this is the first assignment, use it to set the main resource attributes - if len(allAssignments) == 1 { - state.ID = types.StringValue(ip.Address) - state.Address = types.StringValue(ip.Address) - state.LinodeID = types.Int64Value(int64(ip.LinodeID)) - state.Region = types.StringValue(ip.Region) - state.Public = types.BoolValue(ip.Public) - state.Type = types.StringValue(string(ip.Type)) - state.Reserved = types.BoolValue(ip.Reserved) } } - // Update the state with all assignments - state.Assignments = allAssignments - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } @@ -137,41 +113,32 @@ func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp client := r.Meta.Client - if len(plan.Assignments) > 0 { - // Handle IP assignment updates - apiAssignments := make([]linodego.LinodeIPAssignment, len(plan.Assignments)) - for i, assignment := range plan.Assignments { - apiAssignments[i] = linodego.LinodeIPAssignment{ - Address: assignment.Address.ValueString(), - LinodeID: int(assignment.LinodeID.ValueInt64()), - } - } - - assignOpts := linodego.LinodesAssignIPsOptions{ - Region: plan.Region.ValueString(), - Assignments: apiAssignments, - } - - err := client.InstancesAssignIPs(ctx, assignOpts) - if err != nil { - resp.Diagnostics.AddError( - "Error updating IP Assignments", - fmt.Sprintf("Could not update IP assignments: %s", err), - ) - return + apiAssignments := make([]linodego.LinodeIPAssignment, len(plan.Assignments)) + for i, assignment := range plan.Assignments { + apiAssignments[i] = linodego.LinodeIPAssignment{ + Address: assignment.Address.ValueString(), + LinodeID: int(assignment.LinodeID.ValueInt64()), } + } - // Update plan with new assignment details - plan.ID = types.StringValue(plan.Assignments[0].Address.ValueString()) - plan.Address = plan.Assignments[0].Address + assignOpts := linodego.LinodesAssignIPsOptions{ + Region: plan.Region.ValueString(), + Assignments: apiAssignments, } - // Re-read the state to get the latest information - readResp := resource.ReadResponse{State: resp.State} - if readResp.Diagnostics.HasError() { - resp.Diagnostics.Append(readResp.Diagnostics...) + err := client.InstancesAssignIPs(ctx, assignOpts) + if err != nil { + resp.Diagnostics.AddError( + "Error updating IP Assignments", + fmt.Sprintf("Could not update IP assignments: %s", err), + ) return } + + // Update the ID to reflect any changes in the number of assignments + plan.ID = types.StringValue(fmt.Sprintf("%s-%d", plan.Region.ValueString(), len(plan.Assignments))) + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { @@ -183,27 +150,5 @@ func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp return } - client := r.Meta.Client - - // Check if this is an assigned IP - if len(state.Assignments) > 0 { - // For assigned IPs, we need to unassign them - for _, assignment := range state.Assignments { - linodeID := int(assignment.LinodeID.ValueInt64()) - ipAddress := assignment.Address.ValueString() - - err := client.DeleteInstanceIPAddress(ctx, linodeID, ipAddress) - if err != nil { - if lErr, ok := err.(*linodego.Error); (ok && lErr.Code != 404) || !ok { - resp.Diagnostics.AddError( - "Failed to Unassign IP", - fmt.Sprintf( - "failed to unassign ip (%s) from instance (%d): %s", - ipAddress, linodeID, err.Error(), - ), - ) - } - } - } - } + // No need to do anything for delete as the IPs will be automatically unassigned when the Linodes are deleted } diff --git a/linode/networkipassignment/framework_resource_model.go b/linode/networkipassignment/framework_resource_model.go index ebc5c8244..babccd333 100644 --- a/linode/networkipassignment/framework_resource_model.go +++ b/linode/networkipassignment/framework_resource_model.go @@ -2,17 +2,11 @@ package networkipassignment import ( "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/linode/linodego" ) type NetworkingIPModel struct { ID types.String `tfsdk:"id"` - LinodeID types.Int64 `tfsdk:"linode_id"` - Reserved types.Bool `tfsdk:"reserved"` Region types.String `tfsdk:"region"` - Public types.Bool `tfsdk:"public"` - Address types.String `tfsdk:"address"` - Type types.String `tfsdk:"type"` Assignments []AssignmentModel `tfsdk:"assignments"` } @@ -20,17 +14,3 @@ type AssignmentModel struct { Address types.String `tfsdk:"address"` LinodeID types.Int64 `tfsdk:"linode_id"` } - -func (m *NetworkingIPModel) FlattenIPAddress(ip *linodego.InstanceIP) { - m.ID = types.StringValue(ip.Address) - if ip.LinodeID != 0 { - m.LinodeID = types.Int64Value(int64(ip.LinodeID)) - } else { - m.LinodeID = types.Int64Null() - } - m.Reserved = types.BoolValue(ip.Reserved) - m.Region = types.StringValue(ip.Region) - m.Public = types.BoolValue(ip.Public) - m.Address = types.StringValue(ip.Address) - m.Type = types.StringValue(string(ip.Type)) -} diff --git a/linode/networkipassignment/framework_resource_schema.go b/linode/networkipassignment/framework_resource_schema.go index 518e1fa09..1feb39b1f 100644 --- a/linode/networkipassignment/framework_resource_schema.go +++ b/linode/networkipassignment/framework_resource_schema.go @@ -3,46 +3,18 @@ package networkipassignment import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" ) var frameworkResourceSchema = schema.Schema{ Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - Description: "The ID of the IPv4 address.", Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "linode_id": schema.Int64Attribute{ - Description: "The ID of the Linode to allocate an IPv4 address for. Required when reserved is false or not set.", - Optional: true, - }, - "reserved": schema.BoolAttribute{ - Description: "Whether the IPv4 address should be reserved.", - Optional: true, + Description: "The ID of the IP assignment operation.", }, "region": schema.StringAttribute{ - Description: "The region for the reserved IPv4 address. Required when reserved is true and linode_id is not set.", - Optional: true, - Computed: true, - }, - "public": schema.BoolAttribute{ - Description: "Whether the IPv4 address is public or private.", - Optional: true, - // Computed: true, - }, - "address": schema.StringAttribute{ - Description: "The allocated IPv4 address.", - Optional: true, - }, - "type": schema.StringAttribute{ - Description: "The type of IP address (ipv4).", - // Computed: true, - Optional: true, + Required: true, + Description: "The region for the IP assignments.", }, "assignments": schema.ListAttribute{ ElementType: types.ObjectType{ diff --git a/linode/networkipassignment/resource_test.go b/linode/networkipassignment/resource_test.go index 73936d84c..dfb39f130 100644 --- a/linode/networkipassignment/resource_test.go +++ b/linode/networkipassignment/resource_test.go @@ -52,11 +52,7 @@ func TestAccResourceNetworkingIPsAssign(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "assignments.0.address"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, + // Removed ImportState step as it's no longer supported }, }) } @@ -97,13 +93,14 @@ func checkNetworkingIPsAssignDestroy(s *terraform.State) error { continue } - _, err := client.GetIPAddress(context.Background(), rs.Primary.ID) + ipAddress := rs.Primary.ID // Assuming ID is the address of the IP + _, err := client.GetIPAddress(context.Background(), ipAddress) if err == nil { - return fmt.Errorf("Networking IPs Assign with id %s still exists", rs.Primary.ID) + return fmt.Errorf("Networking IPs Assign with id %s still exists", ipAddress) } if apiErr, ok := err.(*linodego.Error); ok && apiErr.Code != 404 { - return fmt.Errorf("Error requesting Networking IPs Assign with id %s", rs.Primary.ID) + return fmt.Errorf("Error requesting Networking IPs Assign with id %s", ipAddress) } } From 610bd4574fed7a89b6f249ed2e68dd275ab7e3ea Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Tue, 12 Nov 2024 17:18:15 -0500 Subject: [PATCH 48/49] made changes to existing tests for updating rdns, assigning IP address, removed dummy reserved IP from test functions --- linode/instance/resource_test.go | 24 ++++++++++++----- linode/instance/tmpl/template.go | 13 +++++++--- .../templates/instance_with_reserved_ip.gotf | 7 +++-- linode/networkingip/framework_resource.go | 2 +- linode/rdns/framework_resource.go | 7 ++--- linode/rdns/framework_resource_schema.go | 1 - linode/rdns/resource_test.go | 26 +++++++++++++++++++ linode/rdns/tmpl/template.go | 10 +++++++ linode/rdns/tmpl/unreserved_to_reserved.gotf | 26 +++++++++++++++++++ 9 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 linode/rdns/tmpl/unreserved_to_reserved.gotf diff --git a/linode/instance/resource_test.go b/linode/instance/resource_test.go index 56bf48f7d..66ff9393a 100644 --- a/linode/instance/resource_test.go +++ b/linode/instance/resource_test.go @@ -2912,12 +2912,11 @@ func TestAccResourceInstance_withReservedIP(t *testing.T) { CheckDestroy: acceptance.CheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: tmpl.WithReservedIP(t, instanceName, acceptance.PublicKeyMaterial, testRegion, rootPass, reservedIP), + Config: tmpl.WithReservedIP(t, instanceName, acceptance.PublicKeyMaterial, testRegion, rootPass), Check: resource.ComposeTestCheckFunc( acceptance.CheckInstanceExists(resourceName, &instance), resource.TestCheckResourceAttr(resourceName, "label", instanceName), resource.TestCheckResourceAttr(resourceName, "ipv4.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ipv4.0", reservedIP), ), }, { @@ -2935,28 +2934,37 @@ func TestAccResourceInstance_deleteWithReservedIP(t *testing.T) { var instance linodego.Instance resourceName := "linode_instance.foobar" testRegion := "us-east" + reservedIP := "" instanceName := acctest.RandomWithPrefix("tf_test") + ipResourceName := "linode_reserved_ip.test" rootPass := acctest.RandString(16) - reservedIP := "172.104.17.36" // Use a test IP or fetch a real reserved IP resource.Test(t, resource.TestCase{ PreCheck: func() { acceptance.PreCheck(t) }, ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, CheckDestroy: acceptance.CheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: tmpl.WithReservedIP(t, instanceName, acceptance.PublicKeyMaterial, testRegion, rootPass, reservedIP), + Config: tmpl.WithReservedIP(t, instanceName, acceptance.PublicKeyMaterial, testRegion, rootPass), Check: resource.ComposeTestCheckFunc( acceptance.CheckInstanceExists(resourceName, &instance), resource.TestCheckResourceAttr(resourceName, "label", instanceName), resource.TestCheckResourceAttr(resourceName, "ipv4.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ipv4.0", reservedIP), + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[ipResourceName] + if !ok { + return fmt.Errorf("Not found: %s", ipResourceName) + } + reservedIP = rs.Primary.Attributes["address"] + return nil + }, ), }, { - Config: " ", // Empty config to trigger instance deletion + Config: tmpl.OnlyReservedIP(t, testRegion), // This config only includes the reserved IP resource Check: resource.ComposeTestCheckFunc( func(s *terraform.State) error { client := acceptance.TestAccProvider.Meta().(*helper.ProviderMeta).Client + // Check if the instance is deleted _, err := client.GetInstance(context.Background(), instance.ID) if err == nil { @@ -2965,14 +2973,16 @@ func TestAccResourceInstance_deleteWithReservedIP(t *testing.T) { if apiErr, ok := err.(*linodego.Error); ok && apiErr.Code != 404 { return fmt.Errorf("Error requesting Linode instance %d: %s", instance.ID, err) } + // Check if the Reserved IP still exists and is reserved ip, err := client.GetIPAddress(context.Background(), reservedIP) if err != nil { - return fmt.Errorf("Error checking if Reserved IP still exists: %s", err) + return fmt.Errorf("Error checking if Reserved IP exists: %s", err) } if !ip.Reserved { return fmt.Errorf("Reserved IP %s is no longer reserved after instance deletion", reservedIP) } + return nil }, ), diff --git a/linode/instance/tmpl/template.go b/linode/instance/tmpl/template.go index f470470a2..aab35fbfe 100644 --- a/linode/instance/tmpl/template.go +++ b/linode/instance/tmpl/template.go @@ -1,6 +1,7 @@ package tmpl import ( + "fmt" "testing" "github.com/linode/linodego" @@ -28,7 +29,6 @@ type TemplateData struct { AssignedGroup string DiskEncryption *linodego.InstanceDiskEncryption - IPv4 []string } func Basic(t testing.TB, label, pubKey, region string, rootPass string) string { @@ -743,7 +743,7 @@ func WithPG(t testing.TB, label, region, assignedGroup string, groups []string) }) } -func WithReservedIP(t *testing.T, label, pubKey, region, rootPass string, reservedIP string) string { +func WithReservedIP(t *testing.T, label, pubKey, region, rootPass string) string { generatedConfig := acceptance.ExecuteTemplate(t, "instance_with_reserved_ip", TemplateData{ Label: label, @@ -751,7 +751,14 @@ func WithReservedIP(t *testing.T, label, pubKey, region, rootPass string, reserv Image: acceptance.TestImageLatest, Region: region, RootPass: rootPass, - IPv4: []string{reservedIP}, }) return generatedConfig } + +func OnlyReservedIP(t *testing.T, region string) string { + return fmt.Sprintf(` +resource "linode_reserved_ip" "test" { + region = "%s" +} +`, region) +} diff --git a/linode/instance/tmpl/templates/instance_with_reserved_ip.gotf b/linode/instance/tmpl/templates/instance_with_reserved_ip.gotf index 7d5aadefd..aaf3ee5ac 100644 --- a/linode/instance/tmpl/templates/instance_with_reserved_ip.gotf +++ b/linode/instance/tmpl/templates/instance_with_reserved_ip.gotf @@ -1,8 +1,11 @@ {{ define "instance_with_reserved_ip" }} - {{ template "e2e_test_firewall" . }} +resource "linode_reserved_ip" "test" { + region = "{{ .Region }}" +} + resource "linode_instance" "foobar" { label = "{{ .Label }}" type = "g6-nanode-1" @@ -12,7 +15,7 @@ resource "linode_instance" "foobar" { root_pass = "{{ .RootPass }}" authorized_keys = ["{{ .PubKey }}"] - ipv4 = [{{ range $index, $ip := .IPv4 }}{{ if $index }}, {{ end }}"{{ $ip }}"{{ end }}] + ipv4 = [linode_reserved_ip.test.address] } {{ end }} \ No newline at end of file diff --git a/linode/networkingip/framework_resource.go b/linode/networkingip/framework_resource.go index a6addc6e6..3ddfb29d0 100644 --- a/linode/networkingip/framework_resource.go +++ b/linode/networkingip/framework_resource.go @@ -41,7 +41,7 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp if !plan.Public.IsNull() { // Handle IP creation createOpts := linodego.LinodeReserveIPOptions{ - Type: "ipv4", + Type: plan.Type.ValueString(), Public: plan.Public.ValueBool(), } diff --git a/linode/rdns/framework_resource.go b/linode/rdns/framework_resource.go index 903bcb530..3ee2a70ed 100644 --- a/linode/rdns/framework_resource.go +++ b/linode/rdns/framework_resource.go @@ -49,7 +49,7 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp if !plan.Reserved.IsNull() { reserved := plan.Reserved.ValueBool() - updateOpts.Reserved = reserved + updateOpts.Reserved = &reserved } ip, err := updateIPAddress( @@ -111,7 +111,7 @@ func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp if !plan.Reserved.IsNull() { reserved := plan.Reserved.ValueBool() - updateOpts.Reserved = reserved + updateOpts.Reserved = &reserved } ip, err := updateIPAddress( @@ -140,9 +140,10 @@ func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp } client := r.Meta.Client + falseValue := false updateOpts := linodego.IPAddressUpdateOptions{ RDNS: nil, - Reserved: false, + Reserved: &falseValue, } _, err := client.UpdateIPAddress(ctx, state.Address.ValueString(), updateOpts) diff --git a/linode/rdns/framework_resource_schema.go b/linode/rdns/framework_resource_schema.go index a10d5d8a9..6f3215ffd 100644 --- a/linode/rdns/framework_resource_schema.go +++ b/linode/rdns/framework_resource_schema.go @@ -31,7 +31,6 @@ var frameworkResourceSchema = schema.Schema{ "reserved": schema.BoolAttribute{ Description: "Whether the IP address is reserved.", Optional: true, - Computed: true, }, "wait_for_available": schema.BoolAttribute{ Description: "If true, the RDNS assignment will be retried within the operation timeout period.", diff --git a/linode/rdns/resource_test.go b/linode/rdns/resource_test.go index 0b0ab9a2a..13e208f8e 100644 --- a/linode/rdns/resource_test.go +++ b/linode/rdns/resource_test.go @@ -206,6 +206,32 @@ func TestAccResourceRDNS_waitForAvailableWithTimeout(t *testing.T) { }) } +func TestAccResourceRDNS_unreservedToReserved(t *testing.T) { + t.Parallel() + + resName := "linode_rdns.foobar" + linodeLabel := acctest.RandomWithPrefix("tf_test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + + Steps: []resource.TestStep{ + { + Config: tmpl.UnreservedToReserved(t, linodeLabel, testRegion, true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resName, "reserved", "true"), + ), + }, + { + ResourceName: resName, + ImportState: true, + ImportStateVerifyIgnore: []string{"rdns", "wait_for_available", "firewall_id"}, + }, + }, + }) +} + func checkRDNSExists(s *terraform.State) error { client := acceptance.TestAccFrameworkProvider.Meta.Client diff --git a/linode/rdns/tmpl/template.go b/linode/rdns/tmpl/template.go index 32d47bd42..30e4dea57 100644 --- a/linode/rdns/tmpl/template.go +++ b/linode/rdns/tmpl/template.go @@ -12,6 +12,7 @@ type TemplateData struct { CreateTimeout string UpdateTimeout string WaitForAvailable bool + Reserved bool } func Basic(t testing.TB, label, region string, waitForAvailable bool) string { @@ -59,3 +60,12 @@ func WithTimeoutUpdated(t testing.TB, label, region, createTimeout, updateTimeou UpdateTimeout: updateTimeout, }) } + +func UnreservedToReserved(t testing.TB, label, region string, reserved bool) string { + return acceptance.ExecuteTemplate(t, + "rdns_unreserved_to_reserved", TemplateData{ + Label: label, + Region: region, + Reserved: reserved, + }) +} diff --git a/linode/rdns/tmpl/unreserved_to_reserved.gotf b/linode/rdns/tmpl/unreserved_to_reserved.gotf new file mode 100644 index 000000000..3f517cdf0 --- /dev/null +++ b/linode/rdns/tmpl/unreserved_to_reserved.gotf @@ -0,0 +1,26 @@ +{{ define "rdns_unreserved_to_reserved" }} + +{{ template "e2e_test_firewall" . }} + +resource "linode_instance" "foobar" { + label = "{{.Label}}" + group = "tf_test" + image = "linode/alpine3.18" + type = "g6-standard-1" + region = "{{ .Region }}" + firewall_id = linode_firewall.e2e_test_firewall.id +} + +resource "linode_networking_ip" "unreserved_ip" { + public = true + type = "ipv4" + linode_id = linode_instance.foobar.id +} + +resource "linode_rdns" "foobar" { + address = linode_networking_ip.unreserved_ip.address + rdns = "${linode_networking_ip.unreserved_ip.address}.nip.io" + reserved = true +} + +{{ end }} \ No newline at end of file From 151b63680aa8478329cfd5aa6388397608ad28a8 Mon Sep 17 00:00:00 2001 From: Anirudh Jagadish Date: Wed, 13 Nov 2024 11:29:53 -0500 Subject: [PATCH 49/49] removed the replace line from the go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a17e5c2fc..bb95ff168 100644 --- a/go.mod +++ b/go.mod @@ -126,4 +126,4 @@ require ( sigs.k8s.io/yaml v1.3.0 // indirect ) -replace github.com/linode/linodego => /home/ajagadis/ReservedIP/linodego +