Repository: cloud-custodian/cloud-custodian

Issue:
library paths for Lambda packaging is hardcoded
Due to hard coded library paths, Lambda cannot be executed.

```
No module named yaml: ImportError Traceback (most recent call last): File "/var/task/custodian_policy.py", line 4, in run return handler.dispatch_event(event, context) File
...
File "/var/task/skew/config.py", line 18, in <module>
import yaml
ImportError: No module named yaml
```
via CloudWatch Logs.

When creating Lambda packages, `c7n/mu.py` only packages libraries under `BASEPATH/lib/python2.7/site-packages/` . 
https://github.com/capitalone/cloud-custodian/blob/master/c7n/mu.py#L114

But packages can be installed under different paths.
For example, on Amazon Linux, after creating virtualenv and using that pip, run `$ pip install XXX` will  install packages under `VENVPATH/lib64/python2.7/site-packages/`(Note `lib` and `lib64` difference.). That's why yaml libraries are not packaged. 

There should be more clever way to search for Python lib paths.
library paths for Lambda packaging is hardcoded
Due to hard coded library paths, Lambda cannot be executed.

```
No module named yaml: ImportError Traceback (most recent call last): File "/var/task/custodian_policy.py", line 4, in run return handler.dispatch_event(event, context) File
...
File "/var/task/skew/config.py", line 18, in <module>
import yaml
ImportError: No module named yaml
```
via CloudWatch Logs.

When creating Lambda packages, `c7n/mu.py` only packages libraries under `BASEPATH/lib/python2.7/site-packages/` . 
https://github.com/capitalone/cloud-custodian/blob/master/c7n/mu.py#L114

But packages can be installed under different paths.
For example, on Amazon Linux, after creating virtualenv and using that pip, run `$ pip install XXX` will  install packages under `VENVPATH/lib64/python2.7/site-packages/`(Note `lib` and `lib64` difference.). That's why yaml libraries are not packaged. 

There should be more clever way to search for Python lib paths.


Discussion:
applicable is sys.path os os.environ['PYTHONPATH'].split(":") in a loop around the same line.

fwiw this has worked on both rhel7.2 and ubuntu14.04 and 16.04, curious what the delta is on this environment besides amz linux, needs some a bit more investigation, there's some .pth files here that we may have to parse.

applicable is sys.path os os.environ['PYTHONPATH'].split(":") in a loop around the same line.

fwiw this has worked on both rhel7.2 and ubuntu14.04 and 16.04, curious what the delta is on this environment besides amz linux, needs some a bit more investigation, there's some .pth files here that we may have to parse.


Resolving patch:
diff --git a/c7n/mu.py b/c7n/mu.py
--- a/c7n/mu.py
+++ b/c7n/mu.py
@@ -19,7 +19,6 @@
 
 import abc
 import base64
-from datetime import datetime
 import inspect
 import fnmatch
 import hashlib
@@ -45,6 +44,10 @@
 
 
 log = logging.getLogger('custodian.lambda')
+RUNTIME = 'python{}.{}'.format(
+    sys.version_info.major,
+    sys.version_info.minor,
+)
 
 
 class PythonPackageArchive(object):
@@ -52,6 +55,8 @@ class PythonPackageArchive(object):
 
     Packages up a virtualenv and a source package directory per lambda's
     directory structure.
+
+    See http://docs.aws.amazon.com/lambda/latest/dg/with-s3-example-deployment-pkg.html#with-s3-example-deployment-pkg-python
     """
 
     def __init__(self, src_path, virtualenv_dir=None, skip=None,
@@ -111,18 +116,29 @@ def create(self):
                     self.add_file(f_path, dest_path)
 
         # Library Source
-        venv_lib_path = os.path.join(
-            self.virtualenv_dir, 'lib', 'python2.7', 'site-packages')
-
-        for root, dirs, files in os.walk(venv_lib_path):
-            if self.lib_filter:
-                dirs, files = self.lib_filter(root, dirs, files)
-            arc_prefix = os.path.relpath(root, venv_lib_path)
-            files = self.filter_files(files)
-            for f in files:
-                f_path = os.path.join(root, f)
-                dest_path = os.path.join(arc_prefix, f)
-                self.add_file(f_path, dest_path)
+        venv_lib_paths = (
+            os.path.join(
+                self.virtualenv_dir,
+                'lib', RUNTIME, 'site-packages',
+            ),
+            os.path.join(
+                self.virtualenv_dir,
+                'lib64', RUNTIME, 'site-packages',
+            ),
+        )
+
+        for venv_lib_path in venv_lib_paths:
+            if not os.path.exists(venv_lib_path):
+                continue
+            for root, dirs, files in os.walk(venv_lib_path):
+                if self.lib_filter:
+                    dirs, files = self.lib_filter(root, dirs, files)
+                arc_prefix = os.path.relpath(root, venv_lib_path)
+                files = self.filter_files(files)
+                for f in files:
+                    f_path = os.path.join(root, f)
+                    dest_path = os.path.join(arc_prefix, f)
+                    self.add_file(f_path, dest_path)
 
     def add_file(self, src, dest):
         info = zipfile.ZipInfo(dest)


Test patch:
diff --git a/tests/test_mu.py b/tests/test_mu.py
--- a/tests/test_mu.py
+++ b/tests/test_mu.py
@@ -20,7 +20,7 @@
 
 from c7n.mu import (
     custodian_archive, LambdaManager, PolicyLambda,
-    CloudWatchLogSubscription)
+    CloudWatchLogSubscription, RUNTIME)
 from c7n.policy import Policy
 from c7n.ufuncs import logsub
 from .common import BaseTest, Config, event_data
@@ -184,7 +184,7 @@ def test_cwe_trail(self):
              'FunctionName': 'custodian-s3-bucket-policy',
              'Handler': 'custodian_policy.run',
              'MemorySize': 512,
-             'Runtime': 'python2.7',
+             'Runtime': RUNTIME,
              'Timeout': 60})
         mgr.remove(pl)
 
@@ -227,7 +227,7 @@ def test_cwe_instance(self):
              'FunctionName': 'maid-ec2-encrypted-vol',
              'Handler': 'maid_policy.run',
              'MemorySize': 512,
-             'Runtime': 'python2.7',
+             'Runtime': RUNTIME,
              'Timeout': 60})
 
         events = session_factory().client('events')
@@ -262,7 +262,7 @@ def test_cwe_asg_instance(self):
             {'FunctionName': 'maid-asg-spin-detector',
              'Handler': 'maid_policy.run',
              'MemorySize': 512,
-             'Runtime': 'python2.7',
+             'Runtime': RUNTIME,
              'Timeout': 60})
 
         events = session_factory().client('events')
@@ -298,7 +298,7 @@ def test_cwe_schedule(self):
             {'FunctionName': 'maid-periodic-ec2-checker',
              'Handler': 'maid_policy.run',
              'MemorySize': 512,
-             'Runtime': 'python2.7',
+             'Runtime': RUNTIME,
              'Timeout': 60})
 
         events = session_factory().client('events')