Changeset - 76588ecee3af
[Not reviewed]
0 2 3
Branko Majic (branko) - 7 years ago 2017-04-18 21:38:50
branko@majic.rs
MAR-98: Added support to common role for checking upgrades in pip requirements files via pip-tools. This is done through a custom script, with interested roles needing to deploy the files under a specific location. A crontab will run during midnight performing the check.
5 files changed with 337 insertions and 0 deletions:
0 comments (0 inline, 0 general)
roles/common/defaults/main.yml
Show inline comments
 
@@ -12,6 +12,11 @@ prompt_id: null
 
extra_backup_patterns:
 
  - "/root"
 
  - "/home"
 
pip_check_requirements:
 
  - click==6.7
 
  - first==2.0.1
 
  - pip-tools==1.9.0
 
  - six==1.10.0
 

	
 
# Internal use only.
 
prompt_colour_mapping:
roles/common/files/pip_check_requirements_upgrades.sh
Show inline comments
 
new file 100755
 
#!/bin/bash
 
#
 
# pip_check_requirements_upgrades.sh
 
#
 
# Copyright (C) 2017, Branko Majic <branko@majic.rs>
 
#
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
program="pip_check_requirements_upgrades.sh"
 

	
 
function usage() {
 
    cat <<EOF
 
$program, a non-interactive utility for checking for available upgrades in
 
Python pip requirements files based on pip-tools
 

	
 
Usage: $program [OPTIONS] configuration_directory
 

	
 
$program a non-interactive utility for checking for available upgrades in Python
 
pip requirements files based on pip-tools. Utility is written specifically with
 
pip-tools in mind, and pip-tools must be available when running the utility.
 

	
 
If you are unfamiliar with pip-tools, please read its documentation first, and
 
make sure you fully understand how it works.
 

	
 
Utility accepts a single positionl argument - path to configuration
 
directory. Configuration directory should contain one or more sub-directories
 
where each sub-directory is treated as describing a separate Python virtual
 
environment. This allows the checks to be run for multiple virtual environments
 
in a modular manner.
 

	
 
Each sub-directory should contain one or more .in files with corresponding .txt
 
file. Base names must match (i.e. if you have production.in, the requirements
 
file must be called production.txt).
 

	
 
Utility iterates over each .in/.txt pair, calculates new requirements based on
 
the .in file, and diffs this against existing .txt file.
 

	
 
Utility creates copy of existing requirements file, stripping it from all
 
comments, then calculates new requirements file (storing result in temporary
 
location as well), and runs a diff between them.
 

	
 
If newer pacakges are available that would satisfiy the provided .in file, a
 
diff is shown on standard output in addition to a warning message.
 

	
 
$program accepts the following options:
 

	
 
    -V virtualenv
 
        Path to virtual environment with pre-installed pip-tools. If specified,
 
        the virtual environment will be activated prior to running the utility.
 

	
 
    -q
 
        Quiet mode. Output a message only if newer packages are available.
 
    -d
 
        Enable debug mode.
 
    -v
 
        Show script licensing information.
 
    -h
 
        Show usage help.
 

	
 

	
 
Please report bugs and send feature requests to <branko@majic.rs>.
 
EOF
 
}
 

	
 
function version() {
 
    cat <<EOF
 
$program
 

	
 
+-----------------------------------------------------------------------+
 
| Copyright (C) 2017, Branko Majic <branko@majic.rs>                    |
 
|                                                                       |
 
| This program is free software: you can redistribute it and/or modify  |
 
| it under the terms of the GNU General Public License as published by  |
 
| the Free Software Foundation, either version 3 of the License, or     |
 
| (at your option) any later version.                                   |
 
|                                                                       |
 
| This program is distributed in the hope that it will be useful,       |
 
| but WITHOUT ANY WARRANTY; without even the implied warranty of        |
 
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         |
 
| GNU General Public License for more details.                          |
 
|                                                                       |
 
| You should have received a copy of the GNU General Public License     |
 
| along with this program.  If not, see <http://www.gnu.org/licenses/>. |
 
+-----------------------------------------------------------------------+
 

	
 
EOF
 
}
 

	
 
# Set-up colours for message printing if we're not piping and terminal is
 
# capable of outputting the colors.
 
_color_terminal=$(tput colors 2>&1)
 
if [[ -t 1 ]] && (( ${_color_terminal} > 0 )); then
 
    _text_bold=$(tput bold)
 
    _text_white=$(tput setaf 7)
 
    _text_blue=$(tput setaf 6)
 
    _text_green=$(tput setaf 2)
 
    _text_yellow=$(tput setaf 3)
 
    _text_red=$(tput setaf 1)
 
    _text_reset=$(tput sgr0)
 
else
 
    _text_bold=""
 
    _text_white=""
 
    _text_blue=""
 
    _text_green=""
 
    _text_yellow=""
 
    _text_red=""
 
    _text_reset=""
 
