Skip to content

Commit cbcee30

Browse files
authored
chore(iot): add support for fast retry with cassettes (#1192)
1 parent 4ef8327 commit cbcee30

22 files changed

+6878
-6218
lines changed

scaleway/helpers_iot.go

+18-17
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package scaleway
22

33
import (
4-
"fmt"
4+
"context"
55
"time"
66

77
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8-
iot "github.com/scaleway/scaleway-sdk-go/api/iot/v1"
8+
"github.com/scaleway/scaleway-sdk-go/api/iot/v1"
99
"github.com/scaleway/scaleway-sdk-go/scw"
1010
)
1111

12+
const (
13+
defaultIoTRetryInterval = 5 * time.Second
14+
defaultIoTHubTimeout = 5 * time.Minute
15+
)
16+
1217
func iotAPIWithRegion(d *schema.ResourceData, m interface{}) (*iot.API, scw.Region, error) {
1318
meta := m.(*Meta)
1419
iotAPI := iot.NewAPI(meta.scwClient)
@@ -26,24 +31,20 @@ func iotAPIWithRegionAndID(m interface{}, id string) (*iot.API, scw.Region, stri
2631
return iotAPI, region, ID, err
2732
}
2833

29-
func waitIotHub(iotAPI *iot.API, region scw.Region, hubID string, timeout time.Duration, desiredStates ...iot.HubStatus) error {
30-
hub, err := iotAPI.WaitForHub(&iot.WaitForHubRequest{
31-
HubID: hubID,
32-
Region: region,
33-
RetryInterval: DefaultWaitRetryInterval,
34-
Timeout: scw.TimeDurationPtr(timeout),
35-
})
36-
if err != nil {
37-
return err
34+
func waitIotHub(ctx context.Context, api *iot.API, region scw.Region, id string, timeout time.Duration) (*iot.Hub, error) {
35+
retryInterval := defaultIoTRetryInterval
36+
if DefaultWaitRetryInterval != nil {
37+
retryInterval = *DefaultWaitRetryInterval
3838
}
3939

40-
for _, desiredState := range desiredStates {
41-
if hub.Status == desiredState {
42-
return nil
43-
}
44-
}
40+
hub, err := api.WaitForHub(&iot.WaitForHubRequest{
41+
HubID: id,
42+
Region: region,
43+
RetryInterval: &retryInterval,
44+
Timeout: scw.TimeDurationPtr(timeout),
45+
}, scw.WithContext(ctx))
4546

46-
return fmt.Errorf("hub %s has state %s, wants one of %+q", hubID, hub.Status, desiredStates)
47+
return hub, err
4748
}
4849

4950
func extractRestHeaders(d *schema.ResourceData, key string) map[string]string {

scaleway/resource_iot_device.go

+7-24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scaleway
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
89
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -181,10 +182,6 @@ func resourceScalewayIotDeviceCreate(ctx context.Context, d *schema.ResourceData
181182
return diag.FromErr(err)
182183
}
183184

184-
////
185-
// Create device
186-
////
187-
188185
req := &iot.CreateDeviceRequest{
189186
Region: region,
190187
HubID: expandID(d.Get("hub_id")),
@@ -245,7 +242,7 @@ func resourceScalewayIotDeviceCreate(ctx context.Context, d *schema.ResourceData
245242

246243
d.SetId(newRegionalIDString(region, res.Device.ID))
247244

248-
// If user certifcate is provided.
245+
// If user certificate is provided.
249246
if devCrt, ok := d.GetOk("certificate.0.crt"); ok {
250247
// Set user certificate to device.
251248
// It cannot currently be added in the create device request.
@@ -258,7 +255,7 @@ func resourceScalewayIotDeviceCreate(ctx context.Context, d *schema.ResourceData
258255
return diag.FromErr(err)
259256
}
260257
} else {
261-
// Update certificate and key as they cannot be retreived later.
258+
// Update certificate and key as they cannot be retrieved later.
262259
cert := map[string]interface{}{
263260
"crt": res.Certificate.Crt,
264261
"key": res.Certificate.Key,
@@ -275,9 +272,6 @@ func resourceScalewayIotDeviceRead(ctx context.Context, d *schema.ResourceData,
275272
return diag.FromErr(err)
276273
}
277274

278-
////
279-
// Read Device
280-
////
281275
device, err := iotAPI.GetDevice(&iot.GetDeviceRequest{
282276
Region: region,
283277
DeviceID: deviceID,
@@ -293,9 +287,9 @@ func resourceScalewayIotDeviceRead(ctx context.Context, d *schema.ResourceData,
293287
_ = d.Set("name", device.Name)
294288
_ = d.Set("status", device.Status)
295289
_ = d.Set("hub_id", newRegionalID(region, device.HubID).String())
296-
_ = d.Set("created_at", device.CreatedAt.String())
297-
_ = d.Set("updated_at", device.UpdatedAt.String())
298-
_ = d.Set("last_activity_at", device.LastActivityAt.String())
290+
_ = d.Set("created_at", device.CreatedAt.Format(time.RFC3339))
291+
_ = d.Set("updated_at", device.UpdatedAt.Format(time.RFC3339))
292+
_ = d.Set("last_activity_at", device.LastActivityAt.Format(time.RFC3339))
299293
_ = d.Set("allow_insecure", device.AllowInsecure)
300294
_ = d.Set("allow_multiple_connections", device.AllowMultipleConnections)
301295
_ = d.Set("is_connected", device.IsConnected)
@@ -336,11 +330,8 @@ func resourceScalewayIotDeviceRead(ctx context.Context, d *schema.ResourceData,
336330
_ = d.Set("message_filters", []map[string]interface{}{mf})
337331
}
338332

339-
////
340333
// Read Device certificate
341-
////
342-
343-
// As we cannot read the key, we get back it from cache and do not change it.
334+
// As we cannot read the key, we get back it from state and do not change it.
344335
if devCrtKey, ok := d.GetOk("certificate.0.key"); ok {
345336
devCrt, err := iotAPI.GetDeviceCertificate(&iot.GetDeviceCertificateRequest{
346337
Region: region,
@@ -366,9 +357,6 @@ func resourceScalewayIotDeviceUpdate(ctx context.Context, d *schema.ResourceData
366357
return diag.FromErr(err)
367358
}
368359

369-
////
370-
// Update Device
371-
////
372360
updateRequest := &iot.UpdateDeviceRequest{
373361
Region: region,
374362
DeviceID: deviceID,
@@ -425,9 +413,7 @@ func resourceScalewayIotDeviceUpdate(ctx context.Context, d *schema.ResourceData
425413
return diag.FromErr(err)
426414
}
427415

428-
////
429416
// Set the device certificate if changed
430-
////
431417
if d.HasChange("certificate.0.crt") {
432418
_, err := iotAPI.SetDeviceCertificate(&iot.SetDeviceCertificateRequest{
433419
Region: region,
@@ -448,9 +434,6 @@ func resourceScalewayIotDeviceDelete(ctx context.Context, d *schema.ResourceData
448434
return diag.FromErr(err)
449435
}
450436

451-
////
452-
// Delete Device
453-
////
454437
err = iotAPI.DeleteDevice(&iot.DeleteDeviceRequest{
455438
Region: region,
456439
DeviceID: deviceID,

scaleway/resource_iot_device_test.go

+59-20
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,39 @@ WuePu1khrEuTaXKyaiLD3pmxM86F/6Ho6V86mJpKXr/wmMU56TcKk9UURucVQZ1o
3434
`
3535

3636
func TestAccScalewayIotDevice_Minimal(t *testing.T) {
37+
tt := NewTestTools(t)
38+
defer tt.Cleanup()
39+
resource.ParallelTest(t, resource.TestCase{
40+
PreCheck: func() { testAccPreCheck(t) },
41+
ProviderFactories: tt.ProviderFactories,
42+
// Destruction is done via the hub destruction.
43+
CheckDestroy: testAccCheckScalewayIotHubDestroy(tt),
44+
Steps: []resource.TestStep{
45+
{
46+
Config: `
47+
resource "scaleway_iot_device" "default-1" {
48+
name = "default-1"
49+
hub_id = scaleway_iot_hub.minimal-1.id
50+
}
51+
resource "scaleway_iot_hub" "minimal-1" {
52+
name = "minimal-1"
53+
product_plan = "plan_shared"
54+
}`,
55+
Check: resource.ComposeTestCheckFunc(
56+
testAccCheckScalewayIotHubExists(tt, "scaleway_iot_hub.minimal-1"),
57+
testAccCheckScalewayIotDeviceExists(tt, "scaleway_iot_device.default-1"),
58+
resource.TestCheckResourceAttrSet("scaleway_iot_device.default-1", "id"),
59+
resource.TestCheckResourceAttrSet("scaleway_iot_device.default-1", "hub_id"),
60+
resource.TestCheckResourceAttrSet("scaleway_iot_device.default-1", "name"),
61+
resource.TestCheckResourceAttr("scaleway_iot_device.default-1", "allow_insecure", "false"),
62+
resource.TestCheckResourceAttr("scaleway_iot_device.default-1", "allow_multiple_connections", "false"),
63+
),
64+
},
65+
},
66+
})
67+
}
68+
69+
func TestAccScalewayIotDevice_MessageFilters(t *testing.T) {
3770
tt := NewTestTools(t)
3871
defer tt.Cleanup()
3972
resource.ParallelTest(t, resource.TestCase{
@@ -84,26 +117,19 @@ func TestAccScalewayIotDevice_Minimal(t *testing.T) {
84117
resource.TestCheckResourceAttr("scaleway_iot_device.default-4", "message_filters.0.subscribe.0.topics.0", "4"),
85118
),
86119
},
87-
{
88-
Config: `
89-
resource "scaleway_iot_device" "default-1" {
90-
name = "default-1"
91-
hub_id = scaleway_iot_hub.minimal-1.id
92-
}
93-
resource "scaleway_iot_hub" "minimal-1" {
94-
name = "minimal-1"
95-
product_plan = "plan_shared"
96-
}`,
97-
Check: resource.ComposeTestCheckFunc(
98-
testAccCheckScalewayIotHubExists(tt, "scaleway_iot_hub.minimal-1"),
99-
testAccCheckScalewayIotDeviceExists(tt, "scaleway_iot_device.default-1"),
100-
resource.TestCheckResourceAttrSet("scaleway_iot_device.default-1", "id"),
101-
resource.TestCheckResourceAttrSet("scaleway_iot_device.default-1", "hub_id"),
102-
resource.TestCheckResourceAttrSet("scaleway_iot_device.default-1", "name"),
103-
resource.TestCheckResourceAttr("scaleway_iot_device.default-1", "allow_insecure", "false"),
104-
resource.TestCheckResourceAttr("scaleway_iot_device.default-1", "allow_multiple_connections", "false"),
105-
),
106-
},
120+
},
121+
})
122+
}
123+
124+
func TestAccScalewayIotDevice_AllowInsecure(t *testing.T) {
125+
tt := NewTestTools(t)
126+
defer tt.Cleanup()
127+
resource.ParallelTest(t, resource.TestCase{
128+
PreCheck: func() { testAccPreCheck(t) },
129+
ProviderFactories: tt.ProviderFactories,
130+
// Destruction is done via the hub destruction.
131+
CheckDestroy: testAccCheckScalewayIotHubDestroy(tt),
132+
Steps: []resource.TestStep{
107133
{
108134
Config: `
109135
resource "scaleway_iot_device" "default-2" {
@@ -146,6 +172,19 @@ func TestAccScalewayIotDevice_Minimal(t *testing.T) {
146172
resource.TestCheckResourceAttr("scaleway_iot_device.default-3", "allow_multiple_connections", "false"),
147173
),
148174
},
175+
},
176+
})
177+
}
178+
179+
func TestAccScalewayIotDevice_Certificate(t *testing.T) {
180+
tt := NewTestTools(t)
181+
defer tt.Cleanup()
182+
resource.ParallelTest(t, resource.TestCase{
183+
PreCheck: func() { testAccPreCheck(t) },
184+
ProviderFactories: tt.ProviderFactories,
185+
// Destruction is done via the hub destruction.
186+
CheckDestroy: testAccCheckScalewayIotHubDestroy(tt),
187+
Steps: []resource.TestStep{
149188
{
150189
Config: `
151190
resource "scaleway_iot_device" "default-5" {

scaleway/resource_iot_hub.go

+18-23
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package scaleway
22

33
import (
44
"context"
5+
"time"
56

67
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
78
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -19,6 +20,9 @@ func resourceScalewayIotHub() *schema.Resource {
1920
Importer: &schema.ResourceImporter{
2021
StateContext: schema.ImportStatePassthroughContext,
2122
},
23+
Timeouts: &schema.ResourceTimeout{
24+
Default: schema.DefaultTimeout(defaultIoTHubTimeout),
25+
},
2226
SchemaVersion: 0,
2327
Schema: map[string]*schema.Schema{
2428
"enabled": {
@@ -115,10 +119,6 @@ func resourceScalewayIotHubCreate(ctx context.Context, d *schema.ResourceData, m
115119
return diag.FromErr(err)
116120
}
117121

118-
////
119-
// Create cluster
120-
////
121-
122122
req := &iot.CreateHubRequest{
123123
Region: region,
124124
Name: expandOrGenerateString(d.Get("name"), "hub"),
@@ -141,8 +141,9 @@ func resourceScalewayIotHubCreate(ctx context.Context, d *schema.ResourceData, m
141141
if err != nil {
142142
return diag.FromErr(err)
143143
}
144+
d.SetId(newRegionalIDString(region, res.ID))
144145

145-
err = waitIotHub(iotAPI, region, res.ID, d.Timeout(schema.TimeoutCreate), iot.HubStatusReady)
146+
_, err = waitIotHub(ctx, iotAPI, region, res.ID, d.Timeout(schema.TimeoutCreate))
146147
if err != nil {
147148
return diag.FromErr(err)
148149
}
@@ -183,14 +184,12 @@ func resourceScalewayIotHubCreate(ctx context.Context, d *schema.ResourceData, m
183184
return diag.FromErr(err)
184185
}
185186

186-
err = waitIotHub(iotAPI, region, res.ID, d.Timeout(schema.TimeoutCreate), iot.HubStatusDisabled)
187+
_, err = waitIotHub(ctx, iotAPI, region, res.ID, d.Timeout(schema.TimeoutCreate))
187188
if err != nil {
188189
return diag.FromErr(err)
189190
}
190191
}
191192

192-
d.SetId(newRegionalIDString(region, res.ID))
193-
194193
return resourceScalewayIotHubRead(ctx, d, meta)
195194
}
196195

@@ -200,9 +199,6 @@ func resourceScalewayIotHubRead(ctx context.Context, d *schema.ResourceData, met
200199
return diag.FromErr(err)
201200
}
202201

203-
////
204-
// Read Hub
205-
////
206202
response, err := iotAPI.GetHub(&iot.GetHubRequest{
207203
Region: region,
208204
HubID: hubID,
@@ -222,8 +218,8 @@ func resourceScalewayIotHubRead(ctx context.Context, d *schema.ResourceData, met
222218
_ = d.Set("status", response.Status.String())
223219
_ = d.Set("product_plan", response.ProductPlan.String())
224220
_ = d.Set("endpoint", response.Endpoint)
225-
_ = d.Set("created_at", response.CreatedAt.String())
226-
_ = d.Set("updated_at", response.UpdatedAt.String())
221+
_ = d.Set("created_at", response.CreatedAt.Format(time.RFC3339))
222+
_ = d.Set("updated_at", response.UpdatedAt.Format(time.RFC3339))
227223
_ = d.Set("enabled", response.Enabled)
228224
_ = d.Set("device_count", int(response.DeviceCount))
229225
_ = d.Set("connected_device_count", int(response.ConnectedDeviceCount))
@@ -262,7 +258,7 @@ func resourceScalewayIotHubUpdate(ctx context.Context, d *schema.ResourceData, m
262258
return diag.FromErr(err)
263259
}
264260

265-
err = waitIotHub(iotAPI, region, hubID, d.Timeout(schema.TimeoutUpdate), iot.HubStatusReady, iot.HubStatusDisabled)
261+
_, err = waitIotHub(ctx, iotAPI, region, hubID, d.Timeout(schema.TimeoutUpdate))
266262
if err != nil {
267263
return diag.FromErr(err)
268264
}
@@ -323,23 +319,22 @@ func resourceScalewayIotHubUpdate(ctx context.Context, d *schema.ResourceData, m
323319
}
324320

325321
func resourceScalewayIotHubDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
326-
iotAPI, region, hubID, err := iotAPIWithRegionAndID(meta, d.Id())
322+
iotAPI, region, id, err := iotAPIWithRegionAndID(meta, d.Id())
327323
if err != nil {
328324
return diag.FromErr(err)
329325
}
330326

331-
////
332-
// Delete hub
333-
////
334327
err = iotAPI.DeleteHub(&iot.DeleteHubRequest{
335328
Region: region,
336-
HubID: hubID,
329+
HubID: id,
337330
// Don't force delete if devices. This avoids deleting a hub by mistake
338331
}, scw.WithContext(ctx))
339-
if err != nil {
340-
if is404Error(err) {
341-
return nil
342-
}
332+
if err != nil && !is404Error(err) {
333+
return diag.FromErr(err)
334+
}
335+
336+
_, err = waitIotHub(ctx, iotAPI, region, id, d.Timeout(schema.TimeoutDelete))
337+
if err != nil && !is404Error(err) {
343338
return diag.FromErr(err)
344339
}
345340

0 commit comments

Comments
 (0)