Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make application code Python 3 compliant #4239

Merged
merged 47 commits into from
Apr 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f99b211
Updates Dockerfile for Python3 environment
kushaldas Mar 6, 2019
5b7d08d
Uses py.test executable for tests
kushaldas Mar 6, 2019
5432a48
Uses Python3 to create dev config
kushaldas Mar 6, 2019
cbe3f25
Uses BytesIO for svg
kushaldas Mar 6, 2019
2490ba7
Uses Python3 interpreter
kushaldas Mar 6, 2019
88f3049
Returns str instead of bytes in Python3
kushaldas Mar 6, 2019
c00fdc7
Mass update from 2to3 tool of the source
kushaldas Mar 6, 2019
5a2e66f
Mass update of tests using 2to3 tool
kushaldas Mar 6, 2019
14bebc3
Fixes crypto_util tests for python3
kushaldas Mar 6, 2019
d4840a7
Fixes bytes vs strings for Python3
kushaldas Mar 21, 2019
e31e212
Fixes bytes vs strings for the i18n tool
kushaldas Mar 21, 2019
bfed0d1
Fixes rq on Python3
kushaldas Mar 21, 2019
c0a60a9
Fixes for bytes vs strings in Python3
kushaldas Mar 21, 2019
a7847d3
Uses binascii.Error as the actual error in exception
kushaldas Mar 21, 2019
7fbefcb
Fixes bytes vs strings for Python3
kushaldas Mar 21, 2019
0539017
Fixes encrypted reply file opening as text file
kushaldas Mar 21, 2019
64308a2
Fixes bytes vs strings for Python3
kushaldas Mar 21, 2019
3f07500
Python3 stdout takes care of the QR code
kushaldas Mar 21, 2019
fefd614
Fixes tests by mocking builtins.input function
kushaldas Mar 21, 2019
8c91695
Uses bytes on Python3
kushaldas Mar 21, 2019
d152ea7
Fixes bytes vs strings for Python3
kushaldas Mar 21, 2019
15e443f
Fixes import path for Python3
kushaldas Mar 21, 2019
2b50b0b
Fixes bytes vs strings for Python3
kushaldas Mar 21, 2019
7eb87be
Fixes bytes vs strings for Python3
kushaldas Mar 21, 2019
fc1dd67
dockerfiles: add python2 and python3 dockerfiles
redshiftzero Mar 22, 2019
ac92d2a
ci: add application test run on python 3 (xenial only)
redshiftzero Mar 22, 2019
5935641
dockerfile: move trusty dockerfile into python2 dir
redshiftzero Mar 22, 2019
945211d
dev: add python 2 / 3 functionality to dev env
redshiftzero Mar 22, 2019
8a8ca22
dev: add makefile target for running test suite under python 3
redshiftzero Mar 22, 2019
80b5a93
dev: remove ticker
redshiftzero Mar 23, 2019
bf5460d
python 2/3 compatibility: revert when python 3 is ready for prod
redshiftzero Apr 8, 2019
8e0aeb7
python 2/3 compatibility: update remainder of imports
redshiftzero Apr 8, 2019
41ef260
python 2/3 compatibility: fix test_api_error_handlers_defined
redshiftzero Apr 8, 2019
5335bb2
round of flake8 fixes
redshiftzero Apr 8, 2019
0af962c
python 2/3 compatibility: use input() from six
redshiftzero Apr 8, 2019
f3855ae
Uses six to work on both Python2 and 3
kushaldas Apr 9, 2019
a669aa4
Assert based on python2 or python3
kushaldas Apr 9, 2019
a9b75fd
Uses six for both Python2 and Python3
kushaldas Apr 9, 2019
08c6301
Uses six for both Python2 and Python3
kushaldas Apr 9, 2019
14f5cc7
Uses six for unicode in Python2 and Python3
kushaldas Apr 10, 2019
45b6351
Fixes code formatting for CI
kushaldas Apr 10, 2019
2030779
Updates code branching for Python2 vs Python3
kushaldas Apr 10, 2019
9e8b6b1
python 2/3 compatibility: use six.text_type where possible
redshiftzero Apr 12, 2019
f39195f
python 2/3 compatibility: use six.BytesIO in test_submit_file
redshiftzero Apr 12, 2019
3606e48
docs: explain how to use dev env and run tests on either py2 or 3
redshiftzero Apr 16, 2019
3d3e0d8
python 2/3 compatibility: cleaning up nits before merge
redshiftzero Apr 16, 2019
44e8d06
ci: fix app-test job, DRY up docker layer cache loading
redshiftzero Apr 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 78 additions & 23 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ common-steps:

- &restorecache
restore_cache:
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/xenial/Dockerfile" }}
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/xenial/python2/Dockerfile" }}
paths:
- /caches/layers.tar.gz

Expand All @@ -30,19 +30,19 @@ common-steps:
command: |
set +o pipefail
docker images
fromtag=$(docker images |grep securedrop-test-xenial |head -n1 |awk '{print $2}')
cd securedrop && DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-xenial:${fromtag:-latest}" ./bin/dev-shell true
fromtag=$(docker images |grep securedrop-test-xenial-py2 |head -n1 |awk '{print $2}')
cd securedrop && DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-xenial-py2:${fromtag:-latest}" ./bin/dev-shell true

- &saveimagelayers
run:
name: Save Docker image layer cache
command: |
docker images
docker save -o /caches/layers.tar securedrop-test-xenial:latest
docker save -o /caches/layers.tar securedrop-test-xenial-py2:latest

- &savecache
save_cache:
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/xenial/Dockerfile" }}
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/xenial/python2/Dockerfile" }}
paths:
- /caches/layers.tar

Expand All @@ -66,22 +66,14 @@ jobs:
steps:
- checkout
- *rebaseontarget

- run:
name: Ensure cache dir exists and permissions are good
command: |
sudo mkdir -p /caches && sudo chown circleci: -R /caches
- *createcachedir

- restore_cache:
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/trusty/Dockerfile" }}
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/trusty/python2/Dockerfile" }}
paths:
- /caches/layers.tar.gz

- run:
name: Load image layer cache
command: |
set +o pipefail
docker load -i /caches/layers.tar |true
- *loadimagelayers

- run:
name: Build Docker image
Expand All @@ -98,7 +90,7 @@ jobs:
docker save -o /caches/layers.tar securedrop-test-trusty:latest

- save_cache:
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/trusty/Dockerfile" }}
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/trusty/python2/Dockerfile" }}
paths:
- /caches/layers.tar

Expand Down Expand Up @@ -145,9 +137,66 @@ jobs:
name: Run tests
command: |
export TESTFILES=$(cd securedrop; circleci tests glob 'tests/test*py' 'tests/**/test*py' |circleci tests split --split-by=timings |xargs echo)
docker rm -f securedrop-test-xenial || true
fromtag=$(docker images |grep securedrop-test-xenial |head -n1 |awk '{print $2}')
cd securedrop && DOCKER_RUN_ARGUMENTS=$(bash <(curl -s https://codecov.io/env)) DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-xenial:${fromtag:-latest}" make test
docker rm -f securedrop-test-xenial-py2 || true
fromtag=$(docker images |grep securedrop-test-xenial-py2 |head -n1 |awk '{print $2}')
cd securedrop && DOCKER_RUN_ARGUMENTS=$(bash <(curl -s https://codecov.io/env)) DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-xenial-py2:${fromtag:-latest}" make test

- store_test_results:
path: ~/test-results

- store_artifacts:
path: ~/test-results

python3-app-tests:
machine:
enabled: true
environment:
DOCKER_API_VERSION: 1.23
BASE_OS: xenial
PYTHON_VERSION: 3
parallelism: 3
steps:
- checkout
- *rebaseontarget
- *createcachedir

- restore_cache:
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/xenial/python3/Dockerfile" }}
paths:
- /caches/layers.tar.gz

- *loadimagelayers

- run:
name: Build Docker images
command: |
set +o pipefail
docker images
fromtag=$(docker images |grep securedrop-test-xenial-py3 |head -n1 |awk '{print $2}')
cd securedrop && DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-xenial-py3:${fromtag:-latest}" ./bin/dev-shell true

- run:
name: Save Docker image layer cache
command: |
docker images
docker save -o /caches/layers.tar securedrop-test-xenial-py3:latest

- save_cache:
key: v1-sd-layers-{{ checksum "securedrop/dockerfiles/xenial/python3/Dockerfile" }}
paths:
- /caches/layers.tar

- run:
name: Make test results directory
command: mkdir -p ~/test-results

- run:
name: Run tests
command: |
export TESTFILES=$(cd securedrop; circleci tests glob 'tests/test*py' 'tests/**/test*py' |circleci tests split --split-by=timings |xargs echo)
docker rm -f securedrop-test-xenial-py3 || true
fromtag=$(docker images |grep securedrop-test-xenial-py3 |head -n1 |awk '{print $2}')
cd securedrop && DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-xenial-py3:${fromtag:-latest}" make test

- store_test_results:
path: ~/test-results
Expand Down Expand Up @@ -180,9 +229,9 @@ jobs:
name: Run tests
command: |
export TESTFILES=$(cd securedrop; circleci tests glob 'tests/pageslayout/test*py' |circleci tests split --split-by=timings |xargs echo)
docker rm -f securedrop-test-xenial || true
fromtag=$(docker images |grep securedrop-test-xenial |head -n1 |awk '{print $2}')
cd securedrop && DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-xenial:${fromtag:-latest}" make translation-test
docker rm -f securedrop-test-xenial-py2 || true
fromtag=$(docker images |grep securedrop-test-xenial-py2 |head -n1 |awk '{print $2}')
cd securedrop && DOCKER_BUILD_ARGUMENTS="--cache-from securedrop-test-xenial-py2:${fromtag:-latest}" make translation-test

- store_test_results:
path: ~/test-results
Expand Down Expand Up @@ -284,6 +333,12 @@ workflows:
ignore:
- /docs-.*/
- /i18n-.*/
- python3-app-tests:
filters:
branches:
ignore:
- /docs-.*/
- /i18n-.*/
- admin-tests:
filters:
branches:
Expand Down
10 changes: 10 additions & 0 deletions docs/development/setup_development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ To get started, you can try the following:
bin/dev-shell bin/run-test tests/functional # functional tests only
bin/dev-shell bash # shell inside the container

To specify the version of Python you want to use, set the ``PYTHON_VERSION``
environmental variable, like so:

.. code:: sh

PYTHON_VERSION=3 make test # Run tests on Python 3
PYTHON_VERSION=2 make test # Run tests on Python 2
PYTHON_VERSION=3 make dev # Run dev container on Python 3
PYTHON_VERSION=2 make dev # Run dev container on Python 2

.. tip:: The interactive shell in the container does not run
``redis``, ``Xvfb`` etc. However you can import shell helper
functions with ``source bin/dev-deps`` and call ``run_xvfb``,
Expand Down
4 changes: 2 additions & 2 deletions molecule/testinfra/staging/app/test_apparmor.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ def test_apparmor_total_profiles(host):
""" Ensure number of total profiles is sum of enforced and
complaining profiles """
with host.sudo():
total_expected = str((len(sdvars.apparmor_enforce)
+ len(sdvars.apparmor_complain)))
total_expected = str(len(sdvars.apparmor_enforce)
+ len(sdvars.apparmor_complain))
# Trusty has ~10, Xenial about ~20 profiles, so let's expect
# *at least* the sum.
assert host.check_output("aa-status --profiled") >= total_expected
Expand Down
6 changes: 5 additions & 1 deletion securedrop/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ test: ## Run the test suite in a Ubuntu 16.04 (Xenial) dockerized environment
test-trusty: ## Run the test suite in a Ubuntu 14.04 (Trusty) dockerized environment (to be removed April 30, 2019)
BASE_OS=trusty ./bin/dev-shell ./bin/run-test -v $${TESTFILES:-tests}

.PHONY: test-python3
test-python3: ## Run the Python 3 test suite in a Ubuntu 16.04 (Xenial) dockerized environment
PYTHON_VERSION=3 BASE_OS=xenial ./bin/dev-shell ./bin/run-test -v $${TESTFILES:-tests}

.PHONY: translation-test
translation-test: ## Run all pages-layout tests in all supported languages
./bin/dev-shell ./bin/translation-test $${TESTFILES:-tests/pageslayout}
Expand All @@ -43,7 +47,7 @@ test-config: ## Generate the test config
python -c 'import os; from jinja2 import Environment, FileSystemLoader; \
env = Environment(loader=FileSystemLoader(".")); \
ctx = {"securedrop_app_gpg_fingerprint": "65A1B5FF195B56353CC63DFFCC40EF1228271441"}; \
ctx.update(dict((k, {"stdout":v}) for k,v in os.environ.iteritems())); \
ctx.update(dict((k, {"stdout":v}) for k,v in os.environ.items())); \
ctx = open("config.py", "w").write(env.get_template("config.py.example").render(ctx))'
@echo >> config.py
@echo "SUPPORTED_LOCALES = ['ar', 'de_DE', 'es_ES', 'en_US', 'el', 'fr_FR', 'it_IT', 'nb_NO', 'nl', 'pt_BR', 'tr', 'zh_Hant']" >> config.py
Expand Down
51 changes: 34 additions & 17 deletions securedrop/bin/dev-shell
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
set -eu

TOPLEVEL=$(git rev-parse --show-toplevel)
source "${BASH_SOURCE%/*}/../../devops/scripts/ticker"

if ! test -n "${BASE_OS:-}" ; then
# If no base OS was specified, then we use Xenial
BASE_OS=xenial
fi

if ! test -n "${PYTHON_VERSION:-}" ; then
PYTHON_VERSION=2
fi

function exit_if_not_supported_base_image() {
# Currently we only support Xenial or Trusty.
if [[ "$1" != "xenial" && "$1" != "trusty" ]]
Expand All @@ -23,37 +26,51 @@ function exit_if_not_supported_base_image() {
fi
}

function validate_python_version() {
# Trusty will be EOL April 2019. Orgs must be on Xenial to upgrade to Python 3.
if [[ "$1" != "xenial" && "$2" != "2" ]]
then
echo "For Ubuntu Trusty, PYTHON_VERSION must be 2"
exit 1
fi

if [[ "$2" != "2" && "$2" != "3" ]]
then
echo "PYTHON_VERSION must be 2 or 3"
exit 1
fi
}

function docker_image() {
exit_if_not_supported_base_image $1
validate_python_version $1 $2

docker build \
${DOCKER_BUILD_ARGUMENTS:-} \
--build-arg=USER_ID="$(id -u)" \
--build-arg=USER_NAME="${USER:-root}" \
-t "securedrop-test-${1}" \
--file "${TOPLEVEL}/securedrop/dockerfiles/${1}/Dockerfile" \
--build-arg=USER_ID="$(id -u)" \
--build-arg=USER_NAME="${USER:-root}" \
-t "securedrop-test-${1}-py${2}" \
--file "${TOPLEVEL}/securedrop/dockerfiles/${1}/python${2}/Dockerfile" \
"${TOPLEVEL}/securedrop"
}

function docker_run() {
exit_if_not_supported_base_image $1
validate_python_version $1 $2

find . \( -name '*.pyc' -o -name __pycache__ \) -delete
docker run \
-p 127.0.0.1:5901:5901 \
--rm \
--rm \
--user "${USER:-root}" \
--volume "${TOPLEVEL}:${TOPLEVEL}" \
--workdir "${TOPLEVEL}/securedrop" \
-e NUM_SOURCES \
--user "${USER:-root}" \
--volume "${TOPLEVEL}:${TOPLEVEL}" \
--workdir "${TOPLEVEL}/securedrop" \
-e LC_ALL=C.UTF-8 \
-e LANG=C.UTF-8 \
--name securedrop-dev \
-ti ${DOCKER_RUN_ARGUMENTS:-} "securedrop-test-${1}" "${@:2}"
-ti ${DOCKER_RUN_ARGUMENTS:-} "securedrop-test-${1}-py${2}" "${@:3}"
}

if test -n "${CIRCLE_SHA1:-}" ; then
docker_image $BASE_OS
else
ticker docker_image $BASE_OS
fi

docker_run $BASE_OS "$@"
docker_image $BASE_OS $PYTHON_VERSION
docker_run $BASE_OS $PYTHON_VERSION "$@"
2 changes: 1 addition & 1 deletion securedrop/bin/run-test
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mkdir -p "/tmp/test-results/logs"
: "${PAGE_LAYOUT_LOCALES:=en_US,ar,fr_FR}"
export PAGE_LAYOUT_LOCALES

pytest \
py.test \
--page-layout \
--durations 10 \
--junitxml=/tmp/test-results/junit.xml \
Expand Down
7 changes: 3 additions & 4 deletions securedrop/create-dev-data.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,9 @@ def create_source_and_submissions(num_submissions=2, num_replies=2):

db.session.commit()

print(("Test source (codename: '{}', journalist designation '{}') "
"added with {} submissions and {} replies")
.format(codename, journalist_designation, num_submissions,
num_replies))
print("Test source (codename: '{}', journalist designation '{}') "
"added with {} submissions and {} replies").format(
codename, journalist_designation, num_submissions, num_replies)


if __name__ == "__main__": # pragma: no cover
Expand Down
9 changes: 7 additions & 2 deletions securedrop/crypto_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pretty_bad_protocol as gnupg
import os
import io
import six
import scrypt
import subprocess
from random import SystemRandom
Expand Down Expand Up @@ -162,7 +163,7 @@ def hash_codename(self, codename, salt=None):
salt = self.scrypt_id_pepper
return b32encode(scrypt.hash(clean(codename),
salt,
**self.scrypt_params))
**self.scrypt_params)).decode('utf-8')

def genkeypair(self, name, secret):
"""Generate a GPG key through batch file key generation. A source's
Expand Down Expand Up @@ -259,7 +260,11 @@ def decrypt(self, secret, ciphertext):
"""
hashed_codename = self.hash_codename(secret,
salt=self.scrypt_gpg_pepper)
return self.gpg.decrypt(ciphertext, passphrase=hashed_codename).data
data = self.gpg.decrypt(ciphertext, passphrase=hashed_codename).data

if not six.PY2: # Python3
return data.decode('utf-8')
return data


def clean(s, also=''):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ RUN apt-get update && \
RUN apt-get install -y devscripts vim \
python-pip libpython2.7-dev libssl-dev secure-delete \
gnupg2 ruby redis-server firefox git xvfb haveged curl \
gettext paxctl x11vnc enchant libffi-dev sqlite3 gettext sudo \
libgtk2.0
gettext paxctl x11vnc enchant libffi-dev sqlite3 gettext sudo

ENV FIREFOX_CHECKSUM=88d25053306d33658580973b063cd459a56e3596a3a298c1fb8ab1d52171d860
RUN curl -LO https://launchpad.net/~ubuntu-mozilla-security/+archive/ubuntu/ppa/+build/9727836/+files/firefox_46.0.1+build1-0ubuntu0.14.04.3_amd64.deb && \
Expand All @@ -27,7 +26,6 @@ RUN curl -LO https://launchpad.net/~ubuntu-mozilla-security/+archive/ubuntu/ppa/
RUN gem install sass -v 3.4.23

COPY requirements requirements

RUN pip install -r requirements/securedrop-app-code-requirements.txt && \
pip install -r requirements/test-requirements.txt

Expand Down
Loading