I got myself a bunch of Raspberry Pi 3s a short while ago:
...the idea being that I can use those to run a few experiments on. After the experiment, I would need to be able to somehow remove the stuff that I put on them again. The most obvious way to do that is to reimage them after every experiment; but the one downside of the multi-pi cases that I've put them in is that removing the micro-SD cards from the Pis without removing them from the case, while possible, is somewhat fiddly. That, plus the fact that I cared more about price than about speed when I got the micro-SD cards makes "image all the cards of these Raspberry Pis" something I don't want to do every day.
So I needed a different way to reset them to a clean state, and I decided to use ansible to get it done. It required a bit of experimentation, but eventually I came up with this:
---
- name: install package-list fact
hosts: all
remote_user: root
tasks:
- name: install libjson-perl
apt:
pkg: libjson-perl
state: latest
- name: create ansible directory
file:
path: /etc/ansible
state: directory
- name: create facts directory
file:
path: /etc/ansible/facts.d
state: directory
- name: copy fact into ansible facts directory
copy:
dest: /etc/ansible/facts.d/allowclean.fact
src: allowclean.fact
mode: 0755
- name: remove extra stuff
hosts: pis
remote_user: root
tasks:
- apt: pkg={{ item }} state=absent purge=yes
with_items: "{{ ansible_local.allowclean.cleanable_packages }}"
- name: remove package facts
hosts: pis
remote_user: root
tasks:
- file:
path: /etc/ansible/facts.d
state: absent
- apt:
pkg: libjson-perl
state: absent
autoremove: yes
purge: yes
This ansible playbook has three plays. The first play installs a custom
ansible fact (written in perl) that emits a json file which defines
cleanable_packages
as a list of packages to remove. The second uses
that fact to actually remove those packages; and the third removes the
fact (and the libjson-perl library we used to produce the JSON).
Which means, really, that all the hard work is done inside the
allowclean.fact
file. That looks like this:
#!/usr/bin/perl
use strict;
use warnings;
use JSON;
open PACKAGES, "dpkg -l|";
my @packages;
my %whitelist = (
...
);
while(<PACKAGES>) {
next unless /^ii\s+(\S+)/;
my $pack = $1;
next if(exists($whitelist{$pack}));
if($pack =~ /(.*):armhf/) {
$pack = $1;
}
push @packages, $1;
}
my %hash;
$hash{cleanable_packages} = \@packages;
print to_json(\%hash);
Basically, we create a whitelist, and compare the output of dpkg -l
against that whitelist. If a certain package is in the whitelist, then
we don't add it to our "packages" list; if it isn't, we do.
The whitelist is just a list of package => 1
tuples. We just need the
hash keys and don't care about the data, really; I could equally well
have made it package => undef
, I suppose, but whatever.
Creating the whitelist is not that hard. I did it by setting up one raspberry pi to the minimum that I wanted, running
dpkg -l|awk '/^ii/{print "\t\""$2"\" => 1,"}' > packagelist
and then adding the contents of that packagelist
file in place of the
...
in the above perl script.
Now whenever we apply the ansible playbook above, all packages (except for those in the whitelist) are removed from the system.
Doing the same for things like users and files is left as an exercise to the reader.
Side note: ...
is a valid perl construct. You can write it, and the
compiler won't complain. You can't run it, though.