gabrieldemarmiesse / python-on-whales

An awesome Python wrapper for an awesome Docker CLI!
MIT License
517 stars 100 forks source link

`DockerClient.info()` doesn't work with podman #592

Open LewisGaul opened 2 months ago

LewisGaul commented 2 months ago
In [2]: import python_on_whales as pow
In [3]: podman = pow.DockerClient(client_call=["podman"])
In [4]: podman.info()
Out[4]: SystemInfo(id=None, containers=None, containers_running=None, containers_paused=None, containers_stopped=None, images=None, driver=None, driver_status=None, docker_root_dir=None, system_status=None, plugins=Plugins(volume=['local'], network=['bridge', 'macvlan'], authorization=None, log=['k8s-file', 'none', 'journald']), memory_limit=None, swap_limit=None, kernel_memory=None, cpu_cfs_period=None, cpu_cfs_quota=None, cpu_shares=None, cpu_set=None, pids_limit=None, oom_kill_disable=None, ipv4_forwarding=None, bridge_nf_iptables=None, bridge_nf_ip6tables=None, debug=None, nfd=None, n_goroutines=None, system_time=None, logging_driver=None, cgroup_driver=None, n_events_listener=None, kernel_version=None, operating_system=None, os_type=None, architecture=None, n_cpu=None, mem_total=None, index_server_address=None, registry_config=None, generic_resources=None, http_proxy=None, https_proxy=None, no_proxy=None, name=None, labels=None, experimental_build=None, server_version=None, cluster_store=None, runtimes=None, default_runtime=None, swarm=None, live_restore_enabled=None, isolation=None, init_binary=None, containerd_commit=None, runc_commit=None, init_commit=None, security_options=None, product_license=None, warnings=None, client_info=None)

This is arguably podman's fault for not maintaining compatibility with this command :(

Here's what you get from podman:

$podman info -f '{{json .}}' | jq
{
  "host": {
    "arch": "amd64",
    "buildahVersion": "1.23.1",
    "cgroupManager": "cgroupfs",
    "cgroupVersion": "v1",
    "cgroupControllers": [],
    "conmon": {
      "package": "conmon: /usr/libexec/podman/conmon",
      "path": "/usr/libexec/podman/conmon",
      "version": "conmon version 2.1.0, commit: "
    },
    "cpus": 8,
    "distribution": {
      "distribution": "ubuntu",
      "version": "20.04",
      "codename": "focal"
    },
    "eventLogger": "file",
    "hostname": "CSCO-W-PF34CGA7",
    "idMappings": {
      "gidmap": [
        {
          "container_id": 0,
          "host_id": 1000,
          "size": 1
        },
        {
          "container_id": 1,
          "host_id": 100000,
          "size": 65536
        }
      ],
      "uidmap": [
        {
          "container_id": 0,
          "host_id": 1000,
          "size": 1
        },
        {
          "container_id": 1,
          "host_id": 100000,
          "size": 65536
        }
      ]
    },
    "kernel": "5.15.133.1-microsoft-standard-WSL2",
    "logDriver": "k8s-file",
    "memFree": 6798467072,
    "memTotal": 8176771072,
    "ociRuntime": {
      "name": "crun",
      "package": "crun: /usr/bin/crun",
      "path": "/usr/bin/crun",
      "version": "crun version UNKNOWN\ncommit: ea1fe3938eefa14eb707f1d22adff4db670645d6\nspec: 1.0.0\n+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL"
    },
    "os": "linux",
    "remoteSocket": {
      "path": "/mnt/wslg/runtime-dir/podman/podman.sock"
    },
    "serviceIsRemote": false,
    "security": {
      "apparmorEnabled": false,
      "capabilities": "CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER,CAP_FSETID,CAP_KILL,CAP_NET_BIND_SERVICE,CAP_SETFCAP,CAP_SETGID,CAP_SETPCAP,CAP_SETUID,CAP_SYS_CHROOT",
      "rootless": true,
      "seccompEnabled": true,
      "seccompProfilePath": "/usr/share/containers/seccomp.json",
      "selinuxEnabled": false
    },
    "slirp4netns": {
      "executable": "/usr/bin/slirp4netns",
      "package": "slirp4netns: /usr/bin/slirp4netns",
      "version": "slirp4netns version 1.1.8\ncommit: unknown\nlibslirp: 4.3.1-git\nSLIRP_CONFIG_VERSION_MAX: 3\nlibseccomp: 2.4.3"
    },
    "swapFree": 2147483648,
    "swapTotal": 2147483648,
    "uptime": "100h 57m 37.95s (Approximately 4.17 days)",
    "linkmode": "dynamic"
  },
  "store": {
    "configFile": "/home/legaul/.config/containers/storage.conf",
    "containerStore": {
      "number": 1,
      "paused": 0,
      "running": 1,
      "stopped": 0
    },
    "graphDriverName": "overlay",
    "graphOptions": {
      "overlay.mount_program": {
        "Executable": "/usr/bin/fuse-overlayfs",
        "Package": "fuse-overlayfs: /usr/bin/fuse-overlayfs",
        "Version": "fusermount3 version: 3.9.0\nfuse-overlayfs: version 1.5\nFUSE library version 3.9.0\nusing FUSE kernel interface version 7.31"
      }
    },
    "graphRoot": "/home/legaul/.local/share/containers/storage",
    "graphStatus": {
      "Backing Filesystem": "extfs",
      "Native Overlay Diff": "false",
      "Supports d_type": "true",
      "Using metacopy": "false"
    },
    "imageStore": {
      "number": 15
    },
    "runRoot": "/mnt/wslg/runtime-dir/containers",
    "volumePath": "/home/legaul/.local/share/containers/storage/volumes"
  },
  "registries": {
    "search": [
      "docker.io",
      "quay.io"
    ]
  },
  "plugins": {
    "volume": [
      "local"
    ],
    "network": [
      "bridge",
      "macvlan"
    ],
    "log": [
      "k8s-file",
      "none",
      "journald"
    ]
  },
  "version": {
    "APIVersion": "3.4.2",
    "Version": "3.4.2",
    "GoVersion": "go1.15.2",
    "GitCommit": "",
    "BuiltTime": "Thu Jan  1 01:00:00 1970",
    "Built": 0,
    "OsArch": "linux/amd64"
  }
}

