Merge "Make delete a little bit handier."
diff --git a/Android.bp b/Android.bp
index 8eadb08..843fcba 100644
--- a/Android.bp
+++ b/Android.bp
@@ -40,6 +40,7 @@
     ],
     libs: [
         "acloud_create",
+        "acloud_delete",
         "acloud_public",
         "acloud_internal",
         "acloud_proto",
@@ -67,6 +68,7 @@
     ],
     libs: [
         "acloud_create",
+        "acloud_delete",
         "acloud_internal",
         "acloud_proto",
         "acloud_public",
@@ -136,3 +138,11 @@
          "create/*.py",
     ],
 }
+
+python_library_host{
+    name: "acloud_delete",
+    defaults: ["acloud_default"],
+    srcs: [
+         "delete/*.py",
+    ],
+}
diff --git a/create/create_common.py b/create/create_common.py
index 7f1c094..be67a2d 100644
--- a/create/create_common.py
+++ b/create/create_common.py
@@ -20,7 +20,6 @@
 import glob
 import logging
 import os
-import sys
 
 from acloud import errors
 from acloud.internal import constants
@@ -64,41 +63,6 @@
     return hw_dict
 
 
-def GetAnswerFromList(answer_list):
-    """Get answer from a list.
-
-    Args:
-        answer_list: list of the answers to choose from.
-
-    Return:
-        String of the answer.
-
-    Raises:
-        error.ChoiceExit: User choice exit.
-    """
-    print("[0] to exit.")
-    for num, item in enumerate(answer_list, 1):
-        print("[%d] %s" % (num, item))
-
-    choice = -1
-    max_choice = len(answer_list)
-    while True:
-        try:
-            choice = raw_input("Enter your choice[0-%d]: " % max_choice)
-            choice = int(choice)
-        except ValueError:
-            print("'%s' is not a valid integer.", choice)
-            continue
-        # Filter out choices
-        if choice == 0:
-            print("Exiting acloud.")
-            sys.exit()
-        if choice < 0 or choice > max_choice:
-            print("please choose between 0 and %d" % max_choice)
-        else:
-            return answer_list[choice-1]
-
-
 def VerifyLocalImageArtifactsExist(local_image_dir):
     """Verify the specifies local image dir.
 
@@ -125,7 +89,7 @@
                                         (image_pattern, local_image_dir))
     if len(images) > 1:
         print("Multiple images found, please choose 1.")
-        image_path = GetAnswerFromList(images)
+        image_path = utils.GetAnswerFromList(images)[0]
     else:
         image_path = images[0]
     logger.debug("Local image: %s ", image_path)
diff --git a/create/create_common_test.py b/create/create_common_test.py
index bb7cf13..25b193a 100644
--- a/create/create_common_test.py
+++ b/create/create_common_test.py
@@ -61,21 +61,6 @@
             create_common.VerifyLocalImageArtifactsExist("/fake_dirs"),
             "/fake_dirs/aosp_cf_x86_phone-img-5046769.zip")
 
-    @mock.patch("__builtin__.raw_input")
-    def testGetAnswerFromList(self, mock_raw_input):
-        """Test GetAnswerFromList."""
-        answer_list = ["image1.zip", "image2.zip", "image3.zip"]
-        mock_raw_input.return_value = 0
-        with self.assertRaises(SystemExit):
-            create_common.GetAnswerFromList(answer_list)
-        mock_raw_input.side_effect = [1, 2, 3]
-        self.assertEqual(create_common.GetAnswerFromList(answer_list),
-                         "image1.zip")
-        self.assertEqual(create_common.GetAnswerFromList(answer_list),
-                         "image2.zip")
-        self.assertEqual(create_common.GetAnswerFromList(answer_list),
-                         "image3.zip")
-
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/delete/__init__.py b/delete/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/delete/__init__.py
diff --git a/delete/delete.py b/delete/delete.py
new file mode 100644
index 0000000..f927489
--- /dev/null
+++ b/delete/delete.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""Delete entry point.
+
+Delete will handle all the logic related to deleting a local/remote instance
+of an Android Virtual Device.
+"""
+
+from __future__ import print_function
+import getpass
+import logging
+import time
+
+from acloud.internal.lib import auth
+from acloud.internal.lib import gcompute_client
+from acloud.internal.lib import utils
+from acloud.public import config
+from acloud.public import device_driver
+
+logger = logging.getLogger(__name__)
+
+_COMPUTE_SCOPE = "https://www.googleapis.com/auth/compute",
+
+
+def _FilterInstancesByUser(instances, user):
+    """Look through the instance data and filter them.
+
+    Args:
+        user: String, username to filter on.
+        instances: List of instance data. This is the response from the GCP
+                   compute API for listing instances.
+
+    Returns:
+        List of strings of the instance names.
+    """
+    filtered_instances = []
+    for instance_info in instances:
+        instance_name = instance_info.get("name")
+        for metadata in instance_info.get("metadata", {}).get("items", []):
+            if metadata["key"] == "user" and metadata["value"] == user:
+                filtered_instances.append(instance_name)
+    return filtered_instances
+
+
+def _FindRemoteInstances(cfg, user):
+    """Find instances created by user in project specified in cfg.
+
+    Args:
+        cfg: AcloudConfig object.
+        user: String, username to look for.
+
+    Returns:
+        List of strings that are instance names.
+    """
+    credentials = auth.CreateCredentials(cfg, _COMPUTE_SCOPE)
+    compute_client = gcompute_client.ComputeClient(cfg, credentials)
+    all_instances = compute_client.ListInstances(cfg.zone)
+    return _FilterInstancesByUser(all_instances, user)
+
+
+def _DeleteRemoteInstances(args, del_all_instances=False):
+    """Look for remote instances and delete them.
+
+    We're going to query the GCP project for all instances that have the user
+    mentioned in the metadata. If we find just one instance, print out the
+    details of it and delete it. If we find more than 1, ask the user which one
+    they'd like to delete unless del_all_instances is True, then just delete
+    them all.
+
+    Args:
+        args: Namespace object from argparse.parse_args.
+        del_all_instances: Boolean, when more than 1 instance is found,
+                           delete them all if True, otherwise prompt user.
+    """
+    cfg = config.GetAcloudConfig(args)
+    instances_to_delete = args.instance_names
+    if instances_to_delete is None:
+        instances_to_delete = _FindRemoteInstances(cfg, getpass.getuser())
+    if instances_to_delete:
+        # If the user didn't specify any instances and we find more than 1, ask
+        # them what they want to do (unless they specified --all).
+        if (args.instance_names is None
+                and len(instances_to_delete) > 1
+                and not del_all_instances):
+            print("Multiple instance detected, choose 1 to delete:")
+            instances_to_delete = utils.GetAnswerFromList(
+                instances_to_delete, enable_choose_all=True)
+        # TODO(b/117474343): We should do a couple extra things here:
+        #                    - adb disconnect
+        #                    - kill ssh tunnel and ssvnc
+        #                    - give details of each instance
+        #                    - Give better messaging about delete.
+        start = time.time()
+        utils.PrintColorString("Deleting %s ..." %
+                               ", ".join(instances_to_delete),
+                               utils.TextColors.WARNING, end="")
+        report = device_driver.DeleteAndroidVirtualDevices(cfg,
+                                                           instances_to_delete)
+        if report.errors:
+            utils.PrintColorString("Fail! (%ds)" % (time.time() - start),
+                                   utils.TextColors.FAIL)
+            logger.debug("Delete failed: %s", report.errors)
+        else:
+            utils.PrintColorString("OK! (%ds)" % (time.time() - start),
+                                   utils.TextColors.OKGREEN)
+        return report
+    print("No instances to delete")
+    return None
+
+
+def Run(args):
+    """Run delete.
+
+    Args:
+        args: Namespace object from argparse.parse_args.
+    """
+    report = _DeleteRemoteInstances(args, args.all)
+    # TODO(b/117474343): Delete local instances.
+    return report
diff --git a/delete/delete_args.py b/delete/delete_args.py
new file mode 100644
index 0000000..e5ee438
--- /dev/null
+++ b/delete/delete_args.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""Delete args.
+
+Defines the delete arg parser that holds delete specific args.
+"""
+
+CMD_DELETE = "delete"
+
+
+def GetDeleteArgParser(subparser):
+    """Return the delete arg parser.
+
+    Args:
+       subparser: argparse.ArgumentParser that is attached to main acloud cmd.
+
+    Returns:
+        argparse.ArgumentParser with delete options defined.
+    """
+    delete_parser = subparser.add_parser(CMD_DELETE)
+    delete_parser.required = False
+    delete_parser.set_defaults(which=CMD_DELETE)
+    delete_parser.add_argument(
+        "--instance_names",
+        dest="instance_names",
+        nargs="+",
+        required=False,
+        help="The names of the instances that need to delete, "
+        "separated by spaces, e.g. --instance_names instance-1 instance-2")
+    delete_parser.add_argument(
+        "--all",
+        action="store_true",
+        dest="all",
+        required=False,
+        help="If more than 1 AVD instance is found, delete them all.")
+
+    return delete_parser
diff --git a/delete/delete_test.py b/delete/delete_test.py
new file mode 100644
index 0000000..adac5f1
--- /dev/null
+++ b/delete/delete_test.py
@@ -0,0 +1,43 @@
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests for delete."""
+
+import unittest
+
+from acloud.delete import delete
+
+
+# pylint: disable=invalid-name,protected-access
+class DeleteTest(unittest.TestCase):
+    """Test delete functions."""
+
+    # pylint: disable=protected-access
+    def testFilterInstancesByUser(self):
+        """Test _FilterInstancesByUser."""
+        user = "instance_match_user"
+        matched_instance = "instance_1"
+        instances = [
+            {"name": matched_instance,
+             "metadata": {"items": [{"key": "user",
+                                     "value": user}]}},
+            {"name": "instance_2",
+             "metadata": {"items": [{"key": "user",
+                                     "value": "instance_no_match_user"}]}}]
+        expected_instances = [matched_instance]
+        self.assertEqual(expected_instances,
+                         delete._FilterInstancesByUser(instances, user))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/internal/lib/utils.py b/internal/lib/utils.py
index c832f3b..4acacbc 100755
--- a/internal/lib/utils.py
+++ b/internal/lib/utils.py
@@ -715,3 +715,41 @@
 
     return ForwardedPorts(vnc_port=local_free_vnc_port,
                           adb_port=local_free_adb_port)
