Add pypi packages downloading python script

Issue-ID: OOM-1803

Change-Id: I5470bad5ad862362976b56350c931929ee4b69d6
Signed-off-by: Milan Verespej <m.verespej@partner.samsung.com>
diff --git a/build/download/download-pip.sh b/build/download/download-pip.sh
deleted file mode 100755
index 7ab4b0c..0000000
--- a/build/download/download-pip.sh
+++ /dev/null
@@ -1,48 +0,0 @@
-#   COPYRIGHT NOTICE STARTS HERE
-#
-#   Copyright 2018 © Samsung Electronics Co., Ltd.
-#
-#   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.
-#
-#   COPYRIGHT NOTICE ENDS HERE
-
-
-# Load common-functions library
-. $(dirname ${0})/../common-functions.sh
-
-LIST_FILE="$1"
-if [[ -z "$LIST_FILE" ]]; then
-    echo "Missing list file"
-    exit 1
-fi
-LIST_FILE=$(readlink -f "$LIST_FILE")
-
-
-outdir="$2"
-if [[ -z "$outdir" ]]; then
-    echo "Missing output directory"
-    exit 1
-fi
-
-lines=$(clean_list "$LIST_FILE" | wc -l)
-cnt=1
-
-# create output dir if not exists
-mkdir -p "$outdir"
-
-cd "$outdir"
-for line in $(clean_list "$LIST_FILE"); do
-    echo "Downloading $cnt / $lines: $line"
-    pip download $line
-    cnt=$((cnt+1))
-done
diff --git a/build/download/download.py b/build/download/download.py
index ebce931..0d8912e 100755
--- a/build/download/download.py
+++ b/build/download/download.py
@@ -30,6 +30,7 @@
 import git_repos
 import http_files
 import npm_packages
+import pypi_packages
 import rpm_packages
 
 log = logging.getLogger(name=__name__)
@@ -54,6 +55,9 @@
     list_group.add_argument('--git', action='append', nargs=2, default=[],
                         metavar=('list', 'dir-name'),
                         help='git repo type list and directory to save downloaded files')
