Enforcing numeric types breaks certain templating patterns.
Closed this issue · 3 comments
Describe the bug
I'm unable to omit any numeric variables by default.
We currently enforce numeric types on various values, like so:
{% if main['worker_rlimit_nofile'] is defined and main['worker_rlimit_nofile'] is number %}
worker_rlimit_nofile {{ main['worker_rlimit_nofile'] }};
{% endif %}The obviates the ability to use any Ansible variable magic, such as setting a default value. As a workaround, I need to have completely separate scopes of nginx_conf variables where everything is virtually identical except for a couple of variables like this one. The normal way to resolve such duplication would be to express a omitted default, ie. | default(omit), but this doesn't work even when postfixing | int on it.
If I'm just missing some other way to express a number variable that can also be omitted, please let me know.
To reproduce
Steps to reproduce the behavior:
---
- hosts: localhost
gather_facts: false
tasks:
- name: omit test (bad result)
debug:
var: nginx_config_main_template.config.main
tags: nginx_test_type
vars:
nginx_worker_rlimit_noprofile: 12288
nginx_config_main_template:
config:
main:
worker_rlimit_nofile: "{{ nginx_worker_rlimit_noprofile | default(omit) }}"
- name: int test (bad result)
debug:
var: nginx_config_main_template.config.main
tags: nginx_test_type
vars:
nginx_worker_rlimit_noprofile: 12288
nginx_config_main_template:
config:
main:
worker_rlimit_nofile: "{{ nginx_worker_rlimit_noprofile | int }}"
- name: basic variable test (good result, but requires duplication of nginx_conf variables when hosts have different values)
debug:
var: nginx_config_main_template.config.main
tags: nginx_test_type
vars:
nginx_worker_rlimit_noprofile: 12288
nginx_config_main_template:
config:
main:
worker_rlimit_nofile: "{{ nginx_worker_rlimit_noprofile }}"Result:
TASK [omit test (bad result)]
ok: [localhost] => {
"nginx_config_main_template.config.main": {
"worker_rlimit_nofile": "12288"
}
}
TASK [int test (bad result)]
ok: [localhost] => {
"nginx_config_main_template.config.main": {
"worker_rlimit_nofile": "12288"
}
}
TASK [basic variable test (good result, but requires duplication of nginx_conf variables when hosts have different values)]
ok: [localhost] => {
"nginx_config_main_template.config.main": {
"worker_rlimit_nofile": 12288
}
}
Expected behavior
The way this manifested for me was that my playbooks were not writing a couple of my configuration values that I had configured. I had to check the syntax, the scoping, differences between the role version here and the role from the nginx_core collection I was using, and so on. Until I finally narrowed it to this.
Your environment
- Version of the Ansible NGINX configuration role or specific commit
- src: https://github.com/nginxinc/ansible-role-nginx-config name: nginx_core.nginx_config version: '0.7.0'
- Version of Ansible
ansible --version ansible [core 2.15.1] python version = 3.11.3 (main, Jun 5 2023, 09:32:32) [GCC 13.1.1 20230429] (/usr/bin/python) jinja version = 3.1.2 libyaml = True
- Version of Jinja2 (if you are using any templating capability)
- Target deployment platform
Ubuntu 22.04
If I get it correctly, the default filter works if the value is not set. Something like in the snipper below:
connections: "{{$env.value | default(30) }}"
The default value of 30 will only be used if $env.value is not set.
In your case the value for nginx_worker_rlimit_noprofile exists. So the value of nginx_worker_rlimit_noprofile is used and the default filter doesn't really work at all.
Am I missing something here?
Interesting find! The default(omit) test case seems to be working as expected (as @oxpa pointed out), but in both that test and the next one it's true that Jinja2 returns a string instead of an int. This seems to be a "known" issue within Ansible, and there are a couple potential workarounds suggested here ansible/ansible#30366.
As to potential changes to this role -- I am adamant about keeping as many type checks as possible (and am always actively looking for more ways to enforce type checks), but I also see how the current implementation does not provide an ideal user experience.
There might be a way to check that the value is indeed an integer without resorting to the built-in number check? Maybe something along the lines of
{% if main['worker_rlimit_nofile'] is defined and main['worker_rlimit_nofile'] | int %}
worker_rlimit_nofile {{ main['worker_rlimit_nofile'] }};
{% endif %}which in turn should fail if the value cannot be converted into an int? I'll test a few scenarios when I get a chance to see if this would be a viable solution. In the meantime, I suggest checking out the workarounds shared in the thread above 😄
Heya! Finally got around doing some testing around this, and the tl;dr is that at this stage, I don't think I am going to change anything. The template will not apply the value if you use a string or try to convert the string to an int (like you did in your second test), but I am okay with that. At some stage in the hopefully near future, I plan to have variable validation, and using type checks is one of the ways variables will be validated.
The first test, which seems to be the root cause of the original issue, should work as intended as long as you comment out the "original" variable (otherwise, the default(omit) filter does not work as intended).