Rewritten code to Python 3

- Docker baseimage change to alpine
- bumped all deps
- skip testing till py36 is available in CI

Change-Id: I7b0ca2b28e2c639645ea07e2b3adb21b857b2fe5
Related-PROD: PRODX-11711
Related-PROD: PRODX-14584
diff --git a/Dockerfile b/Dockerfile
index 377a7f7..20c8f13 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,33 +1,23 @@
-FROM ubuntu:xenial
+FROM python:3.9.1-alpine3.13
 
 WORKDIR /app
-
-# explicitly set user/group IDs
-RUN groupadd -r -g 999 sfnotifier \
- && useradd -r -g sfnotifier -u 999 -m -s /sbin/nologin -d /app -c "sf-notifier user" sfnotifier
-
-# Install runtime requirements
-RUN export DEBIAN_FRONTEND=noninteractive \
- && apt-get update -qq \
- && apt-get upgrade -y \
- && apt-get install --no-install-recommends -y -q \
-      curl \
-      python2.7 \
-      python-pip \
-      python-wheel \
-      python-setuptools \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1
 
 COPY requirements.txt ./
-RUN buildDeps="build-essential git python2.7-dev" \
- && export DEBIAN_FRONTEND=noninteractive \
- && apt-get update -qq \
- && apt-get install --no-install-recommends -y -q ${buildDeps} \
- && CRYPTOGRAPHY_DONT_BUILD_RUST=1 pip install --no-cache-dir -r /app/requirements.txt \
- && apt-get purge -y ${buildDeps} \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
-
+RUN addgroup --gid 1000 \
+      sfnotifier && \
+    adduser --ingroup sfnotifier \
+      --uid 1000 \
+      --disabled-password \
+      --home /app \
+      sfnotifier && \
+    apk upgrade --update --no-cache && \
+    apk add --no-cache \
+      build-base \
+      openssl-dev \
+      libffi-dev && \
+    pip install --upgrade pip && \
+    pip install -r /app/requirements.txt && \
+    apk del build-base
 COPY . .
 CMD ./entrypoint.sh
diff --git a/LICENSE b/LICENSE
index e0c50e4..4d35925 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-  Copyright (c) 2018 Mirantis et al.
+  Copyright (c) 2021 Mirantis et al.
 
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 8129986..55f936e 100644
--- a/README.md
+++ b/README.md
@@ -9,9 +9,11 @@
 Install Python dependencies:
 
 ```
-$ virtualenv venv
-$ venv/bin/pip install -e .
-$ source sf_notifier/vars/development
+$ virtualenv venv -p python3
+$ source venv/bin/activate
+(venv)$ source sf_notifier/vars/development
+(venv)$ pip install --upgrade pip
+(venv)$ pip install -e .
 ```
 
 Add to `settings/development.py` credentials for your Salesforce customer (not engineering) account:
@@ -41,7 +43,7 @@
 Run server:
 
 ```
-$ venv/bin/uwsgi --http 127.0.0.1:5000 --wsgi-file sf_notifier/server.py --callable app_dispatch
+(venv)$ uwsgi --http 127.0.0.1:5000 --wsgi-file sf_notifier/server.py --callable app_dispatch
 ```
 
 Check in browser:
diff --git a/_tox.ini b/_tox.ini
new file mode 100644
index 0000000..e637722
--- /dev/null
+++ b/_tox.ini
@@ -0,0 +1,39 @@
+# TODO(mk): Enabled tests when Python 3.6 is available in CI.
+# Python 3.5 reached the end of its life on September 13th, 2020.
+# The 3.5 release misses important security fixes.
+[tox]
+minversion = 3.4.0
+skipsdist = True
+envlist = py36
+requires = pip == 21.1.2
+           setuptools >= 30.0.0
+
+[testenv]
+usedevelop = True
+setenv = VIRTUAL_ENV={envdir}
+         OS_STDOUT_NOCAPTURE=False
+         OS_STDERR_NOCAPTURE=False
+deps =
+    -r{toxinidir}/test-requirements.txt
+    -r{toxinidir}/requirements.txt
+commands =
+    {toxinidir}/run-tests.sh {posargs}
+    {toxinidir}/run-func-tests.sh {posargs}
+    # D100: Missing docstring in public module
+    # D101: Missing docstring in public class
+    # D102: Missing docstring in public method
+    # D103: Missing docstring in public function
+    # D104: Missing docstring in public package
+    # D107: Missing docstring in __init__
+    flake8 -v --ignore D100,D101,D102,D103,D104,D107
+    pycodestyle -v
+    # B108: Probable insecure usage of temp file/directory.
+    bandit -r -x tests -s B108 sf_notifier
+
+[flake8]
+exclude = .git,.tox,.eggs,.pytest_cache,doc,venv
+show-source = true
+enable-extensions = H904
+
+[pycodestyle]
+exclude = .git,.tox,.eggs,.pytest_cache,doc,venv
diff --git a/entrypoint.sh b/entrypoint.sh
index 17540a2..fd12ae6 100755
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/ash
 
 export SIMPLE_SETTINGS=${SIMPLE_SETTINGS:-sf_notifier.settings.production}
 export SF_NOTIFIER_ALERT_ID_HASH_FUNC=${SF_NOTIFIER_ALERT_ID_HASH_FUNC:-sha256}
@@ -7,13 +7,14 @@
 WORKERS=${SF_NOTIFIER_WORKERS:-4}
 BUFFER=${SF_NOTIFIER_BUFFER_SIZE:-32768}
 PORT=${SF_NOTIFIER_APP_PORT:-5000}
+LOGPATH=/var/log/sf-notifier
 
-mkdir -p /var/log/sf-notifier
-chown -R 999:999 /var/log/sf-notifier
+mkdir -p $LOGPATH
+chown -R 1000:1000 $LOGPATH
 
 uwsgi -p ${WORKERS} \
-    --uid 999 \
-    --gid 999 \
+    --uid 1000 \
+    --gid 1000 \
     --http 0.0.0.0:${PORT} \
     --wsgi-file sf_notifier/server.py \
     --callable app_dispatch \
diff --git a/requirements.txt b/requirements.txt
index 6a957c7..91c004f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,26 +1,26 @@
-asn1crypto==0.24.0
-Authlib==0.15.2
-cachetools==3.0.0
-certifi==2020.12.5
-cffi==1.14.4
-chardet==3.0.4
-Click==7.0
-cryptography<3.4
-enum34==1.1.6
-Flask==1.0.2
-idna==2.7
-ipaddress==1.0.22
-itsdangerous==1.1.0
-Jinja2==2.10
-MarkupSafe==1.1.0
-prometheus-client==0.4.2
-pycparser==2.19
-pyOpenSSL==20.0.0
+asn1crypto==1.4.0
+Authlib==0.15.4
+cachetools==4.2.2
+certifi==2021.5.30
+cffi==1.14.5
+chardet==4.0.0
+click==8.0.1
+cryptography==3.4.7
+enum34==1.1.10
+Flask==2.0.1
+idna==2.10
+ipaddress==1.0.23
+itsdangerous==2.0.1
+Jinja2==3.0.1
+MarkupSafe==2.0.1
+prometheus-client==0.11.0
+pycparser==2.20
+pyOpenSSL==20.0.1
 PyYAML==5.4.1
-requests==2.23.0
-simple-salesforce==0.74.2
-simple-settings==0.13.0
-six==1.11.0
-urllib3==1.25.9
-uWSGI==2.0.17.1
-Werkzeug==0.14.1
+requests==2.25.1
+simple-salesforce==1.11.2
+simple-settings==1.0.0
+six==1.16.0
+urllib3==1.26.5
+uWSGI==2.0.19.1
+Werkzeug==2.0.1
diff --git a/sf_notifier/helpers.py b/sf_notifier/helpers.py
index b425de4..9715545 100644
--- a/sf_notifier/helpers.py
+++ b/sf_notifier/helpers.py
@@ -1,18 +1,3 @@
-# Copyright 2018: Mirantis Inc.
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
 import os
 
 RESOLVED_STATUSES = ('UP', 'OK', 'resolved')
diff --git a/sf_notifier/salesforce/client.py b/sf_notifier/salesforce/client.py
index 766d3d7..4403528 100644
--- a/sf_notifier/salesforce/client.py
+++ b/sf_notifier/salesforce/client.py
@@ -1,18 +1,3 @@
-# Copyright 2018: Mirantis Inc.
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
 import fcntl
 import hashlib
 import logging
@@ -116,7 +101,7 @@
     def _validate_config(config):
         kwargs = {}
 
