Install from source using Ansible

Mar 22, 2016
ansible, ruby, sysadmin, tech
Building an Ansbile play that will build a project (in this case Ruby) from source

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.

1
2
3
4
5
6
- 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.

1
2
3
4
- 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:

1
2
3
4
5
6
7
8
- 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.

1
2
3
4
5
6
7
8
9
10
- 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.

comments powered by Disqus