+    list_group.add_argument('--pypi', action='append', nargs=2, default=[],
+                        metavar=('list', 'dir-name'),
+                        help='pypi packages type list and directory to save downloaded files')
     parser.add_argument('--npm-registry', default='https://registry.npmjs.org',
                         help='npm registry to use (default: https://registry.npmjs.org)')
     parser.add_argument('--check', '-c', action='store_true', default=False,
@@ -63,7 +67,7 @@
 
     args = parser.parse_args()
 
-    for arg in ('docker', 'npm', 'http', 'rpm', 'git'):
+    for arg in ('docker', 'npm', 'http', 'rpm', 'git', 'pypi'):
         if getattr(args, arg):
             return args
 
@@ -143,6 +147,17 @@
         except RuntimeError:
             list_with_errors.append(git_list[0])
 
+    for pypi_list in args.pypi:
+        if args.check:
+            log.info('Check mode for pypi packages is not implemented')
+            break
+        progress = None if args.check else base.init_progress('pypi packages')
+        log.info('Processing {}.'.format(pypi_list[0]))
+        try:
+            pypi_packages.download(pypi_list[0], pypi_list[1], progress)
+        except RuntimeError:
+            list_with_errors.append(pypi_list[0])
+
     e_time = datetime.timedelta(seconds=timeit.default_timer() - timer_start)
     log.info(timeit.default_timer() - timer_start)
     log.info('Execution ended. Total elapsed time {}'.format(e_time))
diff --git a/build/download/pypi_packages.py b/build/download/pypi_packages.py
new file mode 100755
index 0000000..951003c
--- /dev/null
+++ b/build/download/pypi_packages.py
@@ -0,0 +1,88 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#   COPYRIGHT NOTICE STARTS HERE
+
+#   Copyright 2019 © Samsung Electronics Co., Ltd.
+#
+#   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.
+
+#   COPYRIGHT NOTICE ENDS HERE
+
+import argparse
+import logging
+import sys
+import subprocess
+import os
+from retrying import retry
+
+import base
+
+log = logging.getLogger(name=__name__)
+
+@retry(stop_max_attempt_number=5, wait_fixed=5000)
+def download_package(package_name, dst_dir):
+    command = 'pip download --dest {} {}'.format(dst_dir, package_name)
+    log.info('Running: {}'.format(command))
+    log.info(subprocess.check_output(command.split(), stderr=subprocess.STDOUT).decode())
+    log.info('Downloaded: {}'.format(package_name))
+
+
+def download(pypi_list, dst_dir, progress):
+    if not base.check_tool('pip'):
+        log.error('ERROR: pip is not installed')
+        progress.finish(dirty=True)
+        raise RuntimeError('pip missing')
+
+    pypi_set = base.load_list(pypi_list)
+
+    error_count = 0
+
+    base.start_progress(progress, len(pypi_set), [], log)
+
+    for package in pypi_set:
+        try:
+            download_package(package, dst_dir)
+        except subprocess.CalledProcessError as err:
+            log.exception(err.output.decode())
+            error_count += 1
+
+        progress.update(progress.value + 1)
+
+    base.finish_progress(progress, error_count, log)
+    if error_count > 0:
+        log.error('{} packages were not downloaded. Check logs for details'.format(error_count))
+        raise RuntimeError('Download unsuccesfull')
+
+
+def run_cli():
+    parser = argparse.ArgumentParser(description='Download git repositories from list')
+    parser.add_argument('pypi_list', metavar='pypi-list',
+                        help='File with list of pypi packages to download.')
+    parser.add_argument('--output-dir', '-o', default=os.getcwd(),
+                        help='Download destination')
+
+    args = parser.parse_args()
+
+    logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(message)s')
+
+    progress = base.init_progress('pypi packages')
+    try:
+        download(args.pypi_list, args.output_dir, progress)
+    except RuntimeError as err:
+        log.exception(err)
+        sys.exit(1)
+
+
+if __name__ == '__main__':
+    run_cli()
diff --git a/docs/BuildGuide.rst b/docs/BuildGuide.rst
index 043e429..dfeabb0 100644
--- a/docs/BuildGuide.rst
+++ b/docs/BuildGuide.rst
@@ -96,7 +96,13 @@
         # all resources will be stored in expected folder structure within ../resources folder
         # for more details refer to Appendix 1.
 
-        ./build/download/download.py --docker ./build/data_lists/infra_docker_images.list ../resources/offline_data/docker_images_infra --docker ./build/data_lists/rke_docker_images.list ../resources/offline_data/docker_images_for_nexus --docker ./build/data_lists/onap_docker_images.list ../resources/offline_data/docker_images_for_nexus --git ./build/data_lists/onap_git_repos.list ../resources/git-repo --npm ./build/data_lists/onap_npm.list ../resources/offline_data/npm_tar --rpm ./build/data_lists/onap_rpm.list ../resources/pkg/rhel
+        ./build/download/download.py --docker ./build/data_lists/infra_docker_images.list ../resources/offline_data/docker_images_infra \
+        --docker ./build/data_lists/rke_docker_images.list ../resources/offline_data/docker_images_for_nexus \
+        --docker ./build/data_lists/onap_docker_images.list ../resources/offline_data/docker_images_for_nexus \
+        --git ./build/data_lists/onap_git_repos.list ../resources/git-repo \
+        --npm ./build/data_lists/onap_npm.list ../resources/offline_data/npm_tar \
+        --rpm ./build/data_lists/onap_rpm.list ../resources/pkg/rhel \
+        --pypi ./build/data_lists/onap_pip_packages.list ../resources/offline_data/pypi
 
 
 Alternatively, step-by-step procedure is described in Appendix 1.
@@ -130,15 +136,6 @@
 
       createrepo ../resources/pkg/rhel
 
-**Step 6 - pip packages**
-
-Todo: will be incorporated into download.py in near future
-
-::
-
-      # Following step will download all pip packages
-      ./build/download/download-pip.sh ./build/data_lists/onap_pip_packages.list ../resources/offline_data/pypi
-
 
 This concludes SW download part required for ONAP offline platform creating.
 
@@ -268,7 +265,9 @@
         # This step will parse all 3 docker datalists (offline infrastructure images, rke k8s images & onap images)
         # and start building onap offline platform in /tmp/resources folder
 
-        ./build/download/download.py --docker ./build/data_lists/infra_docker_images.list ../resources/offline_data/docker_images_infra --docker ./build/data_lists/rke_docker_images.list ../resources/offline_data/docker_images_for_nexus --docker ./build/data_lists/onap_docker_images.list ../resources/offline_data/docker_images_for_nexus
+        ./build/download/download.py --docker ./build/data_lists/infra_docker_images.list ../resources/offline_data/docker_images_infra \
+        --docker ./build/data_lists/rke_docker_images.list ../resources/offline_data/docker_images_for_nexus \
+        --docker ./build/data_lists/onap_docker_images.list ../resources/offline_data/docker_images_for_nexus
 
 
 **Step 2 - building own dns image**
@@ -314,10 +313,8 @@
 
 **Step 8 - pip packages**
 
-Todo: new python script might be created for that part as well
-
 ::
 
       # Following step will download all pip packages
-      ./build/download/download-pip.sh ./build/data_lists/onap_pip_packages.list ../resources/offline_data/pypi
+      ./build/download/download.py --pypi ./build/data_lists/onap_pip_packages.list ../resources/offline_data/pypi