Repository: cloud-custodian/cloud-custodian

Issue:
Azure - Permission denied when publishing function from Windows
**Environment**
Windows 10 Enterprise, elevated command prompt or PowerShell
(works perfectly in WSL)

**Issue**
Attempting to run a script locally which publishes an Azure Function results in ```PermissionError: [Errno 13] Permission denied```

Policy I'm trying to publish:
```yaml
policies:
    - name: tag-resource-group-creator
      resource: azure.resourcegroup
      mode:
        type: azure-event-grid
        events: [{
            resourceProvider: 'Microsoft.Resources/subscriptions/resourceGroups',
            event: 'write'
          }]
      filters:
        - "tag:CreatorEmail": null
      actions:
        - type: auto-tag-user
          tag: CreatorEmail       
```

Full output below:
```
Traceback (most recent call last):
  File "c:\code\cloud-custodian\c7n\commands.py", line 248, in run
    policy()
  File "c:\code\cloud-custodian\c7n\policy.py", line 878, in __call__
    resources = mode.provision()
  File "c:\code\cloud-custodian\tools\c7n_azure\c7n_azure\policy.py", line 255, in provision
    FunctionAppUtilities.publish_functions_package(self.function_params, package)
  File "c:\code\cloud-custodian\tools\c7n_azure\c7n_azure\functionapp_utils.py", line 152, in publish_functions_package
    FUNCTION_CONSUMPTION_BLOB_CONTAINER, blob_name, package.pkg.path)
  File "c:\code\custodian\lib\site-packages\azure\storage\blob\blockblobservice.py", line 447, in create_blob_from_path
    with open(file_path, 'rb') as stream:
PermissionError: [Errno 13] Permission denied: 'C:\\Users\\[username]\\AppData\\Local\\Temp\\tmp4u4cpc8l'
```

Currently troubleshooting this as the root cause (from what I can determine) is that the temp file is actually being deleted before the following code is called:
https://github.com/cloud-custodian/cloud-custodian/blob/a8e89539d4e5e23d5cb7988eab226aae9ae1bfd8/tools/c7n_azure/c7n_azure/policy.py#L250-L255

Working on a possible resolution which I will submit via PR

@aluong 
Azure - Permission denied when publishing function from Windows
**Environment**
Windows 10 Enterprise, elevated command prompt or PowerShell
(works perfectly in WSL)

**Issue**
Attempting to run a script locally which publishes an Azure Function results in ```PermissionError: [Errno 13] Permission denied```

Policy I'm trying to publish:
```yaml
policies:
    - name: tag-resource-group-creator
      resource: azure.resourcegroup
      mode:
        type: azure-event-grid
        events: [{
            resourceProvider: 'Microsoft.Resources/subscriptions/resourceGroups',
            event: 'write'
          }]
      filters:
        - "tag:CreatorEmail": null
      actions:
        - type: auto-tag-user
          tag: CreatorEmail       
```

Full output below:
```
Traceback (most recent call last):
  File "c:\code\cloud-custodian\c7n\commands.py", line 248, in run
    policy()
  File "c:\code\cloud-custodian\c7n\policy.py", line 878, in __call__
    resources = mode.provision()
  File "c:\code\cloud-custodian\tools\c7n_azure\c7n_azure\policy.py", line 255, in provision
    FunctionAppUtilities.publish_functions_package(self.function_params, package)
  File "c:\code\cloud-custodian\tools\c7n_azure\c7n_azure\functionapp_utils.py", line 152, in publish_functions_package
    FUNCTION_CONSUMPTION_BLOB_CONTAINER, blob_name, package.pkg.path)
  File "c:\code\custodian\lib\site-packages\azure\storage\blob\blockblobservice.py", line 447, in create_blob_from_path
    with open(file_path, 'rb') as stream:
PermissionError: [Errno 13] Permission denied: 'C:\\Users\\[username]\\AppData\\Local\\Temp\\tmp4u4cpc8l'
```

Currently troubleshooting this as the root cause (from what I can determine) is that the temp file is actually being deleted before the following code is called:
https://github.com/cloud-custodian/cloud-custodian/blob/a8e89539d4e5e23d5cb7988eab226aae9ae1bfd8/tools/c7n_azure/c7n_azure/policy.py#L250-L255

Working on a possible resolution which I will submit via PR

@aluong 


Discussion:
With the help of @logachev I've corrected the root cause. Will be testing more and submitting PR for this tomorrow. 
With the help of @logachev I've corrected the root cause. Will be testing more and submitting PR for this tomorrow. 

