# Buggy code
def run(self):
    """
    Run the container.

    Logic is as follows:
    Generate container info (eventually from task definition)
    Start container
    Loop whilst not asked to stop and the container is running.
      Get all logs from container between the last time I checked and now.
    Convert logs into cloudwatch format
    Put logs into cloudwatch

    :return:
    """
    try:
        self.job_state = "PENDING"
        time.sleep(1)

        image = self.job_definition.container_properties.get("image", "alpine:latest")
        privileged = self.job_definition.container_properties.get("privileged", False)
        cmd = self._get_container_property(
            "command",
            '/bin/sh -c "for a in `seq 1 10`; do echo Hello World; sleep 1; done"',
        )
        environment = {
            e["name"]: e["value"]
            for e in self._get_container_property("environment", [])
        }
        volumes = {
            v["name"]: v["host"] for v in self._get_container_property("volumes", [])
        }
        mounts = [
            docker.types.Mount(
                m["containerPath"],
                volumes[m["sourceVolume"]]["sourcePath"],
                type="bind",
                read_only=m["readOnly"],
            )
            for m in self._get_container_property("mountPoints", [])
        ]
        name = "{0}-{1}".format(self.job_name, self.job_id)

        self.job_state = "RUNNABLE"
        # TODO setup ecs container instance
        time.sleep(1)

        self.job_state = "STARTING"
        log_config = docker.types.LogConfig(type=docker.types.LogConfig.types.JSON)
        container = self.docker_client.containers.run(
            image,
            cmd,
            detach=True,
            name=name,
            log_config=log_config,
            environment=environment,
            mounts=mounts,
            privileged=privileged,
        )
        self.job_state = "RUNNING"
        self.job_started_at = datetime.datetime.now()
        try:
            # Log collection
            logs_stdout = []
            logs_stderr = []
            container.reload()

            # Dodgy hack, we can only check docker logs once a second, but we want to loop more
            # so we can stop if asked to in a quick manner, should all go away if we go async
            # There also be some dodgyness when sending an integer to docker logs and some
            # events seem to be duplicated.
            now = datetime.datetime.now()
            i = 1
            while container.status == "running" and not self.stop:
                time.sleep(0.15)
                if i % 10 == 0:
                    logs_stderr.extend(
                        container.logs(
                            stdout=False,
                            stderr=True,
                            timestamps=True,
                            since=datetime2int(now),
                        )
                        .decode()
                        .split("\n")
                    )
                    logs_stdout.extend(
                        container.logs(
                            stdout=True,
                            stderr=False,
                            timestamps=True,
                            since=datetime2int(now),
                        )
                        .decode()
                        .split("\n")
                    )
                    now = datetime.datetime.now()
                    container.reload()
                i += 1

            # Container should be stopped by this point... unless asked to stop
            if container.status == "running":
                container.kill()

            self.job_stopped_at = datetime.datetime.now()
            # Get final logs
            logs_stderr.extend(
                container.logs(
                    stdout=False,
                    stderr=True,
                    timestamps=True,
                    since=datetime2int(now),
                )
                .decode()
                .split("\n")
            )
            logs_stdout.extend(
                container.logs(
                    stdout=True,
                    stderr=False,
                    timestamps=True,
                    since=datetime2int(now),
                )
                .decode()
                .split("\n")
            )

            self.job_state = "SUCCEEDED" if not self.stop else "FAILED"

            # Process logs
            logs_stdout = [x for x in logs_stdout if len(x) > 0]
            logs_stderr = [x for x in logs_stderr if len(x) > 0]
            logs = []
            for line in logs_stdout + logs_stderr:
                date, line = line.split(" ", 1)
                date = dateutil.parser.parse(date)
                date = int(date.timestamp())
                logs.append({"timestamp": date, "message": line.strip()})

            # Send to cloudwatch
            log_group = "/aws/batch/job"
            stream_name = "{0}/default/{1}".format(
                self.job_definition.name, self.job_id
            )
            self.log_stream_name = stream_name
            self._log_backend.ensure_log_group(log_group, None)
            self._log_backend.create_log_stream(log_group, stream_name)
            self._log_backend.put_log_events(log_group, stream_name, logs, None)

        except Exception as err:
            logger.error(
                "Failed to run AWS Batch container {0}. Error {1}".format(
                    self.name, err
                )
            )
            self.job_state = "FAILED"
            container.kill()
        finally:
            container.remove()
    except Exception as err:
        logger.error(
            "Failed to run AWS Batch container {0}. Error {1}".format(self.name, err)
        )
        self.job_state = "FAILED"

    self.job_stopped = True
    self.job_stopped_at = datetime.datetime.now()


# Traceback (AttributeError)
2020-02-28 23:16:27 - werkzeug     - INFO     - 127.0.0.1 - - [28/Feb/2020 23:16:27] "POST /v1/describejobs HTTP/1.1" 500 -
2020-02-28 23:16:27 - werkzeug     - ERROR    - Error on request:
Traceback (most recent call last):
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/werkzeug/serving.py", line 323, in run_wsgi
execute(self.server.app)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/werkzeug/serving.py", line 312, in execute
application_iter = app(environ, start_response)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/moto/server.py", line 135, in __call__
return backend_app(environ, start_response)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/app.py", line 2463, in __call__
return self.wsgi_app(environ, start_response)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/app.py", line 2449, in wsgi_app
response = self.handle_exception(e)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/app.py", line 1866, in handle_exception
reraise(exc_type, exc_value, tb)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/moto/core/utils.py", line 144, in __call__
result = self.callback(request, request.url, {})
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/moto/core/responses.py", line 197, in dispatch
return cls()._dispatch(*args, **kwargs)
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/moto/core/responses.py", line 295, in _dispatch
return self.call_action()
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/moto/core/responses.py", line 380, in call_action
response = method()
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/moto/batch/responses.py", line 257, in describejobs
return json.dumps({"jobs": self.batch_backend.describe_jobs(jobs)})
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/moto/batch/models.py", line 1229, in describe_jobs
result.append(job.describe())
File "/builds/dazza-codes/python-notes/.venv/lib/python3.6/site-packages/moto/batch/models.py", line 355, in describe
result["container"]["logStreamName"] = self.log_stream_name
AttributeError: 'Job' object has no attribute 'log_stream_name'

# Fixed code
def run(self):
    """
    Run the container.

    Logic is as follows:
    Generate container info (eventually from task definition)
    Start container
    Loop whilst not asked to stop and the container is running.
      Get all logs from container between the last time I checked and now.
    Convert logs into cloudwatch format
    Put logs into cloudwatch

    :return:
    """
    try:
        self.job_state = "PENDING"
        time.sleep(1)

        image = self.job_definition.container_properties.get("image", "alpine:latest")
        privileged = self.job_definition.container_properties.get("privileged", False)
        cmd = self._get_container_property(
            "command",
            '/bin/sh -c "for a in `seq 1 10`; do echo Hello World; sleep 1; done"',
        )
        environment = {
            e["name"]: e["value"]
            for e in self._get_container_property("environment", [])
        }
        volumes = {
            v["name"]: v["host"] for v in self._get_container_property("volumes", [])
        }
        mounts = [
            docker.types.Mount(
                m["containerPath"],
                volumes[m["sourceVolume"]]["sourcePath"],
                type="bind",
                read_only=m["readOnly"],
            )
            for m in self._get_container_property("mountPoints", [])
        ]
        name = "{0}-{1}".format(self.job_name, self.job_id)

        self.job_state = "RUNNABLE"
        # TODO setup ecs container instance
        time.sleep(1)

        self.job_state = "STARTING"
        log_config = docker.types.LogConfig(type=docker.types.LogConfig.types.JSON)
        container = self.docker_client.containers.run(
            image,
            cmd,
            detach=True,
            name=name,
            log_config=log_config,
            environment=environment,
            mounts=mounts,
            privileged=privileged,
        )
        self.job_state = "RUNNING"
        self.job_started_at = datetime.datetime.now()
        try:
            # Log collection
            logs_stdout = []
            logs_stderr = []
            container.reload()

            # Dodgy hack, we can only check docker logs once a second, but we want to loop more
            # so we can stop if asked to in a quick manner, should all go away if we go async
            # There also be some dodgyness when sending an integer to docker logs and some
            # events seem to be duplicated.
            now = datetime.datetime.now()
            i = 1
            while container.status == "running" and not self.stop:
                time.sleep(0.15)
                if i % 10 == 0:
                    logs_stderr.extend(
                        container.logs(
                            stdout=False,
                            stderr=True,
                            timestamps=True,
                            since=datetime2int(now),
                        )
                        .decode()
                        .split("\n")
                    )
                    logs_stdout.extend(
                        container.logs(
                            stdout=True,
                            stderr=False,
                            timestamps=True,
                            since=datetime2int(now),
                        )
                        .decode()
                        .split("\n")
                    )
                    now = datetime.datetime.now()
                    container.reload()
                i += 1

            # Container should be stopped by this point... unless asked to stop
            if container.status == "running":
                container.kill()

            self.job_stopped_at = datetime.datetime.now()
            # Get final logs
            logs_stderr.extend(
                container.logs(
                    stdout=False,
                    stderr=True,
                    timestamps=True,
                    since=datetime2int(now),
                )
                .decode()
                .split("\n")
            )
            logs_stdout.extend(
                container.logs(
                    stdout=True,
                    stderr=False,
                    timestamps=True,
                    since=datetime2int(now),
                )
                .decode()
                .split("\n")
            )

            self.job_state = "SUCCEEDED" if not self.stop else "FAILED"

            # Process logs
            logs_stdout = [x for x in logs_stdout if len(x) > 0]
            logs_stderr = [x for x in logs_stderr if len(x) > 0]
            logs = []
            for line in logs_stdout + logs_stderr:
                date, line = line.split(" ", 1)
                date = dateutil.parser.parse(date)
                # TODO: Replace with int(date.timestamp()) once we yeet Python2 out of the window
                date = int(
                    (time.mktime(date.timetuple()) + date.microsecond / 1000000.0)
                )
                logs.append({"timestamp": date, "message": line.strip()})

            # Send to cloudwatch
            log_group = "/aws/batch/job"
            stream_name = "{0}/default/{1}".format(
                self.job_definition.name, self.job_id
            )
            self.log_stream_name = stream_name
            self._log_backend.ensure_log_group(log_group, None)
            self._log_backend.create_log_stream(log_group, stream_name)
            self._log_backend.put_log_events(log_group, stream_name, logs, None)

        except Exception as err:
            logger.error(
                "Failed to run AWS Batch container {0}. Error {1}".format(
                    self.name, err
                )
            )
            self.job_state = "FAILED"
            container.kill()
        finally:
            container.remove()
    except Exception as err:
        logger.error(
            "Failed to run AWS Batch container {0}. Error {1}".format(self.name, err)
        )
        self.job_state = "FAILED"

    self.job_stopped = True
    self.job_stopped_at = datetime.datetime.now()