Skip to content

fix: local PropagateTags: false is not overridden by global PropagateTags: true - #3978

Open
Adityaj0 wants to merge 1 commit into
aws:developfrom
Adityaj0:fix/implicit-api-propagate-tags-false-override
Open

fix: local PropagateTags: false is not overridden by global PropagateTags: true#3978
Adityaj0 wants to merge 1 commit into
aws:developfrom
Adityaj0:fix/implicit-api-propagate-tags-false-override

Conversation

@Adityaj0

Copy link
Copy Markdown

Summary

Fixes #3977.

ImplicitApiPlugin._add_tags_to_implicit_api_if_necessary() decided whether to copy a resource's Tags onto its generated implicit AWS::Serverless::Api/HttpApi resource with:

should_propagate_tags = resource.properties.get("PropagateTags") or globals_var.get("PropagateTags")

PropagateTags is bool | None (see the Pydantic schema in samtranslator/internal/schema_source/aws_serverless_function.py), where None means "not set" and False is a meaningful, explicit opt-out. or treats a local False the same as "not set," so it silently falls through to the global value — a function with an explicit PropagateTags: false still gets tags propagated to its implicit API if a global PropagateTags: true is set.

This checks is not None instead, so an explicit local value (including False) always wins over the global, consistent with the "local wins when present" semantics GlobalProperties._prefer_local already uses for the standard Globals merge (samtranslator/plugins/globals/globals.py).

Test plan

  • Added tests/translator/input/function_with_local_propagate_tags_false_overrides_global_true.yaml (local PropagateTags: false + global PropagateTags: true + global Tags) with expected output for all three partitions (aws, aws-cn, aws-us-gov), asserting the implicit API resource has no Tags.
  • Verified the new test fails against the pre-fix code (tags leak through) and passes with the fix.
  • python -m pytest tests/translator tests/plugins tests/parser — 2581 passed (same 5 pre-existing, unrelated SAR-timing/region failures as on develop).
  • ruff check / black --check clean on the changed file.

…Tags: true

ImplicitApiPlugin._add_tags_to_implicit_api_if_necessary() decided whether to
copy Tags onto the generated implicit API resource with:

    resource.properties.get("PropagateTags") or globals_var.get("PropagateTags")

PropagateTags is bool | None, where None means "not set" and False is a
meaningful, explicit opt-out. `or` treats a local False the same as "not
set" and silently falls through to the global value, so an explicit
`PropagateTags: false` on a function is overridden by a global
`PropagateTags: true`, propagating tags the caller explicitly opted out of.

This checks for None instead, matching the "local wins when present"
semantics that GlobalProperties._prefer_local already uses for the
standard Globals merge.

Fixes aws#3977
@Adityaj0
Adityaj0 requested a review from a team as a code owner August 14, 2026 06:59

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: bbc6a9a..2fb8a84
Files: 5
Comments: 1


Comments on lines outside the diff:

[samtranslator/plugins/api/implicit_api_plugin.py:115] [BUG] The PropagateTags precedence fix on the lines above is right, but the Tags line immediately below has the analogous precedence problem and is left unchanged. or makes a resource-level Tags value fully replace the global one, whereas the documented Globals behavior for dictionaries (see GlobalProperties._merge_dict in samtranslator/plugins/globals/globals.py) is a recursive merge.

Concretely:

Globals:
 Function:
   Tags:
     env: prod
Resources:
 ApiFunction:
   Type: AWS::Serverless::Function
   Properties:
     PropagateTags: true
     Tags:
       app: foo
     Events:
       ApiEvent:
         Type: Api
         Properties: {Path: /, Method: get}

The function and its generated resources end up with {env: prod, app: foo} (Globals merges, then propagate_tags runs), but ServerlessRestApi only receives {app: foo} because resource.properties.get("Tags") is truthy and the global Tags are never consulted. The global tag is silently dropped from the implicit API — the same class of "local silently discards global" bug this PR is fixing for PropagateTags.

Since the implicit API plugin runs before the Globals plugin, the merge has to be done explicitly here:

tags_properties = {**(globals_var.get("Tags") or {}), (resource.properties.get("Tags") or {})}

Note this assumes both are dicts, which the existing code already assumes (see the setdefault("Tags", {}).update(tags_properties) call below and its accompanying comment), so guarding for intrinsics is no worse than today. If you'd rather keep this PR minimal, that's reasonable — but it's worth splitting out rather than leaving, since the two lines now encode inconsistent global/local rules side by side.

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.

Implicit API: local PropagateTags: false is silently overridden by global PropagateTags: true

1 participant