{"id":3276,"date":"2020-01-27T19:22:58","date_gmt":"2020-01-27T18:22:58","guid":{"rendered":"http:\/\/miro.borodziuk.eu\/?p=3276"},"modified":"2021-05-25T16:20:46","modified_gmt":"2021-05-25T14:20:46","slug":"ansible-templates","status":"publish","type":"post","link":"http:\/\/miro.borodziuk.eu\/index.php\/2020\/01\/27\/ansible-templates\/","title":{"rendered":"Ansible Templates"},"content":{"rendered":"<p>Templates give the ability to provide a skeletal file that can be dynamically completed using variables<\/p>\n<p><!--more--><\/p>\n<ul>\n<li>The most common template use case is configuration file management<\/li>\n<li>Templates are generally used by providing a template file on the ansible control node, and then using the template module within your playbook to deploy the file to a target server or group<\/li>\n<li>Templates are processed using the Jinja2 template language<\/li>\n<li>The template module is used to deploy template files<\/li>\n<li>There are two required parameters:\n<ul>\n<li><code>src<\/code> &#8211; the template to use (on the ansible control host)<\/li>\n<li><code>dest<\/code> &#8211; where the resulting file should be located (on the target host)<\/li>\n<\/ul>\n<\/li>\n<li>A useful optional parameter is <code>validate<\/code> which requires a successful validation command to run against the result file prior to deployment<\/li>\n<li>It is also possible to set basic file properties using the template module<\/li>\n<li>Template file are essentially little more than text files<\/li>\n<li>Template files are designated with a file extension of J2<\/li>\n<li>Template files have access to the same variables that the play that calls them does<\/li>\n<\/ul>\n<p><span style=\"color: #3366ff;\"> Delimiters<\/span><br \/>\nVariables or logic expressions are placed between tags, or delimiters. For example, Jinja2 templates use <code>{% EXPR %}<\/code> for expressions or logic (for example, loops), while <code>{{ EXPR }}<\/code> are used for outputting the results of an expression or a variable to the end user. The latter tag, when rendered, is replaced with a value or values, and are seen by the end user. Use <code>{# COMMENT #}<\/code> syntax to enclose comments.<\/p>\n<p>In the following example the first line includes a comment that will not be included in the final file. The variable references in the second line are replaced with the values of the system facts being referenced.<\/p>\n<pre class=\"lang:sh decode:true \">{# \/etc\/hosts line #}\r\n{{ ansible_default_ipv4.address }} {{ ansible_hostname }}<\/pre>\n<p>&nbsp;<\/p>\n<p>Example of j2 template:<\/p>\n<pre class=\"lang:sh decode:true \">#Apache HTTP server -- main configuration \r\n#\r\n# {{ ansible_managed }}\r\n\r\n## General configuration \r\nServerRoot {{ httpd_ServerRoot }} \r\nListen {{ httpd_Listen }}\r\n\r\nInclude conf.modules.d\/*.conf\r\n\r\nUser apache \r\nGroup apache\r\n\r\n## 'Main' server configuration \r\nServerAdmin {{ httpd_ServerAdmin }} \r\n{% if httpd_ServerName is defined %)\r\nServerName {{ httpd_ServerName }} \r\n{% endif %}\r\n\r\nDocumentRoot {{ httpd_DocumentRoot }}<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\"> Issues to be Aware of with YAML vs. Jinja2 in Ansible<\/span><\/p>\n<p>The use of some Jinja2 expressions inside of a YAML playbook may change the meaning for those expressions, so they require some adjustments in the syntax used.<\/p>\n<p>1. YAML syntax requires quotes when a value starts with a variable reference ({{ }}). The quotes prevent the parser from treating the expression as the start of a YAML dictionary. For example, the following playbook snippet will fail:<\/p>\n<pre class=\"lang:sh decode:true\">- hosts: app_servers\r\n  vars:\r\n    app_path: {{ base_path }}\/bin<\/pre>\n<p>Instead, use the following syntax:<\/p>\n<pre class=\"lang:sh decode:true\">- hosts: app_servers\r\n  vars:\r\n    app_path: \"{{ base_path }}\/bin\"<\/pre>\n<p>&nbsp;<\/p>\n<p>2. When there is a need to include nested {{&#8230;}} elements, the braces around the inner ones must be removed. Consider the following playbook snippet:<\/p>\n<pre class=\"lang:sh decode:true\">- name: display the host value\r\n  debug:\r\n    msg: hostname = {{ params[ {{ host_ip }} ] }}, IPaddr = {{ host_ip }}<\/pre>\n<p>Ansible raises the following error when it tries to run it:<\/p>\n<pre class=\"lang:sh decode:true \">... Output omitted ...\r\nTASK [display the host value] **************************************************\r\nfatal: [localhost]: FAILED! =&gt; {\"failed\": true, \"msg\": \"template error\r\nwhile templating string: expected token ':', got '}'. String: hostname =\r\n{{ params[ {{ host_ip }} ] }}, IPaddr = {{ host_ip }}\"}\r\n... Output omitted ...<\/pre>\n<p>Use the following syntax instead. It will run without error.<\/p>\n<pre class=\"lang:sh decode:true \">- name: display the host value\r\n  debug:\r\n    msg: hostname = {{ params[ host_ip ] }}, IPaddr = {{ host_ip }}<\/pre>\n<p>Now the playbook will run without error.<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 1.<\/span><\/p>\n<p>Let&#8217;s create jinja2 template:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ cat ..\/templates\/network.j2\r\n{{ ansible_default_ipv4.address }}<\/pre>\n<p>And now we can create a playbook which will use this template:<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ cat playbook8.yml\r\n---\r\n- hosts: localhost\r\n  tasks:\r\n  - name: deploy local net file\r\n    template:\r\n      src: \/home\/miro\/ansible\/templates\/network.j2\r\n      dest: \/home\/miro\/ansible\/templates\/network.txt<\/pre>\n<p>We can run to see what this playbook do:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ ansible-playbook playbook8.yml\r\n\r\nPLAY [localhost] *****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [localhost]\r\n\r\nTASK [deploy local net file] *****************************************************************************************************\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nlocalhost : ok=2 changed=1 unreachable=0 failed=0\r\n\r\n[miro@controlnode playbooks]$ cat ..\/templates\/network.txt\r\n172.30.9.50<\/pre>\n<p>We can modifiy the j2 template and playbook8 will return other txt file:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ cat ..\/templates\/network.j2\r\nMy IP address is {{ ansible_default_ipv4.address }}\r\n{{ ansible_distribution }} is my OS version\r\n\r\n[miro@controlnode playbooks]$ ansible-playbook playbook8.yml\r\n\r\nPLAY [localhost] *****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [localhost]\r\n\r\nTASK [deploy local net file] *****************************************************************************************************\r\nchanged: [localhost]\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nlocalhost : ok=2 changed=1 unreachable=0 failed=0\r\n\r\n[miro@controlnode playbooks]$ cat ..\/templates\/network.txt\r\nMy IP address is 172.30.9.50\r\nCentOS is my OS version<\/pre>\n<p>&nbsp;<\/p>\n<p>The playbook below has two plays. First play if for group webservers. This play has two tasks. Task related with yum installs the latest httpd package. Task second is template whis puts the text to do httpd.conf fiile. The second play also have two tasks. It installs the postgres and ensure that service is started.<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: webservers\r\n  remote user: root\r\n\r\n  tasks:\r\n  - name: ensure apache is at the latest version\r\n    yum: name=httpd state=latest\r\n  - name: write the apache config file\r\n    template: src=\/srv\/httpd.j2 dest=\/etc\/httpd.conf\r\n\r\n- hosts: databases\r\n  remote user: root\r\n\r\n  tasks:\r\n  - name: ensure postgresql is at the latest version\r\n    yum: name=postgresql state=latest\r\n  - name: ensure that postgresql is started\r\n    service: name=postgresql state=started<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 2.<\/span><\/p>\n<p>1. Create the inventory file in the current directory. This file configures two groups: webservers and workstations. Include the system servera.lab.example.com in the webservers group, and the system workstation.lab.example.com in the workstations group.<\/p>\n<pre class=\"lang:sh decode:true \">[webservers]\r\nservera.lab.example.com\r\n[workstations]\r\nworkstation.lab.example.com<\/pre>\n<p>2. Create a template for the Message of the Day. Include it in the<code> motd.j2<\/code> file in the current directory.\u00a0 Include the following variables in the template:<\/p>\n<ul>\n<li>\u00a0<code>ansible_hostname<\/code> to retrieve the managed host hostname.<\/li>\n<li><code>ansible_date_time.date<\/code> for the managed host date.<\/li>\n<li><code>system_owner<\/code> for the email of the owner of the system. This variable requires to be defined, with an appropriate value, in the vars section of the playbook template.<\/li>\n<\/ul>\n<pre class=\"lang:sh decode:true \">This is the system {{ ansible_hostname }}.\r\nToday's date is: {{ ansible_date_time.date }}.\r\nOnly use this system with permission.\r\nYou can ask {{ system_owner }} for access.<\/pre>\n<p>3. Create a playbook in a new file in the current directory, named <code>motd.yml<\/code>. Define the system_owner variable in the vars section, and include a task for the template module, which maps the motd.j2 Jinja2 template to the remote file <code>\/etc\/motd<\/code> in the managed hosts. Set the owner and group to root, and the mode to <code>0644<\/code>.<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: all\r\n  user: devops\r\n  become: true\r\n  vars:\r\n    system_owner: clyde@example.com\r\n  tasks:\r\n  - template:\r\n      src: motd.j2\r\n      dest: \/etc\/motd\r\n      owner: root\r\n      group: root\r\n      mode: 0644<\/pre>\n<p>&nbsp;<\/p>\n<p>4. Run the playbook included in the <code>motd.yml<\/code> file.<\/p>\n<pre class=\"lang:sh decode:true\">[student@workstation jinja2]$ ansible-playbook motd.yml\r\nPLAY ***************************************************************************\r\nTASK [setup] *******************************************************************\r\nok: [servera.lab.example.com]\r\nok: [workstation.lab.example.com]\r\nTASK [template] ****************************************************************\r\nchanged: [servera.lab.example.com]\r\nchanged: [workstation.lab.example.com]\r\nPLAY RECAP *********************************************************************\r\nservera.lab.example.com : ok=2 changed=1 unreachable=0 failed=0\r\nworkstation.lab.example.com : ok=2 changed=1 unreachable=0 failed=0<\/pre>\n<p>&nbsp;<\/p>\n<p>5. Log in to servera.lab.example.com using the devops user, to verify the motd is displayed when logging in. Log out when you have finished.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation jinja2]$ ssh devops@servera.lab.example.com\r\nThis is the system servera.\r\nToday's date is: 2016-04-11.\r\nOnly use this system with permission.\r\nYou can ask clyde@example.com for access.\r\n[devops@servera ~]# exit\r\nConnection to servera.lab.example.com closed.<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 3.<\/span><\/p>\n<p>1. Create the inventory file. This file configures one group: servers. Include the system serverb.lab.example.com in the servers group.<\/p>\n<pre class=\"lang:sh decode:true\">[servers]\r\nserverb.lab.example.com<\/pre>\n<p>2. Identify the facts in<code> serverb.lab.example.com<\/code> which show what is the status of the system memory. Use the setup module to get a list of all the facts for the serverb.lab.example.com managed host. Both the<code> ansible_memfree_mb<\/code> and <code>ansible_memtotal_mb facts<\/code> provide information about the free memory and the total memory of the managed host.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation jinja2-lab]$ ansible serverb.lab.example.com -m setup\r\nserverb.lab.example.com | SUCCESS =&gt; {\r\n\"ansible_facts\": {\r\n... Output omitted ...\r\nSolution\r\nDO407-A2.0-en-1-20160804 237\r\n\"ansible_memfree_mb\": 741,\r\n... Output omitted ...\r\n\"ansible_memtotal_mb\": 992,\r\n... Output omitted ...\r\n},\r\n\"changed\": false\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>3. Create a template for the Message of the Day, named <code>motd.j2<\/code>, in the current directory. Use the facts previously identified. Create a new file, named <code>motd.j2<\/code>, in the current directory. Use both the <code>ansible_memfree_mb<\/code> and <code>ansible_memtotal_mb facts<\/code> variables to create a Message of the Day.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation jinja2-lab]$ cat motd.j2\r\nThis system's total memory is: {{ ansible_memtotal_mb }} MBs.\r\nThe current free memory is: {{ ansible_memfree_mb }} MBs.<\/pre>\n<p>&nbsp;<\/p>\n<p>4. Create a new playbook file, named <code>motd.yml<\/code>. Using the template module, configure the<code> motd.j2<\/code> Jinja2 template file previously created, to be mapped to the file<code> \/etc\/motd<\/code> in the managed hosts. This file has the root user as owner and group, and its permissions are <code>0644<\/code>. Configure the playbook so it uses the <code>devops<\/code> user, and setup the become parameter to be true.<\/p>\n<pre class=\"lang:sh decode:true\">[student@workstation jinja2-lab]$ cat motd.yml\r\n---\r\n- hosts: all\r\n  user: devops\r\n  become: true\r\n  tasks:\r\n  - template:\r\n      src: motd.j2\r\n      dest: \/etc\/motd\r\n      owner: root\r\n      group: root\r\n      mode: 0644<\/pre>\n<p>&nbsp;<\/p>\n<p>5. Run the playbook included in the motd.yml file.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation jinja2-lab]$ ansible-playbook motd.yml\r\nPLAY ***************************************************************************\r\nTASK [setup] *******************************************************************\r\nok: [serverb.lab.example.com]\r\nTASK [template] ****************************************************************\r\nchanged: [serverb.lab.example.com]\r\nChapter\u00a06.\u00a0Implementing Jinja2 Templates\r\n238 DO407-A2.0-en-1-20160804\r\nPLAY RECAP *********************************************************************\r\nserverb.lab.example.com : ok=2 changed=1 unreachable=0 failed=0<\/pre>\n<p>&nbsp;<\/p>\n<p>6. Check that the playbook included in the <code>motd.yml<\/code> file has been executed correctly. Log in to <code>serverb.lab.example.com<\/code> using the devops user, to verify the motd is displayed when logging in. Log out when you have finished.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation jinja2-lab]$ ssh devops@serverb.lab.example.com\r\nThis system's total memory is: 992 MBs.\r\nThe current free memory is: 741 MBs.\r\n[devops@serverb ~]$ logout<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Templates give the ability to provide a skeletal file that can be dynamically completed using variables<\/p>\n","protected":false},"author":1,"featured_media":3277,"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\/3276"}],"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=3276"}],"version-history":[{"count":10,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3276\/revisions"}],"predecessor-version":[{"id":3387,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3276\/revisions\/3387"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media\/3277"}],"wp:attachment":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media?parent=3276"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/categories?post=3276"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/tags?post=3276"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}