Ansible Pattern: Downloading Artifacts

Often in Ansible I find myself needing to download an artifact. Whether it is a piece of software from GitHub or a build of some internal application that needs to be installed. One thing that is often important in these cases is speed of rollback. If something goes wrong with a new version I want the rollback to be fast, and preferable using preexisting files, and not downloading everything again.

Here is an example of an application that has binaries and needs to be extracted. It assumes that the version is already set.

---
- name: Calculate Settings
  set_fact:
    download_dir:  "{{ base_dir }}/downloads"
    download_url:  "https://examples.com/releases/v{{ app_version }}.tar.gz"
    artifact_file: "{{ base_dir }}/downloads/{{ app_version }}.tar.gz"
    latest_link:   "{{ base_dir }}/releases/latest"
    release_dir:   "{{ base_dir }}/releases/{{ app_version }}"
    binary_dir:    "/usr/local/bin"

Here we are simply calculating the variables we will use later. This makes each step simpler. I use pattern this often when there are lots of paths to deal with.

- name: Create Directories
  file:
    path: "{{ item }}"
    state: directory
  with_items:
    - "{{ download_dir }}"
    - "{{ release_dir }}"

Create the directories for the downloaded artifacts and releases. Ansible will create all the sub directories required on the way to these.

- name: Download the Artifact
  get_url:
    url: "{{ download_url }}"
    dest: "{{ artifact_file }}"

Here we do the actual download of the artifact. You might want to add owner, group and mode to here to choose the mode of the downloaded files.

- name: Extract Artifact to Release Directory
  unarchive:
    copy: no
    src: "{{ artifact_file }}"
    dest: "{{ release_dir }}"

Extract the artifact, because we downloaded it on the remote host we set copy to no to prevent Ansible trying to find the file locally and copy it to the managed host. You may want to add other options to the unachive module to set the permissions you want.

- name: Update the Link to the Latest Release
  file:
    src: "{{ release_dir }}"
    dest: "{{ latest_link }}"
    state: link

Here we atomically change the binaries that are in use. If anything before this step fails, we wont run this step, and once this step runs all the binaries change to the new version immediately. When rolling back to an existing version this is the only step that will need to run.

- name: Make the Link to the latest binaries
  file:
    src: "{{ latest_link }}/bin/{{ item }}"
    dest: "{{ binary_dir }}/{{ item }}"
    state: link
  with_items:
    - cmd1
    - cmd2

Finally we add links to the binaries. This will only be done on the first run as we use the latest link to create the symlinks.

I use this pattern in many different places, often with other steps in between. For example you might want to add steps to perform a compilation of the extracted tarball, or add files from the server into the release folder.