ansible-role-nginx-config icon indicating copy to clipboard operation
ansible-role-nginx-config copied to clipboard

Deploy template fails with ``no test named 'boolean'``

Open Kariton opened this issue 2 years ago • 5 comments

Describe the bug

A clear and concise description of what the bug is.

Deploy template fails with no test named 'boolean'

TASK [nginxinc.nginx_core.nginx_config : Dynamically generate NGINX HTTP config files] ******************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: jinja2.exceptions.TemplateAssertionError: no test named 'boolean'
failed: [host.fqdn] (item={'template_file': 'http/default.conf.j2', 'deployment_location': '/etc/nginx/conf.d/default.conf', 'config': {'upstreams': [{'name': 'upstr', 'least_conn': True, 'servers': [{'address': '0.0.0.0:8081'}, {'address': '0.0.0.0:8082'}]}], 'servers': [{'core': {'listen': [{'port': 80}], 'server_name': 'localhost'}, 'log': {'access': [{'path': '/var/log/nginx/access.log', 'format': 'main'}]}, 'locations': [{'location': '/', 'proxy': {'pass': 'http://upstr/', 'set_header': {'field': 'Host', 'value': '$host'}}}]}, {'core': {'listen': [{'port': 8081}], 'server_name': 'localhost'}, 'log': {'access': [{'path': '/var/log/nginx/access.log', 'format': 'main'}]}, 'locations': [{'location': '/', 'core': {'root': '/usr/share/nginx/html', 'index': 'server_one.html'}}], 'sub_filter': {'sub_filters': [{'string': 'server_hostname', 'replacement': '$hostname'}, {'string': 'server_address', 'replacement': '$server_addr:$server_port'}, {'string': 'server_url', 'replacement': '$request_uri'}, {'string': 'remote_addr', 'replacement': '$remote_addr:$remote_port'}, {'string': 'server_date', 'replacement': '$time_local'}, {'string': 'client_browser', 'replacement': '$http_user_agent'}, {'string': 'request_id', 'replacement': '$request_id'}, {'string': 'nginx_version', 'replacement': '$nginx_version'}, {'string': 'document_root', 'replacement': '$document_root'}, {'string': 'proxied_for_ip', 'replacement': '$http_x_forwarded_for'}], 'once': False}}, {'core': {'listen': [{'port': 8082}], 'server_name': 'localhost'}, 'log': {'access': [{'path': '/var/log/nginx/access.log', 'format': 'main'}]}, 'locations': [{'location': '/', 'core': {'root': '/usr/share/nginx/html', 'index': 'server_two.html'}}], 'sub_filter': {'sub_filters': [{'string': 'server_hostname', 'replacement': '$hostname'}, {'string': 'server_address', 'replacement': '$server_addr:$server_port'}, {'string': 'server_url', 'replacement': '$request_uri'}, {'string': 'remote_addr', 'replacement': '$remote_addr:$remote_port'}, {'string': 'server_date', 'replacement': '$time_local'}, {'string': 'client_browser', 'replacement': '$http_user_agent'}, {'string': 'request_id', 'replacement': '$request_id'}, {'string': 'nginx_version', 'replacement': '$nginx_version'}, {'string': 'document_root', 'replacement': '$document_root'}, {'string': 'proxied_for_ip', 'replacement': '$http_x_forwarded_for'}], 'once': False}}]}}) => {"ansible_loop_var": "item", "changed": false, "item": {"config": {"servers": [{"core": {"listen": [{"port": 80}], "server_name": "localhost"}, "locations": [{"location": "/", "proxy": {"pass": "http://upstr/", "set_header": {"field": "Host", "value": "$host"}}}], "log": {"access": [{"format": "main", "path": "/var/log/nginx/access.log"}]}}, {"core": {"listen": [{"port": 8081}], "server_name": "localhost"}, "locations": [{"core": {"index": "server_one.html", "root": "/usr/share/nginx/html"}, "location": "/"}], "log": {"access": [{"format": "main", "path": "/var/log/nginx/access.log"}]}, "sub_filter": {"once": false, "sub_filters": [{"replacement": "$hostname", "string": "server_hostname"}, {"replacement": "$server_addr:$server_port", "string": "server_address"}, {"replacement": "$request_uri", "string": "server_url"}, {"replacement": "$remote_addr:$remote_port", "string": "remote_addr"}, {"replacement": "$time_local", "string": "server_date"}, {"replacement": "$http_user_agent", "string": "client_browser"}, {"replacement": "$request_id", "string": "request_id"}, {"replacement": "$nginx_version", "string": "nginx_version"}, {"replacement": "$document_root", "string": "document_root"}, {"replacement": "$http_x_forwarded_for", "string": "proxied_for_ip"}]}}, {"core": {"listen": [{"port": 8082}], "server_name": "localhost"}, "locations": [{"core": {"index": "server_two.html", "root": "/usr/share/nginx/html"}, "location": "/"}], "log": {"access": [{"format": "main", "path": "/var/log/nginx/access.log"}]}, "sub_filter": {"once": false, "sub_filters": [{"replacement": "$hostname", "string": "server_hostname"}, {"replacement": "$server_addr:$server_port", "string": "server_address"}, {"replacement": "$request_uri", "string": "server_url"}, {"replacement": "$remote_addr:$remote_port", "string": "remote_addr"}, {"replacement": "$time_local", "string": "server_date"}, {"replacement": "$http_user_agent", "string": "client_browser"}, {"replacement": "$request_id", "string": "request_id"}, {"replacement": "$nginx_version", "string": "nginx_version"}, {"replacement": "$document_root", "string": "document_root"}, {"replacement": "$http_x_forwarded_for", "string": "proxied_for_ip"}]}}], "upstreams": [{"least_conn": true, "name": "upstr", "servers": [{"address": "0.0.0.0:8081"}, {"address": "0.0.0.0:8082"}]}]}, "deployment_location": "/etc/nginx/conf.d/default.conf", "template_file": "http/default.conf.j2"}, "msg": "TemplateAssertionError: no test named 'boolean'"}
The full traceback is:
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/ansible/plugins/action/template.py", line 146, in run
    resultant = templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
  File "/usr/local/lib/python3.6/site-packages/ansible/template/__init__.py", line 1100, in do_template
    res = j2_concat(rf)
  File "<template>", line 43, in root
  File "/usr/lib/python3.6/site-packages/jinja2/environment.py", line 830, in get_template
    return self._load_template(name, self.make_globals(globals))
  File "/usr/lib/python3.6/site-packages/jinja2/environment.py", line 804, in _load_template
    template = self.loader.load(self, name, globals)
  File "/usr/lib/python3.6/site-packages/jinja2/loaders.py", line 125, in load
    code = environment.compile(source, name, filename)
  File "/usr/lib/python3.6/site-packages/jinja2/environment.py", line 591, in compile
    self.handle_exception(exc_info, source_hint=source_hint)
  File "/usr/lib/python3.6/site-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3.6/site-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "/opt/ansible/collections/ansible_collections/nginxinc/nginx_core/roles/nginx_config/templates/http/upstream.j2", line 16, in template
    {{- ' backup' if server['backup'] is defined and server['backup'] is boolean and server['backup'] | bool -}}
  File "/usr/lib/python3.6/site-packages/jinja2/environment.py", line 543, in _generate
    optimized=self.optimized)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 82, in generate
    generator.visit(node)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 754, in visit_Template
    self.blockvisit(node.body, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1176, in visit_Macro
    macro_frame, macro_ref = self.macro_body(node, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 575, in macro_body
    self.blockvisit(node.body, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1122, in visit_For
    self.blockvisit(node.body, loop_frame)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1160, in visit_If
    self.blockvisit(node.body, if_frame)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1160, in visit_If
    self.blockvisit(node.body, if_frame)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1122, in visit_For
    self.blockvisit(node.body, loop_frame)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1160, in visit_If
    self.blockvisit(node.body, if_frame)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1314, in visit_Output
    self.visit(item, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 70, in new_func
    return f(self, node, frame, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1624, in visit_CondExpr
    self.visit(node.test, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 70, in new_func
    return f(self, node, frame, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1482, in visitor
    self.visit(node.left, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 70, in new_func
    return f(self, node, frame, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1484, in visitor
    self.visit(node.right, frame)
  File "/usr/lib/python3.6/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 70, in new_func
    return f(self, node, frame, **kwargs)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 1607, in visit_Test
    self.fail('no test named %r' % node.name, node.lineno)
  File "/usr/lib/python3.6/site-packages/jinja2/compiler.py", line 315, in fail
    raise TemplateAssertionError(msg, lineno, self.name, self.filename)
jinja2.exceptions.TemplateAssertionError: no test named 'boolean'
failed: [host.fqdn] (item={'template_file': 'http/default.conf.j2', 'deployment_location': '/etc/nginx/conf.d/default.conf', 'config': {'upstreams': [{'name': 'upstr', 'least_conn': True, 'servers': [{'address': '0.0.0.0:8081'}, {'address': '0.0.0.0:8082'}]}], 'servers': [{'core': {'listen': [{'port': 80}], 'server_name': 'localhost'}, 'log': {'access': [{'path': '/var/log/nginx/access.log', 'format': 'main'}]}, 'locations': [{'location': '/', 'proxy': {'pass': 'http://upstr/', 'set_header': {'field': 'Host', 'value': '$host'}}}]}, {'core': {'listen': [{'port': 8081}], 'server_name': 'localhost'}, 'log': {'access': [{'path': '/var/log/nginx/access.log', 'format': 'main'}]}, 'locations': [{'location': '/', 'core': {'root': '/usr/share/nginx/html', 'index': 'server_one.html'}}], 'sub_filter': {'sub_filters': [{'string': 'server_hostname', 'replacement': '$hostname'}, {'string': 'server_address', 'replacement': '$server_addr:$server_port'}, {'string': 'server_url', 'replacement': '$request_uri'}, {'string': 'remote_addr', 'replacement': '$remote_addr:$remote_port'}, {'string': 'server_date', 'replacement': '$time_local'}, {'string': 'client_browser', 'replacement': '$http_user_agent'}, {'string': 'request_id', 'replacement': '$request_id'}, {'string': 'nginx_version', 'replacement': '$nginx_version'}, {'string': 'document_root', 'replacement': '$document_root'}, {'string': 'proxied_for_ip', 'replacement': '$http_x_forwarded_for'}], 'once': False}}, {'core': {'listen': [{'port': 8082}], 'server_name': 'localhost'}, 'log': {'access': [{'path': '/var/log/nginx/access.log', 'format': 'main'}]}, 'locations': [{'location': '/', 'core': {'root': '/usr/share/nginx/html', 'index': 'server_two.html'}}], 'sub_filter': {'sub_filters': [{'string': 'server_hostname', 'replacement': '$hostname'}, {'string': 'server_address', 'replacement': '$server_addr:$server_port'}, {'string': 'server_url', 'replacement': '$request_uri'}, {'string': 'remote_addr', 'replacement': '$remote_addr:$remote_port'}, {'string': 'server_date', 'replacement': '$time_local'}, {'string': 'client_browser', 'replacement': '$http_user_agent'}, {'string': 'request_id', 'replacement': '$request_id'}, {'string': 'nginx_version', 'replacement': '$nginx_version'}, {'string': 'document_root', 'replacement': '$document_root'}, {'string': 'proxied_for_ip', 'replacement': '$http_x_forwarded_for'}], 'once': False}}]}}) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": {
        "config": {
            "servers": [
                {
                    "core": {
                        "listen": [
                            {
                                "port": 80
                            }
                        ],
                        "server_name": "localhost"
                    },
                    "locations": [
                        {
                            "location": "/",
                            "proxy": {
                                "pass": "http://upstr/",
                                "set_header": {
                                    "field": "Host",
                                    "value": "$host"
                                }
                            }
                        }
                    ],
                    "log": {
                        "access": [
                            {
                                "format": "main",
                                "path": "/var/log/nginx/access.log"
                            }
                        ]
                    }
                },
                {
                    "core": {
                        "listen": [
                            {
                                "port": 8081
                            }
                        ],
                        "server_name": "localhost"
                    },
                    "locations": [
                        {
                            "core": {
                                "index": "server_one.html",
                                "root": "/usr/share/nginx/html"
                            },
                            "location": "/"
                        }
                    ],
                    "log": {
                        "access": [
                            {
                                "format": "main",
                                "path": "/var/log/nginx/access.log"
                            }
                        ]
                    },
                    "sub_filter": {
                        "once": false,
                        "sub_filters": [
                            {
                                "replacement": "$hostname",
                                "string": "server_hostname"
                            },
                            {
                                "replacement": "$server_addr:$server_port",
                                "string": "server_address"
                            },
                            {
                                "replacement": "$request_uri",
                                "string": "server_url"
                            },
                            {
                                "replacement": "$remote_addr:$remote_port",
                                "string": "remote_addr"
                            },
                            {
                                "replacement": "$time_local",
                                "string": "server_date"
                            },
                            {
                                "replacement": "$http_user_agent",
                                "string": "client_browser"
                            },
                            {
                                "replacement": "$request_id",
                                "string": "request_id"
                            },
                            {
                                "replacement": "$nginx_version",
                                "string": "nginx_version"
                            },
                            {
                                "replacement": "$document_root",
                                "string": "document_root"
                            },
                            {
                                "replacement": "$http_x_forwarded_for",
                                "string": "proxied_for_ip"
                            }
                        ]
                    }
                },
                {
                    "core": {
                        "listen": [
                            {
                                "port": 8082
                            }
                        ],
                        "server_name": "localhost"
                    },
                    "locations": [
                        {
                            "core": {
                                "index": "server_two.html",
                                "root": "/usr/share/nginx/html"
                            },
                            "location": "/"
                        }
                    ],
                    "log": {
                        "access": [
                            {
                                "format": "main",
                                "path": "/var/log/nginx/access.log"
                            }
                        ]
                    },
                    "sub_filter": {
                        "once": false,
                        "sub_filters": [
                            {
                                "replacement": "$hostname",
                                "string": "server_hostname"
                            },
                            {
                                "replacement": "$server_addr:$server_port",
                                "string": "server_address"
                            },
                            {
                                "replacement": "$request_uri",
                                "string": "server_url"
                            },
                            {
                                "replacement": "$remote_addr:$remote_port",
                                "string": "remote_addr"
                            },
                            {
                                "replacement": "$time_local",
                                "string": "server_date"
                            },
                            {
                                "replacement": "$http_user_agent",
                                "string": "client_browser"
                            },
                            {
                                "replacement": "$request_id",
                                "string": "request_id"
                            },
                            {
                                "replacement": "$nginx_version",
                                "string": "nginx_version"
                            },
                            {
                                "replacement": "$document_root",
                                "string": "document_root"
                            },
                            {
                                "replacement": "$http_x_forwarded_for",
                                "string": "proxied_for_ip"
                            }
                        ]
                    }
                }
            ],
            "upstreams": [
                {
                    "least_conn": true,
                    "name": "upstr",
                    "servers": [
                        {
                            "address": "0.0.0.0:8081"
                        },
                        {
                            "address": "0.0.0.0:8082"
                        }
                    ]
                }
            ]
        },
        "deployment_location": "/etc/nginx/conf.d/default.conf",
        "template_file": "http/default.conf.j2"
    },
    "msg": "TemplateAssertionError: no test named 'boolean'"
}

To reproduce

Steps to reproduce the behavior:

  1. Deploy NGINX Config role using https://github.com/nginxinc/ansible-collection-nginx/blob/main/playbooks/deploy-nginx-web-server-proxy.yml
  2. View output/logs/configuration on TASK [nginxinc.nginx_core.nginx_config : Dynamically generate NGINX HTTP config files]
  3. See error

Expected behavior

Successful deployment

Your environment

  • Version of the NGINX Config role or specific commit

Collection installed via:

# ansible-galaxy collection install nginxinc.nginx_core
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/download/nginxinc-nginx_core-0.4.0.tar.gz to ~/.ansible/tmp/ansible-local-138554ko34z3om/tmpbfjxdsv7/nginxinc-nginx_core-0.4.0-_7gl1uhk
Installing 'nginxinc.nginx_core:0.4.0' to '~/.ansible/collections/ansible_collections/nginxinc/nginx_core'
nginxinc.nginx_core:0.4.0 was installed successfully
  • Version of Ansible
  • Version of Jinja2 (if you are using any templating capability)
ansible [core 2.11.6]
  python version = 3.6.8 (default, Sep 12 2021, 04:40:35) [GCC 8.4.1 20200928 (Red Hat 8.4.1-1)]
  jinja version = 2.10.1
  libyaml = True
  • Target deployment platform
NAME="CentOS Stream"
VERSION="8"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Stream 8"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://centos.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux 8"
REDHAT_SUPPORT_PRODUCT_VERSION="CentOS Stream"

Additional context

Add any other context about the problem here.

Kariton avatar Nov 07 '21 20:11 Kariton

Just noticed that my Jinja2 version does not fulfills the requirements.

# pip3 install Jinja2 --upgrade
ansible [core 2.11.6]
  python version = 3.6.8 (default, Sep 12 2021, 04:40:35) [GCC 8.4.1 20200928 (Red Hat 8.4.1-1)]
  jinja version = 3.0.2
  libyaml = True

Does work now!

Is there a way to implement a "requirements" check?

Kariton avatar Nov 07 '21 21:11 Kariton

There probably is a way to implement a requirements check using the shell or command module, but there's a few other items on the roadmap with a higher priority. PRs are welcome though! 😄

alessfg avatar Nov 08 '21 13:11 alessfg

Hi,

The Jinja version requirement breaks compatibility with the the standard execution environment in Ansible Automation Platform (Ansible Tower). A custom execution environment has to be built and maintained, just for the Jinja version requirement.

This is the default execution environment:

ansible [core 2.12.1]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/runner/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.8/site-packages/ansible
  ansible collection location = /home/runner/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.8.8 (default, Aug 11 2021, 06:52:42) [GCC 8.5.0 20210514 (Red Hat 8.5.0-3)]
  jinja version = 2.10.3
  libyaml = True

Would it be possible to make this role work with the standard available version of Jinja?

sigbjornaib avatar Jan 11 '22 07:01 sigbjornaib

Hey @sigbjornaib!

Thanks for bringing this up! I did not realize this was the case -- let me follow up with Ansible and see if they have plans/when will they update Jinja2 to 2.11. Many of the built in checks rely on the boolean test and while they can be backported, I would rather keep it as is for code cleanliness.

alessfg avatar Jan 18 '22 13:01 alessfg

Looks like the minimum Jinja2 version will be updated to 3.x with ansible-core 2.13 due for release sometime in spring. As such, and while I do understand that it's not ideal to have to create and maintain a custom execution environment, I don't think the best path forward is backporting these templates. I would probably advise using a pre_task to update the Jinja2 version instead.

alessfg avatar Feb 14 '22 12:02 alessfg