fi
 

	
 
# Set-up functions for printing coloured messages.
 
function debug() {
 
    if [[ $debug != 0 ]]; then
 
        echo "${_text_bold}${_text_blue}[DEBUG]${_text_reset}" "$@"
 
    fi
 
}
 

	
 
function info() {
 
    echo "${_text_bold}${_text_white}[INFO] ${_text_reset}" "$@"
 
}
 

	
 
function success() {
 
    echo "${_text_bold}${_text_green}[OK]   ${_text_reset}" "$@"
 
}
 

	
 
function warning() {
 
    echo "${_text_bold}${_text_yellow}[WARN] ${_text_reset}" "$@"
 
}
 

	
 
function error() {
 
    echo "${_text_bold}${_text_red}[ERROR]${_text_reset}" "$@" >&2
 
}
 

	
 
# Clean-up command for temporary files.
 
function on_exit() {
 
    debug "Cleaning-up temporary file: $tmp_current"
 
    [[ -f $tmp_current ]] && rm "$tmp_current"
 

	
 
    debug "Cleaning-up temporayr file: $tmp_new"
 
    [[ -f $tmp_new ]] && rm "$tmp_new"
 
}
 
trap on_exit EXIT
 

	
 
# Define error codes.
 
SUCCESS=0
 
ERROR_ARGUMENTS=1
 
ERROR_CONFIG_DIR=2
 
ERROR_PIP_TOOLS_MISSING=3
 

	
 
# Disable debug and quiet modes by default.
 
debug=0
 
quiet=0
 

	
 
# Set-up defaults.
 
virtualenv=""
 

	
 
# If no arguments were given, just show usage help.
 
if [[ -z $1 ]]; then
 
    usage
 
    exit "$SUCCESS"
 
fi
 

	
 
# Parse the arguments
 
while getopts "V:qdvh" opt; do
 
    case "$opt" in
 
	V) virtualenv="$OPTARG";;
 
	q) quiet=1;;
 
	d) debug=1;;
 
        v) version
 
           exit "$SUCCESS";;
 
        h) usage
 
           exit "$SUCCESS";;
 
        *) usage
 
           exit "$ERROR_ARGUMENTS";;
 
    esac
 
done
 
i=$OPTIND
 
shift $(($i-1))
 

	
 

	
 