+
+
+def GetAnswerFromList(answer_list, enable_choose_all=False):
+    """Get answer from a list.
+
+    Args:
+        answer_list: list of the answers to choose from.
+
+    Return:
+        List holding the answer(s).
+    """
+    print("[0] to exit.")
+    start_index = 1
+    if enable_choose_all:
+        start_index = 2
+        print("[1] for all.")
+    for num, item in enumerate(answer_list, start_index):
+        print("[%d] %s" % (num, item))
+
+    choice = -1
+    max_choice = len(answer_list) + 1
+    while True:
+        try:
+            choice = raw_input("Enter your choice[0-%d]: " % max_choice)
+            choice = int(choice)
+        except ValueError:
+            print("'%s' is not a valid integer.", choice)
+            continue
+        # Filter out choices
+        if choice == 0:
+            print("Exiting acloud.")
+            sys.exit()
+        if enable_choose_all and choice == 1:
+            return answer_list
+        if choice < 0 or choice > max_choice:
+            print("please choose between 0 and %d" % max_choice)
+        else:
+            return [answer_list[choice-start_index]]
diff --git a/internal/lib/utils_test.py b/internal/lib/utils_test.py
index aad23c1..9d62077 100644
--- a/internal/lib/utils_test.py
+++ b/internal/lib/utils_test.py
@@ -226,6 +226,24 @@
                 mock.call(16)
             ])
 
