Skip to content

feat: Adds without_initial_secret to mongodbatlas_service_account - #4727

Open
EspenAlbert wants to merge 14 commits into
masterfrom
CLOUDP-444252_provider-allow-sa-without-secret
Open

EspenAlbert wants to merge 14 commits into
masterfrom
CLOUDP-444252_provider-allow-sa-without-secret

Conversation

@EspenAlbert

@EspenAlbert EspenAlbert commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds a without_initial_secret attribute to mongodbatlas_service_account. When set to true, the provider sends withoutInitialSecret: true to the Atlas API, so Atlas creates the Service Account without generating a bootstrap secret. The resource then has an empty secrets list, and the configuration creates its own secrets with mongodbatlas_service_account_secret.

secret_expires_after_hours is no longer required on create. It stays optional and create-only. The API contract is:

  • without_initial_secret = true requires secret_expires_after_hours to be unset.
  • Setting neither is rejected by the API (ARGUMENT_NOT_PROVIDED).
  • Setting both is rejected by the API (MUTUALLY_EXCLUSIVE_ARGUMENTS).

The provider relies on the API error for these two validation cases rather than duplicating the check at plan time.

Link to any related issue(s): CLOUDP-444252, CLOUDP-375930

Type of change:

  • Bug fix (non-breaking change which fixes an issue). Please, add the "bug" label to the PR.
  • New feature (non-breaking change which adds functionality). Please, add the "enhancement" label to the PR. A migration guide must be created or updated if the new feature will go in a major version.
  • Breaking change (fix or feature that would cause existing functionality to not work as expected). Please, add the "breaking change" label to the PR. A migration guide must be created or updated.
  • This change requires a documentation update
  • Documentation fix/enhancement

Required Checklist:

  • I have signed the MongoDB CLA
  • I have read the contributing guides
  • I have checked that this change does not generate any credentials and that they are NOT accidentally logged anywhere.
  • I have added tests that prove my fix is effective or that my feature works per HashiCorp requirements
  • I have added any necessary documentation (if appropriate)
  • I have run make fix and verified my code
  • If changes include deprecations or removals I have added appropriate changelog entries.
  • If changes include removal or addition of 3rd party GitHub actions, I updated our internal document. Reach out to the APIx Integration slack channel to get access to the internal document.

Further comments

Schema: without_initial_secret is Optional + Computed

without_initial_secret is Optional and Computed, and uses customplanmodifier.CreateOnlyBoolWithDefault(false). The plan modifier injects the false default into the plan on create so the resource Create handler needs no custom logic, and it rejects a change on update. Terraform Plugin Framework requires a plan-time default to sit on an Optional + Computed attribute, so the attribute is Computed as well.

true is only meaningful at create time. On update, the API omits withoutInitialSecret from responses, so the state value stays whatever the user set at create (default false), and the attribute is not re-read.

Codegen: create-only plan modifier for computed optional bools with a default

setCreateOnlyValue in tools/codegen/codespec/config.go skipped every computed attribute when deciding whether to emit a create-only plan modifier. That guard exists because a plain CreateOnly() modifier can see an unknown value at plan time on a computed attribute and fail the update validation.

This PR narrows the guard: a called-out ComputedOptional bool that has a default and that the API never returns keeps the create-only behavior, because CreateOnlyBoolWithDefault handles the unknown value and the default. All other computed attributes keep the previous behavior. TestApplyTransformationsToResource_CreateOnlyTransformation covers the new cases.

Regenerating all resources with make autogen-generate-resources step=code-gen changes only without_initial_secret.

Generated files

The tracked OpenAPI spec in tools/codegen/atlasapispec/ is stale for both withoutInitialSecret and systemManaged. I did not commit a refreshed spec: the upstream dev spec is used as a local input only. Both the raw and flattened spec files are left at HEAD, and no spec diff is part of this PR.

The flow used to update the model:

  1. make autogen-update-api-spec spec_source=/tmp/upstream-v2-dev.yaml writes the raw and flattened spec locally.
  2. make autogen-generate-resources resource_name=service_account step=model-gen regenerates tools/codegen/models/service_account.yaml from that local spec.
  3. make autogen-generate-resources resource_name=service_account step=code-gen regenerates internal/serviceapi/serviceaccount/resource_schema.go.
  4. git checkout -- tools/codegen/atlasapispec/ reverts the spec files so they stay out of the PR.

The upstream dev spec now carries the systemManaged field on OrgServiceAccount (this is what the hand-vendored system_managed attribute came from) and withoutInitialSecret on OrgServiceAccountRequest, and it drops secretExpiresAfterHours from the request's required list. Regeneration therefore reproduces system_managed instead of dropping it, so the model no longer needs a hand edit.

