Repository navigation
fix(E3051): stop firing on Content behind an intrinsic function - #4956
Merged
kddejong merged 1 commit intoOct 29, 2026
Merged
Conversation
aws-cloudformation#4844 moved E3051's keyword from Resources/AWS::SSM::Document/Properties/Content up to Resources/AWS::SSM::Document/Properties so validate() could read the sibling DocumentType off the instance. Properties sits in front of cfn-lint's intrinsic-function machinery, so the rule began receiving a raw dict for Content instead of a resolved value and reported 'schemaVersion' is a required property on every document whose Content comes from Ref, Fn::GetAtt or Fn::Sub. Put the keyword back at Content and read DocumentType through Template.get_cfn_path, the same way E1041 reads a sibling VpcId. The logical ID comes from validator.context.path.path[1], as in E3035, E3036, E3055 and W2530. get_cfn_path yields each Fn::If branch with its condition bound, so a conditional DocumentType is now handled too; aws-cloudformation#4844 and its predecessor both validated it as an unresolved dict. The AppConfig exemption from aws-cloudformation#4844 is unchanged: ApplicationConfiguration and ApplicationConfigurationSchema still skip the Automation schema, an absent or unresolvable DocumentType still validates, and a Command document without a schemaVersion still errors. Errors are no longer prefixed with Content: at this keyword the path already ends in Content, so appending it produced a doubled Resources/<id>/Properties/Content/Content in json and sarif output.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4956 +/- ##
==========================================
+ Coverage 94.69% 94.71% +0.02%
==========================================
Files 442 442
Lines 16092 16087 -5
Branches 3121 3120 -1
==========================================
- Hits 15238 15237 -1
+ Misses 470 468 -2
+ Partials 384 382 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
E3051 reports false positives on current
mainfor anyAWS::SSM::DocumentwhoseContentcomes from an intrinsic function.PR #4844 (commit
fecf750f9) moved E3051's keyword up one level:That let
validate()read the siblingDocumentTypeoff the instance, but.../Properties/Contentsits behind cfn-lint's intrinsic-function machinery and.../Propertiesdoes not. The rule went from receiving a resolvedContentvalue to receiving a raw dict, and then validated that dict against the Automation document schema.This template deploys (
CREATE_COMPLETE, us-east-1, 2026-10-28, with a well-formed stringifiedContent):fecf750f9~1is silent on it. Currentmainreports three findings:Ref,Fn::GetAttandFn::Subare all affected.Fn::IfonContentis not — the condition filter still handles it.Fix
Put the keyword back at
Contentand readDocumentTypethroughTemplate.get_cfn_path, the helper built for exactly this.E1041(rules/functions/RefFormat.py) reads a siblingVpcIdthe same way.The logical ID comes from
validator.context.path.path[1], the established positional read used byE3035,E3036,E3055,W2530,W3660and others. Index1rather than-1becauseFn::Ifon a value appends['Fn::If', <branch>]to the path, so the tail is not stable while theResources/<id>prefix is.get_cfn_pathyields eachFn::Ifbranch with its condition bound in context, so a conditionalDocumentTypeis now handled. Neither #4844 nor its predecessor covered that: #4844 saw the unresolved{"Fn::If": [...]}dict, found it absent from the AppConfig tuple, and validated — a false positive when both branches are AppConfig types.Two behaviour notes:
DocumentTypestill validates.DocumentTypeis not required by the resource schema (onlyContentis) and the service defaults it toCommand, which does requireschemaVersion, so failing closed is correct.get_cfn_pathyields nothing for an absent key, which falls through to validation on its own.!If [C, ApplicationConfiguration, Command]) skips. Any branch being an AppConfig type means some scenario of the template is valid without aschemaVersion, and a missed error costs one failed deployment while a false positive breaks CI for a template that was always correct.The 4-line comment from #4844 is replaced by one line, and no new
isinstance/is Noneguards were added —get_cfn_pathhandles the shapes.One deviation from a straight revert
err.path.appendleft("Content")andpath=["Content"]are removed. They were correct at thePropertieskeyword, but at theContentkeyword the context path already ends inContent, so appending it again produced a doubled path. Measured before removing:and after:
Line and column numbers were correct either way (the extra segment does not resolve), so only
json/sarifPathoutput was affected. This matchesfecf750f9~1.Evidence
Measured with
cfn-lint -f parseableagainstupstream/main(027035536) and this branch, aftercfn-lint --update-specs.fecf750f9~1mainContent: !Ref DocBodyContent: !GetAtt Stack1.Outputs.BodyContent: !Sub '{"schemaVersion":"${Ver}"...}'Content: !If [C, <valid>, <valid>]DocumentType: !If [C, AppConfig, AppConfigSchema]DocumentType: !If [C, Command, Automation], bad contentDocumentType: ApplicationConfigurationDocumentType: ApplicationConfigurationSchemaDocumentType: Command, noschemaVersionDocumentTypeabsent, noschemaVersionDocumentTypeabsent, valid contentContent, well-formedContent, malformedThe
E1010andE1019findings in themaincolumn come from E3051's own inner validator:validate()deliberately preservesfn_*errors, so running the document schema over an unresolved function dict surfaces them. They disappear with the keyword position.Stringified
Contentis unchanged, including the pre-existing defect in which a well-formed JSON string still drawsDocument is not valid ('Template could not be parsed: a string or stream input is required'). That is #4894, fixed separately by #4902 — see below.Good-fixture sweep
cfn-lint -t "test/fixtures/templates/good/**/*.yaml" -f parseable168 lines on
main, 168 on this branch, byte-identical apart from log timestamps. Exit code 6 on both. (test/integration/test_good_templates.pyis a curated ~20-template list and does not sweep that directory, so this was run by hand.)Test suite
The same 6 failures on
mainand on this branch, from optional dependencies absent locally (junit-xml,sarif,pydot/pygraphviz). Failure sets diffed identical.Tests
test/unit/rules/resources/ssm/test_document.pynow passesContentvalues tovalidate()again, with the context path set toResources/<id>/Properties/Contentthe wayW2530's tests do.test_validate— the existing value-level cases, with theContentpath prefix dropped from the expectationstest_validate_document_type_scoping— 10 resources covering both AppConfig types (object and stringContent),Automation,Command, absentDocumentType,{"Ref": ...}DocumentType, and the threeFn::Ifshapestest_keyword_registration— template-level viacfnlint.api.lint, which is the only level that can prove the regression:Ref,Fn::GetAttandFn::Subsilent,Fn::IfonContentvalidating each branch,CommandwithoutschemaVersionfiring,ApplicationConfigurationskipping24 tests in the file, up from 15.
Effect on #4902
#4902 becomes meaningful again, and needs a one-line test edit to go green. Measured, not inferred.
#4902 is based on
mainwithout #4844, so its own CI is green, but it does not survive a rebase onto currentmain:rule.validate(validator, {}, <str_node Content>, {}). Post-fix(E3051): skip Automation schema for AppConfig SSM document types #4844 the rule expectsProperties, sovalidator.is_type(instance, "object")isFalsefor astr_nodeand it returns immediately. Its== []assertions pass vacuously and its malformed-JSON assertion fails withassert 0 == 1.decode_strpath again. The well-formedstr_nodeassertion fails because the bug fix(E3051): validate SSM Document Content given as a JSON string #4902 fixes is still there — the test is doing its job.decode_str(str(instance))on top of this branch: both of its tests pass, andcfn-linton a two-document template then reports only the genuinely malformed one, with the real parser message rather than the generic one.The edit #4902 needs is the context path. Because the rule now derives the logical ID from
validator.context.path.path[1], avalidate()call against the defaultvalidatorfixture (emptyPath) raisesIndexError: deque index out of range. Adding thepathparametrization the rest of the file uses is enough; the production path always has the prefix.I have not touched #4902 or its branch.
Net change
src/cfnlint/rules/resources/ssm/Document.py: 13 insertions, 14 deletions — net -1 line.