Install from source using Ansible
TL;DR, All the code can be found here
Sometimes, when you want complete control, you want to be able to install packages from source and still use an automated tool like Ansible to do that.
A simple set of tasks can check for the existence of files to eliminate the need for running tasks that are already complete but that doesn’t help us with making sure we have the correct version installed.
I’m going to walk through creating a play that will build ruby from source. It will not do any work if ruby is already installed and is already the correct version. If not correct, it will:
- download the source tarball
- extract the source
- configure the install
- make the build
- install ruby
- cleanup the build directory
A first pass can be found in
this gist
If repeated, this build will re-download the archive, extract it, configure it and make it. It won’t install the binary again because it checks for the existence of the file
/usr/local/bin/ruby
but other than that, all tasks will re-run.
The first step is to create a task that will determine the installed ruby version if present.
- name: Get installed ruby version
command: ruby --version # Run this command
ignore_errors: true # We don’t want and error in this command to cause the task to fail
changed_when: false
failed_when: false
register: ruby_installed_version # Register a variable with the result of the command
This task will run ruby --version
but will silently fail if ruby is not installed. If ruby is installed, then it registers the version string in a variable named ruby_installed_version
.
The next step is to create a variable we can use to test whether to build ruby or not. This is set in our global_vars to a default of false. Then add a task that will set that variable to true if the version string doesn’t match.
- name: Force install if the version numbers do not match
set_fact:
ruby_reinstall_from_source: true
when: '(ruby_installed_version|success and (ruby_installed_version.stdout | regex_replace("^.*?([0-9\.]+).*$", "\\1") | version_compare(ruby_version, operator="!=")))'
Now we can add a when
clause to all our other tasks. This will skip the task if ruby is correctly installed. That can be seen in
this gist
The when clause checks for two things, (1) the task which checked the ruby version failed (i.e. there is no ruby installed) or (2) the
ruby_reinstall_from_source
variable is true (i.e. the versions don’t match).
An example task with the when clause:
- name: Download Ruby
when: ruby_installed_version|failed or ruby_reinstall_from_source
get_url:
url: "https://cache.ruby-lang.org/pub/ruby/2.3/ruby-{{ruby_version}}.tar.gz"
dest: "/tmp/ruby-{{ruby_version}}.tar.gz"
sha256sum: "{{ruby_sha256sum}}"
# …
We now have a conditional on every test. That seems a bit redundant. This can be improved by using the block syntax. By using a block we can check the condition once, and then run or skip the whole installation in one move.
- when: ruby_installed_version|failed or ruby_reinstall_from_source
block:
- name: Download Ruby
when: ruby_installed_version|failed or ruby_reinstall_from_source
get_url:
url: "https://cache.ruby-lang.org/pub/ruby/2.3/ruby-{{ruby_version}}.tar.gz"
dest: "/tmp/ruby-{{ruby_version}}.tar.gz"
sha256sum: "{{ruby_sha256sum}}"
# …
The final code can be found in this gist, https://gist.github.com/andrewtimberlake/802bd8d285b3e18c5ebe, where you can walk through the three revisions as outlined in the article.