Repository: getmoto/moto
Pull request: Support Podman for mocking Lambda
Podman supports all Docker APIs used in moto since version 3.0. Note
that Podman requires pulling the image before creating a container
using a fully-qualified image name (e.g., "docker.io/library/busybox"
instead of "busybox").

Test plan:
$ podman system service -t 0
$ DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock" pytest

Fixes https://github.com/spulec/moto/issues/3276

Problem statement:
[feature] allow lambda to run with podman as an alternative to docker
* allows you to containers not as root
* cgroups v2
* better support on centos8/fedora OSs

should be an alternative, and not a replacement, as docker is more widespread than podman

Hints from issue discussion:
Hi @yuvalif, thanks for raising this. Hadn't heard of Podman before. 

Is there a specific use case for Lambdas where Podman would give a noticable improvement? From what I can read, it would behave exactly the same in our limited use case. (I.e., run t he container, execute some python code and return the result.)
the main improvement is around installation and setup of docker (especially in case of centos8/fedora OSs), as well as the fact that non-privileged containers don't need root permissions in podman.
don't think there is functional advantage.
I give it a try, and get blocked as Podman hasn't implemented all Docker APIs. After some patches to moto and docker-py, the blocker comes to the `put_archive` function [1][2].

I'm interested in this as I'm maintaining the Arch Linux package and I'd like to run the test suite of moto without extra privileges.

[1] https://github.com/spulec/moto/blob/9f0f230d130a839fb2de6bfc97af4182360fbcdb/moto/awslambda/models.py#L135
[2] https://github.com/containers/podman/issues/6050
Podman 3.0 supports all needed Docker APIs, and only a few changes are needed in moto => #3702

Commit message: Python 2 compatibility

Patch to review:
diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py
index b6c941cd7d3c..95af5d19a950 100644
--- a/moto/awslambda/models.py
+++ b/moto/awslambda/models.py
@@ -131,6 +131,7 @@ def __enter__(self):
                 volumes = {self.name: {"bind": "/tmp/data", "mode": "rw"}}
             else:
                 volumes = {self.name: "/tmp/data"}
+            self._lambda_func.docker_client.images.pull("docker.io/library/alpine")
             container = self._lambda_func.docker_client.containers.run(
                 "alpine", "sleep 100", volumes=volumes, detach=True
             )
@@ -574,6 +575,9 @@ def _invoke_lambda(self, code, event=None, context=None):
                         if settings.TEST_SERVER_MODE
                         else {}
                     )
+                    self.docker_client.images.pull(
+                        "docker.io/lambci/lambda", tag=self.run_time
+                    )
                     container = self.docker_client.containers.run(
                         "lambci/lambda:{}".format(self.run_time),
                         [self.handler, json.dumps(event)],
diff --git a/moto/batch/models.py b/moto/batch/models.py
index 110188aad2c0..4706a0ede929 100644
--- a/moto/batch/models.py
+++ b/moto/batch/models.py
@@ -28,7 +28,7 @@
 from moto.ec2.models import INSTANCE_TYPES as EC2_INSTANCE_TYPES
 from moto.iam.exceptions import IAMNotFoundException
 from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
-from moto.utilities.docker_utilities import DockerModel
+from moto.utilities.docker_utilities import DockerModel, parse_image_name
 
 logger = logging.getLogger(__name__)
 COMPUTE_ENVIRONMENT_NAME_REGEX = re.compile(
@@ -428,6 +428,8 @@ def run(self):
             self.job_started_at = datetime.datetime.now()
             self.job_state = "STARTING"
             log_config = docker.types.LogConfig(type=docker.types.LogConfig.types.JSON)
+            image_repository, image_tag = parse_image_name(image)
+            self.docker_client.images.pull(image_repository, image_tag)
             container = self.docker_client.containers.run(
                 image,
                 cmd,
diff --git a/moto/utilities/docker_utilities.py b/moto/utilities/docker_utilities.py
index 576a9df1d8e8..4e65f5f26d4a 100644
--- a/moto/utilities/docker_utilities.py
+++ b/moto/utilities/docker_utilities.py
@@ -31,3 +31,17 @@ def replace_adapter_send(*args, **kwargs):
 
                 self.docker_client.api.get_adapter = replace_adapter_send
         return self.__docker_client
+
+
+def parse_image_name(image_name):
+    # podman does not support short container image name out of box - try to make a full name
+    if ":" in image_name:
+        image_repository, image_tag = image_name.split(":", 1)
+    else:
+        image_repository = image_name
+        image_tag = "latest"
+    if "/" not in image_repository:
+        image_repository = "library/" + image_repository
+    if len(image_repository.split("/")) < 3:
+        image_repository = "docker.io/" + image_repository
+    return image_repository, image_tag
diff --git a/tests/test_utilities/test_docker_utilities.py b/tests/test_utilities/test_docker_utilities.py
new file mode 100644
index 000000000000..4c1a05b0300c
--- /dev/null
+++ b/tests/test_utilities/test_docker_utilities.py
@@ -0,0 +1,20 @@
+import sure
+import pytest
+
+from moto.utilities.docker_utilities import parse_image_name
+
+
+@pytest.mark.parametrize(
+    "image_name,expected",
+    [
+        ("python", ("docker.io/library/python", "latest")),
+        ("python:3.9", ("docker.io/library/python", "3.9")),
+        ("lambci/lambda:python2.7", ("docker.io/lambci/lambda", "python2.7")),
+        (
+            "gcr.io/google.com/cloudsdktool/cloud-sdk",
+            ("gcr.io/google.com/cloudsdktool/cloud-sdk", "latest"),
+        ),
+    ],
+)
+def test_parse_image_name(image_name, expected):
+    expected.should.be.equal(parse_image_name(image_name))


Code review comments:
diff_hunk: @@ -31,3 +31,17 @@ def replace_adapter_send(*args, **kwargs):
 
                 self.docker_client.api.get_adapter = replace_adapter_send
         return self.__docker_client
+
+
+def parse_image_name(image_name):
+    # podman does not support short container image name out of box - try to make a full name
+    if ":" in image_name:
+        image_repository, image_tag = image_name.split(":", 1)
+    else:
+        image_repository = image_name
+        image_tag = "latest"
+    if "/" not in image_repository:
+        image_repository = "library/" + image_repository
+    if len(image_repository.split("/")) < 3:
+        image_repository = "docker.io/" + image_repository
path: moto/utilities/docker_utilities.py
text: @user1:
Can you make the `docker.io` configurable, just in case people need to use a different repository? Ideally it's an env-variable in settings.py

@author:
Sure! I've also found a bug in this function. Will update both soon.

@author:
Done! I added `$DEFAULT_CONTAINER_REGISTRY`. Please tell me if you want a better name.

diff_hunk: @@ -131,6 +131,7 @@ def __enter__(self):
                 volumes = {self.name: {"bind": "/tmp/data", "mode": "rw"}}
             else:
                 volumes = {self.name: "/tmp/data"}
+            self._lambda_func.docker_client.images.pull("docker.io/library/alpine")
path: moto/awslambda/models.py
text: @user1:
Let's use the parse_image_name method here as well, i.e. `parse_image_name("alpine")` and `parse_image_name("lambci/lambda")`

@author:
Done!