Repository: getmoto/moto

Issue:
Batch compute environment Fargate
Hi! 👋   I'm trying to mock AWS Batch on Fargate resources (which I think Moto should support?) and to do that I need to mock a Batch Compute Environment. As I understand the [Boto documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/batch.html#Batch.Client.create_compute_environment) the `instanceRole` parameter is optional and not recommended when creating a compute environment that uses Fargate.

However, Moto seems to require the `instanceRole` parameter. 
Is this a bug in Moto that it's requiring this parameter when Boto doesn't? Is mocking AWS Batch Fargate resources supported in Moto? 

The full error is:

```
Traceback (most recent call last):
  File "run.py", line 65, in <module>
    create_compute_environment(batch_client, "dummy", iam_role_arn)
  File "run.py", line 18, in create_compute_environment
    response = client.create_compute_environment(
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/client.py", line 386, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/client.py", line 691, in _make_api_call
    http, parsed_response = self._make_request(
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/client.py", line 711, in _make_request
    return self._endpoint.make_request(operation_model, request_dict)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/endpoint.py", line 102, in make_request
    return self._send_request(request_dict, operation_model)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/endpoint.py", line 136, in _send_request
    while self._needs_retry(attempts, operation_model, request_dict,
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/endpoint.py", line 253, in _needs_retry
    responses = self._event_emitter.emit(
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/hooks.py", line 357, in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/hooks.py", line 228, in emit
    return self._emit(event_name, kwargs)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/hooks.py", line 211, in _emit
    response = handler(**kwargs)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/retryhandler.py", line 183, in __call__
    if self._checker(attempts, response, caught_exception):
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/retryhandler.py", line 250, in __call__
    should_retry = self._should_retry(attempt_number, response,
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/retryhandler.py", line 269, in _should_retry
    return self._checker(attempt_number, response, caught_exception)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/retryhandler.py", line 316, in __call__
    checker_response = checker(attempt_number, response,
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/retryhandler.py", line 222, in __call__
    return self._check_caught_exception(
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/retryhandler.py", line 359, in _check_caught_exception
    raise caught_exception
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/endpoint.py", line 197, in _do_get_response
    responses = self._event_emitter.emit(event_name, request=request)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/hooks.py", line 357, in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/hooks.py", line 228, in emit
    return self._emit(event_name, kwargs)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/botocore/hooks.py", line 211, in _emit
    response = handler(**kwargs)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/moto/core/models.py", line 369, in __call__
    status, headers, body = response_callback(
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/moto/core/responses.py", line 205, in dispatch
    return cls()._dispatch(*args, **kwargs)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/moto/core/responses.py", line 315, in _dispatch
    return self.call_action()
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/moto/core/responses.py", line 405, in call_action
    response = method()
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/moto/batch/responses.py", line 53, in createcomputeenvironment
    name, arn = self.batch_backend.create_compute_environment(
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/moto/batch/models.py", line 835, in create_compute_environment
    self._validate_compute_resources(compute_resources)
  File "/Users/adam/sano/playground/awsbatch_moto/venv/lib/python3.8/site-packages/moto/batch/models.py", line 911, in _validate_compute_resources
    "could not find instanceRole {0}".format(cr["instanceRole"])
KeyError: 'instanceRole'
```

This is using Moto 2.2.4 Boto3 1.18.29 from pip in a virtual environment with code:

```
from moto import mock_batch, mock_iam
import boto3

if __name__ == "__main__":
    # requires docker on the system
    with mock_batch(), mock_iam():
        iam_client = boto3.client("iam")

        # create a service role
        iam_role_arn = (
            iam_client.create_role(
                RoleName="AliceServiceRole",
                AssumeRolePolicyDocument="{}",
            )
            .get("Role")
            .get("Arn")
        )

        batch_client = boto3.client("batch")
        # create compute environment
        batch_client.create_compute_environment(
            computeEnvironmentName="DummyEnvironment",
            type="MANAGED",
            state="ENABLED",
            computeResources={
                "type": "FARGATE",
                "maxvCpus": 100,
                "subnets": [
                    "string",
                ],
                "securityGroupIds": [
                    "string",
                ],
            },
            serviceRole=iam_role_arn,
        )
```


Discussion:
Hi @afaulconbridge, welcome to Moto!

The fact that `instanceRole` is required is definitely a bug - I'll mark it as such.

At the moment, Batch only supports EC2 and SPOT though. From what I can see it actively throws an error when specifying anything else then EC2 or SPOT, so that would be bug no 2!

Resolving patch:
diff --git a/moto/batch/models.py b/moto/batch/models.py
--- a/moto/batch/models.py
+++ b/moto/batch/models.py
@@ -859,7 +859,7 @@ def create_compute_environment(
         self._compute_environments[new_comp_env.arn] = new_comp_env
 
         # Ok by this point, everything is legit, so if its Managed then start some instances
-        if _type == "MANAGED":
+        if _type == "MANAGED" and "FARGATE" not in compute_resources["type"]:
             cpus = int(
                 compute_resources.get("desiredvCpus", compute_resources["minvCpus"])
             )
@@ -902,49 +902,38 @@ def _validate_compute_resources(self, cr):
         :param cr: computeResources
         :type cr: dict
         """
-        for param in (
-            "instanceRole",
-            "maxvCpus",
-            "minvCpus",
-            "instanceTypes",
-            "securityGroupIds",
-            "subnets",
-            "type",
-        ):
-            if param not in cr:
-                pass  # commenting out invalid check below - values may be missing (tf-compat)
-                # raise InvalidParameterValueException(
-                #     "computeResources must contain {0}".format(param)
-                # )
-        for profile in self.iam_backend.get_instance_profiles():
-            if profile.arn == cr["instanceRole"]:
-                break
-        else:
-            raise InvalidParameterValueException(
-                "could not find instanceRole {0}".format(cr["instanceRole"])
-            )
-
         if int(cr["maxvCpus"]) < 0:
             raise InvalidParameterValueException("maxVCpus must be positive")
-        if int(cr["minvCpus"]) < 0:
-            raise InvalidParameterValueException("minVCpus must be positive")
-        if int(cr["maxvCpus"]) < int(cr["minvCpus"]):
-            raise InvalidParameterValueException(
-                "maxVCpus must be greater than minvCpus"
-            )
+        if "FARGATE" not in cr["type"]:
+            # Most parameters are not applicable to jobs that are running on Fargate resources:
+            # non exhaustive list: minvCpus, instanceTypes, imageId, ec2KeyPair, instanceRole, tags
+            for profile in self.iam_backend.get_instance_profiles():
+                if profile.arn == cr["instanceRole"]:
+                    break
+            else:
+                raise InvalidParameterValueException(
+                    "could not find instanceRole {0}".format(cr["instanceRole"])
+                )
 
-        if len(cr["instanceTypes"]) == 0:
-            raise InvalidParameterValueException(
-                "At least 1 instance type must be provided"
-            )
-        for instance_type in cr["instanceTypes"]:
-            if instance_type == "optimal":
-                pass  # Optimal should pick from latest of current gen
-            elif instance_type not in EC2_INSTANCE_TYPES:
+            if int(cr["minvCpus"]) < 0:
+                raise InvalidParameterValueException("minvCpus must be positive")
+            if int(cr["maxvCpus"]) < int(cr["minvCpus"]):
                 raise InvalidParameterValueException(
-                    "Instance type {0} does not exist".format(instance_type)
+                    "maxVCpus must be greater than minvCpus"
                 )
 
+            if len(cr["instanceTypes"]) == 0:
+                raise InvalidParameterValueException(
+                    "At least 1 instance type must be provided"
+                )
+            for instance_type in cr["instanceTypes"]:
+                if instance_type == "optimal":
+                    pass  # Optimal should pick from latest of current gen
+                elif instance_type not in EC2_INSTANCE_TYPES:
+                    raise InvalidParameterValueException(
+                        "Instance type {0} does not exist".format(instance_type)
+                    )
+
         for sec_id in cr["securityGroupIds"]:
             if self.ec2_backend.get_security_group_from_id(sec_id) is None:
                 raise InvalidParameterValueException(
@@ -965,9 +954,9 @@ def _validate_compute_resources(self, cr):
         if len(cr["subnets"]) == 0:
             raise InvalidParameterValueException("At least 1 subnet must be provided")
 
-        if cr["type"] not in ("EC2", "SPOT"):
+        if cr["type"] not in {"EC2", "SPOT", "FARGATE", "FARGATE_SPOT"}:
             raise InvalidParameterValueException(
-                "computeResources.type must be either EC2 | SPOT"
+                "computeResources.type must be either EC2 | SPOT | FARGATE | FARGATE_SPOT"
             )
 
     @staticmethod


Test patch:
diff --git a/tests/test_batch/test_batch_compute_envs.py b/tests/test_batch/test_batch_compute_envs.py
--- a/tests/test_batch/test_batch_compute_envs.py
+++ b/tests/test_batch/test_batch_compute_envs.py
@@ -1,4 +1,5 @@
 from . import _get_clients, _setup
+import pytest
 import sure  # noqa # pylint: disable=unused-import
 from moto import mock_batch, mock_iam, mock_ec2, mock_ecs, settings
 from uuid import uuid4
@@ -232,3 +233,38 @@ def test_update_unmanaged_compute_environment_state():
     our_envs = [e for e in all_envs if e["computeEnvironmentName"] == compute_name]
     our_envs.should.have.length_of(1)
     our_envs[0]["state"].should.equal("DISABLED")
+
+
+@pytest.mark.parametrize("compute_env_type", ["FARGATE", "FARGATE_SPOT"])
+@mock_ec2
+@mock_ecs
+@mock_iam
+@mock_batch
+def test_create_fargate_managed_compute_environment(compute_env_type):
+    ec2_client, iam_client, ecs_client, _, batch_client = _get_clients()
+    _, subnet_id, sg_id, iam_arn = _setup(ec2_client, iam_client)
+
+    compute_name = str(uuid4())
+    resp = batch_client.create_compute_environment(
+        computeEnvironmentName=compute_name,
+        type="MANAGED",
+        state="ENABLED",
+        computeResources={
+            "type": compute_env_type,
+            "maxvCpus": 10,
+            "subnets": [subnet_id],
+            "securityGroupIds": [sg_id],
+        },
+        serviceRole=iam_arn,
+    )
+    resp.should.contain("computeEnvironmentArn")
+    resp["computeEnvironmentName"].should.equal(compute_name)
+
+    our_env = batch_client.describe_compute_environments(
+        computeEnvironments=[compute_name]
+    )["computeEnvironments"][0]
+
+    our_env["computeResources"]["type"].should.equal(compute_env_type)
+    # Should have created 1 ECS cluster
+    all_clusters = ecs_client.list_clusters()["clusterArns"]
+    all_clusters.should.contain(our_env["ecsClusterArn"])