+    @mock.patch("__builtin__.raw_input")
+    def testGetAnswerFromList(self, mock_raw_input):
+        """Test GetAnswerFromList."""
+        answer_list = ["image1.zip", "image2.zip", "image3.zip"]
+        mock_raw_input.return_value = 0
+        with self.assertRaises(SystemExit):
+            utils.GetAnswerFromList(answer_list)
+        mock_raw_input.side_effect = [1, 2, 3, 1]
+        self.assertEqual(utils.GetAnswerFromList(answer_list),
+                         ["image1.zip"])
+        self.assertEqual(utils.GetAnswerFromList(answer_list),
+                         ["image2.zip"])
+        self.assertEqual(utils.GetAnswerFromList(answer_list),
+                         ["image3.zip"])
+        self.assertEqual(utils.GetAnswerFromList(answer_list,
+                                                 enable_choose_all=True),
+                         answer_list)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/public/acloud_main.py b/public/acloud_main.py
index f548789..1375236 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -86,6 +86,8 @@
 from acloud.public.actions import create_goldfish_action
 from acloud.create import create
 from acloud.create import create_args
+from acloud.delete import delete
+from acloud.delete import delete_args
 from acloud.setup import setup
 from acloud.setup import setup_args
 
@@ -114,9 +116,9 @@
         CMD_CLEANUP,
         CMD_CREATE_CUTTLEFISH,
         CMD_CREATE_GOLDFISH,
