From 0658fe47dc9c1a19c0c33ad1ece13e39e961e8a6 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:16:14 +0200 Subject: [PATCH 01/41] small change to trigger tests --- internal/config/transport_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/transport_test.go b/internal/config/transport_test.go index 68c20461f0..3a0917331e 100644 --- a/internal/config/transport_test.go +++ b/internal/config/transport_test.go @@ -171,7 +171,7 @@ func TestAccNetworkLogging(t *testing.T) { client, ok := clientInterface.(*config.MongoDBClient) require.True(t, ok) - // Make a simple API call that should trigger our enhanced logging + // Make a simple API call that should trigger our enhanced logging. _, _, err = client.AtlasV2.OrganizationsApi.ListOrgs(t.Context()).Execute() require.NoError(t, err) logStr := logOutput.String() From bcb6cdb12c9f038eae23b4a30f0f0f68660ff706 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 1 Oct 2025 19:20:06 +0200 Subject: [PATCH 02/41] don't use migration tests --- .github/workflows/acceptance-tests-runner.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/acceptance-tests-runner.yml b/.github/workflows/acceptance-tests-runner.yml index 9f38d7b051..3fd5fcaa57 100644 --- a/.github/workflows/acceptance-tests-runner.yml +++ b/.github/workflows/acceptance-tests-runner.yml @@ -196,9 +196,10 @@ env: TF_ACC: 1 TF_LOG: ${{ vars.LOG_LEVEL }} ACCTEST_TIMEOUT: ${{ vars.ACCTEST_TIMEOUT }} - # Only Migration tests are run when a specific previous provider version is set - # If the name (regex) of the test is set, only that test is run - ACCTEST_REGEX_RUN: ${{ inputs.test_name || inputs.provider_version == '' && '^Test(Acc|Mig)' || '^TestMig' }} + # If the name (regex) of the test is set, only that test is run. + # Don't run migration tests if using Service Accounts because previous provider versions don't support SA yet. + # Only Migration tests are run when a specific previous provider version is set. + ACCTEST_REGEX_RUN: ${{ inputs.test_name || inputs.use_sa && '^TestAcc' || inputs.provider_version == '' && '^Test(Acc|Mig)' || '^TestMig' }} MONGODB_ATLAS_BASE_URL: ${{ inputs.mongodb_atlas_base_url }} MONGODB_REALM_BASE_URL: ${{ inputs.mongodb_realm_base_url }} MONGODB_ATLAS_ORG_ID: ${{ inputs.mongodb_atlas_org_id }} @@ -548,6 +549,7 @@ jobs: MONGODB_ATLAS_CLIENT_ID: ${{ secrets.mongodb_atlas_client_id }} MONGODB_ATLAS_CLIENT_SECRET: ${{ secrets.mongodb_atlas_client_secret }} MONGODB_ATLAS_LAST_VERSION: ${{ needs.get-provider-version.outputs.provider_version }} + ACCTEST_REGEX_RUN: '^TestAcc' # Don't run migration tests because previous provider versions don't support SA. ACCTEST_PACKAGES: | ./internal/service/alertconfiguration ./internal/service/databaseuser From 425a8b0d0ebdfa285fdc29515468d4f853f955d4 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:08:01 +0200 Subject: [PATCH 03/41] rename credentials files to aws_credentials --- internal/provider/{credentials.go => aws_credentials.go} | 0 .../provider/{credentials_test.go => aws_credentials_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename internal/provider/{credentials.go => aws_credentials.go} (100%) rename internal/provider/{credentials_test.go => aws_credentials_test.go} (100%) diff --git a/internal/provider/credentials.go b/internal/provider/aws_credentials.go similarity index 100% rename from internal/provider/credentials.go rename to internal/provider/aws_credentials.go diff --git a/internal/provider/credentials_test.go b/internal/provider/aws_credentials_test.go similarity index 100% rename from internal/provider/credentials_test.go rename to internal/provider/aws_credentials_test.go From be7f9a676f1692a702fffc351ed2cb8f24667bbb Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 2 Oct 2025 21:56:39 +0200 Subject: [PATCH 04/41] refactor NewClient and create Credentials --- internal/config/client.go | 207 ++++++++---------- internal/config/credentials.go | 24 ++ internal/config/service_account.go | 16 +- internal/config/transport_test.go | 6 +- internal/provider/provider.go | 11 +- internal/provider/provider_sdk2.go | 11 +- .../organization/resource_organization.go | 20 +- .../resource_organization_test.go | 6 +- internal/testutil/acc/factory.go | 12 +- 9 files changed, 172 insertions(+), 141 deletions(-) create mode 100644 internal/config/credentials.go diff --git a/internal/config/client.go b/internal/config/client.go index c44fb9bcd8..3b03092a6c 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -42,10 +42,10 @@ const ( type AuthMethod int const ( - ServiceAccount AuthMethod = iota - Digest + Unknown AuthMethod = iota AccessToken - Unknown + ServiceAccount + Digest ) // CredentialProvider interface for types that can provide MongoDB Atlas credentials @@ -148,40 +148,15 @@ type SecretData struct { PrivateKey string `json:"private_key"` } -type UAMetadata struct { - Name string - Value string -} - -func (c *Config) NewClient(ctx context.Context) (any, error) { - transport := networkLoggingBaseTransport() - switch ResolveAuthMethod(c) { - case AccessToken: - tokenSource := oauth2.StaticTokenSource(&oauth2.Token{ - AccessToken: c.AccessToken, - TokenType: "Bearer", // Use a static bearer token with oauth2 transport. - }) - transport = &oauth2.Transport{ - Source: tokenSource, - Base: networkLoggingBaseTransport(), - } - case ServiceAccount: - tokenSource, err := getTokenSource(c, networkLoggingBaseTransport()) - if err != nil { - return nil, err - } - transport = &oauth2.Transport{ - Source: tokenSource, - Base: networkLoggingBaseTransport(), - } - case Digest: - transport = digest.NewTransportWithHTTPRoundTripper(c.PublicKey, c.PrivateKey, networkLoggingBaseTransport()) - case Unknown: +func NewClient(ctx context.Context, c *Credentials, terraformVersion string) (*MongoDBClient, error) { + userAgent := userAgent(terraformVersion) + client, err := getHTTPClient(ctx, c) + if err != nil { + return nil, err } - client := &http.Client{Transport: tfLoggingInterceptor(transport)} // Initialize the old SDK - optsAtlas := []matlasClient.ClientOpt{matlasClient.SetUserAgent(userAgent(c))} + optsAtlas := []matlasClient.ClientOpt{matlasClient.SetUserAgent(userAgent)} if c.BaseURL != "" { optsAtlas = append(optsAtlas, matlasClient.SetBaseURL(c.BaseURL)) } @@ -191,26 +166,27 @@ func (c *Config) NewClient(ctx context.Context) (any, error) { } // Initialize the new SDK for different versions - sdkV2Client, err := c.newSDKV2Client(client) + sdkV2Client, err := newSDKV2Client(client, c.BaseURL, userAgent) if err != nil { return nil, err } - sdkPreviewClient, err := c.newSDKPreviewClient(client) + sdkPreviewClient, err := newSDKPreviewClient(client, c.BaseURL, userAgent) if err != nil { return nil, err } - sdkV220240530Client, err := c.newSDKV220240530Client(client) + sdkV220240530Client, err := newSDKV220240530Client(client, c.BaseURL, userAgent) if err != nil { return nil, err } - sdkV220240805Client, err := c.newSDKV220240805Client(client) + sdkV220240805Client, err := newSDKV220240805Client(client, c.BaseURL, userAgent) if err != nil { return nil, err } - sdkV220241113Client, err := c.newSDKV220241113Client(client) + sdkV220241113Client, err := newSDKV220241113Client(client, c.BaseURL, userAgent) if err != nil { return nil, err } + clients := &MongoDBClient{ Atlas: atlasClient, AtlasV2: sdkV2Client, @@ -218,88 +194,94 @@ func (c *Config) NewClient(ctx context.Context) (any, error) { AtlasV220240530: sdkV220240530Client, AtlasV220240805: sdkV220240805Client, AtlasV220241113: sdkV220241113Client, - Config: c, + // TODO: Config: c, } return clients, nil } -func (c *Config) newSDKV2Client(client *http.Client) (*admin.APIClient, error) { - opts := []admin.ClientModifier{ - admin.UseHTTPClient(client), - admin.UseUserAgent(userAgent(c)), - admin.UseBaseURL(c.BaseURL), - admin.UseDebug(false)} - - sdk, err := admin.NewClient(opts...) - if err != nil { - return nil, err +func getHTTPClient(ctx context.Context, c *Credentials) (*http.Client, error) { + transport := networkLoggingBaseTransport() + switch c.AuthMethod() { + case AccessToken: + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: c.AccessToken, + TokenType: "Bearer", // Use a static bearer token with oauth2 transport. + }) + transport = &oauth2.Transport{ + Source: tokenSource, + Base: networkLoggingBaseTransport(), + } + case ServiceAccount: + tokenSource, err := getTokenSource(c.ClientID, c.ClientSecret, c.BaseURL, networkLoggingBaseTransport()) + if err != nil { + return nil, err + } + transport = &oauth2.Transport{ + Source: tokenSource, + Base: networkLoggingBaseTransport(), + } + case Digest: + transport = digest.NewTransportWithHTTPRoundTripper(c.PublicKey, c.PrivateKey, networkLoggingBaseTransport()) + case Unknown: } - return sdk, nil + return &http.Client{Transport: tfLoggingInterceptor(transport)}, nil } -func (c *Config) newSDKPreviewClient(client *http.Client) (*adminpreview.APIClient, error) { - opts := []adminpreview.ClientModifier{ - adminpreview.UseHTTPClient(client), - adminpreview.UseUserAgent(userAgent(c)), - adminpreview.UseBaseURL(c.BaseURL), - adminpreview.UseDebug(false)} +func newSDKV2Client(client *http.Client, baseURL, userAgent string) (*admin.APIClient, error) { + return admin.NewClient( + admin.UseHTTPClient(client), + admin.UseUserAgent(userAgent), + admin.UseBaseURL(baseURL), + admin.UseDebug(false), + ) +} - sdk, err := adminpreview.NewClient(opts...) - if err != nil { - return nil, err - } - return sdk, nil +func newSDKPreviewClient(client *http.Client, baseURL, userAgent string) (*adminpreview.APIClient, error) { + return adminpreview.NewClient( + adminpreview.UseHTTPClient(client), + adminpreview.UseUserAgent(userAgent), + adminpreview.UseBaseURL(baseURL), + adminpreview.UseDebug(false), + ) } -func (c *Config) newSDKV220240530Client(client *http.Client) (*admin20240530.APIClient, error) { - opts := []admin20240530.ClientModifier{ +func newSDKV220240530Client(client *http.Client, baseURL, userAgent string) (*admin20240530.APIClient, error) { + return admin20240530.NewClient( admin20240530.UseHTTPClient(client), - admin20240530.UseUserAgent(userAgent(c)), - admin20240530.UseBaseURL(c.BaseURL), - admin20240530.UseDebug(false)} - - sdk, err := admin20240530.NewClient(opts...) - if err != nil { - return nil, err - } - return sdk, nil + admin20240530.UseUserAgent(userAgent), + admin20240530.UseBaseURL(baseURL), + admin20240530.UseDebug(false), + ) } -func (c *Config) newSDKV220240805Client(client *http.Client) (*admin20240805.APIClient, error) { - opts := []admin20240805.ClientModifier{ +func newSDKV220240805Client(client *http.Client, baseURL, userAgent string) (*admin20240805.APIClient, error) { + return admin20240805.NewClient( admin20240805.UseHTTPClient(client), - admin20240805.UseUserAgent(userAgent(c)), - admin20240805.UseBaseURL(c.BaseURL), - admin20240805.UseDebug(false)} - - sdk, err := admin20240805.NewClient(opts...) - if err != nil { - return nil, err - } - return sdk, nil + admin20240805.UseUserAgent(userAgent), + admin20240805.UseBaseURL(baseURL), + admin20240805.UseDebug(false), + ) } -func (c *Config) newSDKV220241113Client(client *http.Client) (*admin20241113.APIClient, error) { - opts := []admin20241113.ClientModifier{ +func newSDKV220241113Client(client *http.Client, baseURL, userAgent string) (*admin20241113.APIClient, error) { + return admin20241113.NewClient( admin20241113.UseHTTPClient(client), - admin20241113.UseUserAgent(userAgent(c)), - admin20241113.UseBaseURL(c.BaseURL), - admin20241113.UseDebug(false)} - - sdk, err := admin20241113.NewClient(opts...) - if err != nil { - return nil, err - } - return sdk, nil + admin20241113.UseUserAgent(userAgent), + admin20241113.UseBaseURL(baseURL), + admin20241113.UseDebug(false), + ) } +// TODO: lazy because it needs connection func (c *MongoDBClient) GetRealmClient(ctx context.Context) (*realm.Client, error) { // Realm if c.Config.PublicKey == "" && c.Config.PrivateKey == "" { return nil, errors.New("please set `public_key` and `private_key` in order to use the realm client") } - optsRealm := []realm.ClientOpt{realm.SetUserAgent(userAgent(c.Config))} + optsRealm := []realm.ClientOpt{ + realm.SetUserAgent(userAgent(c.Config.TerraformVersion)), + } authConfig := realmAuth.NewConfig(nil) if c.Config.BaseURL != "" && c.Config.RealmBaseURL != "" { @@ -372,20 +354,6 @@ func (c *MongoDBClient) UntypedAPICall(ctx context.Context, params *APICallParam return apiResp, err } -func userAgent(c *Config) string { - metadata := []UAMetadata{ - {toolName, version.ProviderVersion}, - {terraformPlatformName, c.TerraformVersion}, - } - var parts []string - for _, info := range metadata { - part := fmt.Sprintf("%s/%s", info.Name, info.Value) - parts = append(parts, part) - } - - return strings.Join(parts, " ") -} - // ResolveAuthMethod determines the authentication method from any credential provider func ResolveAuthMethod(cg CredentialProvider) AuthMethod { if IsAccessTokenAuthPresent(cg) { @@ -399,3 +367,22 @@ func ResolveAuthMethod(cg CredentialProvider) AuthMethod { } return Unknown } + +func userAgent(terraformVersion string) string { + metadata := []struct { + Name string + Value string + }{ + {toolName, version.ProviderVersion}, + {terraformPlatformName, terraformVersion}, + } + var parts []string + for _, info := range metadata { + if info.Value == "" { + continue + } + part := fmt.Sprintf("%s/%s", info.Name, info.Value) + parts = append(parts, part) + } + return strings.Join(parts, " ") +} diff --git a/internal/config/credentials.go b/internal/config/credentials.go new file mode 100644 index 0000000000..7e0eb6c055 --- /dev/null +++ b/internal/config/credentials.go @@ -0,0 +1,24 @@ +package config + +type Credentials struct { + AccessToken string + ClientID string + ClientSecret string + PublicKey string + PrivateKey string + BaseURL string + RealmBaseURL string +} + +func (c *Credentials) AuthMethod() AuthMethod { + if c.AccessToken != "" { + return AccessToken + } + if c.ClientID != "" || c.ClientSecret != "" { + return ServiceAccount + } + if c.PublicKey != "" || c.PrivateKey != "" { + return Digest + } + return Unknown +} diff --git a/internal/config/service_account.go b/internal/config/service_account.go index 6a214e9b17..51217d10c2 100644 --- a/internal/config/service_account.go +++ b/internal/config/service_account.go @@ -24,20 +24,20 @@ var saInfo = struct { mu sync.Mutex }{} -func getTokenSource(c *Config, tokenRenewalBase http.RoundTripper) (auth.TokenSource, error) { +func getTokenSource(clientID, clientSecret, baseURL string, tokenRenewalBase http.RoundTripper) (auth.TokenSource, error) { saInfo.mu.Lock() defer saInfo.mu.Unlock() if saInfo.tokenSource != nil { // Token source in cache. - if saInfo.clientID != c.ClientID || saInfo.clientSecret != c.ClientSecret || saInfo.baseURL != c.BaseURL { + if saInfo.clientID != clientID || saInfo.clientSecret != clientSecret || saInfo.baseURL != baseURL { return nil, fmt.Errorf("service account credentials changed") } return saInfo.tokenSource, nil } - conf := clientcredentials.NewConfig(c.ClientID, c.ClientSecret) - if c.BaseURL != "" { - baseURL := strings.TrimRight(c.BaseURL, "/") + conf := clientcredentials.NewConfig(clientID, clientSecret) + if baseURL != "" { + baseURL = strings.TrimRight(baseURL, "/") conf.TokenURL = baseURL + clientcredentials.TokenAPIPath conf.RevokeURL = baseURL + clientcredentials.RevokeAPIPath } @@ -47,9 +47,9 @@ func getTokenSource(c *Config, tokenRenewalBase http.RoundTripper) (auth.TokenSo if _, err := tokenSource.Token(); err != nil { // Retrieve token to fail-fast if credentials are invalid. return nil, err } - saInfo.clientID = c.ClientID - saInfo.clientSecret = c.ClientSecret - saInfo.baseURL = c.BaseURL + saInfo.clientID = clientID + saInfo.clientSecret = clientSecret + saInfo.baseURL = baseURL saInfo.tokenSource = tokenSource return saInfo.tokenSource, nil } diff --git a/internal/config/transport_test.go b/internal/config/transport_test.go index 3a0917331e..5a8387d76f 100644 --- a/internal/config/transport_test.go +++ b/internal/config/transport_test.go @@ -159,17 +159,15 @@ func TestAccNetworkLogging(t *testing.T) { var logOutput bytes.Buffer log.SetOutput(&logOutput) defer log.SetOutput(os.Stderr) - cfg := &config.Config{ + c := &config.Credentials{ PublicKey: os.Getenv("MONGODB_ATLAS_PUBLIC_KEY"), PrivateKey: os.Getenv("MONGODB_ATLAS_PRIVATE_KEY"), ClientID: os.Getenv("MONGODB_ATLAS_CLIENT_ID"), ClientSecret: os.Getenv("MONGODB_ATLAS_CLIENT_SECRET"), BaseURL: os.Getenv("MONGODB_ATLAS_BASE_URL"), } - clientInterface, err := cfg.NewClient(t.Context()) + client, err := config.NewClient(t.Context(), c, "") require.NoError(t, err) - client, ok := clientInterface.(*config.MongoDBClient) - require.True(t, ok) // Make a simple API call that should trigger our enhanced logging. _, _, err = client.AtlasV2.OrganizationsApi.ListOrgs(t.Context()).Execute() diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9ec45dbca1..735c4a6b01 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -315,7 +315,16 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config } } - client, err := cfg.NewClient(ctx) + c := &config.Credentials{ + AccessToken: cfg.AccessToken, + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + PublicKey: cfg.PublicKey, + PrivateKey: cfg.PrivateKey, + BaseURL: cfg.BaseURL, + RealmBaseURL: cfg.RealmBaseURL, + } + client, err := config.NewClient(ctx, c, cfg.TerraformVersion) if err != nil { resp.Diagnostics.AddError( diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 47a6df2997..80eee1c199 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -330,7 +330,16 @@ func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *s } } - client, err := cfg.NewClient(ctx) + c := &config.Credentials{ + AccessToken: cfg.AccessToken, + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + PublicKey: cfg.PublicKey, + PrivateKey: cfg.PrivateKey, + BaseURL: cfg.BaseURL, + RealmBaseURL: cfg.RealmBaseURL, + } + client, err := config.NewClient(ctx, c, cfg.TerraformVersion) if err != nil { return nil, append(diagnostics, diag.FromErr(err)...) } diff --git a/internal/service/organization/resource_organization.go b/internal/service/organization/resource_organization.go index f35449caa0..2541b5352f 100644 --- a/internal/service/organization/resource_organization.go +++ b/internal/service/organization/resource_organization.go @@ -294,17 +294,21 @@ func ValidateAPIKeyIsOrgOwner(roles []string) error { // getAtlasV2Connection uses the created credentials for the organization if they exist. // Otherwise, it uses the provider credentials, e.g. if the resource was imported. func getAtlasV2Connection(ctx context.Context, d *schema.ResourceData, meta any) *admin.APIClient { + currentClient := meta.(*config.MongoDBClient) publicKey := d.Get("public_key").(string) privateKey := d.Get("private_key").(string) if publicKey == "" || privateKey == "" { - return meta.(*config.MongoDBClient).AtlasV2 + return currentClient.AtlasV2 } - cfg := config.Config{ - PublicKey: publicKey, - PrivateKey: privateKey, - BaseURL: meta.(*config.MongoDBClient).Config.BaseURL, - TerraformVersion: meta.(*config.MongoDBClient).Config.TerraformVersion, + c := &config.Credentials{ + PublicKey: publicKey, + PrivateKey: privateKey, + BaseURL: currentClient.Config.BaseURL, } - clients, _ := cfg.NewClient(ctx) - return clients.(*config.MongoDBClient).AtlasV2 + terraformVersion := currentClient.Config.TerraformVersion + newClient, err := config.NewClient(ctx, c, terraformVersion) + if err != nil { + return currentClient.AtlasV2 + } + return newClient.AtlasV2 } diff --git a/internal/service/organization/resource_organization_test.go b/internal/service/organization/resource_organization_test.go index 57ed0aece0..7c5d6f3c98 100644 --- a/internal/service/organization/resource_organization_test.go +++ b/internal/service/organization/resource_organization_test.go @@ -432,15 +432,15 @@ func getTestClientWithNewOrgCreds(rs *terraform.ResourceState) (*admin.APIClient return nil, fmt.Errorf("no private_key is set") } - cfg := config.Config{ + c := &config.Credentials{ PublicKey: rs.Primary.Attributes["public_key"], PrivateKey: rs.Primary.Attributes["private_key"], BaseURL: acc.MongoDBClient.Config.BaseURL, } ctx := context.Background() - clients, _ := cfg.NewClient(ctx) - return clients.(*config.MongoDBClient).AtlasV2, nil + client, _ := config.NewClient(ctx, c, "") + return client.AtlasV2, nil } func TestValidateAPIKeyIsOrgOwner(t *testing.T) { diff --git a/internal/testutil/acc/factory.go b/internal/testutil/acc/factory.go index 709c4bb89b..7bb311bbb4 100644 --- a/internal/testutil/acc/factory.go +++ b/internal/testutil/acc/factory.go @@ -42,13 +42,13 @@ func ConnV220241113() *admin20241113.APIClient { } func ConnV2UsingGov() *admin.APIClient { - cfg := config.Config{ + c := &config.Credentials{ PublicKey: os.Getenv("MONGODB_ATLAS_GOV_PUBLIC_KEY"), PrivateKey: os.Getenv("MONGODB_ATLAS_GOV_PRIVATE_KEY"), BaseURL: os.Getenv("MONGODB_ATLAS_GOV_BASE_URL"), } - client, _ := cfg.NewClient(context.Background()) - return client.(*config.MongoDBClient).AtlasV2 + client, _ := config.NewClient(context.Background(), c, "") + return client.AtlasV2 } func init() { @@ -63,7 +63,7 @@ func init() { return provider.MuxProviderFactory()(), nil }, } - cfg := config.Config{ + c := &config.Credentials{ PublicKey: os.Getenv("MONGODB_ATLAS_PUBLIC_KEY"), PrivateKey: os.Getenv("MONGODB_ATLAS_PRIVATE_KEY"), ClientID: os.Getenv("MONGODB_ATLAS_CLIENT_ID"), @@ -71,6 +71,6 @@ func init() { BaseURL: os.Getenv("MONGODB_ATLAS_BASE_URL"), RealmBaseURL: os.Getenv("MONGODB_REALM_BASE_URL"), } - client, _ := cfg.NewClient(context.Background()) - MongoDBClient = client.(*config.MongoDBClient) + client, _ := config.NewClient(context.Background(), c, "") + MongoDBClient = client } From af627fe44c25b62fe8e859d276aae4d7547be9ed Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 04:42:09 +0200 Subject: [PATCH 05/41] remove ctx param from NewClient --- internal/config/client.go | 6 +++--- internal/config/transport_test.go | 2 +- internal/provider/provider.go | 2 +- internal/provider/provider_sdk2.go | 2 +- internal/service/organization/resource_organization.go | 2 +- internal/service/organization/resource_organization_test.go | 6 +----- internal/testutil/acc/factory.go | 5 ++--- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/internal/config/client.go b/internal/config/client.go index 3b03092a6c..aa687954c6 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -148,9 +148,9 @@ type SecretData struct { PrivateKey string `json:"private_key"` } -func NewClient(ctx context.Context, c *Credentials, terraformVersion string) (*MongoDBClient, error) { +func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) { userAgent := userAgent(terraformVersion) - client, err := getHTTPClient(ctx, c) + client, err := getHTTPClient(c) if err != nil { return nil, err } @@ -199,7 +199,7 @@ func NewClient(ctx context.Context, c *Credentials, terraformVersion string) (*M return clients, nil } -func getHTTPClient(ctx context.Context, c *Credentials) (*http.Client, error) { +func getHTTPClient(c *Credentials) (*http.Client, error) { transport := networkLoggingBaseTransport() switch c.AuthMethod() { case AccessToken: diff --git a/internal/config/transport_test.go b/internal/config/transport_test.go index 5a8387d76f..2c54b5d77f 100644 --- a/internal/config/transport_test.go +++ b/internal/config/transport_test.go @@ -166,7 +166,7 @@ func TestAccNetworkLogging(t *testing.T) { ClientSecret: os.Getenv("MONGODB_ATLAS_CLIENT_SECRET"), BaseURL: os.Getenv("MONGODB_ATLAS_BASE_URL"), } - client, err := config.NewClient(t.Context(), c, "") + client, err := config.NewClient(c, "") require.NoError(t, err) // Make a simple API call that should trigger our enhanced logging. diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 735c4a6b01..1e46c5ae91 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -324,7 +324,7 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config BaseURL: cfg.BaseURL, RealmBaseURL: cfg.RealmBaseURL, } - client, err := config.NewClient(ctx, c, cfg.TerraformVersion) + client, err := config.NewClient(c, cfg.TerraformVersion) if err != nil { resp.Diagnostics.AddError( diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 80eee1c199..fd39b7355a 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -339,7 +339,7 @@ func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *s BaseURL: cfg.BaseURL, RealmBaseURL: cfg.RealmBaseURL, } - client, err := config.NewClient(ctx, c, cfg.TerraformVersion) + client, err := config.NewClient(c, cfg.TerraformVersion) if err != nil { return nil, append(diagnostics, diag.FromErr(err)...) } diff --git a/internal/service/organization/resource_organization.go b/internal/service/organization/resource_organization.go index 2541b5352f..fef71c6e80 100644 --- a/internal/service/organization/resource_organization.go +++ b/internal/service/organization/resource_organization.go @@ -306,7 +306,7 @@ func getAtlasV2Connection(ctx context.Context, d *schema.ResourceData, meta any) BaseURL: currentClient.Config.BaseURL, } terraformVersion := currentClient.Config.TerraformVersion - newClient, err := config.NewClient(ctx, c, terraformVersion) + newClient, err := config.NewClient(c, terraformVersion) if err != nil { return currentClient.AtlasV2 } diff --git a/internal/service/organization/resource_organization_test.go b/internal/service/organization/resource_organization_test.go index 7c5d6f3c98..acb6f29e13 100644 --- a/internal/service/organization/resource_organization_test.go +++ b/internal/service/organization/resource_organization_test.go @@ -427,19 +427,15 @@ func getTestClientWithNewOrgCreds(rs *terraform.ResourceState) (*admin.APIClient if rs.Primary.Attributes["public_key"] == "" { return nil, fmt.Errorf("no public_key is set") } - if rs.Primary.Attributes["private_key"] == "" { return nil, fmt.Errorf("no private_key is set") } - c := &config.Credentials{ PublicKey: rs.Primary.Attributes["public_key"], PrivateKey: rs.Primary.Attributes["private_key"], BaseURL: acc.MongoDBClient.Config.BaseURL, } - - ctx := context.Background() - client, _ := config.NewClient(ctx, c, "") + client, _ := config.NewClient(c, "") return client.AtlasV2, nil } diff --git a/internal/testutil/acc/factory.go b/internal/testutil/acc/factory.go index 7bb311bbb4..bf347256cb 100644 --- a/internal/testutil/acc/factory.go +++ b/internal/testutil/acc/factory.go @@ -1,7 +1,6 @@ package acc import ( - "context" "os" matlas "go.mongodb.org/atlas/mongodbatlas" @@ -47,7 +46,7 @@ func ConnV2UsingGov() *admin.APIClient { PrivateKey: os.Getenv("MONGODB_ATLAS_GOV_PRIVATE_KEY"), BaseURL: os.Getenv("MONGODB_ATLAS_GOV_BASE_URL"), } - client, _ := config.NewClient(context.Background(), c, "") + client, _ := config.NewClient(c, "") return client.AtlasV2 } @@ -71,6 +70,6 @@ func init() { BaseURL: os.Getenv("MONGODB_ATLAS_BASE_URL"), RealmBaseURL: os.Getenv("MONGODB_REALM_BASE_URL"), } - client, _ := config.NewClient(context.Background(), c, "") + client, _ := config.NewClient(c, "") MongoDBClient = client } From 1800c31d78be95c330ebce7133c09f12e7fd358a Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 05:39:15 +0200 Subject: [PATCH 06/41] keep only role_arn in assume_role --- internal/config/client.go | 14 +-- internal/provider/aws_credentials.go | 2 +- internal/provider/provider.go | 115 +----------------------- internal/provider/provider_sdk2.go | 127 ++------------------------- 4 files changed, 11 insertions(+), 247 deletions(-) diff --git a/internal/config/client.go b/internal/config/client.go index aa687954c6..bbdcacb8e3 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -113,7 +113,7 @@ type MongoDBClient struct { // Config contains the configurations needed to use SDKs type Config struct { - AssumeRole *AssumeRole + AssumeRoleARN string PublicKey string PrivateKey string BaseURL string @@ -131,18 +131,6 @@ func (c *Config) GetClientID() string { return c.ClientID } func (c *Config) GetClientSecret() string { return c.ClientSecret } func (c *Config) GetAccessToken() string { return c.AccessToken } -type AssumeRole struct { - Tags map[string]string - RoleARN string - ExternalID string - Policy string - SessionName string - SourceIdentity string - PolicyARNs []string - TransitiveTagKeys []string - Duration time.Duration -} - type SecretData struct { PublicKey string `json:"public_key"` PrivateKey string `json:"private_key"` diff --git a/internal/provider/aws_credentials.go b/internal/provider/aws_credentials.go index 08bf8fbb9c..335589d681 100644 --- a/internal/provider/aws_credentials.go +++ b/internal/provider/aws_credentials.go @@ -44,7 +44,7 @@ func configureCredentialsSTS(cfg *config.Config, secret, region, awsAccessKeyID, EndpointResolver: endpoints.ResolverFunc(stsCustResolverFn), })) - creds := stscreds.NewCredentials(sess, cfg.AssumeRole.RoleARN) + creds := stscreds.NewCredentials(sess, cfg.AssumeRoleARN) _, err := sess.Config.Credentials.Get() if err != nil { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1e46c5ae91..e6d658f91c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -4,11 +4,8 @@ import ( "context" "log" "os" - "regexp" - "time" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -24,7 +21,6 @@ import ( "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" - "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/validate" "github.com/mongodb/terraform-provider-mongodbatlas/internal/config" "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/advancedcluster" "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/alertconfiguration" @@ -92,27 +88,11 @@ type tfMongodbAtlasProviderModel struct { } type tfAssumeRoleModel struct { - PolicyARNs types.Set `tfsdk:"policy_arns"` - TransitiveTagKeys types.Set `tfsdk:"transitive_tag_keys"` - Tags types.Map `tfsdk:"tags"` - Duration types.String `tfsdk:"duration"` - ExternalID types.String `tfsdk:"external_id"` - Policy types.String `tfsdk:"policy"` - RoleARN types.String `tfsdk:"role_arn"` - SessionName types.String `tfsdk:"session_name"` - SourceIdentity types.String `tfsdk:"source_identity"` + RoleARN types.String `tfsdk:"role_arn"` } var AssumeRoleType = types.ObjectType{AttrTypes: map[string]attr.Type{ - "policy_arns": types.SetType{ElemType: types.StringType}, - "transitive_tag_keys": types.SetType{ElemType: types.StringType}, - "tags": types.MapType{ElemType: types.StringType}, - "duration": types.StringType, - "external_id": types.StringType, - "policy": types.StringType, - "role_arn": types.StringType, - "session_name": types.StringType, - "source_identity": types.StringType, + "role_arn": types.StringType, }} func (p *MongodbtlasProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { @@ -211,63 +191,10 @@ var fwAssumeRoleSchema = schema.ListNestedBlock{ Validators: []validator.List{listvalidator.SizeAtMost(1)}, NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "duration": schema.StringAttribute{ - Optional: true, - Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", - Validators: []validator.String{ - validate.ValidDurationBetween(15, 12*60), - }, - }, - "external_id": schema.StringAttribute{ - Optional: true, - Description: "A unique identifier that might be required when you assume a role in another account.", - Validators: []validator.String{ - stringvalidator.LengthBetween(2, 1224), - stringvalidator.RegexMatches(regexp.MustCompile(`[\w+=,.@:/\-]*`), ""), - }, - }, - "policy": schema.StringAttribute{ - Optional: true, - Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", - Validators: []validator.String{ - validate.StringIsJSON(), - }, - }, - "policy_arns": schema.SetAttribute{ - ElementType: types.StringType, - Optional: true, - Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", - }, "role_arn": schema.StringAttribute{ Optional: true, Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", }, - "session_name": schema.StringAttribute{ - Optional: true, - Description: "An identifier for the assumed role session.", - Validators: []validator.String{ - stringvalidator.LengthBetween(2, 64), - stringvalidator.RegexMatches(regexp.MustCompile(`[\w+=,.@\-]*`), ""), - }, - }, - "source_identity": schema.StringAttribute{ - Optional: true, - Description: "Source identity specified by the principal assuming the role.", - Validators: []validator.String{ - stringvalidator.LengthBetween(2, 64), - stringvalidator.RegexMatches(regexp.MustCompile(`[\w+=,.@\-]*`), ""), - }, - }, - "tags": schema.MapAttribute{ - ElementType: types.StringType, - Optional: true, - Description: "Assume role session tags.", - }, - "transitive_tag_keys": schema.SetAttribute{ - ElementType: types.StringType, - Optional: true, - Description: "Assume role session tag keys to pass to any subsequent sessions.", - }, }, }, } @@ -300,7 +227,7 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config data.AssumeRole.ElementsAs(ctx, &assumeRoles, true) awsRoleDefined := len(assumeRoles) > 0 if awsRoleDefined { - cfg.AssumeRole = parseTfModel(ctx, &assumeRoles[0]) + cfg.AssumeRoleARN = assumeRoles[0].RoleARN.ValueString() secret := data.SecretName.ValueString() region := conversion.MongoDBRegionToAWSRegion(data.Region.ValueString()) awsAccessKeyID := data.AwsAccessKeyID.ValueString() @@ -338,37 +265,6 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config resp.ResourceData = client } -// parseTfModel extracts the values from tfAssumeRoleModel creating a new instance of our internal model AssumeRole used in Config -func parseTfModel(ctx context.Context, tfAssumeRoleModel *tfAssumeRoleModel) *config.AssumeRole { - assumeRole := config.AssumeRole{} - - if !tfAssumeRoleModel.Duration.IsNull() { - duration, _ := time.ParseDuration(tfAssumeRoleModel.Duration.ValueString()) - assumeRole.Duration = duration - } - - assumeRole.ExternalID = tfAssumeRoleModel.ExternalID.ValueString() - assumeRole.Policy = tfAssumeRoleModel.Policy.ValueString() - - if !tfAssumeRoleModel.PolicyARNs.IsNull() { - var policiesARNs []string - tfAssumeRoleModel.PolicyARNs.ElementsAs(ctx, &policiesARNs, true) - assumeRole.PolicyARNs = policiesARNs - } - - assumeRole.RoleARN = tfAssumeRoleModel.RoleARN.ValueString() - assumeRole.SessionName = tfAssumeRoleModel.SessionName.ValueString() - assumeRole.SourceIdentity = tfAssumeRoleModel.SourceIdentity.ValueString() - - if !tfAssumeRoleModel.TransitiveTagKeys.IsNull() { - var transitiveTagKeys []string - tfAssumeRoleModel.TransitiveTagKeys.ElementsAs(ctx, &transitiveTagKeys, true) - assumeRole.TransitiveTagKeys = transitiveTagKeys - } - - return &assumeRole -} - func setDefaultValuesWithValidations(ctx context.Context, data *tfMongodbAtlasProviderModel, resp *provider.ConfigureResponse) tfMongodbAtlasProviderModel { if mongodbgovCloud := data.IsMongodbGovCloud.ValueBool(); mongodbgovCloud { if !isGovBaseURLConfiguredForProvider(data) { @@ -393,10 +289,7 @@ func setDefaultValuesWithValidations(ctx context.Context, data *tfMongodbAtlasPr var diags diag.Diagnostics data.AssumeRole, diags = types.ListValueFrom(ctx, AssumeRoleType, []tfAssumeRoleModel{ { - Tags: types.MapNull(types.StringType), - PolicyARNs: types.SetNull(types.StringType), - TransitiveTagKeys: types.SetNull(types.StringType), - RoleARN: types.StringValue(assumeRoleArn), + RoleARN: types.StringValue(assumeRoleArn), }, }) if diags.HasError() { diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index fd39b7355a..022b6544d0 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -2,13 +2,9 @@ package provider import ( "context" - "fmt" - "regexp" - "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" "github.com/mongodb/terraform-provider-mongodbatlas/internal/config" @@ -316,7 +312,7 @@ func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *s assumeRoleValue, ok := d.GetOk("assume_role") awsRoleDefined := ok && len(assumeRoleValue.([]any)) > 0 && assumeRoleValue.([]any)[0] != nil if awsRoleDefined { - cfg.AssumeRole = expandAssumeRole(assumeRoleValue.([]any)[0].(map[string]any)) + cfg.AssumeRoleARN = expandAssumeRole(assumeRoleValue.([]any)[0].(map[string]any)) secret := d.Get("secret_name").(string) region := conversion.MongoDBRegionToAWSRegion(d.Get("region").(string)) awsAccessKeyID := d.Get("aws_access_key_id").(string) @@ -498,137 +494,24 @@ func assumeRoleSchema() *schema.Schema { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "duration": { - Type: schema.TypeString, - Optional: true, - Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", - ValidateFunc: validAssumeRoleDuration, - }, - "external_id": { - Type: schema.TypeString, - Optional: true, - Description: "A unique identifier that might be required when you assume a role in another account.", - ValidateFunc: validation.All( - validation.StringLenBetween(2, 1224), - validation.StringMatch(regexp.MustCompile(`[\w+=,.@:/\-]*`), ""), - ), - }, - "policy": { - Type: schema.TypeString, - Optional: true, - Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", - ValidateFunc: validation.StringIsJSON, - }, - "policy_arns": { - Type: schema.TypeSet, - Optional: true, - Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, "role_arn": { Type: schema.TypeString, Optional: true, Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", }, - "session_name": { - Type: schema.TypeString, - Optional: true, - Description: "An identifier for the assumed role session.", - ValidateFunc: validAssumeRoleSessionName, - }, - "source_identity": { - Type: schema.TypeString, - Optional: true, - Description: "Source identity specified by the principal assuming the role.", - ValidateFunc: validAssumeRoleSourceIdentity, - }, - "tags": { - Type: schema.TypeMap, - Optional: true, - Description: "Assume role session tags.", - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "transitive_tag_keys": { - Type: schema.TypeSet, - Optional: true, - Description: "Assume role session tag keys to pass to any subsequent sessions.", - Elem: &schema.Schema{Type: schema.TypeString}, - }, }, }, } } -var validAssumeRoleSessionName = validation.All( - validation.StringLenBetween(2, 64), - validation.StringMatch(regexp.MustCompile(`[\w+=,.@\-]*`), ""), -) - -var validAssumeRoleSourceIdentity = validation.All( - validation.StringLenBetween(2, 64), - validation.StringMatch(regexp.MustCompile(`[\w+=,.@\-]*`), ""), -) - -// validAssumeRoleDuration validates a string can be parsed as a valid time.Duration -// and is within a minimum of 15 minutes and maximum of 12 hours -func validAssumeRoleDuration(v any, k string) (ws []string, errorResults []error) { - duration, err := time.ParseDuration(v.(string)) - - if err != nil { - errorResults = append(errorResults, fmt.Errorf("%q cannot be parsed as a duration: %w", k, err)) - return - } - - if duration.Minutes() < 15 || duration.Hours() > 12 { - errorResults = append(errorResults, fmt.Errorf("duration %q must be between 15 minutes (15m) and 12 hours (12h), inclusive", k)) - } - - return -} - -func expandAssumeRole(tfMap map[string]any) *config.AssumeRole { +func expandAssumeRole(tfMap map[string]any) string { if tfMap == nil { - return nil - } - - assumeRole := config.AssumeRole{} - - if v, ok := tfMap["duration"].(string); ok && v != "" { - duration, _ := time.ParseDuration(v) - assumeRole.Duration = duration - } - - if v, ok := tfMap["external_id"].(string); ok && v != "" { - assumeRole.ExternalID = v - } - - if v, ok := tfMap["policy"].(string); ok && v != "" { - assumeRole.Policy = v + return "" } - - if v, ok := tfMap["policy_arns"].(*schema.Set); ok && v.Len() > 0 { - assumeRole.PolicyARNs = conversion.ExpandStringList(v.List()) - } - if v, ok := tfMap["role_arn"].(string); ok && v != "" { - assumeRole.RoleARN = v - } - - if v, ok := tfMap["session_name"].(string); ok && v != "" { - assumeRole.SessionName = v + return v } - - if v, ok := tfMap["source_identity"].(string); ok && v != "" { - assumeRole.SourceIdentity = v - } - - if v, ok := tfMap["transitive_tag_keys"].(*schema.Set); ok && v.Len() > 0 { - assumeRole.TransitiveTagKeys = conversion.ExpandStringList(v.List()) - } - - return &assumeRole + return "" } func isGovBaseURLConfiguredForSDK2Provider(d *schema.ResourceData) bool { From 8f796cc3894c0115465ffd3e28a2a66601a39ad1 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 06:05:00 +0200 Subject: [PATCH 07/41] fix TestAccServiceAccount_basic --- internal/config/service_account.go | 2 +- internal/testutil/acc/factory.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/config/service_account.go b/internal/config/service_account.go index 51217d10c2..84b41cc64e 100644 --- a/internal/config/service_account.go +++ b/internal/config/service_account.go @@ -28,6 +28,7 @@ func getTokenSource(clientID, clientSecret, baseURL string, tokenRenewalBase htt saInfo.mu.Lock() defer saInfo.mu.Unlock() + baseURL = strings.TrimRight(baseURL, "/") if saInfo.tokenSource != nil { // Token source in cache. if saInfo.clientID != clientID || saInfo.clientSecret != clientSecret || saInfo.baseURL != baseURL { return nil, fmt.Errorf("service account credentials changed") @@ -37,7 +38,6 @@ func getTokenSource(clientID, clientSecret, baseURL string, tokenRenewalBase htt conf := clientcredentials.NewConfig(clientID, clientSecret) if baseURL != "" { - baseURL = strings.TrimRight(baseURL, "/") conf.TokenURL = baseURL + clientcredentials.TokenAPIPath conf.RevokeURL = baseURL + clientcredentials.RevokeAPIPath } diff --git a/internal/testutil/acc/factory.go b/internal/testutil/acc/factory.go index bf347256cb..183027108d 100644 --- a/internal/testutil/acc/factory.go +++ b/internal/testutil/acc/factory.go @@ -70,6 +70,5 @@ func init() { BaseURL: os.Getenv("MONGODB_ATLAS_BASE_URL"), RealmBaseURL: os.Getenv("MONGODB_REALM_BASE_URL"), } - client, _ := config.NewClient(c, "") - MongoDBClient = client + MongoDBClient, _ = config.NewClient(c, "") } From ccfca8aa2476b865d005ca5872bd2eb50fb31853 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:15:33 +0200 Subject: [PATCH 08/41] TEMPORARY delete credentials from unit tests init --- internal/testutil/acc/factory.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/internal/testutil/acc/factory.go b/internal/testutil/acc/factory.go index 183027108d..e654ec914c 100644 --- a/internal/testutil/acc/factory.go +++ b/internal/testutil/acc/factory.go @@ -51,12 +51,6 @@ func ConnV2UsingGov() *admin.APIClient { } func init() { - if InUnitTest() { // Dummy credentials for unit tests - os.Setenv("MONGODB_ATLAS_PUBLIC_KEY", "dummy") - os.Setenv("MONGODB_ATLAS_PRIVATE_KEY", "dummy") - os.Unsetenv("MONGODB_ATLAS_CLIENT_ID") - os.Unsetenv("MONGODB_ATLAS_CLIENT_SECRET") - } TestAccProviderV6Factories = map[string]func() (tfprotov6.ProviderServer, error){ ProviderNameMongoDBAtlas: func() (tfprotov6.ProviderServer, error) { return provider.MuxProviderFactory()(), nil From 9d778df60e7ac91398641c467224dae38446e917 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:29:39 +0200 Subject: [PATCH 09/41] dummy credentials in mocked unit tests --- internal/testutil/unit/http_mocker.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/testutil/unit/http_mocker.go b/internal/testutil/unit/http_mocker.go index 44ca54d5a7..f085bd38a0 100644 --- a/internal/testutil/unit/http_mocker.go +++ b/internal/testutil/unit/http_mocker.go @@ -55,6 +55,12 @@ func IsDataUpdate() bool { func CaptureOrMockTestCaseAndRun(t *testing.T, config MockHTTPDataConfig, testCase *resource.TestCase) { //nolint: gocritic // Want each test run to have its own config (hugeParam: config is heavy (112 bytes); consider passing it by pointer) t.Helper() + if acc.InUnitTest() { // Dummy credentials for mocked unit tests + t.Setenv("MONGODB_ATLAS_PUBLIC_KEY", "dummy") + t.Setenv("MONGODB_ATLAS_PRIVATE_KEY", "dummy") + t.Setenv("MONGODB_ATLAS_CLIENT_ID", "") + t.Setenv("MONGODB_ATLAS_CLIENT_SECRET", "") + } var err error noneSet := !IsCapture() && !IsReplay() bothSet := IsCapture() && IsReplay() From 2420ecec3bd096aaa0a85adea3bcdf085f9d4e7f Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:42:36 +0200 Subject: [PATCH 10/41] TestPlanChecksClusterTwoRepSpecsWithAutoScalingAndSpecs only in unit tests --- internal/service/advancedcluster/plan_modifier_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/service/advancedcluster/plan_modifier_test.go b/internal/service/advancedcluster/plan_modifier_test.go index c427dcda9d..e3a592b0ab 100644 --- a/internal/service/advancedcluster/plan_modifier_test.go +++ b/internal/service/advancedcluster/plan_modifier_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc" "github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/unit" ) @@ -34,7 +35,8 @@ func specInstanceSizeNodeCount(instanceSize string, nodeCount int) knownvalue.Ch }) } -func TestPlanChecksClusterTwoRepSpecsWithAutoScalingAndSpecs(t *testing.T) { +func TestAccPlanChecksClusterTwoRepSpecsWithAutoScalingAndSpecs(t *testing.T) { + acc.SkipInUnitTest(t) var ( baseConfig = unit.NewMockPlanChecksConfig(t, &mockConfig, unit.ImportNameClusterTwoRepSpecsWithAutoScalingAndSpecs) resourceName = baseConfig.ResourceName From 0aa6126c0153df150e0e0f8179f6ad455c0e5036 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:07:14 +0200 Subject: [PATCH 11/41] Revert "dummy credentials in mocked unit tests" This reverts commit 9d778df60e7ac91398641c467224dae38446e917. --- internal/testutil/unit/http_mocker.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/internal/testutil/unit/http_mocker.go b/internal/testutil/unit/http_mocker.go index f085bd38a0..44ca54d5a7 100644 --- a/internal/testutil/unit/http_mocker.go +++ b/internal/testutil/unit/http_mocker.go @@ -55,12 +55,6 @@ func IsDataUpdate() bool { func CaptureOrMockTestCaseAndRun(t *testing.T, config MockHTTPDataConfig, testCase *resource.TestCase) { //nolint: gocritic // Want each test run to have its own config (hugeParam: config is heavy (112 bytes); consider passing it by pointer) t.Helper() - if acc.InUnitTest() { // Dummy credentials for mocked unit tests - t.Setenv("MONGODB_ATLAS_PUBLIC_KEY", "dummy") - t.Setenv("MONGODB_ATLAS_PRIVATE_KEY", "dummy") - t.Setenv("MONGODB_ATLAS_CLIENT_ID", "") - t.Setenv("MONGODB_ATLAS_CLIENT_SECRET", "") - } var err error noneSet := !IsCapture() && !IsReplay() bothSet := IsCapture() && IsReplay() From 77707859e2ffcbbe1f6e5fc7079b49506dded04b Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:29:10 +0200 Subject: [PATCH 12/41] rename token env var to MONGODB_ATLAS_ACCESS_TOKEN --- internal/provider/provider.go | 4 ++-- internal/provider/provider_sdk2.go | 4 ++-- internal/testutil/acc/pre_check.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e6d658f91c..e80025ccc8 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -380,8 +380,8 @@ func setDefaultValuesWithValidations(ctx context.Context, data *tfMongodbAtlasPr if data.AccessToken.ValueString() == "" { data.AccessToken = types.StringValue(MultiEnvDefaultFunc([]string{ - "MONGODB_ATLAS_OAUTH_TOKEN", - "TF_VAR_OAUTH_TOKEN", + "MONGODB_ATLAS_ACCESS_TOKEN", + "TF_VAR_ACCESS_TOKEN", }, "").(string)) } diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 022b6544d0..76bfe7fbb3 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -458,8 +458,8 @@ func setDefaultsAndValidations(d *schema.ResourceData) diag.Diagnostics { } if err := setValueFromConfigOrEnv(d, "access_token", []string{ - "MONGODB_ATLAS_OAUTH_TOKEN", - "TF_VAR_OAUTH_TOKEN", + "MONGODB_ATLAS_ACCESS_TOKEN", + "TF_VAR_ACCESS_TOKEN", }); err != nil { return append(diagnostics, diag.FromErr(err)...) } diff --git a/internal/testutil/acc/pre_check.go b/internal/testutil/acc/pre_check.go index 8d98c621b5..0bc99d38b7 100644 --- a/internal/testutil/acc/pre_check.go +++ b/internal/testutil/acc/pre_check.go @@ -347,7 +347,7 @@ func PreCheckAwsMsk(tb testing.TB) { func PreCheckAccessToken(tb testing.TB) { tb.Helper() - if os.Getenv("MONGODB_ATLAS_OAUTH_TOKEN") == "" { - tb.Fatal("`MONGODB_ATLAS_OAUTH_TOKEN` must be set for Atlas Access Token acceptance testing") + if os.Getenv("MONGODB_ATLAS_ACCESS_TOKEN") == "" { + tb.Fatal("`MONGODB_ATLAS_ACCESS_TOKEN` must be set for Atlas Access Token acceptance testing") } } From b61df5bd92951b9c05ec2b2edb4dc83bef1ad9ad Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:54:39 +0200 Subject: [PATCH 13/41] Reapply "dummy credentials in mocked unit tests" This reverts commit 0aa6126c0153df150e0e0f8179f6ad455c0e5036. --- internal/testutil/unit/http_mocker.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/testutil/unit/http_mocker.go b/internal/testutil/unit/http_mocker.go index 44ca54d5a7..f085bd38a0 100644 --- a/internal/testutil/unit/http_mocker.go +++ b/internal/testutil/unit/http_mocker.go @@ -55,6 +55,12 @@ func IsDataUpdate() bool { func CaptureOrMockTestCaseAndRun(t *testing.T, config MockHTTPDataConfig, testCase *resource.TestCase) { //nolint: gocritic // Want each test run to have its own config (hugeParam: config is heavy (112 bytes); consider passing it by pointer) t.Helper() + if acc.InUnitTest() { // Dummy credentials for mocked unit tests + t.Setenv("MONGODB_ATLAS_PUBLIC_KEY", "dummy") + t.Setenv("MONGODB_ATLAS_PRIVATE_KEY", "dummy") + t.Setenv("MONGODB_ATLAS_CLIENT_ID", "") + t.Setenv("MONGODB_ATLAS_CLIENT_SECRET", "") + } var err error noneSet := !IsCapture() && !IsReplay() bothSet := IsCapture() && IsReplay() From e5c73bf51055b04115137f5ffa26782e88373d2b Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:54:50 +0200 Subject: [PATCH 14/41] Revert "TestPlanChecksClusterTwoRepSpecsWithAutoScalingAndSpecs only in unit tests" This reverts commit 2420ecec3bd096aaa0a85adea3bcdf085f9d4e7f. --- internal/service/advancedcluster/plan_modifier_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/service/advancedcluster/plan_modifier_test.go b/internal/service/advancedcluster/plan_modifier_test.go index e3a592b0ab..c427dcda9d 100644 --- a/internal/service/advancedcluster/plan_modifier_test.go +++ b/internal/service/advancedcluster/plan_modifier_test.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" - "github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc" "github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/unit" ) @@ -35,8 +34,7 @@ func specInstanceSizeNodeCount(instanceSize string, nodeCount int) knownvalue.Ch }) } -func TestAccPlanChecksClusterTwoRepSpecsWithAutoScalingAndSpecs(t *testing.T) { - acc.SkipInUnitTest(t) +func TestPlanChecksClusterTwoRepSpecsWithAutoScalingAndSpecs(t *testing.T) { var ( baseConfig = unit.NewMockPlanChecksConfig(t, &mockConfig, unit.ImportNameClusterTwoRepSpecsWithAutoScalingAndSpecs) resourceName = baseConfig.ResourceName From e9c477edb7b7be7c314ca1c73f3832a2e2d7be33 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:00:11 +0200 Subject: [PATCH 15/41] revert changes to acc test runner config --- .github/workflows/acceptance-tests-runner.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/acceptance-tests-runner.yml b/.github/workflows/acceptance-tests-runner.yml index 3fd5fcaa57..9f38d7b051 100644 --- a/.github/workflows/acceptance-tests-runner.yml +++ b/.github/workflows/acceptance-tests-runner.yml @@ -196,10 +196,9 @@ env: TF_ACC: 1 TF_LOG: ${{ vars.LOG_LEVEL }} ACCTEST_TIMEOUT: ${{ vars.ACCTEST_TIMEOUT }} - # If the name (regex) of the test is set, only that test is run. - # Don't run migration tests if using Service Accounts because previous provider versions don't support SA yet. - # Only Migration tests are run when a specific previous provider version is set. - ACCTEST_REGEX_RUN: ${{ inputs.test_name || inputs.use_sa && '^TestAcc' || inputs.provider_version == '' && '^Test(Acc|Mig)' || '^TestMig' }} + # Only Migration tests are run when a specific previous provider version is set + # If the name (regex) of the test is set, only that test is run + ACCTEST_REGEX_RUN: ${{ inputs.test_name || inputs.provider_version == '' && '^Test(Acc|Mig)' || '^TestMig' }} MONGODB_ATLAS_BASE_URL: ${{ inputs.mongodb_atlas_base_url }} MONGODB_REALM_BASE_URL: ${{ inputs.mongodb_realm_base_url }} MONGODB_ATLAS_ORG_ID: ${{ inputs.mongodb_atlas_org_id }} @@ -549,7 +548,6 @@ jobs: MONGODB_ATLAS_CLIENT_ID: ${{ secrets.mongodb_atlas_client_id }} MONGODB_ATLAS_CLIENT_SECRET: ${{ secrets.mongodb_atlas_client_secret }} MONGODB_ATLAS_LAST_VERSION: ${{ needs.get-provider-version.outputs.provider_version }} - ACCTEST_REGEX_RUN: '^TestAcc' # Don't run migration tests because previous provider versions don't support SA. ACCTEST_PACKAGES: | ./internal/service/alertconfiguration ./internal/service/databaseuser From eeb1d5f0c143607bd90e232b6e38b45b3ae6f9d2 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:01:34 +0200 Subject: [PATCH 16/41] revert init changes --- internal/testutil/acc/factory.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/testutil/acc/factory.go b/internal/testutil/acc/factory.go index e654ec914c..183027108d 100644 --- a/internal/testutil/acc/factory.go +++ b/internal/testutil/acc/factory.go @@ -51,6 +51,12 @@ func ConnV2UsingGov() *admin.APIClient { } func init() { + if InUnitTest() { // Dummy credentials for unit tests + os.Setenv("MONGODB_ATLAS_PUBLIC_KEY", "dummy") + os.Setenv("MONGODB_ATLAS_PRIVATE_KEY", "dummy") + os.Unsetenv("MONGODB_ATLAS_CLIENT_ID") + os.Unsetenv("MONGODB_ATLAS_CLIENT_SECRET") + } TestAccProviderV6Factories = map[string]func() (tfprotov6.ProviderServer, error){ ProviderNameMongoDBAtlas: func() (tfprotov6.ProviderServer, error) { return provider.MuxProviderFactory()(), nil From d06071242796a941abdb23fef832c17712c4e99b Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 13:35:22 +0200 Subject: [PATCH 17/41] revert dummy credentials in mocked tests --- internal/testutil/unit/http_mocker.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/internal/testutil/unit/http_mocker.go b/internal/testutil/unit/http_mocker.go index f085bd38a0..44ca54d5a7 100644 --- a/internal/testutil/unit/http_mocker.go +++ b/internal/testutil/unit/http_mocker.go @@ -55,12 +55,6 @@ func IsDataUpdate() bool { func CaptureOrMockTestCaseAndRun(t *testing.T, config MockHTTPDataConfig, testCase *resource.TestCase) { //nolint: gocritic // Want each test run to have its own config (hugeParam: config is heavy (112 bytes); consider passing it by pointer) t.Helper() - if acc.InUnitTest() { // Dummy credentials for mocked unit tests - t.Setenv("MONGODB_ATLAS_PUBLIC_KEY", "dummy") - t.Setenv("MONGODB_ATLAS_PRIVATE_KEY", "dummy") - t.Setenv("MONGODB_ATLAS_CLIENT_ID", "") - t.Setenv("MONGODB_ATLAS_CLIENT_SECRET", "") - } var err error noneSet := !IsCapture() && !IsReplay() bothSet := IsCapture() && IsReplay() From 178b4b5bbd420c1b2d44c9580cffed16e235d424 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:43:57 +0200 Subject: [PATCH 18/41] initial AWS code --- internal/config/credentials.go | 1 + internal/provider/aws_credentials.go | 48 ++++++----------- internal/provider/provider.go | 79 +++++++++++++--------------- internal/provider/provider_sdk2.go | 71 ++++++++++++------------- 4 files changed, 89 insertions(+), 110 deletions(-) diff --git a/internal/config/credentials.go b/internal/config/credentials.go index 7e0eb6c055..5818562dc0 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -1,6 +1,7 @@ package config type Credentials struct { + Method string AccessToken string ClientID string ClientSecret string diff --git a/internal/provider/aws_credentials.go b/internal/provider/aws_credentials.go index 335589d681..def8af7771 100644 --- a/internal/provider/aws_credentials.go +++ b/internal/provider/aws_credentials.go @@ -25,7 +25,8 @@ const ( minSegmentsForSTSRegionalHost = 4 ) -func configureCredentialsSTS(cfg *config.Config, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint string) (config.Config, error) { +// TODO: req object +func configureCredentialsSTS(c *config.Credentials, assumeRoleARN, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint string) error { defaultResolver := endpoints.DefaultResolver() stsCustResolverFn := func(service, _ string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) { if service == sts.EndpointsID { @@ -37,51 +38,34 @@ func configureCredentialsSTS(cfg *config.Config, secret, region, awsAccessKeyID, } return defaultResolver.EndpointFor(service, region, optFns...) } - sess := session.Must(session.NewSession(&aws.Config{ Region: aws.String(region), Credentials: credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, awsSessionToken), EndpointResolver: endpoints.ResolverFunc(stsCustResolverFn), })) - - creds := stscreds.NewCredentials(sess, cfg.AssumeRoleARN) - - _, err := sess.Config.Credentials.Get() - if err != nil { - log.Printf("Session get credentials error: %s", err) - return *cfg, err + creds := stscreds.NewCredentials(sess, assumeRoleARN) + if _, err := sess.Config.Credentials.Get(); err != nil { + return err } - _, err = creds.Get() - if err != nil { - log.Printf("STS get credentials error: %s", err) - return *cfg, err + if _, err := creds.Get(); err != nil { + return err } secretString, err := secretsManagerGetSecretValue(sess, &aws.Config{Credentials: creds, Region: aws.String(region)}, secret) if err != nil { - log.Printf("Get Secrets error: %s", err) - return *cfg, err + return err } - var secretData SecretData err = json.Unmarshal([]byte(secretString), &secretData) if err != nil { - return *cfg, err - } - - switch config.ResolveAuthMethod(&secretData) { - case config.AccessToken: - cfg.AccessToken = secretData.AccessToken - case config.Digest: - cfg.PublicKey = secretData.PublicKey - cfg.PrivateKey = secretData.PrivateKey - case config.ServiceAccount: - cfg.ClientID = secretData.ClientID - cfg.ClientSecret = secretData.ClientSecret - case config.Unknown: - return *cfg, fmt.Errorf("secret missing value for supported credentials: PrivateKey/PublicKey, ClientID/ClientSecret or AccessToken") + return err } - - return *cfg, nil + // TODO: how to read URLs in AWS Secrets Manager? + c.AccessToken = secretData.AccessToken + c.ClientID = secretData.ClientID + c.ClientSecret = secretData.ClientSecret + c.PublicKey = secretData.PublicKey + c.PrivateKey = secretData.PrivateKey + return nil } func DeriveSTSRegionFromEndpoint(ep string) string { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e80025ccc8..a8d6a47582 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -2,6 +2,7 @@ package provider import ( "context" + "fmt" "log" "os" @@ -201,61 +202,31 @@ var fwAssumeRoleSchema = schema.ListNestedBlock{ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { var data tfMongodbAtlasProviderModel - resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } - data = setDefaultValuesWithValidations(ctx, &data, resp) if resp.Diagnostics.HasError() { return } - cfg := config.Config{ - PublicKey: data.PublicKey.ValueString(), - PrivateKey: data.PrivateKey.ValueString(), - BaseURL: data.BaseURL.ValueString(), - RealmBaseURL: data.RealmBaseURL.ValueString(), - TerraformVersion: req.TerraformVersion, - ClientID: data.ClientID.ValueString(), - ClientSecret: data.ClientSecret.ValueString(), - AccessToken: data.AccessToken.ValueString(), - } - - var assumeRoles []tfAssumeRoleModel - data.AssumeRole.ElementsAs(ctx, &assumeRoles, true) - awsRoleDefined := len(assumeRoles) > 0 - if awsRoleDefined { - cfg.AssumeRoleARN = assumeRoles[0].RoleARN.ValueString() - secret := data.SecretName.ValueString() - region := conversion.MongoDBRegionToAWSRegion(data.Region.ValueString()) - awsAccessKeyID := data.AwsAccessKeyID.ValueString() - awsSecretAccessKey := data.AwsSecretAccessKeyID.ValueString() - awsSessionToken := data.AwsSessionToken.ValueString() - endpoint := data.StsEndpoint.ValueString() - var err error - cfg, err = configureCredentialsSTS(&cfg, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint) - if err != nil { - resp.Diagnostics.AddError("failed to configure credentials STS", err.Error()) - return - } - } - - c := &config.Credentials{ - AccessToken: cfg.AccessToken, - ClientID: cfg.ClientID, - ClientSecret: cfg.ClientSecret, - PublicKey: cfg.PublicKey, - PrivateKey: cfg.PrivateKey, - BaseURL: cfg.BaseURL, - RealmBaseURL: cfg.RealmBaseURL, + awsCredentials, err := getTPFAWSCredentials(ctx, &data) + if err != nil { + // TODO: error message + resp.Diagnostics.AddError( + "failed to get AWS credentials", + err.Error(), + ) + return } - client, err := config.NewClient(c, cfg.TerraformVersion) + // TODO: chooose the credentials between AWS, SA or PAK + client, err := config.NewClient(awsCredentials, req.TerraformVersion) if err != nil { + // TODO: error message resp.Diagnostics.AddError( - "failed to initialize a new client", + "TODO FAIL AWS SECRETS MANAGER", err.Error(), ) return @@ -265,6 +236,30 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config resp.ResourceData = client } +func getTPFAWSCredentials(ctx context.Context, data *tfMongodbAtlasProviderModel) (*config.Credentials, error) { + c := &config.Credentials{ + Method: "AWS Secrets Manager", + } + var assumeRoles []tfAssumeRoleModel + data.AssumeRole.ElementsAs(ctx, &assumeRoles, true) + if len(assumeRoles) == 0 { + return c, nil + } + assumeRoleARN := assumeRoles[0].RoleARN.ValueString() + secret := data.SecretName.ValueString() + region := conversion.MongoDBRegionToAWSRegion(data.Region.ValueString()) + awsAccessKeyID := data.AwsAccessKeyID.ValueString() + awsSecretAccessKey := data.AwsSecretAccessKeyID.ValueString() + awsSessionToken := data.AwsSessionToken.ValueString() + endpoint := data.StsEndpoint.ValueString() + // TODO: read from env vars if not here, and req object to configure + err := configureCredentialsSTS(c, assumeRoleARN, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint) + if err != nil { + return nil, fmt.Errorf("failed to configure STS credentials: %w", err) + } + return c, nil +} + func setDefaultValuesWithValidations(ctx context.Context, data *tfMongodbAtlasProviderModel, resp *provider.ConfigureResponse) tfMongodbAtlasProviderModel { if mongodbgovCloud := data.IsMongodbGovCloud.ValueBool(); mongodbgovCloud { if !isGovBaseURLConfiguredForProvider(data) { diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index f639016eae..ed6a189166 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -2,6 +2,7 @@ package provider import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -298,51 +299,49 @@ func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *s return nil, diagnostics } - cfg := config.Config{ - PublicKey: d.Get("public_key").(string), - PrivateKey: d.Get("private_key").(string), - BaseURL: d.Get("base_url").(string), - RealmBaseURL: d.Get("realm_base_url").(string), - TerraformVersion: provider.TerraformVersion, - ClientID: d.Get("client_id").(string), - ClientSecret: d.Get("client_secret").(string), - AccessToken: d.Get("access_token").(string), - } - - assumeRoleValue, ok := d.GetOk("assume_role") - awsRoleDefined := ok && len(assumeRoleValue.([]any)) > 0 && assumeRoleValue.([]any)[0] != nil - if awsRoleDefined { - cfg.AssumeRoleARN = getAssumeRoleARN(assumeRoleValue.([]any)[0].(map[string]any)) - secret := d.Get("secret_name").(string) - region := conversion.MongoDBRegionToAWSRegion(d.Get("region").(string)) - awsAccessKeyID := d.Get("aws_access_key_id").(string) - awsSecretAccessKey := d.Get("aws_secret_access_key").(string) - awsSessionToken := d.Get("aws_session_token").(string) - endpoint := d.Get("sts_endpoint").(string) - var err error - cfg, err = configureCredentialsSTS(&cfg, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint) - if err != nil { - return nil, append(diagnostics, diag.FromErr(err)...) - } + awsCredentials, err := getSDKv2AWSCredentials(d) + if err != nil { + // TODO: error message + return nil, append(diagnostics, diag.FromErr(fmt.Errorf("failed to get AWS credentials: %w", err))...) } - c := &config.Credentials{ - AccessToken: cfg.AccessToken, - ClientID: cfg.ClientID, - ClientSecret: cfg.ClientSecret, - PublicKey: cfg.PublicKey, - PrivateKey: cfg.PrivateKey, - BaseURL: cfg.BaseURL, - RealmBaseURL: cfg.RealmBaseURL, - } - client, err := config.NewClient(c, cfg.TerraformVersion) + // TODO: chooose the credentials between AWS, SA or PAK + client, err := config.NewClient(awsCredentials, provider.TerraformVersion) if err != nil { + // TODO: error message return nil, append(diagnostics, diag.FromErr(err)...) } return client, diagnostics } } +func getSDKv2AWSCredentials(d *schema.ResourceData) (*config.Credentials, error) { + c := &config.Credentials{ + Method: "AWS Secrets Manager", + } + assumeRoleVal, ok := d.GetOk("assume_role") + if !ok { + return c, nil + } + assumeRoles := assumeRoleVal.([]any) + if len(assumeRoles) == 0 { + return c, nil + } + assumeRoleARN := getAssumeRoleARN(assumeRoles[0].(map[string]any)) + secret := d.Get("secret_name").(string) + region := conversion.MongoDBRegionToAWSRegion(d.Get("region").(string)) + awsAccessKeyID := d.Get("aws_access_key_id").(string) + awsSecretAccessKey := d.Get("aws_secret_access_key").(string) + awsSessionToken := d.Get("aws_session_token").(string) + endpoint := d.Get("sts_endpoint").(string) + // TODO: read from env vars if not here, and req object to configure + err := configureCredentialsSTS(c, assumeRoleARN, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint) + if err != nil { + return nil, fmt.Errorf("failed to configure STS credentials: %w", err) + } + return c, nil +} + func setDefaultsAndValidations(d *schema.ResourceData) diag.Diagnostics { diagnostics := []diag.Diagnostic{} From 1257c5dd302b439b825a40be6f75e2b763ff0b88 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sat, 4 Oct 2025 07:59:07 +0200 Subject: [PATCH 19/41] use env vars --- internal/config/credentials.go | 70 ++++++++++++++ internal/provider/provider.go | 147 +++-------------------------- internal/provider/provider_sdk2.go | 147 +++-------------------------- 3 files changed, 96 insertions(+), 268 deletions(-) diff --git a/internal/config/credentials.go b/internal/config/credentials.go index 5818562dc0..a7f3c57f68 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -1,5 +1,7 @@ package config +import "os" + type Credentials struct { Method string AccessToken string @@ -23,3 +25,71 @@ func (c *Credentials) AuthMethod() AuthMethod { } return Unknown } + +type Vars struct { + AccessToken string + ClientID string + ClientSecret string + PublicKey string + PrivateKey string + BaseURL string + RealmBaseURL string + AWSAssumeRoleARN string + AWSSecretName string + AWSRegion string + AWSAccessKeyID string + AWSSecretAccessKey string + AWSSessionToken string + AWSEndpoint string +} + +func NewEnvVars() *Vars { + return &Vars{ + AccessToken: getEnv("MONGODB_ATLAS_ACCESS_TOKEN", "TF_VAR_ACCESS_TOKEN"), + ClientID: getEnv("MONGODB_ATLAS_CLIENT_ID", "TF_VAR_CLIENT_ID"), + ClientSecret: getEnv("MONGODB_ATLAS_CLIENT_SECRET", "TF_VAR_CLIENT_SECRET"), + PublicKey: getEnv("MONGODB_ATLAS_PUBLIC_API_KEY", "MONGODB_ATLAS_PUBLIC_KEY", "MCLI_PUBLIC_API_KEY"), + PrivateKey: getEnv("MONGODB_ATLAS_PRIVATE_API_KEY", "MONGODB_ATLAS_PRIVATE_KEY", "MCLI_PRIVATE_API_KEY"), + BaseURL: getEnv("MONGODB_ATLAS_BASE_URL", "MCLI_OPS_MANAGER_URL"), + RealmBaseURL: getEnv("MONGODB_REALM_BASE_URL"), + AWSAssumeRoleARN: getEnv("ASSUME_ROLE_ARN", "TF_VAR_ASSUME_ROLE_ARN"), + AWSSecretName: getEnv("SECRET_NAME", "TF_VAR_SECRET_NAME"), + AWSRegion: getEnv("AWS_REGION", "TF_VAR_AWS_REGION"), + AWSAccessKeyID: getEnv("AWS_ACCESS_KEY_ID", "TF_VAR_AWS_ACCESS_KEY_ID"), + AWSSecretAccessKey: getEnv("AWS_SECRET_ACCESS_KEY", "TF_VAR_AWS_SECRET_ACCESS_KEY"), + AWSSessionToken: getEnv("AWS_SESSION_TOKEN", "TF_VAR_AWS_SESSION_TOKEN"), + AWSEndpoint: getEnv("STS_ENDPOINT", "TF_VAR_STS_ENDPOINT"), + } +} + +func (e *Vars) GetCredentials() *Credentials { + return &Credentials{ + Method: "Environment Variables", + AccessToken: e.AccessToken, + ClientID: e.ClientID, + ClientSecret: e.ClientSecret, + PublicKey: e.PublicKey, + PrivateKey: e.PrivateKey, + BaseURL: e.BaseURL, + RealmBaseURL: e.RealmBaseURL, + } +} + +func getEnv(key ...string) string { + for _, k := range key { + if v := os.Getenv(k); v != "" { + return v + } + } + return "" +} + +// TODO lowercase when used +func Coalesce(str ...string) string { + for _, s := range str { + if s != "" { + return s + } + } + return "" +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a8d6a47582..b6bbd03a53 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "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/provider" "github.com/hashicorp/terraform-plugin-framework/provider/metaschema" "github.com/hashicorp/terraform-plugin-framework/provider/schema" @@ -206,10 +205,8 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config if resp.Diagnostics.HasError() { return } - data = setDefaultValuesWithValidations(ctx, &data, resp) - if resp.Diagnostics.HasError() { - return - } + + envVars := config.NewEnvVars() awsCredentials, err := getTPFAWSCredentials(ctx, &data) if err != nil { @@ -221,8 +218,15 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config return } + providerCredentials := getTPFProviderCredentials(&data, resp) + if resp.Diagnostics.HasError() { + return + } + + _, _ = providerCredentials, awsCredentials + // TODO: chooose the credentials between AWS, SA or PAK - client, err := config.NewClient(awsCredentials, req.TerraformVersion) + client, err := config.NewClient(envVars.GetCredentials(), req.TerraformVersion) if err != nil { // TODO: error message resp.Diagnostics.AddError( @@ -260,136 +264,13 @@ func getTPFAWSCredentials(ctx context.Context, data *tfMongodbAtlasProviderModel return c, nil } -func setDefaultValuesWithValidations(ctx context.Context, data *tfMongodbAtlasProviderModel, resp *provider.ConfigureResponse) tfMongodbAtlasProviderModel { +// TODO: implement and return Vars, see Gov, see if return diagnostics +func getTPFProviderCredentials(data *tfMongodbAtlasProviderModel, resp *provider.ConfigureResponse) tfMongodbAtlasProviderModel { if mongodbgovCloud := data.IsMongodbGovCloud.ValueBool(); mongodbgovCloud { if !isGovBaseURLConfiguredForProvider(data) { data.BaseURL = types.StringValue(MongodbGovCloudURL) } } - if data.BaseURL.ValueString() == "" { - data.BaseURL = types.StringValue(MultiEnvDefaultFunc([]string{ - "MONGODB_ATLAS_BASE_URL", - "MCLI_OPS_MANAGER_URL", - }, "").(string)) - } - - awsRoleDefined := false - if len(data.AssumeRole.Elements()) == 0 { - assumeRoleArn := MultiEnvDefaultFunc([]string{ - "ASSUME_ROLE_ARN", - "TF_VAR_ASSUME_ROLE_ARN", - }, "").(string) - if assumeRoleArn != "" { - awsRoleDefined = true - var diags diag.Diagnostics - data.AssumeRole, diags = types.ListValueFrom(ctx, AssumeRoleType, []tfAssumeRoleModel{ - { - RoleARN: types.StringValue(assumeRoleArn), - }, - }) - if diags.HasError() { - resp.Diagnostics.Append(diags...) - } - } - } else { - awsRoleDefined = true - } - - if data.PublicKey.ValueString() == "" { - data.PublicKey = types.StringValue(MultiEnvDefaultFunc([]string{ - "MONGODB_ATLAS_PUBLIC_API_KEY", - "MONGODB_ATLAS_PUBLIC_KEY", - "MCLI_PUBLIC_API_KEY", - }, "").(string)) - } - - if data.PrivateKey.ValueString() == "" { - data.PrivateKey = types.StringValue(MultiEnvDefaultFunc([]string{ - "MONGODB_ATLAS_PRIVATE_API_KEY", - "MONGODB_ATLAS_PRIVATE_KEY", - "MCLI_PRIVATE_API_KEY", - }, "").(string)) - } - - if data.RealmBaseURL.ValueString() == "" { - data.RealmBaseURL = types.StringValue(MultiEnvDefaultFunc([]string{ - "MONGODB_REALM_BASE_URL", - }, "").(string)) - } - - if data.Region.ValueString() == "" { - data.Region = types.StringValue(MultiEnvDefaultFunc([]string{ - "AWS_REGION", - "TF_VAR_AWS_REGION", - }, "").(string)) - } - - if data.StsEndpoint.ValueString() == "" { - data.StsEndpoint = types.StringValue(MultiEnvDefaultFunc([]string{ - "STS_ENDPOINT", - "TF_VAR_STS_ENDPOINT", - }, "").(string)) - } - - if data.AwsAccessKeyID.ValueString() == "" { - data.AwsAccessKeyID = types.StringValue(MultiEnvDefaultFunc([]string{ - "AWS_ACCESS_KEY_ID", - "TF_VAR_AWS_ACCESS_KEY_ID", - }, "").(string)) - } - - if data.AwsSecretAccessKeyID.ValueString() == "" { - data.AwsSecretAccessKeyID = types.StringValue(MultiEnvDefaultFunc([]string{ - "AWS_SECRET_ACCESS_KEY", - "TF_VAR_AWS_SECRET_ACCESS_KEY", - }, "").(string)) - } - - if data.AwsSessionToken.ValueString() == "" { - data.AwsSessionToken = types.StringValue(MultiEnvDefaultFunc([]string{ - "AWS_SESSION_TOKEN", - "TF_VAR_AWS_SESSION_TOKEN", - }, "").(string)) - } - - if data.SecretName.ValueString() == "" { - data.SecretName = types.StringValue(MultiEnvDefaultFunc([]string{ - "SECRET_NAME", - "TF_VAR_SECRET_NAME", - }, "").(string)) - } - - if data.ClientID.ValueString() == "" { - data.ClientID = types.StringValue(MultiEnvDefaultFunc([]string{ - "MONGODB_ATLAS_CLIENT_ID", - "TF_VAR_CLIENT_ID", - }, "").(string)) - } - - if data.ClientSecret.ValueString() == "" { - data.ClientSecret = types.StringValue(MultiEnvDefaultFunc([]string{ - "MONGODB_ATLAS_CLIENT_SECRET", - "TF_VAR_CLIENT_SECRET", - }, "").(string)) - } - - if data.AccessToken.ValueString() == "" { - data.AccessToken = types.StringValue(MultiEnvDefaultFunc([]string{ - "MONGODB_ATLAS_ACCESS_TOKEN", - "TF_VAR_ACCESS_TOKEN", - }, "").(string)) - } - - // Check if any valid authentication method is provided - if !config.HasValidAuthCredentials(&config.Config{ - PublicKey: data.PublicKey.ValueString(), - PrivateKey: data.PrivateKey.ValueString(), - ClientID: data.ClientID.ValueString(), - ClientSecret: data.ClientSecret.ValueString(), - AccessToken: data.AccessToken.ValueString(), - }) && !awsRoleDefined { - resp.Diagnostics.AddError(ProviderConfigError, MissingAuthAttrError) - } return *data } @@ -491,7 +372,7 @@ func MuxProviderFactory() func() tfprotov6.ProviderServer { return muxServer.ProviderServer } -func MultiEnvDefaultFunc(ks []string, def any) any { +func multiEnvDefaultFunc(ks []string, def any) any { for _, k := range ks { if v := os.Getenv(k); v != "" { return v @@ -502,7 +383,7 @@ func MultiEnvDefaultFunc(ks []string, def any) any { func isGovBaseURLConfigured(baseURL string) bool { if baseURL == "" { - baseURL = MultiEnvDefaultFunc([]string{ + baseURL = multiEnvDefaultFunc([]string{ "MONGODB_ATLAS_BASE_URL", "MCLI_OPS_MANAGER_URL", }, "").(string) diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index ed6a189166..a00d49ecad 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -294,10 +294,9 @@ func getResourcesMap() map[string]*schema.Resource { func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { return func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { - diagnostics := setDefaultsAndValidations(d) - if diagnostics.HasError() { - return nil, diagnostics - } + diagnostics := []diag.Diagnostic{} + + envVars := config.NewEnvVars() awsCredentials, err := getSDKv2AWSCredentials(d) if err != nil { @@ -305,8 +304,15 @@ func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *s return nil, append(diagnostics, diag.FromErr(fmt.Errorf("failed to get AWS credentials: %w", err))...) } + diags := getSDKv2ProviderCredentials(d) + if diags.HasError() { + return nil, append(diagnostics, diags...) + } + + _ = awsCredentials + // TODO: chooose the credentials between AWS, SA or PAK - client, err := config.NewClient(awsCredentials, provider.TerraformVersion) + client, err := config.NewClient(envVars.GetCredentials(), provider.TerraformVersion) if err != nil { // TODO: error message return nil, append(diagnostics, diag.FromErr(err)...) @@ -342,7 +348,7 @@ func getSDKv2AWSCredentials(d *schema.ResourceData) (*config.Credentials, error) return c, nil } -func setDefaultsAndValidations(d *schema.ResourceData) diag.Diagnostics { +func getSDKv2ProviderCredentials(d *schema.ResourceData) diag.Diagnostics { diagnostics := []diag.Diagnostic{} mongodbgovCloud := conversion.Pointer(d.Get("is_mongodbgov_cloud").(bool)) @@ -353,138 +359,9 @@ func setDefaultsAndValidations(d *schema.ResourceData) diag.Diagnostics { } } } - - if err := setValueFromConfigOrEnv(d, "base_url", []string{ - "MONGODB_ATLAS_BASE_URL", - "MCLI_OPS_MANAGER_URL", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - awsRoleDefined := false - assumeRoles := d.Get("assume_role").([]any) - if len(assumeRoles) == 0 { - roleArn := MultiEnvDefaultFunc([]string{ - "ASSUME_ROLE_ARN", - "TF_VAR_ASSUME_ROLE_ARN", - }, "").(string) - if roleArn != "" { - awsRoleDefined = true - if err := d.Set("assume_role", []map[string]any{{"role_arn": roleArn}}); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - } - } else { - awsRoleDefined = true - } - - if err := setValueFromConfigOrEnv(d, "public_key", []string{ - "MONGODB_ATLAS_PUBLIC_API_KEY", - "MONGODB_ATLAS_PUBLIC_KEY", - "MCLI_PUBLIC_API_KEY", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "private_key", []string{ - "MONGODB_ATLAS_PRIVATE_API_KEY", - "MONGODB_ATLAS_PRIVATE_KEY", - "MCLI_PRIVATE_API_KEY", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "realm_base_url", []string{ - "MONGODB_REALM_BASE_URL", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "region", []string{ - "AWS_REGION", - "TF_VAR_AWS_REGION", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "sts_endpoint", []string{ - "STS_ENDPOINT", - "TF_VAR_STS_ENDPOINT", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "aws_access_key_id", []string{ - "AWS_ACCESS_KEY_ID", - "TF_VAR_AWS_ACCESS_KEY_ID", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "aws_secret_access_key", []string{ - "AWS_SECRET_ACCESS_KEY", - "TF_VAR_AWS_SECRET_ACCESS_KEY", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "secret_name", []string{ - "SECRET_NAME", - "TF_VAR_SECRET_NAME", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "aws_session_token", []string{ - "AWS_SESSION_TOKEN", - "TF_VAR_AWS_SESSION_TOKEN", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "client_id", []string{ - "MONGODB_ATLAS_CLIENT_ID", - "TF_VAR_CLIENT_ID", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "client_secret", []string{ - "MONGODB_ATLAS_CLIENT_SECRET", - "TF_VAR_CLIENT_SECRET", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - if err := setValueFromConfigOrEnv(d, "access_token", []string{ - "MONGODB_ATLAS_OAUTH_TOKEN", - "TF_VAR_OAUTH_TOKEN", - }); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - - // Check if any valid authentication method is provided - if !config.HasValidAuthCredentials(&config.Config{ - PublicKey: d.Get("public_key").(string), - PrivateKey: d.Get("private_key").(string), - ClientID: d.Get("client_id").(string), - ClientSecret: d.Get("client_secret").(string), - AccessToken: d.Get("access_token").(string), - }) && !awsRoleDefined { - diagnostics = append(diagnostics, diag.Diagnostic{Severity: diag.Error, Summary: MissingAuthAttrError}) - } - return diagnostics } -func setValueFromConfigOrEnv(d *schema.ResourceData, attrName string, envVars []string) error { - var val = d.Get(attrName).(string) - if val == "" { - val = MultiEnvDefaultFunc(envVars, "").(string) - } - return d.Set(attrName, val) -} - // assumeRoleSchema From aws provider.go func assumeRoleSchema() *schema.Schema { return &schema.Schema{ From f63dcf3974718eb7fa90bdc76d3c7f7b8c213666 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sat, 4 Oct 2025 08:02:10 +0200 Subject: [PATCH 20/41] remove CredentialProvider --- internal/config/client.go | 43 --------------------------------------- 1 file changed, 43 deletions(-) diff --git a/internal/config/client.go b/internal/config/client.go index bbdcacb8e3..7eea12b414 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -48,35 +48,6 @@ const ( Digest ) -// CredentialProvider interface for types that can provide MongoDB Atlas credentials -type CredentialProvider interface { - GetPublicKey() string - GetPrivateKey() string - GetClientID() string - GetClientSecret() string - GetAccessToken() string -} - -// IsDigestAuth checks if public/private key credentials are present -func IsDigestAuthPresent(cp CredentialProvider) bool { - return cp.GetPublicKey() != "" && cp.GetPrivateKey() != "" -} - -// IsServiceAccountAuth checks if client ID/secret credentials are present -func IsServiceAccountAuthPresent(cp CredentialProvider) bool { - return cp.GetClientID() != "" && cp.GetClientSecret() != "" -} - -// IsAccessTokenAuth checks if access token credentials are present -func IsAccessTokenAuthPresent(cp CredentialProvider) bool { - return cp.GetAccessToken() != "" -} - -// HasValidAuthCredentials checks if any valid authentication method is provided -func HasValidAuthCredentials(cp CredentialProvider) bool { - return IsDigestAuthPresent(cp) || IsServiceAccountAuthPresent(cp) || IsAccessTokenAuthPresent(cp) -} - var baseTransport = &http.Transport{ DialContext: (&net.Dialer{ Timeout: timeout, @@ -342,20 +313,6 @@ func (c *MongoDBClient) UntypedAPICall(ctx context.Context, params *APICallParam return apiResp, err } -// ResolveAuthMethod determines the authentication method from any credential provider -func ResolveAuthMethod(cg CredentialProvider) AuthMethod { - if IsAccessTokenAuthPresent(cg) { - return AccessToken - } - if IsServiceAccountAuthPresent(cg) { - return ServiceAccount - } - if IsDigestAuthPresent(cg) { - return Digest - } - return Unknown -} - func userAgent(terraformVersion string) string { metadata := []struct { Name string From d700653eed8f10cc353f5895c2fc9bb0eabce23f Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sat, 4 Oct 2025 11:43:42 +0200 Subject: [PATCH 21/41] refactor AWS and Provider credentials --- internal/config/client.go | 12 --- internal/config/credentials.go | 29 +++++- internal/provider/aws_credentials.go | 50 +++++---- internal/provider/provider.go | 146 +++++++++------------------ internal/provider/provider_sdk2.go | 144 +++++++++----------------- 5 files changed, 156 insertions(+), 225 deletions(-) diff --git a/internal/config/client.go b/internal/config/client.go index 7eea12b414..a065e5d227 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -95,18 +95,6 @@ type Config struct { AccessToken string } -// CredentialProvider implementation for Config -func (c *Config) GetPublicKey() string { return c.PublicKey } -func (c *Config) GetPrivateKey() string { return c.PrivateKey } -func (c *Config) GetClientID() string { return c.ClientID } -func (c *Config) GetClientSecret() string { return c.ClientSecret } -func (c *Config) GetAccessToken() string { return c.AccessToken } - -type SecretData struct { - PublicKey string `json:"public_key"` - PrivateKey string `json:"private_key"` -} - func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) { userAgent := userAgent(terraformVersion) client, err := getHTTPClient(c) diff --git a/internal/config/credentials.go b/internal/config/credentials.go index a7f3c57f68..3034b92e21 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -1,6 +1,10 @@ package config -import "os" +import ( + "os" + + "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" +) type Credentials struct { Method string @@ -75,6 +79,29 @@ func (e *Vars) GetCredentials() *Credentials { } } +type AWSVars struct { + AssumeRoleARN string + SecretName string + Region string + AccessKeyID string + SecretAccessKey string + SessionToken string + Endpoint string +} + +// GetAWS returns variables in the format AWS expects, e.g. region in lowercase. +func (e *Vars) GetAWS() *AWSVars { + return &AWSVars{ + AssumeRoleARN: e.AWSAssumeRoleARN, + SecretName: e.AWSSecretName, + Region: conversion.MongoDBRegionToAWSRegion(e.AWSRegion), + AccessKeyID: e.AWSAccessKeyID, + SecretAccessKey: e.AWSSecretAccessKey, + SessionToken: e.AWSSessionToken, + Endpoint: e.AWSEndpoint, + } +} + func getEnv(key ...string) string { for _, k := range key { if v := os.Getenv(k); v != "" { diff --git a/internal/provider/aws_credentials.go b/internal/provider/aws_credentials.go index def8af7771..5ad9a60c19 100644 --- a/internal/provider/aws_credentials.go +++ b/internal/provider/aws_credentials.go @@ -25,47 +25,57 @@ const ( minSegmentsForSTSRegionalHost = 4 ) -// TODO: req object -func configureCredentialsSTS(c *config.Credentials, assumeRoleARN, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint string) error { +func getAWSCredentials(c *config.AWSVars) (*config.Credentials, error) { defaultResolver := endpoints.DefaultResolver() stsCustResolverFn := func(service, _ string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) { if service == sts.EndpointsID { - resolved, err := ResolveSTSEndpoint(endpoint, region) + resolved, err := ResolveSTSEndpoint(c.Endpoint, c.Region) if err != nil { return endpoints.ResolvedEndpoint{}, err } return resolved, nil } - return defaultResolver.EndpointFor(service, region, optFns...) + return defaultResolver.EndpointFor(service, c.Region, optFns...) } sess := session.Must(session.NewSession(&aws.Config{ - Region: aws.String(region), - Credentials: credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, awsSessionToken), + Region: aws.String(c.Region), + Credentials: credentials.NewStaticCredentials(c.AccessKeyID, c.SecretAccessKey, c.SessionToken), EndpointResolver: endpoints.ResolverFunc(stsCustResolverFn), })) - creds := stscreds.NewCredentials(sess, assumeRoleARN) + creds := stscreds.NewCredentials(sess, c.AssumeRoleARN) if _, err := sess.Config.Credentials.Get(); err != nil { - return err + return nil, err } if _, err := creds.Get(); err != nil { - return err + return nil, err } - secretString, err := secretsManagerGetSecretValue(sess, &aws.Config{Credentials: creds, Region: aws.String(region)}, secret) + secretString, err := secretsManagerGetSecretValue(sess, &aws.Config{Credentials: creds, Region: aws.String(c.Region)}, c.SecretName) if err != nil { - return err + return nil, err } - var secretData SecretData - err = json.Unmarshal([]byte(secretString), &secretData) + // TODO could credentials be reused removing Method? + var secret struct { + AccessToken string `json:"access_token"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + PublicKey string `json:"public_key"` + PrivateKey string `json:"private_key"` + } + err = json.Unmarshal([]byte(secretString), &secret) if err != nil { - return err + return nil, err } // TODO: how to read URLs in AWS Secrets Manager? - c.AccessToken = secretData.AccessToken - c.ClientID = secretData.ClientID - c.ClientSecret = secretData.ClientSecret - c.PublicKey = secretData.PublicKey - c.PrivateKey = secretData.PrivateKey - return nil + return &config.Credentials{ + Method: "AWS Secrets Manager", + AccessToken: secret.AccessToken, + ClientID: secret.ClientID, + ClientSecret: secret.ClientSecret, + PublicKey: secret.PublicKey, + PrivateKey: secret.PrivateKey, + BaseURL: "", // TODO: how to read + RealmBaseURL: "", // TODO: how to read + }, nil } func DeriveSTSRegionFromEndpoint(ep string) string { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b6bbd03a53..0ec0e8588b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -2,12 +2,9 @@ package provider import ( "context" - "fmt" "log" - "os" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/metaschema" @@ -20,7 +17,6 @@ import ( "github.com/hashicorp/terraform-plugin-mux/tf5to6server" "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" - "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" "github.com/mongodb/terraform-provider-mongodbatlas/internal/config" "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/advancedcluster" "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/alertconfiguration" @@ -69,32 +65,28 @@ const ( type MongodbtlasProvider struct { } -type tfMongodbAtlasProviderModel struct { - AssumeRole types.List `tfsdk:"assume_role"` - Region types.String `tfsdk:"region"` - PrivateKey types.String `tfsdk:"private_key"` - BaseURL types.String `tfsdk:"base_url"` - RealmBaseURL types.String `tfsdk:"realm_base_url"` - SecretName types.String `tfsdk:"secret_name"` - PublicKey types.String `tfsdk:"public_key"` - StsEndpoint types.String `tfsdk:"sts_endpoint"` - AwsAccessKeyID types.String `tfsdk:"aws_access_key_id"` - AwsSecretAccessKeyID types.String `tfsdk:"aws_secret_access_key"` - AwsSessionToken types.String `tfsdk:"aws_session_token"` - ClientID types.String `tfsdk:"client_id"` - ClientSecret types.String `tfsdk:"client_secret"` - AccessToken types.String `tfsdk:"access_token"` - IsMongodbGovCloud types.Bool `tfsdk:"is_mongodbgov_cloud"` +type tfModel struct { + Region types.String `tfsdk:"region"` + PrivateKey types.String `tfsdk:"private_key"` + BaseURL types.String `tfsdk:"base_url"` + RealmBaseURL types.String `tfsdk:"realm_base_url"` + SecretName types.String `tfsdk:"secret_name"` + PublicKey types.String `tfsdk:"public_key"` + StsEndpoint types.String `tfsdk:"sts_endpoint"` + AwsAccessKeyID types.String `tfsdk:"aws_access_key_id"` + AwsSecretAccessKeyID types.String `tfsdk:"aws_secret_access_key"` + AwsSessionToken types.String `tfsdk:"aws_session_token"` + ClientID types.String `tfsdk:"client_id"` + ClientSecret types.String `tfsdk:"client_secret"` + AccessToken types.String `tfsdk:"access_token"` + AssumeRole []tfAssumeRoleModel `tfsdk:"assume_role"` + IsMongodbGovCloud types.Bool `tfsdk:"is_mongodbgov_cloud"` } type tfAssumeRoleModel struct { RoleARN types.String `tfsdk:"role_arn"` } -var AssumeRoleType = types.ObjectType{AttrTypes: map[string]attr.Type{ - "role_arn": types.StringType, -}} - func (p *MongodbtlasProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { resp.TypeName = "mongodbatlas" resp.Version = version.ProviderVersion @@ -200,79 +192,64 @@ var fwAssumeRoleSchema = schema.ListNestedBlock{ } func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { - var data tfMongodbAtlasProviderModel - resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + providerVars := getProviderVars(ctx, req, resp) if resp.Diagnostics.HasError() { return } + // TODO: refactor, it's similar to the other provider + envVars := config.NewEnvVars() - awsCredentials, err := getTPFAWSCredentials(ctx, &data) + // decide what to do if AWS is not chosen from provider and env vars + awsCredentials, err := getAWSCredentials(envVars.GetAWS()) if err != nil { - // TODO: error message - resp.Diagnostics.AddError( - "failed to get AWS credentials", - err.Error(), - ) - return - } - - providerCredentials := getTPFProviderCredentials(&data, resp) - if resp.Diagnostics.HasError() { + resp.Diagnostics.AddError("Error getting AWS credentials", err.Error()) return } - _, _ = providerCredentials, awsCredentials + _, _ = providerVars, awsCredentials // TODO: chooose the credentials between AWS, SA or PAK client, err := config.NewClient(envVars.GetCredentials(), req.TerraformVersion) if err != nil { - // TODO: error message - resp.Diagnostics.AddError( - "TODO FAIL AWS SECRETS MANAGER", - err.Error(), - ) + resp.Diagnostics.AddError("Error initializing provider", err.Error()) return } + // TODO gov look former code + resp.DataSourceData = client resp.ResourceData = client } -func getTPFAWSCredentials(ctx context.Context, data *tfMongodbAtlasProviderModel) (*config.Credentials, error) { - c := &config.Credentials{ - Method: "AWS Secrets Manager", +// TODO: see Gov +func getProviderVars(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) *config.Vars { + var data tfModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return nil } - var assumeRoles []tfAssumeRoleModel - data.AssumeRole.ElementsAs(ctx, &assumeRoles, true) - if len(assumeRoles) == 0 { - return c, nil + assumeRoleARN := "" + if len(data.AssumeRole) > 0 { + assumeRoleARN = data.AssumeRole[0].RoleARN.ValueString() } - assumeRoleARN := assumeRoles[0].RoleARN.ValueString() - secret := data.SecretName.ValueString() - region := conversion.MongoDBRegionToAWSRegion(data.Region.ValueString()) - awsAccessKeyID := data.AwsAccessKeyID.ValueString() - awsSecretAccessKey := data.AwsSecretAccessKeyID.ValueString() - awsSessionToken := data.AwsSessionToken.ValueString() - endpoint := data.StsEndpoint.ValueString() - // TODO: read from env vars if not here, and req object to configure - err := configureCredentialsSTS(c, assumeRoleARN, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint) - if err != nil { - return nil, fmt.Errorf("failed to configure STS credentials: %w", err) + return &config.Vars{ + AccessToken: data.AccessToken.ValueString(), + ClientID: data.ClientID.ValueString(), + ClientSecret: data.ClientSecret.ValueString(), + PublicKey: data.PublicKey.ValueString(), + PrivateKey: data.PrivateKey.ValueString(), + BaseURL: data.BaseURL.ValueString(), + RealmBaseURL: data.RealmBaseURL.ValueString(), + AWSAssumeRoleARN: assumeRoleARN, + AWSSecretName: data.SecretName.ValueString(), + AWSRegion: data.Region.ValueString(), + AWSAccessKeyID: data.AwsAccessKeyID.ValueString(), + AWSSecretAccessKey: data.AwsSecretAccessKeyID.ValueString(), + AWSSessionToken: data.AwsSessionToken.ValueString(), + AWSEndpoint: data.StsEndpoint.ValueString(), } - return c, nil -} - -// TODO: implement and return Vars, see Gov, see if return diagnostics -func getTPFProviderCredentials(data *tfMongodbAtlasProviderModel, resp *provider.ConfigureResponse) tfMongodbAtlasProviderModel { - if mongodbgovCloud := data.IsMongodbGovCloud.ValueBool(); mongodbgovCloud { - if !isGovBaseURLConfiguredForProvider(data) { - data.BaseURL = types.StringValue(MongodbGovCloudURL) - } - } - - return *data } func (p *MongodbtlasProvider) DataSources(context.Context) []func() datasource.DataSource { @@ -371,26 +348,3 @@ func MuxProviderFactory() func() tfprotov6.ProviderServer { } return muxServer.ProviderServer } - -func multiEnvDefaultFunc(ks []string, def any) any { - for _, k := range ks { - if v := os.Getenv(k); v != "" { - return v - } - } - return def -} - -func isGovBaseURLConfigured(baseURL string) bool { - if baseURL == "" { - baseURL = multiEnvDefaultFunc([]string{ - "MONGODB_ATLAS_BASE_URL", - "MCLI_OPS_MANAGER_URL", - }, "").(string) - } - return baseURL == MongodbGovCloudDevURL || baseURL == MongodbGovCloudQAURL -} - -func isGovBaseURLConfiguredForProvider(data *tfMongodbAtlasProviderModel) bool { - return isGovBaseURLConfigured(data.BaseURL.ValueString()) -} diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index a00d49ecad..968213700f 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" "github.com/mongodb/terraform-provider-mongodbatlas/internal/config" "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/accesslistapikey" "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/apikey" @@ -53,21 +52,6 @@ import ( "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/x509authenticationdatabaseuser" ) -type SecretData struct { - PublicKey string `json:"public_key"` - PrivateKey string `json:"private_key"` - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - AccessToken string `json:"access_token"` -} - -// CredentialProvider implementation for SecretData -func (s *SecretData) GetPublicKey() string { return s.PublicKey } -func (s *SecretData) GetPrivateKey() string { return s.PrivateKey } -func (s *SecretData) GetClientID() string { return s.ClientID } -func (s *SecretData) GetClientSecret() string { return s.ClientSecret } -func (s *SecretData) GetAccessToken() string { return s.AccessToken } - // NewSdkV2Provider returns the provider to be use by the code. func NewSdkV2Provider() *schema.Provider { provider := &schema.Provider{ @@ -170,6 +154,23 @@ func NewSdkV2Provider() *schema.Provider { return provider } +func assumeRoleSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "role_arn": { + Type: schema.TypeString, + Optional: true, + Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", + }, + }, + }, + } +} + func getDataSourcesMap() map[string]*schema.Resource { dataSourcesMap := map[string]*schema.Resource{ "mongodbatlas_custom_db_role": customdbrole.DataSource(), @@ -294,102 +295,53 @@ func getResourcesMap() map[string]*schema.Resource { func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { return func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { - diagnostics := []diag.Diagnostic{} + diagnostics := diag.Diagnostics{} + + providerVars := getSDKv2ProviderVars(d) + + // TODO: refactor, it's similar to the other provider envVars := config.NewEnvVars() - awsCredentials, err := getSDKv2AWSCredentials(d) + awsCredentials, err := getAWSCredentials(envVars.GetAWS()) if err != nil { - // TODO: error message - return nil, append(diagnostics, diag.FromErr(fmt.Errorf("failed to get AWS credentials: %w", err))...) - } - - diags := getSDKv2ProviderCredentials(d) - if diags.HasError() { - return nil, append(diagnostics, diags...) + return nil, diag.FromErr(fmt.Errorf("error getting AWS credentials: %w", err)) } - _ = awsCredentials + _, _ = providerVars, awsCredentials // TODO: chooose the credentials between AWS, SA or PAK client, err := config.NewClient(envVars.GetCredentials(), provider.TerraformVersion) if err != nil { - // TODO: error message - return nil, append(diagnostics, diag.FromErr(err)...) + return nil, diag.FromErr(fmt.Errorf("error initializing provider: %w", err)) } return client, diagnostics } -} -func getSDKv2AWSCredentials(d *schema.ResourceData) (*config.Credentials, error) { - c := &config.Credentials{ - Method: "AWS Secrets Manager", - } - assumeRoleVal, ok := d.GetOk("assume_role") - if !ok { - return c, nil - } - assumeRoles := assumeRoleVal.([]any) - if len(assumeRoles) == 0 { - return c, nil - } - assumeRoleARN := getAssumeRoleARN(assumeRoles[0].(map[string]any)) - secret := d.Get("secret_name").(string) - region := conversion.MongoDBRegionToAWSRegion(d.Get("region").(string)) - awsAccessKeyID := d.Get("aws_access_key_id").(string) - awsSecretAccessKey := d.Get("aws_secret_access_key").(string) - awsSessionToken := d.Get("aws_session_token").(string) - endpoint := d.Get("sts_endpoint").(string) - // TODO: read from env vars if not here, and req object to configure - err := configureCredentialsSTS(c, assumeRoleARN, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint) - if err != nil { - return nil, fmt.Errorf("failed to configure STS credentials: %w", err) - } - return c, nil -} - -func getSDKv2ProviderCredentials(d *schema.ResourceData) diag.Diagnostics { - diagnostics := []diag.Diagnostic{} - - mongodbgovCloud := conversion.Pointer(d.Get("is_mongodbgov_cloud").(bool)) - if *mongodbgovCloud { - if !isGovBaseURLConfiguredForSDK2Provider(d) { - if err := d.Set("base_url", MongodbGovCloudURL); err != nil { - return append(diagnostics, diag.FromErr(err)...) - } - } - } - return diagnostics -} - -// assumeRoleSchema From aws provider.go -func assumeRoleSchema() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "role_arn": { - Type: schema.TypeString, - Optional: true, - Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", - }, - }, - }, - } + // TODO gov look former code } -func getAssumeRoleARN(tfMap map[string]any) string { - if tfMap == nil { - return "" +// TODO: implement this +func getSDKv2ProviderVars(d *schema.ResourceData) *config.Vars { + assumeRoleARN := "" + assumeRoles := d.Get("assume_role").([]any) + if len(assumeRoles) > 0 { + assumeRoleARN = assumeRoles[0].(map[string]any)["role_arn"].(string) } - if v, ok := tfMap["role_arn"].(string); ok && v != "" { - return v + return &config.Vars{ + AccessToken: d.Get("access_token").(string), + ClientID: d.Get("client_id").(string), + ClientSecret: d.Get("client_secret").(string), + PublicKey: d.Get("public_key").(string), + PrivateKey: d.Get("private_key").(string), + BaseURL: d.Get("base_url").(string), + RealmBaseURL: d.Get("realm_base_url").(string), + AWSAssumeRoleARN: assumeRoleARN, + AWSSecretName: d.Get("secret_name").(string), + AWSRegion: d.Get("region").(string), + AWSAccessKeyID: d.Get("aws_access_key_id").(string), + AWSSecretAccessKey: d.Get("aws_secret_access_key").(string), + AWSSessionToken: d.Get("aws_session_token").(string), + AWSEndpoint: d.Get("sts_endpoint").(string), } - return "" -} - -func isGovBaseURLConfiguredForSDK2Provider(d *schema.ResourceData) bool { - return isGovBaseURLConfigured(d.Get("base_url").(string)) } From 9193eb25eebb6372550712096daf68111ded3df2 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sat, 4 Oct 2025 12:02:08 +0200 Subject: [PATCH 22/41] gov --- internal/provider/provider.go | 18 +++++++++++++++--- internal/provider/provider_sdk2.go | 8 +++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0ec0e8588b..30ea49d1ed 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -3,6 +3,7 @@ package provider import ( "context" "log" + "slices" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -49,8 +50,7 @@ import ( ) const ( - MongodbGovCloudURL = "https://cloud.mongodbgov.com" - MongodbGovCloudQAURL = "https://cloud-qa.mongodbgov.com" + govURL = "https://cloud.mongodbgov.com" MongodbGovCloudDevURL = "https://cloud-dev.mongodbgov.com" ProviderConfigError = "error in configuring the provider." MissingAuthAttrError = "either AWS Secrets Manager, Service Accounts or Atlas Programmatic API Keys attributes must be set" @@ -62,6 +62,13 @@ const ( ProviderMetaModuleVersionDesc = "The version of the module using the provider" ) +var ( + govAdditionalURLs = []string{ + "https://cloud-dev.mongodbgov.com", + "https://cloud-qa.mongodbgov.com", + } +) + type MongodbtlasProvider struct { } @@ -234,13 +241,18 @@ func getProviderVars(ctx context.Context, req provider.ConfigureRequest, resp *p if len(data.AssumeRole) > 0 { assumeRoleARN = data.AssumeRole[0].RoleARN.ValueString() } + baseURL := data.BaseURL.ValueString() + // TODO: check that is_mongodbgov_cloud works for undefined, true, false + if data.IsMongodbGovCloud.ValueBool() && !slices.Contains(govAdditionalURLs, baseURL) { + baseURL = govURL + } return &config.Vars{ AccessToken: data.AccessToken.ValueString(), ClientID: data.ClientID.ValueString(), ClientSecret: data.ClientSecret.ValueString(), PublicKey: data.PublicKey.ValueString(), PrivateKey: data.PrivateKey.ValueString(), - BaseURL: data.BaseURL.ValueString(), + BaseURL: baseURL, RealmBaseURL: data.RealmBaseURL.ValueString(), AWSAssumeRoleARN: assumeRoleARN, AWSSecretName: data.SecretName.ValueString(), diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 968213700f..5ccdad0615 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -3,6 +3,7 @@ package provider import ( "context" "fmt" + "slices" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -328,13 +329,18 @@ func getSDKv2ProviderVars(d *schema.ResourceData) *config.Vars { if len(assumeRoles) > 0 { assumeRoleARN = assumeRoles[0].(map[string]any)["role_arn"].(string) } + baseURL := d.Get("base_url").(string) + // TODO: check that is_mongodbgov_cloud works for undefined, true, false + if d.Get("is_mongodbgov_cloud").(bool) && !slices.Contains(govAdditionalURLs, baseURL) { + baseURL = govURL + } return &config.Vars{ AccessToken: d.Get("access_token").(string), ClientID: d.Get("client_id").(string), ClientSecret: d.Get("client_secret").(string), PublicKey: d.Get("public_key").(string), PrivateKey: d.Get("private_key").(string), - BaseURL: d.Get("base_url").(string), + BaseURL: baseURL, RealmBaseURL: d.Get("realm_base_url").(string), AWSAssumeRoleARN: assumeRoleARN, AWSSecretName: d.Get("secret_name").(string), From 61b62c5cdd3f0350bb6a74b7eccfdd629a4c6e38 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sat, 4 Oct 2025 13:01:35 +0200 Subject: [PATCH 23/41] configClient --- internal/provider/provider.go | 26 +++++++++++++------------- internal/provider/provider_sdk2.go | 21 ++------------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 30ea49d1ed..6afe9558d9 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -203,34 +203,34 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config if resp.Diagnostics.HasError() { return } + client, err := configClient(providerVars, req.TerraformVersion) + if err != nil { + resp.Diagnostics.AddError("Error initializing provider", err.Error()) + return + } + resp.DataSourceData = client + resp.ResourceData = client +} - // TODO: refactor, it's similar to the other provider - +func configClient(providerVars *config.Vars, terraformVersion string) (*config.MongoDBClient, error) { envVars := config.NewEnvVars() // decide what to do if AWS is not chosen from provider and env vars awsCredentials, err := getAWSCredentials(envVars.GetAWS()) if err != nil { - resp.Diagnostics.AddError("Error getting AWS credentials", err.Error()) - return + return nil, err } _, _ = providerVars, awsCredentials // TODO: chooose the credentials between AWS, SA or PAK - client, err := config.NewClient(envVars.GetCredentials(), req.TerraformVersion) + client, err := config.NewClient(envVars.GetCredentials(), terraformVersion) if err != nil { - resp.Diagnostics.AddError("Error initializing provider", err.Error()) - return + return nil, err } - - // TODO gov look former code - - resp.DataSourceData = client - resp.ResourceData = client + return client, nil } -// TODO: see Gov func getProviderVars(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) *config.Vars { var data tfModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 5ccdad0615..045c1a9d8f 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -296,30 +296,13 @@ func getResourcesMap() map[string]*schema.Resource { func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { return func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { - diagnostics := diag.Diagnostics{} - providerVars := getSDKv2ProviderVars(d) - - // TODO: refactor, it's similar to the other provider - - envVars := config.NewEnvVars() - - awsCredentials, err := getAWSCredentials(envVars.GetAWS()) - if err != nil { - return nil, diag.FromErr(fmt.Errorf("error getting AWS credentials: %w", err)) - } - - _, _ = providerVars, awsCredentials - - // TODO: chooose the credentials between AWS, SA or PAK - client, err := config.NewClient(envVars.GetCredentials(), provider.TerraformVersion) + client, err := configClient(providerVars, provider.TerraformVersion) if err != nil { return nil, diag.FromErr(fmt.Errorf("error initializing provider: %w", err)) } - return client, diagnostics + return client, nil } - - // TODO gov look former code } // TODO: implement this From 104fb2527fedc5d9bc40a48cdba564e07d8e0cb0 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 07:17:41 +0200 Subject: [PATCH 24/41] refactor Realm client --- internal/config/client.go | 68 ++++++++++--------- internal/provider/provider_sdk2.go | 1 - .../eventtrigger/data_source_event_trigger.go | 2 +- .../data_source_event_triggers.go | 2 +- .../eventtrigger/resource_event_trigger.go | 11 ++- .../resource_event_trigger_test.go | 4 +- .../organization/resource_organization.go | 5 +- .../resource_organization_test.go | 4 +- 8 files changed, 48 insertions(+), 49 deletions(-) diff --git a/internal/config/client.go b/internal/config/client.go index a065e5d227..c4d51912c7 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -73,26 +73,22 @@ func tfLoggingInterceptor(base http.RoundTripper) http.RoundTripper { // MongoDBClient contains the mongodbatlas clients and configurations type MongoDBClient struct { - Atlas *matlasClient.Client - AtlasV2 *admin.APIClient - AtlasPreview *adminpreview.APIClient - AtlasV220240805 *admin20240805.APIClient // used in advanced_cluster to avoid adopting 2024-10-23 release with ISS autoscaling - AtlasV220240530 *admin20240530.APIClient // used in advanced_cluster and cloud_backup_schedule for avoiding breaking changes (supporting deprecated replication_specs.id) - AtlasV220241113 *admin20241113.APIClient // used in teams and atlas_users to avoiding breaking changes - Config *Config + Atlas *matlasClient.Client + AtlasV2 *admin.APIClient + AtlasPreview *adminpreview.APIClient + AtlasV220240805 *admin20240805.APIClient // used in advanced_cluster to avoid adopting 2024-10-23 release with ISS autoscaling + AtlasV220240530 *admin20240530.APIClient // used in advanced_cluster and cloud_backup_schedule for avoiding breaking changes (supporting deprecated replication_specs.id) + AtlasV220241113 *admin20241113.APIClient // used in teams and atlas_users to avoiding breaking changes + Realm *RealmClient + BaseURL string // neeeded by organization resource + TerraformVersion string // neeeded by organization resource } -// Config contains the configurations needed to use SDKs -type Config struct { - AssumeRoleARN string - PublicKey string - PrivateKey string - BaseURL string - RealmBaseURL string - TerraformVersion string - ClientID string - ClientSecret string - AccessToken string +type RealmClient struct { + publicKey string + privateKey string + realmBaseURL string + terraformVersion string } func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) { @@ -135,13 +131,20 @@ func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) } clients := &MongoDBClient{ - Atlas: atlasClient, - AtlasV2: sdkV2Client, - AtlasPreview: sdkPreviewClient, - AtlasV220240530: sdkV220240530Client, - AtlasV220240805: sdkV220240805Client, - AtlasV220241113: sdkV220241113Client, - // TODO: Config: c, + Atlas: atlasClient, + AtlasV2: sdkV2Client, + AtlasPreview: sdkPreviewClient, + AtlasV220240530: sdkV220240530Client, + AtlasV220240805: sdkV220240805Client, + AtlasV220241113: sdkV220241113Client, + BaseURL: c.BaseURL, + TerraformVersion: terraformVersion, + Realm: &RealmClient{ + publicKey: c.PublicKey, + privateKey: c.PrivateKey, + realmBaseURL: c.RealmBaseURL, + terraformVersion: terraformVersion, + }, } return clients, nil } @@ -219,25 +222,24 @@ func newSDKV220241113Client(client *http.Client, baseURL, userAgent string) (*ad ) } -// TODO: lazy because it needs connection -func (c *MongoDBClient) GetRealmClient(ctx context.Context) (*realm.Client, error) { - // Realm - if c.Config.PublicKey == "" && c.Config.PrivateKey == "" { +// Get in RealmClient is a method instead of Atlas fields so it's lazy initialized as it needs a roundtrip to authenticate. +func (r *RealmClient) Get(ctx context.Context) (*realm.Client, error) { + if r.publicKey == "" && r.privateKey == "" { return nil, errors.New("please set `public_key` and `private_key` in order to use the realm client") } optsRealm := []realm.ClientOpt{ - realm.SetUserAgent(userAgent(c.Config.TerraformVersion)), + realm.SetUserAgent(userAgent(r.terraformVersion)), } authConfig := realmAuth.NewConfig(nil) - if c.Config.BaseURL != "" && c.Config.RealmBaseURL != "" { - adminURL := c.Config.RealmBaseURL + "api/admin/v3.0/" + if r.realmBaseURL != "" { + adminURL := r.realmBaseURL + "api/admin/v3.0/" optsRealm = append(optsRealm, realm.SetBaseURL(adminURL)) authConfig.AuthURL, _ = url.Parse(adminURL + "auth/providers/mongodb-cloud/login") } - token, err := authConfig.NewTokenFromCredentials(ctx, c.Config.PublicKey, c.Config.PrivateKey) + token, err := authConfig.NewTokenFromCredentials(ctx, r.publicKey, r.privateKey) if err != nil { return nil, err } diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 045c1a9d8f..8fe123b2b0 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -305,7 +305,6 @@ func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *s } } -// TODO: implement this func getSDKv2ProviderVars(d *schema.ResourceData) *config.Vars { assumeRoleARN := "" assumeRoles := d.Get("assume_role").([]any) diff --git a/internal/service/eventtrigger/data_source_event_trigger.go b/internal/service/eventtrigger/data_source_event_trigger.go index 5cf8c318ff..bac40a4d0a 100644 --- a/internal/service/eventtrigger/data_source_event_trigger.go +++ b/internal/service/eventtrigger/data_source_event_trigger.go @@ -133,7 +133,7 @@ func DataSource() *schema.Resource { } func dataSourceMongoDBAtlasEventTriggerRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - conn, err := meta.(*config.MongoDBClient).GetRealmClient(ctx) + conn, err := meta.(*config.MongoDBClient).Realm.Get(ctx) if err != nil { return diag.FromErr(err) } diff --git a/internal/service/eventtrigger/data_source_event_triggers.go b/internal/service/eventtrigger/data_source_event_triggers.go index 120b42bdca..9fa885cafd 100644 --- a/internal/service/eventtrigger/data_source_event_triggers.go +++ b/internal/service/eventtrigger/data_source_event_triggers.go @@ -146,7 +146,7 @@ func PluralDataSource() *schema.Resource { func dataSourceMongoDBAtlasEventTriggersRead(d *schema.ResourceData, meta any) error { // Get client connection. ctx := context.Background() - conn, err := meta.(*config.MongoDBClient).GetRealmClient(ctx) + conn, err := meta.(*config.MongoDBClient).Realm.Get(ctx) if err != nil { return err } diff --git a/internal/service/eventtrigger/resource_event_trigger.go b/internal/service/eventtrigger/resource_event_trigger.go index b1e99aee41..1a90c8f3ab 100644 --- a/internal/service/eventtrigger/resource_event_trigger.go +++ b/internal/service/eventtrigger/resource_event_trigger.go @@ -210,7 +210,7 @@ func Resource() *schema.Resource { } func resourceMongoDBAtlasEventTriggersCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - conn, err := meta.(*config.MongoDBClient).GetRealmClient(ctx) + conn, err := meta.(*config.MongoDBClient).Realm.Get(ctx) if err != nil { return diag.FromErr(err) } @@ -312,7 +312,7 @@ func resourceMongoDBAtlasEventTriggersCreate(ctx context.Context, d *schema.Reso } func resourceMongoDBAtlasEventTriggersRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - conn, err := meta.(*config.MongoDBClient).GetRealmClient(ctx) + conn, err := meta.(*config.MongoDBClient).Realm.Get(ctx) if err != nil { return diag.FromErr(err) } @@ -402,7 +402,7 @@ func resourceMongoDBAtlasEventTriggersRead(ctx context.Context, d *schema.Resour } func resourceMongoDBAtlasEventTriggersUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - conn, err := meta.(*config.MongoDBClient).GetRealmClient(ctx) + conn, err := meta.(*config.MongoDBClient).Realm.Get(ctx) if err != nil { return diag.FromErr(err) } @@ -453,8 +453,7 @@ func resourceMongoDBAtlasEventTriggersUpdate(ctx context.Context, d *schema.Reso } func resourceMongoDBAtlasEventTriggersDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - // Get the client connection. - conn, err := meta.(*config.MongoDBClient).GetRealmClient(ctx) + conn, err := meta.(*config.MongoDBClient).Realm.Get(ctx) if err != nil { return diag.FromErr(err) } @@ -515,7 +514,7 @@ func flattenTriggerEventProcessorAWSEventBridge(eventProcessor map[string]any) [ } func resourceMongoDBAtlasEventTriggerImportState(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - conn, err := meta.(*config.MongoDBClient).GetRealmClient(ctx) + conn, err := meta.(*config.MongoDBClient).Realm.Get(ctx) if err != nil { return nil, err } diff --git a/internal/service/eventtrigger/resource_event_trigger_test.go b/internal/service/eventtrigger/resource_event_trigger_test.go index 30f2c2734d..865690c6a7 100644 --- a/internal/service/eventtrigger/resource_event_trigger_test.go +++ b/internal/service/eventtrigger/resource_event_trigger_test.go @@ -484,7 +484,7 @@ func TestAccEventTrigger_functionBasic(t *testing.T) { func checkExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() - conn, err := acc.MongoDBClient.GetRealmClient(ctx) + conn, err := acc.MongoDBClient.Realm.Get(ctx) if err != nil { return err } @@ -513,7 +513,7 @@ func checkExists(resourceName string) resource.TestCheckFunc { func checkDestroy(s *terraform.State) error { ctx := context.Background() - conn, err := acc.MongoDBClient.GetRealmClient(ctx) + conn, err := acc.MongoDBClient.Realm.Get(ctx) if err != nil { return err } diff --git a/internal/service/organization/resource_organization.go b/internal/service/organization/resource_organization.go index fef71c6e80..0cd4800033 100644 --- a/internal/service/organization/resource_organization.go +++ b/internal/service/organization/resource_organization.go @@ -303,10 +303,9 @@ func getAtlasV2Connection(ctx context.Context, d *schema.ResourceData, meta any) c := &config.Credentials{ PublicKey: publicKey, PrivateKey: privateKey, - BaseURL: currentClient.Config.BaseURL, + BaseURL: currentClient.BaseURL, } - terraformVersion := currentClient.Config.TerraformVersion - newClient, err := config.NewClient(c, terraformVersion) + newClient, err := config.NewClient(c, currentClient.TerraformVersion) if err != nil { return currentClient.AtlasV2 } diff --git a/internal/service/organization/resource_organization_test.go b/internal/service/organization/resource_organization_test.go index acb6f29e13..7402c219c0 100644 --- a/internal/service/organization/resource_organization_test.go +++ b/internal/service/organization/resource_organization_test.go @@ -433,9 +433,9 @@ func getTestClientWithNewOrgCreds(rs *terraform.ResourceState) (*admin.APIClient c := &config.Credentials{ PublicKey: rs.Primary.Attributes["public_key"], PrivateKey: rs.Primary.Attributes["private_key"], - BaseURL: acc.MongoDBClient.Config.BaseURL, + BaseURL: acc.MongoDBClient.BaseURL, } - client, _ := config.NewClient(c, "") + client, _ := config.NewClient(c, acc.MongoDBClient.TerraformVersion) return client.AtlasV2, nil } From 8b2415f86ec513a12ab07c6bd5f02a7bf8ae2c96 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 07:25:50 +0200 Subject: [PATCH 25/41] remove Atlas version 220240805 --- go.mod | 3 +- go.sum | 2 - internal/config/client.go | 16 -- .../common_model_sdk_version_conversion.go | 256 ------------------ ...ommon_model_sdk_version_conversion_test.go | 193 ------------- 5 files changed, 1 insertion(+), 469 deletions(-) delete mode 100644 internal/service/advancedcluster/common_model_sdk_version_conversion.go delete mode 100644 internal/service/advancedcluster/common_model_sdk_version_conversion_test.go diff --git a/go.mod b/go.mod index 65919a5b6f..94a45aca3f 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/zclconf/go-cty v1.17.0 go.mongodb.org/atlas v0.38.0 go.mongodb.org/atlas-sdk/v20240530005 v20240530005.0.0 - go.mongodb.org/atlas-sdk/v20240805005 v20240805005.0.1-0.20250402112219-2468c5354718 // uses api-bot-update-v20240805-backport-cluster to support AdvancedConfiguration in create/updateCluster APIs go.mongodb.org/atlas-sdk/v20241113005 v20241113005.0.0 go.mongodb.org/realm v0.1.0 gopkg.in/yaml.v3 v3.0.1 @@ -43,6 +42,7 @@ require ( github.com/hashicorp/terraform-json v0.27.2 github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 go.mongodb.org/atlas-sdk/v20250312007 v20250312007.0.0 + golang.org/x/oauth2 v0.31.0 ) require ( @@ -163,7 +163,6 @@ require ( golang.org/x/crypto v0.42.0 // indirect golang.org/x/mod v0.27.0 // indirect golang.org/x/net v0.43.0 // indirect - golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect diff --git a/go.sum b/go.sum index b3fb988a3c..cee9c557df 100644 --- a/go.sum +++ b/go.sum @@ -1364,8 +1364,6 @@ go.mongodb.org/atlas v0.38.0 h1:zfwymq20GqivGwxPZfypfUDry+WwMGVui97z1d8V4bU= go.mongodb.org/atlas v0.38.0/go.mod h1:DJYtM+vsEpPEMSkQzJnFHrT0sP7ev6cseZc/GGjJYG8= go.mongodb.org/atlas-sdk/v20240530005 v20240530005.0.0 h1:d/gbYJ+obR0EM/3DZf7+ZMi2QWISegm3mid7Or708cc= go.mongodb.org/atlas-sdk/v20240530005 v20240530005.0.0/go.mod h1:O47ZrMMfcWb31wznNIq2PQkkdoFoK0ea2GlmRqGJC2s= -go.mongodb.org/atlas-sdk/v20240805005 v20240805005.0.1-0.20250402112219-2468c5354718 h1:M2mNSBdTkP+paQ1qZ6FliiPdTEbDR9m9qvv4vsWoJAw= -go.mongodb.org/atlas-sdk/v20240805005 v20240805005.0.1-0.20250402112219-2468c5354718/go.mod h1:PeByRxdvzfvz7xhG5vDn60j836EoduWqTqs76okUc9c= go.mongodb.org/atlas-sdk/v20241113005 v20241113005.0.0 h1:aaU2E4rtzYXuEDxv9MoSON2gOEAA9M2gsDf2CqjcGj8= go.mongodb.org/atlas-sdk/v20241113005 v20241113005.0.0/go.mod h1:eV9REWR36iVMrpZUAMZ5qPbXEatoVfmzwT+Ue8yqU+U= go.mongodb.org/atlas-sdk/v20250312007 v20250312007.0.0 h1:2k6eXWQzTpbc/maZotRIoyXq3l/pbCF1RBMt+WnuB0I= diff --git a/internal/config/client.go b/internal/config/client.go index c4d51912c7..bab77c1c95 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -11,7 +11,6 @@ import ( "time" admin20240530 "go.mongodb.org/atlas-sdk/v20240530005/admin" - admin20240805 "go.mongodb.org/atlas-sdk/v20240805005/admin" admin20241113 "go.mongodb.org/atlas-sdk/v20241113005/admin" "go.mongodb.org/atlas-sdk/v20250312007/admin" matlasClient "go.mongodb.org/atlas/mongodbatlas" @@ -76,7 +75,6 @@ type MongoDBClient struct { Atlas *matlasClient.Client AtlasV2 *admin.APIClient AtlasPreview *adminpreview.APIClient - AtlasV220240805 *admin20240805.APIClient // used in advanced_cluster to avoid adopting 2024-10-23 release with ISS autoscaling AtlasV220240530 *admin20240530.APIClient // used in advanced_cluster and cloud_backup_schedule for avoiding breaking changes (supporting deprecated replication_specs.id) AtlasV220241113 *admin20241113.APIClient // used in teams and atlas_users to avoiding breaking changes Realm *RealmClient @@ -121,10 +119,6 @@ func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) if err != nil { return nil, err } - sdkV220240805Client, err := newSDKV220240805Client(client, c.BaseURL, userAgent) - if err != nil { - return nil, err - } sdkV220241113Client, err := newSDKV220241113Client(client, c.BaseURL, userAgent) if err != nil { return nil, err @@ -135,7 +129,6 @@ func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) AtlasV2: sdkV2Client, AtlasPreview: sdkPreviewClient, AtlasV220240530: sdkV220240530Client, - AtlasV220240805: sdkV220240805Client, AtlasV220241113: sdkV220241113Client, BaseURL: c.BaseURL, TerraformVersion: terraformVersion, @@ -204,15 +197,6 @@ func newSDKV220240530Client(client *http.Client, baseURL, userAgent string) (*ad ) } -func newSDKV220240805Client(client *http.Client, baseURL, userAgent string) (*admin20240805.APIClient, error) { - return admin20240805.NewClient( - admin20240805.UseHTTPClient(client), - admin20240805.UseUserAgent(userAgent), - admin20240805.UseBaseURL(baseURL), - admin20240805.UseDebug(false), - ) -} - func newSDKV220241113Client(client *http.Client, baseURL, userAgent string) (*admin20241113.APIClient, error) { return admin20241113.NewClient( admin20241113.UseHTTPClient(client), diff --git a/internal/service/advancedcluster/common_model_sdk_version_conversion.go b/internal/service/advancedcluster/common_model_sdk_version_conversion.go deleted file mode 100644 index 137e755e77..0000000000 --- a/internal/service/advancedcluster/common_model_sdk_version_conversion.go +++ /dev/null @@ -1,256 +0,0 @@ -package advancedcluster - -import ( - admin20240530 "go.mongodb.org/atlas-sdk/v20240530005/admin" - admin20240805 "go.mongodb.org/atlas-sdk/v20240805005/admin" - "go.mongodb.org/atlas-sdk/v20250312007/admin" -) - -// Conversions from one SDK model version to another are used to avoid duplicating our flatten/expand conversion functions. -// - These functions must not contain any business logic. -// - All will be removed once we rely on a single API version. - -func ConvertClusterDescription20241023to20240805(clusterDescription *admin.ClusterDescription20240805) *admin20240805.ClusterDescription20240805 { - return &admin20240805.ClusterDescription20240805{ - Name: clusterDescription.Name, - ClusterType: clusterDescription.ClusterType, - ReplicationSpecs: convertReplicationSpecs20241023to20240805(clusterDescription.ReplicationSpecs), - BackupEnabled: clusterDescription.BackupEnabled, - BiConnector: convertBiConnector20241023to20240805(clusterDescription.BiConnector), - EncryptionAtRestProvider: clusterDescription.EncryptionAtRestProvider, - Labels: convertLabels20241023to20240805(clusterDescription.Labels), - Tags: convertTag20241023to20240805(clusterDescription.Tags), - MongoDBMajorVersion: clusterDescription.MongoDBMajorVersion, - PitEnabled: clusterDescription.PitEnabled, - RootCertType: clusterDescription.RootCertType, - TerminationProtectionEnabled: clusterDescription.TerminationProtectionEnabled, - VersionReleaseSystem: clusterDescription.VersionReleaseSystem, - GlobalClusterSelfManagedSharding: clusterDescription.GlobalClusterSelfManagedSharding, - ReplicaSetScalingStrategy: clusterDescription.ReplicaSetScalingStrategy, - RedactClientLogData: clusterDescription.RedactClientLogData, - ConfigServerManagementMode: clusterDescription.ConfigServerManagementMode, - AdvancedConfiguration: convertAdvancedConfiguration20250312to20240805(clusterDescription.AdvancedConfiguration), - } -} - -func convertReplicationSpecs20241023to20240805(replicationSpecs *[]admin.ReplicationSpec20240805) *[]admin20240805.ReplicationSpec20240805 { - if replicationSpecs == nil { - return nil - } - result := make([]admin20240805.ReplicationSpec20240805, len(*replicationSpecs)) - for i, replicationSpec := range *replicationSpecs { - result[i] = admin20240805.ReplicationSpec20240805{ - Id: replicationSpec.Id, - ZoneName: replicationSpec.ZoneName, - ZoneId: replicationSpec.ZoneId, - RegionConfigs: convertCloudRegionConfig20241023to20240805(replicationSpec.RegionConfigs), - } - } - return &result -} - -func convertCloudRegionConfig20241023to20240805(cloudRegionConfig *[]admin.CloudRegionConfig20240805) *[]admin20240805.CloudRegionConfig20240805 { - if cloudRegionConfig == nil { - return nil - } - result := make([]admin20240805.CloudRegionConfig20240805, len(*cloudRegionConfig)) - for i, regionConfig := range *cloudRegionConfig { - result[i] = admin20240805.CloudRegionConfig20240805{ - ProviderName: regionConfig.ProviderName, - RegionName: regionConfig.RegionName, - BackingProviderName: regionConfig.BackingProviderName, - Priority: regionConfig.Priority, - ElectableSpecs: convertHardwareSpec20241023to20240805(regionConfig.ElectableSpecs), - ReadOnlySpecs: convertDedicatedHardwareSpec20241023to20240805(regionConfig.ReadOnlySpecs), - AnalyticsSpecs: convertDedicatedHardwareSpec20241023to20240805(regionConfig.AnalyticsSpecs), - AutoScaling: convertAdvancedAutoScalingSettings20241023to20240805(regionConfig.AutoScaling), - AnalyticsAutoScaling: convertAdvancedAutoScalingSettings20241023to20240805(regionConfig.AnalyticsAutoScaling), - } - } - return &result -} - -func convertAdvancedAutoScalingSettings20241023to20240805(advancedAutoScalingSettings *admin.AdvancedAutoScalingSettings) *admin20240805.AdvancedAutoScalingSettings { - if advancedAutoScalingSettings == nil { - return nil - } - return &admin20240805.AdvancedAutoScalingSettings{ - Compute: convertAdvancedComputeAutoScaling20241023to20240805(advancedAutoScalingSettings.Compute), - DiskGB: convertDiskGBAutoScaling20241023to20240805(advancedAutoScalingSettings.DiskGB), - } -} - -func convertDiskGBAutoScaling20241023to20240805(diskGBAutoScaling *admin.DiskGBAutoScaling) *admin20240805.DiskGBAutoScaling { - if diskGBAutoScaling == nil { - return nil - } - return &admin20240805.DiskGBAutoScaling{ - Enabled: diskGBAutoScaling.Enabled, - } -} - -func convertAdvancedComputeAutoScaling20241023to20240805(advancedComputeAutoScaling *admin.AdvancedComputeAutoScaling) *admin20240805.AdvancedComputeAutoScaling { - if advancedComputeAutoScaling == nil { - return nil - } - return &admin20240805.AdvancedComputeAutoScaling{ - Enabled: advancedComputeAutoScaling.Enabled, - MaxInstanceSize: advancedComputeAutoScaling.MaxInstanceSize, - MinInstanceSize: advancedComputeAutoScaling.MinInstanceSize, - ScaleDownEnabled: advancedComputeAutoScaling.ScaleDownEnabled, - } -} - -func convertHardwareSpec20241023to20240805(hardwareSpec *admin.HardwareSpec20240805) *admin20240805.HardwareSpec20240805 { - if hardwareSpec == nil { - return nil - } - return &admin20240805.HardwareSpec20240805{ - DiskSizeGB: hardwareSpec.DiskSizeGB, - NodeCount: hardwareSpec.NodeCount, - DiskIOPS: hardwareSpec.DiskIOPS, - EbsVolumeType: hardwareSpec.EbsVolumeType, - InstanceSize: hardwareSpec.InstanceSize, - } -} - -func convertDedicatedHardwareSpec20241023to20240805(hardwareSpec *admin.DedicatedHardwareSpec20240805) *admin20240805.DedicatedHardwareSpec20240805 { - if hardwareSpec == nil { - return nil - } - return &admin20240805.DedicatedHardwareSpec20240805{ - DiskSizeGB: hardwareSpec.DiskSizeGB, - NodeCount: hardwareSpec.NodeCount, - DiskIOPS: hardwareSpec.DiskIOPS, - EbsVolumeType: hardwareSpec.EbsVolumeType, - InstanceSize: hardwareSpec.InstanceSize, - } -} - -func convertBiConnector20241023to20240805(biConnector *admin.BiConnector) *admin20240805.BiConnector { - if biConnector == nil { - return nil - } - return &admin20240805.BiConnector{ - ReadPreference: biConnector.ReadPreference, - Enabled: biConnector.Enabled, - } -} - -func convertAdvancedConfiguration20250312to20240805(advConfig *admin.ApiAtlasClusterAdvancedConfiguration) *admin20240805.ApiAtlasClusterAdvancedConfiguration { - if advConfig == nil { - return nil - } - - return &admin20240805.ApiAtlasClusterAdvancedConfiguration{ - MinimumEnabledTlsProtocol: advConfig.MinimumEnabledTlsProtocol, - CustomOpensslCipherConfigTls12: advConfig.CustomOpensslCipherConfigTls12, - TlsCipherConfigMode: advConfig.TlsCipherConfigMode, - } -} - -func convertLabels20241023to20240805(labels *[]admin.ComponentLabel) *[]admin20240805.ComponentLabel { - if labels == nil { - return nil - } - result := make([]admin20240805.ComponentLabel, len(*labels)) - for i, label := range *labels { - result[i] = admin20240805.ComponentLabel{ - Key: label.Key, - Value: label.Value, - } - } - return &result -} - -func convertTag20241023to20240805(tags *[]admin.ResourceTag) *[]admin20240805.ResourceTag { - if tags == nil { - return nil - } - result := make([]admin20240805.ResourceTag, len(*tags)) - for i, tag := range *tags { - result[i] = admin20240805.ResourceTag{ - Key: tag.Key, - Value: tag.Value, - } - } - return &result -} - -func ConvertRegionConfigSlice20241023to20240530(slice *[]admin.CloudRegionConfig20240805) *[]admin20240530.CloudRegionConfig { - if slice == nil { - return nil - } - cloudRegionSlice := *slice - results := make([]admin20240530.CloudRegionConfig, len(cloudRegionSlice)) - for i := range cloudRegionSlice { - cloudRegion := cloudRegionSlice[i] - results[i] = admin20240530.CloudRegionConfig{ - ElectableSpecs: convertHardwareSpec20241023to20240530(cloudRegion.ElectableSpecs), - Priority: cloudRegion.Priority, - ProviderName: cloudRegion.ProviderName, - RegionName: cloudRegion.RegionName, - AnalyticsAutoScaling: convertAdvancedAutoScalingSettings20241023to20240530(cloudRegion.AnalyticsAutoScaling), - AnalyticsSpecs: convertDedicatedHardwareSpec20241023to20240530(cloudRegion.AnalyticsSpecs), - AutoScaling: convertAdvancedAutoScalingSettings20241023to20240530(cloudRegion.AutoScaling), - ReadOnlySpecs: convertDedicatedHardwareSpec20241023to20240530(cloudRegion.ReadOnlySpecs), - BackingProviderName: cloudRegion.BackingProviderName, - } - } - return &results -} - -func convertHardwareSpec20241023to20240530(hwspec *admin.HardwareSpec20240805) *admin20240530.HardwareSpec { - if hwspec == nil { - return nil - } - return &admin20240530.HardwareSpec{ - DiskIOPS: hwspec.DiskIOPS, - EbsVolumeType: hwspec.EbsVolumeType, - InstanceSize: hwspec.InstanceSize, - NodeCount: hwspec.NodeCount, - } -} - -func convertAdvancedAutoScalingSettings20241023to20240530(settings *admin.AdvancedAutoScalingSettings) *admin20240530.AdvancedAutoScalingSettings { - if settings == nil { - return nil - } - return &admin20240530.AdvancedAutoScalingSettings{ - Compute: convertAdvancedComputeAutoScaling20241023to20240530(settings.Compute), - DiskGB: convertDiskGBAutoScaling20241023to20240530(settings.DiskGB), - } -} - -func convertAdvancedComputeAutoScaling20241023to20240530(settings *admin.AdvancedComputeAutoScaling) *admin20240530.AdvancedComputeAutoScaling { - if settings == nil { - return nil - } - return &admin20240530.AdvancedComputeAutoScaling{ - Enabled: settings.Enabled, - MaxInstanceSize: settings.MaxInstanceSize, - MinInstanceSize: settings.MinInstanceSize, - ScaleDownEnabled: settings.ScaleDownEnabled, - } -} - -func convertDiskGBAutoScaling20241023to20240530(settings *admin.DiskGBAutoScaling) *admin20240530.DiskGBAutoScaling { - if settings == nil { - return nil - } - return &admin20240530.DiskGBAutoScaling{ - Enabled: settings.Enabled, - } -} - -func convertDedicatedHardwareSpec20241023to20240530(spec *admin.DedicatedHardwareSpec20240805) *admin20240530.DedicatedHardwareSpec { - if spec == nil { - return nil - } - return &admin20240530.DedicatedHardwareSpec{ - NodeCount: spec.NodeCount, - DiskIOPS: spec.DiskIOPS, - EbsVolumeType: spec.EbsVolumeType, - InstanceSize: spec.InstanceSize, - } -} diff --git a/internal/service/advancedcluster/common_model_sdk_version_conversion_test.go b/internal/service/advancedcluster/common_model_sdk_version_conversion_test.go deleted file mode 100644 index 88fa992c63..0000000000 --- a/internal/service/advancedcluster/common_model_sdk_version_conversion_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package advancedcluster_test - -import ( - "testing" - - "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" - "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/advancedcluster" - "github.com/stretchr/testify/assert" - admin20240805 "go.mongodb.org/atlas-sdk/v20240805005/admin" - "go.mongodb.org/atlas-sdk/v20250312007/admin" -) - -func TestConvertClusterDescription20241023to20240805(t *testing.T) { - var ( - clusterName = "clusterName" - clusterType = "REPLICASET" - earProvider = "AWS" - booleanValue = true - mongoDBMajorVersion = "7.0" - rootCertType = "rootCertType" - replicaSetScalingStrategy = "WORKLOAD_TYPE" - configServerManagementMode = "ATLAS_MANAGED" - readPreference = "primary" - zoneName = "z1" - id = "id1" - regionConfigProvider = "AWS" - region = "EU_WEST_1" - priority = 7 - instanceSize = "M10" - nodeCount = 3 - diskSizeGB = 30.3 - ebsVolumeType = "STANDARD" - diskIOPS = 100 - ) - testCases := []struct { - input *admin.ClusterDescription20240805 - expectedOutput *admin20240805.ClusterDescription20240805 - name string - }{ - { - name: "Converts cluster description from 20241023 to 20240805", - input: &admin.ClusterDescription20240805{ - Name: conversion.StringPtr(clusterName), - ClusterType: conversion.StringPtr(clusterType), - ReplicationSpecs: &[]admin.ReplicationSpec20240805{ - { - Id: conversion.StringPtr(id), - ZoneName: conversion.StringPtr(zoneName), - RegionConfigs: &[]admin.CloudRegionConfig20240805{ - { - ProviderName: conversion.StringPtr(regionConfigProvider), - RegionName: conversion.StringPtr(region), - BackingProviderName: conversion.StringPtr(regionConfigProvider), - Priority: conversion.IntPtr(priority), - AnalyticsSpecs: &admin.DedicatedHardwareSpec20240805{ - InstanceSize: conversion.StringPtr(instanceSize), - NodeCount: conversion.IntPtr(nodeCount), - DiskSizeGB: conversion.Pointer(diskSizeGB), - EbsVolumeType: conversion.StringPtr(ebsVolumeType), - DiskIOPS: conversion.IntPtr(diskIOPS), - }, - ElectableSpecs: &admin.HardwareSpec20240805{ - InstanceSize: conversion.StringPtr(instanceSize), - NodeCount: conversion.IntPtr(nodeCount), - DiskSizeGB: conversion.Pointer(diskSizeGB), - EbsVolumeType: conversion.StringPtr(ebsVolumeType), - DiskIOPS: conversion.IntPtr(diskIOPS), - }, - AutoScaling: &admin.AdvancedAutoScalingSettings{ - Compute: &admin.AdvancedComputeAutoScaling{ - Enabled: conversion.Pointer(booleanValue), - MaxInstanceSize: conversion.Pointer(instanceSize), - MinInstanceSize: conversion.Pointer(instanceSize), - ScaleDownEnabled: conversion.Pointer(booleanValue), - }, - DiskGB: &admin.DiskGBAutoScaling{ - Enabled: conversion.Pointer(booleanValue), - }, - }, - }, - }, - }, - }, - BackupEnabled: conversion.Pointer(booleanValue), - BiConnector: &admin.BiConnector{ - Enabled: conversion.Pointer(booleanValue), - ReadPreference: conversion.StringPtr(readPreference), - }, - EncryptionAtRestProvider: conversion.StringPtr(earProvider), - Labels: &[]admin.ComponentLabel{ - {Key: conversion.StringPtr("key1"), Value: conversion.StringPtr("value1")}, - {Key: conversion.StringPtr("key2"), Value: conversion.StringPtr("value2")}, - }, - Tags: &[]admin.ResourceTag{ - {Key: "key1", Value: "value1"}, - {Key: "key2", Value: "value2"}, - }, - MongoDBMajorVersion: conversion.StringPtr(mongoDBMajorVersion), - PitEnabled: conversion.Pointer(booleanValue), - RootCertType: conversion.StringPtr(rootCertType), - TerminationProtectionEnabled: conversion.Pointer(booleanValue), - VersionReleaseSystem: conversion.StringPtr(""), - GlobalClusterSelfManagedSharding: conversion.Pointer(booleanValue), - ReplicaSetScalingStrategy: conversion.StringPtr(replicaSetScalingStrategy), - RedactClientLogData: conversion.Pointer(booleanValue), - ConfigServerManagementMode: conversion.StringPtr(configServerManagementMode), - }, - expectedOutput: &admin20240805.ClusterDescription20240805{ - Name: conversion.StringPtr(clusterName), - ClusterType: conversion.StringPtr(clusterType), - ReplicationSpecs: &[]admin20240805.ReplicationSpec20240805{ - { - Id: conversion.StringPtr(id), - ZoneName: conversion.StringPtr(zoneName), - RegionConfigs: &[]admin20240805.CloudRegionConfig20240805{ - { - ProviderName: conversion.StringPtr(regionConfigProvider), - RegionName: conversion.StringPtr(region), - BackingProviderName: conversion.StringPtr(regionConfigProvider), - Priority: conversion.IntPtr(priority), - AnalyticsSpecs: &admin20240805.DedicatedHardwareSpec20240805{ - InstanceSize: conversion.StringPtr(instanceSize), - NodeCount: conversion.IntPtr(nodeCount), - DiskSizeGB: conversion.Pointer(diskSizeGB), - EbsVolumeType: conversion.StringPtr(ebsVolumeType), - DiskIOPS: conversion.IntPtr(diskIOPS), - }, - ElectableSpecs: &admin20240805.HardwareSpec20240805{ - InstanceSize: conversion.StringPtr(instanceSize), - NodeCount: conversion.IntPtr(nodeCount), - DiskSizeGB: conversion.Pointer(diskSizeGB), - EbsVolumeType: conversion.StringPtr(ebsVolumeType), - DiskIOPS: conversion.IntPtr(diskIOPS), - }, - AutoScaling: &admin20240805.AdvancedAutoScalingSettings{ - Compute: &admin20240805.AdvancedComputeAutoScaling{ - Enabled: conversion.Pointer(booleanValue), - MaxInstanceSize: conversion.Pointer(instanceSize), - MinInstanceSize: conversion.Pointer(instanceSize), - ScaleDownEnabled: conversion.Pointer(booleanValue), - }, - DiskGB: &admin20240805.DiskGBAutoScaling{ - Enabled: conversion.Pointer(booleanValue), - }, - }, - }, - }, - }, - }, - BackupEnabled: conversion.Pointer(booleanValue), - BiConnector: &admin20240805.BiConnector{ - Enabled: conversion.Pointer(booleanValue), - ReadPreference: conversion.StringPtr(readPreference), - }, - EncryptionAtRestProvider: conversion.StringPtr(earProvider), - Labels: &[]admin20240805.ComponentLabel{ - {Key: conversion.StringPtr("key1"), Value: conversion.StringPtr("value1")}, - {Key: conversion.StringPtr("key2"), Value: conversion.StringPtr("value2")}, - }, - Tags: &[]admin20240805.ResourceTag{ - {Key: "key1", Value: "value1"}, - {Key: "key2", Value: "value2"}, - }, - MongoDBMajorVersion: conversion.StringPtr(mongoDBMajorVersion), - PitEnabled: conversion.Pointer(booleanValue), - RootCertType: conversion.StringPtr(rootCertType), - TerminationProtectionEnabled: conversion.Pointer(booleanValue), - VersionReleaseSystem: conversion.StringPtr(""), - GlobalClusterSelfManagedSharding: conversion.Pointer(booleanValue), - ReplicaSetScalingStrategy: conversion.StringPtr(replicaSetScalingStrategy), - RedactClientLogData: conversion.Pointer(booleanValue), - ConfigServerManagementMode: conversion.StringPtr(configServerManagementMode), - }, - }, - { - name: "Converts cluster description from 20241023 to 20240805 with nil values", - input: &admin.ClusterDescription20240805{}, - expectedOutput: &admin20240805.ClusterDescription20240805{ - ReplicationSpecs: nil, - BiConnector: nil, - Labels: nil, - Tags: nil, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result := advancedcluster.ConvertClusterDescription20241023to20240805(tc.input) - assert.Equal(t, tc.expectedOutput, result) - }) - } -} From 0dc3ab4ab83e7922b554123ce70b1d8ae312f1ea Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 07:30:20 +0200 Subject: [PATCH 26/41] Revert "remove Atlas version 220240805" This reverts commit 8b2415f86ec513a12ab07c6bd5f02a7bf8ae2c96. --- go.mod | 3 +- go.sum | 2 + internal/config/client.go | 16 ++ .../common_model_sdk_version_conversion.go | 256 ++++++++++++++++++ ...ommon_model_sdk_version_conversion_test.go | 193 +++++++++++++ 5 files changed, 469 insertions(+), 1 deletion(-) create mode 100644 internal/service/advancedcluster/common_model_sdk_version_conversion.go create mode 100644 internal/service/advancedcluster/common_model_sdk_version_conversion_test.go diff --git a/go.mod b/go.mod index 94a45aca3f..65919a5b6f 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/zclconf/go-cty v1.17.0 go.mongodb.org/atlas v0.38.0 go.mongodb.org/atlas-sdk/v20240530005 v20240530005.0.0 + go.mongodb.org/atlas-sdk/v20240805005 v20240805005.0.1-0.20250402112219-2468c5354718 // uses api-bot-update-v20240805-backport-cluster to support AdvancedConfiguration in create/updateCluster APIs go.mongodb.org/atlas-sdk/v20241113005 v20241113005.0.0 go.mongodb.org/realm v0.1.0 gopkg.in/yaml.v3 v3.0.1 @@ -42,7 +43,6 @@ require ( github.com/hashicorp/terraform-json v0.27.2 github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 go.mongodb.org/atlas-sdk/v20250312007 v20250312007.0.0 - golang.org/x/oauth2 v0.31.0 ) require ( @@ -163,6 +163,7 @@ require ( golang.org/x/crypto v0.42.0 // indirect golang.org/x/mod v0.27.0 // indirect golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect diff --git a/go.sum b/go.sum index cee9c557df..b3fb988a3c 100644 --- a/go.sum +++ b/go.sum @@ -1364,6 +1364,8 @@ go.mongodb.org/atlas v0.38.0 h1:zfwymq20GqivGwxPZfypfUDry+WwMGVui97z1d8V4bU= go.mongodb.org/atlas v0.38.0/go.mod h1:DJYtM+vsEpPEMSkQzJnFHrT0sP7ev6cseZc/GGjJYG8= go.mongodb.org/atlas-sdk/v20240530005 v20240530005.0.0 h1:d/gbYJ+obR0EM/3DZf7+ZMi2QWISegm3mid7Or708cc= go.mongodb.org/atlas-sdk/v20240530005 v20240530005.0.0/go.mod h1:O47ZrMMfcWb31wznNIq2PQkkdoFoK0ea2GlmRqGJC2s= +go.mongodb.org/atlas-sdk/v20240805005 v20240805005.0.1-0.20250402112219-2468c5354718 h1:M2mNSBdTkP+paQ1qZ6FliiPdTEbDR9m9qvv4vsWoJAw= +go.mongodb.org/atlas-sdk/v20240805005 v20240805005.0.1-0.20250402112219-2468c5354718/go.mod h1:PeByRxdvzfvz7xhG5vDn60j836EoduWqTqs76okUc9c= go.mongodb.org/atlas-sdk/v20241113005 v20241113005.0.0 h1:aaU2E4rtzYXuEDxv9MoSON2gOEAA9M2gsDf2CqjcGj8= go.mongodb.org/atlas-sdk/v20241113005 v20241113005.0.0/go.mod h1:eV9REWR36iVMrpZUAMZ5qPbXEatoVfmzwT+Ue8yqU+U= go.mongodb.org/atlas-sdk/v20250312007 v20250312007.0.0 h1:2k6eXWQzTpbc/maZotRIoyXq3l/pbCF1RBMt+WnuB0I= diff --git a/internal/config/client.go b/internal/config/client.go index bab77c1c95..c4d51912c7 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -11,6 +11,7 @@ import ( "time" admin20240530 "go.mongodb.org/atlas-sdk/v20240530005/admin" + admin20240805 "go.mongodb.org/atlas-sdk/v20240805005/admin" admin20241113 "go.mongodb.org/atlas-sdk/v20241113005/admin" "go.mongodb.org/atlas-sdk/v20250312007/admin" matlasClient "go.mongodb.org/atlas/mongodbatlas" @@ -75,6 +76,7 @@ type MongoDBClient struct { Atlas *matlasClient.Client AtlasV2 *admin.APIClient AtlasPreview *adminpreview.APIClient + AtlasV220240805 *admin20240805.APIClient // used in advanced_cluster to avoid adopting 2024-10-23 release with ISS autoscaling AtlasV220240530 *admin20240530.APIClient // used in advanced_cluster and cloud_backup_schedule for avoiding breaking changes (supporting deprecated replication_specs.id) AtlasV220241113 *admin20241113.APIClient // used in teams and atlas_users to avoiding breaking changes Realm *RealmClient @@ -119,6 +121,10 @@ func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) if err != nil { return nil, err } + sdkV220240805Client, err := newSDKV220240805Client(client, c.BaseURL, userAgent) + if err != nil { + return nil, err + } sdkV220241113Client, err := newSDKV220241113Client(client, c.BaseURL, userAgent) if err != nil { return nil, err @@ -129,6 +135,7 @@ func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) AtlasV2: sdkV2Client, AtlasPreview: sdkPreviewClient, AtlasV220240530: sdkV220240530Client, + AtlasV220240805: sdkV220240805Client, AtlasV220241113: sdkV220241113Client, BaseURL: c.BaseURL, TerraformVersion: terraformVersion, @@ -197,6 +204,15 @@ func newSDKV220240530Client(client *http.Client, baseURL, userAgent string) (*ad ) } +func newSDKV220240805Client(client *http.Client, baseURL, userAgent string) (*admin20240805.APIClient, error) { + return admin20240805.NewClient( + admin20240805.UseHTTPClient(client), + admin20240805.UseUserAgent(userAgent), + admin20240805.UseBaseURL(baseURL), + admin20240805.UseDebug(false), + ) +} + func newSDKV220241113Client(client *http.Client, baseURL, userAgent string) (*admin20241113.APIClient, error) { return admin20241113.NewClient( admin20241113.UseHTTPClient(client), diff --git a/internal/service/advancedcluster/common_model_sdk_version_conversion.go b/internal/service/advancedcluster/common_model_sdk_version_conversion.go new file mode 100644 index 0000000000..137e755e77 --- /dev/null +++ b/internal/service/advancedcluster/common_model_sdk_version_conversion.go @@ -0,0 +1,256 @@ +package advancedcluster + +import ( + admin20240530 "go.mongodb.org/atlas-sdk/v20240530005/admin" + admin20240805 "go.mongodb.org/atlas-sdk/v20240805005/admin" + "go.mongodb.org/atlas-sdk/v20250312007/admin" +) + +// Conversions from one SDK model version to another are used to avoid duplicating our flatten/expand conversion functions. +// - These functions must not contain any business logic. +// - All will be removed once we rely on a single API version. + +func ConvertClusterDescription20241023to20240805(clusterDescription *admin.ClusterDescription20240805) *admin20240805.ClusterDescription20240805 { + return &admin20240805.ClusterDescription20240805{ + Name: clusterDescription.Name, + ClusterType: clusterDescription.ClusterType, + ReplicationSpecs: convertReplicationSpecs20241023to20240805(clusterDescription.ReplicationSpecs), + BackupEnabled: clusterDescription.BackupEnabled, + BiConnector: convertBiConnector20241023to20240805(clusterDescription.BiConnector), + EncryptionAtRestProvider: clusterDescription.EncryptionAtRestProvider, + Labels: convertLabels20241023to20240805(clusterDescription.Labels), + Tags: convertTag20241023to20240805(clusterDescription.Tags), + MongoDBMajorVersion: clusterDescription.MongoDBMajorVersion, + PitEnabled: clusterDescription.PitEnabled, + RootCertType: clusterDescription.RootCertType, + TerminationProtectionEnabled: clusterDescription.TerminationProtectionEnabled, + VersionReleaseSystem: clusterDescription.VersionReleaseSystem, + GlobalClusterSelfManagedSharding: clusterDescription.GlobalClusterSelfManagedSharding, + ReplicaSetScalingStrategy: clusterDescription.ReplicaSetScalingStrategy, + RedactClientLogData: clusterDescription.RedactClientLogData, + ConfigServerManagementMode: clusterDescription.ConfigServerManagementMode, + AdvancedConfiguration: convertAdvancedConfiguration20250312to20240805(clusterDescription.AdvancedConfiguration), + } +} + +func convertReplicationSpecs20241023to20240805(replicationSpecs *[]admin.ReplicationSpec20240805) *[]admin20240805.ReplicationSpec20240805 { + if replicationSpecs == nil { + return nil + } + result := make([]admin20240805.ReplicationSpec20240805, len(*replicationSpecs)) + for i, replicationSpec := range *replicationSpecs { + result[i] = admin20240805.ReplicationSpec20240805{ + Id: replicationSpec.Id, + ZoneName: replicationSpec.ZoneName, + ZoneId: replicationSpec.ZoneId, + RegionConfigs: convertCloudRegionConfig20241023to20240805(replicationSpec.RegionConfigs), + } + } + return &result +} + +func convertCloudRegionConfig20241023to20240805(cloudRegionConfig *[]admin.CloudRegionConfig20240805) *[]admin20240805.CloudRegionConfig20240805 { + if cloudRegionConfig == nil { + return nil + } + result := make([]admin20240805.CloudRegionConfig20240805, len(*cloudRegionConfig)) + for i, regionConfig := range *cloudRegionConfig { + result[i] = admin20240805.CloudRegionConfig20240805{ + ProviderName: regionConfig.ProviderName, + RegionName: regionConfig.RegionName, + BackingProviderName: regionConfig.BackingProviderName, + Priority: regionConfig.Priority, + ElectableSpecs: convertHardwareSpec20241023to20240805(regionConfig.ElectableSpecs), + ReadOnlySpecs: convertDedicatedHardwareSpec20241023to20240805(regionConfig.ReadOnlySpecs), + AnalyticsSpecs: convertDedicatedHardwareSpec20241023to20240805(regionConfig.AnalyticsSpecs), + AutoScaling: convertAdvancedAutoScalingSettings20241023to20240805(regionConfig.AutoScaling), + AnalyticsAutoScaling: convertAdvancedAutoScalingSettings20241023to20240805(regionConfig.AnalyticsAutoScaling), + } + } + return &result +} + +func convertAdvancedAutoScalingSettings20241023to20240805(advancedAutoScalingSettings *admin.AdvancedAutoScalingSettings) *admin20240805.AdvancedAutoScalingSettings { + if advancedAutoScalingSettings == nil { + return nil + } + return &admin20240805.AdvancedAutoScalingSettings{ + Compute: convertAdvancedComputeAutoScaling20241023to20240805(advancedAutoScalingSettings.Compute), + DiskGB: convertDiskGBAutoScaling20241023to20240805(advancedAutoScalingSettings.DiskGB), + } +} + +func convertDiskGBAutoScaling20241023to20240805(diskGBAutoScaling *admin.DiskGBAutoScaling) *admin20240805.DiskGBAutoScaling { + if diskGBAutoScaling == nil { + return nil + } + return &admin20240805.DiskGBAutoScaling{ + Enabled: diskGBAutoScaling.Enabled, + } +} + +func convertAdvancedComputeAutoScaling20241023to20240805(advancedComputeAutoScaling *admin.AdvancedComputeAutoScaling) *admin20240805.AdvancedComputeAutoScaling { + if advancedComputeAutoScaling == nil { + return nil + } + return &admin20240805.AdvancedComputeAutoScaling{ + Enabled: advancedComputeAutoScaling.Enabled, + MaxInstanceSize: advancedComputeAutoScaling.MaxInstanceSize, + MinInstanceSize: advancedComputeAutoScaling.MinInstanceSize, + ScaleDownEnabled: advancedComputeAutoScaling.ScaleDownEnabled, + } +} + +func convertHardwareSpec20241023to20240805(hardwareSpec *admin.HardwareSpec20240805) *admin20240805.HardwareSpec20240805 { + if hardwareSpec == nil { + return nil + } + return &admin20240805.HardwareSpec20240805{ + DiskSizeGB: hardwareSpec.DiskSizeGB, + NodeCount: hardwareSpec.NodeCount, + DiskIOPS: hardwareSpec.DiskIOPS, + EbsVolumeType: hardwareSpec.EbsVolumeType, + InstanceSize: hardwareSpec.InstanceSize, + } +} + +func convertDedicatedHardwareSpec20241023to20240805(hardwareSpec *admin.DedicatedHardwareSpec20240805) *admin20240805.DedicatedHardwareSpec20240805 { + if hardwareSpec == nil { + return nil + } + return &admin20240805.DedicatedHardwareSpec20240805{ + DiskSizeGB: hardwareSpec.DiskSizeGB, + NodeCount: hardwareSpec.NodeCount, + DiskIOPS: hardwareSpec.DiskIOPS, + EbsVolumeType: hardwareSpec.EbsVolumeType, + InstanceSize: hardwareSpec.InstanceSize, + } +} + +func convertBiConnector20241023to20240805(biConnector *admin.BiConnector) *admin20240805.BiConnector { + if biConnector == nil { + return nil + } + return &admin20240805.BiConnector{ + ReadPreference: biConnector.ReadPreference, + Enabled: biConnector.Enabled, + } +} + +func convertAdvancedConfiguration20250312to20240805(advConfig *admin.ApiAtlasClusterAdvancedConfiguration) *admin20240805.ApiAtlasClusterAdvancedConfiguration { + if advConfig == nil { + return nil + } + + return &admin20240805.ApiAtlasClusterAdvancedConfiguration{ + MinimumEnabledTlsProtocol: advConfig.MinimumEnabledTlsProtocol, + CustomOpensslCipherConfigTls12: advConfig.CustomOpensslCipherConfigTls12, + TlsCipherConfigMode: advConfig.TlsCipherConfigMode, + } +} + +func convertLabels20241023to20240805(labels *[]admin.ComponentLabel) *[]admin20240805.ComponentLabel { + if labels == nil { + return nil + } + result := make([]admin20240805.ComponentLabel, len(*labels)) + for i, label := range *labels { + result[i] = admin20240805.ComponentLabel{ + Key: label.Key, + Value: label.Value, + } + } + return &result +} + +func convertTag20241023to20240805(tags *[]admin.ResourceTag) *[]admin20240805.ResourceTag { + if tags == nil { + return nil + } + result := make([]admin20240805.ResourceTag, len(*tags)) + for i, tag := range *tags { + result[i] = admin20240805.ResourceTag{ + Key: tag.Key, + Value: tag.Value, + } + } + return &result +} + +func ConvertRegionConfigSlice20241023to20240530(slice *[]admin.CloudRegionConfig20240805) *[]admin20240530.CloudRegionConfig { + if slice == nil { + return nil + } + cloudRegionSlice := *slice + results := make([]admin20240530.CloudRegionConfig, len(cloudRegionSlice)) + for i := range cloudRegionSlice { + cloudRegion := cloudRegionSlice[i] + results[i] = admin20240530.CloudRegionConfig{ + ElectableSpecs: convertHardwareSpec20241023to20240530(cloudRegion.ElectableSpecs), + Priority: cloudRegion.Priority, + ProviderName: cloudRegion.ProviderName, + RegionName: cloudRegion.RegionName, + AnalyticsAutoScaling: convertAdvancedAutoScalingSettings20241023to20240530(cloudRegion.AnalyticsAutoScaling), + AnalyticsSpecs: convertDedicatedHardwareSpec20241023to20240530(cloudRegion.AnalyticsSpecs), + AutoScaling: convertAdvancedAutoScalingSettings20241023to20240530(cloudRegion.AutoScaling), + ReadOnlySpecs: convertDedicatedHardwareSpec20241023to20240530(cloudRegion.ReadOnlySpecs), + BackingProviderName: cloudRegion.BackingProviderName, + } + } + return &results +} + +func convertHardwareSpec20241023to20240530(hwspec *admin.HardwareSpec20240805) *admin20240530.HardwareSpec { + if hwspec == nil { + return nil + } + return &admin20240530.HardwareSpec{ + DiskIOPS: hwspec.DiskIOPS, + EbsVolumeType: hwspec.EbsVolumeType, + InstanceSize: hwspec.InstanceSize, + NodeCount: hwspec.NodeCount, + } +} + +func convertAdvancedAutoScalingSettings20241023to20240530(settings *admin.AdvancedAutoScalingSettings) *admin20240530.AdvancedAutoScalingSettings { + if settings == nil { + return nil + } + return &admin20240530.AdvancedAutoScalingSettings{ + Compute: convertAdvancedComputeAutoScaling20241023to20240530(settings.Compute), + DiskGB: convertDiskGBAutoScaling20241023to20240530(settings.DiskGB), + } +} + +func convertAdvancedComputeAutoScaling20241023to20240530(settings *admin.AdvancedComputeAutoScaling) *admin20240530.AdvancedComputeAutoScaling { + if settings == nil { + return nil + } + return &admin20240530.AdvancedComputeAutoScaling{ + Enabled: settings.Enabled, + MaxInstanceSize: settings.MaxInstanceSize, + MinInstanceSize: settings.MinInstanceSize, + ScaleDownEnabled: settings.ScaleDownEnabled, + } +} + +func convertDiskGBAutoScaling20241023to20240530(settings *admin.DiskGBAutoScaling) *admin20240530.DiskGBAutoScaling { + if settings == nil { + return nil + } + return &admin20240530.DiskGBAutoScaling{ + Enabled: settings.Enabled, + } +} + +func convertDedicatedHardwareSpec20241023to20240530(spec *admin.DedicatedHardwareSpec20240805) *admin20240530.DedicatedHardwareSpec { + if spec == nil { + return nil + } + return &admin20240530.DedicatedHardwareSpec{ + NodeCount: spec.NodeCount, + DiskIOPS: spec.DiskIOPS, + EbsVolumeType: spec.EbsVolumeType, + InstanceSize: spec.InstanceSize, + } +} diff --git a/internal/service/advancedcluster/common_model_sdk_version_conversion_test.go b/internal/service/advancedcluster/common_model_sdk_version_conversion_test.go new file mode 100644 index 0000000000..88fa992c63 --- /dev/null +++ b/internal/service/advancedcluster/common_model_sdk_version_conversion_test.go @@ -0,0 +1,193 @@ +package advancedcluster_test + +import ( + "testing" + + "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" + "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/advancedcluster" + "github.com/stretchr/testify/assert" + admin20240805 "go.mongodb.org/atlas-sdk/v20240805005/admin" + "go.mongodb.org/atlas-sdk/v20250312007/admin" +) + +func TestConvertClusterDescription20241023to20240805(t *testing.T) { + var ( + clusterName = "clusterName" + clusterType = "REPLICASET" + earProvider = "AWS" + booleanValue = true + mongoDBMajorVersion = "7.0" + rootCertType = "rootCertType" + replicaSetScalingStrategy = "WORKLOAD_TYPE" + configServerManagementMode = "ATLAS_MANAGED" + readPreference = "primary" + zoneName = "z1" + id = "id1" + regionConfigProvider = "AWS" + region = "EU_WEST_1" + priority = 7 + instanceSize = "M10" + nodeCount = 3 + diskSizeGB = 30.3 + ebsVolumeType = "STANDARD" + diskIOPS = 100 + ) + testCases := []struct { + input *admin.ClusterDescription20240805 + expectedOutput *admin20240805.ClusterDescription20240805 + name string + }{ + { + name: "Converts cluster description from 20241023 to 20240805", + input: &admin.ClusterDescription20240805{ + Name: conversion.StringPtr(clusterName), + ClusterType: conversion.StringPtr(clusterType), + ReplicationSpecs: &[]admin.ReplicationSpec20240805{ + { + Id: conversion.StringPtr(id), + ZoneName: conversion.StringPtr(zoneName), + RegionConfigs: &[]admin.CloudRegionConfig20240805{ + { + ProviderName: conversion.StringPtr(regionConfigProvider), + RegionName: conversion.StringPtr(region), + BackingProviderName: conversion.StringPtr(regionConfigProvider), + Priority: conversion.IntPtr(priority), + AnalyticsSpecs: &admin.DedicatedHardwareSpec20240805{ + InstanceSize: conversion.StringPtr(instanceSize), + NodeCount: conversion.IntPtr(nodeCount), + DiskSizeGB: conversion.Pointer(diskSizeGB), + EbsVolumeType: conversion.StringPtr(ebsVolumeType), + DiskIOPS: conversion.IntPtr(diskIOPS), + }, + ElectableSpecs: &admin.HardwareSpec20240805{ + InstanceSize: conversion.StringPtr(instanceSize), + NodeCount: conversion.IntPtr(nodeCount), + DiskSizeGB: conversion.Pointer(diskSizeGB), + EbsVolumeType: conversion.StringPtr(ebsVolumeType), + DiskIOPS: conversion.IntPtr(diskIOPS), + }, + AutoScaling: &admin.AdvancedAutoScalingSettings{ + Compute: &admin.AdvancedComputeAutoScaling{ + Enabled: conversion.Pointer(booleanValue), + MaxInstanceSize: conversion.Pointer(instanceSize), + MinInstanceSize: conversion.Pointer(instanceSize), + ScaleDownEnabled: conversion.Pointer(booleanValue), + }, + DiskGB: &admin.DiskGBAutoScaling{ + Enabled: conversion.Pointer(booleanValue), + }, + }, + }, + }, + }, + }, + BackupEnabled: conversion.Pointer(booleanValue), + BiConnector: &admin.BiConnector{ + Enabled: conversion.Pointer(booleanValue), + ReadPreference: conversion.StringPtr(readPreference), + }, + EncryptionAtRestProvider: conversion.StringPtr(earProvider), + Labels: &[]admin.ComponentLabel{ + {Key: conversion.StringPtr("key1"), Value: conversion.StringPtr("value1")}, + {Key: conversion.StringPtr("key2"), Value: conversion.StringPtr("value2")}, + }, + Tags: &[]admin.ResourceTag{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + }, + MongoDBMajorVersion: conversion.StringPtr(mongoDBMajorVersion), + PitEnabled: conversion.Pointer(booleanValue), + RootCertType: conversion.StringPtr(rootCertType), + TerminationProtectionEnabled: conversion.Pointer(booleanValue), + VersionReleaseSystem: conversion.StringPtr(""), + GlobalClusterSelfManagedSharding: conversion.Pointer(booleanValue), + ReplicaSetScalingStrategy: conversion.StringPtr(replicaSetScalingStrategy), + RedactClientLogData: conversion.Pointer(booleanValue), + ConfigServerManagementMode: conversion.StringPtr(configServerManagementMode), + }, + expectedOutput: &admin20240805.ClusterDescription20240805{ + Name: conversion.StringPtr(clusterName), + ClusterType: conversion.StringPtr(clusterType), + ReplicationSpecs: &[]admin20240805.ReplicationSpec20240805{ + { + Id: conversion.StringPtr(id), + ZoneName: conversion.StringPtr(zoneName), + RegionConfigs: &[]admin20240805.CloudRegionConfig20240805{ + { + ProviderName: conversion.StringPtr(regionConfigProvider), + RegionName: conversion.StringPtr(region), + BackingProviderName: conversion.StringPtr(regionConfigProvider), + Priority: conversion.IntPtr(priority), + AnalyticsSpecs: &admin20240805.DedicatedHardwareSpec20240805{ + InstanceSize: conversion.StringPtr(instanceSize), + NodeCount: conversion.IntPtr(nodeCount), + DiskSizeGB: conversion.Pointer(diskSizeGB), + EbsVolumeType: conversion.StringPtr(ebsVolumeType), + DiskIOPS: conversion.IntPtr(diskIOPS), + }, + ElectableSpecs: &admin20240805.HardwareSpec20240805{ + InstanceSize: conversion.StringPtr(instanceSize), + NodeCount: conversion.IntPtr(nodeCount), + DiskSizeGB: conversion.Pointer(diskSizeGB), + EbsVolumeType: conversion.StringPtr(ebsVolumeType), + DiskIOPS: conversion.IntPtr(diskIOPS), + }, + AutoScaling: &admin20240805.AdvancedAutoScalingSettings{ + Compute: &admin20240805.AdvancedComputeAutoScaling{ + Enabled: conversion.Pointer(booleanValue), + MaxInstanceSize: conversion.Pointer(instanceSize), + MinInstanceSize: conversion.Pointer(instanceSize), + ScaleDownEnabled: conversion.Pointer(booleanValue), + }, + DiskGB: &admin20240805.DiskGBAutoScaling{ + Enabled: conversion.Pointer(booleanValue), + }, + }, + }, + }, + }, + }, + BackupEnabled: conversion.Pointer(booleanValue), + BiConnector: &admin20240805.BiConnector{ + Enabled: conversion.Pointer(booleanValue), + ReadPreference: conversion.StringPtr(readPreference), + }, + EncryptionAtRestProvider: conversion.StringPtr(earProvider), + Labels: &[]admin20240805.ComponentLabel{ + {Key: conversion.StringPtr("key1"), Value: conversion.StringPtr("value1")}, + {Key: conversion.StringPtr("key2"), Value: conversion.StringPtr("value2")}, + }, + Tags: &[]admin20240805.ResourceTag{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + }, + MongoDBMajorVersion: conversion.StringPtr(mongoDBMajorVersion), + PitEnabled: conversion.Pointer(booleanValue), + RootCertType: conversion.StringPtr(rootCertType), + TerminationProtectionEnabled: conversion.Pointer(booleanValue), + VersionReleaseSystem: conversion.StringPtr(""), + GlobalClusterSelfManagedSharding: conversion.Pointer(booleanValue), + ReplicaSetScalingStrategy: conversion.StringPtr(replicaSetScalingStrategy), + RedactClientLogData: conversion.Pointer(booleanValue), + ConfigServerManagementMode: conversion.StringPtr(configServerManagementMode), + }, + }, + { + name: "Converts cluster description from 20241023 to 20240805 with nil values", + input: &admin.ClusterDescription20240805{}, + expectedOutput: &admin20240805.ClusterDescription20240805{ + ReplicationSpecs: nil, + BiConnector: nil, + Labels: nil, + Tags: nil, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := advancedcluster.ConvertClusterDescription20241023to20240805(tc.input) + assert.Equal(t, tc.expectedOutput, result) + }) + } +} From b7e555b79150a7bf908271c3b65920e5142e2da4 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 07:53:47 +0200 Subject: [PATCH 27/41] use same credentials struct in AWS Secrets Manager --- internal/config/credentials.go | 17 ++++++++--------- internal/provider/aws_credentials.go | 21 ++------------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/internal/config/credentials.go b/internal/config/credentials.go index 3034b92e21..b755e2b34f 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -6,15 +6,15 @@ import ( "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" ) +// Credentials has all the authentication fields, it also coincides with fields that can be stored in AWS Secrets Manager. type Credentials struct { - Method string - AccessToken string - ClientID string - ClientSecret string - PublicKey string - PrivateKey string - BaseURL string - RealmBaseURL string + AccessToken string `json:"access_token"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + PublicKey string `json:"public_key"` + PrivateKey string `json:"private_key"` + BaseURL string `json:"base_url"` + RealmBaseURL string `json:"realm_base_url"` } func (c *Credentials) AuthMethod() AuthMethod { @@ -68,7 +68,6 @@ func NewEnvVars() *Vars { func (e *Vars) GetCredentials() *Credentials { return &Credentials{ - Method: "Environment Variables", AccessToken: e.AccessToken, ClientID: e.ClientID, ClientSecret: e.ClientSecret, diff --git a/internal/provider/aws_credentials.go b/internal/provider/aws_credentials.go index 5ad9a60c19..9f7e2f41c5 100644 --- a/internal/provider/aws_credentials.go +++ b/internal/provider/aws_credentials.go @@ -53,29 +53,12 @@ func getAWSCredentials(c *config.AWSVars) (*config.Credentials, error) { if err != nil { return nil, err } - // TODO could credentials be reused removing Method? - var secret struct { - AccessToken string `json:"access_token"` - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - PublicKey string `json:"public_key"` - PrivateKey string `json:"private_key"` - } + var secret config.Credentials err = json.Unmarshal([]byte(secretString), &secret) if err != nil { return nil, err } - // TODO: how to read URLs in AWS Secrets Manager? - return &config.Credentials{ - Method: "AWS Secrets Manager", - AccessToken: secret.AccessToken, - ClientID: secret.ClientID, - ClientSecret: secret.ClientSecret, - PublicKey: secret.PublicKey, - PrivateKey: secret.PrivateKey, - BaseURL: "", // TODO: how to read - RealmBaseURL: "", // TODO: how to read - }, nil + return &secret, nil } func DeriveSTSRegionFromEndpoint(ep string) string { From c818683507f788abe2ed45e86d691e6bbff5d780 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 08:27:30 +0200 Subject: [PATCH 28/41] method pick logic --- internal/config/credentials.go | 28 ++++++++++++++++++++++------ internal/provider/provider.go | 24 +++++++++++++----------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/internal/config/credentials.go b/internal/config/credentials.go index b755e2b34f..936112e4e4 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -30,6 +30,10 @@ func (c *Credentials) AuthMethod() AuthMethod { return Unknown } +func (c *Credentials) IsPresent() bool { + return c.AuthMethod() != Unknown +} + type Vars struct { AccessToken string ClientID string @@ -88,6 +92,10 @@ type AWSVars struct { Endpoint string } +func (a *AWSVars) IsPresent() bool { + return a.AssumeRoleARN != "" +} + // GetAWS returns variables in the format AWS expects, e.g. region in lowercase. func (e *Vars) GetAWS() *AWSVars { return &AWSVars{ @@ -110,12 +118,20 @@ func getEnv(key ...string) string { return "" } -// TODO lowercase when used -func Coalesce(str ...string) string { - for _, s := range str { - if s != "" { - return s +func CoalesceAWSVars(awsVars ...*AWSVars) *AWSVars { + for _, awsVar := range awsVars { + if awsVar.IsPresent() { + return awsVar } } - return "" + return nil +} + +func CoalesceCredentials(credentials ...*Credentials) *Credentials { + for _, credential := range credentials { + if credential.IsPresent() { + return credential + } + } + return nil } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6afe9558d9..37536094b8 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -215,20 +215,22 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config func configClient(providerVars *config.Vars, terraformVersion string) (*config.MongoDBClient, error) { envVars := config.NewEnvVars() - // decide what to do if AWS is not chosen from provider and env vars - awsCredentials, err := getAWSCredentials(envVars.GetAWS()) - if err != nil { - return nil, err - } + // TODO: warnings if multiple credentials are set, inside NewClient? or better in Credentials. - _, _ = providerVars, awsCredentials + if awsVars := config.CoalesceAWSVars(providerVars.GetAWS(), envVars.GetAWS()); awsVars != nil { + awsCredentials, err := getAWSCredentials(awsVars) + if err != nil { + return nil, err + } + return config.NewClient(awsCredentials, terraformVersion) + } - // TODO: chooose the credentials between AWS, SA or PAK - client, err := config.NewClient(envVars.GetCredentials(), terraformVersion) - if err != nil { - return nil, err + if c := config.CoalesceCredentials(providerVars.GetCredentials(), envVars.GetCredentials()); c != nil { + return config.NewClient(c, terraformVersion) } - return client, nil + + // TODO: warning if not credentials are set, maybe inside Credentials. + return config.NewClient(&config.Credentials{}, terraformVersion) } func getProviderVars(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) *config.Vars { From 2cd7c135c6e9b751af474dc06a9e037b475ce9c2 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:54:17 +0200 Subject: [PATCH 29/41] config.GetCredentials --- internal/config/client.go | 1 + internal/config/credentials.go | 48 +++++++++++++++++++++--------- internal/provider/provider.go | 28 ++++------------- internal/provider/provider_sdk2.go | 6 +++- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/internal/config/client.go b/internal/config/client.go index c4d51912c7..9ed3caeb8f 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -149,6 +149,7 @@ func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) return clients, nil } +// getHTTPClient follows the order of token, SA and PAK. func getHTTPClient(c *Credentials) (*http.Client, error) { transport := networkLoggingBaseTransport() switch c.AuthMethod() { diff --git a/internal/config/credentials.go b/internal/config/credentials.go index 936112e4e4..e0e7ba13ea 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -17,6 +17,26 @@ type Credentials struct { RealmBaseURL string `json:"realm_base_url"` } +// GetCredentials follows the order of AWS Secrets Manager, provider vars and env vars. +func GetCredentials(providerVars, envVars *Vars, getAWSCredentials func(*AWSVars) (*Credentials, error)) (*Credentials, error) { + // TODO: warnings if multiple credentials are set, inside NewClient? or better in Credentials. + + if awsVars := CoalesceAWSVars(providerVars.GetAWS(), envVars.GetAWS()); awsVars != nil { + awsCredentials, err := getAWSCredentials(awsVars) + if err != nil { + return nil, err + } + return awsCredentials, nil + } + + if c := CoalesceCredentials(providerVars.GetCredentials(), envVars.GetCredentials()); c != nil { + return c, nil + } + + // TODO: warning if not credentials are set, maybe inside Credentials. + return &Credentials{}, nil +} + func (c *Credentials) AuthMethod() AuthMethod { if c.AccessToken != "" { return AccessToken @@ -34,6 +54,20 @@ func (c *Credentials) IsPresent() bool { return c.AuthMethod() != Unknown } +type AWSVars struct { + AssumeRoleARN string + SecretName string + Region string + AccessKeyID string + SecretAccessKey string + SessionToken string + Endpoint string +} + +func (a *AWSVars) IsPresent() bool { + return a.AssumeRoleARN != "" +} + type Vars struct { AccessToken string ClientID string @@ -82,20 +116,6 @@ func (e *Vars) GetCredentials() *Credentials { } } -type AWSVars struct { - AssumeRoleARN string - SecretName string - Region string - AccessKeyID string - SecretAccessKey string - SessionToken string - Endpoint string -} - -func (a *AWSVars) IsPresent() bool { - return a.AssumeRoleARN != "" -} - // GetAWS returns variables in the format AWS expects, e.g. region in lowercase. func (e *Vars) GetAWS() *AWSVars { return &AWSVars{ diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 37536094b8..ac050c8dc1 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -203,7 +203,12 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config if resp.Diagnostics.HasError() { return } - client, err := configClient(providerVars, req.TerraformVersion) + c, err := config.GetCredentials(providerVars, config.NewEnvVars(), getAWSCredentials) + if err != nil { + resp.Diagnostics.AddError("Error getting credentials for provider", err.Error()) + return + } + client, err := config.NewClient(c, req.TerraformVersion) if err != nil { resp.Diagnostics.AddError("Error initializing provider", err.Error()) return @@ -212,27 +217,6 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config resp.ResourceData = client } -func configClient(providerVars *config.Vars, terraformVersion string) (*config.MongoDBClient, error) { - envVars := config.NewEnvVars() - - // TODO: warnings if multiple credentials are set, inside NewClient? or better in Credentials. - - if awsVars := config.CoalesceAWSVars(providerVars.GetAWS(), envVars.GetAWS()); awsVars != nil { - awsCredentials, err := getAWSCredentials(awsVars) - if err != nil { - return nil, err - } - return config.NewClient(awsCredentials, terraformVersion) - } - - if c := config.CoalesceCredentials(providerVars.GetCredentials(), envVars.GetCredentials()); c != nil { - return config.NewClient(c, terraformVersion) - } - - // TODO: warning if not credentials are set, maybe inside Credentials. - return config.NewClient(&config.Credentials{}, terraformVersion) -} - func getProviderVars(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) *config.Vars { var data tfModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 8fe123b2b0..1d6e5ffbc5 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -297,7 +297,11 @@ func getResourcesMap() map[string]*schema.Resource { func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { return func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { providerVars := getSDKv2ProviderVars(d) - client, err := configClient(providerVars, provider.TerraformVersion) + c, err := config.GetCredentials(providerVars, config.NewEnvVars(), getAWSCredentials) + if err != nil { + return nil, diag.FromErr(fmt.Errorf("error getting credentials for provider: %w", err)) + } + client, err := config.NewClient(c, provider.TerraformVersion) if err != nil { return nil, diag.FromErr(fmt.Errorf("error initializing provider: %w", err)) } From 7c56a757a586248f3e85b9dc5bb012b34fb4c377 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:35:38 +0200 Subject: [PATCH 30/41] warnings --- internal/config/client.go | 1 - internal/config/credentials.go | 50 +++++++++++++++++++++++------- internal/provider/provider.go | 3 ++ internal/provider/provider_sdk2.go | 11 +++++-- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/internal/config/client.go b/internal/config/client.go index 9ed3caeb8f..c4d51912c7 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -149,7 +149,6 @@ func NewClient(c *Credentials, terraformVersion string) (*MongoDBClient, error) return clients, nil } -// getHTTPClient follows the order of token, SA and PAK. func getHTTPClient(c *Credentials) (*http.Client, error) { transport := networkLoggingBaseTransport() switch c.AuthMethod() { diff --git a/internal/config/credentials.go b/internal/config/credentials.go index e0e7ba13ea..eeb1a5515f 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -19,8 +19,6 @@ type Credentials struct { // GetCredentials follows the order of AWS Secrets Manager, provider vars and env vars. func GetCredentials(providerVars, envVars *Vars, getAWSCredentials func(*AWSVars) (*Credentials, error)) (*Credentials, error) { - // TODO: warnings if multiple credentials are set, inside NewClient? or better in Credentials. - if awsVars := CoalesceAWSVars(providerVars.GetAWS(), envVars.GetAWS()); awsVars != nil { awsCredentials, err := getAWSCredentials(awsVars) if err != nil { @@ -28,32 +26,62 @@ func GetCredentials(providerVars, envVars *Vars, getAWSCredentials func(*AWSVars } return awsCredentials, nil } - if c := CoalesceCredentials(providerVars.GetCredentials(), envVars.GetCredentials()); c != nil { return c, nil } - - // TODO: warning if not credentials are set, maybe inside Credentials. return &Credentials{}, nil } +// AuthMethod follows the order of token, SA and PAK. func (c *Credentials) AuthMethod() AuthMethod { - if c.AccessToken != "" { + switch { + case c.HasAccessToken(): return AccessToken - } - if c.ClientID != "" || c.ClientSecret != "" { + case c.HasServiceAccount(): return ServiceAccount - } - if c.PublicKey != "" || c.PrivateKey != "" { + case c.HasDigest(): return Digest + default: + return Unknown } - return Unknown +} + +func (c *Credentials) HasAccessToken() bool { + return c.AccessToken != "" +} + +func (c *Credentials) HasServiceAccount() bool { + return c.ClientID != "" || c.ClientSecret != "" +} + +func (c *Credentials) HasDigest() bool { + return c.PublicKey != "" || c.PrivateKey != "" } func (c *Credentials) IsPresent() bool { return c.AuthMethod() != Unknown } +func (c *Credentials) Warnings() string { + if !c.IsPresent() { + return "No credentials set" + } + // Prefer specific checks over generaric code as there are few combinations and code is clearer. + if c.HasAccessToken() && c.HasServiceAccount() && c.HasDigest() { + return "Access Token will be used although Service Account and API Keys are also set" + } + if c.HasAccessToken() && c.HasServiceAccount() { + return "Access Token will be used although Service Account is also set" + } + if c.HasAccessToken() && c.HasDigest() { + return "Access Token will be used although API Keys is also set" + } + if c.HasServiceAccount() && c.HasDigest() { + return "Service Account will be used although API Keys is also set" + } + return "" +} + type AWSVars struct { AssumeRoleARN string SecretName string diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ac050c8dc1..631f79f772 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -208,6 +208,9 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config resp.Diagnostics.AddError("Error getting credentials for provider", err.Error()) return } + for c.Warnings() != "" { + resp.Diagnostics.AddWarning("Warning getting credentials for provider", c.Warnings()) + } client, err := config.NewClient(c, req.TerraformVersion) if err != nil { resp.Diagnostics.AddError("Error initializing provider", err.Error()) diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 1d6e5ffbc5..2816360e52 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -296,14 +296,21 @@ func getResourcesMap() map[string]*schema.Resource { func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { return func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { + var diags diag.Diagnostics providerVars := getSDKv2ProviderVars(d) c, err := config.GetCredentials(providerVars, config.NewEnvVars(), getAWSCredentials) if err != nil { - return nil, diag.FromErr(fmt.Errorf("error getting credentials for provider: %w", err)) + return nil, append(diags, diag.FromErr(fmt.Errorf("error getting credentials for provider: %w", err))...) + } + if c.Warnings() != "" { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "warning getting credentials for provider: " + c.Warnings(), + }) } client, err := config.NewClient(c, provider.TerraformVersion) if err != nil { - return nil, diag.FromErr(fmt.Errorf("error initializing provider: %w", err)) + return nil, append(diags, diag.FromErr(fmt.Errorf("error initializing provider: %w", err))...) } return client, nil } From 35353d3aa959278bab5059d738559b40f2dc676e Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:22:52 +0200 Subject: [PATCH 31/41] unit tests --- internal/config/credentials_test.go | 504 ++++++++++++++++++++++++++++ internal/provider/provider.go | 2 +- 2 files changed, 505 insertions(+), 1 deletion(-) create mode 100644 internal/config/credentials_test.go diff --git a/internal/config/credentials_test.go b/internal/config/credentials_test.go new file mode 100644 index 0000000000..c10d443d62 --- /dev/null +++ b/internal/config/credentials_test.go @@ -0,0 +1,504 @@ +package config_test + +import ( + "errors" + "testing" + + "github.com/mongodb/terraform-provider-mongodbatlas/internal/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCredentials_AuthMethod(t *testing.T) { + testCases := map[string]struct { + credentials config.Credentials + want config.AuthMethod + }{ + "Empty credentials returns Unknown": { + credentials: config.Credentials{}, + want: config.Unknown, + }, + "Access token takes priority": { + credentials: config.Credentials{ + AccessToken: "token", + ClientID: "id", + ClientSecret: "secret", + PublicKey: "public", + PrivateKey: "private", + }, + want: config.AccessToken, + }, + "Service account when no access token": { + credentials: config.Credentials{ + ClientID: "id", + ClientSecret: "secret", + PublicKey: "public", + PrivateKey: "private", + }, + want: config.ServiceAccount, + }, + "Service account with only ClientID": { + credentials: config.Credentials{ + ClientID: "id", + }, + want: config.ServiceAccount, + }, + "Service account with only ClientSecret": { + credentials: config.Credentials{ + ClientSecret: "secret", + }, + want: config.ServiceAccount, + }, + "Digest when only digest credentials": { + credentials: config.Credentials{ + PublicKey: "public", + PrivateKey: "private", + }, + want: config.Digest, + }, + "Digest with only PublicKey": { + credentials: config.Credentials{ + PublicKey: "public", + }, + want: config.Digest, + }, + "Digest with only PrivateKey": { + credentials: config.Credentials{ + PrivateKey: "private", + }, + want: config.Digest, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := tc.credentials.AuthMethod() + assert.Equal(t, tc.want, got) + }) + } +} + +func TestCredentials_HasAccessToken(t *testing.T) { + testCases := map[string]struct { + credentials config.Credentials + want bool + }{ + "Empty credentials": { + credentials: config.Credentials{}, + want: false, + }, + "With access token": { + credentials: config.Credentials{ + AccessToken: "token", + }, + want: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := tc.credentials.HasAccessToken() + assert.Equal(t, tc.want, got) + }) + } +} + +func TestCredentials_HasServiceAccount(t *testing.T) { + testCases := map[string]struct { + credentials config.Credentials + want bool + }{ + "Empty credentials": { + credentials: config.Credentials{}, + want: false, + }, + "With ClientID only": { + credentials: config.Credentials{ + ClientID: "id", + }, + want: true, + }, + "With ClientSecret only": { + credentials: config.Credentials{ + ClientSecret: "secret", + }, + want: true, + }, + "With both ClientID and ClientSecret": { + credentials: config.Credentials{ + ClientID: "id", + ClientSecret: "secret", + }, + want: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := tc.credentials.HasServiceAccount() + assert.Equal(t, tc.want, got) + }) + } +} + +func TestCredentials_HasDigest(t *testing.T) { + testCases := map[string]struct { + credentials config.Credentials + want bool + }{ + "Empty credentials": { + credentials: config.Credentials{}, + want: false, + }, + "With PublicKey only": { + credentials: config.Credentials{ + PublicKey: "public", + }, + want: true, + }, + "With PrivateKey only": { + credentials: config.Credentials{ + PrivateKey: "private", + }, + want: true, + }, + "With both PublicKey and PrivateKey": { + credentials: config.Credentials{ + PublicKey: "public", + PrivateKey: "private", + }, + want: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := tc.credentials.HasDigest() + assert.Equal(t, tc.want, got) + }) + } +} + +func TestCredentials_IsPresent(t *testing.T) { + testCases := map[string]struct { + credentials config.Credentials + want bool + }{ + "Empty credentials": { + credentials: config.Credentials{}, + want: false, + }, + "With access token": { + credentials: config.Credentials{ + AccessToken: "token", + }, + want: true, + }, + "With service account": { + credentials: config.Credentials{ + ClientID: "id", + }, + want: true, + }, + "With digest": { + credentials: config.Credentials{ + PublicKey: "public", + }, + want: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := tc.credentials.IsPresent() + assert.Equal(t, tc.want, got) + }) + } +} + +func TestCredentials_Warnings(t *testing.T) { + testCases := map[string]struct { + credentials config.Credentials + want string + }{ + "No credentials": { + credentials: config.Credentials{}, + want: "No credentials set", + }, + "Only access token - no warning": { + credentials: config.Credentials{ + AccessToken: "token", + }, + want: "", + }, + "Only service account - no warning": { + credentials: config.Credentials{ + ClientID: "id", + }, + want: "", + }, + "Only digest - no warning": { + credentials: config.Credentials{ + PublicKey: "public", + }, + want: "", + }, + "Access token and service account": { + credentials: config.Credentials{ + AccessToken: "token", + ClientID: "id", + ClientSecret: "secret", + }, + want: "Access Token will be used although Service Account is also set", + }, + "Access token and digest": { + credentials: config.Credentials{ + AccessToken: "token", + PublicKey: "public", + PrivateKey: "private", + }, + want: "Access Token will be used although API Keys is also set", + }, + "Service account and digest": { + credentials: config.Credentials{ + ClientID: "id", + PublicKey: "public", + PrivateKey: "private", + }, + want: "Service Account will be used although API Keys is also set", + }, + "All three methods": { + credentials: config.Credentials{ + AccessToken: "token", + ClientID: "id", + ClientSecret: "secret", + PublicKey: "public", + PrivateKey: "private", + }, + want: "Access Token will be used although Service Account and API Keys are also set", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := tc.credentials.Warnings() + assert.Equal(t, tc.want, got) + }) + } +} + +func TestGetCredentials(t *testing.T) { + mockGetAWSCredentials := func(awsVars *config.AWSVars) (*config.Credentials, error) { + if awsVars.AssumeRoleARN == "error" { + return nil, errors.New("AWS error") + } + return &config.Credentials{ + AccessToken: "aws-token", + }, nil + } + + testCases := map[string]struct { + providerVars *config.Vars + envVars *config.Vars + want *config.Credentials + wantErr bool + }{ + "AWS credentials take priority": { + providerVars: &config.Vars{ + AWSAssumeRoleARN: "arn", + PublicKey: "provider-public", + }, + envVars: &config.Vars{ + PublicKey: "env-public", + }, + want: &config.Credentials{ + AccessToken: "aws-token", + }, + wantErr: false, + }, + "AWS credentials error": { + providerVars: &config.Vars{ + AWSAssumeRoleARN: "error", + }, + envVars: &config.Vars{}, + want: nil, + wantErr: true, + }, + "Provider vars take priority over env vars": { + providerVars: &config.Vars{ + PublicKey: "provider-public", + }, + envVars: &config.Vars{ + PublicKey: "env-public", + }, + want: &config.Credentials{ + PublicKey: "provider-public", + }, + wantErr: false, + }, + "Env vars when no provider vars": { + providerVars: &config.Vars{}, + envVars: &config.Vars{ + PublicKey: "env-public", + }, + want: &config.Credentials{ + PublicKey: "env-public", + }, + wantErr: false, + }, + "Empty credentials when nothing provided": { + providerVars: &config.Vars{}, + envVars: &config.Vars{}, + want: &config.Credentials{}, + wantErr: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got, err := config.GetCredentials(tc.providerVars, tc.envVars, mockGetAWSCredentials) + if tc.wantErr { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, tc.want, got) + } + }) + } +} + +func TestAWSVars_IsPresent(t *testing.T) { + testCases := map[string]struct { + awsVars *config.AWSVars + want bool + }{ + "Empty AWS vars": { + awsVars: &config.AWSVars{}, + want: false, + }, + "With AssumeRoleARN": { + awsVars: &config.AWSVars{ + AssumeRoleARN: "arn", + }, + want: true, + }, + "With other fields but no AssumeRoleARN": { + awsVars: &config.AWSVars{ + SecretName: "secret", + Region: "us-east-1", + }, + want: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := tc.awsVars.IsPresent() + assert.Equal(t, tc.want, got) + }) + } +} + +func TestNewEnvVars(t *testing.T) { + // Test the first env var for each attribute. + t.Setenv("MONGODB_ATLAS_ACCESS_TOKEN", "env-token") + t.Setenv("MONGODB_ATLAS_CLIENT_ID", "env-client-id") + t.Setenv("MONGODB_ATLAS_CLIENT_SECRET", "env-client-secret") + t.Setenv("MONGODB_ATLAS_PUBLIC_API_KEY", "env-public") + t.Setenv("MONGODB_ATLAS_PRIVATE_API_KEY", "env-private") + t.Setenv("MONGODB_ATLAS_BASE_URL", "url1") + t.Setenv("MONGODB_REALM_BASE_URL", "url2") + t.Setenv("ASSUME_ROLE_ARN", "arn") + t.Setenv("SECRET_NAME", "env-secret") + t.Setenv("AWS_REGION", "us-west-2") + t.Setenv("AWS_ACCESS_KEY_ID", "env-access") + t.Setenv("AWS_SECRET_ACCESS_KEY", "env-secret-key") + t.Setenv("AWS_SESSION_TOKEN", "env-token") + t.Setenv("STS_ENDPOINT", "https://sts.amazonaws.com") + + vars := config.NewEnvVars() + assert.Equal(t, "env-token", vars.AccessToken) + assert.Equal(t, "env-client-id", vars.ClientID) + assert.Equal(t, "env-client-secret", vars.ClientSecret) + assert.Equal(t, "env-public", vars.PublicKey) + assert.Equal(t, "env-private", vars.PrivateKey) + assert.Equal(t, "url1", vars.BaseURL) + assert.Equal(t, "url2", vars.RealmBaseURL) + assert.Equal(t, "arn", vars.AWSAssumeRoleARN) + assert.Equal(t, "env-secret", vars.AWSSecretName) + assert.Equal(t, "us-west-2", vars.AWSRegion) + assert.Equal(t, "env-access", vars.AWSAccessKeyID) + assert.Equal(t, "env-secret-key", vars.AWSSecretAccessKey) + assert.Equal(t, "env-token", vars.AWSSessionToken) + assert.Equal(t, "https://sts.amazonaws.com", vars.AWSEndpoint) +} + +func TestCoalesceAWSVars(t *testing.T) { + awsVars1 := &config.AWSVars{AssumeRoleARN: "arn1"} + awsVars2 := &config.AWSVars{AssumeRoleARN: "arn2"} + awsVarsEmpty := &config.AWSVars{} + + testCases := map[string]struct { + awsVars []*config.AWSVars + want *config.AWSVars + }{ + "First present AWS vars": { + awsVars: []*config.AWSVars{awsVars1, awsVars2}, + want: awsVars1, + }, + "Skip empty, return first present": { + awsVars: []*config.AWSVars{awsVarsEmpty, awsVars2}, + want: awsVars2, + }, + "All empty returns nil": { + awsVars: []*config.AWSVars{awsVarsEmpty, awsVarsEmpty}, + want: nil, + }, + "No vars returns nil": { + awsVars: []*config.AWSVars{}, + want: nil, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := config.CoalesceAWSVars(tc.awsVars...) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestCoalesceCredentials(t *testing.T) { + creds1 := &config.Credentials{PublicKey: "key1"} + creds2 := &config.Credentials{PublicKey: "key2"} + credsEmpty := &config.Credentials{} + + testCases := map[string]struct { + want *config.Credentials + credentials []*config.Credentials + }{ + "First present credentials": { + credentials: []*config.Credentials{creds1, creds2}, + want: creds1, + }, + "Skip empty, return first present": { + credentials: []*config.Credentials{credsEmpty, creds2}, + want: creds2, + }, + "All empty returns nil": { + credentials: []*config.Credentials{credsEmpty, credsEmpty}, + want: nil, + }, + "No credentials returns nil": { + credentials: []*config.Credentials{}, + want: nil, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got := config.CoalesceCredentials(tc.credentials...) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 631f79f772..9dae3903c9 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -208,7 +208,7 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config resp.Diagnostics.AddError("Error getting credentials for provider", err.Error()) return } - for c.Warnings() != "" { + if c.Warnings() != "" { resp.Diagnostics.AddWarning("Warning getting credentials for provider", c.Warnings()) } client, err := config.NewClient(c, req.TerraformVersion) From 2afb3a3f47fd124d7cf407eaf582c9670bb0a8a2 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:29:40 +0200 Subject: [PATCH 32/41] fix linter --- internal/config/credentials_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/credentials_test.go b/internal/config/credentials_test.go index c10d443d62..86641189eb 100644 --- a/internal/config/credentials_test.go +++ b/internal/config/credentials_test.go @@ -439,8 +439,8 @@ func TestCoalesceAWSVars(t *testing.T) { awsVarsEmpty := &config.AWSVars{} testCases := map[string]struct { - awsVars []*config.AWSVars want *config.AWSVars + awsVars []*config.AWSVars }{ "First present AWS vars": { awsVars: []*config.AWSVars{awsVars1, awsVars2}, From e37facc6ace2c12de2c5741b56aefd1a58e52d60 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:29:40 +0200 Subject: [PATCH 33/41] don't log warnings in SDKv2 provider --- internal/config/credentials.go | 2 +- internal/provider/provider.go | 2 +- internal/provider/provider_sdk2.go | 7 +------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/internal/config/credentials.go b/internal/config/credentials.go index eeb1a5515f..d4d26da116 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -6,7 +6,7 @@ import ( "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" ) -// Credentials has all the authentication fields, it also coincides with fields that can be stored in AWS Secrets Manager. +// Credentials has all the authentication fields, it also matches with fields that can be stored in AWS Secrets Manager. type Credentials struct { AccessToken string `json:"access_token"` ClientID string `json:"client_id"` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9dae3903c9..737a90b34d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -333,8 +333,8 @@ func NewFrameworkProvider() provider.Provider { } func MuxProviderFactory() func() tfprotov6.ProviderServer { - v2Provider := NewSdkV2Provider() newProvider := NewFrameworkProvider() + v2Provider := NewSdkV2Provider() ctx := context.Background() upgradedSdkProvider, err := tf5to6server.UpgradeServer(ctx, v2Provider.GRPCProvider) if err != nil { diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 2816360e52..bf5f66955d 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -302,12 +302,7 @@ func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *s if err != nil { return nil, append(diags, diag.FromErr(fmt.Errorf("error getting credentials for provider: %w", err))...) } - if c.Warnings() != "" { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "warning getting credentials for provider: " + c.Warnings(), - }) - } + // Don't log possible warnings as they will be logged by the TPF provider. client, err := config.NewClient(c, provider.TerraformVersion) if err != nil { return nil, append(diags, diag.FromErr(fmt.Errorf("error initializing provider: %w", err))...) From f837b8e381e075fee158a3ff6987f880e99673f6 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:35:06 +0200 Subject: [PATCH 34/41] remove todo comments --- internal/provider/provider.go | 3 +-- internal/provider/provider_sdk2.go | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 737a90b34d..3801398d08 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -231,7 +231,6 @@ func getProviderVars(ctx context.Context, req provider.ConfigureRequest, resp *p assumeRoleARN = data.AssumeRole[0].RoleARN.ValueString() } baseURL := data.BaseURL.ValueString() - // TODO: check that is_mongodbgov_cloud works for undefined, true, false if data.IsMongodbGovCloud.ValueBool() && !slices.Contains(govAdditionalURLs, baseURL) { baseURL = govURL } @@ -333,8 +332,8 @@ func NewFrameworkProvider() provider.Provider { } func MuxProviderFactory() func() tfprotov6.ProviderServer { - newProvider := NewFrameworkProvider() v2Provider := NewSdkV2Provider() + newProvider := NewFrameworkProvider() ctx := context.Background() upgradedSdkProvider, err := tf5to6server.UpgradeServer(ctx, v2Provider.GRPCProvider) if err != nil { diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index bf5f66955d..6a4975cb66 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -318,7 +318,6 @@ func getSDKv2ProviderVars(d *schema.ResourceData) *config.Vars { assumeRoleARN = assumeRoles[0].(map[string]any)["role_arn"].(string) } baseURL := d.Get("base_url").(string) - // TODO: check that is_mongodbgov_cloud works for undefined, true, false if d.Get("is_mongodbgov_cloud").(bool) && !slices.Contains(govAdditionalURLs, baseURL) { baseURL = govURL } From 9e2ce3acd11bcb827a0b2ac3354e9d0cfeff5820 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:49:09 +0200 Subject: [PATCH 35/41] Update internal/config/credentials.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/config/credentials.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/credentials.go b/internal/config/credentials.go index d4d26da116..14adc7bdb4 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -66,7 +66,7 @@ func (c *Credentials) Warnings() string { if !c.IsPresent() { return "No credentials set" } - // Prefer specific checks over generaric code as there are few combinations and code is clearer. + // Prefer specific checks over generic code as there are few combinations and code is clearer. if c.HasAccessToken() && c.HasServiceAccount() && c.HasDigest() { return "Access Token will be used although Service Account and API Keys are also set" } From c1153b5a7770c9982db28cb1cde0e1796878ed05 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:49:26 +0200 Subject: [PATCH 36/41] Update internal/config/client.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/config/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/config/client.go b/internal/config/client.go index c4d51912c7..574eb579a0 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -80,8 +80,8 @@ type MongoDBClient struct { AtlasV220240530 *admin20240530.APIClient // used in advanced_cluster and cloud_backup_schedule for avoiding breaking changes (supporting deprecated replication_specs.id) AtlasV220241113 *admin20241113.APIClient // used in teams and atlas_users to avoiding breaking changes Realm *RealmClient - BaseURL string // neeeded by organization resource - TerraformVersion string // neeeded by organization resource + BaseURL string // needed by organization resource + TerraformVersion string // needed by organization resource } type RealmClient struct { From f4bb8826926b884d1cbf3526de534474d1135a0e Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:18:15 +0200 Subject: [PATCH 37/41] no need to get the credentials --- internal/provider/aws_credentials.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/internal/provider/aws_credentials.go b/internal/provider/aws_credentials.go index 9f7e2f41c5..34ceaf93fb 100644 --- a/internal/provider/aws_credentials.go +++ b/internal/provider/aws_credentials.go @@ -43,12 +43,6 @@ func getAWSCredentials(c *config.AWSVars) (*config.Credentials, error) { EndpointResolver: endpoints.ResolverFunc(stsCustResolverFn), })) creds := stscreds.NewCredentials(sess, c.AssumeRoleARN) - if _, err := sess.Config.Credentials.Get(); err != nil { - return nil, err - } - if _, err := creds.Get(); err != nil { - return nil, err - } secretString, err := secretsManagerGetSecretValue(sess, &aws.Config{Credentials: creds, Region: aws.String(c.Region)}, c.SecretName) if err != nil { return nil, err From 5b7ba2af740f164eb5590f409b385aaf5d7efc6b Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:02:02 +0200 Subject: [PATCH 38/41] fix if assume_role is defined but role_arn is not --- internal/provider/provider_sdk2.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/provider/provider_sdk2.go b/internal/provider/provider_sdk2.go index 6a4975cb66..0259167d13 100644 --- a/internal/provider/provider_sdk2.go +++ b/internal/provider/provider_sdk2.go @@ -315,7 +315,9 @@ func getSDKv2ProviderVars(d *schema.ResourceData) *config.Vars { assumeRoleARN := "" assumeRoles := d.Get("assume_role").([]any) if len(assumeRoles) > 0 { - assumeRoleARN = assumeRoles[0].(map[string]any)["role_arn"].(string) + if assumeRole, ok := assumeRoles[0].(map[string]any); ok { + assumeRoleARN = assumeRole["role_arn"].(string) + } } baseURL := d.Get("base_url").(string) if d.Get("is_mongodbgov_cloud").(bool) && !slices.Contains(govAdditionalURLs, baseURL) { From 5f2c411599be1495a2b6768ce556bd24a31dba3b Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:53:37 +0200 Subject: [PATCH 39/41] remove unused ctx param --- .../eventtrigger/data_source_event_triggers.go | 1 - .../service/organization/resource_organization.go | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/service/eventtrigger/data_source_event_triggers.go b/internal/service/eventtrigger/data_source_event_triggers.go index 9fa885cafd..02295a331d 100644 --- a/internal/service/eventtrigger/data_source_event_triggers.go +++ b/internal/service/eventtrigger/data_source_event_triggers.go @@ -144,7 +144,6 @@ func PluralDataSource() *schema.Resource { } func dataSourceMongoDBAtlasEventTriggersRead(d *schema.ResourceData, meta any) error { - // Get client connection. ctx := context.Background() conn, err := meta.(*config.MongoDBClient).Realm.Get(ctx) if err != nil { diff --git a/internal/service/organization/resource_organization.go b/internal/service/organization/resource_organization.go index 0cd4800033..3cd342f7f0 100644 --- a/internal/service/organization/resource_organization.go +++ b/internal/service/organization/resource_organization.go @@ -113,7 +113,7 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag. if err := ValidateAPIKeyIsOrgOwner(conversion.ExpandStringList(d.Get("role_names").(*schema.Set).List())); err != nil { return diag.FromErr(err) } - conn := getAtlasV2Connection(ctx, d, meta) // Using provider credentials. + conn := getAtlasV2Connection(d, meta) // Using provider credentials. organization, resp, err := conn.OrganizationsApi.CreateOrg(ctx, newCreateOrganizationRequest(d)).Execute() if err != nil { if validate.StatusNotFound(resp) && !strings.Contains(err.Error(), "USER_NOT_FOUND") { @@ -128,7 +128,7 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag. if err := d.Set("public_key", organization.ApiKey.GetPublicKey()); err != nil { return diag.FromErr(fmt.Errorf("error setting `public_key`: %s", err)) } - conn = getAtlasV2Connection(ctx, d, meta) // Using new credentials from the created organization. + conn = getAtlasV2Connection(d, meta) // Using new credentials from the created organization. orgID := organization.Organization.GetId() _, _, errUpdate := conn.OrganizationsApi.UpdateOrgSettings(ctx, orgID, newOrganizationSettings(d)).Execute() if errUpdate != nil { @@ -146,7 +146,7 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag. } func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - conn := getAtlasV2Connection(ctx, d, meta) + conn := getAtlasV2Connection(d, meta) ids := conversion.DecodeStateID(d.Id()) orgID := ids["org_id"] @@ -194,7 +194,7 @@ func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Di } func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - conn := getAtlasV2Connection(ctx, d, meta) + conn := getAtlasV2Connection(d, meta) ids := conversion.DecodeStateID(d.Id()) orgID := ids["org_id"] for _, attr := range attrsCreateOnly { @@ -227,7 +227,7 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag. } func resourceDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - conn := getAtlasV2Connection(ctx, d, meta) + conn := getAtlasV2Connection(d, meta) ids := conversion.DecodeStateID(d.Id()) orgID := ids["org_id"] @@ -293,7 +293,7 @@ func ValidateAPIKeyIsOrgOwner(roles []string) error { // getAtlasV2Connection uses the created credentials for the organization if they exist. // Otherwise, it uses the provider credentials, e.g. if the resource was imported. -func getAtlasV2Connection(ctx context.Context, d *schema.ResourceData, meta any) *admin.APIClient { +func getAtlasV2Connection(d *schema.ResourceData, meta any) *admin.APIClient { currentClient := meta.(*config.MongoDBClient) publicKey := d.Get("public_key").(string) privateKey := d.Get("private_key").(string) From 25c351f6f3338286fd81d7dbe4f3f6a2279f15ee Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 14:16:50 +0200 Subject: [PATCH 40/41] changelog --- .changelog/3738.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/3738.txt diff --git a/.changelog/3738.txt b/.changelog/3738.txt new file mode 100644 index 0000000000..dc2ae8f702 --- /dev/null +++ b/.changelog/3738.txt @@ -0,0 +1,3 @@ +```release-note:bug +provider: Enforces strict hierarchy when selecting the credential source (AWS Secrets Manager, provider attributes or environment variables) to prevent mix-and-match behavior where some values could be read from different sources +``` From 6df44c7b59bd7572023d3b80676eb6a96c56860c Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 6 Oct 2025 14:50:35 +0200 Subject: [PATCH 41/41] Update .changelog/3738.txt Co-authored-by: kanchana-mongodb <54281287+kanchana-mongodb@users.noreply.github.com> --- .changelog/3738.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/3738.txt b/.changelog/3738.txt index dc2ae8f702..2c423e83a5 100644 --- a/.changelog/3738.txt +++ b/.changelog/3738.txt @@ -1,3 +1,3 @@ ```release-note:bug -provider: Enforces strict hierarchy when selecting the credential source (AWS Secrets Manager, provider attributes or environment variables) to prevent mix-and-match behavior where some values could be read from different sources +provider: Enforces strict hierarchy when selecting the credential source such as AWS Secrets Manager, provider attributes, or environment variables to prevent combining with values from different sources ```