Skip to content

feat(sns): read-first subscription reconciliation with typed managedAttributes - #618

Merged
CarlosGamero merged 4 commits into
feat/subscription_locate_modefrom
feat/subscription-readonly-check
Oct 2, 2026
Merged

CarlosGamero merged 4 commits into
feat/subscription_locate_modefrom
feat/subscription-readonly-check

Conversation

@kibertoad

@kibertoad kibertoad commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Every consumer init() wrote to its subscription even when nothing had changed: Subscribe on the creation path, and one SetSubscriptionAttributes per attribute on the locateOnly path and for the reused DLQ. There was also no way to say which attributes the application owns, so an attribute removed from the config stayed on the subscription forever. This PR reads first, writes only what differs, and adds a typed managedAttributes list.

Stacked on #608, so merge that one first.

Changes

  • assertSubscription looks up the existing subscription (ListSubscriptionsByTopic) and reads its attributes (GetSubscriptionAttributes). If everything matches, it returns without writing. If something differs, it writes only those attributes when updateAttributesIfExists is on, and throws otherwise. The error lists the differing attribute names.
  • Subscribe is only called when no confirmed subscription exists. If it then fails with "already exists with different attributes" (another instance won the race), the subscription is reconciled the same way as above.
  • New subscriptionConfig.managedAttributes?: SubscriptionManagedAttributeName[] on both the creation and the locateOnly variants. It defaults to all of FilterPolicy, FilterPolicyScope, RawMessageDelivery and RedrivePolicy.
    • A managed attribute missing from Attributes is reset. FilterPolicy is set to {} (as in the SNS docs). RedrivePolicy is removed by leaving AttributeValue out, because AWS rejects an empty string for it (the Terraform provider does the same). The other two go back to MessageAttributes and false.
    • An unmanaged attribute is never read or written.
    • Listing an unmanaged attribute in Attributes throws invalid_subscription_configuration.
  • setSubscriptionAttributes (used by locateOnly) applies the same read-first logic and runs even when Attributes is empty, so resets also happen in locate mode.
  • The consumer's DLQ RedrivePolicy write goes through setSubscriptionAttributes. With reuseConsumerDeadLetterQueue, RedrivePolicy is removed from the managed set. Without that, the subscription step would clear it and the DLQ step would set it again on every startup. Managing it explicitly alongside that flag throws.

Decisions

  • The type is limited to the four attributes that apply to SQS subscriptions and can be reset. Other keys in Attributes (e.g. ReplayPolicy) are still compared and written when present, but never reset.
  • Policy attributes are compared as parsed JSON, because SNS can return a different formatting than the one we sent. A missing value, "", the default value, and {} for FilterPolicy all count as unset.
  • FilterPolicyScope is not reset on its own when the subscription ends up without a filter policy. On its own, the scope has no effect.

Risk

  • Behaviour change through the default. Every attribute is managed unless configured otherwise. Existing subscriptions that carry attributes missing from the config (for example RawMessageDelivery or RedrivePolicy set by Terraform) are now reset when updateAttributesIfExists is on. When it is off, startup fails instead. This also covers locateOnly configs without Attributes. Services that share ownership with Terraform need managedAttributes: ['FilterPolicy', 'FilterPolicyScope'] before upgrading.
  • Startup now calls ListSubscriptionsByTopic (30 TPS per account) before anything else. Many consumers starting at once against topics with many subscriptions could hit throttling sooner than before.
  • When updateAttributesIfExists is off, the error message for differing attributes changes. The error code sns_subscription_creation_failed stays the same.
  • Removing a RedrivePolicy can't be tested on localstack, which rejects both an empty and an omitted value, so that test runs on fauxqs only. The reset values follow the AWS docs and the Terraform provider, but haven't been run against real AWS.

assertSubscription now looks up an existing subscription and reads its
attributes before doing anything. When they already match the config,
no Subscribe or SetSubscriptionAttributes call is made. Otherwise only
the differing attributes are written (or an error is thrown when
updateAttributesIfExists is off). The locateOnly path gets the same
read-first behaviour through setSubscriptionAttributes.

