{"id":3470,"date":"2020-02-03T01:37:29","date_gmt":"2020-02-03T00:37:29","guid":{"rendered":"http:\/\/miro.borodziuk.eu\/?p=3470"},"modified":"2020-05-08T23:00:11","modified_gmt":"2020-05-08T21:00:11","slug":"ansible-tags","status":"publish","type":"post","link":"http:\/\/miro.borodziuk.eu\/index.php\/2020\/02\/03\/ansible-tags\/","title":{"rendered":"Ansible Tags"},"content":{"rendered":"<p>For long playbooks, it is useful to be able to run subsets of the tasks in the playbook. To do this, <em>tags<\/em> can be set on specific resources as a text label. Tagging a resource only requires that the <em>tags<\/em> keyword be used, followed by a list of tags to apply. When plays are tagged, the <em>&#8211;tags<\/em> option can be used with <em>ansible-playbook<\/em> to filter the playbook to only execute specific tagged plays.<\/p>\n<p><!--more--><\/p>\n<p>Tags are available for the following resources:<\/p>\n<ul>\n<li>In playbooks, each task can be tagged, using the tags keyword:<\/li>\n<\/ul>\n<pre class=\"lang:sh decode:true\">tasks:\r\n- name: Install {{ item }} package\r\n  yum: name={{ item }} state=installed\r\n  with_items:\r\n    - postfix\r\n    - mariadb-server\r\n  tags:\r\n    - packages<\/pre>\n<ul>\n<li>When a task file is included in a playbook, the task can be tagged, allowing administrators to set a global tag for the include statement:<\/li>\n<\/ul>\n<pre class=\"lang:sh decode:true\">- include: common.yml\r\n  tags: [webproxy, webserver]<\/pre>\n<p><em><strong>Note<\/strong><\/em><br \/>\nWhen tagging a role or an include statement, all tasks they define are also tagged.<\/p>\n<pre class=\"lang:sh decode:true \">roles:\r\n- { role: databases, tags: ['production', 'staging'] }<\/pre>\n<p><em><strong>Important <\/strong><\/em><\/p>\n<p>When roles or include statements are tagged, the tag is not a way to exclude some of the tagged tasks the included files contain. Tags in this context are a way to apply a global tag to all tasks.<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Managing tagged resources<\/span><br \/>\nWhen a tag has been applied to a resource, the <em>ansible-playbook<\/em> command can be used with the <strong><em>&#8211;tags<\/em><\/strong> or <strong><em>&#8211;skip-tags<\/em><\/strong> argument to either execute the tagged resources, or prevent the tagged resources from being included in the play. The following playbook contains two tasks. The first one is tagged with the production tag, the other one does not have any tag associated with it:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: all\r\n  tasks:\r\n    - name: My tagged task\r\n      yum:\r\n        name: httpd\r\n        state: latest\r\n      tags: production\r\n   \r\n    - name: Installs postfix\r\n      yum:\r\n        name: postfix\r\n        state: latest<\/pre>\n<p>To only run the first task, the <strong><em>&#8211;tags<\/em> <\/strong>argument can be used:<\/p>\n<pre class=\"lang:sh decode:true\">[user@demo ~]$ ansible-playbook main.yml --tags 'production'\r\nTASK [My tagged task] ***************************************************\r\nok: [demo.example.com]\r\n... Output omitted ...<\/pre>\n<p>Because the &#8211;tags option was specified, the playbook only ran one task, the task tagged with the <em>production<\/em> tag. To skip the first task and only run the second task, the<strong><em> &#8211;skip-tags <\/em><\/strong>option can be used:<\/p>\n<pre class=\"lang:sh decode:true\">[user@demo ~]$ ansible-playbook main.yml --skip-tags 'production'\r\n... Output omitted ...\r\nTASK [Installs postfix] **************************************************\r\nok: [demo.example.com]<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Special tags<\/span><br \/>\nAnsible has a special tag that can be assigned in a playbook: <em><strong>always<\/strong><\/em>. This tag causes the task to always be executed even if the &#8211;skip-tags option is used, unless explicitly skipped with &#8211;skip-tags always.<\/p>\n<p>There are three special tags that can be used from the command-line with the &#8211;tags option:<br \/>\n1. The <strong>tagged<\/strong> keyword is used to run any tagged resource.<br \/>\n2. The <strong>untagged<\/strong> keyword does the opposite of the tagged keyword by excluding all tagged resources from the play.<br \/>\n3. The <strong>all<\/strong> keyword allows administrators to include all tasks in the play. This is the default behavior of the command line.<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 1. Selectively Run Specific Tasks In Playbooks Using Tags<\/span><\/p>\n<p>The playbook has two tasks. First copy inde html to the managedhost1 and is tagged as webdeploy. Second copy sql script to the managedhost2 and is tagged as dbdeploy.<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ cat playbook7.yml\r\n---\r\n- hosts: managedhost1\r\n  become: yes\r\n  tasks:\r\n  - name: deploy app binary\r\n    copy:\r\n      src: \/home\/miro\/apps\/index.html\r\n      dest: \/var\/www\/html\/index.html\r\n    tags:\r\n      - webdeploy\r\n- hosts: managedhost2\r\n  become: yes\r\n  tasks:\r\n  - name: deploy db script\r\n    copy:\r\n      src: \/home\/miro\/apps\/script.sql\r\n      dest: \/opt\/script.sql\r\n    tags:\r\n      - dbdeploy<\/pre>\n<p>You can run only first or only second tasks using a tags:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ ansible-playbook playbook7.yml --tags webdeploy\r\n\r\nPLAY [managedhost1] *******************************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ****************************************************************************************************************************\r\nok: [managedhost1]\r\n\r\nTASK [deploy app binary] **************************************************************************************************************************\r\nchanged: [managedhost1]\r\n\r\nPLAY [managedhost2] *******************************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ****************************************************************************************************************************\r\nok: [managedhost2]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************\r\nmanagedhost1 : ok=2 changed=1 unreachable=0 failed=0\r\nmanagedhost2 : ok=1 changed=0 unreachable=0 failed=0<\/pre>\n<p>And second task:<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ ansible-playbook playbook7.yml --tags dbdeploy\r\n\r\nPLAY [managedhost1] *******************************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ****************************************************************************************************************************\r\nok: [managedhost1]\r\n\r\nPLAY [managedhost2] *******************************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ****************************************************************************************************************************\r\nok: [managedhost2]\r\n\r\nTASK [deploy db script] ***************************************************************************************************************************\r\nchanged: [managedhost2]\r\n\r\nPLAY RECAP ****************************************************************************************************************************************\r\nmanagedhost1 : ok=1 changed=0 unreachable=0 failed=0\r\nmanagedhost2 : ok=2 changed=1 unreachable=0 failed=0<\/pre>\n<p>You can even use <code>--skip-tags<\/code> parameter to only run plays and tasks whose tags do not match these values.<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 2.<br \/>\n<\/span><\/p>\n<p>Create the<code> prepare_db.yml<\/code> task file and define a task that installs the required services for the database. Tag the task as dev, and have it notify the start_db handler.\u00a0 Define a second task to retrieve the database configuration file if the dev tag is active in the execution environment, and that restarts that database service. The task will use a conditional to ensure the configuration file path is defined. Define a third task to retrieve a different database configuration file if the prod tag is active in the execution environment, and that restarts the database service. Like the previous task, this task will use a conditional to ensure that the configuration file path is defined.\u00a0 Define a task that sets the Message of The Day for the managed host. Tag the task with the <em>dev<\/em> task. Repeat the previous step but change both the tag as well as the command (dev to prod).<\/p>\n<p>When completed, the task file should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- name: Install database packages\r\n  yum:\r\n    name: \"{{ item }}\"\r\n    state: latest\r\n  with_items: \"{{ db_packages }}\"\r\n  tags:\r\n    - dev\r\n  notify:\r\n    - start_db\r\n\r\n- name: Get small config file\r\n  get_url:\r\n    url: \"{{ db_config_src_path_small }}\"\r\n    dest: \"{{ db_config_path }}\"\r\n  when: db_config_src_path_small is defined\r\n  notify:\r\n    - restart_db\r\n  tags:\r\n    - dev\r\n\r\n- name: Get large config file\r\n  get_url:\r\n    url: \"{{ db_config_src_path_large }}\"\r\n    dest: \"{{ db_config_path }}\"\r\n  when: db_config_src_path_large is defined\r\n  notify:\r\n    - restart_db\r\n  tags:\r\n    - prod\r\n\r\n- name: Update motd for development\r\n  copy:\r\n    content: \"This is a development server\"\r\n    dest: \/etc\/motd\r\n  tags:\r\n    - dev\r\n\r\n- name: Update motd for production\r\n  copy:\r\n    content: \"This is a production server\"\r\n    dest: \/etc\/motd\r\n  tags:\r\n    - prod<\/pre>\n<p>In the project directory, create the main playbook, <code>playbook.yml<\/code> for servers in the databases group. The playbook will define the variables required by the task file upon import. Define the first task that includes the task file; the task file will use a conditional to ensure the managed host is in the group databases. Define the two handlers the task file requires, start_db and restart_db.<\/p>\n<p>When completed, the playbook should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: all\r\n  vars:\r\n    db_packages:\r\n      - mariadb-server\r\n      - MySQL-python\r\n    db_config_url: http:\/\/materials.example.com\/task_control\r\n    db_config_src_path_small: \"{{ db_config_url }}\/my.cnf.small\"\r\n    db_config_src_path_large: \"{{ db_config_url }}\/my.cnf.large\"\r\n    db_config_path: \/etc\/my.cnf\r\n    db_service: mariadb\r\n \r\n  tasks:\r\n    - include:\r\n        prepare_db.yml\r\n      when: inventory_hostname in groups['databases']\r\n\r\n  handlers:\r\n    - name: start_db\r\n      service:\r\n        name: \"{{ db_service }}\"\r\n        state: started\r\n   - name: restart_db\r\n     service:\r\n       name: \"{{ db_service }}\"\r\n       state: restarted<\/pre>\n<p>Run the playbook, applying the dev tag using the &#8211;tags option.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation demo-tags]$ ansible-playbook playbook.yml --tags 'dev'\r\n... Output omitted ...\r\nTASK [Get small config file] ***************************************************\r\nchanged: [servera.lab.example.com]\r\nTASK [Update motd for development] *********************************************\r\nchanged: [servera.lab.example.com]\r\n... Output omitted ...<\/pre>\n<p>Notice the configuration file that has been retrieved (<code>my.cnf.small<\/code>).<\/p>\n<p>Run an ad hoc command to display the<code> \/etc\/motd <\/code>file on servera.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-tags]$ ansible databases -a 'cat \/etc\/motd'\r\nservera.lab.example.com | SUCCESS | rc=0 &gt;&gt;\r\nThis is a development server<\/pre>\n<p>Run the playbook again, this time skipping the tasks tagged as dev:<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation demo-tags]$ ansible-playbook playbook.yml --skip-tags 'dev'\r\n... Output omitted ...\r\nTASK [Get large config file] ***************************************************\r\nok: [servera.lab.example.com]\r\nTASK [Update motd for production] **********************************************\r\nchanged: [servera.lab.example.com]\r\n... Output omitted ...<\/pre>\n<p>Run an ad hoc command to display the \/etc\/motd file on servera.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-tags]$ ansible databases -a 'cat \/etc\/motd'\r\nservera.lab.example.com | SUCCESS | rc=0 &gt;&gt;\r\nThis is a production server<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 3.<\/span><\/p>\n<p>In the project directory, create the <code>configure_mail.yml<\/code> task file. The task file contains instructions to install the required packages for the mail server, as well as instructions to retrieve the configuration files for the mail server.<br \/>\nCreate the first task that uses the <em>yum<\/em> module to install the <em>postfix<\/em> package. Notify the <em>start_postfix<\/em> handler, and tag the task as server using the tags keyword. Add a task that installs the <em>dovecot<\/em> package using the <em>yum<\/em> module. It should notify the <em>start_dovecot<\/em> handler. Tag the task as client. Define a task that uses the <em>get_url<\/em> module to retrieve the Postfix configuration file.<br \/>\nNotify the<em> restart_postfix<\/em> handler and tag the task as server.<\/p>\n<p>When completed, the task file should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- name: Install postfix\r\n  yum:\r\n    name: postfix\r\n    state: latest\r\n  tags:\r\n    - server\r\n  notify:\r\n    - start_postfix\r\n\r\n- name: Install dovecot\r\n  yum:\r\n    name: dovecot\r\n    state: latest\r\n  tags:\r\n    - client\r\n  notify:\r\n    - start_dovecot\r\n\r\n- name: Download main.cf configuration\r\n  get_url:\r\n    url: http:\/\/materials.example.com\/task_control\/main.cf\r\n    dest: \/etc\/postfix\/main.cf\r\n  tags:\r\n    - server\r\n  notify:\r\n    - restart_postfix<\/pre>\n<p>Create a playbook file named <code>playbook.yml<\/code>. Define the playbook for all hosts. Define the task that includes the task file <code>configure_mail.yml<\/code> using the include module. Add a conditional to only run the task for the hosts in the <em>mailservers<\/em> group. Define the three handlers the task file requires: <em>start_postfix<\/em>, <em>start_dovecot<\/em>, and <em>restart_postfix<\/em>. Define <em>start_postfix<\/em> handler to start the mail server. Define the <em>start_dovecot<\/em> handler to start the mail client.\u00a0 Define the <em>restart_postfix<\/em> handler that restarts the mail server.<\/p>\n<p>When completed, the playbook should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: all\r\n  tasks:\r\n  - name: Include configure_mail.yml\r\n    include:\r\n      configure_mail.yml\r\n    when: inventory_hostname in groups['mailservers']\r\n\r\n  handlers:\r\n    - name: start_postfix\r\n      service:\r\n        name: postfix\r\n        state: started\r\n\r\n    - name: start_dovecot\r\n      service:\r\n        name: dovecot\r\n        state: started\r\n\r\n    - name: restart_postfix\r\n      service:\r\n        name: postfix\r\n        state: restarted<\/pre>\n<p>Run the playbook, only applying the server tagged tasks using the <em>&#8211;tags<\/em> option. Notice how only the start_postfix handler gets triggered.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-tags]$ ansible-playbook playbook.yml --tags 'server'\r\n... Output omitted ...\r\nRUNNING HANDLER [start_postfix] ************************************************\r\nchanged: [servera.lab.example.com]\r\n... Output omitted ...\r\n<\/pre>\n<p>Run an ad hoc command to ensure the postfix package has been successfully installed.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-tags]$ ansible mailservers -a 'yum list installed postfix'\r\nservera.lab.example.com | SUCCESS | rc=0 &gt;&gt;\r\nLoaded plugins: langpacks, search-disabled-repos\r\nInstalled Packages\r\npostfix.x86_64 2:2.10.1-6.el7 @rhel_dvd\r\n<\/pre>\n<p>Run the playbook again, but this time skip the tasks tagged with the server tag. The play will install the dovecot package, because the task is tagged with the client tag, and it will trigger the start_dovecot handler.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-tags]$ ansible-playbook playbook.yml --skip-tags 'server'\r\n... Output omitted ...\r\nTASK [Install dovecot] *********************************************************\r\nchanged: [servera.lab.example.com]\r\nRUNNING HANDLER [start_dovecot] ************************************************\r\nchanged: [servera.lab.example.com]\r\n... Output omitted ...\r\n<\/pre>\n<p>Run an ad hoc command to ensure the dovecot package has been successfully installed.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-tags]$ ansible mailservers -a 'yum list installed dovecot'\r\nservera.lab.example.com | SUCCESS | rc=0 &gt;&gt;\r\nLoaded plugins: langpacks, search-disabled-repos\r\nInstalled Packages\r\ndovecot.x86_64 1:2.2.10-5.el7 @rhel_dvd<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For long playbooks, it is useful to be able to run subsets of the tasks in the playbook. To do this, tags can be set on specific resources as a text label. Tagging a resource only requires that the tags keyword be used, followed by a list of tags to apply. When plays are tagged, &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/miro.borodziuk.eu\/index.php\/2020\/02\/03\/ansible-tags\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Ansible Tags&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3482,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[86],"tags":[],"_links":{"self":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3470"}],"collection":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/comments?post=3470"}],"version-history":[{"count":12,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3470\/revisions"}],"predecessor-version":[{"id":3561,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3470\/revisions\/3561"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media\/3482"}],"wp:attachment":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media?parent=3470"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/categories?post=3470"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/tags?post=3470"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}