Repository: PrefectHQ/prefect

Issue:
Fargate container definitions argument parsing failing (CLI)
## Description

It seems the string data input to be used by the Fargate Agent is never cast as a dict:

```
prefect agent start fargate --containerDefinitions="[{'logConfiguration': 'options': {'awslogs-group': 'something', 'awslogs-stream-prefix': 'prefect-flow-runs', 'awslogs-create-group': 'true'}}]"
```

```
Traceback (most recent call last):
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/bin/prefect", line 10, in <module>
    sys.exit(cli())
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/prefect/cli/agent.py", line 268, in start
    from_qualified_name(retrieved_agent)(
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/prefect/agent/fargate/agent.py", line 142, in __init__
    ) = self._parse_kwargs(kwargs, True)
  File "/Users/nbatalha/Library/Caches/pypoetry/virtualenvs/pipelines-P6yz7rn1-py3.8/lib/python3.8/site-packages/prefect/agent/fargate/agent.py", line 359, in _parse_kwargs
    for key, item in user_kwargs.get("containerDefinitions", [{}])[0].items():
AttributeError: 'str' object has no attribute 'items'
```

## Expected Behavior
<!-- What did you expect to happen instead? -->

It should behave [as the docs describe](https://docs.prefect.io/orchestration/agents/fargate.html#prefect-cli-using-kwargs) and use the above for the fargate task container definition, example in the docs:

```
prefect agent start fargate cpu=256 memory=512 networkConfiguration="{'awsvpcConfiguration': {'assignPublicIp': 'ENABLED', 'subnets': ['my_subnet_id'], 'securityGroups': []}}"
```


## Environment

```
 poetry run prefect diagnostics
{
  "config_overrides": {
    "server": {
      "telemetry": {
        "enabled": true
      }
    }
  },
  "env_vars": [
    "PREFECT__CLOUD__AUTH_TOKEN"
  ],
  "system_information": {
    "platform": "macOS-10.15.5-x86_64-i386-64bit",
    "prefect_version": "0.12.1",
    "python_version": "3.8.2"
  }
}
```

(I deactivated telemetry in the config, need to look into this too)



Discussion:

Resolving patch:
diff --git a/src/prefect/agent/fargate/agent.py b/src/prefect/agent/fargate/agent.py
--- a/src/prefect/agent/fargate/agent.py
+++ b/src/prefect/agent/fargate/agent.py
@@ -370,7 +370,18 @@ def _parse_kwargs(self, user_kwargs: dict, check_envars: bool = False) -> tuple:
                 self.logger.debug("{} = {}".format(key, item))
 
         container_definitions_kwargs = {}
-        for key, item in user_kwargs.get("containerDefinitions", [{}])[0].items():
+        container_defs = user_kwargs.get("containerDefinitions", [{}])
+        try:
+            container_defs = literal_eval(container_defs)
+        except (ValueError, SyntaxError):
+            pass
+
+        if len(container_defs) != 1:
+            raise ValueError(
+                "Fargate agent only accepts configuration for a single container definition."
+            )
+
+        for key, item in container_defs[0].items():
             if key in container_definitions_kwarg_list:
                 try:
                     # Parse kwarg if needed


Test patch:
diff --git a/tests/agent/test_fargate_agent.py b/tests/agent/test_fargate_agent.py
--- a/tests/agent/test_fargate_agent.py
+++ b/tests/agent/test_fargate_agent.py
@@ -188,6 +188,72 @@ def test_parse_container_definition_kwargs(monkeypatch, runner_token):
     }
 
 
+def test_parse_container_definition_kwargs_provided_as_string(
+    monkeypatch, runner_token
+):
+    boto3_client = MagicMock()
+    monkeypatch.setattr("boto3.client", boto3_client)
+
+    agent = FargateAgent()
+
+    kwarg_dict = {
+        "containerDefinitions": str(
+            [
+                {
+                    "environment": "test",
+                    "secrets": "test",
+                    "mountPoints": "test",
+                    "logConfiguration": "test",
+                    "repositoryCredentials": "repo",
+                }
+            ]
+        )
+    }
+
+    (
+        task_definition_kwargs,
+        task_run_kwargs,
+        container_definitions_kwargs,
+    ) = agent._parse_kwargs(kwarg_dict)
+
+    assert container_definitions_kwargs == {
+        "environment": "test",
+        "secrets": "test",
+        "mountPoints": "test",
+        "logConfiguration": "test",
+        "repositoryCredentials": "repo",
+    }
+
+
+def test_parse_container_definition_kwargs_errors_on_multiple(
+    monkeypatch, runner_token
+):
+    boto3_client = MagicMock()
+    monkeypatch.setattr("boto3.client", boto3_client)
+
+    agent = FargateAgent()
+
+    kwarg_dict = {
+        "containerDefinitions": [
+            {
+                "environment": "test",
+                "secrets": "test",
+                "mountPoints": "test",
+                "logConfiguration": "test",
+                "repositoryCredentials": "repo",
+            },
+            {"test": "here"},
+        ]
+    }
+
+    with pytest.raises(ValueError):
+        (
+            task_definition_kwargs,
+            task_run_kwargs,
+            container_definitions_kwargs,
+        ) = agent._parse_kwargs(kwarg_dict)
+
+
 def test_parse_container_definition_kwargs_errors(monkeypatch, runner_token):
     boto3_client = MagicMock()
     monkeypatch.setattr("boto3.client", boto3_client)