Python is an open source, and widely used programming language known to be easy to learn and with powerful capabilities. There exists hundreds of libraries and frameworks created by the open source community that makes Python dynamic for adoption in any environment and varying use cases. Some of the popular python frameworks are Flask, Django, TensorFlow, PyTorch, Pandas, SciPy, among many others.

Overall, Python’s vast ecosystem make it a go-to programming language for a wide range of applications. You can use it as your favorite scripting language for Linux and other operating systems, and also to solve complex artificial intelligence and data analysis problems. To get the latest version of Python installed in your Debian or Ubuntu Linux, you’ll have to build from source. This article is created to automate the process of building and installing the latest release of Python from source using Ansible Playbook.

An Ansible playbook contains a set of instructions and tasks in YAML file. It is used to define and execute automations on a local host or remote server machines. A Playbook can be defined as the heart of Ansible configuration management since its execution results in the desired state of your system.

Before you can automate Python 3.x installation you’ll need Ansible installed. We have a number of articles that you can follow along to install Ansible.

$ ansible --version
ansible [core 2.16.0]
  config file = None
  configured module search path = ['/Users/jkmutai/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/Cellar/ansible/9.0.1/libexec/lib/python3.12/site-packages/ansible
  ansible collection location = /Users/jkmutai/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.12.0 (main, Oct  2 2023, 12:03:24) [Clang 15.0.0 (clang-1500.0.40.1)] (/usr/local/Cellar/ansible/9.0.1/libexec/bin/python)
  jinja version = 3.1.2
  libyaml = True

Create inventory file with the IP address of Ubuntu or Debian server where Python is to be installed.

$ vim hosts
49.13.163.29

Create a Playbook for installing Python 3.x

vim build_install_python.yml

Here are the playbook contents. Parameters to set are host IP, remote user, and Python release number.

---
- name: Install Python 3.x on Debian / Ubuntu
  hosts: 49.13.163.29
  remote_user: root
  become: yes
  become_method: sudo
  vars:
    python_release: 3.12.1 # See https://www.python.org/downloads/source/
    python_src_dir: /tmp
    python_archive: Python-{{ python_release }}.tgz
    python_url: "https://www.python.org/ftp/python/{{ python_release }}/{{ python_archive }}"
    packages:
      - git
      - gcc
      - make
      - tar
      - vim
      - wget
      - libncursesw5-dev
      - libssl-dev
      - libsqlite3-dev
      - tk-dev
      - libgdbm-dev
      - libc6-dev
      - libbz2-dev
      - libffi-dev
      - zlib1g-dev
      - build-essential
      - liblzma-dev
      - tcl-dev
  tasks:
    - name: Upgrade OS packages
      ansible.builtin.apt:
        name: "*"
        state: latest
        update_cache: yes

    - name: Check if a reboot is required
      ansible.builtin.stat:
        path: /var/run/reboot-required
        get_checksum: no
      register: reboot_required_file
    
    - name: Reboot the server (if required).
      ansible.builtin.reboot:
        reboot_timeout: 900
      when: reboot_required_file.stat.exists == true

    - name: Install dependency packages
      ansible.builtin.apt:
        name: "{{ item }}"
        state: latest
      loop: "{{ packages }}"

    - name: Download Python source archive
      ansible.builtin.get_url:
        dest: "{{ python_src_dir }}"
        url: "{{ python_url }}"

    - name: Extract archive
      ansible.builtin.unarchive:
        src: "{{ python_src_dir }}/{{ python_archive }}"
        dest: "{{ python_src_dir }}"
        remote_src: true

    - name: Run ./configure
      ansible.builtin.command:
        cmd: ./configure --enable-optimizations --prefix=/usr/local --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib"
        chdir: "{{ python_src_dir }}/Python-{{ python_release }}"
        creates: "{{ python_src_dir }}/Python-{{ python_release }}/config.log"

    - name: Run make to build python
      community.general.make:
        chdir: "{{ python_src_dir }}/Python-{{ python_release }}"
      register: make_result

    - name: Run make install
      community.general.make:
        chdir: "{{ python_src_dir }}/Python-{{ python_release }}"
        target: install
      register: install_result
      when: make_result is not skipped

Default Python build configuration commands executed by playbook are as shown below. You can adjust them to suit your use case.

./configure --enable-optimizations --prefix=/usr/local --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib"

Copy SSH key to the remote server if you are doing password authentication.

ssh-copy-id remote_user@ServerIP

Next execute the playbook.

# Using user credentials defined in playbook file.
ansible-playbook -i hosts  build_install_python.yml

# Using remote user called ubuntu with become password(sudo password)
 ansible-playbook -i hosts build_install_python.yml --become --become-user=ubuntu --ask-become-pass

Sample execution output.

$ ansible-playbook -i hosts  build_install_python.yml
PLAY [Install Python 3.x on Debian / Ubuntu] *********************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************
ok: [49.13.163.29]

TASK [Upgrade OS packages] ***************************************************************************************************************************************************************************
changed: [49.13.163.29]

TASK [Check if a reboot is required] *****************************************************************************************************************************************************************
ok: [49.13.163.29]

TASK [Reboot the server (if required).] **************************************************************************************************************************************************************
changed: [49.13.163.29]

TASK [Install dependency packages] *******************************************************************************************************************************************************************
ok: [49.13.163.29] => (item=git)
changed: [49.13.163.29] => (item=gcc)
changed: [49.13.163.29] => (item=make)
ok: [49.13.163.29] => (item=tar)
ok: [49.13.163.29] => (item=vim)
ok: [49.13.163.29] => (item=wget)
changed: [49.13.163.29] => (item=libncursesw5-dev)
changed: [49.13.163.29] => (item=libssl-dev)
changed: [49.13.163.29] => (item=libsqlite3-dev)
changed: [49.13.163.29] => (item=tk-dev)
changed: [49.13.163.29] => (item=libgdbm-dev)
ok: [49.13.163.29] => (item=libc6-dev)
changed: [49.13.163.29] => (item=libbz2-dev)
changed: [49.13.163.29] => (item=libffi-dev)
ok: [49.13.163.29] => (item=zlib1g-dev)
ok: [49.13.163.29] => (item=build-essential)
changed: [49.13.163.29] => (item=liblzma-dev)
ok: [49.13.163.29] => (item=tcl-dev)

TASK [Download Python source archive] ****************************************************************************************************************************************************************
changed: [49.13.163.29]

TASK [Extract archive] *******************************************************************************************************************************************************************************
changed: [49.13.163.29]

TASK [Run ./configure] *******************************************************************************************************************************************************************************
changed: [49.13.163.29]

TASK [Run make to build python] **********************************************************************************************************************************************************************
changed: [49.13.163.29]

TASK [Run make install] ******************************************************************************************************************************************************************************
changed: [49.13.163.29]

PLAY RECAP *******************************************************************************************************************************************************************************************
49.13.163.29               : ok=10   changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

After the playbook execution, login to the server and confirm Python installation. See examples below.

# Python 3.12
$ python3.12 -V
Python 3.12.1

# Python 3.11
$ python3.11 -V
Python 3.11.7

# Python 3.10
$ python3.10 -V
Python 3.10.13

Pip matching Python version is also installed.

$ pip3.12 -V
pip 23.2.1 from /usr/local/lib/python3.12/site-packages/pip (python 3.12)

Check the absolute path of both Python and Pip installed

$ which python3.12
/usr/local/bin/python3.12

$ which pip3.12
/usr/local/bin/pip3.12

You can run it using absolute bin path or just calling python<version> command.

$ /usr/local/bin/python3.12
Python 3.12.1 (main, Jan  7 2024, 19:29:29) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

#OR
$ python3.12
Python 3.12.1 (main, Jan  7 2024, 19:29:29) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Congratulations, you’ve just installed the latest release of Python by building it from source code and with the power of Ansible automation. We hope this article was helpful. You can check other Ansible guides available in our website.

LEAVE A REPLY

Please enter your comment!
Please enter your name here