Skip to content

Commit 5ac983b

Browse files
authored
Merge pull request #27699 from brittandeyoung/b-aws_lightsail_public_ports_disappears
Add IPv6 support `aws_lightsail_instance`
2 parents 90e140f + 3176f9e commit 5ac983b

File tree

7 files changed

+183
-67
lines changed

7 files changed

+183
-67
lines changed

.changelog/27699.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```release-note:enhancement
2+
resource/aws_lightsail_instance: Add `ip_address_type` argument
3+
```
4+
5+
```release-note:bug
6+
resource/aws_lightsail_instance_public_ports: Resource will now be removed from state properly when parent instance is removed
7+
```

internal/service/lightsail/instance.go

+36-48
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"github.com/aws/aws-sdk-go/aws"
1010
"github.com/aws/aws-sdk-go/aws/awserr"
1111
"github.com/aws/aws-sdk-go/service/lightsail"
12-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1312
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1413
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1514
"github.com/hashicorp/terraform-provider-aws/internal/conns"
@@ -93,6 +92,11 @@ func ResourceInstance() *schema.Resource {
9392
Type: schema.TypeFloat,
9493
Computed: true,
9594
},
95+
"ip_address_type": {
96+
Type: schema.TypeString,
97+
Optional: true,
98+
Default: "dualstack",
99+
},
96100
"ipv6_address": {
97101
Type: schema.TypeString,
98102
Computed: true,
@@ -144,10 +148,15 @@ func resourceInstanceCreate(d *schema.ResourceData, meta interface{}) error {
144148
if v, ok := d.GetOk("key_pair_name"); ok {
145149
req.KeyPairName = aws.String(v.(string))
146150
}
151+
147152
if v, ok := d.GetOk("user_data"); ok {
148153
req.UserData = aws.String(v.(string))
149154
}
150155

156+
if v, ok := d.GetOk("ip_address_type"); ok {
157+
req.IpAddressType = aws.String(v.(string))
158+
}
159+
151160
if len(tags) > 0 {
152161
req.Tags = Tags(tags.IgnoreAWS())
153162
}
@@ -164,16 +173,8 @@ func resourceInstanceCreate(d *schema.ResourceData, meta interface{}) error {
164173
op := resp.Operations[0]
165174
d.SetId(d.Get("name").(string))
166175

167-
stateConf := &resource.StateChangeConf{
168-
Pending: []string{"Started"},
169-
Target: []string{"Completed", "Succeeded"},
170-
Refresh: resourceOperationRefreshFunc(op.Id, meta),
171-
Timeout: 10 * time.Minute,
172-
Delay: 5 * time.Second,
173-
MinTimeout: 3 * time.Second,
174-
}
176+
err = waitOperation(conn, op.Id)
175177

176-
_, err = stateConf.WaitForState()
177178
if err != nil {
178179
// We don't return an error here because the Create call succeeded
179180
log.Printf("[ERR] Error waiting for instance (%s) to become ready: %s", d.Id(), err)
@@ -230,6 +231,7 @@ func resourceInstanceRead(d *schema.ResourceData, meta interface{}) error {
230231
}
231232

232233
d.Set("ipv6_addresses", aws.StringValueSlice(i.Ipv6Addresses))
234+
d.Set("ip_address_type", i.IpAddressType)
233235
d.Set("is_static_ip", i.IsStaticIp)
234236
d.Set("private_ip_address", i.PrivateIpAddress)
235237
d.Set("public_ip_address", i.PublicIpAddress)
@@ -260,16 +262,8 @@ func resourceInstanceDelete(d *schema.ResourceData, meta interface{}) error {
260262

261263
op := resp.Operations[0]
262264

263-
stateConf := &resource.StateChangeConf{
264-
Pending: []string{"Started"},
265-
Target: []string{"Completed", "Succeeded"},
266-
Refresh: resourceOperationRefreshFunc(op.Id, meta),
267-
Timeout: 10 * time.Minute,
268-
Delay: 5 * time.Second,
269-
MinTimeout: 3 * time.Second,
270-
}
265+
err = waitOperation(conn, op.Id)
271266

272-
_, err = stateConf.WaitForState()
273267
if err != nil {
274268
return fmt.Errorf(
275269
"Error waiting for instance (%s) to become destroyed: %s",
@@ -282,42 +276,36 @@ func resourceInstanceDelete(d *schema.ResourceData, meta interface{}) error {
282276
func resourceInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
283277
conn := meta.(*conns.AWSClient).LightsailConn
284278

285-
if d.HasChange("tags_all") {
286-
o, n := d.GetChange("tags_all")
279+
if d.HasChange("ip_address_type") {
280+
resp, err := conn.SetIpAddressType(&lightsail.SetIpAddressTypeInput{
281+
ResourceName: aws.String(d.Id()),
282+
ResourceType: aws.String("Instance"),
283+
IpAddressType: aws.String(d.Get("ip_address_type").(string)),
284+
})
287285

288-
if err := UpdateTags(conn, d.Id(), o, n); err != nil {
289-
return fmt.Errorf("error updating Lightsail Instance (%s) tags: %s", d.Id(), err)
286+
if err != nil {
287+
return err
290288
}
291-
}
292289

293-
return resourceInstanceRead(d, meta)
294-
}
290+
if len(resp.Operations) == 0 {
291+
return fmt.Errorf("No operations found for CreateInstance request")
292+
}
295293

296-
// method to check the status of an Operation, which is returned from
297-
// Create/Delete methods.
298-
// Status's are an aws.OperationStatus enum:
299-
// - NotStarted
300-
// - Started
301-
// - Failed
302-
// - Completed
303-
// - Succeeded (not documented?)
304-
func resourceOperationRefreshFunc(
305-
oid *string, meta interface{}) resource.StateRefreshFunc {
306-
return func() (interface{}, string, error) {
307-
conn := meta.(*conns.AWSClient).LightsailConn
308-
log.Printf("[DEBUG] Checking if Lightsail Operation (%s) is Completed", *oid)
309-
o, err := conn.GetOperation(&lightsail.GetOperationInput{
310-
OperationId: oid,
311-
})
294+
op := resp.Operations[0]
295+
296+
err = waitOperation(conn, op.Id)
312297
if err != nil {
313-
return o, "FAILED", err
298+
return err
314299
}
300+
}
315301

316-
if o.Operation == nil {
317-
return nil, "Failed", fmt.Errorf("Error retrieving Operation info for operation (%s)", *oid)
318-
}
302+
if d.HasChange("tags_all") {
303+
o, n := d.GetChange("tags_all")
319304

320-
log.Printf("[DEBUG] Lightsail Operation (%s) is currently %q", *oid, *o.Operation.Status)
321-
return o, *o.Operation.Status, nil
305+
if err := UpdateTags(conn, d.Id(), o, n); err != nil {
306+
return fmt.Errorf("error updating Lightsail Instance (%s) tags: %s", d.Id(), err)
307+
}
322308
}
309+
310+
return resourceInstanceRead(d, meta)
323311
}

internal/service/lightsail/instance_public_ports.go

+25
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func ResourceInstancePublicPorts() *schema.Resource {
4040
Type: schema.TypeSet,
4141
Optional: true,
4242
Computed: true,
43+
// Default: []string{"0.0.0.0/0"},
4344
Elem: &schema.Schema{
4445
Type: schema.TypeString,
4546
ValidateFunc: verify.ValidCIDRNetworkAddress,
@@ -51,6 +52,16 @@ func ResourceInstancePublicPorts() *schema.Resource {
5152
ForceNew: true,
5253
ValidateFunc: validation.IntBetween(0, 65535),
5354
},
55+
"ipv6_cidrs": {
56+
Type: schema.TypeSet,
57+
Optional: true,
58+
Computed: true,
59+
// Default: []string{"::/0"},
60+
Elem: &schema.Schema{
61+
Type: schema.TypeString,
62+
ValidateFunc: verify.ValidCIDRNetworkAddress,
63+
},
64+
},
5465
"protocol": {
5566
Type: schema.TypeString,
5667
Required: true,
@@ -118,6 +129,12 @@ func resourceInstancePublicPortsRead(d *schema.ResourceData, meta interface{}) e
118129
return fmt.Errorf("error reading Lightsail instance public ports (%s): %w", d.Id(), err)
119130
}
120131

132+
if output == nil || len(output.PortStates) == 0 || output.PortStates == nil {
133+
log.Printf("[WARN] Lightsail instance public ports (%s) not found, removing from state", d.Id())
134+
d.SetId("")
135+
return nil
136+
}
137+
121138
if err := d.Set("port_info", flattenInstancePortStates(output.PortStates)); err != nil {
122139
return fmt.Errorf("error setting port_info: %w", err)
123140
}
@@ -168,6 +185,10 @@ func expandPortInfo(tfMap map[string]interface{}) *lightsail.PortInfo {
168185
apiObject.Cidrs = flex.ExpandStringSet(v)
169186
}
170187

188+
if v, ok := tfMap["ipv6_cidrs"].(*schema.Set); ok && v.Len() > 0 {
189+
apiObject.Ipv6Cidrs = flex.ExpandStringSet(v)
190+
}
191+
171192
return apiObject
172193
}
173194

@@ -212,6 +233,10 @@ func flattenInstancePortState(apiObject *lightsail.InstancePortState) map[string
212233
tfMap["cidrs"] = aws.StringValueSlice(v)
213234
}
214235

236+
if v := apiObject.Ipv6Cidrs; v != nil {
237+
tfMap["ipv6_cidrs"] = aws.StringValueSlice(v)
238+
}
239+
215240
return tfMap
216241
}
217242

internal/service/lightsail/instance_public_ports_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
1313
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
1414
"github.com/hashicorp/terraform-provider-aws/internal/conns"
15+
tflightsail "github.com/hashicorp/terraform-provider-aws/internal/service/lightsail"
1516
)
1617

1718
func TestAccLightsailInstancePublicPorts_basic(t *testing.T) {
@@ -112,6 +113,59 @@ func TestAccLightsailInstancePublicPorts_cidrs(t *testing.T) {
112113
})
113114
}
114115

116+
func TestAccLightsailInstancePublicPorts_disappears(t *testing.T) {
117+
resourceName := "aws_lightsail_instance_public_ports.test"
118+
rName := sdkacctest.RandomWithPrefix("tf-acc-test")
119+
120+
resource.ParallelTest(t, resource.TestCase{
121+
PreCheck: func() {
122+
acctest.PreCheck(t)
123+
acctest.PreCheckPartitionHasService(lightsail.EndpointsID, t)
124+
testAccPreCheck(t)
125+
},
126+
ErrorCheck: acctest.ErrorCheck(t, lightsail.EndpointsID),
127+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
128+
CheckDestroy: testAccCheckInstancePublicPortsDestroy,
129+
Steps: []resource.TestStep{
130+
{
131+
Config: testAccInstancePublicPortsConfig_basic(rName),
132+
Check: resource.ComposeTestCheckFunc(
133+
testAccCheckInstancePublicPortsExists(resourceName),
134+
acctest.CheckResourceDisappears(acctest.Provider, tflightsail.ResourceInstancePublicPorts(), resourceName),
135+
),
136+
ExpectNonEmptyPlan: true,
137+
},
138+
},
139+
})
140+
}
141+
142+
func TestAccLightsailInstancePublicPorts_disappears_Instance(t *testing.T) {
143+
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
144+
parentResourceName := "aws_lightsail_instance.test"
145+
resourceName := "aws_lightsail_instance_public_ports.test"
146+
147+
resource.ParallelTest(t, resource.TestCase{
148+
PreCheck: func() {
149+
acctest.PreCheck(t)
150+
acctest.PreCheckPartitionHasService(lightsail.EndpointsID, t)
151+
testAccPreCheck(t)
152+
},
153+
ErrorCheck: acctest.ErrorCheck(t, lightsail.EndpointsID),
154+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
155+
CheckDestroy: testAccCheckInstancePublicPortsDestroy,
156+
Steps: []resource.TestStep{
157+
{
158+
Config: testAccInstancePublicPortsConfig_basic(rName),
159+
Check: resource.ComposeTestCheckFunc(
160+
testAccCheckInstancePublicPortsExists(resourceName),
161+
acctest.CheckResourceDisappears(acctest.Provider, tflightsail.ResourceInstance(), parentResourceName),
162+
),
163+
ExpectNonEmptyPlan: true,
164+
},
165+
},
166+
})
167+
}
168+
115169
func testAccCheckInstancePublicPortsExists(resourceName string) resource.TestCheckFunc {
116170
return func(s *terraform.State) error {
117171
rs, ok := s.RootModule().Resources[resourceName]

internal/service/lightsail/instance_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,44 @@ func TestAccLightsailInstance_tags(t *testing.T) {
138138
})
139139
}
140140

141+
func TestAccLightsailInstance_IPAddressType(t *testing.T) {
142+
var instance lightsail.Instance
143+
rName := sdkacctest.RandomWithPrefix("tf-acc-test")
144+
resourceName := "aws_lightsail_instance.test"
145+
146+
resource.ParallelTest(t, resource.TestCase{
147+
PreCheck: func() {
148+
acctest.PreCheck(t)
149+
acctest.PreCheckPartitionHasService(lightsail.EndpointsID, t)
150+
testAccPreCheck(t)
151+
},
152+
ErrorCheck: acctest.ErrorCheck(t, lightsail.EndpointsID),
153+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
154+
CheckDestroy: testAccCheckInstanceDestroy,
155+
Steps: []resource.TestStep{
156+
{
157+
Config: testAccInstanceConfig_IPAddressType(rName, "ipv4"),
158+
Check: resource.ComposeTestCheckFunc(
159+
testAccCheckInstanceExists(resourceName, &instance),
160+
resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv4"),
161+
),
162+
},
163+
{
164+
ResourceName: resourceName,
165+
ImportState: true,
166+
ImportStateVerify: true,
167+
},
168+
{
169+
Config: testAccInstanceConfig_IPAddressType(rName, "dualstack"),
170+
Check: resource.ComposeTestCheckFunc(
171+
testAccCheckInstanceExists(resourceName, &instance),
172+
resource.TestCheckResourceAttr(resourceName, "ip_address_type", "dualstack"),
173+
),
174+
},
175+
},
176+
})
177+
}
178+
141179
func TestAccLightsailInstance_disappears(t *testing.T) {
142180
var conf lightsail.Instance
143181
lightsailName := fmt.Sprintf("tf-test-lightsail-%d", sdkacctest.RandInt())
@@ -324,3 +362,22 @@ resource "aws_lightsail_instance" "lightsail_instance_test" {
324362
}
325363
`, lightsailName)
326364
}
365+
366+
func testAccInstanceConfig_IPAddressType(rName string, rIPAddressType string) string {
367+
return fmt.Sprintf(`
368+
data "aws_availability_zones" "available" {
369+
state = "available"
370+
filter {
371+
name = "opt-in-status"
372+
values = ["opt-in-not-required"]
373+
}
374+
}
375+
resource "aws_lightsail_instance" "test" {
376+
name = %[1]q
377+
availability_zone = data.aws_availability_zones.available.names[0]
378+
blueprint_id = "amazon_linux"
379+
bundle_id = "nano_1_0"
380+
ip_address_type = %[2]q
381+
}
382+
`, rName, rIPAddressType)
383+
}

internal/service/lightsail/key_pair.go

+3-19
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"log"
77
"strings"
8-
"time"
98

109
"github.com/aws/aws-sdk-go/aws"
1110
"github.com/aws/aws-sdk-go/aws/awserr"
@@ -152,16 +151,8 @@ func resourceKeyPairCreate(d *schema.ResourceData, meta interface{}) error {
152151
op = resp.Operation
153152
}
154153

155-
stateConf := &resource.StateChangeConf{
156-
Pending: []string{"Started"},
157-
Target: []string{"Completed", "Succeeded"},
158-
Refresh: resourceOperationRefreshFunc(op.Id, meta),
159-
Timeout: 10 * time.Minute,
160-
Delay: 5 * time.Second,
161-
MinTimeout: 3 * time.Second,
162-
}
154+
err := waitOperation(conn, op.Id)
163155

164-
_, err := stateConf.WaitForState()
165156
if err != nil {
166157
// We don't return an error here because the Create call succeeded
167158
log.Printf("[ERR] Error waiting for KeyPair (%s) to become ready: %s", d.Id(), err)
@@ -208,16 +199,9 @@ func resourceKeyPairDelete(d *schema.ResourceData, meta interface{}) error {
208199
}
209200

210201
op := resp.Operation
211-
stateConf := &resource.StateChangeConf{
212-
Pending: []string{"Started"},
213-
Target: []string{"Completed", "Succeeded"},
214-
Refresh: resourceOperationRefreshFunc(op.Id, meta),
215-
Timeout: 10 * time.Minute,
216-
Delay: 5 * time.Second,
217-
MinTimeout: 3 * time.Second,
218-
}
219202

220-
_, err = stateConf.WaitForState()
203+
err = waitOperation(conn, op.Id)
204+
221205
if err != nil {
222206
return fmt.Errorf(
223207
"Error waiting for KeyPair (%s) to become destroyed: %s",

0 commit comments

Comments
 (0)