18

I have a single Ansible-provisioned server running a number of sites.

My Ansible tasks look roughly like:

- name: site nginx config
  template: src="nginx-site.conf.j2" dest=/etc/nginx/conf.d/{{item.name}}.conf
            owner=root group=root mode=0444
  with_items: sites
  notify: restart nginx

- name: nginx conf
  template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf 
            owner=root group=root mode=0444
  notify: restart nginx

I'd like to use the validate parameter to Ansible's template module to call nginx -t and make sure my new configs are syntactically valid. It works for the main nginx.conf:

  template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf 
            owner=root group=root mode=0444
            validate="/usr/sbin/nginx -c %s -t"

But it doesn't seem to pick up changes to the site-specific config files. Putting validate on the site-specific templates doesn't work, as they need to be wrapped in an http directive to be valid.

What can I do to check the validity of these site-specific files?

Erin Call
  • 291
  • 1
  • 2
  • 5

6 Answers6

16

You may do there some trick and validate placed file like (idea borrowed from https://gist.github.com/psd/5042334):

    validate: bash -c 'nginx -t -c /dev/stdin <<< "events {worker_connections 1;} http { include %s; }"'

Update 2022-12-14

In fresh versions of bash such construction will not work! Please refer to nginx bug-report 2381 (2425) because of changed implementation - pipe used for the here-documents instead of temporary files.

So, you may use slightly less readable construction like:

validate: bash -c 'echo "events { worker_connections 2; } http { include %s; }" > /tmp/nginx.conf; sudo nginx -T -c /tmp/nginx.conf && rm -f /tmp/nginx.conf'
Hubbitus
  • 311
  • 3
  • 5
  • 1
    This worked with nginx 1.14, but it doesn't seem to work anymore with 1.18 – Danilo Bargen May 28 '22 at 10:03
  • 1
    Why? Which problem do you get in 1.8? – Hubbitus May 29 '22 at 14:59
  • Couldn't we simply `echo "%s"` your way and then run `nginx -t` without specify an explicit config? This way it would still validate the current snippet and roll back changes, but you'd test against a complete config. – 0xC0000022L Dec 08 '22 at 12:20
  • If I remember correctly, config parameter required for validation. – Hubbitus Dec 11 '22 at 11:28
  • 1
    Strange, but indeed fresh versions of nginx do not willing read from stdout... You may try then use construction like: `validate: bash -c 'echo "events { worker_connections 2; } http { include %s; }" > /tmp/nginx.conf; sudo nginx -T -c /tmp/nginx.conf && rm -f /tmp/nginx.conf'` – Hubbitus Dec 11 '22 at 11:36
  • I've created bug report to nginx for that: https://trac.nginx.org/nginx/ticket/2425#ticket – Hubbitus Dec 11 '22 at 12:33
  • @Hubbitus this only worked because bash used to provide here-documents in temporary files, but it doesn't anymore. – Maxim Dounin Dec 11 '22 at 23:43
7

I used an approach similar to the accepted answer taking into account the other answer's concerns.

I created this gist for that purpose.

The idea is to copy the whole /etc/nginx directory to a temporary directory, change one the file from the %s parameter and test the main nginx config for problems. If you assume that initially the nginx config is valid and all tasks that modify the nginx config use that to validate, then I guess there would be no problem.

As a one liner it would look like this:

validate: bash -c 'NGINX_CONF_DIR=`mktemp -d`; cp -rTp /etc/nginx/ "$NGINX_CONF_DIR" && cp -Tp %s "$NGINX_CONF_DIR"/sites-enabled/new-site.conf && nginx -t -c "$NGINX_CONF_DIR"/nginx.conf'

jadkik94
  • 171
  • 1
  • 3
6

It makes no sense to directly call validate on a file included in your nginx main configuration file because the validity of directives in a particular configuration file may depend on the rest of your configuration files (for instance you have two configuration files that declare the same server block etc).

You must always call nginx -t on the main configuration file and not one of its subpart whenever you want to validate any nginx's configuration change.

Xavier Lucas
  • 13,095
  • 2
  • 44
  • 50
4

Here's a more straightforward way that works at least with Ansible 2.5:

- name: Verify Nginx config
  become: yes
  command: nginx -t
  changed_when: false

It runs the equivalent of sudo nginx -t and checks its output. If there is an error in the nginx config, it returns non-zero and the Ansible task would error (changed_when).

If you installed Nginx as a user, just remove the become, although I think it would still work even with it.

Juha Untinen
  • 149
  • 4
  • 5
    This is a bad example, when using the `template` module and the validate option in Ansible you have a guarantee for the validity of the config, but if you deploy the template and then to manual check and fail, then you still have the problem that a invalid template was deployed, and not need to be reverted. – Rabin Jan 01 '19 at 12:19
0

The validation using /dev/stdin seems to not work with the newer bash versions, they recommend to use a proper temporary configuration file: https://trac.nginx.org/nginx/ticket/2381#comment:1

What we can do is to create the temporary file e.g.:

- name: Create a temporary file
  tempfile:
    state: file
    suffix: temp
  register: tempfile_1

- name: Ensure Nginx configuration file
  template:
    src: server.j2
    dest: /path/to/whatever.conf
    validate: bash -c 'echo "events {worker_connections 3;} http { include %s; }" > {{ tempfile_1.path }} && 
                       nginx -prefix=/etc/nginx -t -c {{ tempfile_1.path }}'
  notify: reload nginx

- name: Remove the temporary file
  file:
    path: "{{ tempfile_1.path }}"
    state: absent
  when: tempfile_1.path is defined
moronizzz
  • 5
  • 3
0

If you want to test your full nginx.conf with new configuration and validation with /dev/stdin works fine for you, you can use the following approach:

if you are updating files in /etc/nginx/conf.d:

validate: bash -c "nginx -t -c /dev/stdin <<< \"$(cat /etc/nginx/nginx.conf | sed 's,include /etc/nginx/conf\.d/\*\.conf;,include %s;,g')\""

if you are updating files in /etc/nginx/sites-enabled:

validate: bash -c "nginx -t -c /dev/stdin <<< \"$(cat /etc/nginx/nginx.conf | sed 's,include /etc/nginx/sites-enabled/\*;,include %s;,g')\""

But take a note that all other files in directory /etc/nginx/conf.d or /etc/nginx/sites-enabled (depends on where you are updating files) will be ignored :(

This is required because if we will just add a new include with new file in nginx.conf, old version of file may conflict with new version.

murtll
  • 1
  • 2