Repository: getmoto/moto
Pull request #5926 (2023-02-13 22:24:37)

EC2: Simplify DryRun-handling
Almost all EC2-methods followed the format:
```
if self.is_not_dryrun():
    act()
```
where `self.is_not_dryrun` would either throw an error, or return True. 

This has all been refactored to the following:
```
self.error_on_dryrun
act()
```

This is easier to read, and also will make it easier to add type annotations later on.


Patch:
diff --git a/moto/core/responses.py b/moto/core/responses.py
index f273f0223..ccffe4332 100644
--- a/moto/core/responses.py
+++ b/moto/core/responses.py
@@ -838,14 +838,10 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
         return "JSON" in self.querystring.get("ContentType", [])
 
     def error_on_dryrun(self) -> None:
-        self.is_not_dryrun()
-
-    def is_not_dryrun(self, action: Optional[str] = None) -> bool:
         if "true" in self.querystring.get("DryRun", ["false"]):
-            a = action or self._get_param("Action")
+            a = self._get_param("Action")
             message = f"An error occurred (DryRunOperation) when calling the {a} operation: Request would have succeeded, but DryRun flag is set"
             raise DryRunClientError(error_type="DryRunOperation", message=message)
-        return True
 
 
 class _RecursiveDictRef(object):
diff --git a/moto/ec2/responses/amis.py b/moto/ec2/responses/amis.py
index 47925ba74..689cf7f59 100644
--- a/moto/ec2/responses/amis.py
+++ b/moto/ec2/responses/amis.py
@@ -7,34 +7,40 @@ class AmisResponse(EC2BaseResponse):
         description = self._get_param("Description", if_none="")
         instance_id = self._get_param("InstanceId")
         tag_specifications = self._get_multi_param("TagSpecification")
-        if self.is_not_dryrun("CreateImage"):
-            image = self.ec2_backend.create_image(
-                instance_id,
-                name,
-                description,
-                tag_specifications=tag_specifications,
-            )
-            template = self.response_template(CREATE_IMAGE_RESPONSE)
-            return template.render(image=image)
+
+        self.error_on_dryrun()
+
+        image = self.ec2_backend.create_image(
+            instance_id,
+            name,
+            description,
+            tag_specifications=tag_specifications,
+        )
+        template = self.response_template(CREATE_IMAGE_RESPONSE)
+        return template.render(image=image)
 
     def copy_image(self):
         source_image_id = self._get_param("SourceImageId")
         source_region = self._get_param("SourceRegion")
         name = self._get_param("Name")
         description = self._get_param("Description")
-        if self.is_not_dryrun("CopyImage"):
-            image = self.ec2_backend.copy_image(
-                source_image_id, source_region, name, description
-            )
-            template = self.response_template(COPY_IMAGE_RESPONSE)
-            return template.render(image=image)
+
+        self.error_on_dryrun()
+
+        image = self.ec2_backend.copy_image(
+            source_image_id, source_region, name, description
+        )
+        template = self.response_template(COPY_IMAGE_RESPONSE)
+        return template.render(image=image)
 
     def deregister_image(self):
         ami_id = self._get_param("ImageId")
-        if self.is_not_dryrun("DeregisterImage"):
-            self.ec2_backend.deregister_image(ami_id)
-            template = self.response_template(DEREGISTER_IMAGE_RESPONSE)
-            return template.render(success="true")
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.deregister_image(ami_id)
+        template = self.response_template(DEREGISTER_IMAGE_RESPONSE)
+        return template.render(success="true")
 
     def describe_images(self):
         self.error_on_dryrun()
@@ -60,30 +66,33 @@ class AmisResponse(EC2BaseResponse):
         operation_type = self._get_param("OperationType")
         group = self._get_param("UserGroup.1")
         user_ids = self._get_multi_param("UserId")
-        if self.is_not_dryrun("ModifyImageAttribute"):
-            if operation_type == "add":
-                self.ec2_backend.add_launch_permission(
-                    ami_id, user_ids=user_ids, group=group
-                )
-            elif operation_type == "remove":
-                self.ec2_backend.remove_launch_permission(
-                    ami_id, user_ids=user_ids, group=group
-                )
-            return MODIFY_IMAGE_ATTRIBUTE_RESPONSE
+
+        self.error_on_dryrun()
+
+        if operation_type == "add":
+            self.ec2_backend.add_launch_permission(
+                ami_id, user_ids=user_ids, group=group
+            )
+        elif operation_type == "remove":
+            self.ec2_backend.remove_launch_permission(
+                ami_id, user_ids=user_ids, group=group
+            )
+        return MODIFY_IMAGE_ATTRIBUTE_RESPONSE
 
     def register_image(self):
         name = self.querystring.get("Name")[0]
         description = self._get_param("Description", if_none="")
-        if self.is_not_dryrun("RegisterImage"):
-            image = self.ec2_backend.register_image(name, description)
-            template = self.response_template(REGISTER_IMAGE_RESPONSE)
-            return template.render(image=image)
+
+        self.error_on_dryrun()
+
+        image = self.ec2_backend.register_image(name, description)
+        template = self.response_template(REGISTER_IMAGE_RESPONSE)
+        return template.render(image=image)
 
     def reset_image_attribute(self):
