prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Certifi python module missing when deploying cloudtrail lambda functions
  The following error happens when the lambda function is triggered
  
  `[ERROR] Runtime.ImportModuleError: Unable to import module 'custodian_policy': No module named 'certifi'`
  
  As a workaround, I had to add the *certifi* package to the policy files.
  
  ```
  policies:
    - name: my-policy
      description: my description
      resource: my-resource
      mode:
        type: cloudtrail
        packages:
          - botocore
          - boto3
          - urllib3
          - certifi
  ```
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `c7n/actions/webhook.py`
  Content:
  ```
  1 # Copyright 2019 Microsoft Corporation
  2 #
  3 # Licensed under the Apache License, Version 2.0 (the "License");
  4 # you may not use this file except in compliance with the License.
  5 # You may obtain a copy of the License at
  6 #
  7 # http://www.apache.org/licenses/LICENSE-2.0
  8 #
  9 # Unless required by applicable law or agreed to in writing, software
  10 # distributed under the License is distributed on an "AS IS" BASIS,
  11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12 # See the License for the specific language governing permissions and
  13 # limitations under the License.
  14 
  15 import json
  16 
  17 import certifi
  18 import jmespath
  19 import urllib3
  20 from six.moves.urllib import parse
  21 
  22 from c7n import utils
  23 from .core import EventAction
  24 
  25 
  26 class Webhook(EventAction):
  27     """Calls a webhook with optional parameters and body
  28        populated from JMESPath queries.
  29 
  30         .. code-block:: yaml
  31 
  32           policies:
  33             - name: call-webhook
  34               resource: ec2
  35               description: |
  36                 Call webhook with list of resource groups
  37               actions:
  38                - type: webhook
  39                  url: http://foo.com
  40                  query-params:
  41                     resource_name: resource.name
  42                     policy_name: policy.name
  43     """
  44 
  45     schema_alias = True
  46     schema = utils.type_schema(
  47         'webhook',
  48         required=['url'],
  49         **{
  50             'url': {'type': 'string'},
  51             'body': {'type': 'string'},
  52             'batch': {'type': 'boolean'},
  53             'batch-size': {'type': 'number'},
  54             'method': {'type': 'string', 'enum': ['PUT', 'POST', 'GET', 'PATCH', 'DELETE']},
  55             'query-params': {
  56                 "type": "object",
  57                 "additionalProperties": {
  58                     "type": "string",
  59                     "description": "query string values"
  60                 }
  61             },
  62             'headers': {
  63                 "type": "object",
  64                 "additionalProperties": {
  65                     "type": "string",
  66                     "description": "header values"
  67                 }
  68             }
  69         }
  70     )
  71 
  72     def __init__(self, data=None, manager=None, log_dir=None):
  73         super(Webhook, self).__init__(data, manager, log_dir)
  74         self.http = None
  75         self.url = self.data.get('url')
  76         self.body = self.data.get('body')
  77         self.batch = self.data.get('batch', False)
  78         self.batch_size = self.data.get('batch-size', 500)
  79         self.query_params = self.data.get('query-params', {})
  80         self.headers = self.data.get('headers', {})
  81         self.method = self.data.get('method', 'POST')
  82         self.lookup_data = {
  83             'account_id': self.manager.config.account_id,
  84             'region': self.manager.config.region,
  85             'execution_id': self.manager.ctx.execution_id,
  86             'execution_start': self.manager.ctx.start_time,
  87             'policy': self.manager.data
  88         }
  89 
  90     def process(self, resources, event=None):
  91         self.http = urllib3.PoolManager(
  92             cert_reqs='CERT_REQUIRED',
  93             ca_certs=certifi.where())
  94 
  95         if self.batch:
  96             for chunk in utils.chunks(resources, self.batch_size):
  97                 resource_data = self.lookup_data
  98                 resource_data['resources'] = chunk
  99                 self._process_call(resource_data)
  100         else:
  101             for r in resources:
  102                 resource_data = self.lookup_data
  103                 resource_data['resource'] = r
  104                 self._process_call(resource_data)
  105 
  106     def _process_call(self, resource):
  107         prepared_url = self._build_url(resource)
  108         prepared_body = self._build_body(resource)
  109         prepared_headers = self._build_headers(resource)
  110 
  111         if prepared_body:
  112             prepared_headers['Content-Type'] = 'application/json'
  113 
  114         try:
  115             res = self.http.request(
  116                 method=self.method,
  117                 url=prepared_url,
  118                 body=prepared_body,
  119                 headers=prepared_headers)
  120 
  121             self.log.info("%s got response %s with URL %s" %
  122                           (self.method, res.status, prepared_url))
  123         except urllib3.exceptions.HTTPError as e:
  124             self.log.error("Error calling %s. Code: %s" % (prepared_url, e.reason))
  125 
  126     def _build_headers(self, resource):
  127         return {k: jmespath.search(v, resource) for k, v in self.headers.items()}
  128 
  129     def _build_url(self, resource):
  130         """
  131         Compose URL with query string parameters.
  132 
  133         Will not lose existing static parameters in the URL string
  134         but does not support 'duplicate' parameter entries
  135         """
  136 
  137         if not self.query_params:
  138             return self.url
  139 
  140         evaluated_params = {k: jmespath.search(v, resource) for k, v in self.query_params.items()}
  141 
  142         url_parts = list(parse.urlparse(self.url))
  143         query = dict(parse.parse_qsl(url_parts[4]))
  144         query.update(evaluated_params)
  145         url_parts[4] = parse.urlencode(query)
  146 
  147         return parse.urlunparse(url_parts)
  148 
  149     def _build_body(self, resource):
  150         """Create a JSON body and dump it to encoded bytes."""
  151 
  152         if not self.body:
  153             return None
  154 
  155         return json.dumps(jmespath.search(self.body, resource)).encode('utf-8')
  156
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/c7n/actions/webhook.py b/c7n/actions/webhook.py
  --- a/c7n/actions/webhook.py
  +++ b/c7n/actions/webhook.py
  @@ -14,7 +14,11 @@
   
   import json
   
  -import certifi
  +try:
  +    import certifi
  +except ImportError:
  +    certifi = None
  +
   import jmespath
   import urllib3
   from six.moves.urllib import parse
  @@ -90,7 +94,7 @@
       def process(self, resources, event=None):
           self.http = urllib3.PoolManager(
               cert_reqs='CERT_REQUIRED',
  -            ca_certs=certifi.where())
  +            ca_certs=certifi and certifi.where() or None)
   
           if self.batch:
               for chunk in utils.chunks(resources, self.batch_size):
  
verification_info: {"golden_diff": "diff --git a/c7n/actions/webhook.py b/c7n/actions/webhook.py\n--- a/c7n/actions/webhook.py\n+++ b/c7n/actions/webhook.py\n@@ -14,7 +14,11 @@\n \n import json\n \n-import certifi\n+try:\n+    import certifi\n+except ImportError:\n+    certifi = None\n+\n import jmespath\n import urllib3\n from six.moves.urllib import parse\n@@ -90,7 +94,7 @@\n     def process(self, resources, event=None):\n         self.http = urllib3.PoolManager(\n             cert_reqs='CERT_REQUIRED',\n-            ca_certs=certifi.where())\n+            ca_certs=certifi and certifi.where() or None)\n \n         if self.batch:\n             for chunk in utils.chunks(resources, self.batch_size):\n", "issue": "Certifi python module missing when deploying cloudtrail lambda functions\nThe following error happens when the lambda function is triggered\r\n\r\n`[ERROR] Runtime.ImportModuleError: Unable to import module 'custodian_policy': No module named 'certifi'`\r\n\r\nAs a workaround, I had to add the *certifi* package to the policy files.\r\n\r\n```\r\npolicies:\r\n  - name: my-policy\r\n    description: my description\r\n    resource: my-resource\r\n    mode:\r\n      type: cloudtrail\r\n      packages:\r\n        - botocore\r\n        - boto3\r\n        - urllib3\r\n        - certifi\r\n```\n", "before_files": [{"content": "# Copyright 2019 Microsoft Corporation\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport certifi\nimport jmespath\nimport urllib3\nfrom six.moves.urllib import parse\n\nfrom c7n import utils\nfrom .core import EventAction\n\n\nclass Webhook(EventAction):\n    \"\"\"Calls a webhook with optional parameters and body\n       populated from JMESPath queries.\n\n        .. code-block:: yaml\n\n          policies:\n            - name: call-webhook\n              resource: ec2\n              description: |\n                Call webhook with list of resource groups\n              actions:\n               - type: webhook\n                 url: http://foo.com\n                 query-params:\n                    resource_name: resource.name\n                    policy_name: policy.name\n    \"\"\"\n\n    schema_alias = True\n    schema = utils.type_schema(\n        'webhook',\n        required=['url'],\n        **{\n            'url': {'type': 'string'},\n            'body': {'type': 'string'},\n            'batch': {'type': 'boolean'},\n            'batch-size': {'type': 'number'},\n            'method': {'type': 'string', 'enum': ['PUT', 'POST', 'GET', 'PATCH', 'DELETE']},\n            'query-params': {\n                \"type\": \"object\",\n                \"additionalProperties\": {\n                    \"type\": \"string\",\n                    \"description\": \"query string values\"\n                }\n            },\n            'headers': {\n                \"type\": \"object\",\n                \"additionalProperties\": {\n                    \"type\": \"string\",\n                    \"description\": \"header values\"\n                }\n            }\n        }\n    )\n\n    def __init__(self, data=None, manager=None, log_dir=None):\n        super(Webhook, self).__init__(data, manager, log_dir)\n        self.http = None\n        self.url = self.data.get('url')\n        self.body = self.data.get('body')\n        self.batch = self.data.get('batch', False)\n        self.batch_size = self.data.get('batch-size', 500)\n        self.query_params = self.data.get('query-params', {})\n        self.headers = self.data.get('headers', {})\n        self.method = self.data.get('method', 'POST')\n        self.lookup_data = {\n            'account_id': self.manager.config.account_id,\n            'region': self.manager.config.region,\n            'execution_id': self.manager.ctx.execution_id,\n            'execution_start': self.manager.ctx.start_time,\n            'policy': self.manager.data\n        }\n\n    def process(self, resources, event=None):\n        self.http = urllib3.PoolManager(\n            cert_reqs='CERT_REQUIRED',\n            ca_certs=certifi.where())\n\n        if self.batch:\n            for chunk in utils.chunks(resources, self.batch_size):\n                resource_data = self.lookup_data\n                resource_data['resources'] = chunk\n                self._process_call(resource_data)\n        else:\n            for r in resources:\n                resource_data = self.lookup_data\n                resource_data['resource'] = r\n                self._process_call(resource_data)\n\n    def _process_call(self, resource):\n        prepared_url = self._build_url(resource)\n        prepared_body = self._build_body(resource)\n        prepared_headers = self._build_headers(resource)\n\n        if prepared_body:\n            prepared_headers['Content-Type'] = 'application/json'\n\n        try:\n            res = self.http.request(\n                method=self.method,\n                url=prepared_url,\n                body=prepared_body,\n                headers=prepared_headers)\n\n            self.log.info(\"%s got response %s with URL %s\" %\n                          (self.method, res.status, prepared_url))\n        except urllib3.exceptions.HTTPError as e:\n            self.log.error(\"Error calling %s. Code: %s\" % (prepared_url, e.reason))\n\n    def _build_headers(self, resource):\n        return {k: jmespath.search(v, resource) for k, v in self.headers.items()}\n\n    def _build_url(self, resource):\n        \"\"\"\n        Compose URL with query string parameters.\n\n        Will not lose existing static parameters in the URL string\n        but does not support 'duplicate' parameter entries\n        \"\"\"\n\n        if not self.query_params:\n            return self.url\n\n        evaluated_params = {k: jmespath.search(v, resource) for k, v in self.query_params.items()}\n\n        url_parts = list(parse.urlparse(self.url))\n        query = dict(parse.parse_qsl(url_parts[4]))\n        query.update(evaluated_params)\n        url_parts[4] = parse.urlencode(query)\n\n        return parse.urlunparse(url_parts)\n\n    def _build_body(self, resource):\n        \"\"\"Create a JSON body and dump it to encoded bytes.\"\"\"\n\n        if not self.body:\n            return None\n\n        return json.dumps(jmespath.search(self.body, resource)).encode('utf-8')\n", "path": "c7n/actions/webhook.py"}], "after_files": [{"content": "# Copyright 2019 Microsoft Corporation\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\ntry:\n    import certifi\nexcept ImportError:\n    certifi = None\n\nimport jmespath\nimport urllib3\nfrom six.moves.urllib import parse\n\nfrom c7n import utils\nfrom .core import EventAction\n\n\nclass Webhook(EventAction):\n    \"\"\"Calls a webhook with optional parameters and body\n       populated from JMESPath queries.\n\n        .. code-block:: yaml\n\n          policies:\n            - name: call-webhook\n              resource: ec2\n              description: |\n                Call webhook with list of resource groups\n              actions:\n               - type: webhook\n                 url: http://foo.com\n                 query-params:\n                    resource_name: resource.name\n                    policy_name: policy.name\n    \"\"\"\n\n    schema_alias = True\n    schema = utils.type_schema(\n        'webhook',\n        required=['url'],\n        **{\n            'url': {'type': 'string'},\n            'body': {'type': 'string'},\n            'batch': {'type': 'boolean'},\n            'batch-size': {'type': 'number'},\n            'method': {'type': 'string', 'enum': ['PUT', 'POST', 'GET', 'PATCH', 'DELETE']},\n            'query-params': {\n                \"type\": \"object\",\n                \"additionalProperties\": {\n                    \"type\": \"string\",\n                    \"description\": \"query string values\"\n                }\n            },\n            'headers': {\n                \"type\": \"object\",\n                \"additionalProperties\": {\n                    \"type\": \"string\",\n                    \"description\": \"header values\"\n                }\n            }\n        }\n    )\n\n    def __init__(self, data=None, manager=None, log_dir=None):\n        super(Webhook, self).__init__(data, manager, log_dir)\n        self.http = None\n        self.url = self.data.get('url')\n        self.body = self.data.get('body')\n        self.batch = self.data.get('batch', False)\n        self.batch_size = self.data.get('batch-size', 500)\n        self.query_params = self.data.get('query-params', {})\n        self.headers = self.data.get('headers', {})\n        self.method = self.data.get('method', 'POST')\n        self.lookup_data = {\n            'account_id': self.manager.config.account_id,\n            'region': self.manager.config.region,\n            'execution_id': self.manager.ctx.execution_id,\n            'execution_start': self.manager.ctx.start_time,\n            'policy': self.manager.data\n        }\n\n    def process(self, resources, event=None):\n        self.http = urllib3.PoolManager(\n            cert_reqs='CERT_REQUIRED',\n            ca_certs=certifi and certifi.where() or None)\n\n        if self.batch:\n            for chunk in utils.chunks(resources, self.batch_size):\n                resource_data = self.lookup_data\n                resource_data['resources'] = chunk\n                self._process_call(resource_data)\n        else:\n            for r in resources:\n                resource_data = self.lookup_data\n                resource_data['resource'] = r\n                self._process_call(resource_data)\n\n    def _process_call(self, resource):\n        prepared_url = self._build_url(resource)\n        prepared_body = self._build_body(resource)\n        prepared_headers = self._build_headers(resource)\n\n        if prepared_body:\n            prepared_headers['Content-Type'] = 'application/json'\n\n        try:\n            res = self.http.request(\n                method=self.method,\n                url=prepared_url,\n                body=prepared_body,\n                headers=prepared_headers)\n\n            self.log.info(\"%s got response %s with URL %s\" %\n                          (self.method, res.status, prepared_url))\n        except urllib3.exceptions.HTTPError as e:\n            self.log.error(\"Error calling %s. Code: %s\" % (prepared_url, e.reason))\n\n    def _build_headers(self, resource):\n        return {k: jmespath.search(v, resource) for k, v in self.headers.items()}\n\n    def _build_url(self, resource):\n        \"\"\"\n        Compose URL with query string parameters.\n\n        Will not lose existing static parameters in the URL string\n        but does not support 'duplicate' parameter entries\n        \"\"\"\n\n        if not self.query_params:\n            return self.url\n\n        evaluated_params = {k: jmespath.search(v, resource) for k, v in self.query_params.items()}\n\n        url_parts = list(parse.urlparse(self.url))\n        query = dict(parse.parse_qsl(url_parts[4]))\n        query.update(evaluated_params)\n        url_parts[4] = parse.urlencode(query)\n\n        return parse.urlunparse(url_parts)\n\n    def _build_body(self, resource):\n        \"\"\"Create a JSON body and dump it to encoded bytes.\"\"\"\n\n        if not self.body:\n            return None\n\n        return json.dumps(jmespath.search(self.body, resource)).encode('utf-8')\n", "path": "c7n/actions/webhook.py"}]}