Repository: getmoto/moto

Issue:
Invalid instance ID error behaves differently from AWS
https://github.com/spulec/moto/blob/ea81377cd0ce669dbb01d2141ef6f7e92c9b757d/moto/ec2/exceptions.py#L238

When I terminate more than one non-existent ID in AWS, I get the error:
An error occurred (InvalidInstanceID.NotFound) when calling the TerminateInstances operation: The instance IDs 'i-0f5d8ccc56c69fae4, i-0eecca434287a1a33, i-05a2b7d81718e0678' do not exist

But this error currently only reports a single instance ID at once.

Not a major issue, but the behaviour difference did trip me up at first.

Thanks!


Discussion:
Thanks for raising this @cheshirex :+1: 
Hi @cheshirex, what version of Moto are you using?
Against the latest version, I am seeing the correct error message:
`The instance ID '['instance-1', 'i-34f0548abfb5d2a97', 'instance-2']' does not exist`

(Where `i-34f0548abfb5d2a97` is an existing instance ID)
(Sorry, I missed the notification -- I need to configure my email to not bury github notifications)

Looks like I have a hardcoded older version of localstack, so yes, that's going to be out of date. I'll nitpick here and point out the singular vs plural difference is still there, but I highly doubt that will realistically bother anyone.

Thanks!

Resolving patch:
diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py
--- a/moto/ec2/exceptions.py
+++ b/moto/ec2/exceptions.py
@@ -233,10 +233,13 @@ def __init__(self, route_table_id, cidr):
 
 class InvalidInstanceIdError(EC2ClientError):
     def __init__(self, instance_id):
-        super().__init__(
-            "InvalidInstanceID.NotFound",
-            "The instance ID '{0}' does not exist".format(instance_id),
-        )
+        if isinstance(instance_id, str):
+            instance_id = [instance_id]
+        if len(instance_id) > 1:
+            msg = f"The instance IDs '{', '.join(instance_id)}' do not exist"
+        else:
+            msg = f"The instance ID '{instance_id[0]}' does not exist"
+        super().__init__("InvalidInstanceID.NotFound", msg)
 
 
 class InvalidInstanceTypeError(EC2ClientError):
diff --git a/moto/ec2/models.py b/moto/ec2/models.py
--- a/moto/ec2/models.py
+++ b/moto/ec2/models.py
@@ -1284,9 +1284,10 @@ def get_multi_instances_by_id(self, instance_ids, filters=None):
                     if instance.applies(filters):
                         result.append(instance)
 
-        # TODO: Trim error message down to specific invalid id.
         if instance_ids and len(instance_ids) > len(result):
-            raise InvalidInstanceIdError(instance_ids)
+            result_ids = [i.id for i in result]
+            missing_instance_ids = [i for i in instance_ids if i not in result_ids]
+            raise InvalidInstanceIdError(missing_instance_ids)
 
         return result
 


Test patch:
diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py
--- a/tests/test_ec2/test_instances.py
+++ b/tests/test_ec2/test_instances.py
@@ -2049,6 +2049,43 @@ def test_instance_termination_protection():
     instance["State"]["Name"].should.equal("terminated")
 
 
+@mock_ec2
+def test_terminate_unknown_instances():
+    client = boto3.client("ec2", region_name="us-west-1")
+
+    # Correct error message for single unknown instance
+    with pytest.raises(ClientError) as ex:
+        client.terminate_instances(InstanceIds=["i-12345678"])
+    error = ex.value.response["Error"]
+    error["Code"].should.equal("InvalidInstanceID.NotFound")
+    error["Message"].should.equal("The instance ID 'i-12345678' does not exist")
+
+    # Correct error message for multiple unknown instances
+    with pytest.raises(ClientError) as ex:
+        client.terminate_instances(InstanceIds=["i-12345678", "i-12345668"])
+    error = ex.value.response["Error"]
+    error["Code"].should.equal("InvalidInstanceID.NotFound")
+    error["Message"].should.equal(
+        "The instance IDs 'i-12345678, i-12345668' do not exist"
+    )
+
+    # Create an instance
+    resp = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
+    instance_id = resp["Instances"][0]["InstanceId"]
+
+    # Correct error message if one instance is known
+    with pytest.raises(ClientError) as ex:
+        client.terminate_instances(InstanceIds=["i-12345678", instance_id])
+    error = ex.value.response["Error"]
+    error["Code"].should.equal("InvalidInstanceID.NotFound")
+    error["Message"].should.equal("The instance ID 'i-12345678' does not exist")
+
+    # status = still running
+    resp = client.describe_instances(InstanceIds=[instance_id])
+    instance = resp["Reservations"][0]["Instances"][0]
+    instance["State"]["Name"].should.equal("running")
+
+
 @mock_ec2
 def test_instance_lifecycle():
     ec2_resource = boto3.resource("ec2", "us-west-1")