Ansible - Default/Explicit Tags

2019-04-06 04:23发布

问题:

I've got a playbook that includes and tags various roles:

- name:  base
  hosts: "{{ host | default('localhost') }}"

roles: 

  - { role: apt,              tags: [ 'base', 'apt', 'ubuntu']}
  - { role: homebrew,         tags: [ 'base', 'homebrew', osx' ]}
  - { role: base16,           tags: [ 'base', 'base16', 'osx' ]}
  - { role: nodejs,           tags: [ 'base', 'nodejs' ]}
  - { role: tmux,             tags: [ 'base', 'tmux' ]}
  - { role: vim,              tags: [ 'base', 'vim' ]}
  - { role: virtualenv,       tags: [ 'base',  virtualenv', 'python' ]}
  - { role: homebrew_cask,    tags: [ 'desktop', 'homebrew_cask', osx' ]}
  - { role: gnome_terminator, tags: [ 'desktop', 'gnome_terminator', ubuntu' ]}

Most of the tasks are using when clauses to determine which OS they should run on, for example:

- name: install base packages
  when: ansible_distribution == 'MacOSX'
  sudo: no
  homebrew:
    name: "{{ item.name }}"
    state: latest
    install_options: "{{ item.install_options|default() }}"
  with_items: homebrew_packages

If I run ansible-playbook base.yml without specifying any tags, all the tasks run. If I specify a tag, for example ansible-playbook base.yml --tags='base', only the roles tagged with base run.

By default (if no tags are specified), I'd only like to run the roles tagged with 'base', and not the roles tagged with 'desktop'.

It would also be really nice to set a default 'os' tag, based on the current operating system, to avoid including all the tasks for the ubuntu when I'm running the playbook on OSX (and vice-versa).

Any ideas if this is possible, and how I might do it?

回答1:

Unfortunately there is no such feature. Tag handling in Ansible currently is very limited. You can not set default tags and you can not exclude tags by default.

There are some threads on the Google user group and feature requests on github regarding this. But no outcome yet. The common answer so far is, you should create a shell script and place it in front of your playbook. This script then can set the --tags and --skip-tags accordingly to your needs. Very unpleasant but as far as I know the only option right now.



回答2:

If I run ansible-playbook base.yml without specifying any tags, all the tasks run.

Yes, this is very dangerous. If You forget to add '--tags=xxxxx' it may run unwanted tasks...

There is a workaround, it's nasty, but it would prevent running Your tasks when there are no tags in commandline.

You could use --extra-vars and use it in your playbook, then run:

ansible-playbook base.yml -e'SOMEVAR=TRUE'

And in Your playbook:

- hosts: localhost
  tasks:
   - name: some task
     ping:
     tags:
      - sometag

   - name: Register SOMEVARANS
     set_fact: SOMEVARANS={{SOMEVAR | default('False')}}
     tags:
      - every_tag_you_know_since_there_is_no_ALL_option
      - sometag

   - debug: msg="{{SOMEVARANS}}"
     tags:
      - every_tag_you_know_since_there_is_no_ALL_option
      - sometag

   - debug: msg="run only with extravars SOMEVAR=True"
     when: SOMEVARANS
     tags:
      - every_tag_you_know_since_there_is_no_ALL_option
      - sometag

The result:

$ ansible-playbook -i /subsystem/ansible/etc/inventory-qa.ini  tags.yml

PLAY [localhost] **************************************************************

TASK: [some task] *************************************************************
ok: [localhost]

TASK: [Register SOMEVARANS] ***************************************************
ok: [localhost]

TASK: [debug msg="{{SOMEVARANS}}"] ********************************************
ok: [localhost] => {
    "msg": "False"
}

TASK: [debug msg="run only with extravars SOMEVAR=True"] **********************
skipping: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0



 WITH 'SOMEVAR=True'
    $ ansible-playbook -i /subsystem/ansible/etc/inventory-qa.ini  tags.yml  -e 'SOMEVAR=True'

    PLAY [localhost] **************************************************************

    TASK: [some task] *************************************************************
    ok: [localhost]

    TASK: [Register SOMEVARANS] ***************************************************
    ok: [localhost]

    TASK: [debug msg="{{SOMEVARANS}}"] ********************************************
    ok: [localhost] => {
        "msg": "True"
    }

    TASK: [debug msg="run only with extravars SOMEVAR=True"] **********************
    ok: [localhost] => {
        "msg": "run only with extravars SOMEVAR=True"
    }

    PLAY RECAP ********************************************************************
    localhost                  : ok=4    changed=0    unreachable=0    failed=0


ALL IN ONE :)
$ ansible-playbook -i /subsystem/ansible/etc/inventory-qa.ini  tags.yml  -e 'SOMEVAR=True'  --tags=sometag

PLAY [localhost] **************************************************************

TASK: [some task] *************************************************************
ok: [localhost]

TASK: [Register SOMEVARANS] ***************************************************
ok: [localhost]

TASK: [debug msg="{{SOMEVARANS}}"] ********************************************
ok: [localhost] => {
    "msg": "True"
}

TASK: [debug msg="run only with extravars SOMEVAR=True"] **********************
ok: [localhost] => {
    "msg": "run only with extravars SOMEVAR=True"
}

PLAY RECAP ********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0


回答3:

I use command-line variable overrides (-e) to achieve this:

$ ansible-playbook playbook.xml -e desktop=true

Rather than define desktop as a tag, it could instead be defined as a variable with a default value of false. Then, for roles requiring desktop to be true, replace the tag with a when clause. The playbook posted in the question might be rewritten like this:

- name:  base
  hosts: "{{ host | default('localhost') }}"

  # default value to prevent errors when not overridden
  vars:
    desktop: false

  roles:

    # no change to "base" roles
    - { role: apt, tags: [ 'base', 'apt', 'ubuntu'] }
    # ...

    # "desktop" roles get a "when" clause
    - role: homebrew_cask
      when: desktop | bool
      tags: [ 'homebrew_cask', 'osx' ]
    - role: gnome_terminator
      when: desktop | bool
      tags: [ 'gnome_terminator', ubuntu' ]

This is probably just a simplified version of one of the existing answers. As stated elsewhere, tags aren't the answer at this time to defaulting some actions to not run unless something is explicitly specified. Variables, on the other hand, are perfect for this.



回答4:

Since Ansible 2.5 there is a new feature which solves these kinds of situations.

Another special tag is never, which will prevent a task from running unless a tag is specifically requested.

Example:

tasks:
  - debug: msg='{{ showmevar}}'
    tags: [ 'never', 'debug' ]

So your problem should be addressed like this:

- name:  base
  hosts: "{{ host | default('localhost') }}"

roles: 

  - { role: apt,              tags: [ 'base', 'apt', 'ubuntu']}
  - { role: homebrew,         tags: [ 'base', 'homebrew', osx' ]}
  - { role: base16,           tags: [ 'base', 'base16', 'osx' ]}
  - { role: nodejs,           tags: [ 'base', 'nodejs' ]}
  - { role: tmux,             tags: [ 'base', 'tmux' ]}
  - { role: vim,              tags: [ 'base', 'vim' ]}
  - { role: virtualenv,       tags: [ 'base',  virtualenv', 'python' ]}
  - { role: homebrew_cask,    tags: [ 'never','desktop', 'homebrew_cask', osx' ]}
  - { role: gnome_terminator, tags: [ 'never','desktop', 'gnome_terminator', ubuntu' ]}


回答5:

There are another 3 special keywords for tags, ‘tagged’, ‘untagged’ and ‘all’, which run only tagged, only untagged and all tasks respectively. By default ansible runs as if ‘–tags all’ had been specified.

You can check the docs here: http://docs.ansible.com/ansible/playbooks_tags.html#special-tags



回答6:

The reason to use default usually is: I want to type as less as possible in my most common use case. So I think what OP actually want is:

Are there ways to run 'base' tasks with as less arguments as possible.

As others pointed out, there are no "default" tag in ansible. But there are always ways.

First way is just use script to wrap it, like writing a run.sh like this:

ansible-playbook -t base base.yml

It is much shorter to type ./run.sh instead of full command.

Second way is for the people that is looking for the pure ansible solution. You can have two playbooks like:

base.yml

- { role: apt,              tags: [ 'base', 'apt', 'ubuntu']}
- { role: homebrew,         tags: [ 'base', 'homebrew', osx' ]}
- { role: base16,           tags: [ 'base', 'base16', 'osx' ]}
- { role: nodejs,           tags: [ 'base', 'nodejs' ]}
- { role: tmux,             tags: [ 'base', 'tmux' ]}
- { role: vim,              tags: [ 'base', 'vim' ]}
- { role: virtualenv,       tags: [ 'base',  virtualenv', 'python' ]}

desktop.yml

- { role: homebrew_cask,    tags: [ 'desktop', 'homebrew_cask', osx' ]}
- { role: gnome_terminator, tags: [ 'desktop', 'gnome_terminator', ubuntu' ]}

For base only tasks, run ansible-playbook base.yml; for desktop only tasks, run ansible-playbook desktop.yml; for all tasks, run ansible-playbook base.yml desktop.yml
It is also more error-prone than defining a 'default' tag.

OP also wants a default OS tag. It is easy to achieve that with when

   - { role: some_role, when: "ansible_os_family == 'Debian'" } 

with when, you don't need to do anything in command line, ansible detects the type and runs corresponding tasks for you.