if [[ ${#@} == 0 ]]; then
 
    error "Must pass configuration directory."
 
    usage
 
    exit "$ERROR_ARGUMENTS"
 
fi
 

	
 
# Verfiy positional arguments.
 
config_dir="$1"
 
if [[ ! -d $config_dir ]]; then
 
    error "No such directory: $config_dir"
 
    exit "$ERROR_ARGUMENTS"
 
fi
 

	
 
# Make sure virtual environment exists.
 
if [[ -n $virtualenv && ! -f $virtualenv/bin/activate ]]; then
 
    error "Invalid virtual environment specified: $virtualenv"
 
    exit "$ERROR_ARGUMENTS"
 
fi
 

	
 
# Activate the virtual environment if it was specified.
 
[[ -n $virtualenv ]] && source "$virtualenv/bin/activate"
 

	
 
# Verify pip-compile is available.
 
if ! which pip-compile >/dev/null 2>&1; then
 
    error "Could not find command pip-compile from packagep pip-tools. Package might not be available in the virtual environment."
 
    exit "$ERROR_PIP_TOOLS_MISSING"
 
fi
 

	
 
# Create temporary files where files where "normalised" outputs will be
 
# stored.
 
tmp_current=$(mktemp)
 
tmp_new=$(mktemp)
 

	
 
# Process each environment.
 
for environment in "$config_dir"/*; do
 
    # Empty directory.
 
    if [[ ! -e $environment ]]; then
 
	error "Configuration directory is empty: $config_dir"
 
	exit "$ERROR_CONFIG_DIR"
 
    fi
 

	
 
    # Process each .in file.
 
    for req_in in "$environment"/*.in; do
 
	# Directory without .in files.
 
	if [[ ! -e $req_in ]]; then
 
	    error "No .in files in directory, skipping: $environment"
 
	    continue
 
	fi
 

	
 
	# Figure out if .txt file exists.
 
	req_txt="${req_in%.in}.txt"
 
	if [[ ! -f $req_txt ]]; then
 
	    warning "Missing .txt file for: $req_in"
 
	    continue
 
	fi
 

	
 
	# Deploy the existing requirements file and the new one.
 
	sed -e 's/[[:blank:]]*#.*//' "$req_txt" | grep -v "^$" | sort -u > "$tmp_current" 
 
	if ! pip-compile --no-header --no-annotate --no-index --output-file "$tmp_new" "$req_in" > /dev/null; then
 
	    error "Failed while running pip-compile command against (see error stack trace above): $req_in"
 
	    continue
 
	fi
 

	
 
	# Run diff, storing the output and result.
 
	diff=$(diff -u "$tmp_current" "$tmp_new")
 
	result="$?"
 

	
 
	# Show warning about available updates.
 
	if [[ $result == 0 ]]; then
 
	    [[ $quiet == 0 ]] && info "No upgrades available for: $req_txt"
 
	else
 
	    warning "Upgrades available for: $req_txt"
 
	    echo
 
	    echo "$diff"
 
	    echo
 
	fi
 
    done
 
done
roles/common/files/pipreqcheck_requirements.in
Show inline comments
 
new file 100644
 
pip-tools
 
\ No newline at end of file
roles/common/tasks/main.yml
Show inline comments
 
@@ -149,6 +149,65 @@
 
- name: Install apticron (for checking available upgrades)
 
  apt: name=apticron state=installed
 

	
 
# Implementation for checking pip requirements files via via pip-tools.
 
- name: Install virtualenv for pip requirements checks
 
  apt: name=virtualenv state=installed
 

	
 
- name: Create dedicated group for user running pip requirements checks
 
  group: name="pipreqcheck" gid="{{ pipreqcheck_gid | default(omit) }}" state=present
 

	
 
- name: Create user for running pip requirements checks
 
  user: name="pipreqcheck" uid="{{ pipreqcheck_uid | default(omit) }}" group="pipreqcheck"
 
        home="/var/lib/pipreqcheck" state=present
 

	
 
- name: Create directory for Python virtual environment used for installing/running pip-tools
 
  file: path="/var/lib/pipreqcheck/virtualenv" state=directory
 
        owner="pipreqcheck" group="pipreqcheck" mode="0750"
 

	
 
- name: Create Python virtual environment used for installing/running pip-tools
 
  become_user: "pipreqcheck"
 
  command: /usr/bin/virtualenv --prompt "(pipreqcheck)" "/var/lib/pipreqcheck/virtualenv" creates="/var/lib/pipreqcheck/virtualenv/bin/activate"
 

	
 
- name: Create directory for storing pip requirements files
 
  file: path="/etc/pip_check_requirements_upgrades" state="directory"
 
        owner="root" group="pipreqcheck" mode=750
 

	
 
- name: Set-up directory for storing pip requirements file for pip-tools virtual environment itself
 
  file: path="/etc/pip_check_requirements_upgrades/pipreqcheck" state="directory"
 
        owner="root" group="pipreqcheck" mode=750
 

	
 
- name: Deploy .in file for pip requirements in pip-tools virtual environment
 
  copy: src="pipreqcheck_requirements.in" dest="/etc/pip_check_requirements_upgrades/pipreqcheck/requirements.in"
 
        owner="root" group="pipreqcheck" mode=640
 

	
 
- name: Deploy requirements file for pipreqcheck virtual environment
 
  template: src="pipreqcheck_requirements.txt.j2" dest="/etc/pip_check_requirements_upgrades/pipreqcheck/requirements.txt"
 
            owner="root" group="pipreqcheck" mode=640
 

	
 
- name: Install latest pip in pip-tools virtual environment
 
  become_user: "pipreqcheck"
 
  pip: name=pip state=latest virtualenv="~pipreqcheck/virtualenv"
 

	
 
- name: Install pip-tools if not present
 
  become_user: "pipreqcheck"
 
  pip: name=pip-tools state=present virtualenv="~pipreqcheck/virtualenv"
 

	
 
- name: Synchronise pip-tools virtual environment via deployed requirements file
 
  become_user: "pipreqcheck"
 
  shell: "source ~pipreqcheck/virtualenv/bin/activate && pip-sync /etc/pip_check_requirements_upgrades/pipreqcheck/requirements.txt"
 
  args:
 
    executable: /bin/bash
 
  register: pipreqcheck_pip_sync
 
  changed_when: "pipreqcheck_pip_sync.stdout != 'Everything up-to-date'"
 

	
 
- name: Deploy script for checking available upgrades
 
  copy: src="pip_check_requirements_upgrades.sh" dest="/usr/local/bin/pip_check_requirements_upgrades.sh"
 
        owner=root group=root mode=755
 

	
 
- name: Deploy crontab entry for checking pip requirements
 
  cron: name="check_pip_requirements" cron_file="check_pip_requirements" hour=0 minute=0 job="/usr/local/bin/pip_check_requirements_upgrades.sh /etc/pip_check_requirements_upgrades"
 
        state=present user=pipreqcheck
 

	
 
- name: Explicitly run all handlers
 
  include: ../handlers/main.yml
 
  when: "handlers | default(False) | bool() == True"
roles/common/templates/pipreqcheck_requirements.txt.j2
Show inline comments
 
new file 100644
 
{% for requirement in pip_check_requirements %}
 
{{ requirement }}
 
{% endfor %}
0 comments (0 inline, 0 general)