Skip to content

Commit 2c10620

Browse files
test: add cassette system (#617)
Co-authored-by: Rémy Léone <[email protected]>
1 parent e6d678b commit 2c10620

19 files changed

+1025
-146
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module github.com/terraform-providers/terraform-provider-scaleway
22

33
require (
44
github.com/aws/aws-sdk-go v1.34.32
5+
github.com/dnaeon/go-vcr v1.0.1
56
github.com/google/go-cmp v0.5.2
67
github.com/hashicorp/go-retryablehttp v0.6.7
78
github.com/hashicorp/terraform-plugin-sdk/v2 v2.0.3

main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ func main() {
1818
if debugMode {
1919
err := plugin.Debug(context.Background(), "registry.terraform.io/namespace/provider",
2020
&plugin.ServeOpts{
21-
ProviderFunc: scaleway.Provider(),
21+
ProviderFunc: scaleway.Provider(scaleway.DefaultProviderConfig()),
2222
})
2323
if err != nil {
2424
log.Println(err.Error())
2525
}
2626
} else {
2727
plugin.Serve(&plugin.ServeOpts{
28-
ProviderFunc: scaleway.Provider(),
28+
ProviderFunc: scaleway.Provider(scaleway.DefaultProviderConfig()),
2929
})
3030
}
3131
}

scaleway/provider.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scaleway
33
import (
44
"context"
55
"fmt"
6+
"net/http"
67

78
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
89
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -11,8 +12,20 @@ import (
1112
"github.com/scaleway/scaleway-sdk-go/validation"
1213
)
1314

15+
// Provider config can be used to provide additional config when creating provider.
16+
type ProviderConfig struct {
17+
// Meta can be used to override Meta that will be used by the provider.
18+
// This is useful for tests.
19+
Meta *Meta
20+
}
21+
22+
// DefaultProviderConfig return default ProviderConfig struct
23+
func DefaultProviderConfig() *ProviderConfig {
24+
return &ProviderConfig{}
25+
}
26+
1427
// Provider returns a terraform.ResourceProvider.
15-
func Provider() plugin.ProviderFunc {
28+
func Provider(config *ProviderConfig) plugin.ProviderFunc {
1629
return func() *schema.Provider {
1730
p := &schema.Provider{
1831
Schema: map[string]*schema.Schema{
@@ -102,9 +115,16 @@ func Provider() plugin.ProviderFunc {
102115

103116
p.ConfigureContextFunc = func(ctx context.Context, data *schema.ResourceData) (interface{}, diag.Diagnostics) {
104117
terraformVersion := p.TerraformVersion
118+
119+
// If we provide meta in config use it. This is useful for tests
120+
if config.Meta != nil {
121+
return config.Meta, nil
122+
}
123+
105124
meta, err := buildMeta(&MetaConfig{
106125
providerSchema: data,
107126
terraformVersion: terraformVersion,
127+
httpClient: &http.Client{Transport: newRetryableTransport(http.DefaultTransport)},
108128
})
109129
if err != nil {
110130
return nil, diag.FromErr(err)
@@ -128,12 +148,11 @@ type MetaConfig struct {
128148
providerSchema *schema.ResourceData
129149
terraformVersion string
130150
forceZone scw.Zone
151+
httpClient *http.Client
131152
}
132153

133154
// providerConfigure creates the Meta object containing the SDK client.
134155
func buildMeta(config *MetaConfig) (*Meta, error) {
135-
httpClient := createRetryableHTTPClient(false)
136-
137156
////
138157
// Load Profile
139158
////
@@ -158,7 +177,7 @@ func buildMeta(config *MetaConfig) (*Meta, error) {
158177
opts := []scw.ClientOption{
159178
scw.WithUserAgent(fmt.Sprintf("terraform-provider/%s terraform/%s", version, config.terraformVersion)),
160179
scw.WithProfile(profile),
161-
scw.WithHTTPClient(httpClient),
180+
scw.WithHTTPClient(config.httpClient),
162181
}
163182

164183
scwClient, err := scw.NewClient(opts...)

scaleway/provider_test.go

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
package scaleway
22

33
import (
4+
"flag"
5+
"net/http"
6+
"os"
7+
"path/filepath"
8+
"regexp"
9+
"strings"
410
"testing"
511

12+
"github.com/dnaeon/go-vcr/cassette"
13+
"github.com/dnaeon/go-vcr/recorder"
614
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
15+
"github.com/scaleway/scaleway-sdk-go/strcase"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
718
)
819

9-
var testAccProviders map[string]*schema.Provider
10-
var testAccProvider *schema.Provider
20+
var (
21+
// Deprecated
22+
testAccProviders map[string]*schema.Provider
23+
// Deprecated
24+
testAccProvider *schema.Provider
25+
26+
// UpdateCassettes will update all cassettes of a given test
27+
UpdateCassettes = flag.Bool("cassettes", os.Getenv("TF_UPDATE_CASSETTES") == "true", "Record Cassettes")
28+
)
1129

1230
func init() {
13-
p := Provider()()
31+
p := Provider(DefaultProviderConfig())()
1432
testAccProvider = p
1533
version += "-tftest"
1634
testAccProviders = map[string]*schema.Provider{
@@ -19,3 +37,83 @@ func init() {
1937
}
2038

2139
func testAccPreCheck(_ *testing.T) {}
40+
41+
// getTestFilePath returns a valid filename path based on the go test name and suffix. (Take care of non fs friendly char)
42+
func getTestFilePath(t *testing.T, suffix string) string {
43+
specialChars := regexp.MustCompile(`[\\?%*:|"<>. ]`)
44+
45+
// Replace nested tests separators.
46+
fileName := strings.Replace(t.Name(), "/", "-", -1)
47+
48+
fileName = strcase.ToBashArg(fileName)
49+
50+
// Replace special characters.
51+
fileName = specialChars.ReplaceAllLiteralString(fileName, "") + suffix
52+
53+
// Remove prefix to simplify
54+
fileName = strings.TrimPrefix(fileName, "test-acc-scaleway-")
55+
56+
return filepath.Join(".", "testdata", fileName)
57+
}
58+
59+
// getHTTPRecoder creates a new httpClient that records all HTTP requests in a cassette.
60+
// This cassette is then replayed whenever tests are executed again. This means that once the
61+
// requests are recorded in the cassette, no more real HTTP requests must be made to run the tests.
62+
//
63+
// It is important to add a `defer cleanup()` so the given cassette files are correctly
64+
// closed and saved after the requests.
65+
func getHTTPRecoder(t *testing.T, update bool) (client *http.Client, cleanup func(), err error) {
66+
recorderMode := recorder.ModeReplaying
67+
if update {
68+
recorderMode = recorder.ModeRecording
69+
}
70+
71+
// Setup recorder and scw client
72+
r, err := recorder.NewAsMode(getTestFilePath(t, ".cassette"), recorderMode, nil)
73+
if err != nil {
74+
return nil, nil, err
75+
}
76+
77+
// Add a filter which removes Authorization headers from all requests:
78+
r.AddFilter(func(i *cassette.Interaction) error {
79+
delete(i.Request.Headers, "x-auth-token")
80+
delete(i.Request.Headers, "X-Auth-Token")
81+
return nil
82+
})
83+
84+
return &http.Client{Transport: newRetryableTransport(r)}, func() {
85+
assert.NoError(t, r.Stop()) // Make sure recorder is stopped once done with it
86+
}, nil
87+
}
88+
89+
type TestTools struct {
90+
T *testing.T
91+
Meta *Meta
92+
ProviderFactories map[string]func() (*schema.Provider, error)
93+
Cleanup func()
94+
}
95+
96+
func NewTestTools(t *testing.T) *TestTools {
97+
// Create an http client with recording capabilities
98+
httpClient, cleanup, err := getHTTPRecoder(t, *UpdateCassettes)
99+
require.NoError(t, err)
100+
101+
// Create meta that will be passed in the provider config
102+
meta, err := buildMeta(&MetaConfig{
103+
providerSchema: nil,
104+
terraformVersion: "terraform-tests",
105+
httpClient: httpClient,
106+
})
107+
require.NoError(t, err)
108+
109+
return &TestTools{
110+
T: t,
111+
Meta: meta,
112+
ProviderFactories: map[string]func() (*schema.Provider, error){
113+
"scaleway": func() (*schema.Provider, error) {
114+
return Provider(&ProviderConfig{Meta: meta})(), nil
115+
},
116+
},
117+
Cleanup: cleanup,
118+
}
119+
}

scaleway/resource_baremetal_server_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ func init() {
1818
}
1919

2020
func testSweepBaremetalServer(_ string) error {
21-
return sweepZones([]scw.Zone{scw.ZoneFrPar2}, func(scwClient *scw.Client) error {
21+
return sweepZones([]scw.Zone{scw.ZoneFrPar2}, func(scwClient *scw.Client, zone scw.Zone) error {
2222
baremetalAPI := baremetal.NewAPI(scwClient)
23-
zone, _ := scwClient.GetDefaultZone()
2423
l.Debugf("sweeper: destroying the baremetal server in (%s)", zone)
2524
listServers, err := baremetalAPI.ListServers(&baremetal.ListServersRequest{}, scw.WithAllPages())
2625
if err != nil {

scaleway/resource_instance_ip.go

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
package scaleway
22

33
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
47
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
58
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
9+
"github.com/scaleway/scaleway-sdk-go/scw"
610
)
711

812
func resourceScalewayInstanceIP() *schema.Resource {
913
return &schema.Resource{
10-
Create: resourceScalewayInstanceIPCreate,
11-
Read: resourceScalewayInstanceIPRead,
12-
Delete: resourceScalewayInstanceIPDelete,
13-
14-
// Because of removed attribute server_id we must add an update func that does nothing. This could be removed on
15-
// next major release.
16-
Update: resourceScalewayInstanceIPRead,
17-
14+
CreateContext: resourceScalewayInstanceIPCreate,
15+
ReadContext: resourceScalewayInstanceIPRead,
16+
DeleteContext: resourceScalewayInstanceIPDelete,
1817
Importer: &schema.ResourceImporter{
19-
State: schema.ImportStatePassthrough,
18+
StateContext: schema.ImportStatePassthroughContext,
2019
},
2120
SchemaVersion: 0,
2221
Schema: map[string]*schema.Schema{
@@ -32,7 +31,7 @@ func resourceScalewayInstanceIP() *schema.Resource {
3231
},
3332
"server_id": {
3433
Type: schema.TypeString,
35-
Optional: true,
34+
Computed: true,
3635
Description: "The server associated with this IP",
3736
},
3837
"zone": zoneSchema(),
@@ -42,67 +41,73 @@ func resourceScalewayInstanceIP() *schema.Resource {
4241
}
4342
}
4443

45-
func resourceScalewayInstanceIPCreate(d *schema.ResourceData, m interface{}) error {
44+
func resourceScalewayInstanceIPCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
4645
instanceAPI, zone, err := instanceAPIWithZone(d, m)
4746
if err != nil {
48-
return err
47+
return diag.FromErr(err)
4948
}
5049

5150
res, err := instanceAPI.CreateIP(&instance.CreateIPRequest{
5251
Zone: zone,
5352
Organization: expandStringPtr(d.Get("organization_id")),
5453
Project: expandStringPtr(d.Get("project_id")),
55-
})
54+
}, scw.WithContext(ctx))
5655
if err != nil {
57-
return err
56+
return diag.FromErr(err)
5857
}
5958

6059
d.SetId(newZonedIDString(zone, res.IP.ID))
61-
return resourceScalewayInstanceIPRead(d, m)
60+
return resourceScalewayInstanceIPRead(ctx, d, m)
6261
}
6362

64-
func resourceScalewayInstanceIPRead(d *schema.ResourceData, m interface{}) error {
63+
func resourceScalewayInstanceIPRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
6564
instanceAPI, zone, ID, err := instanceAPIWithZoneAndID(m, d.Id())
6665
if err != nil {
67-
return err
66+
return diag.FromErr(err)
6867
}
6968

7069
res, err := instanceAPI.GetIP(&instance.GetIPRequest{
7170
IP: ID,
7271
Zone: zone,
73-
})
72+
}, scw.WithContext(ctx))
7473

7574
if err != nil {
7675
// We check for 403 because instance API returns 403 for a deleted IP
7776
if is404Error(err) || is403Error(err) {
7877
d.SetId("")
7978
return nil
8079
}
81-
return err
80+
return diag.FromErr(err)
8281
}
8382

8483
_ = d.Set("address", res.IP.Address.String())
85-
_ = d.Set("zone", string(zone))
84+
_ = d.Set("zone", zone)
8685
_ = d.Set("organization_id", res.IP.Organization)
8786
_ = d.Set("project_id", res.IP.Project)
8887
_ = d.Set("reverse", res.IP.Reverse)
8988

89+
if res.IP.Server != nil {
90+
_ = d.Set("server_id", newZonedIDString(res.IP.Zone, res.IP.Server.ID))
91+
} else {
92+
_ = d.Set("server_id", "")
93+
}
94+
9095
return nil
9196
}
9297

93-
func resourceScalewayInstanceIPDelete(d *schema.ResourceData, m interface{}) error {
98+
func resourceScalewayInstanceIPDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
9499
instanceAPI, zone, ID, err := instanceAPIWithZoneAndID(m, d.Id())
95100
if err != nil {
96-
return err
101+
return diag.FromErr(err)
97102
}
98103

99104
err = instanceAPI.DeleteIP(&instance.DeleteIPRequest{
100105
IP: ID,
101106
Zone: zone,
102-
})
107+
}, scw.WithContext(ctx))
103108

104109
if err != nil && !is404Error(err) {
105-
return err
110+
return diag.FromErr(err)
106111
}
107112

108113
return nil

scaleway/resource_instance_ip_reverse_dns_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import (
88

99
func TestAccScalewayInstanceReverseDnsIP(t *testing.T) {
1010
resource.ParallelTest(t, resource.TestCase{
11-
PreCheck: func() { testAccPreCheck(t) },
12-
Providers: testAccProviders,
13-
CheckDestroy: testAccCheckScalewayInstanceIPDestroy,
11+
PreCheck: func() { testAccPreCheck(t) },
12+
Providers: testAccProviders,
13+
// TODO enable back when using TestTools
14+
//CheckDestroy: testAccCheckScalewayInstanceIPDestroy,
1415
Steps: []resource.TestStep{
1516
{
1617
Config: `

0 commit comments

Comments
 (0)