Automating OS Go Binary Installation and Management with Ansible

3 months ago
3 min read

I use Ansible to manage my entire OS setup, with a playbook that installs all necessary software, sets up my dotfiles, and configures my system. This playbook can be run on a fresh installation of my OS and have my system set up exactly how I like it. Recently, I shifted from the Linux Arch AUR to go install for Go binaries, as it felt simpler and ensured up-to-date dependencies - without having to depend on the authors creating Arch AUR packaging for it. However, I still couldn’t find a way to pin packages to specific GitHub tags/releases. To solve this, I created a simple Ansible playbook to install Go binaries directly from GitHub releases, allowing me to pin and auto update the version of the Go binaries I want to install.

First, we need to fetch the release versions from GitHub and store them in a JSON file:

---
- name: Fetch latest Go release for packages
  hosts: localhost
  vars:
    go_packages:
      # jsonnet
      - github.com/google/go-jsonnet/cmd/jsonnet
      - github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb
      - github.com/google/go-jsonnet/cmd/jsonnet-lint
      - github.com/google/go-jsonnet/cmd/jsonnetfmt
      # configuration-management
      - github.com/grafana/tanka/cmd/tk
      - sigs.k8s.io/kustomize/kustomize/v5
      - github.com/hashicorp/terraform
      # ... Other packages

  tasks:
    - name: Fetch latest release from GitHub
      uri:
        url: "https://api.github.com/repos/{{ item.split('/')[1] }}/{{ item.split('/')[2] }}/releases/latest"
        method: GET
        return_content: true
        headers:
          Accept: "application/vnd.github.v3+json"
      register: github_release
      when: item.startswith('github.com')
      loop: "{{ go_packages }}"
      ignore_errors: true
      loop_control:
        label: "{{ item }}"

    - name: Initialize release_versions variable
      set_fact:
        release_versions: {}

    - name: Parse release versions and handle errors
      set_fact:
        release_versions: >-
          {{
            release_versions | combine({
              item.item: (
                item.content | from_json).tag_name if (
                'json' in item and 'tag_name' in (item.content | from_json) and item.status == 200
              ) else 'latest'
            })
          }}
      loop: "{{ github_release.results }}"
      loop_control:
        label: "{{ item.item }}"

    - name: Write versions to JSON file
      copy:
        content: "{{ release_versions | to_nice_json }}"
        dest: "~/dotfiles/ansible/deps/go-package-versions.json"

Note: if the Go dependency is not a GitHub repository, it will just set the version to latest.

This will output a JSON file with the latest release versions of the Go packages we want to install. Here's an example:

{
    "github.com/go-delve/delve/cmd/dlv": "v1.23.1",
    "github.com/google/go-jsonnet/cmd/jsonnet": "v0.20.0",
    "github.com/google/go-jsonnet/cmd/jsonnet-lint": "v0.20.0",
    "github.com/google/go-jsonnet/cmd/jsonnetfmt": "v0.20.0"
}

We can then use the JSON file to install the Go binaries:

---
- name: Install and upgrade Go packages from JSON file
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Read the Go packages JSON file
      slurp:
        src: ~/dotfiles/ansible/deps/go-package-versions.json
      register: go_packages_file

    - name: Set Go packages fact
      set_fact:
        go_packages: "{{ go_packages_file.content | b64decode | from_json }}"

    - name: Install or upgrade Go packages
      command: >
        go install {{ item.key }}@{{ item.value }}
      environment:
        GOPATH: "{{ ansible_env.HOME }}/go"
      with_dict: "{{ go_packages }}"
      when: item.value is defined
      loop_control:
        label: "{{ item.key }}"

    - name: Verify installations
      command: "{{ ansible_env.HOME }}/go/bin/{{ item.key.split('/')[-1] }} --version"
      register: go_pkg_versions
      failed_when: go_pkg_versions.rc != 0
      with_dict: "{{ go_packages }}"
      loop_control:
        label: "{{ item.key }}"

    - name: Display installed Go package versions
      debug:
        msg: "Installed {{ item.key }} version: {{ item.stdout }}"
      with_items: "{{ go_pkg_versions.results }}"

I hope this helps you manage your Go binaries with Ansible. I also manage all other pip, npm, ruby etc. with Ansible. The full examples of fetching and installing Go deps can be found here:


Similar Posts

Great Linux System Trays

2 min read

Finding quality system trays for Linux can be challenging due to limited documentation and scattered resources. However, there are several great system tray options available for Linux users. This blog post highlights some of the best and most useful system …


Using the Ducky One 2 Mini with Vim

2 min read

Due to the keyboard being 60% they've moved the arrow keys to I,J,K,L which seems logical, however if your using Vim this gets confusing as you are using H,J,K,L as arrow keys. The Up arrow key is K in Vim …