Repository: PrefectHQ/prefect

Issue:
Override Customizations not functional via CLI Deployments
### First check

- [X] I added a descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the Prefect documentation for this issue.
- [X] I checked that this issue is related to Prefect and not one of its dependencies.

### Bug summary

Adding customizations (e.g. JSON Patch for KubernetesJob) via CLI does not work.
Input is either treated as a string, or cannot be parsed.

### Reproduction


healthcheck.py
```python3
import prefect
from prefect import task, flow
from prefect import get_run_logger
from prefect.filesystems import Azure


@task
def log_platform_info():
    import platform
    import sys
    from prefect.orion.api.server import ORION_API_VERSION
    logger = get_run_logger()
    logger.info("Host's network name = %s", platform.node())
    logger.info("Python version = %s", platform.python_version())
    logger.info("Platform information (instance type) = %s ", platform.platform())
    logger.info("OS/Arch = %s/%s", sys.platform, platform.machine())
    logger.info("Prefect Version = %s 🚀", prefect.__version__)
    logger.info("Prefect API Version = %s", ORION_API_VERSION)
@flow
def healthcheck():
    log_platform_info()
if __name__ == "__main__":
    healthcheck()
```

```shell
prefect deployment build ./healthcheck.py:healthcheck -n test_customizations -i kubernetes-job -q kubernetes -sb azure/boydblock --override customizations=[{"op": "add","path": "/spec/template/spec/resources","value": {"limits": {"memory": "8Gi","cpu": "4000m"}}}]

prefect deployment build ./healthcheck.py:healthcheck -n test_customizations -i kubernetes-job -q kubernetes -sb azure/boydblock --override customizations='[{"op": "add","path": "/spec/template/spec/resources","value": {"limits": {"memory": "8Gi","cpu": "4000m"}}}]'
```


### Error

```python3
Without quoting the string:
`zsh: bad pattern: customizations=[{op:`

With quoting the string, the deployment can be successfully applied, but attempting to run the deployment fails with the error:


State Message
Submission failed. pydantic.error_wrappers.ValidationError: 2 validation errors for KubernetesJob customizations instance of JsonPatch expected (type=type_error.arbitrary_type; expected_arbitrary_type=JsonPatch) job_watch_timeout_seconds none is not an allowed value (type=type_error.none.not_allowed)
```
```


### Versions

```Text
prefect version
14:49:50.605 | DEBUG   | prefect.client - Connecting to API at https://api.prefect.cloud/api/accounts/e16f474c-4714-40ae-89c9-ba3cbdd3bf13/workspaces/e7c934ce-f75b-4d2f-a166-e25d40e647d7/
Version:             2.7.1
API version:         0.8.3
Python version:      3.9.14
Git commit:          33f7de50
Built:               Thu, Dec 8, 2022 4:09 PM
OS/Arch:             darwin/arm64
Profile:             boyd
Server type:         cloud
```


### Additional context

This can be done via pythonic deployment by setting customizations=[{}] in the kubernetes job.
It seems like passing a list through CLI is not possible currently.


Discussion:
`job_watch_timeout_seconds none is not an allowed value` looks like the error resolved in #7914, I'm guessing you won't see that on 2.7.2

`zsh: bad pattern: customizations=[{op:` is expected; `[` is used for zsh syntax so you always need to quote these things.

Looks like `instance of JsonPatch expected (type=type_error.arbitrary_type; expected_arbitrary_type=JsonPatch)` is the primary error. I think this is because our validator for the `customizations` field will only cast lists to a `JsonPatch` instance but here you are passing a string. Maybe we can update the validator to run `json.loads()` on string values or change `--overrides` to parse strings into Python types. 


Resolving patch:
diff --git a/src/prefect/infrastructure/kubernetes.py b/src/prefect/infrastructure/kubernetes.py
--- a/src/prefect/infrastructure/kubernetes.py
+++ b/src/prefect/infrastructure/kubernetes.py
@@ -1,5 +1,6 @@
 import copy
 import enum
+import json
 import os
 from contextlib import contextmanager
 from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, Union
@@ -182,10 +183,17 @@ def ensure_job_has_compatible_values(cls, value: KubernetesManifest):
 
     @validator("customizations", pre=True)
     def cast_customizations_to_a_json_patch(
-        cls, value: Union[List[Dict], JsonPatch]
+        cls, value: Union[List[Dict], JsonPatch, str]
     ) -> JsonPatch:
         if isinstance(value, list):
             return JsonPatch(value)
+        elif isinstance(value, str):
+            try:
+                return JsonPatch(json.loads(value))
+            except json.JSONDecodeError as exc:
+                raise ValueError(
+                    f"Unable to parse customizations as JSON: {value}. Please make sure that the provided value is a valid JSON string."
+                ) from exc
         return value
 
     @root_validator


Test patch:
diff --git a/tests/infrastructure/test_kubernetes_job.py b/tests/infrastructure/test_kubernetes_job.py
--- a/tests/infrastructure/test_kubernetes_job.py
+++ b/tests/infrastructure/test_kubernetes_job.py
@@ -1044,6 +1044,22 @@ def test_providing_a_secret_key_as_an_environment_variable(self):
             },
         }
 
+    def test_providing_a_string_customization(self):
+        KubernetesJob(
+            command=["echo", "hello"],
+            customizations='[{"op": "add", "path": "/spec/template/spec/containers/0/env/-","value": {"name": "MY_API_TOKEN", "valueFrom": {"secretKeyRef": {"name": "the-secret-name", "key": "api-token"}}}}]',
+        )
+
+    def test_providing_an_illformatted_string_customization_raises(self):
+        with pytest.raises(
+            ValueError,
+            match=f"Unable to parse customizations as JSON: .* Please make sure that the provided value is a valid JSON string.",
+        ):
+            KubernetesJob(
+                command=["echo", "hello"],
+                customizations='[{"op": ""add", "path": "/spec/template/spec/containers/0/env/-","value": {"name": "MY_API_TOKEN", "valueFrom": {"secretKeyRef": {"name": "the-secret-name", "key": "api-token"}}}}]',
+            )
+
     def test_setting_pod_resource_requests(self):
         manifest = KubernetesJob(
             command=["echo", "hello"],