Repository: rackerlabs/lambda-uploader

Issue:
Allow a virtualenv for another platform to be included
Instead of building a virtualenv with requirements.txt, allow an existing virtualenv to be used. This would allow us to build a virtualenv for Amazon Linux, and then still deploy lambda functions using lambda-uploader from client machines or other automation that may not be the same architecture as what Lambda runs on.

This is needed for me due to PyPi's cryptography package using different shared libraries on Ubuntu (my desktop) and Amazon Linux (where the function will run).

Discussion:

Patch:
diff --git a/README.md b/README.md
index 13e2baa..b3654f3 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,11 @@ To specify an alternative profile that has been defined in `~/.aws/credentials`
 lambda-uploader --profile=alternative-profile
 ```
 
+To specify an alternative, prexisting virtualenv use the `--virtualenv` parameter.
+```shell
+lambda-uploader --virtualenv=~/.virtualenv/my_custom_virtualenv
+```
+
 If you would prefer to upload another way you can tell the uploader to ignore the upload.
 This will create a package and leave it in the project directory.
 ```shell
diff --git a/README.rst b/README.rst
index ad7b199..db1a8b3 100644
--- a/README.rst
+++ b/README.rst
@@ -63,6 +63,12 @@ To specify an alternative profile that has been defined in
 
     lambda-uploader --profile=alternative-profile
 
+To specify an alternative, prexisting virtualenv use the ``--virtualenv`` parameter.
+
+.. code:: shell
+
+    lambda-uploader --virtualenv=~/.virtualenv/my_custom_virtualenv
+
 If you would prefer to upload another way you can tell the uploader to
 ignore the upload. This will create a package and leave it in the
 project directory.
diff --git a/lambda_uploader/package.py b/lambda_uploader/package.py
index 083077f..8bbbf54 100644
--- a/lambda_uploader/package.py
+++ b/lambda_uploader/package.py
@@ -26,28 +26,38 @@ TEMP_WORKSPACE_NAME = ".lamba_uploader_temp"
 ZIPFILE_NAME = 'lambda_function.zip'
 
 
-def build_package(path, requirements):
-    pkg = Package(path)
+def build_package(path, requirements, virtualenv=None):
+    pkg = Package(path, virtualenv)
 
     pkg.clean_workspace()
     pkg.clean_zipfile()
     pkg.prepare_workspace()
-    pkg.install_requirements(requirements)
+    if virtualenv:
+        if not os.path.isdir(virtualenv):
+            raise Exception("supplied virtualenv %s not found" % virtualenv)
+        LOG.info("Using existing virtualenv found in %s" % virtualenv)
+    else:
+        LOG.info('Building new virtualenv and installing requirements')
+        pkg.prepare_virtualenv()
+        pkg.install_requirements(requirements)
     pkg.package()
     return pkg
 
 
 class Package(object):
-    def __init__(self, path):
+    def __init__(self, path, virtualenv=None):
         self._path = path
         self._temp_workspace = os.path.join(path,
                                             TEMP_WORKSPACE_NAME)
         self.zip_file = os.path.join(path, ZIPFILE_NAME)
 
-        self._pkg_venv = os.path.join(self._temp_workspace, 'venv')
-        self._venv_pip = 'bin/pip'
-        if sys.platform == 'win32' or sys.platform == 'cygwin':
-            self._venv_pip = 'Scripts\pip.exe'
+        if virtualenv:
+            self._pkg_venv = virtualenv
+        else:
+            self._pkg_venv = os.path.join(self._temp_workspace, 'venv')
+            self._venv_pip = 'bin/pip'
+            if sys.platform == 'win32' or sys.platform == 'cygwin':
+                self._venv_pip = 'Scripts\pip.exe'
 
     def clean_workspace(self):
         if os.path.isdir(self._temp_workspace):
@@ -61,6 +71,7 @@ class Package(object):
         # Setup temporary workspace
         os.mkdir(self._temp_workspace)
 
+    def prepare_virtualenv(self):
         proc = Popen(["virtualenv", self._pkg_venv], stdout=PIPE, stderr=PIPE)
         stdout, stderr = proc.communicate()
         LOG.debug("Virtualenv stdout: %s" % stdout)
diff --git a/lambda_uploader/shell.py b/lambda_uploader/shell.py
index 027685f..5b57967 100644
--- a/lambda_uploader/shell.py
+++ b/lambda_uploader/shell.py
@@ -50,7 +50,7 @@ def _execute(args):
     cfg = config.Config(pth, args.config, role=args.role)
 
     _print('Building Package')
-    pkg = package.build_package(pth, cfg.requirements)
+    pkg = package.build_package(pth, cfg.requirements, args.virtualenv)
 
     if not args.no_clean:
         pkg.clean_workspace()
@@ -97,6 +97,9 @@ def main(arv=None):
                         action='store_const',
                         help='publish an upload to an immutable version',
                         const=True)
+    parser.add_argument('--virtualenv', '-e',
+                        help='use specified virtualenv instead of making one',
+                        default=None)
     parser.add_argument('--role', dest='role',
                         default=getenv('LAMBDA_UPLOADER_ROLE'),
                         help=('IAM role to assign the lambda function, '


Test patch:
diff --git a/test/test_package.py b/test/test_package.py
index 88cc9f6..1a64572 100644
--- a/test/test_package.py
+++ b/test/test_package.py
@@ -39,6 +39,7 @@ def test_prepare_workspace():
 
     pkg = package.Package(TESTING_TEMP_DIR)
     pkg.prepare_workspace()
+    pkg.prepare_virtualenv()
     assert path.isdir(temp_workspace)
     assert path.isdir(path.join(temp_workspace, 'venv'))
     if sys.platform == 'win32' or sys.platform == 'cygwin':
@@ -63,6 +64,11 @@ def test_install_requirements():
     assert path.isdir(path.join(site_packages, '_pytest'))
 
 
+def test_existing_virtualenv():
+    pkg = package.Package(TESTING_TEMP_DIR, 'abc')
+    assert pkg._pkg_venv == 'abc'
+
+
 def test_package():
     pkg = package.Package(TESTING_TEMP_DIR)
     pkg.package()