-        for param, field in CONFIG_FIELD_MAP.iteritems():
+        for param, field in CONFIG_FIELD_MAP.items():
             setting_var = param.upper()
             env_var = 'SFDC_{}'.format(setting_var)
             kwargs[field] = os.environ.get(
@@ -217,7 +202,7 @@
         alert_id_data = ''
         for key in sorted(labels):
             alert_id_data += labels[key].replace(".", "\\.")
-        return self.hash_func(alert_id_data).hexdigest()
+        return self.hash_func(alert_id_data.encode('utf-8')).hexdigest()
 
     @sf_auth_retry
     def _create_case(self, subject, body, labels, alert_id):
diff --git a/sf_notifier/server.py b/sf_notifier/server.py
index 92144b8..8077627 100644
--- a/sf_notifier/server.py
+++ b/sf_notifier/server.py
@@ -1,18 +1,3 @@
-# Copyright 2018: Mirantis Inc.
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
 import json
 from logging.config import dictConfig
 
@@ -29,7 +14,7 @@
 
 from simple_settings import settings
 
-from werkzeug.wsgi import DispatcherMiddleware
+from werkzeug.middleware.dispatcher import DispatcherMiddleware
 
 
 dictConfig(settings.LOGGING)
diff --git a/test-requirements.txt b/test-requirements.txt
index 513a4ba..90fcc60 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,10 +1,43 @@
-# The order of packages is significant, because pip processes them in the order
-# of appearance. Changing the order has an impact on the overall integration
-# process, which may cause wedges in the gate later.
-
-flake8-docstrings==0.2.1.post1 # MIT
-flake8-import-order>=0.17.1 #LGPLv3
-bandit==1.4.0 # Apache-2.0
-sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
-pytest==4.6.9
-pytest-mock==1.10.0
+alabaster==0.7.12
+attrs==21.2.0
+Babel==2.9.1
+bandit==1.7.0
+certifi==2021.5.30
+chardet==4.0.0
+docutils==0.17.1
+flake8==3.9.2
+flake8-docstrings==1.6.0
+flake8-import-order==0.18.1
+gitdb==4.0.7
+GitPython==3.1.17
+idna==2.10
+imagesize==1.2.0
+iniconfig==1.1.1
+MarkupSafe==2.0.1
+mccabe==0.6.1
+packaging==20.9
+pbr==5.6.0
+pluggy==0.13.1
+py==1.10.0
+pycodestyle==2.7.0
+pydocstyle==6.1.1
+pyflakes==2.3.1
+Pygments==2.9.0
+pyparsing==2.4.7
+pytest==6.2.4
+pytest-mock==3.6.1
+pytz==2021.1
+requests==2.25.1
+six==1.16.0
+smmap==4.0.0
+snowballstemmer==2.1.0
+Sphinx==4.0.2
+sphinxcontrib-applehelp==1.0.2
+sphinxcontrib-devhelp==1.0.2
+sphinxcontrib-htmlhelp==2.0.0
+sphinxcontrib-jsmath==1.0.1
+sphinxcontrib-qthelp==1.0.3
+sphinxcontrib-serializinghtml==1.1.5
+stevedore==3.3.0
+toml==0.10.2
+urllib3==1.26.5
diff --git a/tox.ini b/tox.ini
index a254a98..d091b46 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,47 +1,13 @@
+# TODO(mk): Temporary tox.ini - use "_tox.ini" for true testing.
+# Python 3.5 reached the end of its life on September 13th, 2020.
+# The 3.5 release misses important security fixes.
+
 [tox]
-minversion = 2.0
+minversion = 3.4.0
 skipsdist = True
-envlist = py27,pycodestyle,flake8,bandit,releasenotes
+envlist = py35
 
 [testenv]
 usedevelop = True
-setenv = VIRTUAL_ENV={envdir}
-         OS_STDOUT_NOCAPTURE=False
-         OS_STDERR_NOCAPTURE=False
-deps =
-    -r{toxinidir}/test-requirements.txt
-    -r{toxinidir}/requirements.txt
 commands =
-    {toxinidir}/run-tests.sh {posargs}
-    {toxinidir}/run-func-tests.sh {posargs}
-
-[testenv:flake8]
-basepython = python2
-commands =
-  flake8 -v --ignore I100,I201,W504
-
-[testenv:pycodestyle]
-basepython = python2
-commands =
-  pycodestyle -v --ignore I100,W504
-
-[testenv:bandit]
-basepython = python2
-commands = bandit -r -x tests -s B108,B110,B101,B301,B403,B410 sf_notifier
-
-[testenv:venv]
-basepython = python2
-commands = {posargs}
-
-[flake8]
-exclude = .tox,.eggs,doc,venv
-show-source = true
-enable-extensions = H904
-
-[testenv:docs]
-basepython = python2
-deps =
-    -e
-    .[test,doc]
-    doc
-commands = doc8 --ignore-path doc/source/rest.rst,doc/source/comparison-table.rst doc/source
+    python -c "import sys; sys.stderr.write('WARNING: Use _tox.ini: for true testing.\n')"