CI's check-autogen-resources workflow runs autogen-generate-resources step=code-gen against the committed models, so the committed model is the source of truth for generated code and the local spec is not needed to reproduce it.

@EspenAlbert EspenAlbert changed the title feat: Allow creating a Service Account without a bootstrap secret feat: Add without_initial_secret to mongodbatlas_service_account Sep 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Outstanding acceptance-test and generated-documentation issues must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds without_initial_secret support for MongoDB Atlas Service Accounts, enabling Terraform-managed secret creation.

Changes:

  • Adds the new create-only schema attribute and updates code generation.
  • Adds acceptance tests and dedicated example flows.
  • Updates documentation and changelog entries.
File summaries
File Summary
tools/codegen/models/service_account.yaml Updates Service Account model metadata.
tools/codegen/config.yml Configures schema overrides and descriptions.
tools/codegen/codespec/config.go Supports create-only computed booleans with defaults.
tools/codegen/codespec/config_test.go Tests code-generation transformations.
templates/resources/service_account.md.tmpl Updates resource documentation examples.
templates/data-sources/service_accounts.md.tmpl Updates plural data-source documentation.
templates/data-sources/service_account.md.tmpl Updates singular data-source documentation.
internal/serviceapi/serviceaccount/resource_test.go Adds acceptance coverage and test configuration updates.
internal/serviceapi/serviceaccount/resource_schema.go Adds the new resource attribute.
examples/mongodbatlas_service_account/without_initial_secret/versions.tf Defines Terraform and provider requirements.
examples/mongodbatlas_service_account/without_initial_secret/variables.tf Defines the organization variable.
examples/mongodbatlas_service_account/without_initial_secret/README.md Documents secret-free Service Account creation.
examples/mongodbatlas_service_account/without_initial_secret/providers.tf Configures the provider example.
examples/mongodbatlas_service_account/without_initial_secret/main.tf Demonstrates explicit secret creation.
examples/mongodbatlas_service_account/variables.tf Removes obsolete root variables.
examples/mongodbatlas_service_account/README.md Indexes the available example flows.
examples/mongodbatlas_service_account/provider.tf Removes obsolete root provider configuration.
examples/mongodbatlas_service_account/bootstrap_secret/versions.tf Defines Terraform and provider requirements.
examples/mongodbatlas_service_account/bootstrap_secret/variables.tf Defines the organization variable.
examples/mongodbatlas_service_account/bootstrap_secret/README.md Documents bootstrap-secret usage.
examples/mongodbatlas_service_account/bootstrap_secret/providers.tf Configures the provider example.
examples/mongodbatlas_service_account/bootstrap_secret/main.tf Provides the bootstrap-secret example.
docs/resources/service_account.md Documents the new attribute and example flows.
.changelog/4721.txt Adds the enhancement release note.
Review details

Files not reviewed (1)

  • internal/serviceapi/serviceaccount/resource_schema.go: Generated file

Suppressed comments (5)

.changelog/4721.txt:1

  • This changelog is named .changelog/4721.txt, but this pull request is #4727. Changelog tooling associates the release-note filename with the PR number, so the entry may be skipped or attributed to another change; please rename it to .changelog/4727.txt.
```release-note:enhancement

docs/resources/service_account.md:54

  • The newly added example directories are not present at the pinned v2.17.0 tag, so these links in the generated resource documentation currently return 404. Point the new-example links at a ref that contains this change (for example master, or update them to the release tag when the feature is released).
- [Bootstrap secret](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.17.0/examples/mongodbatlas_service_account/bootstrap_secret)
- [Without initial secret](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.17.0/examples/mongodbatlas_service_account/without_initial_secret)

internal/serviceapi/serviceaccount/resource_schema.go:95

  • This Optional + Computed create-only attribute has state-retention behavior that is not covered by the acceptance tests: they only set true and import. Add lifecycle coverage for omitting it on create (including a subsequent empty plan) and for removing it from a configuration after it was set, asserting whether the value should be retained or rejected; otherwise the custom default/state handling can regress without detection.
    internal/serviceapi/serviceaccount/resource_test.go:124
  • Please add an acceptance step that removes without_initial_secret after creating it as true. Because this attribute is Optional+Computed and create-only, omission is expected to preserve the state value without an update; that lifecycle behavior is not covered by the current create and false-to-true rejection steps.
				Config: configBasic(orgID, name, "Without initial secret", []string{"ORG_READ_ONLY"}, nil, "without_initial_secret = true"),
				Check: resource.ComposeAggregateTestCheckFunc(
					checkExists(resourceName),
					resource.TestCheckResourceAttr(resourceName, "secrets.#", "0"),
					resource.TestCheckResourceAttr(resourceName, "without_initial_secret", "true"),

templates/resources/service_account.md.tmpl:19

  • The newly added example directories are not present at the pinned v2.17.0 tag, so both links rendered from this template currently return 404. Point these new-example links at a ref that contains this change (for example master, or update them to the release tag when the feature is released).
- [Bootstrap secret](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.17.0/examples/mongodbatlas_service_account/bootstrap_secret)
- [Without initial secret](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.17.0/examples/mongodbatlas_service_account/without_initial_secret)
  • Files reviewed: 21/24 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/serviceapi/serviceaccount/resource_test.go
Comment thread internal/serviceapi/serviceaccount/resource_test.go
Comment thread templates/data-sources/service_account.md.tmpl Outdated
Comment thread templates/data-sources/service_accounts.md.tmpl Outdated
Comment thread internal/serviceapi/serviceaccount/resource_test.go Outdated
Comment thread internal/serviceapi/serviceaccount/resource_test.go
Comment thread internal/serviceapi/serviceaccount/resource_test.go
Comment thread templates/data-sources/service_account.md.tmpl Outdated
Comment thread templates/data-sources/service_accounts.md.tmpl Outdated
Comment thread internal/serviceapi/serviceaccount/resource_test.go Outdated
Comment thread .changelog/4727.txt
Comment thread templates/resources/service_account.md.tmpl Outdated
@EspenAlbert EspenAlbert changed the title feat: Add without_initial_secret to mongodbatlas_service_account feat: Adds without_initial_secret to mongodbatlas_service_account Sep 15, 2026
@EspenAlbert
EspenAlbert marked this pull request as ready for review September 15, 2026 11:52
@EspenAlbert
EspenAlbert requested review from a team as code owners September 15, 2026 11:52
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

APIx bot: a message has been sent to Docs Slack channel
APIx bot: a message has been sent to Docs Slack channel

@augmentcode

augmentcode Bot commented Sep 15, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: Adds support for creating organization Service Accounts without an Atlas-generated bootstrap secret.

Changes:

  • Introduces the optional, create-only without_initial_secret attribute on mongodbatlas_service_account.
  • Maps the attribute to the Atlas create request and excludes it from update requests.
  • Makes secret_expires_after_hours optional rather than provider-required on creation.
  • Leaves mutually exclusive or missing-create-input validation to the Atlas API.
  • Adds acceptance coverage for no-initial-secret creation, imports, and create-only behavior.
  • Updates generated models, schema configuration, and codegen support to clear API-specified defaults.
  • Reworks resource and data-source examples around explicitly managed service-account secrets.
  • Updates generated documentation, import guidance, and the enhancement changelog entry.

Technical Notes: Request-only values are retained from Terraform's plan/state because Atlas does not return them on reads; the examples create the first secret through mongodbatlas_service_account_secret so Terraform owns its lifecycle.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. No suggestions at this time.

Comment augment review to trigger a new review at any time.

Comment thread internal/serviceapi/serviceaccount/resource_test.go

@manupedrozo manupedrozo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, left comments.

  • Planning to cover project_service_account changes and the rotation guide in a separate PR?
  • Ideally we'd also update other examples/guides to use the without_initial_secret attribute. A relevant one is the PAK to SA migration guide.

Comment thread docs/resources/service_account.md Outdated
Comment thread examples/mongodbatlas_service_account/bootstrap_secret/providers.tf Outdated
Comment thread examples/mongodbatlas_service_account/without_initial_secret/providers.tf Outdated
Comment thread examples/mongodbatlas_service_account/without_initial_secret/main.tf Outdated
Comment thread internal/serviceapi/serviceaccount/resource_test.go Outdated
Comment thread tools/codegen/codespec/config.go Outdated
Comment on lines +452 to +459
// A plain CreateOnly plan modifier is not applied to computed attributes: their value may be
// unknown at plan time, which would falsely fail the update validation. The exception is a
// computed optional bool with a default that the API never returns, where
// CreateOnlyBoolWithDefault sets the default and keeps the create-only behavior.
if attr.ComputedOptionalRequired == Computed {
return
}
if attr.ComputedOptionalRequired == ComputedOptional && !isCreateOnlyBoolWithDefault(attr) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we leave the attribute optional only, could we avoid the override + this change?
Seems like we are forcing computed to an attribute that given the api spec is not and then we are fixing it back here, I might be missing something!

If we do want this behavior, I think we should add this particular createOnly plan modifier through config/custom hook instead of changing the codegen for a case we are forcing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got the same doubt, would be clear on API behaviour and work backwards from there.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented the optional-only design: the provider clears the API default and uses the normal create-only modifier, so it does not write an unconfigured value to state. This removes the computed override and prior specialized generator path. The PR description still needs the corresponding Optional-only/ClearDefault update.

Comment thread internal/serviceapi/serviceaccount/resource_test.go Outdated
@JuliaMongo
JuliaMongo self-requested a review September 15, 2026 14:18

@JuliaMongo JuliaMongo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for copy review with a few suggestions to use active voice in a few places (all optional).

Comment thread docs/resources/service_account.md Outdated
Comment thread docs/resources/service_account.md Outdated
Comment thread docs/resources/service_account.md Outdated
Comment thread examples/mongodbatlas_service_account/bootstrap_secret/README.md Outdated
Comment thread examples/mongodbatlas_service_account/bootstrap_secret/README.md Outdated
Comment thread templates/resources/service_account.md.tmpl Outdated
Comment thread tools/codegen/models/service_account.yaml Outdated
Comment thread tools/codegen/models/service_account.yaml Outdated
Comment thread tools/codegen/config.yml Outdated
Comment thread tools/codegen/config.yml Outdated
Comment thread tools/codegen/config.yml Outdated
Comment thread tools/codegen/codespec/config.go Outdated
Comment on lines +452 to +459
// A plain CreateOnly plan modifier is not applied to computed attributes: their value may be
// unknown at plan time, which would falsely fail the update validation. The exception is a
// computed optional bool with a default that the API never returns, where
// CreateOnlyBoolWithDefault sets the default and keeps the create-only behavior.
if attr.ComputedOptionalRequired == Computed {
return
}
if attr.ComputedOptionalRequired == ComputedOptional && !isCreateOnlyBoolWithDefault(attr) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got the same doubt, would be clear on API behaviour and work backwards from there.

@EspenAlbert

Copy link
Copy Markdown
Collaborator Author

Nice, left comments.

  • Planning to cover project_service_account changes and the rotation guide in a separate PR?
  • Ideally we'd also update other examples/guides to use the without_initial_secret attribute. A relevant one is the PAK to SA migration guide.

Thanks for the comments, will do a tech sync item to make the decision, seems tempting to simplify this for users and use without_initial_secret when the secret_expires is not set.

@EspenAlbert
EspenAlbert marked this pull request as draft September 16, 2026 06:00
Comment thread internal/serviceapi/serviceaccount/resource_test.go Outdated
Comment thread tools/codegen/codespec/config.go Outdated
Comment on lines +452 to +459
// A plain CreateOnly plan modifier is not applied to computed attributes: their value may be
// unknown at plan time, which would falsely fail the update validation. The exception is a
// computed optional bool with a default that the API never returns, where
// CreateOnlyBoolWithDefault sets the default and keeps the create-only behavior.
if attr.ComputedOptionalRequired == Computed {
return
}
if attr.ComputedOptionalRequired == ComputedOptional && !isCreateOnlyBoolWithDefault(attr) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented the optional-only design: the provider clears the API default and uses the normal create-only modifier, so it does not write an unconfigured value to state. This removes the computed override and prior specialized generator path. The PR description still needs the corresponding Optional-only/ClearDefault update.

Comment thread internal/serviceapi/serviceaccount/resource_test.go Outdated
Comment thread docs/resources/service_account.md Outdated
Comment thread docs/resources/service_account.md Outdated
Comment thread templates/resources/service_account.md.tmpl Outdated
Comment thread tools/codegen/models/service_account.yaml Outdated
Comment thread tools/codegen/config.yml Outdated
Comment thread examples/mongodbatlas_service_account/README.md
@EspenAlbert
EspenAlbert marked this pull request as ready for review September 18, 2026 12:36

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

"without_initial_secret": schema.BoolAttribute{
Optional: true,
MarkdownDescription: "When true, creates the Service Account without generating an initial secret. If you set this field to true, do not set `secret_expires_after_hours`.",
PlanModifiers: []planmodifier.Bool{customplanmodifier.CreateOnly()},

@augmentcode augmentcode Bot Sep 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateOnly() permits a null state to change to true, which is the normal state after creating a resource with this optional attribute omitted. Terraform will therefore run an update that omits this field from the PATCH but persists true from the plan, leaving an Atlas account with its bootstrap secret while state claims it was created without one (as the new acceptance test explicitly permits).

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@AgustinBettati AgustinBettati left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for followups! 2 smaller questions on my side

Comment thread .changelog/4727.txt
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/mongodbatlas_service_account: Adds `without_initial_secret` attribute to create a Service Account without generating an initial secret. `secret_expires_after_hours` is now optional when `without_initial_secret` is `true`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't project_service_account have the same change? Or was this not added on the API side?

Comment on lines +395 to +396
// clearStaticDefault drops a static default copied from the API spec. Code generation otherwise emits
// a schema Default, which forces computed_optional and is rejected by terraform-plugin-framework on a

@AgustinBettati AgustinBettati Sep 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generation otherwise emits a schema Default
Is this statement accurate? From what I recall we used API Spec default to determine if an attribute was Optional + Computed, or only Optional (no default value actually reflected in schema). With this in mind, curious of config override of defining it is optional only is sufficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants