Automated and Tested Dotfile Deployment Using Ansible and Docker
March 8, 2018
shell dotfiles ansible travis dockerThis is the second part of my posts about Dotfile management. Part one can be found here.
After spending a lot of time and effort on your Dotfiles, it may be useful to setup an automated deployment process. There are existing solutions like GNU Stow, but for maximum flexibility the use of Ansible may be a better option. Using this, files and advanced configuration hierarchies can be distributed easily. This post covers my personal setup, which also includes an automated deployment test approach for multiple linux distributions.
Getting started
Structure
This step will guide you through the process of setting up the required folder hierarchy for the Ansible project. First of all, a folder called roles will be created at the project root to store all created Ansible roles. A role equips a host with the ability to perform required actions and to fulfill a specific ‘role’. Basic roles can be
dotfiles: Provide all Dotfiles, fonts, etc.general: Installs the most basic tools you will need on all hosts and Dotfile dependencies (This can be moved into thedotfilerole if desired)vim: Installsvim/neovimand all plugin dependencies.
Each role gets its on subfolder in the roles directory. In a specific role folder, all executed actions will be defined in the file tasks/main.yml, while additional task files can be included at runtime. For more info on this and additional actions, you can refer to the Ansible documentation.
After setting up roles, additional helper scripts and files are created to execute the deployment process, e.g.:
Makefileto start specific deployment strategiesdeploy.ymlto call the created roles in a modular mannerupdateDotfiles.ymlonly executesdotfilesrole to update the provided files and symlinks on a host
After creating this structure, it can look like this:
├── roles
│ ├── dotfiles
│ │ └── tasks
│ │ ├── main.yml
│ │ └── symlinks.yml
│ ├── general
│ │ ├── files
│ │ └── tasks
│ │ ├── arch.yml
│ │ ├── debian.yml
│ │ ├── main.yml
│ │ └── ubuntu.yml
│ └── vim
│ └── tasks
│ ├── arch.yml
│ ├── debian.yml
│ ├── main.yml
│ └── ubuntu.yml
├── .deployTest.yml
├── deploy.yml
├── Makefile
├── README.md
├── .travis.yml
└── updateDotfiles.yml
Role example
When using Ansible, it’s important to use the predefined modules in order to have idempotency for all actions. Unwanted side effects can occur if this isn’t taken care of. For example, cloning a Dotfiles repository can be as simple as:
- name: Clone Dotfiles repo
git:
repo: 'https://github.com/ps1337/Dotfiles.git'
dest: ~/.dotfiles
update: yes
recursive: yes
To create symlinks for specific files, the following structure can be of use:
- name: Create Symlinks for public files
file:
src: "{{ srcBase }}/.dotfiles/{{ item.src }}"
dest: "{{ dstBase }}/{{ item.dst }}"
state: link
force: yes
with_items:
- { src: "aliases", dst: ".aliases" }
- { src: "bash_profile", dst: ".bash_profile" }
- { src: "bashrc", dst: ".bashrc" }
- { src: "bindings", dst: ".bindings" }
- { src: "dockerfunc", dst: ".dockerfunc" }
- { src: "dunst", dst: ".config/dunst" }
- { src: "exports", dst: ".exports" }
Note that srcBase and dstBase are Ansible variables. These values can be overridden for each user. Using this, multiple users can share a locally cloned repository of Dotfiles.
Additional Tricks
-
Support for secret sub repositories can be implemented pretty easy using the
vars_promptandwhendirectives. -
The generic package manager
packagecan be used to install packages easily without having to specifyaptorpacman. -
Actions which are specific for a distribution can be included at runtime using:
- include_tasks: ubuntu.yml
when: ansible_distribution == "Ubuntu"`
Docker + Travis = Automated Testing
After setting up Travis CI for your Dotfile deployment repository, this .travis.yml file can be used to trigger the deployment tests:
services:
- docker
env:
- BUILD_CFG=arch
- BUILD_CFG=ubuntu
- BUILD_CFG=debian
script:
- |
if [ "$BUILD_CFG" == "ubuntu" ]; then
docker run \
--rm \
-v $PWD:/tmp/Dotfile-tools \
ubuntu:latest \
bash -c "apt update && \
apt -y install \
sudo \
make && \
cd /tmp/Dotfile-tools && \
make ansible && \
make test"
elif [ "$BUILD_CFG" == "debian" ]; then
docker run \
--rm \
-v $PWD:/tmp/Dotfile-tools \
debian:latest \
bash -c "apt update && \
apt -y install \
sudo \
make \
gnupg && \
cd /tmp/Dotfile-tools && \
make ansible && \
make test"
elif [ "$BUILD_CFG" == "arch" ]; then
docker run \
--rm \
-v $PWD:/tmp/Dotfile-tools \
alekzonder/archlinux-yaourt:latest \
bash -c "pacman --noconfirm -Syu \
sudo \
which \
gawk \
python \
make && \
cd /tmp/Dotfile-tools && \
sudo -u yaourt bash -c 'cd /tmp/Dotfile-tools && \
make ansible && make test'"
fi
after_success:
- // e.g. send notification using Telegram
after_failure:
- // e.g. send notification using Telegram
As you can see, it’s pretty easy to test the Dotfile provisioning on Arch, Ubuntu and Debian at the same time using Docker and Travis. For each value for BUILD_CFG, a separate deploying process will be started. In each spawned container, Ansible gets installed and all Ansible roles will be executed locally in the container to simulate deploying the Dotfiles and dependencies on a real host.
This can also be performed in intervals using cron jobs to ensure a working deployment suite.
As a bonus, the dependencies of all plugins, helper scripts and other fancy stuff you use will be documented within Ansible roles if done correctly.