fix: publish new Lambda version when AutoPublishAliasAllProperties references a changed parameter - #3965
Conversation
…operty references a changed parameter Fixes aws#3820. With `AutoPublishAliasAllProperties: true`, a property whose value comes from a template parameter — e.g. an Environment variable set to `!Ref SomeParam` — did not trigger a new Lambda version when the parameter value changed, so `sam deploy` published nothing. The version's logical id is a hash of the properties that should trigger a new version. The non-AllProperties (CodeUri) path resolves parameter references before hashing, with a comment explaining exactly why: an unresolved `{"Ref": "SomeParam"}` hashes identically regardless of the value supplied. The AllProperties path skipped that step and hashed the raw resource dict, so `{"Ref": "TestParameter"}` produced the same id whether the override was `2` or `3`. Resolve template parameter references on the AllProperties dict too. Pseudo parameters (AWS::Region, AWS::Partition, ...) are excluded: they are present in the resolver's parameter map but do not represent a template change, and resolving them would rewrite `Fn::Sub` strings that reference them and shift the version id of existing, unchanged templates. Excluding them keeps this a no-op for any template that does not reference a real parameter in a version-tracked property, so existing version ids are preserved — verified by the full translator suite passing unchanged (2174 tests). Testing: - New regression tests in test_function_resources.py assert that changing a referenced parameter value yields a different version logical id, and that an unchanged value is stable. Confirmed both fail against the pre-fix code. - Full tests/translator suite passes (2229 tests), so no golden-file version id changed — confirming backward compatibility, including the pseudo-parameter fixture (function_with_alias_and_all_properties_property) that a naive fix regressed.
… output Addresses review feedback on aws#3965. `resolve_parameter_refs` mutates the dict it is given -- `_traverse_dict` assigns back via `input_dict[key] = ...` -- and the values reachable from `_generate_resource_dict()` are the live objects from the user's template (`properties["Environment"] is function.Environment`). Resolving in place therefore inlined parameter values into the resources the translator actually emits, replacing `{"Ref": "TestParameter"}` with the literal on the AWS::Lambda::Function. Confirmed a NoEcho parameter value appeared as plaintext in the transformed template. The layer branch is affected the same way and is worse: `layer_properties` comes from ResourceResolver over the output template, so resolving could rewrite a different resource's emitted properties. Resolve against `copy.deepcopy(properties)` instead. This is what the resolver's own docstring warns about: "Don't pass this dictionary directly into transform's output because it changes the template structure by inlining parameter values." Adds two tests that a version-logical-id assertion cannot cover: the emitted function still carries `{"Ref": "TestParameter"}`, and a NoEcho value never appears anywhere in the output. Both fail without the deepcopy. Testing: 4 tests in the new class pass, and both new ones confirmed to fail against the pre-deepcopy code. Full tests/translator suite passes (2425). ruff and black clean.
|
An Alternative to avoid the breaking change is to add a new parameter to track this behavior. Which could be something like |
|
Sorry for the slow reply, @valerena — and thanks, the breaking-change framing is the right question to ask. My argument for keeping this as a fix rather than a new property: 1. The current behaviour contradicts the documented contract.
So this isn't introducing new semantics, it is making 2. The failure mode of today's behaviour is silent and bad. You change 3. A new opt-in property makes the common case the hard case. Scope of the change, concretely: it only affects templates that set On the second commit ( That said, this is your repo's compatibility bar, not mine — if you'd still rather have the opt-in property, say so and I'll rework it that way. I'd just like the decision recorded here so it doesn't sit for another month. Happy to take it to a quick sync if that's faster. |
Issue #, if available
Fixes #3820
Description of changes
With
AutoPublishAliasAllProperties: true, a property whose value comes from a template parameter — e.g. anEnvironmentvariable set to!Ref SomeParam— did not trigger a new Lambda version when the parameter value changed, sosam deploypublished nothing.The version's logical id is a hash of the properties that should trigger a new version. The non-
AllProperties(CodeUri) path resolves parameter references before hashing, with a comment explaining exactly why — an unresolved{"Ref": "SomeParam"}hashes identically regardless of the value supplied:The
AllPropertiespath skipped that step and hashed the raw resource dict, so{"Ref": "TestParameter"}produced the same id whether the override was2or3, and no newAWS::Lambda::Versionwas created.This change resolves template parameter references on the
AllPropertiesdict too. Pseudo parameters (AWS::Region,AWS::Partition, ...) are deliberately excluded: they live in the resolver's parameter map but do not represent a template change, and resolving them would rewriteFn::Substrings that reference them — shifting the version id of existing, unchanged templates. Excluding them keeps this a no-op for any template that does not reference a real parameter in a version-tracked property, so existing version ids are preserved.Flagging this explicitly: for affected templates, a customer who changes nothing in their template will get one new Lambda version published on their next deploy.
The version logical id is a hash of the tracked properties. This change alters the hash input for exactly one case, so the id shifts once even with an unchanged template:
AutoPublishAliasAllProperties: true)!Ref MyParam)FVersion63442c9615FVersioncf1b5d3b18FVersiona6597ab2daFVersiona6597ab2daFn::Subwith pseudo params (${AWS::Partition})FVersionb48d397249FVersionb48d397249!Refto another resource (not a parameter)FVersion7e8c27dae7FVersion7e8c27dae7(Measured by transforming the same template with the same parameter values on
developvs this branch.)Scope: only templates that both set
AutoPublishAliasAllProperties: trueand reference a template parameter in a version-tracked property. Everything else is byte-identical, which is why all 2174 existing transform tests pass with no golden-file changes.Impact of that one-time shift: CloudFormation sees a new
AWS::Lambda::Versionlogical id, creates the new version, and points the alias at it. The old version is retained (DeletionPolicy: Retain), so nothing is deleted. Practical effects to be aware of:DeploymentPreference(canary/linear), that deploy will run a real traffic shift rather than a no-op.This is unavoidable if the bug is to be fixed at all: publishing a version when the parameter changes requires the parameter value to be part of the hash. I kept the blast radius as small as I could by excluding pseudo parameters — resolving those too would have shifted ids for any template using
${AWS::Partition}-styleFn::Sub, which is a far larger population and is what an earlier iteration of this patch did (caught byfunction_with_alias_and_all_properties_property).Worth a note in the release notes.
Description of how you validated changes
tests/translator/test_function_resources.py): assert that changing a referenced parameter value yields a different version logical id, and that an unchanged value is stable. Confirmed both fail against the pre-fix code and pass with the fix.tests/translatorsuite passes (2229 tests) with no golden-file version id changes — confirming backward compatibility. This specifically includesfunction_with_alias_and_all_properties_property, a fixture that usesFn::Subwith${AWS::Partition}etc., which a naive fix (resolving all parameters including pseudo) regressed. The pseudo-parameter exclusion is what keeps that fixture stable.ruffandblackclean on both files.Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.