Adds subscriptionConfig.manageOnlyFilterPolicy, which limits checking
and writing to FilterPolicy and FilterPolicyScope.
@kibertoad kibertoad added the minor label Oct 1, 2026
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e8c6659a-857d-462f-9ab8-4a1495da875c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kibertoad
kibertoad marked this pull request as draft October 1, 2026 15:16
subscriptionConfig.managedAttributes lists the subscription attributes
the application owns, defaulting to FilterPolicy, FilterPolicyScope,
RawMessageDelivery and RedrivePolicy. A managed attribute missing from
Attributes is now reset on the existing subscription, unmanaged ones
are never read or written, and configuring an unmanaged one throws.

The consumer DLQ RedrivePolicy write goes through the read-first
setSubscriptionAttributes and RedrivePolicy is excluded from the
managed set when reuseConsumerDeadLetterQueue is on, so the two never
fight over it.
@kibertoad kibertoad changed the title feat(sns): skip subscription writes when attributes are already correct feat(sns): read-first subscription reconciliation with typed managedAttributes Oct 1, 2026
@kibertoad
kibertoad marked this pull request as ready for review October 1, 2026 15:23
@kibertoad
kibertoad requested a review from CarlosGamero October 1, 2026 15:23
LocalStack rejects any RedrivePolicy that is not a policy with a valid
deadLetterTargetArn, so it cannot remove one. The reset test no longer
sets RedrivePolicy, and its removal is covered by a separate test that
runs on fauxqs only.
Real AWS rejects SetSubscriptionAttributes with an empty RedrivePolicy;
the attribute is removed by leaving AttributeValue out, which is what
the Terraform AWS provider does. FilterPolicy is reset with "{}" as in
the SNS docs.
@CarlosGamero

Copy link
Copy Markdown
Collaborator

Merging this on #608 to trigger a single release and ask facilitate review of the new feature

@CarlosGamero
CarlosGamero merged commit cd6a94f into feat/subscription_locate_mode Oct 2, 2026
9 checks passed
@CarlosGamero
CarlosGamero deleted the feat/subscription-readonly-check branch October 2, 2026 07:43
CarlosGamero added a commit that referenced this pull request Oct 2, 2026
* feat: adding subscription locateOnly mode

* Adding deprecation notice

* Improve validation to avoid locate only subscription when creating topic and queue (not possible)

* Adding tests to cover changes

* readme update

* readme fix

* feat(sns): read-first subscription reconciliation with typed managedAttributes (#618)

* feat(sns): check subscription attributes before writing them

assertSubscription now looks up an existing subscription and reads its
attributes before doing anything. When they already match the config,
no Subscribe or SetSubscriptionAttributes call is made. Otherwise only
the differing attributes are written (or an error is thrown when
updateAttributesIfExists is off). The locateOnly path gets the same
read-first behaviour through setSubscriptionAttributes.

Adds subscriptionConfig.manageOnlyFilterPolicy, which limits checking
and writing to FilterPolicy and FilterPolicyScope.

* feat(sns): replace manageOnlyFilterPolicy with typed managedAttributes

subscriptionConfig.managedAttributes lists the subscription attributes
the application owns, defaulting to FilterPolicy, FilterPolicyScope,
RawMessageDelivery and RedrivePolicy. A managed attribute missing from
Attributes is now reset on the existing subscription, unmanaged ones
are never read or written, and configuring an unmanaged one throws.

The consumer DLQ RedrivePolicy write goes through the read-first
setSubscriptionAttributes and RedrivePolicy is excluded from the
managed set when reuseConsumerDeadLetterQueue is on, so the two never
fight over it.

* test(sns): skip redrive policy removal on localstack

LocalStack rejects any RedrivePolicy that is not a policy with a valid
deadLetterTargetArn, so it cannot remove one. The reset test no longer
sets RedrivePolicy, and its removal is covered by a separate test that
runs on fauxqs only.

* fix(sns): remove RedrivePolicy by omitting its value

Real AWS rejects SetSubscriptionAttributes with an empty RedrivePolicy;
the attribute is removed by leaving AttributeValue out, which is what
the Terraform AWS provider does. FilterPolicy is reset with "{}" as in
the SNS docs.

* chore: fauxqs bump

* readme fix

---------

Co-authored-by: Igor Savin <iselwin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants