Skip to content

feat(smtp): write-only password support for SMTP credentials (#52)#76

Open
dimoschi wants to merge 7 commits into
mainfrom
feat-52-write-only-smtp-password
Open

feat(smtp): write-only password support for SMTP credentials (#52)#76
dimoschi wants to merge 7 commits into
mainfrom
feat-52-write-only-smtp-password

Conversation

@dimoschi

Copy link
Copy Markdown
Collaborator

Summary

Adds first-class write-only password support to mailgun_smtp_credential, closing #52.

  • New password_wo (write-only, never stored in state) and password_wo_version (Int64) arguments; bumping the version rotates the password.
  • Legacy password argument is deprecated but remains fully functional (backward-compatible minor release).
  • Schema validators enforce password XOR (password_wo + password_wo_version).
  • Write-only values are read from config (never plan/state) and kept null in state, per the framework contract.
  • Import behavior preserved: imported credentials have no password in state; adopt password_wo/password_wo_version to enable rotation.
  • Docs (template + generated), example, and changelog updated; documents the Terraform CLI >= 1.11 requirement.

Implementation notes

  • Rotation/resolution logic is extracted into pure, unit-tested helpers (passwordForCreate, writeOnlyRotationRequested, resolveUpdatePassword) so the CRUD branching is testable without the Mailgun API client.
  • Legacy → write-only migration was validated by reasoning + unit tests (the password Computed attribute is pinned to null when the write-only path is used to avoid spurious diffs). It is not covered by a live acceptance test, and the ConflictsWith validator is covered by schema unit assertions rather than an acceptance step, given the shared 1-domain CI account.

Test Plan

  • go build ./..., go vet ./..., gofmt -l clean
  • go test ./... — unit tests pass (helpers + schema); acceptance tests skip without MAILGUN_API_KEY/MAILGUN_TEST_DOMAIN
  • make generate produces no docs drift
  • Acceptance: TF_ACC=1 MAILGUN_API_KEY=... MAILGUN_TEST_DOMAIN=... go test ./internal/provider/smtp_credentials/ -run TestAccSmtpCredentialResource_WriteOnly (create at v1 → no-op plan at v1 → rotate at v2) on Terraform CLI >= 1.11

dimoschi added 6 commits June 16, 2026 21:37
- Extend SmtpCredentialModel with PasswordWO (types.String) and
  PasswordWOVersion (types.Int64) tfsdk fields alongside the existing
  Password field.
- Add password_wo (WriteOnly, Optional, Sensitive) and
  password_wo_version (Optional) schema attributes with mutual
  conflict/require validators against the legacy password field.
- Deprecate the password schema attribute in favour of password_wo;
  it remains Computed+Optional for backward compatibility.
- TestSmtpCredentialResourceSchema_WriteOnlyPassword covers the new
  schema shape (red→green TDD cycle).
Two pure unexported helpers for write-only SMTP password support:
- passwordForCreate: prefers password_wo, falls back to legacy password
- writeOnlyRotationRequested: detects version bump as rotation signal

9 table-driven unit tests cover all paths (CRAP ≤3 for both functions).
Wire passwordForCreate (prefers password_wo over legacy password) into
Create. Read password_wo from req.Config since write-only values are
absent from plan/state. Pin plan.Password to null in state when the
write-only path is used to avoid leaving it unknown.

Add resolveUpdatePassword pure helper (100% unit-tested via
TestResolveUpdatePassword) that encapsulates the Update rotation
decision: write-only path rotates only on version bump, legacy null
preserves imported state, legacy empty errors, legacy non-empty rotates.

Note: Create and Update are framework lifecycle methods (I/O glue only);
all decision logic extracted into pure helpers with 100% unit coverage.

# Write-only password (recommended). Requires Terraform CLI >= 1.11.
# The secret is never written to Terraform state. Bump password_wo_version to rotate.
resource "random_password" "smtp" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
resource "random_password" "smtp" {
ephemeral "random_password" "smtp" {

Since the only destination of this is a wo field, we should use an ephemeral resources for random_password as well.

🔗 docs reference

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.

Good call. Switched the example to an ephemeral "random_password" and updated the reference to ephemeral.random_password.smtp.result so the generated secret never lands in state. Done in fc26208.

Comment thread docs/resources/smtp_credential.md Outdated

# Write-only password (recommended). Requires Terraform CLI >= 1.11.
# The secret is never written to Terraform state. Bump password_wo_version to rotate.
resource "random_password" "smtp" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
resource "random_password" "smtp" {
ephemeral "random_password" "smtp" {

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.

This block is generated from the example file, so it now shows the ephemeral resource after make generate (fc26208).

Comment thread docs/resources/smtp_credential.md Outdated
- `password` (String, Sensitive) The password for SMTP authentication. This is write-only and cannot be read back from the API. Set this when creating a credential or when rotating the password of an imported credential. Leave it unset to keep the existing password of an imported credential.
> **NOTE**: [Write-only arguments](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments) are supported in Terraform 1.11 and later.

- `password` (String, Sensitive, Deprecated) The password for SMTP authentication. This is write-only and cannot be read back from the API. Set this when creating a credential or when rotating the password of an imported credential. Leave it unset to keep the existing password of an imported credential.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
- `password` (String, Sensitive, Deprecated) The password for SMTP authentication. This is write-only and cannot be read back from the API. Set this when creating a credential or when rotating the password of an imported credential. Leave it unset to keep the existing password of an imported credential.
- `password` (String, Sensitive, Deprecated) The password for SMTP authentication. This is write-only and cannot be read back from the API. Set this when creating credentials or when rotating the password. Leave it unset to maintain the existing password during import.

Some suggestion on the text, highly optional

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.

Applied (at the schema Description source, then regenerated). fc26208.

Comment thread docs/resources/smtp_credential.md Outdated
> **NOTE**: [Write-only arguments](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments) are supported in Terraform 1.11 and later.

- `password` (String, Sensitive, Deprecated) The password for SMTP authentication. This is write-only and cannot be read back from the API. Set this when creating a credential or when rotating the password of an imported credential. Leave it unset to keep the existing password of an imported credential.
- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for SMTP authentication. The value is never stored in Terraform state. Set it together with password_wo_version and increment the version to rotate the password. Requires Terraform CLI >= 1.11.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for SMTP authentication. The value is never stored in Terraform state. Set it together with password_wo_version and increment the version to rotate the password. Requires Terraform CLI >= 1.11.
- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for SMTP authentication. The value is never stored in Terraform state. Always set it together with password_wo_version.

I think we can skip the password version specifics since we have a dedicated entry below

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.

Trimmed the version/CLI specifics from the password_wo description since password_wo_version has its own entry. fc26208.

Comment thread docs/resources/smtp_credential.md Outdated

- `password` (String, Sensitive, Deprecated) The password for SMTP authentication. This is write-only and cannot be read back from the API. Set this when creating a credential or when rotating the password of an imported credential. Leave it unset to keep the existing password of an imported credential.
- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for SMTP authentication. The value is never stored in Terraform state. Set it together with password_wo_version and increment the version to rotate the password. Requires Terraform CLI >= 1.11.
- `password_wo_version` (Number) Version counter for password_wo. Increment this value to rotate the write-only password. Required when password_wo is set.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
- `password_wo_version` (Number) Version counter for password_wo. Increment this value to rotate the write-only password. Required when password_wo is set.
- `password_wo_version` (Number) Version counter for password_wo. Increment this value to rotate the write-only password. **Required** when password_wo is set.

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.

Bolded Required on password_wo_version. fc26208.

…riptions

Address PR #76 review feedback from @dstrants:
- example uses ephemeral random_password so the generated secret stays out of state
- trim password_wo description (version specifics live in the dedicated entry)
- bold Required on password_wo_version
@dimoschi dimoschi requested a review from dstrants June 19, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants