Force limit parameter to be set in ansible

Viewed 3166

Is there a way to force commands ansible-playbook, ansible-variable, etc... to be executed with a --limit option (otherwise to deny it) ?

I discovered that, on a cluster it can easily run a playbook to all nodes if you mistakenly run it without limit, I'd like to prevent it from ansible users.

4 Answers

Use the ansible_limit variable (added in ansible 2.5). You can test like this:

tasks:
  - fail:
      msg: "you must use -l or --limit"
    when: ansible_limit is not defined
    run_once: true

It's the opposite of the task I've solved recently. My goal was to detect there is a --limit and to skip some plays.

https://medium.com/opsops/how-to-detect-if-ansible-is-run-with-limit-5ddb8d3bd145

In your case you can check this in the play and fail if it "full run":

- hosts: all
  gather_facts: no
  tasks:
    - set_fact:
         full_run: '{{play_hosts == groups.all}}'
    - fail:
        msg: 'Use --limit, Luke!'
      when: full_run

You can use a different group instead of all, of course (change it in both hosts and set_fact lines).

I did it that way in a task:

$ cat exit-if-no-limit.yml
---
  - name: Verifying that a limit is set
    fail:
      msg: 'This playbook cannot be run with no limit'
    run_once: true
    when: ansible_limit is not defined

  - debug:
      msg: Limit is {{ ansible_limit }}, let's continue
    run_once: true
    when: ansible_limit is defined

Which I include in my playbooks when I need to disallow them to run on all the hosts:

- include_role:
    name: myrole
    tasks_from: "{{ item }}.yml"
  loop:
  - exit-if-no-limit
  - something
  - something_else

Easy to reuse when needed. It works like that:

TASK [myrole: Verifying that a limit is set] 
fatal: [ahost]: FAILED! => {"changed": false, "msg": "This playbook cannot be run with no limit"}

or

TASK [myrole: debug] 
ok: [anotherhost] => {
    "msg": "Limit is anotherhost, let's continue"
}

This can be done with the assert module.

I like to do this in a separate play, at the start of the playbook, with fact gathering disabled. That way, the playbook fails instantly if the limit is not specified.

- hosts: all
  gather_facts: no
  tasks:

  - name: assert limit
    run_once: yes
    assert:
      that:
      - 'ansible_limit is defined'
      fail_msg: Playbook must be run with a limit (normally staging or production)
      quiet: yes

When a limit is not set, you get:

$ ansible-playbook site.yaml 

PLAY [all] *********************************************************************

TASK [assert limit] ************************************************************
fatal: [host1.example.net]: FAILED! => {"assertion": "ansible_limit is defined", "changed": false, "evaluated_to": false, "msg": "Playbook must be run with a limit (normally staging or production)"}

NO MORE HOSTS LEFT *************************************************************

PLAY RECAP *********************************************************************
host1.example.com : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

And when a limit is set, you get:

$ ansible-playbook -l staging site.yaml 

PLAY [all] *********************************************************************

TASK [assert limit] ************************************************************
ok: [host1.example.com]

PLAY [all] *********************************************************************

[... etc ...]

Functionally this is very similar to using the fail module, guarded with when. The difference is that the assert task itself is responsible for checking the assertions, therefore if the assertions pass, the task succeeds. When using the fail module, if the when condition fails, the task is skipped.

Related