Is there anything we can do about this in PoW, or is the statement that we'll only support CLIs compatible with docker?

gabrieldemarmiesse commented 2 months ago

It makes sense that the info is different as podman and docker work differently internally, so I'm not blaming them for this.

What I see is that we may need a better support for docker/podman differences. Looking at the popularity of Docker vs Podman: https://github.com/moby/moby vs https://github.com/containers/podman, I think it's fair to say that we should also support the kwirks of podman correctly.

Seeing long-term, I think we'll need better support for the podman/docker differences, but at the moment, would it be possible to fit the information of podman info into the DockerInfo pydantic model? Some fields won't be equivalent, but we can do a best effort here. What do you think?

LewisGaul commented 2 months ago

What I see is that we may need a better support for docker/podman differences. Looking at the popularity of Docker vs Podman: https://github.com/moby/moby vs https://github.com/containers/podman, I think it's fair to say that we should also support the kwirks of podman correctly.

I'm happy to hear this :)

Seeing long-term, I think we'll need better support for the podman/docker differences, but at the moment, would it be possible to fit the information of podman info into the DockerInfo pydantic model? Some fields won't be equivalent, but we can do a best effort here. What do you think?

I'm not particularly keen on this idea, I'm not sure stuff would map very well and it would create transition friction if we then switch to handling it in a more complete way.

Do you have any initial thoughts on how we might support docker/podman differences in cases like this? One option would be to just return different objects based on which is being used, e.g. DockerSystemInfo or PodmanSystemInfo, which the caller would then have to handle appropriately. Alternatively something more like the pathlib approach for Posix/Windows paths where you get either a DockerClient or PodmanClient object. The latter would probably be my preferred option, where we'd then just need to get the class hierarchy right to allow code sharing where desired.

gabrieldemarmiesse commented 2 months ago

I'm not particularly keen on this idea, I'm not sure stuff would map very well and it would create transition friction if we then switch to handling it in a more complete way. I understand, I saw this more as a quick win solution. But yeah if it creates more problems than it solves, let's drop it.

Concerning the option with DockerCLient and PodmanClient, indeed the whole difficulty will be with the class hierarchy.

One solution we can explore too, would be to make DockerClient generic, like list or dict. See https://mypy.readthedocs.io/en/stable/generics.html for a detailed explanation. We could have DockerClient["podman"] and DockerClient["docker"] and return the correct type depending on the situation.