fix(util): skip jinja config files which fail to render - #7008
Conversation
| fname, | ||
| instance_data_file, | ||
| ) | ||
| if rendered is None: |
There was a problem hiding this comment.
If the function returns None, would it have already logged an error?
There was a problem hiding this comment.
Yup here it does.
holmanb
left a comment
There was a problem hiding this comment.
Please run tox -e do_format to fix the formatting error.
So I did but its another mypy issue that was caught by my changes. Seems the schema also doesn't handle the None return value as well. I'll add a fix in a new commit |
42ebe05 to
0ba1652
Compare
|
hmm not sure the reason for the failing docs check |
|
Oh pylint doesn't play well with the overloading. |
0ba1652 to
5f1c6d6
Compare
| ) | ||
| if rendered is None: | ||
| error("Failed to render templated user-data.", sys_exit=True) | ||
| raise SystemExit(1) # error() exits here |
There was a problem hiding this comment.
why i'm checking in schema? it didn't handle the None return like the original read_conf function
For the raise, okay so mypy complains because error() doesn't have a no return annotation that can be checked when sys_exit=True happens. If I didn't terminate here, the content = rendered assignment below will get a type checking error as well (after I annotated the jinja templating function to fix another mypy checker).
There was a problem hiding this comment.
Would this be a better fit?
| raise SystemExit(1) # error() exits here | |
| raise SystemExit(error("Failed to render templated user-data.")) |
There was a problem hiding this comment.
I tried to add No return overloads to the error function as well but pylint kinda lit up and it was probably touching more files than needed. So I dropped it
There was a problem hiding this comment.
Trying to render a jinja template file with nested instance-data attributes that isn't provided by the active DataSource fails the init stage and prevent cloud-init from starting up.
How does throwing an exception here fix the reported bug? Even the PR title says "skip jinja config files which fail to render", yet this proposed code throws an exception which crashes the program.
There was a problem hiding this comment.
This is not where the bug happens. The bug was fixed in util.read_conf here.
This exception thrown here is a change made as a consequence of fixing the real bug. The cloud-init schema CLI has a tool that also does jinja-template/cloud-config validation. It calls the same jinja render function that util.read_conf does and it also didn't handle the None return from the renderer. I found this out because after annotating render_jinja_payload_from_file with Optional[str], I got a complaint from mypy that there was another call site not properly handling the return.
So this other proposed code doesn't crash the program because it is a CLI command that only validates and isn't part of the running service, but it still needs to cleanly error out and not have a traceback. The service itself will only log on exceptions and continue the program.
Without the schema fix
root@localhost ~]# sudo cloud-init schema --config-file /etc/cloud/cloud.cfg.d/10_test.cfg
2026-08-20 19:47:46,407 - jinja_template.py[WARNING]: Ignoring jinja template for /etc/cloud/cloud.cfg.d/10_test.cfg: 'dict object' has no attribute 'placement'
2026-08-20 19:47:46,407 - util.py[WARNING]: Skipping jinja config file '/etc/cloud/cloud.cfg.d/10_test.cfg'. Failed to render template.
2026-08-20 19:47:46,417 - jinja_template.py[WARNING]: Ignoring jinja template for /etc/cloud/cloud.cfg.d/10_test.cfg: 'dict object' has no attribute 'placement'
2026-08-20 19:47:46,417 - util.py[WARNING]: Skipping jinja config file '/etc/cloud/cloud.cfg.d/10_test.cfg'. Failed to render template.
2026-08-20 19:47:46,421 - jinja_template.py[WARNING]: Ignoring jinja template for /etc/cloud/cloud.cfg.d/10_test.cfg: 'dict object' has no attribute 'placement'
Traceback (most recent call last):
File "/usr/sbin/cloud-init", line 9, in <module>
sys.exit(main.main())
~~~~~~~~~^^
File "/usr/lib/python3.14/site-packages/cloudinit/cmd/main.py", line 1310, in main
return sub_main(args, parser)
File "/usr/lib/python3.14/site-packages/cloudinit/cmd/main.py", line 1448, in sub_main
retval = functor(name, args)
File "/usr/lib/python3.14/site-packages/cloudinit/config/schema.py", line 1478, in handle_schema_args
performed_schema_validation = validate_cloudconfig_file(
cfg_part.config_path,
...<3 lines>...
instance_data_path,
)
File "/usr/lib/python3.14/site-packages/cloudinit/config/schema.py", line 1123, in validate_cloudconfig_file
decoded_config = _get_config_type_and_rendered_userdata(
config_path, decoded_content, instance_data_path
)
File "/usr/lib/python3.14/site-packages/cloudinit/config/schema.py", line 1065, in _get_config_type_and_rendered_userdata
user_data_type = type_from_starts_with(content)
File "/usr/lib/python3.14/site-packages/cloudinit/handlers/__init__.py", line 293, in type_from_starts_with
payload_lc = util.decode_binary(payload).lower()
~~~~~~~~~~~~~~~~~~^^^^^^^^^
File "/usr/lib/python3.14/site-packages/cloudinit/util.py", line 143, in decode_binary
return blob if isinstance(blob, str) else blob.decode(encoding=encoding)
^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'decode'
[root@localhost ~]#
With the schema fix
[root@localhost ~]# sudo cloud-init schema --config-file /etc/cloud/cloud.cfg.d/10_test.cfg
2026-08-20 19:44:04,811 - jinja_template.py[WARNING]: Ignoring jinja template for /etc/cloud/cloud.cfg.d/10_test.cfg: 'dict object' has no attribute 'placement'
2026-08-20 19:44:04,811 - util.py[WARNING]: Skipping jinja config file '/etc/cloud/cloud.cfg.d/10_test.cfg. Failed to render template.
2026-08-20 19:44:04,821 - jinja_template.py[WARNING]: Ignoring jinja template for /etc/cloud/cloud.cfg.d/10_test.cfg: 'dict object' has no attribute 'placement'
2026-08-20 19:44:04,821 - util.py[WARNING]: Skipping jinja config file '/etc/cloud/cloud.cfg.d/10_test.cfg'. Failed to render template.
2026-08-20 19:44:04,825 - jinja_template.py[WARNING]: Ignoring jinja template for /etc/cloud/cloud.cfg.d/10_test.cfg: 'dict object' has no attribute 'placement'
Error:
Failed to render templated user-data.
There was a problem hiding this comment.
I can make updates to the PR description to make this more clearer
There was a problem hiding this comment.
In any case, throwing a SystemExit in runtime code is still not what we want - this isn't a pattern used anywhere else.
You can satisfy mypy without throwing the exception by conditionally assigning only when it has a value.
There was a problem hiding this comment.
Thanks. That makes sense. i'll update it
read_conf doesn't handle the None return value from render_jinja_payload_from_file() when a template render fails. This gets passed into load_yaml which results in an AttributeError on NoneType and crashes the running service. This check was removed from canonical#5350 as unreachable due to the render functions not having any return annotations. Adding Optional[str] to avoid this being caught again and fix the schema to check None return. Fixes canonicalGH-7007
5f1c6d6 to
3552582
Compare
|
All good on here @holmanb? |
Proposed Commit Message
Additional Context
Filed as an issue in [bug] cloud-init-local.service crashes with AttributeError on failed jinja template render #7007
I followed the previous removed code and returned an empty {} as but I don't know if there is another expectation here.
Test Validation
Reproducer in GH issue above.
Test output with current branch
After fix
Merge type