Difference between Ansible's copy and template module
Tags: ansible
Published: 2019-11-09 09:26:12

Ansible has copy module and template module.
Basically, both modules are to copy files from local machine to remote machines, but technically behaviors are a bit different.
In this note, I would like to go through the difference between them and points to care.

*Version of Ansible is 2.8.1
## copy

https://docs.ansible.com/ansible/latest/modules/copy_module.html#copy-module


### Basic features

- Variables cannot be used in the file
- default path to src files are under `roles/{{ rolename }}/files`
- with remote_src parameter, you can copy files from remote machines to the machine's other place
- with content parameter, you can specify the contents of files directly without src files


## template

https://docs.ansible.com/ansible/latest/modules/template_module.html#template-module

### Basic features

- with Jinja2, you can program like for loop or if conditions in files
- Variables can be used in files
- default path to src files are under `roles/{{ rolename }}/templates`


### About Jinja2

I would like to explain about Jinja2 a bit.

http://jinja.pocoo.org/docs/2.10/#

>Jinja2 is a modern and designer-friendly templating language for Python, modelled after Django’s templates.

Here are simple examples of for loop and if conditions


```
[koh@kohs-MBP] ~/vag_test
% cat test.j2
{% for item in ["foo","bar","baz"] %}
{{ item }}
{% endfor %}

{% if inventory_hostname == "Vag2" %}
hostname is Vag2
{% else %}
hostname is not Vag2
{% endif %}
[koh@kohs-MBP] ~/vag_test
%
```

With for loop it prints foo,bar,baz.
After that, if inventory_hostname is Vag2, then print `hostname is Vag2`
if not, print `hostname is not Vag2`.

Below is the result.

```
[koh@kohs-MBP] ~/vag_test
% ansible Vag1 -m template -a "src=test.j2 dest=."
Vag1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "c2383df182afda3f4de83ce7171885ce7fd4839a",
    "dest": "././test.j2",
    "gid": 1000,
    "group": "vagrant",
    "md5sum": "54a03d50ba7a7751f6d647afdeb80e02",
    "mode": "0644",
    "owner": "vagrant",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 34,
    "src": "/home/vagrant/.ansible/tmp/ansible-tmp-1562164682.016716-96505856164811/source",
    "state": "file",
    "uid": 1000
}
[koh@kohs-MBP] ~/vag_test
% ssh Vag1 "cat test.j2"
foo
bar
baz

hostname is not Vag2
[koh@kohs-MBP] ~/vag_test
%
```

By using Jinja2 or Ansible's variables, your Playbook would be much better for practical uses like the below cases.

- using for loops for configs of reverse proxies like HAProxy or Nginx
- using variables is useful to share the same templates for prod/stg/dev environments

### Important points

As I explained template module is very convenient, but as long as it modifies original files at execution of Ansible, there is a possibility of miss configurations.
 
Wrong syntax is common mistakes, but worse cases are wrong recognition of Jinja2 format by Ansible.

For example, Apache's config about LogFormat might be recognized as Jinja2.
Here is a template with Logformat entry and running Ansible.

```
[koh@kohs-MBP] ~/vag_test
% cat logformat.j2
LogFormat "%{%d/%b/%Y %T}t.%{msec_frac}t %{%z}t"
[koh@kohs-MBP] ~/vag_test
%
[koh@kohs-MBP] ~/vag_test
% ansible Vag1 -m template -a "src=logformat.j2 dest=."
Vag1 | FAILED! => {
    "changed": false,
    "msg": "AnsibleError: template error while templating string: Encountered unknown tag 'd'.. String: LogFormat \"%{%d/%b/%Y %T}t.%{msec_frac}t %{%z}t\"\n"
}
zsh: exit 2     ansible Vag1 -m template -a "src=logformat.j2 dest=."
[koh@kohs-MBP] ~/vag_test
%
```
  
`{%d` in the template is recognized as a Tag of Jinja2 and Ansible threw the error.

### How to avoid these errors

Below ideas are good for avoiding the above errors and miss configurations.

#### Use copy module when Jinja2 templating or variables are not used

Copy module never modifies the source file so using copy module is properly is the best way to make Playbook safer.

#### Use validate parameter

Template module has validate parameter which executes a command to check the validity of templates.
It really works when setting up critical configurations that cannot be failed like sshd or sudoers.
Copy module also has this parameter so it is still useful for copy module too.

```
- name: Copy a new sudoers file into place, after passing validation with visudo
  template:
    src: /mine/sudoers
    dest: /etc/sudoers
    validate: /usr/sbin/visudo -cf %s
```

#### Use check mode

Ansible has check mode(-C) you can check how the Playbook works without making any changes to the actual environment.
It works great with Diff option(-D).


```
[koh@kohs-MBP] ~/vag_test
% ansible Vag1 -m template -a "src=test.j2 dest=." -D -C
--- before: ./test.j2
+++ after: /Users/koh/.ansible/tmp/ansible-local-90545ps884h5s/tmpuvs9nt1z/test.j2
@@ -1,4 +1,4 @@
-foo
+fooooo
 bar
 baz


Vag1 | CHANGED => {
    "changed": true
}
[koh@kohs-MBP] ~/vag_test
%
```

## Conclusion

By using template module effectively, your Playbook would be more practical.
Ansible is famous for simplicity, but template can bring chaos to your Playbook so please use with consideration not to use too much.