-        if self.is_not_dryrun("ResetImageAttribute"):
-            raise NotImplementedError(
-                "AMIs.reset_image_attribute is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError("AMIs.reset_image_attribute is not yet implemented")
 
 
 CREATE_IMAGE_RESPONSE = """<CreateImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
diff --git a/moto/ec2/responses/elastic_block_store.py b/moto/ec2/responses/elastic_block_store.py
index 71729f143..c234eeef3 100644
--- a/moto/ec2/responses/elastic_block_store.py
+++ b/moto/ec2/responses/elastic_block_store.py
@@ -6,12 +6,12 @@ class ElasticBlockStore(EC2BaseResponse):
         volume_id = self._get_param("VolumeId")
         instance_id = self._get_param("InstanceId")
         device_path = self._get_param("Device")
-        if self.is_not_dryrun("AttachVolume"):
-            attachment = self.ec2_backend.attach_volume(
-                volume_id, instance_id, device_path
-            )
-            template = self.response_template(ATTACHED_VOLUME_RESPONSE)
-            return template.render(attachment=attachment)
+
+        self.error_on_dryrun()
+
+        attachment = self.ec2_backend.attach_volume(volume_id, instance_id, device_path)
+        template = self.response_template(ATTACHED_VOLUME_RESPONSE)
+        return template.render(attachment=attachment)
 
     def copy_snapshot(self):
         source_snapshot_id = self._get_param("SourceSnapshotId")
@@ -19,24 +19,28 @@ class ElasticBlockStore(EC2BaseResponse):
         description = self._get_param("Description")
         tags = self._parse_tag_specification()
         snapshot_tags = tags.get("snapshot", {})
-        if self.is_not_dryrun("CopySnapshot"):
-            snapshot = self.ec2_backend.copy_snapshot(
-                source_snapshot_id, source_region, description
-            )
-            snapshot.add_tags(snapshot_tags)
-            template = self.response_template(COPY_SNAPSHOT_RESPONSE)
-            return template.render(snapshot=snapshot)
+
+        self.error_on_dryrun()
+
+        snapshot = self.ec2_backend.copy_snapshot(
+            source_snapshot_id, source_region, description
+        )
+        snapshot.add_tags(snapshot_tags)
+        template = self.response_template(COPY_SNAPSHOT_RESPONSE)
+        return template.render(snapshot=snapshot)
 
     def create_snapshot(self):
         volume_id = self._get_param("VolumeId")
         description = self._get_param("Description")
         tags = self._parse_tag_specification()
         snapshot_tags = tags.get("snapshot", {})
-        if self.is_not_dryrun("CreateSnapshot"):
-            snapshot = self.ec2_backend.create_snapshot(volume_id, description)
-            snapshot.add_tags(snapshot_tags)
-            template = self.response_template(CREATE_SNAPSHOT_RESPONSE)
-            return template.render(snapshot=snapshot)
+
+        self.error_on_dryrun()
+
+        snapshot = self.ec2_backend.create_snapshot(volume_id, description)
+        snapshot.add_tags(snapshot_tags)
+        template = self.response_template(CREATE_SNAPSHOT_RESPONSE)
+        return template.render(snapshot=snapshot)
 
     def create_snapshots(self):
         params = self._get_params()
@@ -45,12 +49,13 @@ class ElasticBlockStore(EC2BaseResponse):
         tags = self._parse_tag_specification()
         snapshot_tags = tags.get("snapshot", {})
 
-        if self.is_not_dryrun("CreateSnapshots"):
-            snapshots = self.ec2_backend.create_snapshots(
-                instance_spec, description, snapshot_tags
-            )
-            template = self.response_template(CREATE_SNAPSHOTS_RESPONSE)
-            return template.render(snapshots=snapshots)
+        self.error_on_dryrun()
+
+        snapshots = self.ec2_backend.create_snapshots(
+            instance_spec, description, snapshot_tags
+        )
+        template = self.response_template(CREATE_SNAPSHOTS_RESPONSE)
+        return template.render(snapshots=snapshots)
 
     def create_volume(self):
         size = self._get_param("Size")
@@ -62,31 +67,34 @@ class ElasticBlockStore(EC2BaseResponse):
         encrypted = self._get_bool_param("Encrypted", if_none=False)
         kms_key_id = self._get_param("KmsKeyId")
         iops = self._get_param("Iops")
-        if self.is_not_dryrun("CreateVolume"):
-            volume = self.ec2_backend.create_volume(
-                size=size,
-                zone_name=zone,
-                snapshot_id=snapshot_id,
-                encrypted=encrypted,
-                kms_key_id=kms_key_id,
-                volume_type=volume_type,
-                iops=iops,
-            )
-            volume.add_tags(volume_tags)
-            template = self.response_template(CREATE_VOLUME_RESPONSE)
-            return template.render(volume=volume)
+
+        self.error_on_dryrun()
+
+        volume = self.ec2_backend.create_volume(
+            size=size,
+            zone_name=zone,
+            snapshot_id=snapshot_id,
+            encrypted=encrypted,
+            kms_key_id=kms_key_id,
+            volume_type=volume_type,
+            iops=iops,
+        )
+        volume.add_tags(volume_tags)
+        template = self.response_template(CREATE_VOLUME_RESPONSE)
+        return template.render(volume=volume)
 
     def modify_volume(self):
         volume_id = self._get_param("VolumeId")
         target_size = self._get_param("Size")
         target_volume_type = self._get_param("VolumeType")
 
-        if self.is_not_dryrun("ModifyVolume"):
-            volume = self.ec2_backend.modify_volume(
-                volume_id, target_size, target_volume_type
-            )
-            template = self.response_template(MODIFY_VOLUME_RESPONSE)
-            return template.render(volume=volume)
+        self.error_on_dryrun()
+
+        volume = self.ec2_backend.modify_volume(
+            volume_id, target_size, target_volume_type
+        )
+        template = self.response_template(MODIFY_VOLUME_RESPONSE)
+        return template.render(volume=volume)
 
     def describe_volumes_modifications(self):
         filters = self._filters_from_querystring()
@@ -99,15 +107,19 @@ class ElasticBlockStore(EC2BaseResponse):
 
     def delete_snapshot(self):
         snapshot_id = self._get_param("SnapshotId")
-        if self.is_not_dryrun("DeleteSnapshot"):
-            self.ec2_backend.delete_snapshot(snapshot_id)
-            return DELETE_SNAPSHOT_RESPONSE
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.delete_snapshot(snapshot_id)
+        return DELETE_SNAPSHOT_RESPONSE
 
     def delete_volume(self):
         volume_id = self._get_param("VolumeId")
-        if self.is_not_dryrun("DeleteVolume"):
-            self.ec2_backend.delete_volume(volume_id)
-            return DELETE_VOLUME_RESPONSE
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.delete_volume(volume_id)
+        return DELETE_VOLUME_RESPONSE
 
     def describe_snapshots(self):
         filters = self._filters_from_querystring()
@@ -141,24 +153,26 @@ class ElasticBlockStore(EC2BaseResponse):
         volume_id = self._get_param("VolumeId")
         instance_id = self._get_param("InstanceId")
         device_path = self._get_param("Device")
-        if self.is_not_dryrun("DetachVolume"):
-            attachment = self.ec2_backend.detach_volume(
-                volume_id, instance_id, device_path
-            )
-            template = self.response_template(DETATCH_VOLUME_RESPONSE)
-            return template.render(attachment=attachment)
+
+        self.error_on_dryrun()
+
+        attachment = self.ec2_backend.detach_volume(volume_id, instance_id, device_path)
+        template = self.response_template(DETATCH_VOLUME_RESPONSE)
+        return template.render(attachment=attachment)
 
     def enable_volume_io(self):
-        if self.is_not_dryrun("EnableVolumeIO"):
-            raise NotImplementedError(
-                "ElasticBlockStore.enable_volume_io is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "ElasticBlockStore.enable_volume_io is not yet implemented"
+        )
 
     def import_volume(self):
-        if self.is_not_dryrun("ImportVolume"):
-            raise NotImplementedError(
-                "ElasticBlockStore.import_volume is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "ElasticBlockStore.import_volume is not yet implemented"
+        )
 
     def describe_snapshot_attribute(self):
         snapshot_id = self._get_param("SnapshotId")
@@ -172,28 +186,32 @@ class ElasticBlockStore(EC2BaseResponse):
         operation_type = self._get_param("OperationType")
         groups = self._get_multi_param("UserGroup")
         user_ids = self._get_multi_param("UserId")
-        if self.is_not_dryrun("ModifySnapshotAttribute"):
-            if operation_type == "add":
-                self.ec2_backend.add_create_volume_permission(
-                    snapshot_id, user_ids=user_ids, groups=groups
-                )
-            elif operation_type == "remove":
-                self.ec2_backend.remove_create_volume_permission(
-                    snapshot_id, user_ids=user_ids, groups=groups
-                )
-            return MODIFY_SNAPSHOT_ATTRIBUTE_RESPONSE
 
-    def modify_volume_attribute(self):
-        if self.is_not_dryrun("ModifyVolumeAttribute"):
-            raise NotImplementedError(
-                "ElasticBlockStore.modify_volume_attribute is not yet implemented"
+        self.error_on_dryrun()
+
+        if operation_type == "add":
+            self.ec2_backend.add_create_volume_permission(
+                snapshot_id, user_ids=user_ids, groups=groups
+            )
+        elif operation_type == "remove":
+            self.ec2_backend.remove_create_volume_permission(
+                snapshot_id, user_ids=user_ids, groups=groups
             )
+        return MODIFY_SNAPSHOT_ATTRIBUTE_RESPONSE
+
+    def modify_volume_attribute(self):
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "ElasticBlockStore.modify_volume_attribute is not yet implemented"
+        )
 
     def reset_snapshot_attribute(self):
-        if self.is_not_dryrun("ResetSnapshotAttribute"):
-            raise NotImplementedError(
-                "ElasticBlockStore.reset_snapshot_attribute is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "ElasticBlockStore.reset_snapshot_attribute is not yet implemented"
+        )
 
 
 CREATE_VOLUME_RESPONSE = """<CreateVolumeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
diff --git a/moto/ec2/responses/elastic_ip_addresses.py b/moto/ec2/responses/elastic_ip_addresses.py
index 2deba6be8..6d03ec6ce 100644
--- a/moto/ec2/responses/elastic_ip_addresses.py
+++ b/moto/ec2/responses/elastic_ip_addresses.py
@@ -9,15 +9,16 @@ class ElasticIPAddresses(EC2BaseResponse):
         tags = self._get_multi_param("TagSpecification")
         tags = add_tag_specification(tags)
 
-        if self.is_not_dryrun("AllocateAddress"):
-            if reallocate_address:
-                address = self.ec2_backend.allocate_address(
-                    domain, address=reallocate_address, tags=tags
-                )
-            else:
-                address = self.ec2_backend.allocate_address(domain, tags=tags)
-            template = self.response_template(ALLOCATE_ADDRESS_RESPONSE)
-            return template.render(address=address)
+        self.error_on_dryrun()
+
+        if reallocate_address:
+            address = self.ec2_backend.allocate_address(
+                domain, address=reallocate_address, tags=tags
+            )
+        else:
+            address = self.ec2_backend.allocate_address(domain, tags=tags)
+        template = self.response_template(ALLOCATE_ADDRESS_RESPONSE)
+        return template.render(address=address)
 
     def associate_address(self):
         instance = eni = None
@@ -38,35 +39,36 @@ class ElasticIPAddresses(EC2BaseResponse):
         if "AllowReassociation" in self.querystring:
             reassociate = self._get_param("AllowReassociation") == "true"
 
-        if self.is_not_dryrun("AssociateAddress"):
-            if instance or eni:
-                if "PublicIp" in self.querystring:
-                    eip = self.ec2_backend.associate_address(
-                        instance=instance,
-                        eni=eni,
-                        address=self._get_param("PublicIp"),
-                        reassociate=reassociate,
-                    )
-                elif "AllocationId" in self.querystring:
-                    eip = self.ec2_backend.associate_address(
-                        instance=instance,
-                        eni=eni,
-                        allocation_id=self._get_param("AllocationId"),
-                        reassociate=reassociate,
-                    )
-                else:
-                    self.ec2_backend.raise_error(
-                        "MissingParameter",
-                        "Invalid request, expect PublicIp/AllocationId parameter.",
-                    )
+        self.error_on_dryrun()
+
+        if instance or eni:
+            if "PublicIp" in self.querystring:
+                eip = self.ec2_backend.associate_address(
+                    instance=instance,
+                    eni=eni,
+                    address=self._get_param("PublicIp"),
+                    reassociate=reassociate,
+                )
+            elif "AllocationId" in self.querystring:
+                eip = self.ec2_backend.associate_address(
+                    instance=instance,
+                    eni=eni,
+                    allocation_id=self._get_param("AllocationId"),
+                    reassociate=reassociate,
+                )
             else:
                 self.ec2_backend.raise_error(
                     "MissingParameter",
-                    "Invalid request, expect either instance or ENI.",
+                    "Invalid request, expect PublicIp/AllocationId parameter.",
                 )
+        else:
+            self.ec2_backend.raise_error(
+                "MissingParameter",
+                "Invalid request, expect either instance or ENI.",
+            )
 
-            template = self.response_template(ASSOCIATE_ADDRESS_RESPONSE)
-            return template.render(address=eip)
+        template = self.response_template(ASSOCIATE_ADDRESS_RESPONSE)
+        return template.render(address=eip)
 
     def describe_addresses(self):
         self.error_on_dryrun()
@@ -80,38 +82,46 @@ class ElasticIPAddresses(EC2BaseResponse):
         return template.render(addresses=addresses)
 
     def disassociate_address(self):
-        if self.is_not_dryrun("DisAssociateAddress"):
-            if "PublicIp" in self.querystring:
-                self.ec2_backend.disassociate_address(
-                    address=self._get_param("PublicIp")
-                )
-            elif "AssociationId" in self.querystring:
-                self.ec2_backend.disassociate_address(
-                    association_id=self._get_param("AssociationId")
-                )
-            else:
-                self.ec2_backend.raise_error(
-                    "MissingParameter",
-                    "Invalid request, expect PublicIp/AssociationId parameter.",
-                )
+        if (
+            "PublicIp" not in self.querystring
+            and "AssociationId" not in self.querystring
+        ):
+            self.ec2_backend.raise_error(
+                "MissingParameter",
+                "Invalid request, expect PublicIp/AssociationId parameter.",
+            )
+
+        self.error_on_dryrun()
 
-            return self.response_template(DISASSOCIATE_ADDRESS_RESPONSE).render()
+        if "PublicIp" in self.querystring:
+            self.ec2_backend.disassociate_address(address=self._get_param("PublicIp"))
+        elif "AssociationId" in self.querystring:
+            self.ec2_backend.disassociate_address(
+                association_id=self._get_param("AssociationId")
+            )
+
+        return self.response_template(DISASSOCIATE_ADDRESS_RESPONSE).render()
 
     def release_address(self):
-        if self.is_not_dryrun("ReleaseAddress"):
-            if "PublicIp" in self.querystring:
-                self.ec2_backend.release_address(address=self._get_param("PublicIp"))
-            elif "AllocationId" in self.querystring:
-                self.ec2_backend.release_address(
-                    allocation_id=self._get_param("AllocationId")
-                )
-            else:
-                self.ec2_backend.raise_error(
-                    "MissingParameter",
-                    "Invalid request, expect PublicIp/AllocationId parameter.",
-                )
+        if (
+            "PublicIp" not in self.querystring
+            and "AllocationId" not in self.querystring
+        ):
+            self.ec2_backend.raise_error(
+                "MissingParameter",
+                "Invalid request, expect PublicIp/AllocationId parameter.",
+            )
+
+        self.error_on_dryrun()
+
+        if "PublicIp" in self.querystring:
+            self.ec2_backend.release_address(address=self._get_param("PublicIp"))
+        elif "AllocationId" in self.querystring:
+            self.ec2_backend.release_address(
+                allocation_id=self._get_param("AllocationId")
+            )
 
-            return self.response_template(RELEASE_ADDRESS_RESPONSE).render()
+        return self.response_template(RELEASE_ADDRESS_RESPONSE).render()
 
 
 ALLOCATE_ADDRESS_RESPONSE = """<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
diff --git a/moto/ec2/responses/elastic_network_interfaces.py b/moto/ec2/responses/elastic_network_interfaces.py
index c436f5305..567dd1cfd 100644
--- a/moto/ec2/responses/elastic_network_interfaces.py
+++ b/moto/ec2/responses/elastic_network_interfaces.py
@@ -17,96 +17,110 @@ class ElasticNetworkInterfaces(EC2BaseResponse):
         tags = self._get_multi_param("TagSpecification")
         tags = add_tag_specification(tags)
 
-        if self.is_not_dryrun("CreateNetworkInterface"):
-            eni = self.ec2_backend.create_network_interface(
-                subnet,
-                private_ip_address,
-                private_ip_addresses,
-                groups,
-                description,
-                tags,
-                secondary_ips_count=secondary_ips_count,
-                ipv6_addresses=ipv6_addresses,
-                ipv6_address_count=ipv6_address_count,
-            )
-            template = self.response_template(CREATE_NETWORK_INTERFACE_RESPONSE)
-            return template.render(eni=eni)
+        self.error_on_dryrun()
+
+        eni = self.ec2_backend.create_network_interface(
+            subnet,
+            private_ip_address,
+            private_ip_addresses,
+            groups,
+            description,
+            tags,
+            secondary_ips_count=secondary_ips_count,
+            ipv6_addresses=ipv6_addresses,
+            ipv6_address_count=ipv6_address_count,
+        )
+        template = self.response_template(CREATE_NETWORK_INTERFACE_RESPONSE)
+        return template.render(eni=eni)
 
     def delete_network_interface(self):
         eni_id = self._get_param("NetworkInterfaceId")
-        if self.is_not_dryrun("DeleteNetworkInterface"):
-            self.ec2_backend.delete_network_interface(eni_id)
-            template = self.response_template(DELETE_NETWORK_INTERFACE_RESPONSE)
-            return template.render()
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.delete_network_interface(eni_id)
+        template = self.response_template(DELETE_NETWORK_INTERFACE_RESPONSE)
+        return template.render()
 
     def describe_network_interface_attribute(self):
         eni_id = self._get_param("NetworkInterfaceId")
         attribute = self._get_param("Attribute")
-        if self.is_not_dryrun("DescribeNetworkInterfaceAttribute"):
-            eni = self.ec2_backend.get_all_network_interfaces([eni_id])[0]
-
-            if attribute == "description":
-                template = self.response_template(
-                    DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_DESCRIPTION
-                )
-            elif attribute == "groupSet":
-                template = self.response_template(
-                    DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_GROUPSET
-                )
-            elif attribute == "sourceDestCheck":
-                template = self.response_template(
-                    DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_SOURCEDESTCHECK
-                )
-            elif attribute == "attachment":
-                template = self.response_template(
-                    DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_ATTACHMENT
-                )
-            else:
-                raise InvalidParameterValueErrorUnknownAttribute(attribute)
+
+        self.error_on_dryrun()
+
+        eni = self.ec2_backend.get_all_network_interfaces([eni_id])[0]
+
+        if attribute == "description":
+            template = self.response_template(
+                DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_DESCRIPTION
+            )
+        elif attribute == "groupSet":
+            template = self.response_template(
+                DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_GROUPSET
+            )
+        elif attribute == "sourceDestCheck":
+            template = self.response_template(
+                DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_SOURCEDESTCHECK
+            )
+        elif attribute == "attachment":
+            template = self.response_template(
+                DESCRIBE_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE_ATTACHMENT
+            )
+        else:
+            raise InvalidParameterValueErrorUnknownAttribute(attribute)
         return template.render(eni=eni)
 
     def describe_network_interfaces(self):
         eni_ids = self._get_multi_param("NetworkInterfaceId")
         filters = self._filters_from_querystring()
-        if self.is_not_dryrun("DescribeNetworkInterfaces"):
-            enis = self.ec2_backend.get_all_network_interfaces(eni_ids, filters)
-            template = self.response_template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
-            return template.render(enis=enis)
+
+        self.error_on_dryrun()
+
+        enis = self.ec2_backend.get_all_network_interfaces(eni_ids, filters)
+        template = self.response_template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
+        return template.render(enis=enis)
 
     def attach_network_interface(self):
         eni_id = self._get_param("NetworkInterfaceId")
         instance_id = self._get_param("InstanceId")
         device_index = self._get_param("DeviceIndex")
-        if self.is_not_dryrun("AttachNetworkInterface"):
-            attachment_id = self.ec2_backend.attach_network_interface(
-                eni_id, instance_id, device_index
-            )
-            template = self.response_template(ATTACH_NETWORK_INTERFACE_RESPONSE)
-            return template.render(attachment_id=attachment_id)
+
+        self.error_on_dryrun()
+
+        attachment_id = self.ec2_backend.attach_network_interface(
+            eni_id, instance_id, device_index
+        )
+        template = self.response_template(ATTACH_NETWORK_INTERFACE_RESPONSE)
+        return template.render(attachment_id=attachment_id)
 
     def detach_network_interface(self):
         attachment_id = self._get_param("AttachmentId")
-        if self.is_not_dryrun("DetachNetworkInterface"):
-            self.ec2_backend.detach_network_interface(attachment_id)
-            template = self.response_template(DETACH_NETWORK_INTERFACE_RESPONSE)
-            return template.render()
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.detach_network_interface(attachment_id)
+        template = self.response_template(DETACH_NETWORK_INTERFACE_RESPONSE)
+        return template.render()
 
     def modify_network_interface_attribute(self):
         eni_id = self._get_param("NetworkInterfaceId")
         group_ids = self._get_multi_param("SecurityGroupId")
         source_dest_check = get_attribute_value("SourceDestCheck", self.querystring)
         description = get_attribute_value("Description", self.querystring)
-        if self.is_not_dryrun("ModifyNetworkInterface"):
-            self.ec2_backend.modify_network_interface_attribute(
-                eni_id, group_ids, source_dest_check, description
-            )
-            return MODIFY_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.modify_network_interface_attribute(
+            eni_id, group_ids, source_dest_check, description
+        )
+        return MODIFY_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE
 
     def reset_network_interface_attribute(self):
-        if self.is_not_dryrun("ResetNetworkInterface"):
-            raise NotImplementedError(
-                "ElasticNetworkInterfaces(AmazonVPC).reset_network_interface_attribute is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "ElasticNetworkInterfaces(AmazonVPC).reset_network_interface_attribute is not yet implemented"
+        )
 
     def assign_private_ip_addresses(self):
         eni_id = self._get_param("NetworkInterfaceId")
diff --git a/moto/ec2/responses/flow_logs.py b/moto/ec2/responses/flow_logs.py
index 58445ac76..c827d4faa 100644
--- a/moto/ec2/responses/flow_logs.py
+++ b/moto/ec2/responses/flow_logs.py
@@ -17,37 +17,42 @@ class FlowLogs(EC2BaseResponse):
 
         tags = self._parse_tag_specification()
         tags = tags.get("vpc-flow-log", {})
-        if self.is_not_dryrun("CreateFlowLogs"):
-            flow_logs, errors = self.ec2_backend.create_flow_logs(
-                resource_type=resource_type,
-                resource_ids=resource_ids,
-                traffic_type=traffic_type,
-                deliver_logs_permission_arn=deliver_logs_permission_arn,
-                log_destination_type=log_destination_type,
-                log_destination=log_destination,
-                log_group_name=log_group_name,
-                log_format=log_format,
-                max_aggregation_interval=max_aggregation_interval,
-            )
-            for fl in flow_logs:
-                fl.add_tags(tags)
-            template = self.response_template(CREATE_FLOW_LOGS_RESPONSE)
-            return template.render(flow_logs=flow_logs, errors=errors)
+
+        self.error_on_dryrun()
+
+        flow_logs, errors = self.ec2_backend.create_flow_logs(
+            resource_type=resource_type,
+            resource_ids=resource_ids,
+            traffic_type=traffic_type,
+            deliver_logs_permission_arn=deliver_logs_permission_arn,
+            log_destination_type=log_destination_type,
+            log_destination=log_destination,
+            log_group_name=log_group_name,
+            log_format=log_format,
+            max_aggregation_interval=max_aggregation_interval,
+        )
+        for fl in flow_logs:
+            fl.add_tags(tags)
+        template = self.response_template(CREATE_FLOW_LOGS_RESPONSE)
+        return template.render(flow_logs=flow_logs, errors=errors)
 
     def describe_flow_logs(self):
         flow_log_ids = self._get_multi_param("FlowLogId")
         filters = self._filters_from_querystring()
         flow_logs = self.ec2_backend.describe_flow_logs(flow_log_ids, filters)
-        if self.is_not_dryrun("DescribeFlowLogs"):
-            template = self.response_template(DESCRIBE_FLOW_LOGS_RESPONSE)
-            return template.render(flow_logs=flow_logs)
+
+        self.error_on_dryrun()
+
+        template = self.response_template(DESCRIBE_FLOW_LOGS_RESPONSE)
+        return template.render(flow_logs=flow_logs)
 
     def delete_flow_logs(self):
         flow_log_ids = self._get_multi_param("FlowLogId")
+
+        self.error_on_dryrun()
+
         self.ec2_backend.delete_flow_logs(flow_log_ids)
-        if self.is_not_dryrun("DeleteFlowLogs"):
-            template = self.response_template(DELETE_FLOW_LOGS_RESPONSE)
-            return template.render()
+        return self.response_template(DELETE_FLOW_LOGS_RESPONSE).render()
 
 
 CREATE_FLOW_LOGS_RESPONSE = """
diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py
index fa587a111..380be566b 100644
--- a/moto/ec2/responses/instances.py
+++ b/moto/ec2/responses/instances.py
@@ -87,62 +87,70 @@ class InstanceResponse(EC2BaseResponse):
         if mappings:
             kwargs["block_device_mappings"] = mappings
 
-        if self.is_not_dryrun("RunInstance"):
-            new_reservation = self.ec2_backend.add_instances(
-                image_id, min_count, user_data, security_group_names, **kwargs
+        self.error_on_dryrun()
+
+        new_reservation = self.ec2_backend.add_instances(
+            image_id, min_count, user_data, security_group_names, **kwargs
+        )
+        if kwargs.get("iam_instance_profile_name"):
+            self.ec2_backend.associate_iam_instance_profile(
+                instance_id=new_reservation.instances[0].id,
+                iam_instance_profile_name=kwargs.get("iam_instance_profile_name"),
             )
-            if kwargs.get("iam_instance_profile_name"):
-                self.ec2_backend.associate_iam_instance_profile(
-                    instance_id=new_reservation.instances[0].id,
-                    iam_instance_profile_name=kwargs.get("iam_instance_profile_name"),
-                )
-            if kwargs.get("iam_instance_profile_arn"):
-                self.ec2_backend.associate_iam_instance_profile(
-                    instance_id=new_reservation.instances[0].id,
-                    iam_instance_profile_arn=kwargs.get("iam_instance_profile_arn"),
-                )
-
-            template = self.response_template(EC2_RUN_INSTANCES)
-            return template.render(
-                account_id=self.current_account, reservation=new_reservation
+        if kwargs.get("iam_instance_profile_arn"):
+            self.ec2_backend.associate_iam_instance_profile(
+                instance_id=new_reservation.instances[0].id,
+                iam_instance_profile_arn=kwargs.get("iam_instance_profile_arn"),
             )
 
+        template = self.response_template(EC2_RUN_INSTANCES)
+        return template.render(
+            account_id=self.current_account, reservation=new_reservation
+        )
+
     def terminate_instances(self):
         instance_ids = self._get_multi_param("InstanceId")
-        if self.is_not_dryrun("TerminateInstance"):
-            instances = self.ec2_backend.terminate_instances(instance_ids)
-            from moto.autoscaling import autoscaling_backends
-            from moto.elbv2 import elbv2_backends
-
-            autoscaling_backends[self.current_account][
-                self.region
-            ].notify_terminate_instances(instance_ids)
-            elbv2_backends[self.current_account][
-                self.region
-            ].notify_terminate_instances(instance_ids)
-            template = self.response_template(EC2_TERMINATE_INSTANCES)
-            return template.render(instances=instances)
+
+        self.error_on_dryrun()
+
+        instances = self.ec2_backend.terminate_instances(instance_ids)
+        from moto.autoscaling import autoscaling_backends
+        from moto.elbv2 import elbv2_backends
+
+        autoscaling_backends[self.current_account][
+            self.region
+        ].notify_terminate_instances(instance_ids)
+        elbv2_backends[self.current_account][self.region].notify_terminate_instances(
+            instance_ids
+        )
+        template = self.response_template(EC2_TERMINATE_INSTANCES)
+        return template.render(instances=instances)
 
     def reboot_instances(self):
         instance_ids = self._get_multi_param("InstanceId")
-        if self.is_not_dryrun("RebootInstance"):
-            instances = self.ec2_backend.reboot_instances(instance_ids)
-            template = self.response_template(EC2_REBOOT_INSTANCES)
-            return template.render(instances=instances)
+
+        self.error_on_dryrun()
+
+        instances = self.ec2_backend.reboot_instances(instance_ids)
+        template = self.response_template(EC2_REBOOT_INSTANCES)
+        return template.render(instances=instances)
 
     def stop_instances(self):
         instance_ids = self._get_multi_param("InstanceId")
-        if self.is_not_dryrun("StopInstance"):
-            instances = self.ec2_backend.stop_instances(instance_ids)
-            template = self.response_template(EC2_STOP_INSTANCES)
-            return template.render(instances=instances)
+
+        self.error_on_dryrun()
+
+        instances = self.ec2_backend.stop_instances(instance_ids)
+        template = self.response_template(EC2_STOP_INSTANCES)
+        return template.render(instances=instances)
 
     def start_instances(self):
         instance_ids = self._get_multi_param("InstanceId")
-        if self.is_not_dryrun("StartInstance"):
-            instances = self.ec2_backend.start_instances(instance_ids)
-            template = self.response_template(EC2_START_INSTANCES)
-            return template.render(instances=instances)
+        self.error_on_dryrun()
+
+        instances = self.ec2_backend.start_instances(instance_ids)
+        template = self.response_template(EC2_START_INSTANCES)
+        return template.render(instances=instances)
 
     def _get_list_of_dict_params(self, param_prefix, _dct):
         """
@@ -268,9 +276,10 @@ class InstanceResponse(EC2BaseResponse):
             instance_id = self._get_param("InstanceId")
             instance = self.ec2_backend.get_instance(instance_id)
 
-            if self.is_not_dryrun("ModifyInstanceAttribute"):
-                block_device_type = instance.block_device_mapping[device_name_value]
-                block_device_type.delete_on_termination = del_on_term_value
+            self.error_on_dryrun()
+
+            block_device_type = instance.block_device_mapping[device_name_value]
+            block_device_type.delete_on_termination = del_on_term_value
 
             # +1 for the next device
             mapping_counter += 1
@@ -288,14 +297,15 @@ class InstanceResponse(EC2BaseResponse):
         if not attribute_key:
             return
 
-        if self.is_not_dryrun("Modify" + attribute_key.split(".")[0]):
-            value = self.querystring.get(attribute_key)[0]
-            normalized_attribute = camelcase_to_underscores(attribute_key.split(".")[0])
-            instance_id = self._get_param("InstanceId")
-            self.ec2_backend.modify_instance_attribute(
-                instance_id, normalized_attribute, value
-            )
-            return EC2_MODIFY_INSTANCE_ATTRIBUTE
+        self.error_on_dryrun()
+
+        value = self.querystring.get(attribute_key)[0]
+        normalized_attribute = camelcase_to_underscores(attribute_key.split(".")[0])
+        instance_id = self._get_param("InstanceId")
+        self.ec2_backend.modify_instance_attribute(
+            instance_id, normalized_attribute, value
+        )
+        return EC2_MODIFY_INSTANCE_ATTRIBUTE
 
     def _attribute_value_handler(self):
         attribute_key = self._get_param("Attribute")
@@ -303,14 +313,15 @@ class InstanceResponse(EC2BaseResponse):
         if attribute_key is None:
             return
 
-        if self.is_not_dryrun("ModifyInstanceAttribute"):
-            value = self._get_param("Value")
-            normalized_attribute = camelcase_to_underscores(attribute_key)
-            instance_id = self._get_param("InstanceId")
-            self.ec2_backend.modify_instance_attribute(
-                instance_id, normalized_attribute, value
-            )
-            return EC2_MODIFY_INSTANCE_ATTRIBUTE
+        self.error_on_dryrun()
+
+        value = self._get_param("Value")
+        normalized_attribute = camelcase_to_underscores(attribute_key)
+        instance_id = self._get_param("InstanceId")
+        self.ec2_backend.modify_instance_attribute(
+            instance_id, normalized_attribute, value
+        )
+        return EC2_MODIFY_INSTANCE_ATTRIBUTE
 
     def _security_grp_instance_attribute_handler(self):
         new_security_grp_list = []
@@ -319,11 +330,12 @@ class InstanceResponse(EC2BaseResponse):
                 new_security_grp_list.append(self.querystring.get(key)[0])
 
         instance_id = self._get_param("InstanceId")
-        if self.is_not_dryrun("ModifyInstanceSecurityGroups"):
-            self.ec2_backend.modify_instance_security_groups(
-                instance_id, new_security_grp_list
-            )
-            return EC2_MODIFY_INSTANCE_ATTRIBUTE
+        self.error_on_dryrun()
+
+        self.ec2_backend.modify_instance_security_groups(
+            instance_id, new_security_grp_list
+        )
+        return EC2_MODIFY_INSTANCE_ATTRIBUTE
 
     def _parse_block_device_mapping(self):
         device_mappings = self._get_list_prefix("BlockDeviceMapping")
diff --git a/moto/ec2/responses/internet_gateways.py b/moto/ec2/responses/internet_gateways.py
index aa773ec4f..7428f938a 100644
--- a/moto/ec2/responses/internet_gateways.py
+++ b/moto/ec2/responses/internet_gateways.py
@@ -5,28 +5,29 @@ class InternetGateways(EC2BaseResponse):
     def attach_internet_gateway(self):
         igw_id = self._get_param("InternetGatewayId")
         vpc_id = self._get_param("VpcId")
-        if self.is_not_dryrun("AttachInternetGateway"):
-            self.ec2_backend.attach_internet_gateway(igw_id, vpc_id)
-            template = self.response_template(ATTACH_INTERNET_GATEWAY_RESPONSE)
-            return template.render()
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.attach_internet_gateway(igw_id, vpc_id)
+        return self.response_template(ATTACH_INTERNET_GATEWAY_RESPONSE).render()
 
     def create_internet_gateway(self):
-        if self.is_not_dryrun("CreateInternetGateway"):
-            tags = self._get_multi_param(
-                "TagSpecification", skip_result_conversion=True
-            )
-            if tags:
-                tags = tags[0].get("Tag") or []
-            igw = self.ec2_backend.create_internet_gateway(tags=tags)
-            template = self.response_template(CREATE_INTERNET_GATEWAY_RESPONSE)
-            return template.render(internet_gateway=igw)
+        self.error_on_dryrun()
+
+        tags = self._get_multi_param("TagSpecification", skip_result_conversion=True)
+        if tags:
+            tags = tags[0].get("Tag") or []
+        igw = self.ec2_backend.create_internet_gateway(tags=tags)
+        return self.response_template(CREATE_INTERNET_GATEWAY_RESPONSE).render(
+            internet_gateway=igw
+        )
 
     def delete_internet_gateway(self):
         igw_id = self._get_param("InternetGatewayId")
-        if self.is_not_dryrun("DeleteInternetGateway"):
-            self.ec2_backend.delete_internet_gateway(igw_id)
-            template = self.response_template(DELETE_INTERNET_GATEWAY_RESPONSE)
-            return template.render()
+        self.error_on_dryrun()
+
+        self.ec2_backend.delete_internet_gateway(igw_id)
+        return self.response_template(DELETE_INTERNET_GATEWAY_RESPONSE).render()
 
     def describe_internet_gateways(self):
         filter_dict = self._filters_from_querystring()
@@ -46,10 +47,10 @@ class InternetGateways(EC2BaseResponse):
         # raise else DependencyViolationError()
         igw_id = self._get_param("InternetGatewayId")
         vpc_id = self._get_param("VpcId")
-        if self.is_not_dryrun("DetachInternetGateway"):
-            self.ec2_backend.detach_internet_gateway(igw_id, vpc_id)
-            template = self.response_template(DETACH_INTERNET_GATEWAY_RESPONSE)
-            return template.render()
+        self.error_on_dryrun()
+
+        self.ec2_backend.detach_internet_gateway(igw_id, vpc_id)
+        return self.response_template(DETACH_INTERNET_GATEWAY_RESPONSE).render()
 
 
 ATTACH_INTERNET_GATEWAY_RESPONSE = """<AttachInternetGatewayResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
diff --git a/moto/ec2/responses/ip_addresses.py b/moto/ec2/responses/ip_addresses.py
index 258b332d7..bd1a6b066 100644
--- a/moto/ec2/responses/ip_addresses.py
+++ b/moto/ec2/responses/ip_addresses.py
@@ -3,13 +3,15 @@ from moto.core.responses import BaseResponse
 
 class IPAddresses(BaseResponse):
     def assign_private_ip_addresses(self):
-        if self.is_not_dryrun("AssignPrivateIPAddress"):
-            raise NotImplementedError(
-                "IPAddresses.assign_private_ip_addresses is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "IPAddresses.assign_private_ip_addresses is not yet implemented"
+        )
 
     def unassign_private_ip_addresses(self):
-        if self.is_not_dryrun("UnAssignPrivateIPAddress"):
-            raise NotImplementedError(
-                "IPAddresses.unassign_private_ip_addresses is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "IPAddresses.unassign_private_ip_addresses is not yet implemented"
+        )
diff --git a/moto/ec2/responses/key_pairs.py b/moto/ec2/responses/key_pairs.py
index bb5a57cfd..c922b18f7 100644
--- a/moto/ec2/responses/key_pairs.py
+++ b/moto/ec2/responses/key_pairs.py
@@ -4,16 +4,17 @@ from ._base_response import EC2BaseResponse
 class KeyPairs(EC2BaseResponse):
     def create_key_pair(self):
         name = self._get_param("KeyName")
-        if self.is_not_dryrun("CreateKeyPair"):
-            keypair = self.ec2_backend.create_key_pair(name)
-            template = self.response_template(CREATE_KEY_PAIR_RESPONSE)
-            return template.render(keypair=keypair)
+        self.error_on_dryrun()
+
+        keypair = self.ec2_backend.create_key_pair(name)
+        return self.response_template(CREATE_KEY_PAIR_RESPONSE).render(keypair=keypair)
 
     def delete_key_pair(self):
         name = self._get_param("KeyName")
-        if self.is_not_dryrun("DeleteKeyPair"):
-            self.ec2_backend.delete_key_pair(name)
-            return self.response_template(DELETE_KEY_PAIR_RESPONSE).render()
+        self.error_on_dryrun()
+
+        self.ec2_backend.delete_key_pair(name)
+        return self.response_template(DELETE_KEY_PAIR_RESPONSE).render()
 
     def describe_key_pairs(self):
         names = self._get_multi_param("KeyName")
@@ -25,10 +26,10 @@ class KeyPairs(EC2BaseResponse):
     def import_key_pair(self):
         name = self._get_param("KeyName")
         material = self._get_param("PublicKeyMaterial")
-        if self.is_not_dryrun("ImportKeyPair"):
-            keypair = self.ec2_backend.import_key_pair(name, material)
-            template = self.response_template(IMPORT_KEYPAIR_RESPONSE)
-            return template.render(keypair=keypair)
+        self.error_on_dryrun()
+
+        keypair = self.ec2_backend.import_key_pair(name, material)
+        return self.response_template(IMPORT_KEYPAIR_RESPONSE).render(keypair=keypair)
 
 
 DESCRIBE_KEY_PAIRS_RESPONSE = """<DescribeKeyPairsResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py
index 595f37f2f..bb539c106 100644
--- a/moto/ec2/responses/launch_templates.py
+++ b/moto/ec2/responses/launch_templates.py
@@ -48,95 +48,53 @@ def pretty_xml(tree):
     return parsed.toprettyxml(indent="    ")
 
 
-def parse_object(raw_data):
-    out_data = {}
-    for key, value in raw_data.items():
-        key_fix_splits = key.split("_")
-        key_len = len(key_fix_splits)
-
-        new_key = ""
-        for i in range(0, key_len):
-            new_key += key_fix_splits[i][0].upper() + key_fix_splits[i][1:]
-
-        data = out_data
-        splits = new_key.split(".")
-        for split in splits[:-1]:
-            if split not in data:
-                data[split] = {}
-            data = data[split]
-
-        data[splits[-1]] = value
-
-    out_data = parse_lists(out_data)
-    return out_data
-
-
-def parse_lists(data):
-    for key, value in data.items():
-        if isinstance(value, dict):
-            keys = data[key].keys()
-            is_list = all(map(lambda k: k.isnumeric(), keys))
-
-            if is_list:
-                new_value = []
-                keys = sorted(list(keys))
-                for k in keys:
-                    lvalue = value[k]
-                    if isinstance(lvalue, dict):
-                        lvalue = parse_lists(lvalue)
-                    new_value.append(lvalue)
-                data[key] = new_value
-    return data
-
-
 class LaunchTemplates(EC2BaseResponse):
     def create_launch_template(self):
         name = self._get_param("LaunchTemplateName")
         version_description = self._get_param("VersionDescription")
         tag_spec = self._parse_tag_specification()
 
-        raw_template_data = self._get_dict_param("LaunchTemplateData.")
-        parsed_template_data = parse_object(raw_template_data)
-
-        if self.is_not_dryrun("CreateLaunchTemplate"):
-            if tag_spec:
-                if "TagSpecifications" not in parsed_template_data:
-                    parsed_template_data["TagSpecifications"] = []
-                converted_tag_spec = []
-                for resource_type, tags in tag_spec.items():
-                    converted_tag_spec.append(
-                        {
-                            "ResourceType": resource_type,
-                            "Tags": [
-                                {"Key": key, "Value": value}
-                                for key, value in tags.items()
-                            ],
-                        }
-                    )
-
-                parsed_template_data["TagSpecifications"].extend(converted_tag_spec)
-
-            template = self.ec2_backend.create_launch_template(
-                name, version_description, parsed_template_data, tag_spec
-            )
-            version = template.default_version()
+        parsed_template_data = self._get_multi_param_dict("LaunchTemplateData")
 
-            tree = xml_root("CreateLaunchTemplateResponse")
-            xml_serialize(
-                tree,
-                "launchTemplate",
-                {
-                    "createTime": version.create_time,
-                    "createdBy": f"arn:aws:iam::{self.current_account}:root",
-                    "defaultVersionNumber": template.default_version_number,
-                    "latestVersionNumber": version.number,
-                    "launchTemplateId": template.id,
-                    "launchTemplateName": template.name,
-                    "tags": template.tags,
-                },
-            )
+        self.error_on_dryrun()
+
+        if tag_spec:
+            if "TagSpecifications" not in parsed_template_data:
+                parsed_template_data["TagSpecifications"] = []
+            converted_tag_spec = []
+            for resource_type, tags in tag_spec.items():
+                converted_tag_spec.append(
+                    {
+                        "ResourceType": resource_type,
+                        "Tags": [
+                            {"Key": key, "Value": value} for key, value in tags.items()
+                        ],
+                    }
+                )
+
+            parsed_template_data["TagSpecifications"].extend(converted_tag_spec)
+
+        template = self.ec2_backend.create_launch_template(
+            name, version_description, parsed_template_data, tag_spec
+        )
+        version = template.default_version()
+
+        tree = xml_root("CreateLaunchTemplateResponse")
+        xml_serialize(
+            tree,
+            "launchTemplate",
+            {
+                "createTime": version.create_time,
+                "createdBy": f"arn:aws:iam::{self.current_account}:root",
+                "defaultVersionNumber": template.default_version_number,
+                "latestVersionNumber": version.number,
+                "launchTemplateId": template.id,
+                "launchTemplateName": template.name,
+                "tags": template.tags,
+            },
+        )
 
-            return pretty_xml(tree)
+        return pretty_xml(tree)
 
     def create_launch_template_version(self):
         name = self._get_param("LaunchTemplateName")
@@ -148,51 +106,49 @@ class LaunchTemplates(EC2BaseResponse):
 
         version_description = self._get_param("VersionDescription")
 
-        raw_template_data = self._get_dict_param("LaunchTemplateData.")
-        template_data = parse_object(raw_template_data)
-
-        if self.is_not_dryrun("CreateLaunchTemplate"):
-            version = template.create_version(template_data, version_description)
-
-            tree = xml_root("CreateLaunchTemplateVersionResponse")
-            xml_serialize(
-                tree,
-                "launchTemplateVersion",
-                {
-                    "createTime": version.create_time,
-                    "createdBy": f"arn:aws:iam::{self.current_account}:root",
-                    "defaultVersion": template.is_default(version),
-                    "launchTemplateData": version.data,
-                    "launchTemplateId": template.id,
-                    "launchTemplateName": template.name,
-                    "versionDescription": version.description,
-                    "versionNumber": version.number,
-                },
-            )
-            return pretty_xml(tree)
+        template_data = self._get_multi_param_dict("LaunchTemplateData")
+
+        self.error_on_dryrun()
+
+        version = template.create_version(template_data, version_description)
+
+        tree = xml_root("CreateLaunchTemplateVersionResponse")
+        xml_serialize(
+            tree,
+            "launchTemplateVersion",
+            {
+                "createTime": version.create_time,
+                "createdBy": f"arn:aws:iam::{self.current_account}:root",
+                "defaultVersion": template.is_default(version),
+                "launchTemplateData": version.data,
+                "launchTemplateId": template.id,
+                "launchTemplateName": template.name,
+                "versionDescription": version.description,
+                "versionNumber": version.number,
+            },
+        )
+        return pretty_xml(tree)
 
     def delete_launch_template(self):
         name = self._get_param("LaunchTemplateName")
         tid = self._get_param("LaunchTemplateId")
 
-        if self.is_not_dryrun("DeleteLaunchTemplate"):
-            template = self.ec2_backend.delete_launch_template(name, tid)
+        self.error_on_dryrun()
 
-            tree = xml_root("DeleteLaunchTemplatesResponse")
-            xml_serialize(
-                tree,
-                "launchTemplate",
-                {
-                    "defaultVersionNumber": template.default_version_number,
-                    "launchTemplateId": template.id,
-                    "launchTemplateName": template.name,
-                },
-            )
+        template = self.ec2_backend.delete_launch_template(name, tid)
 
-            return pretty_xml(tree)
+        tree = xml_root("DeleteLaunchTemplatesResponse")
+        xml_serialize(
+            tree,
+            "launchTemplate",
+            {
+                "defaultVersionNumber": template.default_version_number,
+                "launchTemplateId": template.id,
+                "launchTemplateName": template.name,
+            },
+        )
 
-    # def delete_launch_template_versions(self):
-    #     pass
+        return pretty_xml(tree)
 
     def describe_launch_template_versions(self):
         name = self._get_param("LaunchTemplateName")
@@ -213,57 +169,58 @@ class LaunchTemplates(EC2BaseResponse):
                 "all filters", "DescribeLaunchTemplateVersions"
             )
 
-        if self.is_not_dryrun("DescribeLaunchTemplateVersions"):
-            tree = ElementTree.Element(
-                "DescribeLaunchTemplateVersionsResponse",
-                {"xmlns": "http://ec2.amazonaws.com/doc/2016-11-15/"},
-            )
-            request_id = ElementTree.SubElement(tree, "requestId")
-            request_id.text = "65cadec1-b364-4354-8ca8-4176dexample"
-
-            versions_node = ElementTree.SubElement(tree, "launchTemplateVersionSet")
-
-            ret_versions = []
-            if versions:
-                for v in versions:
-                    if str(v).lower() == "$latest" or "$default":
-                        tv = template.get_version(v)
-                    else:
-                        tv = template.get_version(int(v))
-                    ret_versions.append(tv)
-            elif min_version:
-                if max_version:
-                    vMax = max_version
-                else:
-                    vMax = min_version + max_results
+        self.error_on_dryrun()
+
+        tree = ElementTree.Element(
+            "DescribeLaunchTemplateVersionsResponse",
+            {"xmlns": "http://ec2.amazonaws.com/doc/2016-11-15/"},
+        )
+        request_id = ElementTree.SubElement(tree, "requestId")
+        request_id.text = "65cadec1-b364-4354-8ca8-4176dexample"
 
-                vMin = min_version - 1
-                ret_versions = template.versions[vMin:vMax]
-            elif max_version:
+        versions_node = ElementTree.SubElement(tree, "launchTemplateVersionSet")
+
+        ret_versions = []
+        if versions:
+            for v in versions:
+                if str(v).lower() == "$latest" or "$default":
+                    tv = template.get_version(v)
+                else:
+                    tv = template.get_version(int(v))
+                ret_versions.append(tv)
+        elif min_version:
+            if max_version:
                 vMax = max_version
-                ret_versions = template.versions[:vMax]
             else:
-                ret_versions = template.versions
+                vMax = min_version + max_results
 
-            ret_versions = ret_versions[:max_results]
+            vMin = min_version - 1
+            ret_versions = template.versions[vMin:vMax]
+        elif max_version:
+            vMax = max_version
+            ret_versions = template.versions[:vMax]
+        else:
+            ret_versions = template.versions
 
-            for version in ret_versions:
-                xml_serialize(
-                    versions_node,
-                    "item",
-                    {
-                        "createTime": version.create_time,
-                        "createdBy": f"arn:aws:iam::{self.current_account}:root",
-                        "defaultVersion": True,
-                        "launchTemplateData": version.data,
-                        "launchTemplateId": template.id,
-                        "launchTemplateName": template.name,
-                        "versionDescription": version.description,
-                        "versionNumber": version.number,
-                    },
-                )
+        ret_versions = ret_versions[:max_results]
 
-            return pretty_xml(tree)
+        for version in ret_versions:
+            xml_serialize(
+                versions_node,
+                "item",
+                {
+                    "createTime": version.create_time,
+                    "createdBy": f"arn:aws:iam::{self.current_account}:root",
+                    "defaultVersion": True,
+                    "launchTemplateData": version.data,
+                    "launchTemplateId": template.id,
+                    "launchTemplateName": template.name,
+                    "versionDescription": version.description,
+                    "versionNumber": version.number,
+                },
+            )
+
+        return pretty_xml(tree)
 
     def describe_launch_templates(self):
         max_results = self._get_int_param("MaxResults", 15)
@@ -271,34 +228,32 @@ class LaunchTemplates(EC2BaseResponse):
         template_ids = self._get_multi_param("LaunchTemplateId")
         filters = self._filters_from_querystring()
 
-        if self.is_not_dryrun("DescribeLaunchTemplates"):
-            tree = ElementTree.Element("DescribeLaunchTemplatesResponse")
-            templates_node = ElementTree.SubElement(tree, "launchTemplates")
+        self.error_on_dryrun()
 
-            templates = self.ec2_backend.describe_launch_templates(
-                template_names=template_names,
-                template_ids=template_ids,
-                filters=filters,
-            )
+        tree = ElementTree.Element("DescribeLaunchTemplatesResponse")
+        templates_node = ElementTree.SubElement(tree, "launchTemplates")
 
-            templates = templates[:max_results]
+        templates = self.ec2_backend.describe_launch_templates(
+            template_names=template_names,
+            template_ids=template_ids,
+            filters=filters,
+        )
 
-            for template in templates:
-                xml_serialize(
-                    templates_node,
-                    "item",
-                    {
-                        "createTime": template.create_time,
-                        "createdBy": f"arn:aws:iam::{self.current_account}:root",
-                        "defaultVersionNumber": template.default_version_number,
-                        "latestVersionNumber": template.latest_version_number,
-                        "launchTemplateId": template.id,
-                        "launchTemplateName": template.name,
-                        "tags": template.tags,
-                    },
-                )
+        templates = templates[:max_results]
 
-            return pretty_xml(tree)
+        for template in templates:
+            xml_serialize(
+                templates_node,
+                "item",
+                {
+                    "createTime": template.create_time,
+                    "createdBy": f"arn:aws:iam::{self.current_account}:root",
+                    "defaultVersionNumber": template.default_version_number,
+                    "latestVersionNumber": template.latest_version_number,
+                    "launchTemplateId": template.id,
+                    "launchTemplateName": template.name,
+                    "tags": template.tags,
+                },
+            )
 
-    # def modify_launch_template(self):
-    #     pass
+        return pretty_xml(tree)
diff --git a/moto/ec2/responses/monitoring.py b/moto/ec2/responses/monitoring.py
index ce8fc7630..b755650ea 100644
--- a/moto/ec2/responses/monitoring.py
+++ b/moto/ec2/responses/monitoring.py
@@ -3,13 +3,13 @@ from moto.core.responses import BaseResponse
 
 class Monitoring(BaseResponse):
     def monitor_instances(self):
-        if self.is_not_dryrun("MonitorInstances"):
-            raise NotImplementedError(
-                "Monitoring.monitor_instances is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError("Monitoring.monitor_instances is not yet implemented")
 
     def unmonitor_instances(self):
-        if self.is_not_dryrun("UnMonitorInstances"):
-            raise NotImplementedError(
-                "Monitoring.unmonitor_instances is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "Monitoring.unmonitor_instances is not yet implemented"
+        )
diff --git a/moto/ec2/responses/placement_groups.py b/moto/ec2/responses/placement_groups.py
index f7e6bb540..4a72cc487 100644
--- a/moto/ec2/responses/placement_groups.py
+++ b/moto/ec2/responses/placement_groups.py
@@ -3,16 +3,18 @@ from moto.core.responses import BaseResponse
 
 class PlacementGroups(BaseResponse):
     def create_placement_group(self):
-        if self.is_not_dryrun("CreatePlacementGroup"):
-            raise NotImplementedError(
-                "PlacementGroups.create_placement_group is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "PlacementGroups.create_placement_group is not yet implemented"
+        )
 
     def delete_placement_group(self):
-        if self.is_not_dryrun("DeletePlacementGroup"):
-            raise NotImplementedError(
-                "PlacementGroups.delete_placement_group is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "PlacementGroups.delete_placement_group is not yet implemented"
+        )
 
     def describe_placement_groups(self):
         raise NotImplementedError(
diff --git a/moto/ec2/responses/reserved_instances.py b/moto/ec2/responses/reserved_instances.py
index 78b4d7f2c..e32eb82ac 100644
--- a/moto/ec2/responses/reserved_instances.py
+++ b/moto/ec2/responses/reserved_instances.py
@@ -3,16 +3,18 @@ from moto.core.responses import BaseResponse
 
 class ReservedInstances(BaseResponse):
     def cancel_reserved_instances_listing(self):
-        if self.is_not_dryrun("CancelReservedInstances"):
-            raise NotImplementedError(
-                "ReservedInstances.cancel_reserved_instances_listing is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "ReservedInstances.cancel_reserved_instances_listing is not yet implemented"
+        )
 
     def create_reserved_instances_listing(self):
-        if self.is_not_dryrun("CreateReservedInstances"):
-            raise NotImplementedError(
-                "ReservedInstances.create_reserved_instances_listing is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "ReservedInstances.create_reserved_instances_listing is not yet implemented"
+        )
 
     def describe_reserved_instances(self):
         raise NotImplementedError(
@@ -30,7 +32,8 @@ class ReservedInstances(BaseResponse):
         )
 
     def purchase_reserved_instances_offering(self):
-        if self.is_not_dryrun("PurchaseReservedInstances"):
-            raise NotImplementedError(
-                "ReservedInstances.purchase_reserved_instances_offering is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "ReservedInstances.purchase_reserved_instances_offering is not yet implemented"
+        )
diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py
index 5b60660c5..30bb039dd 100644
--- a/moto/ec2/responses/security_groups.py
+++ b/moto/ec2/responses/security_groups.py
@@ -128,20 +128,22 @@ class SecurityGroups(EC2BaseResponse):
             )
 
     def authorize_security_group_egress(self):
-        if self.is_not_dryrun("GrantSecurityGroupEgress"):
-            for args in self._process_rules_from_querystring():
-                rule, group = self.ec2_backend.authorize_security_group_egress(*args)
-            self.ec2_backend.sg_old_egress_ruls[group.id] = group.egress_rules.copy()
-            template = self.response_template(AUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE)
-            return template.render(rule=rule, group=group)
+        self.error_on_dryrun()
+
+        for args in self._process_rules_from_querystring():
+            rule, group = self.ec2_backend.authorize_security_group_egress(*args)
+        self.ec2_backend.sg_old_egress_ruls[group.id] = group.egress_rules.copy()
+        template = self.response_template(AUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE)
+        return template.render(rule=rule, group=group)
 
     def authorize_security_group_ingress(self):
-        if self.is_not_dryrun("GrantSecurityGroupIngress"):
-            for args in self._process_rules_from_querystring():
-                rule, group = self.ec2_backend.authorize_security_group_ingress(*args)
-            self.ec2_backend.sg_old_ingress_ruls[group.id] = group.ingress_rules.copy()
-            template = self.response_template(AUTHORIZE_SECURITY_GROUP_INGRESS_RESPONSE)
-            return template.render(rule=rule, group=group)
+        self.error_on_dryrun()
+
+        for args in self._process_rules_from_querystring():
+            rule, group = self.ec2_backend.authorize_security_group_ingress(*args)
+        self.ec2_backend.sg_old_ingress_ruls[group.id] = group.ingress_rules.copy()
+        template = self.response_template(AUTHORIZE_SECURITY_GROUP_INGRESS_RESPONSE)
+        return template.render(rule=rule, group=group)
 
     def create_security_group(self):
         name = self._get_param("GroupName")
@@ -152,19 +154,16 @@ class SecurityGroups(EC2BaseResponse):
         tags = (tags or {}).get("Tag", [])
         tags = {t["Key"]: t["Value"] for t in tags}
 
-        if self.is_not_dryrun("CreateSecurityGroup"):
-            group = self.ec2_backend.create_security_group(
-                name, description, vpc_id=vpc_id, tags=tags
-            )
-            if group:
-                self.ec2_backend.sg_old_ingress_ruls[
-                    group.id
-                ] = group.ingress_rules.copy()
-                self.ec2_backend.sg_old_egress_ruls[
-                    group.id
-                ] = group.egress_rules.copy()
-            template = self.response_template(CREATE_SECURITY_GROUP_RESPONSE)
-            return template.render(group=group)
+        self.error_on_dryrun()
+
+        group = self.ec2_backend.create_security_group(
+            name, description, vpc_id=vpc_id, tags=tags
+        )
+        if group:
+            self.ec2_backend.sg_old_ingress_ruls[group.id] = group.ingress_rules.copy()
+            self.ec2_backend.sg_old_egress_ruls[group.id] = group.egress_rules.copy()
+        template = self.response_template(CREATE_SECURITY_GROUP_RESPONSE)
+        return template.render(group=group)
 
     def delete_security_group(self):
         # TODO this should raise an error if there are instances in the group.
@@ -174,13 +173,14 @@ class SecurityGroups(EC2BaseResponse):
         name = self._get_param("GroupName")
         sg_id = self._get_param("GroupId")
 
-        if self.is_not_dryrun("DeleteSecurityGroup"):
-            if name:
-                self.ec2_backend.delete_security_group(name)
-            elif sg_id:
-                self.ec2_backend.delete_security_group(group_id=sg_id)
+        self.error_on_dryrun()
 
-            return DELETE_GROUP_RESPONSE
+        if name:
+            self.ec2_backend.delete_security_group(name)
+        elif sg_id:
+            self.ec2_backend.delete_security_group(group_id=sg_id)
+
+        return DELETE_GROUP_RESPONSE
 
     def describe_security_groups(self):
         groupnames = self._get_multi_param("GroupName")
@@ -197,22 +197,26 @@ class SecurityGroups(EC2BaseResponse):
     def describe_security_group_rules(self):
         group_id = self._get_param("GroupId")
         filters = self._get_param("Filter")
-        if self.is_not_dryrun("DescribeSecurityGroups"):
-            rules = self.ec2_backend.describe_security_group_rules(group_id, filters)
-            template = self.response_template(DESCRIBE_SECURITY_GROUP_RULES_RESPONSE)
-            return template.render(rules=rules)
+
+        self.error_on_dryrun()
+
+        rules = self.ec2_backend.describe_security_group_rules(group_id, filters)
+        template = self.response_template(DESCRIBE_SECURITY_GROUP_RULES_RESPONSE)
+        return template.render(rules=rules)
 
     def revoke_security_group_egress(self):
-        if self.is_not_dryrun("RevokeSecurityGroupEgress"):
-            for args in self._process_rules_from_querystring():
-                self.ec2_backend.revoke_security_group_egress(*args)
-            return REVOKE_SECURITY_GROUP_EGRESS_RESPONSE
+        self.error_on_dryrun()
+
+        for args in self._process_rules_from_querystring():
+            self.ec2_backend.revoke_security_group_egress(*args)
+        return REVOKE_SECURITY_GROUP_EGRESS_RESPONSE
 
     def revoke_security_group_ingress(self):
-        if self.is_not_dryrun("RevokeSecurityGroupIngress"):
-            for args in self._process_rules_from_querystring():
-                self.ec2_backend.revoke_security_group_ingress(*args)
-            return REVOKE_SECURITY_GROUP_INGRESS_RESPONSE
+        self.error_on_dryrun()
+
+        for args in self._process_rules_from_querystring():
+            self.ec2_backend.revoke_security_group_ingress(*args)
+        return REVOKE_SECURITY_GROUP_INGRESS_RESPONSE
 
     def update_security_group_rule_descriptions_ingress(self):
         for args in self._process_rules_from_querystring():
diff --git a/moto/ec2/responses/settings.py b/moto/ec2/responses/settings.py
index 8cf2114cd..b3fdeba33 100644
--- a/moto/ec2/responses/settings.py
+++ b/moto/ec2/responses/settings.py
@@ -3,32 +3,29 @@ from moto.core.responses import BaseResponse
 
 class Settings(BaseResponse):
     def disable_ebs_encryption_by_default(self):
-        if self.is_not_dryrun("DisableEbsEncryptionByDefault"):
-            self.ec2_backend.disable_ebs_encryption_by_default()
-            template = self.response_template(
-                DISABLE_EBS_ENCRYPTION_BY_DEFAULT_RESPONSE
-            )
-            return template.render(ebsEncryptionByDefault=False).replace(
-                "False", "false"
-            )
+        self.error_on_dryrun()
+
+        self.ec2_backend.disable_ebs_encryption_by_default()
+        template = self.response_template(DISABLE_EBS_ENCRYPTION_BY_DEFAULT_RESPONSE)
+        return template.render(ebsEncryptionByDefault=False).replace("False", "false")
 
     def enable_ebs_encryption_by_default(self):
-        if self.is_not_dryrun("EnableEbsEncryptionByDefault"):
-            self.ec2_backend.enable_ebs_encryption_by_default()
-            template = self.response_template(
-                ENABLED_EBS_ENCRYPTION_BY_DEFAULT_RESPONSE
-            )
-            return template.render(ebsEncryptionByDefault=True).replace("True", "true")
+        self.error_on_dryrun()
+
+        self.ec2_backend.enable_ebs_encryption_by_default()
+        template = self.response_template(ENABLED_EBS_ENCRYPTION_BY_DEFAULT_RESPONSE)
+        return template.render(ebsEncryptionByDefault=True).replace("True", "true")
 
     def get_ebs_encryption_by_default(self):
-        if self.is_not_dryrun("GetEbsEncryptionByDefault"):
-            result = self.ec2_backend.get_ebs_encryption_by_default()
-            template = self.response_template(GET_EBS_ENCRYPTION_BY_DEFAULT_RESPONSE)
-            return (
-                template.render(ebsEncryptionByDefault=result)
-                .replace("False", "false")
-                .replace("True", "true")
-            )
+        self.error_on_dryrun()
+
+        result = self.ec2_backend.get_ebs_encryption_by_default()
+        template = self.response_template(GET_EBS_ENCRYPTION_BY_DEFAULT_RESPONSE)
+        return (
+            template.render(ebsEncryptionByDefault=result)
+            .replace("False", "false")
+            .replace("True", "true")
+        )
 
 
 DISABLE_EBS_ENCRYPTION_BY_DEFAULT_RESPONSE = """<DisableEbsEncryptionByDefaultResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
diff --git a/moto/ec2/responses/spot_instances.py b/moto/ec2/responses/spot_instances.py
index 05de98eb9..da88de9f9 100644
--- a/moto/ec2/responses/spot_instances.py
+++ b/moto/ec2/responses/spot_instances.py
@@ -4,22 +4,26 @@ from ._base_response import EC2BaseResponse
 class SpotInstances(EC2BaseResponse):
     def cancel_spot_instance_requests(self):
         request_ids = self._get_multi_param("SpotInstanceRequestId")
-        if self.is_not_dryrun("CancelSpotInstance"):
-            requests = self.ec2_backend.cancel_spot_instance_requests(request_ids)
-            template = self.response_template(CANCEL_SPOT_INSTANCES_TEMPLATE)
-            return template.render(requests=requests)
+
+        self.error_on_dryrun()
+
+        requests = self.ec2_backend.cancel_spot_instance_requests(request_ids)
+        template = self.response_template(CANCEL_SPOT_INSTANCES_TEMPLATE)
+        return template.render(requests=requests)
 
     def create_spot_datafeed_subscription(self):
-        if self.is_not_dryrun("CreateSpotDatafeedSubscription"):
-            raise NotImplementedError(
-                "SpotInstances.create_spot_datafeed_subscription is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "SpotInstances.create_spot_datafeed_subscription is not yet implemented"
+        )
 
     def delete_spot_datafeed_subscription(self):
-        if self.is_not_dryrun("DeleteSpotDatafeedSubscription"):
-            raise NotImplementedError(
-                "SpotInstances.delete_spot_datafeed_subscription is not yet implemented"
-            )
+        self.error_on_dryrun()
+
+        raise NotImplementedError(
+            "SpotInstances.delete_spot_datafeed_subscription is not yet implemented"
+        )
 
     def describe_spot_datafeed_subscription(self):
         raise NotImplementedError(
@@ -67,31 +71,32 @@ class SpotInstances(EC2BaseResponse):
         )
         tags = self._parse_tag_specification()
 
-        if self.is_not_dryrun("RequestSpotInstance"):
-            requests = self.ec2_backend.request_spot_instances(
-                price=price,
-                image_id=image_id,
-                count=count,
-                spot_instance_type=spot_instance_type,
-                valid_from=valid_from,
-                valid_until=valid_until,
-                launch_group=launch_group,
-                availability_zone_group=availability_zone_group,
-                key_name=key_name,
-                security_groups=security_groups,
-                user_data=user_data,
-                instance_type=instance_type,
-                placement=placement,
-                kernel_id=kernel_id,
-                ramdisk_id=ramdisk_id,
-                monitoring_enabled=monitoring_enabled,
-                subnet_id=subnet_id,
-                instance_interruption_behaviour=instance_interruption_behaviour,
-                tags=tags,
-            )
+        self.error_on_dryrun()
 
-            template = self.response_template(REQUEST_SPOT_INSTANCES_TEMPLATE)
-            return template.render(requests=requests)
+        requests = self.ec2_backend.request_spot_instances(
+            price=price,
+            image_id=image_id,
+            count=count,
+            spot_instance_type=spot_instance_type,
+            valid_from=valid_from,
+            valid_until=valid_until,
+            launch_group=launch_group,
+            availability_zone_group=availability_zone_group,
+            key_name=key_name,
+            security_groups=security_groups,
+            user_data=user_data,
+            instance_type=instance_type,
+            placement=placement,
+            kernel_id=kernel_id,
+            ramdisk_id=ramdisk_id,
+            monitoring_enabled=monitoring_enabled,
+            subnet_id=subnet_id,
+            instance_interruption_behaviour=instance_interruption_behaviour,
+            tags=tags,
+        )
+
+        template = self.response_template(REQUEST_SPOT_INSTANCES_TEMPLATE)
+        return template.render(requests=requests)
 
 
 REQUEST_SPOT_INSTANCES_TEMPLATE = """<RequestSpotInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
diff --git a/moto/ec2/responses/tags.py b/moto/ec2/responses/tags.py
index f36839c68..215739318 100644
--- a/moto/ec2/responses/tags.py
+++ b/moto/ec2/responses/tags.py
@@ -9,20 +9,27 @@ class TagResponse(EC2BaseResponse):
         validate_resource_ids(resource_ids)
         self.ec2_backend.do_resources_exist(resource_ids)
         tags = tags_from_query_string(self.querystring)
-        if self.is_not_dryrun("CreateTags"):
-            self.ec2_backend.create_tags(resource_ids, tags)
-            return CREATE_RESPONSE
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.create_tags(resource_ids, tags)
+        return CREATE_RESPONSE
 
     def delete_tags(self):
         resource_ids = self._get_multi_param("ResourceId")
         validate_resource_ids(resource_ids)
         tags = tags_from_query_string(self.querystring)
-        if self.is_not_dryrun("DeleteTags"):
-            self.ec2_backend.delete_tags(resource_ids, tags)
-            return DELETE_RESPONSE
+
+        self.error_on_dryrun()
+
+        self.ec2_backend.delete_tags(resource_ids, tags)
+        return DELETE_RESPONSE
 
     def describe_tags(self):
         filters = self._filters_from_querystring()
+
+        self.error_on_dryrun()
+
         tags = self.ec2_backend.describe_tags(filters=filters)
         template = self.response_template(DESCRIBE_RESPONSE)
         return template.render(tags=tags)
diff --git a/moto/ecr/responses.py b/moto/ecr/responses.py
index 60f054e5a..717483491 100644
--- a/moto/ecr/responses.py
+++ b/moto/ecr/responses.py
@@ -90,10 +90,10 @@ class ECRResponse(BaseResponse):
         )
 
     def batch_check_layer_availability(self):
-        if self.is_not_dryrun("BatchCheckLayerAvailability"):
-            raise NotImplementedError(
-                "ECR.batch_check_layer_availability is not yet implemented"
-            )
+        self.error_on_dryrun()
+        raise NotImplementedError(
+            "ECR.batch_check_layer_availability is not yet implemented"
+        )
 
     def batch_delete_image(self):
         repository_str = self._get_param("repositoryName")
@@ -116,10 +116,8 @@ class ECRResponse(BaseResponse):
         return json.dumps(response)
 
     def complete_layer_upload(self):
-        if self.is_not_dryrun("CompleteLayerUpload"):
-            raise NotImplementedError(
-                "ECR.complete_layer_upload is not yet implemented"
-            )
+        self.error_on_dryrun()
+        raise NotImplementedError("ECR.complete_layer_upload is not yet implemented")
 
     def delete_repository_policy(self):
         registry_id = self._get_param("registryId")
@@ -149,10 +147,10 @@ class ECRResponse(BaseResponse):
         return json.dumps({"authorizationData": auth_data})
 
     def get_download_url_for_layer(self):
-        if self.is_not_dryrun("GetDownloadUrlForLayer"):
-            raise NotImplementedError(
-                "ECR.get_download_url_for_layer is not yet implemented"
-            )
+        self.error_on_dryrun()
+        raise NotImplementedError(
+            "ECR.get_download_url_for_layer is not yet implemented"
+        )
 
     def get_repository_policy(self):
         registry_id = self._get_param("registryId")
@@ -165,10 +163,8 @@ class ECRResponse(BaseResponse):
         )
 
     def initiate_layer_upload(self):
-        if self.is_not_dryrun("InitiateLayerUpload"):
-            raise NotImplementedError(
-                "ECR.initiate_layer_upload is not yet implemented"
-            )
+        self.error_on_dryrun()
+        raise NotImplementedError("ECR.initiate_layer_upload is not yet implemented")
 
     def set_repository_policy(self):
         registry_id = self._get_param("registryId")
@@ -187,8 +183,8 @@ class ECRResponse(BaseResponse):
         )
 
     def upload_layer_part(self):
-        if self.is_not_dryrun("UploadLayerPart"):
-            raise NotImplementedError("ECR.upload_layer_part is not yet implemented")
+        self.error_on_dryrun()
+        raise NotImplementedError("ECR.upload_layer_part is not yet implemented")
 
     def list_tags_for_resource(self):
         arn = self._get_param("resourceArn")


Test patch:
diff --git a/tests/test_ec2/test_elastic_ip_addresses.py b/tests/test_ec2/test_elastic_ip_addresses.py
index 0677d3a86..458ba514b 100644
--- a/tests/test_ec2/test_elastic_ip_addresses.py
+++ b/tests/test_ec2/test_elastic_ip_addresses.py
@@ -155,7 +155,7 @@ def test_eip_associate_classic():
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the DisAssociateAddress operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the DisassociateAddress operation: Request would have succeeded, but DryRun flag is set"
     )
 
     client.disassociate_address(PublicIp=eip.public_ip)
diff --git a/tests/test_ec2/test_elastic_network_interfaces.py b/tests/test_ec2/test_elastic_network_interfaces.py
index c9282b053..08a34021e 100644
--- a/tests/test_ec2/test_elastic_network_interfaces.py
+++ b/tests/test_ec2/test_elastic_network_interfaces.py
@@ -182,7 +182,7 @@ def test_elastic_network_interfaces_modify_attribute():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the ModifyNetworkInterface operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the ModifyNetworkInterfaceAttribute operation: Request would have succeeded, but DryRun flag is set"
     )
 
     client.modify_network_interface_attribute(
diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py
index 5c0c5c45e..0c670c80b 100644
--- a/tests/test_ec2/test_instances.py
+++ b/tests/test_ec2/test_instances.py
@@ -43,7 +43,7 @@ def test_instance_launch_and_terminate():
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the RunInstance operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the RunInstances operation: Request would have succeeded, but DryRun flag is set"
     )
 
     reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
@@ -85,7 +85,7 @@ def test_instance_launch_and_terminate():
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the TerminateInstance operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the TerminateInstances operation: Request would have succeeded, but DryRun flag is set"
     )
 
     response = client.terminate_instances(InstanceIds=[instance_id])
@@ -920,7 +920,7 @@ def test_instance_start_and_stop():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the StopInstance operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the StopInstances operation: Request would have succeeded, but DryRun flag is set"
     )
 
     stopped_instances = client.stop_instances(InstanceIds=instance_ids)[
@@ -939,7 +939,7 @@ def test_instance_start_and_stop():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the StartInstance operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the StartInstances operation: Request would have succeeded, but DryRun flag is set"
     )
 
     instance1.reload()
@@ -967,7 +967,7 @@ def test_instance_reboot():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the RebootInstance operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the RebootInstances operation: Request would have succeeded, but DryRun flag is set"
     )
 
     instance.state.should.equal({"Code": 16, "Name": "running"})
@@ -988,7 +988,7 @@ def test_instance_attribute_instance_type():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the ModifyInstanceType operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the ModifyInstanceAttribute operation: Request would have succeeded, but DryRun flag is set"
     )
 
     instance.modify_attribute(InstanceType={"Value": "m1.medium"})
@@ -1019,7 +1019,7 @@ def test_modify_instance_attribute_security_groups():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the ModifyInstanceSecurityGroups operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the ModifyInstanceAttribute operation: Request would have succeeded, but DryRun flag is set"
     )
 
     instance.modify_attribute(Groups=[sg_id, sg_id2])
@@ -1043,7 +1043,7 @@ def test_instance_attribute_user_data():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the ModifyUserData operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the ModifyInstanceAttribute operation: Request would have succeeded, but DryRun flag is set"
     )
 
     instance.modify_attribute(UserData={"Value": "this is my user data"})
@@ -1068,7 +1068,7 @@ def test_instance_attribute_source_dest_check():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the ModifySourceDestCheck operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the ModifyInstanceAttribute operation: Request would have succeeded, but DryRun flag is set"
     )
 
     instance.modify_attribute(SourceDestCheck={"Value": False})
@@ -1971,7 +1971,7 @@ def test_get_instance_by_security_group():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the ModifyInstanceSecurityGroups operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the ModifyInstanceAttribute operation: Request would have succeeded, but DryRun flag is set"
     )
 
     client.modify_instance_attribute(InstanceId=instance.id, Groups=[security_group.id])
diff --git a/tests/test_ec2/test_launch_templates.py b/tests/test_ec2/test_launch_templates.py
index 738c0e96d..9b98c1e25 100644
--- a/tests/test_ec2/test_launch_templates.py
+++ b/tests/test_ec2/test_launch_templates.py
@@ -50,6 +50,28 @@ def test_launch_template_create():
     )
 
 
+@mock_ec2
+def test_create_launch_template__dryrun():
+    cli = boto3.client("ec2", region_name="us-east-1")
+
+    template_name = str(uuid4())
+
+    with pytest.raises(ClientError) as exc:
+        cli.create_launch_template(
+            DryRun=True,
+            LaunchTemplateName=template_name,
+            LaunchTemplateData={"ImageId": "ami-abc123"},
+            TagSpecifications=[
+                {"ResourceType": "instance", "Tags": [{"Key": "key", "Value": "value"}]}
+            ],
+        )
+    err = exc.value.response["Error"]
+    err.should.have.key("Code").equals("DryRunOperation")
+    err["Message"].should.equal(
+        "An error occurred (DryRunOperation) when calling the CreateLaunchTemplate operation: Request would have succeeded, but DryRun flag is set"
+    )
+
+
 @mock_ec2
 def test_describe_launch_template_versions():
     template_data = {
@@ -142,6 +164,29 @@ def test_create_launch_template_version():
     version["VersionNumber"].should.equal(2)
 
 
+@mock_ec2
+def test_create_launch_template_version__dryrun():
+    cli = boto3.client("ec2", region_name="us-east-1")
+
+    template_name = str(uuid4())
+    cli.create_launch_template(
+        LaunchTemplateName=template_name, LaunchTemplateData={"ImageId": "ami-abc123"}
+    )
+
+    with pytest.raises(ClientError) as exc:
+        cli.create_launch_template_version(
+            DryRun=True,
+            LaunchTemplateName=template_name,
+            LaunchTemplateData={"ImageId": "ami-def456"},
+            VersionDescription="new ami",
+        )
+    err = exc.value.response["Error"]
+    err.should.have.key("Code").equals("DryRunOperation")
+    err["Message"].should.equal(
+        "An error occurred (DryRunOperation) when calling the CreateLaunchTemplateVersion operation: Request would have succeeded, but DryRun flag is set"
+    )
+
+
 @mock_ec2
 def test_create_launch_template_version_by_id():
     cli = boto3.client("ec2", region_name="us-east-1")
@@ -476,6 +521,9 @@ def test_delete_launch_template__dryrun():
         cli.delete_launch_template(DryRun=True, LaunchTemplateName=template_name)
     err = exc.value.response["Error"]
     err.should.have.key("Code").equals("DryRunOperation")
+    err["Message"].should.equal(
+        "An error occurred (DryRunOperation) when calling the DeleteLaunchTemplate operation: Request would have succeeded, but DryRun flag is set"
+    )
 
     # Template still exists
     cli.describe_launch_templates(LaunchTemplateNames=[template_name])[
diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py
index f88c1ae3e..208963974 100644
--- a/tests/test_ec2/test_security_groups.py
+++ b/tests/test_ec2/test_security_groups.py
@@ -224,7 +224,7 @@ def test_authorize_ip_range_and_revoke():
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the GrantSecurityGroupIngress operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the AuthorizeSecurityGroupIngress operation: Request would have succeeded, but DryRun flag is set"
     )
 
     ingress_permissions = [
@@ -287,7 +287,7 @@ def test_authorize_ip_range_and_revoke():
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the GrantSecurityGroupEgress operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the AuthorizeSecurityGroupEgress operation: Request would have succeeded, but DryRun flag is set"
     )
 
     egress_security_group.authorize_egress(IpPermissions=egress_permissions)
diff --git a/tests/test_ec2/test_spot_instances.py b/tests/test_ec2/test_spot_instances.py
index 7266a4477..b2f173a02 100644
--- a/tests/test_ec2/test_spot_instances.py
+++ b/tests/test_ec2/test_spot_instances.py
@@ -57,7 +57,7 @@ def test_request_spot_instances():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the RequestSpotInstance operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the RequestSpotInstances operation: Request would have succeeded, but DryRun flag is set"
     )
 
     request = conn.request_spot_instances(
@@ -178,7 +178,7 @@ def test_cancel_spot_instance_request():
     ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
     ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
     ex.value.response["Error"]["Message"].should.equal(
-        "An error occurred (DryRunOperation) when calling the CancelSpotInstance operation: Request would have succeeded, but DryRun flag is set"
+        "An error occurred (DryRunOperation) when calling the CancelSpotInstanceRequests operation: Request would have succeeded, but DryRun flag is set"
     )
 
     client.cancel_spot_instance_requests(