-        CMD_DELETE,
         CMD_SSHKEY,
         create_args.CMD_CREATE,
+        delete_args.CMD_DELETE,
         setup_args.CMD_SETUP,
     ])
     parser = argparse.ArgumentParser(
@@ -126,9 +128,6 @@
     subparsers = parser.add_subparsers()
     subparser_list = []
 
-    # Command "create"
-    subparser_list.append(create_args.GetCreateArgParser(subparsers))
-
     # Command "create_cf", create cuttlefish instances
     create_cf_parser = subparsers.add_parser(CMD_CREATE_CUTTLEFISH)
     create_cf_parser.required = False
@@ -211,19 +210,6 @@
     create_args.AddCommonCreateArgs(create_gf_parser)
     subparser_list.append(create_gf_parser)
 
-    # Command "Delete"
-    delete_parser = subparsers.add_parser(CMD_DELETE)
-    delete_parser.required = False
-    delete_parser.set_defaults(which=CMD_DELETE)
-    delete_parser.add_argument(
-        "--instance_names",
-        dest="instance_names",
-        nargs="+",
-        required=True,
-        help="The names of the instances that need to delete, "
-        "separated by spaces, e.g. --instance_names instance-1 instance-2")
-    subparser_list.append(delete_parser)
-
     # Command "cleanup"
     cleanup_parser = subparsers.add_parser(CMD_CLEANUP)
     cleanup_parser.required = False
@@ -257,9 +243,15 @@
         "that will be added as project-wide ssh key.")
     subparser_list.append(sshkey_parser)
 
+    # Command "create"
+    subparser_list.append(create_args.GetCreateArgParser(subparsers))
+
     # Command "setup"
     subparser_list.append(setup_args.GetSetupArgParser(subparsers))
 
+    # Command "Delete"
+    subparser_list.append(delete_args.GetDeleteArgParser(subparsers))
+
     # Add common arguments.
     for subparser in subparser_list:
         acloud_common.AddCommonArguments(subparser)
@@ -462,8 +454,7 @@
             branch=args.branch,
             report_internal_ip=args.report_internal_ip)
     elif args.which == CMD_DELETE:
-        report = device_driver.DeleteAndroidVirtualDevices(
-            cfg, args.instance_names)
+        report = delete.Run(args)
     elif args.which == CMD_CLEANUP:
         report = device_driver.Cleanup(cfg, args.expiration_mins)
     elif args.which == CMD_SSHKEY: