Any new technology requires a mental effort to understand. When trying to automate the boring stuff, one decision I have to make is whether to use straight shell scripting or whether to perform that operation using Ansible. What I want to do is look at a simple Ansible playbook I have written, and then compare what the comparable shell script would look like to determine if it would help my team to use Ansible or not in this situation.
The activity is building a Linux Kernel that comes from a series of topic branches applied on top of a specific upstream version. The majority of the work is done by a pre-existing shell script, so what we mostly need to do is git work.
Here is an annotated playbook. After each play, I note what it would take to do that operation in shell.
---
- name: Build a kernel out of supporting branches
hosts: servers
remote_user: root
vars:
#defined in an external vars file so we can move ahead
#kernel_version:
#soc_id:
test_dir: /root/testing
linux_dir: "{{ test_dir }}/linux"
tools_dir: "{{ test_dir }}/packaging"
tasks:
- name: ssh key forwarding for gitlab
ansible.builtin.copy:
src: files/ssh.config
dest: /root/.ssh/config
owner: root
group: root
mode: '0600'
backup: no
#scp $SRCDIR/files/ssh.config $SSH_USER@SSH_HOST:/root/.ssh/ssh.config
#ssh $SSH_USER@SSH_HOST chmod 600/root/.ssh
#ssh $SSH_USER@SSH_HOST chown root:root /root/.ssh/ssh.config
- name: create testing dir
ansible.builtin.file:
path: /root/testing
state: directory
#ssh $SSH_USER@SSH_HOST mkdir -p /root/testing
- name: Install Build Tools
ansible.builtin.yum:
name: make, gcc, git, dwarves, openssl, grubby, rpm-build, perl
state: latest
#ssh $SSH_USER@SSH_HOST yum -y install make, gcc, git, dwarves, openssl, grubby, rpm-build, perl
- name: git checkout Linux Kernel
ansible.builtin.git:
repo: git@gitlab.com:{{ GIT_REPO_URL }}/linux.git
dest: /root/testing/linux
version: v6.5
#This one is a bit more complex, as it needs to check if the repo already
#exists, and, if so, do a pull, otherwise do a clone.
- name: add stable stable Linux Kernel repo
ansible.builtin.command: git remote add stable https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
changed_when: false
args:
chdir: /root/testing/linux
ignore_errors: true
#there should be and Ansible git command for adding an additional remote.
#I could not find it, so I resported to command.
#This is identical to running via ssh
- name: fetch stable stable Linux Kernel repo
ansible.builtin.command: git fetch stable
args:
chdir: /root/testing/linux
#Same issue as above. This shows that, when an Ansible command is
#well crafted, it can link multiple steps into a single command, reduce the #need for an additional ssh-based command.
- name: git checkout gen-early-patches
ansible.builtin.git:
repo: git@gitlab.com:{{ GIT_REPO_URL }}/packaging.git
dest: "{{ tools_dir }}"
version: main
#same issue as with the clone for the Linux Kernel repository
- name: generate early kernel patches
ansible.builtin.shell:
cmd: "{{tools_dir }}/git-gen-early-patches.sh {{ tools_dir }}/gen-{{ soc_id }}-{{ kernel_version }}-general.conf"
chdir: /root/testing/linux
#One benefit to running with Ansible is that it will automatically
#wrap a shell call like this with an error check. This cuts dowm
#on boilerplate code and the potential to miss one.
- name: determine current patch subdir
ansible.builtin.find:
paths: /root/testing/linux
use_regex: true
#TODO build this pattern from the linux kernel version
pattern: ".*-v6.7.6-.*"
file_type: directory
register: patch_directory
#This would probably be easier to do in shell:
#BUILD_CMD=$( find . -name ".*-v6.7.6-.*/build.sh" | sort | tail -1 )
#
- ansible.builtin.debug:
msg: "{{ patch_directory.files | last }}"
- set_fact:
patch_dir: "{{ patch_directory.files | last }}"
- ansible.builtin.debug:
msg: "{{ patch_dir.path }}/build.sh"
- name: build kernel
ansible.builtin.shell:
cmd: "{{ patch_dir.path }}/build.sh"
chdir: /root/testing/linux
#Just execute the value of BUILD_CMD
#ssh $SSH_USER@SSH_HOST /root/testing/linux/$BUILD_CMD
So, should this one be in Ansible or shell? It is a close call. Ansible makes it hard to do shell things, and this needs a bunch of shell things. But Ansible is cleaner in doing stuff on a remote machine from a known starting machine, which is how this is run: I keep the Ansible playbook on my Laptop, connect via VPN, and run the playbook on a newly provisioned machine, or rerun it on a machine while we are in the process of updating the Kernel version, etc.
This use case does not make use of one of the primary things that Ansible is very good at doing: running the same thing on a bunch of machines at the same time. Still, it shows that Ansible is at least worth evaluating if you are running a workflow that spans two machines, and has to synchronize state between them. For most tasks, the Ansible play will be sufficient, and falling back to Shell is not difficult for most other tasks.