
setup-python action for self-hosted runners running Amazon Linux
Intro
Back in 2022, setup-python Github’s officials have stated they won’t be supporting Amazon Linux 2 or Amazon Linux 2023 systems, at least no for a while. In this article we will create a snippet together to extend the os support of the official action, while having a unified solution to fit most cases (rule of thumb, there will always be an edge case!) without breaking backwards compatibility, using simple logic.
But first, what’s actions/setup-python?
setup-python is one of Github’s official actions, used widely in order to set up a GitHub Actions workflow with a specific version of python, it also includes cacheing capabilities for the popolar tooling.
What’s wrong with installing python on the self-hosted runner?
Even thought it is possible to install python on the host, and it is also ChatGPT best answer, it is an bad pattern. The reason for it is by having python as a pre-requisite for running your workflows.
It can also get significantly difficult when your need different versions for different workflows / applications, and wish to use the same runner.
Shoutout: 🌟 kishaningithub/setup-python-amazon-linux 🌟
Out of that issue a new actions have emerged, meant to complete the puzzle and provide a action the replicated the same principals as the original, just meant for amazon’s most common AMI’s, Amazon Linux 2 & Amazon Linux 2023.
# https://github.com/kishaningithub/setup-python-amazon-linux/blob/main/action.yml
name: 'Setup python amazon linux'
description: 'setup-python action for amazon linux self hosted runners'
inputs:
python-version:
description: 'Version of python to be installed. Reads from .python-version if unset.'
python-version-file:
description: 'Version of python to be installed'
default: '.python-version'
cache:
description: >
[Deprecated] Used to specify whether caching is needed. Set to true, if you'd like to enable caching.
Current implementation uses uv which pulls in pre-compiled binaries of python thereby eliminating the
need to cache compiled python artifacts.
default: 'true'
runs:
using: "composite"
steps:
- name: Ensure system dependencies are installed
shell: bash
run: |
sudo yum install -y tar gzip which
- name: Install uv
shell: bash
run: |
installation_directory="${{ github.action_path }}/.setup-python-amazon-linux/uv"
echo "Installing uv.. installation_directory=${installation_directory}"
uv_version="0.7.10"
# HOME is set to foobar till this is resolved https://github.com/astral-sh/uv/issues/6965#issuecomment-2915796022
curl -LsSf "https://github.com/astral-sh/uv/releases/download/${uv_version}/uv-installer.sh" | HOME="foobar" UV_UNMANAGED_INSTALL="${installation_directory}" bash --login
echo "${installation_directory}" >> "${GITHUB_PATH}"
- name: Find desired python version
id: find-desired-python-version
shell: bash
run: |
desired_python_version=$(${GITHUB_ACTION_PATH}/find-desired-python-version.sh "${{ inputs.python-version }}" "${{ inputs.python-version-file }}")
echo "desired_python_version=${desired_python_version}" | tee -a "${GITHUB_OUTPUT}"
- name: Set installation directory
id: set-installation-directory
shell: bash
run: |
desired_python_version="${{ steps.find-desired-python-version.outputs.desired_python_version }}"
echo "installation_directory=${HOME}/.setup-python-amazon-linux/.python-versions/${desired_python_version}" | tee -a "${GITHUB_OUTPUT}"
- id: setup-python
shell: bash
run: |
installation_directory="${{ steps.set-installation-directory.outputs.installation_directory }}"
desired_python_version="${{ steps.find-desired-python-version.outputs.desired_python_version }}"
uv venv --python "${desired_python_version}" "${installation_directory}"
- name: Add python to PATH
shell: bash
run: |
installation_directory="${{ steps.set-installation-directory.outputs.installation_directory }}"
echo "${installation_directory}/bin" >> "${GITHUB_PATH}"
echo "The following python binaries are now available in the PATH"
ls "${installation_directory}/bin"
- name: Install pip
shell: bash
run: |
installation_directory="${{ steps.set-installation-directory.outputs.installation_directory }}"
python -m ensurepip --upgrade
ln -sf "${installation_directory}/bin/pip3" "${installation_directory}/bin/pip"
pip install --upgrade pip
branding:
icon: 'code'
color: 'yellow'
What’s behind the scenes?
This actions uses the composite approach to leverage yum, which is the package manager for these AMIs, as well as UV, the new kid on the block for managing python packages and projects, and has already taken the hearts of many due to it being “blazingly fast”, and written in rust.
Once uv is installed, the desired python version is quickly calculated using plain bash, and initialized as a virtual environment (venv) in the runner’s action path, aka the path to where the actions is located. It then added this venv path to the GITHUB_PATH
which is the runners search path for executing binaries, to make our desired python versions (virtualized!) available for the rest of our job by simply invoking python
.
# https://github.com/actions/setup-python/blob/main/action.yml
---
name: "Setup Python"
description: "Set up a specific version of Python and add the command-line tools to the PATH."
author: "GitHub"
inputs:
python-version:
description: "Version range or exact version of Python or PyPy to use, using SemVer's version range syntax. Reads from .python-version if unset."
python-version-file:
description: "File containing the Python version to use. Example: .python-version"
cache:
description: "Used to specify a package manager for caching in the default directory. Supported values: pip, pipenv, poetry."
required: false
architecture:
description: "The target architecture (x86, x64, arm64) of the Python or PyPy interpreter."
check-latest:
description: "Set this option if you want the action to check for the latest available version that satisfies the version spec."
default: false
token:
description: "The token used to authenticate when fetching Python distributions from https://github.com/actions/python-versions. When running this action on github.com, the default value is sufficient. When running on GHES, you can pass a personal access token for github.com if you are experiencing rate limiting."
default: ${{ github.server_url == 'https://github.com' && github.token || '' }}
cache-dependency-path:
description: "Used to specify the path to dependency files. Supports wildcards or a list of file names for caching multiple dependencies."
update-environment:
description: "Set this option if you want the action to update environment variables."
default: true
allow-prereleases:
description: "When 'true', a version range passed to 'python-version' input will match prerelease versions if no GA versions are found. Only 'x.y' version range is supported for CPython."
default: false
freethreaded:
description: "When 'true', use the freethreaded version of Python."
default: false
pip-version:
description: "Used to specify the version of pip to install with the Python. Supported format: major[.minor][.patch]."
outputs:
python-version:
description: "The installed Python or PyPy version. Useful when given a version range as input."
cache-hit:
description: "A boolean value to indicate a cache entry was found"
python-path:
description: "The absolute path to the Python or PyPy executable."
runs:
using: 'node20'
main: 'dist/setup/index.js'
post: 'dist/cache-save/index.js'
post-if: success()
branding:
icon: 'code'
color: 'yellow'
Key differences from the official action
Other than the obvious os support, a few differences to notice when you consider using this action:
- Not as well-known and trusted
- Runs in composite mode, which does not support and
pre
/post
/post-if
event driven executions - Short & simple
- Easily readable, using plain bash
- Lack’s caching, among other useful features.
- Download python versions from a different source then the official.
Summary
I love it when the open-source community takes actions where the corp lacks, especially when the issues are relevant, and could potentielly benifit many people. I could already tell it would be a Large / XL size t-shirt to build Golden AMI’s with pyenv installed and shift workflows to new tooling so I was stocked to find this action, allowing me to focus on other tasks (clearing my backlog amongst them).
So even though it is not yet perfect (and you can help if be!), is get the job done, and most importantly, make our workflows independent, with no prerequisites or special tooling required.
BONUS
Here’s a short but useful snippet which allowed me to set-and-forget and standardize the way we setup python for our workflows:
steps:
- name: Pre cleanup
uses: zMynxx/github-actions/blob/main/.github/actions/shared/clean-workspace@main
with:
github-workspace: ${{ github.workspace }}
- name: Checkout
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
- name: Check OS
id: os_check
shell: bash
run: |
set -exu
cat /etc/os-release
source /etc/os-release
echo "os_id=$ID" >> $GITHUB_OUTPUT
- name: Setup Python on Ubuntu
if: steps.os_check.outputs.os_id == 'ubuntu'
uses: actions/setup-python@v4
with:
python-version: "3.13"
- name: Setup Python on Amazon Linux
if: steps.os_check.outputs.os_id == 'amzn' || steps.os_check.outputs.os_id == 'alinux2023'
uses: kishaningithub/setup-python-amazon-linux@v1
with:
python-version: "3.13"