Repository: getmoto/moto
Issue: SSM: PutParameter requires Type for parameter updates

moto version: 4.1.11 using Python mocks (boto3 1.22.12/botocore 1.25.12)

Per [AWS documentation](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_PutParameter.html#systemsmanager-PutParameter-request-Type), specifiying a `Type` in `PutParameter` request is not required when updating a parameter. However, when using moto, `PutParameter` `Type` field is required even if the parameter already exists and is intended to be updated.

```python
import os

import boto3
import moto

os.environ = {
    "AWS_DEFAULT_REGION": "us-east-1",
    "AWS_ACCESS_KEY_ID": "testing",
    "AWS_SECRET_ACCESS_KEY": "testing",
    "AWS_SESSION_TOKEN": "testing",
}

with moto.mock_ssm():
    ssm_client = boto3.client("ssm")
    ssm_client.put_parameter(
        Description="Description",
        Name="Name",
        Type="String",
        Value="Value",
    )
    ssm_client.put_parameter(
        Name="Name",
        Overwrite=True,
        Value="UpdatedValue",
    )
```

In the code above, the second put_parameter call fails unexpectedly with the following exception.

```
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the PutParameter operation: 1 validation error detected: Value 'None' at 'type' failed to satisfy constraint: Member must satisfy enum value set: [SecureString, StringList, String]
```

When I tested this under moto 3.1.8, the `Type` parameter is not required for `put_parameter`. However, because the second call to `put_parameter` lacks a `Type` parameter, subsequent calls to `describe_parameters` results in a response that lacks a `Type` for parameters. The expected result in this case is that the parameter maintains the `Type` supplied in the initial `put_parameter` call that created the parameter.

Changed files:
moto/ssm/exceptions.py, tests/test_ssm/test_ssm_boto3.py, moto/ssm/responses.py, moto/ssm/models.py

diff --git a/moto/ssm/exceptions.py b/moto/ssm/exceptions.py
index 4c6b69ca6..29813c22d 100644
--- a/moto/ssm/exceptions.py
+++ b/moto/ssm/exceptions.py
@@ -139,3 +139,13 @@ class ParameterMaxVersionLimitExceeded(JsonRESTError):
 
     def __init__(self, message: str):
         super().__init__("ParameterMaxVersionLimitExceeded", message)
+
+
+class ParameterAlreadyExists(JsonRESTError):
+    code = 400
+
+    def __init__(self) -> None:
+        super().__init__(
+            "ParameterAlreadyExists",
+            "The parameter already exists. To overwrite this value, set the overwrite option in the request to true.",
+        )
diff --git a/moto/ssm/models.py b/moto/ssm/models.py
index 2902fd04c..3a9f66550 100644
--- a/moto/ssm/models.py
+++ b/moto/ssm/models.py
@@ -1899,7 +1899,11 @@ class SimpleSystemManagerBackend(BaseBackend):
                 )
             raise ValidationException(invalid_prefix_error)
 
-        if not _valid_parameter_type(parameter_type):
+        if (
+            not _valid_parameter_type(parameter_type)
+            and not overwrite
+            and name not in self._parameters
+        ):
             raise ValidationException(
                 f"1 validation error detected: Value '{parameter_type}' at 'type' failed to satisfy constraint: Member must satisfy enum value set: [SecureString, StringList, String]",
             )
diff --git a/moto/ssm/responses.py b/moto/ssm/responses.py
index 7d7b9d3d2..43476aadb 100644
--- a/moto/ssm/responses.py
+++ b/moto/ssm/responses.py
@@ -2,7 +2,7 @@ import json
 from typing import Any, Dict, Tuple, Union
 
 from moto.core.responses import BaseResponse
-from .exceptions import ValidationException
+from .exceptions import ValidationException, ParameterAlreadyExists
 from .models import ssm_backends, SimpleSystemManagerBackend
 
 
@@ -286,11 +286,7 @@ class SimpleSystemManagerResponse(BaseResponse):
         )
 
         if result is None:
-            error = {
-                "__type": "ParameterAlreadyExists",
-                "message": f"Parameter {name} already exists.",
-            }
-            return json.dumps(error), dict(status=400)
+            raise ParameterAlreadyExists
 
         response = {"Version": result}
         return json.dumps(response)
diff --git a/tests/test_ssm/test_ssm_boto3.py b/tests/test_ssm/test_ssm_boto3.py
index daf6a73a9..6c227ee8d 100644
--- a/tests/test_ssm/test_ssm_boto3.py
+++ b/tests/test_ssm/test_ssm_boto3.py
@@ -253,7 +253,7 @@ def test_put_parameter(name):
     except botocore.exceptions.ClientError as err:
         err.operation_name.should.equal("PutParameter")
         err.response["Error"]["Message"].should.equal(
-            f"Parameter {name} already exists."
+            "The parameter already exists. To overwrite this value, set the overwrite option in the request to true."
         )
 
     response = client.get_parameters(Names=[name], WithDecryption=False)
@@ -431,6 +431,61 @@ def test_put_parameter_invalid_type():
     )
 
 
+@mock_ssm
+def test_update_parameter():
+    # Setup
+    client = boto3.client("ssm", "us-east-1")
+    param_name = "test_param"
+    updated_value = "UpdatedValue"
+    client.put_parameter(
+        Description="Description",
+        Name=param_name,
+        Type="String",
+        Value="Value",
+    )
+
+    # Execute
+    response = client.put_parameter(
+        Name=param_name,
+        Overwrite=True,
+        Value=updated_value,
+    )
+    new_param = client.get_parameter(Name=param_name)
+
+    # Verify
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+    assert new_param["Parameter"]["Value"] == updated_value
+
+
+@mock_ssm
+def test_update_parameter_already_exists_error():
+    # Setup
+    client = boto3.client("ssm", "us-east-1")
+    client.put_parameter(
+        Description="Description",
+        Name="Name",
+        Type="String",
+        Value="Value",
+    )
+
+    # Execute
+    with pytest.raises(ClientError) as exc:
+        client.put_parameter(
+            Name="Name",
+            Value="UpdatedValue",
+        )
+
+    # Verify
+    ex = exc.value
+    assert ex.operation_name.should.equal("PutParameter")
+    assert ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert ex.response["Error"]["Code"] == "ParameterAlreadyExists"
+    assert (
+        ex.response["Error"]["Message"]
+        == "The parameter already exists. To overwrite this value, set the overwrite option in the request to true."
+    )
+
+
 @mock_ssm
 def test_get_parameter():
     client = boto3.client("ssm", region_name="us-east-1")