#!/bin/bash # # pip_check_requirements_upgrades.sh # # Copyright (C) 2017, Branko Majic # # 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 . # program="pip_check_requirements_upgrades.sh" function usage() { cat <. EOF } function version() { cat < | | | | 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 . | +-----------------------------------------------------------------------+ 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 -e "${_text_bold}${_text_blue}[DEBUG]${_text_reset}" "$@" fi } function info() { echo -e "${_text_bold}${_text_white}[INFO] ${_text_reset}" "$@" } function success() { echo -e "${_text_bold}${_text_green}[OK] ${_text_reset}" "$@" } function warning() { echo -e "${_text_bold}${_text_yellow}[WARN] ${_text_reset}" "$@" } function error() { echo -e "${_text_bold}${_text_red}[ERROR]${_text_reset}" "$@" >&2 } # 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 # Verify 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 # 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 [[ $quiet == 0 ]] && info "Skipping input file with missing .txt file: $req_in" continue fi # Clean up the existing requirements file to contain only # package versioning information. current=$(sed -e 's/[[:blank:]]*#.*//' "$req_txt" | grep -v "^$" | sort -u) # Calculate up-to-date requirements. new=$(pip-compile --quiet --allow-unsafe --no-header --no-annotate --no-index --output-file - --upgrade "$req_in") result="$?" if [[ $result != 0 ]]; then error "Failed while running pip-compile command against (see error stack trace above): $req_in" continue fi # Clean up the new requirements to contain only package # versioning information. new=$(echo "$new" | sed -e 's/[[:blank:]]*#.*//' | grep -v "^$" | sort -u) debug "Current requirements:\n$(echo "$current")" debug "New requirements:\n$(echo "$new")" # Run diff, storing the output and result. # This is using Bash process substitution, which helps avoid # creating and keeping track of temporary files (since diff # must be fed with two input files). E.g. the <() will # essentially be path to a Bash-created/managed temporary # file. diff=$(diff -u <(echo "$current") <(echo "$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