From ca783f46595a7d90cea0ad8491b65aa5f9370a04 Mon Sep 17 00:00:00 2001
From: Dean Troyer <dtroyer@gmail.com>
Date: Mon, 13 Oct 2014 22:40:11 -0500
Subject: [PATCH] Close files on image create

The file opened for --file was never closed.  Close it if it is a
file object.

Change-Id: I7bd120a2413de42339771d01e8fd1894d38c3011
---
 openstackclient/image/v1/image.py            | 49 +++++++++++---------
 openstackclient/tests/image/v1/test_image.py | 17 +++++--
 2 files changed, 40 insertions(+), 26 deletions(-)

diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 465e9d7b15..32dd388c0f 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -15,6 +15,7 @@
 
 """Image V1 Action Implementations"""
 
+import io
 import logging
 import os
 import six
@@ -214,10 +215,9 @@ class CreateImage(show.ShowOne):
             elif parsed_args.file:
                 # Send an open file handle to glanceclient so it will
                 # do a chunked transfer
-                kwargs["data"] = open(parsed_args.file, "rb")
+                kwargs["data"] = io.open(parsed_args.file, "rb")
             else:
                 # Read file from stdin
-                kwargs["data"] = None
                 if sys.stdin.isatty() is not True:
                     if msvcrt:
                         msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
@@ -225,29 +225,36 @@ class CreateImage(show.ShowOne):
                     # do a chunked transfer
                     kwargs["data"] = sys.stdin
 
+        # Wrap the call to catch exceptions in order to close files
         try:
-            image = utils.find_resource(
-                image_client.images,
-                parsed_args.name,
-            )
+            try:
+                image = utils.find_resource(
+                    image_client.images,
+                    parsed_args.name,
+                )
 
-            # Preserve previous properties if any are being set now
-            if image.properties:
-                if parsed_args.properties:
-                    image.properties.update(kwargs['properties'])
-                kwargs['properties'] = image.properties
+                # Preserve previous properties if any are being set now
+                if image.properties:
+                    if parsed_args.properties:
+                        image.properties.update(kwargs['properties'])
+                    kwargs['properties'] = image.properties
 
-        except exceptions.CommandError:
-            if not parsed_args.volume:
-                # This is normal for a create or reserve (create w/o an image)
-                # But skip for create from volume
-                image = image_client.images.create(**kwargs)
-        else:
-            # Update an existing reservation
+            except exceptions.CommandError:
+                if not parsed_args.volume:
+                    # This is normal for a create or reserve (create w/o
+                    # an image), but skip for create from volume
+                    image = image_client.images.create(**kwargs)
+            else:
+                # Update an existing reservation
 
-            # If an image is specified via --file, --location or
-            # --copy-from let the API handle it
-            image = image_client.images.update(image.id, **kwargs)
+                # If an image is specified via --file, --location or
+                # --copy-from let the API handle it
+                image = image_client.images.update(image.id, **kwargs)
+        finally:
+            # Clean up open files - make sure data isn't a string
+            if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
+               kwargs['data'] != sys.stdin):
+                    kwargs['data'].close()
 
         info = {}
         info.update(image._info)
diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py
index 3f97b151c2..a05669300e 100644
--- a/openstackclient/tests/image/v1/test_image.py
+++ b/openstackclient/tests/image/v1/test_image.py
@@ -139,14 +139,17 @@ class TestImageCreate(TestImage):
         self.assertEqual(image_fakes.IMAGE_columns, columns)
         self.assertEqual(image_fakes.IMAGE_data, data)
 
-    @mock.patch('six.moves.builtins.open')
-    def test_image_create_file(self, open_mock):
+    @mock.patch('openstackclient.image.v1.image.io.open', name='Open')
+    def test_image_create_file(self, mock_open):
+        mock_file = mock.MagicMock(name='File')
+        mock_open.return_value = mock_file
+        mock_open.read.return_value = image_fakes.image_data
         mock_exception = {
             'find.side_effect': exceptions.CommandError('x'),
             'get.side_effect': exceptions.CommandError('x'),
         }
         self.images_mock.configure_mock(**mock_exception)
-        open_mock.return_value = image_fakes.image_data
+
         arglist = [
             '--file', 'filer',
             '--unprotected',
@@ -169,7 +172,11 @@ class TestImageCreate(TestImage):
         # DisplayCommandBase.take_action() returns two tuples
         columns, data = self.cmd.take_action(parsed_args)
 
-        open_mock.assert_called_with('filer', 'rb')
+        # Ensure input file is opened
+        mock_open.assert_called_with('filer', 'rb')
+
+        # Ensure the input file is closed
+        mock_file.close.assert_called_with()
 
         # ImageManager.get(name)
         self.images_mock.get.assert_called_with(image_fakes.image_name)
@@ -185,7 +192,7 @@ class TestImageCreate(TestImage):
                 'Alpha': '1',
                 'Beta': '2',
             },
-            data=image_fakes.image_data,
+            data=mock_file,
         )
 
         # Verify update() was not called, if it was show the args