Skip to content

Commit 3c68921

Browse files
test: Enables simulation of cloud-dev using hoverfly in alert configuration acceptance tests (#2057)
* run capture mode in CI test to verify correct configuration of hoverfly ca cert * remove general https proxy variable and adjust specific go client * specify latest version of hoverfly * workaround in client to be able to use hoverfly sdk * adjust client code to use dynamic proxy port * define env variables for capturing and simulating * adding serialization of execution variables * linter fixes * define a single execution variables file per test * avoid creating project during simulate mode * fixing linter issues * define separate function for mux provider factory explicit for testing * replace hoverfly go sdk with cli to avoid usage of tls insecure flag * addressing PR comments, removing repetition of t.cleanup in each test * include small note in contribution guide * double quotes in scripts * simplify calls to ManageProjectID by changing signature * adjust docs
1 parent 69d69da commit 3c68921

18 files changed

+443
-140
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ log.*
3232
test.env
3333
__debug_*
3434
coverage.out
35+
simulations
3536

3637
website/vendor
3738

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Thanks for your interest in contributing to MongoDB Atlas Terraform Provider, th
1010
- [Open a Pull Request](#open-a-pull-request)
1111
- [Testing the Provider](#testing-the-provider)
1212
- [Running Acceptance Tests](#running-acceptance-tests)
13+
- [Replaying HTTP Requests with Hoverfly](#replaying-http-requests-with-hoverfly)
1314
- [Code and Test Best Practices](#code-and-test-best-practices)
1415
- [Creating New Resource and Data Sources](#creating-new-resources-and-data-sources)
1516
- [Scaffolding Initial Code and File Structure](#scaffolding-initial-code-and-file-structure)
@@ -252,7 +253,14 @@ You must also configure the following environment variables before running the t
252253
~> **Notice:** Acceptance tests create real resources, and often cost money to run. Please note in any PRs made if you are unable to pay to run acceptance tests for your contribution. We will accept "best effort" implementations of acceptance tests in this case and run them for you on our side. This may delay the contribution but we do not want your contribution blocked by funding.
253254
- Run `make testacc`
254255

256+
#### Replaying HTTP requests with hoverfly
255257

258+
Some resources allow recording and replaying http requests using hoverfly when running tests (e.g. alert_configuration acceptance tests). You will be able to identify this if the test calls `replay.SetupReplayProxy(t)`.
259+
260+
- For capturing http traffic of an execution you have to configure the environment variable `REPLAY_MODE=capture`. Captured request/responses will be present in the directory `./simulations`.
261+
- For replaying http traffic of an execution you have to configure the environment variable `REPLAY_MODE=simulate` which will use files present in the simulation directory.
262+
263+
**Note**: [Hoverfly](https://docs.hoverfly.io/en/latest/pages/introduction/introduction.html) is the proxy server used for capturing and simulating request. You must use the following [installation docs](https://docs.hoverfly.io/en/latest/pages/introduction/downloadinstallation.html#download-and-installation) to have the CLI available, as well as setting up the [hoverfly CA cert](https://docs.hoverfly.io/en/latest/pages/tutorials/basic/https/https.html) in your trust store.
256264

257265
### Testing Atlas Provider Versions that are NOT hosted on Terraform Registry (i.e. pre-release versions)
258266
To test development / pre-release versions of the Terraform Atlas Provider that are not hosted on the Terraform Registry, you will need to create a [Terraform Provider Network Mirror](https://developer.hashicorp.com/terraform/internals/provider-network-mirror-protocol).

internal/config/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type MongoDBClient struct {
3838
// Config contains the configurations needed to use SDKs
3939
type Config struct {
4040
AssumeRole *AssumeRole
41+
ProxyPort *int
4142
PublicKey string
4243
PrivateKey string
4344
BaseURL string
@@ -66,6 +67,14 @@ func (c *Config) NewClient(ctx context.Context) (any, error) {
6667
// setup a transport to handle digest
6768
transport := digest.NewTransport(cast.ToString(c.PublicKey), cast.ToString(c.PrivateKey))
6869

70+
// proxy is only used for testing purposes to connect with hoverfly for capturing/replaying requests
71+
if c.ProxyPort != nil {
72+
proxyURL, _ := url.Parse(fmt.Sprintf("http://localhost:%d", *c.ProxyPort))
73+
transport.Transport = &http.Transport{
74+
Proxy: http.ProxyURL(proxyURL),
75+
}
76+
}
77+
6978
// initialize the client
7079
client, err := transport.Client()
7180
if err != nil {

internal/provider/credentials.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ const (
1919
endPointSTSDefault = "https://sts.amazonaws.com"
2020
)
2121

22-
func configureCredentialsSTS(cfg config.Config, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint string) (config.Config, error) {
22+
func configureCredentialsSTS(cfg *config.Config, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint string) (config.Config, error) {
2323
ep, err := endpoints.GetSTSRegionalEndpoint("regional")
2424
if err != nil {
2525
log.Printf("GetSTSRegionalEndpoint error: %s", err)
26-
return cfg, err
26+
return *cfg, err
2727
}
2828

2929
defaultResolver := endpoints.DefaultResolver()
@@ -56,35 +56,35 @@ func configureCredentialsSTS(cfg config.Config, secret, region, awsAccessKeyID,
5656
_, err = sess.Config.Credentials.Get()
5757
if err != nil {
5858
log.Printf("Session get credentials error: %s", err)
59-
return cfg, err
59+
return *cfg, err
6060
}
6161
_, err = creds.Get()
6262
if err != nil {
6363
log.Printf("STS get credentials error: %s", err)
64-
return cfg, err
64+
return *cfg, err
6565
}
6666
secretString, err := secretsManagerGetSecretValue(sess, &aws.Config{Credentials: creds, Region: aws.String(region)}, secret)
6767
if err != nil {
6868
log.Printf("Get Secrets error: %s", err)
69-
return cfg, err
69+
return *cfg, err
7070
}
7171

7272
var secretData SecretData
7373
err = json.Unmarshal([]byte(secretString), &secretData)
7474
if err != nil {
75-
return cfg, err
75+
return *cfg, err
7676
}
7777
if secretData.PrivateKey == "" {
78-
return cfg, fmt.Errorf("secret missing value for credential PrivateKey")
78+
return *cfg, fmt.Errorf("secret missing value for credential PrivateKey")
7979
}
8080

8181
if secretData.PublicKey == "" {
82-
return cfg, fmt.Errorf("secret missing value for credential PublicKey")
82+
return *cfg, fmt.Errorf("secret missing value for credential PublicKey")
8383
}
8484

8585
cfg.PublicKey = secretData.PublicKey
8686
cfg.PrivateKey = secretData.PrivateKey
87-
return cfg, nil
87+
return *cfg, nil
8888
}
8989

9090
func secretsManagerGetSecretValue(sess *session.Session, creds *aws.Config, secret string) (string, error) {

internal/provider/provider.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ const (
4545
MissingAuthAttrError = "either Atlas Programmatic API Keys or AWS Secrets Manager attributes must be set"
4646
)
4747

48-
type MongodbtlasProvider struct{}
48+
type MongodbtlasProvider struct {
49+
proxyPort *int
50+
}
4951

5052
type tfMongodbAtlasProviderModel struct {
5153
AssumeRole types.List `tfsdk:"assume_role"`
@@ -229,6 +231,7 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config
229231
PrivateKey: data.PrivateKey.ValueString(),
230232
BaseURL: data.BaseURL.ValueString(),
231233
RealmBaseURL: data.RealmBaseURL.ValueString(),
234+
ProxyPort: p.proxyPort,
232235
}
233236

234237
var assumeRoles []tfAssumeRoleModel
@@ -243,7 +246,7 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config
243246
awsSessionToken := data.AwsSessionToken.ValueString()
244247
endpoint := data.StsEndpoint.ValueString()
245248
var err error
246-
cfg, err = configureCredentialsSTS(cfg, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint)
249+
cfg, err = configureCredentialsSTS(&cfg, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint)
247250
if err != nil {
248251
resp.Diagnostics.AddError("failed to configure credentials STS", err.Error())
249252
return
@@ -448,13 +451,23 @@ func (p *MongodbtlasProvider) Resources(context.Context) []func() resource.Resou
448451
return resources
449452
}
450453

451-
func NewFrameworkProvider() provider.Provider {
452-
return &MongodbtlasProvider{}
454+
func NewFrameworkProvider(proxyPort *int) provider.Provider {
455+
return &MongodbtlasProvider{
456+
proxyPort: proxyPort,
457+
}
458+
}
459+
460+
func MuxProviderFactory() func() tfprotov6.ProviderServer {
461+
return muxProviderFactory(nil)
462+
}
463+
464+
func MuxProviderFactoryForTesting(proxyPort *int) func() tfprotov6.ProviderServer {
465+
return muxProviderFactory(proxyPort)
453466
}
454467

455-
func MuxedProviderFactory() func() tfprotov6.ProviderServer {
456-
v2Provider := NewSdkV2Provider()
457-
newProvider := NewFrameworkProvider()
468+
func muxProviderFactory(proxyPort *int) func() tfprotov6.ProviderServer {
469+
v2Provider := NewSdkV2Provider(proxyPort)
470+
newProvider := NewFrameworkProvider(proxyPort)
458471
ctx := context.Background()
459472
upgradedSdkProvider, err := tf5to6server.UpgradeServer(ctx, v2Provider.GRPCProvider)
460473
if err != nil {

internal/provider/provider_sdk2.go

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ type SecretData struct {
7272
}
7373

7474
// NewSdkV2Provider returns the provider to be use by the code.
75-
func NewSdkV2Provider() *schema.Provider {
75+
func NewSdkV2Provider(proxyPort *int) *schema.Provider {
7676
provider := &schema.Provider{
7777
Schema: map[string]*schema.Schema{
7878
"public_key": {
@@ -135,7 +135,7 @@ func NewSdkV2Provider() *schema.Provider {
135135
},
136136
DataSourcesMap: getDataSourcesMap(),
137137
ResourcesMap: getResourcesMap(),
138-
ConfigureContextFunc: providerConfigure,
138+
ConfigureContextFunc: providerConfigure(proxyPort),
139139
}
140140
addPreviewFeatures(provider)
141141
return provider
@@ -283,41 +283,44 @@ func addPreviewFeatures(provider *schema.Provider) {
283283
}
284284
}
285285

286-
func providerConfigure(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) {
287-
diagnostics := setDefaultsAndValidations(d)
288-
if diagnostics.HasError() {
289-
return nil, diagnostics
290-
}
291-
292-
cfg := config.Config{
293-
PublicKey: d.Get("public_key").(string),
294-
PrivateKey: d.Get("private_key").(string),
295-
BaseURL: d.Get("base_url").(string),
296-
RealmBaseURL: d.Get("realm_base_url").(string),
297-
}
298-
299-
assumeRoleValue, ok := d.GetOk("assume_role")
300-
awsRoleDefined := ok && len(assumeRoleValue.([]any)) > 0 && assumeRoleValue.([]any)[0] != nil
301-
if awsRoleDefined {
302-
cfg.AssumeRole = expandAssumeRole(assumeRoleValue.([]any)[0].(map[string]any))
303-
secret := d.Get("secret_name").(string)
304-
region := conversion.MongoDBRegionToAWSRegion(d.Get("region").(string))
305-
awsAccessKeyID := d.Get("aws_access_key_id").(string)
306-
awsSecretAccessKey := d.Get("aws_secret_access_key").(string)
307-
awsSessionToken := d.Get("aws_session_token").(string)
308-
endpoint := d.Get("sts_endpoint").(string)
309-
var err error
310-
cfg, err = configureCredentialsSTS(cfg, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint)
286+
func providerConfigure(proxyPort *int) func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) {
287+
return func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) {
288+
diagnostics := setDefaultsAndValidations(d)
289+
if diagnostics.HasError() {
290+
return nil, diagnostics
291+
}
292+
293+
cfg := config.Config{
294+
PublicKey: d.Get("public_key").(string),
295+
PrivateKey: d.Get("private_key").(string),
296+
BaseURL: d.Get("base_url").(string),
297+
RealmBaseURL: d.Get("realm_base_url").(string),
298+
ProxyPort: proxyPort,
299+
}
300+
301+
assumeRoleValue, ok := d.GetOk("assume_role")
302+
awsRoleDefined := ok && len(assumeRoleValue.([]any)) > 0 && assumeRoleValue.([]any)[0] != nil
303+
if awsRoleDefined {
304+
cfg.AssumeRole = expandAssumeRole(assumeRoleValue.([]any)[0].(map[string]any))
305+
secret := d.Get("secret_name").(string)
306+
region := conversion.MongoDBRegionToAWSRegion(d.Get("region").(string))
307+
awsAccessKeyID := d.Get("aws_access_key_id").(string)
308+
awsSecretAccessKey := d.Get("aws_secret_access_key").(string)
309+
awsSessionToken := d.Get("aws_session_token").(string)
310+
endpoint := d.Get("sts_endpoint").(string)
311+
var err error
312+
cfg, err = configureCredentialsSTS(&cfg, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint)
313+
if err != nil {
314+
return nil, append(diagnostics, diag.FromErr(err)...)
315+
}
316+
}
317+
318+
client, err := cfg.NewClient(ctx)
311319
if err != nil {
312320
return nil, append(diagnostics, diag.FromErr(err)...)
313321
}
322+
return client, diagnostics
314323
}
315-
316-
client, err := cfg.NewClient(ctx)
317-
if err != nil {
318-
return nil, append(diagnostics, diag.FromErr(err)...)
319-
}
320-
return client, diagnostics
321324
}
322325

323326
func setDefaultsAndValidations(d *schema.ResourceData) diag.Diagnostics {

internal/provider/provider_sdk2_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
)
88

99
func TestSdkV2Provider(t *testing.T) {
10-
if err := provider.NewSdkV2Provider().InternalValidate(); err != nil {
10+
if err := provider.NewSdkV2Provider(nil).InternalValidate(); err != nil {
1111
t.Fatalf("err: %s", err)
1212
}
1313
}

internal/provider/provider_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
func TestResourceSchemas(t *testing.T) {
1414
t.Parallel()
1515
ctxProvider := context.Background()
16-
prov := provider.NewFrameworkProvider()
16+
prov := provider.NewFrameworkProvider(nil)
1717
var provReq providerfw.MetadataRequest
1818
var provRes providerfw.MetadataResponse
1919
prov.Metadata(ctxProvider, provReq, &provRes)
@@ -45,7 +45,7 @@ func TestResourceSchemas(t *testing.T) {
4545
func TestDataSourceSchemas(t *testing.T) {
4646
t.Parallel()
4747
ctxProvider := context.Background()
48-
prov := provider.NewFrameworkProvider()
48+
prov := provider.NewFrameworkProvider(nil)
4949
var provReq providerfw.MetadataRequest
5050
var provRes providerfw.MetadataResponse
5151
prov.Metadata(ctxProvider, provReq, &provRes)

internal/service/alertconfiguration/data_source_alert_configuration_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
8+
"github.com/hashicorp/terraform-plugin-testing/terraform"
89
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
910
)
1011

@@ -237,3 +238,11 @@ func configWithPagerDutyDS(projectID, serviceKey string, enabled bool) string {
237238
}
238239
`, projectID, serviceKey, enabled)
239240
}
241+
242+
func checkExists(resourceName string) resource.TestCheckFunc {
243+
return checkExistsUsingProxy(nil, resourceName)
244+
}
245+
246+
func checkDestroy(s *terraform.State) error {
247+
return checkDestroyUsingProxy(nil)(s)
248+
}

0 commit comments

Comments
 (0)