Automating OS Go Binary Installation and Management with Ansible

2 weeks ago New!
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

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 …