Resolving patch:
diff --git a/c7n/mu.py b/c7n/mu.py
--- a/c7n/mu.py
+++ b/c7n/mu.py
@@ -199,6 +199,10 @@ def close(self):
                 1024.0 * 1024.0)))
         return self
 
+    @staticmethod
+    def _temporary_opener(name, flag, mode=0o777):
+        return os.open(name, flag | os.O_TEMPORARY, mode)
+
     def remove(self):
         """Dispose of the temp file for garbage collection."""
         if self._temp_archive_file:
@@ -213,7 +217,16 @@ def get_checksum(self, encoder=base64.b64encode, hasher=hashlib.sha256):
     def get_bytes(self):
         """Return the entire zip file as a byte string. """
         assert self._closed, "Archive not closed"
-        return open(self._temp_archive_file.name, 'rb').read()
+        return self.get_stream().read()
+
+    def get_stream(self):
+        """Return the entire zip file as a stream. """
+        assert self._closed, "Archive not closed"
+        # Windows requires TEMPORARY flag if you want to open files created by tempfile library
+        if os.name == 'nt':
+            return open(self._temp_archive_file.name, 'rb', opener=self._temporary_opener)
+        else:
+            return open(self._temp_archive_file.name, 'rb')
 
     def get_reader(self):
         """Return a read-only :py:class:`~zipfile.ZipFile`."""
diff --git a/tools/c7n_azure/c7n_azure/function_package.py b/tools/c7n_azure/c7n_azure/function_package.py
--- a/tools/c7n_azure/c7n_azure/function_package.py
+++ b/tools/c7n_azure/c7n_azure/function_package.py
@@ -213,10 +213,6 @@ def status(self, deployment_creds):
 
         return True
 
-    @staticmethod
-    def _temporary_opener(name, flag, mode=0o777):
-        return os.open(name, flag | os.O_TEMPORARY, mode)
-
     def publish(self, deployment_creds):
         self.close()
 
@@ -226,11 +222,7 @@ def publish(self, deployment_creds):
 
         self.log.info("Publishing Function package from %s" % self.pkg.path)
 
-        # Windows requires TEMPORARY flag if you want to open files created by tempfile library
-        if os.name == 'nt':
-            zip_file = open(self.pkg.path, 'rb', opener=FunctionPackage._temporary_opener).read()
-        else:
-            zip_file = open(self.pkg.path, 'rb').read()
+        zip_file = self.pkg.get_bytes()
 
         try:
             r = requests.post(zip_api_url, data=zip_file, timeout=300, verify=self.enable_ssl_cert)
diff --git a/tools/c7n_azure/c7n_azure/functionapp_utils.py b/tools/c7n_azure/c7n_azure/functionapp_utils.py
--- a/tools/c7n_azure/c7n_azure/functionapp_utils.py
+++ b/tools/c7n_azure/c7n_azure/functionapp_utils.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 import datetime
 import logging
+import os
 import re
 import time
 
@@ -148,8 +149,12 @@ def publish_functions_package(cls, function_params, package):
 
             # upload package
             blob_name = '%s.zip' % function_params.function_app_name
-            blob_client.create_blob_from_path(
-                FUNCTION_CONSUMPTION_BLOB_CONTAINER, blob_name, package.pkg.path)
+            packageToPublish = package.pkg.get_stream()
+            count = os.path.getsize(package.pkg.path)
+
+            blob_client.create_blob_from_stream(
+                FUNCTION_CONSUMPTION_BLOB_CONTAINER, blob_name, packageToPublish, count)
+            packageToPublish.close()
 
             # create blob url for package
             sas = blob_client.generate_blob_shared_access_signature(


Test patch:
diff --git a/tools/c7n_azure/tests/test_functionapp_utils.py b/tools/c7n_azure/tests/test_functionapp_utils.py
--- a/tools/c7n_azure/tests/test_functionapp_utils.py
+++ b/tools/c7n_azure/tests/test_functionapp_utils.py
@@ -157,8 +157,11 @@ def test_publish_functions_package_consumption(self):
             function_app_resource_group_name=CONST_GROUP_NAME,
             function_app_name='cloud-custodian-test')
 
+        package = FunctionPackage("TestPolicy")
+        package.close()
+
         FunctionAppUtilities.publish_functions_package(
-            parameters, FunctionPackage("TestPolicy"))
+            parameters, package)
 
         # verify app setting updated
         wc = self.session.client('azure.mgmt.web.WebSiteManagementClient')