From 9a76b8f9f03deb9628c437f6a81e1e67c2213538 Mon Sep 17 00:00:00 2001 From: Federico Ressi Date: Wed, 4 Dec 2019 10:44:49 +0100 Subject: [PATCH] Create Ansible role to install arbitrary Python version The purpose of this is to be able to install a version of python that is not available on target node. It downloads source code from Python site, compiles it using default compiler and install it. It also installs and upgrade python packages (pip, setuptools, etc.) and install additional ones required by user via Pip Change-Id: I3212d6fc1872451942aaa82e613236351f8592a3 --- .ansible-lint | 4 + roles/python/.gitignore | 1 + roles/python/Vagrantfile | 94 ++++++++++++++++++++++++ roles/python/ansible.cfg | 3 + roles/python/defaults/main.yaml | 31 ++++++++ roles/python/resolv_conf.yaml | 13 ++++ roles/python/tasks/main.yaml | 105 +++++++++++++++++++++++++++ roles/python/templates/profile.sh.j2 | 1 + roles/python/tox-py38.yaml | 42 +++++++++++ 9 files changed, 294 insertions(+) create mode 100644 .ansible-lint create mode 100644 roles/python/.gitignore create mode 100644 roles/python/Vagrantfile create mode 100644 roles/python/ansible.cfg create mode 100644 roles/python/defaults/main.yaml create mode 100644 roles/python/resolv_conf.yaml create mode 100644 roles/python/tasks/main.yaml create mode 100644 roles/python/templates/profile.sh.j2 create mode 100644 roles/python/tox-py38.yaml diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 000000000..96f8e876b --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,4 @@ +--- + +skip_list: + - '403' diff --git a/roles/python/.gitignore b/roles/python/.gitignore new file mode 100644 index 000000000..3827718ab --- /dev/null +++ b/roles/python/.gitignore @@ -0,0 +1 @@ +tox-py38 diff --git a/roles/python/Vagrantfile b/roles/python/Vagrantfile new file mode 100644 index 000000000..257cba349 --- /dev/null +++ b/roles/python/Vagrantfile @@ -0,0 +1,94 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +# Customize the count of CPU cores on the VM +CPUS = 4 + +# Customize the amount of memory on the VM +MEMORY = 8192 + +# Every Vagrant development environment requires a box. You can search for +# boxes at https://vagrantcloud.com/search. +BOX = "generic/centos7" + +HOSTNAME = "tobiko" + +TOX_INI_DIR = '../..' + + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + config.vm.box = BOX + config.vm.hostname = HOSTNAME + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # NOTE: This will enable public access to the opened port + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine and only allow access + # via 127.0.0.1 to disable public access + # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: DEVSTACK_HOST_IP + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network", ip: "172.18.161.6" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + + config.vm.provider "virtualbox" do |vb| + # Display the VirtualBox GUI when booting the machine + vb.gui = false + + vb.cpus = CPUS + vb.memory = MEMORY + end + + config.vm.provider "libvirt" do |libvirt| + libvirt.cpus = CPUS + libvirt.memory = MEMORY + end + + # No need to copy tox.ini folder to nodes to execute test cases + config.vm.synced_folder TOX_INI_DIR, "/vagrant", type: "rsync", + rsync__exclude: [".tox/"] + + config.vm.provision "ansible" do |ansible| + ansible.playbook = "resolv_conf.yaml" + end + + config.vm.provision "ansible" do |ansible| + ansible.playbook = "tox-py38.yaml" + end + +end diff --git a/roles/python/ansible.cfg b/roles/python/ansible.cfg new file mode 100644 index 000000000..e84f32523 --- /dev/null +++ b/roles/python/ansible.cfg @@ -0,0 +1,3 @@ +[default] +# human-readable stdout/stderr results display +stdout_callback = debug diff --git a/roles/python/defaults/main.yaml b/roles/python/defaults/main.yaml new file mode 100644 index 000000000..763f8382c --- /dev/null +++ b/roles/python/defaults/main.yaml @@ -0,0 +1,31 @@ +python_release: "3.8.0" +python_version: "3.8" +python_command: "python{{ python_version }}" +python_name: "Python-{{ python_release }}" +python_prefix: "/opt/{{ python_name }}" +python_executable: "{{ python_prefix }}/bin/{{ python_command }}" +python_url: "https://www.python.org/ftp/python/{{ python_release }}/{{ python_name }}.tgz" +python_tar: "{{ ansible_env.HOME }}/{{ python_url | basename }}" +python_src_dir: "{{ ansible_env.HOME }}/{{ python_name }}" + +pip_command: "pip{{ python_version }}" +pip_executable: "{{ python_prefix }}/bin/{{ pip_command }}" +pip_url: "https://bootstrap.pypa.io/get-pip.py" +pip_installer: "{{ ansible_env.HOME }}/{{ pip_url | basename }}" + +profile_file: "/etc/profile.d/{{ python_name }}.sh" +make_jobs: "{{ ansible_processor_vcpus }}" + +yum_install_packages: + - "@Development tools" + - zlib-devel + - openssl-devel + - bzip2-devel + - libffi-devel + +pip_install_base_packages: + - setuptools + - pip + - wheel + +pip_install_packages: [] diff --git a/roles/python/resolv_conf.yaml b/roles/python/resolv_conf.yaml new file mode 100644 index 000000000..c9925121c --- /dev/null +++ b/roles/python/resolv_conf.yaml @@ -0,0 +1,13 @@ +--- + +- hosts: all + tasks: + - name: Copy /etc/resolv.conf + become: yes + become_user: root + copy: + src: /etc/resolv.conf + dest: /etc/resolv.conf + owner: root + group: root + mode: '0644' diff --git a/roles/python/tasks/main.yaml b/roles/python/tasks/main.yaml new file mode 100644 index 000000000..a33466347 --- /dev/null +++ b/roles/python/tasks/main.yaml @@ -0,0 +1,105 @@ +--- + +- block: + - name: check '{{ python_executable }}' is installed + shell: | + '{{ python_executable }}' --version 2>&1 | \ + grep 'Python {{ python_version}}' + changed_when: false + + rescue: + - name: install '{{ python_name }}' build requeirements + become: yes + become_user: root + yum: + state: present + name: '{{ yum_install_packages }}' + when: "(yum_install_packages | length) > 0" + + - name: download '{{ python_name }}' from '{{ python_url }}' + get_url: + url: "{{ python_url }}" + dest: "{{ python_tar }}" + + - name: ensure '{{ python_src_dir | dirname }}' directory exists + file: + path: '{{ python_src_dir }}' + state: directory + + - name: extract '{{ python_tar | basename }}' into '{{ python_src_dir }}' + unarchive: + src: '{{ python_tar }}' + dest: '{{ python_src_dir | dirname }}' + remote_src: yes + + - name: configure '{{ python_name }}' + command: + cmd: './configure "--prefix={{ python_prefix }}"' + chdir: '{{ python_src_dir }}' + + - name: compile '{{ python_name }}' + command: + cmd: 'make -j {{ make_jobs }}' + chdir: '{{ python_src_dir }}' + + - name: install '{{ python_name }}' + become: yes + become_user: root + command: + cmd: 'make install' + chdir: '{{ python_src_dir }}' + + - name: check '{{ python_executable }}' is installed + shell: | + '{{ python_executable }}' --version 2>&1 | \ + grep 'Python {{ python_version}}' + changed_when: false + + +- block: + - name: check '{{ pip_executable }}' is installed + command: "'{{ pip_executable }}' --version" + changed_when: false + + rescue: + + + - name: download Pip installer from '{{ pip_url }}' + get_url: + url: "{{ pip_url }}" + dest: "{{ pip_installer }}" + + - name: "Install '{{ pip_executable }}'" + become: yes + become_user: root + command: "'{{ python_executable }}' '{{ pip_installer }}'" + + - name: check Pip is installed for '{{ pip_executable }}' + command: "'{{ pip_executable }}' --version" + changed_when: false + + +- name: "ensure base Python packages are installed and up-to-date" + become: true + become_user: root + pip: + executable: '{{ pip_executable }}' + state: latest + name: "{{ item }}" + vars: + ansible_python_interpreter: '{{ python_executable }}' + when: (item | length ) > 0 + loop: + - "{{ pip_install_base_packages }}" + - "{{ pip_install_packages }}" + + +- name: add '{{ profile_file }}' + become: yes + become_user: root + template: + src: profile.sh.j2 + dest: '{{ profile_file }}' + owner: root + group: root + mode: '0644' diff --git a/roles/python/templates/profile.sh.j2 b/roles/python/templates/profile.sh.j2 new file mode 100644 index 000000000..519137559 --- /dev/null +++ b/roles/python/templates/profile.sh.j2 @@ -0,0 +1 @@ +PATH={{ python_executable | dirname }}:$PATH diff --git a/roles/python/tox-py38.yaml b/roles/python/tox-py38.yaml new file mode 100644 index 000000000..e913ddb2c --- /dev/null +++ b/roles/python/tox-py38.yaml @@ -0,0 +1,42 @@ +--- + +- hosts: all + roles: + - role: . + vars: + python_version: "3.8" + python_release: "3.8.0" + pip_install_packages: + - virtualenv + - tox + + tasks: + - name: run test cases + shell: + cmd: tox -e py38 + chdir: /vagrant + register: run_test_cases + ignore_errors: true + + - name: produce test reports + shell: + cmd: tox -e report 2>&1 + chdir: /vagrant + + - name: get test reports + fetch: + src: "/vagrant/{{ item }}" + dest: "{{ playbook_dir }}/tox-py38/{{ inventory_hostname }}/{{ item }}" + flat: yes + loop: + - tobiko_results.html + + - name: check test cases result + assert: + that: run_test_cases.rc == 0 + fail_msg: | + Test cases failed + {{ run_test_cases.stdout }} + success_msg: | + Test cases passed + {{ run_test_cases.stdout }}