In the previous article in this series, 8 steps to developing an Ansible role in Linux, I created an Ansible role to install Vim and configure it with some plugins and a static vimrc
configuration file.
This article improves this role by replacing this static configuration file with a more flexible one, dynamically generated using an Ansible template.
Ansible templates allow you to create files dynamically by interpolating variables or using logical expressions such as conditionals and loops. It's useful to define configuration files that adapt to different contexts without having to manage additional files. For example, you can create an /etc/hosts
file containing the current node's IP address. You can then execute the playbook in different hosts, resulting in different files—one for each unique host.
The Ansible template engine uses Jinja2 template language, a popular template language for the Python ecosystem. Jinja allows you to interpolate variables and expressions with regular text by using special characters such as {
and {%
. By doing this, you can keep most of the configuration file as regular text and inject logic only when necessary, making it easier to create, understand, and maintain template files.
You can use this template language throughout Ansible playbooks but, to instantiate configuration files based on templates, you use the template module. This module works similar to the copy module but, instead of copying a predefined file, it first renders the template file based on its embedded logic, then copies it to the target host. This module is idempotent, and it only replaces the file if the target's content does not match the rendered template's content.
This example builds upon the role developed in the previous article. If you don't have it, you can find the original files in this repository.
Define a template
Get started by creating a template directory. Switch to the vim
role directory and create a new subdirectory, templates
. This is not strictly necessary, but Ansible looks for template files in this subdirectory by default, making it easier to use them without specifying a full path:
$ cd roles/vim
$ mkdir templates
$ ls
defaults files handlers meta molecule README.md tasks templates tests vars
Then switch into the newly created directory and edit a new template file. By convention, name the template file with the target file name and the .j2
extension. In this example, since you're templating the Vim configuration file .vimrc
, the template name is vimrc.j2
:
$ cd templates
$ vim vimrc.j2
Add the basic static configuration lines at the top of the file:
execute pathogen#infect()
syntax on
filetype plugin indent on
These initial lines are still static and will always be the same for every deployment. So next, let's interpolate some variables to generate a dynamic configuration.
[ Download now: A system administrator's guide to IT automation. ]
Using variables
The primary way to customize a template is by interpolating variables. Add a placeholder for a variable by providing the variable name between pairs of curly braces {{ }}
. When rendering the template, Ansible replaces the entire expression with the variable's value. For example, define a dynamic Vim configuration, allowing the user to specify the desired color scheme by using variable color_scheme
, like this:
colo {{ color_scheme }}
Use another variable, fzf_preview
, to specify the preview window type for the FZF plugin:
" Configuration Vim.FZF
let g:fzf_preview_window = '{{ fzf_preview }}'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" Configuration NERDTree
map <F5> :NERDTreeToggle<CR>
Using variables provides the flexibility to address several requirements, but the template engine can do even more. Next, let's create a conditional block.
Conditional configuration
In some cases, variables are not enough to do what you need. For example, imagine that you want to include a block of text in the configuration file only in certain conditions. The template engine provides more complex expressions, such as if
conditions. Add these conditions delimited by {% %}
. For example, you can include the Airline configuration lines to your .vimrc
file, but only if the vim-airline plugin is in the list of plugins to install:
{% if 'vim-airline' in plugins | map(attribute='name') | list %}
" Configuration vim Airline
set laststatus=2
let g:airline#extensions#tabline#enabled=1
let g:airline_powerline_fonts=1
{% endif %}
In this case, the if
condition looks for the name vim-airline
in the list of plugin names that are extracted from the variable plugins
using the map filter
. For more information about Ansible filters, check the documentation.
Use Ansible facts
In addition to variables that you define in the role or playbook directly, you can also customize your templates by using data collected from the hosts using Ansible facts. The data is available in the ansible_facts
dictionary where each key represents a piece of data collected from the target system.
This example configures the floaterm plugin shell as the user-defined shell in the target machine:
" Configuration floaterm
let g:floaterm_shell = '{{ ansible_facts["user_shell"] }} --login'
This is a powerful resource that allows you to generate a configuration tailored for every host automatically.
[ Need more on Ansible? Take a free technical overview course from Red Hat. Ansible Essentials: Simplicity in Automation Technical Overview. ]
Looping
The last resource you'll use in this example is for
loops. Loops allow you to iterate over a list or dictionary and execute something for each item found. This case allows users to provide a dictionary of extra options they want to use to configure floaterm and define a configuration line for each, like this:
{% for opt in floaterm_options %}
let g:{{ opt }} = {{ floaterm_options[opt] }}
{% endfor %}
In this case, floaterm_options is a dictionary of options defined with a key and value. For each iteration of the loop, the template engine assigns the key to variable opt
. You can use it directly within the for
block or use it to extract the value using the dictionary notation floaterm_options[opt]
.
Define default values for variables
You completed the template definition. The entire template looks like this:
execute pathogen#infect()
syntax on
filetype plugin indent on
colo {{ color_scheme }}
" Configuration Vim.FZF
let g:fzf_preview_window = '{{ fzf_preview }}'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" Configuration NERDTree
map <F5> :NERDTreeToggle<CR>
{% if 'vim-airline' in plugins | map(attribute='name') | list %}
" Configuration vim Airline
set laststatus=2
let g:airline#extensions#tabline#enabled=1
let g:airline_powerline_fonts=1
{% endif %}
" Configuration floaterm
let g:floaterm_shell = '{{ ansible_facts["user_shell"] }} --login'
{% for opt in floaterm_options %}
let g:{{ opt }} = {{ floaterm_options[opt] }}
{% endfor %}
Save and close this file, and then edit the default variable file defaults/main.yml
to define default values for these variables in case the user does not specify them:
---
# defaults file for vim
color_scheme: darkblue
floaterm_options:
floaterm_width: 0.9
floaterm_height: 0.9
floaterm_keymap_toggle: "'<F12>'"
fzf_preview: "right:50%"
plugins:
- name: vim-airline
url: https://github.com/vim-airline/vim-airline
- name: nerdtree
url: https://github.com/preservim/nerdtree
- name: fzf-vim
url: https://github.com/junegunn/fzf.vim
- name: vim-gitgutter
url: https://github.com/airblade/vim-gitgutter
- name: vim-fugitive
url: https://github.com/tpope/vim-fugitive
- name: vim-floaterm
url: https://github.com/voldikss/vim-floaterm
In this case, if the user does not specify the color scheme, the role uses darkblue by default. This is a good way to provide a ready-to-use configuration, yet allow the user to make changes when needed.
Save and close the default variables file. Next, ensure the role uses the template file.
Edit the task file to use the template
Now that your template file and default variables are ready, use them in your role by updating the task Ensure .vimrc config in place
in the file tasks/main.yml
to use the template module instead of the copy module. Make sure the src template name matches the template file with the .j2
extension:
---
- name: Ensure .vimrc config in place
template:
src: vimrc.j2
dest: "{{ vimrc }}"
backup: true
mode: 0640
This is the only required change to the tasks. The role executes all the other tasks in the same way and, in the end, renders the template and uses the resulting file as Vim's configuration file.
[ Looking for more on system automation? Get started with The Automated Enterprise, a free book from Red Hat. ]
Execute the playbook
Now, test your role by executing the playbook using the ansible-playbook
command and the playbook name, the same way you ran it in the previous article. If you've executed this playbook with the vim role before, it will change the configuration file only if needed. If this is the first time you've run it, it will execute all the tasks.
Note: Back up an existing .vimrc
configuration file prior to running this playbook:
$ cd ../..
$ ansible-playbook -K vim-config.yaml
Check the resulting configuration file to see the rendered template:
$ cat $HOME/.vimrc
execute pathogen#infect()
syntax on
filetype plugin indent on
colo darkblue
" Configuration Vim.FZF
let g:fzf_preview_window = 'right:50%'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" Configuration NERDTree
map <F5> :NERDTreeToggle<CR>
" Configuration vim Airline
set laststatus=2
let g:airline#extensions#tabline#enabled=1
let g:airline_powerline_fonts=1
" Configuration floaterm
let g:floaterm_shell = '/bin/bash --login'
let g:floaterm_width = 0.9
let g:floaterm_height = 0.9
let g:floaterm_keymap_toggle = '<F12>'
You can execute the playbook, vim-config.yaml
, again, changing some variables to see the effect on the configuration file. For example, remove the plugin vim-airline from the list, and change the color scheme to industry:
---
- name: Config Vim with plugins
hosts: localhost
gather_facts: true
become: false
tasks:
- name: Configure Vim using role
import_role:
name: vim
vars:
color_scheme: industry
plugins:
- name: nerdtree
url: https://github.com/preservim/nerdtree
- name: fzf-vim
url: https://github.com/junegunn/fzf.vim
- name: vim-gitgutter
url: https://github.com/airblade/vim-gitgutter
- name: vim-fugitive
url: https://github.com/tpope/vim-fugitive
- name: vim-floaterm
url: https://github.com/voldikss/vim-floaterm
Save the playbook, and execute it again:
$ ansible-playbook -K vim-config.yaml
The new configuration file no longer has the Airline configuration:
$ cat $HOME/.vimrc
execute pathogen#infect()
syntax on
filetype plugin indent on
colo industry
" Configuration Vim.FZF
let g:fzf_preview_window = 'right:50%'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" Configuration NERDTree
map <F5> :NERDTreeToggle<CR>
" Configuration floaterm
let g:floaterm_shell = '/bin/bash --login'
let g:floaterm_width = 0.9
let g:floaterm_height = 0.9
let g:floaterm_keymap_toggle = '<F12>'
What's next?
Templating is one of Ansible's most powerful features. It allows you to define a customizable, dynamic configuration that adapts to each target machine during the playbook execution. You can manage configuration files for hundreds of target machines within a single file or a small group of files by using templates. It makes your roles more flexible and easier to maintain.
This article covered the template engine's basic features, and you can go a long way using only these features. The Jinja2 template language provides many other features that can help you create extensible and adaptable templates for a variety of needs. Make sure to consult its documentation for additional details.
For more information about Ansible, consult its official documentation.
저자 소개
Ricardo Gerardi is Technical Community Advocate for Enable Sysadmin and Enable Architect. He was previously a senior consultant at Red Hat Canada, where he specialized in IT automation with Ansible and OpenShift.
He has been a Linux and open source enthusiast and contributor for over 20 years. He is currently interested in hacking stuff using the Go programming language, and he's the author of Powerful Command-Line Applications in Go: Build Fast and Maintainable Tools. Ricardo also writes regularly about Linux, Vim, and command line tools for Opensource.com and Enable Sysadmin community publications.
Ricardo enjoys spending time with his daughters, reading science fiction books, and playing video games.
채널별 검색
오토메이션
기술, 팀, 인프라를 위한 IT 자동화 최신 동향
인공지능
고객이 어디서나 AI 워크로드를 실행할 수 있도록 지원하는 플랫폼 업데이트
오픈 하이브리드 클라우드
하이브리드 클라우드로 더욱 유연한 미래를 구축하는 방법을 알아보세요
보안
환경과 기술 전반에 걸쳐 리스크를 감소하는 방법에 대한 최신 정보
엣지 컴퓨팅
엣지에서의 운영을 단순화하는 플랫폼 업데이트
인프라
세계적으로 인정받은 기업용 Linux 플랫폼에 대한 최신 정보
애플리케이션
복잡한 애플리케이션에 대한 솔루션 더 보기
오리지널 쇼
엔터프라이즈 기술 분야의 제작자와 리더가 전하는 흥미로운 스토리
제품
- Red Hat Enterprise Linux
- Red Hat OpenShift Enterprise
- Red Hat Ansible Automation Platform
- 클라우드 서비스
- 모든 제품 보기
툴
체험, 구매 & 영업
커뮤니케이션
Red Hat 소개
Red Hat은 Linux, 클라우드, 컨테이너, 쿠버네티스 등을 포함한 글로벌 엔터프라이즈 오픈소스 솔루션 공급업체입니다. Red Hat은 코어 데이터센터에서 네트워크 엣지에 이르기까지 다양한 플랫폼과 환경에서 기업의 업무 편의성을 높여 주는 강화된 기능의 솔루션을 제공합니다.