From 56e691c662388ac549f0cb5716a2e349f27c6612 Mon Sep 17 00:00:00 2001 From: christophe-duc Date: Wed, 7 Jan 2026 08:56:09 -0400 Subject: [PATCH] executed phase2 of the plan --- go.mod | 200 +++++++-- go.sum | 541 +++++++++++++++++++---- main.go | 4 +- pkg/app/app.go | 16 +- pkg/commands/container.go | 77 ++-- pkg/commands/docker.go | 425 ------------------ pkg/commands/docker_host_unix.go | 7 - pkg/commands/docker_host_windows.go | 5 - pkg/commands/dummies.go | 12 +- pkg/commands/errors.go | 16 + pkg/commands/image.go | 79 +--- pkg/commands/network.go | 43 +- pkg/commands/podman.go | 650 ++++++++++++++++++++++++++++ pkg/commands/podman_host_unix.go | 48 ++ pkg/commands/podman_host_windows.go | 25 ++ pkg/commands/runtime.go | 48 ++ pkg/commands/runtime_libpod.go | 517 ++++++++++++++++++++++ pkg/commands/runtime_libpod_stub.go | 137 ++++++ pkg/commands/runtime_socket.go | 535 +++++++++++++++++++++++ pkg/commands/runtime_types.go | 426 ++++++++++++++++++ pkg/commands/service.go | 15 +- pkg/commands/volume.go | 45 +- pkg/gui/container_logs.go | 72 ++- pkg/gui/containers_panel.go | 30 +- pkg/gui/gui.go | 68 ++- pkg/gui/images_panel.go | 41 +- pkg/gui/networks_panel.go | 8 +- pkg/gui/project_panel.go | 10 +- pkg/gui/services_panel.go | 22 +- pkg/gui/views.go | 2 +- pkg/gui/volumes_panel.go | 8 +- 31 files changed, 3210 insertions(+), 922 deletions(-) delete mode 100644 pkg/commands/docker.go delete mode 100644 pkg/commands/docker_host_unix.go delete mode 100644 pkg/commands/docker_host_windows.go create mode 100644 pkg/commands/podman.go create mode 100644 pkg/commands/podman_host_unix.go create mode 100644 pkg/commands/podman_host_windows.go create mode 100644 pkg/commands/runtime.go create mode 100644 pkg/commands/runtime_libpod.go create mode 100644 pkg/commands/runtime_libpod_stub.go create mode 100644 pkg/commands/runtime_socket.go create mode 100644 pkg/commands/runtime_types.go diff --git a/go.mod b/go.mod index ab347d2f..b43cccdd 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,14 @@ module github.com/christophe-duc/lazypodman -go 1.22 - -toolchain go1.23.6 +go 1.24.2 require ( github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1 github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 - github.com/docker/cli v27.1.1+incompatible - github.com/docker/docker v28.5.2+incompatible - github.com/fatih/color v1.10.0 + github.com/containers/podman/v5 v5.7.1 + github.com/docker/docker v28.5.1+incompatible + github.com/fatih/color v1.15.0 github.com/go-errors/errors v1.5.1 github.com/gookit/color v1.5.0 github.com/imdario/mergo v0.3.16 @@ -20,66 +18,180 @@ require ( github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/lazycore v0.0.0-20221023210126-718a4caea996 github.com/jesseduffield/yaml v0.0.0-20190702115811-b900b7e08b56 - github.com/mattn/go-runewidth v0.0.15 + github.com/mattn/go-runewidth v0.0.16 github.com/mcuadros/go-lookup v0.0.0-20171110082742-5650f26be767 github.com/mgutz/str v1.2.0 - github.com/pmezard/go-difflib v1.0.0 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/samber/lo v1.31.0 - github.com/sasha-s/go-deadlock v0.3.1 + github.com/sasha-s/go-deadlock v0.3.5 github.com/sirupsen/logrus v1.9.3 github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.11.1 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/checkpoint-restore/checkpointctl v1.4.0 // indirect + github.com/checkpoint-restore/go-criu/v7 v7.2.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v1.0.0-rc.1 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.17.0 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/containernetworking/cni v1.3.0 // indirect + github.com/containernetworking/plugins v1.8.0 // indirect + github.com/containers/buildah v1.42.2 // indirect + github.com/containers/conmon v2.0.20+incompatible // indirect + github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect + github.com/containers/luksy v0.0.0-20250910190358-2cf5bc928957 // indirect + github.com/containers/ocicrypt v1.2.1 // indirect + github.com/containers/psgo v1.9.1-0.20250826150930-4ae76f200c86 // indirect + github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect + github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect + github.com/cyphar/filepath-securejoin v0.5.2 // indirect + github.com/disiqueira/gotree/v3 v3.0.2 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.4 // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsouza/go-dockerclient v1.12.2 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.1-0.20241109141217-c266b19b28e9 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-containerregistry v0.20.6 // indirect + github.com/google/go-intervals v0.0.2 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/schema v1.4.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hpcloud/tail v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jinzhu/copier v0.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect + github.com/kr/fs v0.1.0 // indirect + github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect + github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect + github.com/mistifyio/go-zfs/v3 v3.1.0 // indirect + github.com/moby/buildkit v0.25.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/capability v0.4.0 // indirect + github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/nxadm/tail v1.4.11 // indirect + github.com/opencontainers/cgroups v0.0.5 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/opencontainers/runc v1.3.4 // indirect + github.com/opencontainers/runtime-spec v1.2.1 // indirect + github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2 // indirect + github.com/opencontainers/selinux v1.13.1 // indirect + github.com/openshift/imagebuilder v1.2.19 // indirect + github.com/pkg/sftp v1.13.9 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/proglottis/gpgme v0.1.5 // indirect + github.com/seccomp/libseccomp-golang v0.11.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect + github.com/shirou/gopsutil/v4 v4.25.9 // indirect + github.com/sigstore/fulcio v1.7.1 // indirect + github.com/sigstore/protobuf-specs v0.4.1 // indirect + github.com/sigstore/sigstore v1.9.5 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/smallstep/pkcs7 v0.1.1 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect + github.com/sylabs/sif/v2 v2.22.0 // indirect + github.com/tchap/go-patricia/v2 v2.3.3 // indirect + github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect + github.com/vbatts/tar-split v0.12.1 // indirect + github.com/vbauerster/mpb/v8 v8.10.2 // indirect + github.com/vishvananda/netlink v1.3.1 // indirect + github.com/vishvananda/netns v0.0.5 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.etcd.io/bbolt v1.4.3 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.podman.io/common v0.66.1 // indirect + go.podman.io/image/v5 v5.38.0 // indirect + go.podman.io/storage v1.61.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect + google.golang.org/grpc v1.72.2 // indirect + google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/fsnotify.v1 v1.4.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect + tags.cncf.io/container-device-interface v1.0.1 // indirect + tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect ) require ( - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fvbommel/sortorder v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/tcell/v2 v2.7.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/goccy/go-yaml v1.11.0 github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/onsi/ginkgo v1.8.0 // indirect - github.com/onsi/gomega v1.5.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 4f20d188..be01da8b 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,46 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c h1:YDsGA6tou+tAxVe0Dre29iSbQ8TrWdWfwOisKArJT5E= github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c/go.mod h1:tMoSueLQlMf0TCldjrJLNIjAc5qAOIcHt5REi88/Ygo= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpYk2gxGJnDjsYuboNTcRmbtGKGs= +github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1 h1:1fx+RA5lk1ZkzPAUP7DEgZnVHYxEcHO77vQO/V8z/2Q= github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1/go.mod h1:z0nyIb42Zs97wyX1V+8MbEFhHeTw1OgFQfR6q57ZuHc= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/checkpointctl v1.4.0 h1:3kRns56TArwiyHOMakaumUgSZZlB1hZBkjVgR6IeZ3E= +github.com/checkpoint-restore/checkpointctl v1.4.0/go.mod h1:ynQ52zQBazgcTZuxpwTFzRinIcAf0haDTC1X1LA/FKA= +github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo= +github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -16,28 +49,76 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= +github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE= +github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo= +github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4= +github.com/containernetworking/plugins v1.8.0 h1:WjGbV/0UQyo8A4qBsAh6GaDAtu1hevxVxsEuqtBqUFk= +github.com/containernetworking/plugins v1.8.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c= +github.com/containers/buildah v1.42.2 h1:be4mKtMOtvuW3R1TYWP+MupxzCaq6PRn7+m1iZH9YbE= +github.com/containers/buildah v1.42.2/go.mod h1:SDA+ClXamnZPV7GBS2uKY0dfXKfvTykSUA+kJWa1mNg= +github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= +github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= +github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= +github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= +github.com/containers/luksy v0.0.0-20250910190358-2cf5bc928957 h1:WxixhZ0typ8o668V0V7RVCZb3lNs58UF42RbwlQ4vdE= +github.com/containers/luksy v0.0.0-20250910190358-2cf5bc928957/go.mod h1:fGPsLPRi1etbHfe5o6sdx6ajsW810tI43uyF6ugmP/o= +github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= +github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= +github.com/containers/podman/v5 v5.7.1 h1:rxt3Fl3JS85ZnWZU7jTqR9YOk9fYFqI/2Gy6UMooAhk= +github.com/containers/podman/v5 v5.7.1/go.mod h1:+3SYEQiTjid9pnoFMF8jKWVOymTzIQCFGDsZ28b5nqw= +github.com/containers/psgo v1.9.1-0.20250826150930-4ae76f200c86 h1:bYj0TVlkRZtMJYd6SbFOi1gjUJDJmVsYCpJla3URD7Y= +github.com/containers/psgo v1.9.1-0.20250826150930-4ae76f200c86/go.mod h1:52GX23ST30pXpeviDvNpOCXoHqr+WJDv+7BjEH+AabY= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= +github.com/cyphar/filepath-securejoin v0.5.2 h1:w/T2bhKr4pgwG0SUGjU4S/Is9+zUknLh5ROTJLzWX8E= +github.com/cyphar/filepath-securejoin v0.5.2/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWhkNRq8= +github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= -github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= -github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw59yGVc+yY= +github.com/docker/cli v28.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI= +github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8 h1:IMfrF5LCzP2Vhw7j4IIH3HxPsCLuZYjDqFAM/C88ulg= +github.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8/go.mod h1:LFyLie6XcDbyKGeVK6bHe+9aJTYCxWLBg5IrJZOaXKA= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= +github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= -github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsouza/go-dockerclient v1.12.2 h1:+pbP/SacoHfqaVZuiudvcdYGd9jzU7y9EcgoBOHivEI= +github.com/fsouza/go-dockerclient v1.12.2/go.mod h1:ZGCkAsnBGjnTRG9wV6QaICPJ5ig2KlaxTccDQy5WQ38= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= @@ -46,32 +127,68 @@ github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7 github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/godbus/dbus/v5 v5.1.1-0.20241109141217-c266b19b28e9 h1:Kzr9J0S0V2PRxiX6B6xw1kWjzsIyjLO2Ibi4fNTaYBM= +github.com/godbus/dbus/v5 v5.1.1-0.20241109141217-c266b19b28e9/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU= +github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y= +github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM= +github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= +github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM= github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI= github.com/jesseduffield/asciigraph v0.0.0-20190605104717-6d88e39309ee h1:7Zi/OQlGbMz4MT2V1+prN/gv1C64NDyVb/MbJnS0ZfA= @@ -84,164 +201,396 @@ github.com/jesseduffield/lazycore v0.0.0-20221023210126-718a4caea996 h1:CH1en6Gp github.com/jesseduffield/lazycore v0.0.0-20221023210126-718a4caea996/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ= github.com/jesseduffield/yaml v0.0.0-20190702115811-b900b7e08b56 h1:33wSxJWU/f2TAozHYtJ8zqBxEnEVYM+22moLoiAkxvg= github.com/jesseduffield/yaml v0.0.0-20190702115811-b900b7e08b56/go.mod h1:FZJBwOhE+RXz8EVZfY+xnbCw2cVOwxlK3/aIi581z/s= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= +github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ= +github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mcuadros/go-lookup v0.0.0-20171110082742-5650f26be767 h1:BrhJNdEFWGuiJk/3/SwsG5Rex3zjFxYsDi2bpd7382Y= github.com/mcuadros/go-lookup v0.0.0-20171110082742-5650f26be767/go.mod h1:ct+byCpkFokm4J0tiuAvB8cf2ttm6GcCe89Yr25nGKg= github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw= github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs/v3 v3.1.0 h1:FZaylcg0hjUp27i23VcJJQiuBeAZjrC8lPqCGM1CopY= +github.com/mistifyio/go-zfs/v3 v3.1.0/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k= +github.com/moby/buildkit v0.25.1 h1:j7IlVkeNbEo+ZLoxdudYCHpmTsbwKvhgc/6UJ/mY/o8= +github.com/moby/buildkit v0.25.1/go.mod h1:phM8sdqnvgK2y1dPDnbwI6veUCXHOZ6KFSl6E164tkc= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= +github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE= +github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/opencontainers/cgroups v0.0.5 h1:DRITAqcOnY0uSBzIpt1RYWLjh5DPDiqUs4fY6Y0ktls= +github.com/opencontainers/cgroups v0.0.5/go.mod h1:oWVzJsKK0gG9SCRBfTpnn16WcGEqDI8PAcpMGbqWxcs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= -github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runc v1.3.4 h1:+lwmPUTzbgv0JFqu8zBU2WtHYbm+JPPS9hxB/PvWd30= +github.com/opencontainers/runc v1.3.4/go.mod h1:o1wyv76EDlTkcf0KTFgN8bMWLPvgF/HfX709lDv+rr4= +github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= +github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2 h1:2xZEHOdeQBV6PW8ZtimN863bIOl7OCW/X10K0cnxKeA= +github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2/go.mod h1:MXdPzqAA8pHC58USHqNCSjyLnRQ6D+NjbpP+02Z1U/0= +github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= +github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= +github.com/openshift/imagebuilder v1.2.19 h1:Xqq36KMJgsRU2MPaLRML23Myvk+AaY8pE8VJ6m6Vmy4= +github.com/openshift/imagebuilder v1.2.19/go.mod h1:fdbnfQWjxMBoB/jrvEzUk+UT1zqvtZZj7oQ7GU6RD9I= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw= +github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/proglottis/gpgme v0.1.5 h1:KCGyOw8sQ+SI96j6G8D8YkOGn+1TwbQTT9/zQXoVlz0= +github.com/proglottis/gpgme v0.1.5/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM= github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= -github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= -github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E= +github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/seccomp/libseccomp-golang v0.11.1 h1:wuk4ZjSx6kyQII4rj6G6fvVzRHQaSiPvccJazDagu4g= +github.com/seccomp/libseccomp-golang v0.11.1/go.mod h1:5m1Lk8E9OwgZTTVz4bBOer7JuazaBa+xTkM895tDiWc= +github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g= +github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= +github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= +github.com/sigstore/fulcio v1.7.1 h1:RcoW20Nz49IGeZyu3y9QYhyyV3ZKQ85T+FXPKkvE+aQ= +github.com/sigstore/fulcio v1.7.1/go.mod h1:7lYY+hsd8Dt+IvKQRC+KEhWpCZ/GlmNvwIa5JhypMS8= +github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc= +github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= +github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU= +github.com/sigstore/sigstore v1.9.5/go.mod h1:VtxgvGqCmEZN9X2zhFSOkfXxvKUjpy8RpUW39oCtoII= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU= +github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/sylabs/sif/v2 v2.22.0 h1:Y+xXufp4RdgZe02SR3nWEg7S6q4tPWN237WHYzkDSKA= +github.com/sylabs/sif/v2 v2.22.0/go.mod h1:W1XhWTmG1KcG7j5a3KSYdMcUIFvbs240w/MMVW627hs= +github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= +github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= +github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM= +github.com/vbauerster/mpb/v8 v8.10.2/go.mod h1:+Ja4P92E3/CorSZgfDtK46D7AVbDqmBQRTmyTqPElo0= +github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= +github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.podman.io/common v0.66.1 h1:zDyd4HhVgQAN8LupBHCnhtM3FEOJ9DwmThjulXZq2qA= +go.podman.io/common v0.66.1/go.mod h1:aNd2a0S7pY+fx1X5kpQYuF4hbwLU8ZOccuVrhu7h1Xc= +go.podman.io/image/v5 v5.38.0 h1:aUKrCANkPvze1bnhLJsaubcfz0d9v/bSDLnwsXJm6G4= +go.podman.io/image/v5 v5.38.0/go.mod h1:hSIoIUzgBnmc4DjoIdzk63aloqVbD7QXDMkSE/cvG90= +go.podman.io/storage v1.61.0 h1:5hD/oyRYt1f1gxgvect+8syZBQhGhV28dCw2+CZpx0Q= +go.podman.io/storage v1.61.0/go.mod h1:A3UBK0XypjNZ6pghRhuxg62+2NIm5lcUGv/7XyMhMUI= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY= +google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc= +tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0= +tags.cncf.io/container-device-interface/specs-go v1.0.0 h1:8gLw29hH1ZQP9K1YtAzpvkHCjjyIxHZYzBAvlQ+0vD8= +tags.cncf.io/container-device-interface/specs-go v1.0.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ= diff --git a/main.go b/main.go index f1734a16..f783fd3c 100644 --- a/main.go +++ b/main.go @@ -8,10 +8,10 @@ import ( "runtime" "runtime/debug" - "github.com/docker/docker/client" "github.com/go-errors/errors" "github.com/integrii/flaggy" "github.com/christophe-duc/lazypodman/pkg/app" + "github.com/christophe-duc/lazypodman/pkg/commands" "github.com/christophe-duc/lazypodman/pkg/config" "github.com/christophe-duc/lazypodman/pkg/utils" "github.com/jesseduffield/yaml" @@ -88,7 +88,7 @@ func main() { os.Exit(0) } - if client.IsErrConnectionFailed(err) { + if commands.IsConnectionFailed(err) { log.Println(app.Tr.ConnectionFailed) os.Exit(0) } diff --git a/pkg/app/app.go b/pkg/app/app.go index 8e7385f3..d4529634 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -20,7 +20,7 @@ type App struct { Config *config.AppConfig Log *logrus.Entry OSCommand *commands.OSCommand - DockerCommand *commands.DockerCommand + PodmanCommand *commands.PodmanCommand Gui *gui.Gui Tr *i18n.TranslationSet ErrorChan chan error @@ -43,12 +43,12 @@ func NewApp(config *config.AppConfig) (*App, error) { // here is the place to make use of the docker-compose.yml file in the current directory - app.DockerCommand, err = commands.NewDockerCommand(app.Log, app.OSCommand, app.Tr, app.Config, app.ErrorChan) + app.PodmanCommand, err = commands.NewPodmanCommand(app.Log, app.OSCommand, app.Tr, app.Config, app.ErrorChan) if err != nil { return app, err } - app.closers = append(app.closers, app.DockerCommand) - app.Gui, err = gui.NewGui(app.Log, app.DockerCommand, app.OSCommand, app.Tr, config, app.ErrorChan) + app.closers = append(app.closers, app.PodmanCommand) + app.Gui, err = gui.NewGui(app.Log, app.PodmanCommand, app.OSCommand, app.Tr, config, app.ErrorChan) if err != nil { return app, err } @@ -77,6 +77,14 @@ func (app *App) KnownError(err error) (string, bool) { originalError: "Got permission denied while trying to connect to the Docker daemon socket", newError: app.Tr.CannotAccessDockerSocketError, }, + { + originalError: "Got permission denied while trying to connect to the Podman", + newError: app.Tr.CannotAccessDockerSocketError, + }, + { + originalError: "permission denied while trying to connect", + newError: app.Tr.CannotAccessDockerSocketError, + }, } for _, mapping := range mappings { diff --git a/pkg/commands/container.go b/pkg/commands/container.go index 28990cfe..d4e00609 100644 --- a/pkg/commands/container.go +++ b/pkg/commands/container.go @@ -6,9 +6,6 @@ import ( "os/exec" "strings" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/client" "github.com/go-errors/errors" "github.com/christophe-duc/lazypodman/pkg/i18n" "github.com/christophe-duc/lazypodman/pkg/utils" @@ -17,7 +14,7 @@ import ( "golang.org/x/xerrors" ) -// Container : A docker Container +// Container represents a Podman container type Container struct { Name string ServiceName string @@ -27,24 +24,26 @@ type Container struct { OneOff bool ProjectName string ID string - Container container.Summary - Client *client.Client + Summary ContainerSummary + Runtime ContainerRuntime OSCommand *OSCommand Log *logrus.Entry StatHistory []*RecordedStats - Details container.InspectResponse + Details *ContainerDetails MonitoringStats bool - DockerCommand LimitedDockerCommand + PodmanCommand LimitedPodmanCommand Tr *i18n.TranslationSet StatsMutex deadlock.Mutex } // Remove removes the container -func (c *Container) Remove(options container.RemoveOptions) error { +func (c *Container) Remove(force bool, removeVolumes bool) error { c.Log.Warn(fmt.Sprintf("removing container %s", c.Name)) - if err := c.Client.ContainerRemove(context.Background(), c.ID, options); err != nil { - if strings.Contains(err.Error(), "Stop the container before attempting removal or force remove") { + ctx := context.Background() + if err := c.Runtime.RemoveContainer(ctx, c.ID, force, removeVolumes); err != nil { + if strings.Contains(err.Error(), "Stop the container before attempting removal or force remove") || + strings.Contains(err.Error(), "container is running") { return ComplexError{ Code: MustStopContainer, Message: err.Error(), @@ -60,25 +59,29 @@ func (c *Container) Remove(options container.RemoveOptions) error { // Stop stops the container func (c *Container) Stop() error { c.Log.Warn(fmt.Sprintf("stopping container %s", c.Name)) - return c.Client.ContainerStop(context.Background(), c.ID, container.StopOptions{}) + ctx := context.Background() + return c.Runtime.StopContainer(ctx, c.ID, nil) } // Pause pauses the container func (c *Container) Pause() error { c.Log.Warn(fmt.Sprintf("pausing container %s", c.Name)) - return c.Client.ContainerPause(context.Background(), c.ID) + ctx := context.Background() + return c.Runtime.PauseContainer(ctx, c.ID) } // Unpause unpauses the container func (c *Container) Unpause() error { c.Log.Warn(fmt.Sprintf("unpausing container %s", c.Name)) - return c.Client.ContainerUnpause(context.Background(), c.ID) + ctx := context.Background() + return c.Runtime.UnpauseContainer(ctx, c.ID) } // Restart restarts the container func (c *Container) Restart() error { c.Log.Warn(fmt.Sprintf("restarting container %s", c.Name)) - return c.Client.ContainerRestart(context.Background(), c.ID, container.StopOptions{}) + ctx := context.Background() + return c.Runtime.RestartContainer(ctx, c.ID, nil) } // Attach attaches the container @@ -88,44 +91,47 @@ func (c *Container) Attach() (*exec.Cmd, error) { } // verify that we can in fact attach to this container - if !c.Details.Config.OpenStdin { + if c.Details.Config != nil && !c.Details.Config.OpenStdin { return nil, errors.New(c.Tr.UnattachableContainerError) } - if c.Container.State == "exited" { + if c.Summary.State == "exited" { return nil, errors.New(c.Tr.CannotAttachStoppedContainerError) } c.Log.Warn(fmt.Sprintf("attaching to container %s", c.Name)) - // TODO: use SDK - cmd := c.OSCommand.NewCmd("docker", "attach", "--sig-proxy=false", c.ID) + // Use podman attach command + cmd := c.OSCommand.NewCmd("podman", "attach", "--sig-proxy=false", c.ID) return cmd, nil } // Top returns process information -func (c *Container) Top(ctx context.Context) (container.TopResponse, error) { - detail, err := c.Inspect() +func (c *Container) Top(ctx context.Context) (TopResponse, error) { + details, err := c.Inspect() if err != nil { - return container.TopResponse{}, err + return TopResponse{}, err } // check container status - if !detail.State.Running { - return container.TopResponse{}, errors.New("container is not running") + if details.State == nil || !details.State.Running { + return TopResponse{}, errors.New("container is not running") } - return c.Client.ContainerTop(ctx, c.ID, []string{}) -} + titles, processes, err := c.Runtime.ContainerTop(ctx, c.ID) + if err != nil { + return TopResponse{}, err + } -// PruneContainers prunes containers -func (c *DockerCommand) PruneContainers() error { - _, err := c.Client.ContainersPrune(context.Background(), filters.Args{}) - return err + return TopResponse{ + Titles: titles, + Processes: processes, + }, nil } // Inspect returns details about the container -func (c *Container) Inspect() (container.InspectResponse, error) { - return c.Client.ContainerInspect(context.Background(), c.ID) +func (c *Container) Inspect() (*ContainerDetails, error) { + ctx := context.Background() + return c.Runtime.InspectContainer(ctx, c.ID) } // RenderTop returns details about the container @@ -142,5 +148,10 @@ func (c *Container) RenderTop(ctx context.Context) (string, error) { // Sometimes it takes some time for a container to have its details loaded // after it starts. func (c *Container) DetailsLoaded() bool { - return c.Details.ContainerJSONBase != nil + return c.Details != nil +} + +// GetState returns the container state from the Summary +func (c *Container) GetState() string { + return c.Summary.State } diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go deleted file mode 100644 index a63355e6..00000000 --- a/pkg/commands/docker.go +++ /dev/null @@ -1,425 +0,0 @@ -package commands - -import ( - "bufio" - "context" - "encoding/json" - "fmt" - "io" - ogLog "log" - "os" - "os/exec" - "strings" - "sync" - "time" - - cliconfig "github.com/docker/cli/cli/config" - ddocker "github.com/docker/cli/cli/context/docker" - ctxstore "github.com/docker/cli/cli/context/store" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" - "github.com/imdario/mergo" - "github.com/christophe-duc/lazypodman/pkg/commands/ssh" - "github.com/christophe-duc/lazypodman/pkg/config" - "github.com/christophe-duc/lazypodman/pkg/i18n" - "github.com/christophe-duc/lazypodman/pkg/utils" - "github.com/sasha-s/go-deadlock" - "github.com/sirupsen/logrus" -) - -const ( - dockerHostEnvKey = "DOCKER_HOST" -) - -// DockerCommand is our main docker interface -type DockerCommand struct { - Log *logrus.Entry - OSCommand *OSCommand - Tr *i18n.TranslationSet - Config *config.AppConfig - Client *client.Client - InDockerComposeProject bool - ErrorChan chan error - ContainerMutex deadlock.Mutex - ServiceMutex deadlock.Mutex - - Closers []io.Closer -} - -var _ io.Closer = &DockerCommand{} - -// LimitedDockerCommand is a stripped-down DockerCommand with just the methods the container/service/image might need -type LimitedDockerCommand interface { - NewCommandObject(CommandObject) CommandObject -} - -// CommandObject is what we pass to our template resolvers when we are running a custom command. We do not guarantee that all fields will be populated: just the ones that make sense for the current context -type CommandObject struct { - DockerCompose string - Service *Service - Container *Container - Image *Image - Volume *Volume - Network *Network -} - -// NewCommandObject takes a command object and returns a default command object with the passed command object merged in -func (c *DockerCommand) NewCommandObject(obj CommandObject) CommandObject { - defaultObj := CommandObject{DockerCompose: c.Config.UserConfig.CommandTemplates.DockerCompose} - _ = mergo.Merge(&defaultObj, obj) - return defaultObj -} - -// NewDockerCommand it runs docker commands -func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) { - dockerHost, err := determineDockerHost() - if err != nil { - ogLog.Printf("> could not determine host %v", err) - } - - // NOTE: Inject the determined docker host to the environment. This allows the - // `SSHHandler.HandleSSHDockerHost()` to create a local unix socket tunneled - // over SSH to the specified ssh host. - if strings.HasPrefix(dockerHost, "ssh://") { - os.Setenv(dockerHostEnvKey, dockerHost) - } - - tunnelCloser, err := ssh.NewSSHHandler(osCommand).HandleSSHDockerHost() - if err != nil { - ogLog.Fatal(err) - } - - // Retrieve the docker host from the environment which could have been set by - // the `SSHHandler.HandleSSHDockerHost()` and override `dockerHost`. - dockerHostFromEnv := os.Getenv(dockerHostEnvKey) - if dockerHostFromEnv != "" { - dockerHost = dockerHostFromEnv - } - - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithHost(dockerHost)) - if err != nil { - ogLog.Fatal(err) - } - - dockerCommand := &DockerCommand{ - Log: log, - OSCommand: osCommand, - Tr: tr, - Config: config, - Client: cli, - ErrorChan: errorChan, - InDockerComposeProject: true, - Closers: []io.Closer{tunnelCloser}, - } - - dockerCommand.setDockerComposeCommand(config) - - err = osCommand.RunCommand( - utils.ApplyTemplate( - config.UserConfig.CommandTemplates.CheckDockerComposeConfig, - dockerCommand.NewCommandObject(CommandObject{}), - ), - ) - if err != nil { - dockerCommand.InDockerComposeProject = false - log.Warn(err.Error()) - } - - return dockerCommand, nil -} - -func (c *DockerCommand) setDockerComposeCommand(config *config.AppConfig) { - if config.UserConfig.CommandTemplates.DockerCompose != "docker compose" { - return - } - - // it's possible that a user is still using docker-compose, so we'll check if 'docker comopose' is available, and if not, we'll fall back to 'docker-compose' - err := c.OSCommand.RunCommand("docker compose version") - if err != nil { - config.UserConfig.CommandTemplates.DockerCompose = "docker-compose" - } -} - -func (c *DockerCommand) Close() error { - return utils.CloseMany(c.Closers) -} - -func (c *DockerCommand) CreateClientStatMonitor(container *Container) { - container.MonitoringStats = true - stream, err := c.Client.ContainerStats(context.Background(), container.ID, true) - if err != nil { - // not creating error panel because if we've disconnected from docker we'll - // have already created an error panel - c.Log.Error(err) - container.MonitoringStats = false - return - } - - defer stream.Body.Close() - - scanner := bufio.NewScanner(stream.Body) - for scanner.Scan() { - data := scanner.Bytes() - var stats ContainerStats - _ = json.Unmarshal(data, &stats) - - recordedStats := &RecordedStats{ - ClientStats: stats, - DerivedStats: DerivedStats{ - CPUPercentage: stats.CalculateContainerCPUPercentage(), - MemoryPercentage: stats.CalculateContainerMemoryUsage(), - }, - RecordedAt: time.Now(), - } - - container.appendStats(recordedStats, c.Config.UserConfig.Stats.MaxDuration) - } - - container.MonitoringStats = false -} - -func (c *DockerCommand) RefreshContainersAndServices(currentServices []*Service, currentContainers []*Container) ([]*Container, []*Service, error) { - c.ServiceMutex.Lock() - defer c.ServiceMutex.Unlock() - - containers, err := c.GetContainers(currentContainers) - if err != nil { - return nil, nil, err - } - - var services []*Service - // we only need to get these services once because they won't change in the runtime of the program - if currentServices != nil { - services = currentServices - } else { - services, err = c.GetServices() - if err != nil { - return nil, nil, err - } - } - - c.assignContainersToServices(containers, services) - - return containers, services, nil -} - -func (c *DockerCommand) assignContainersToServices(containers []*Container, services []*Service) { -L: - for _, service := range services { - for _, ctr := range containers { - if !ctr.OneOff && ctr.ServiceName == service.Name { - service.Container = ctr - continue L - } - } - service.Container = nil - } -} - -// GetContainers gets the docker containers -func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Container, error) { - c.ContainerMutex.Lock() - defer c.ContainerMutex.Unlock() - - containers, err := c.Client.ContainerList(context.Background(), container.ListOptions{All: true}) - if err != nil { - return nil, err - } - - ownContainers := make([]*Container, len(containers)) - - for i, ctr := range containers { - var newContainer *Container - - // check if we already have data stored against the container - for _, existingContainer := range existingContainers { - if existingContainer.ID == ctr.ID { - newContainer = existingContainer - break - } - } - - // initialise the container if it's completely new - if newContainer == nil { - newContainer = &Container{ - ID: ctr.ID, - Client: c.Client, - OSCommand: c.OSCommand, - Log: c.Log, - DockerCommand: c, - Tr: c.Tr, - } - } - - newContainer.Container = ctr - // if the container is made with a name label we will use that - if name, ok := ctr.Labels["name"]; ok { - newContainer.Name = name - } else { - if len(ctr.Names) > 0 { - newContainer.Name = strings.TrimLeft(ctr.Names[0], "/") - } else { - newContainer.Name = ctr.ID - } - } - newContainer.ServiceName = ctr.Labels["com.docker.compose.service"] - newContainer.ProjectName = ctr.Labels["com.docker.compose.project"] - newContainer.ContainerNumber = ctr.Labels["com.docker.compose.container"] - newContainer.OneOff = ctr.Labels["com.docker.compose.oneoff"] == "True" - - ownContainers[i] = newContainer - } - - c.SetContainerDetails(ownContainers) - - return ownContainers, nil -} - -// GetServices gets services -func (c *DockerCommand) GetServices() ([]*Service, error) { - if !c.InDockerComposeProject { - return nil, nil - } - - composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose - output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --services", composeCommand)) - if err != nil { - return nil, err - } - - // output looks like: - // service1 - // service2 - - lines := utils.SplitLines(output) - services := make([]*Service, len(lines)) - for i, str := range lines { - services[i] = &Service{ - Name: str, - ID: str, - OSCommand: c.OSCommand, - Log: c.Log, - DockerCommand: c, - } - } - - return services, nil -} - -func (c *DockerCommand) RefreshContainerDetails(containers []*Container) error { - c.ContainerMutex.Lock() - defer c.ContainerMutex.Unlock() - - c.SetContainerDetails(containers) - - return nil -} - -// Attaches the details returned from docker inspect to each of the containers -// this contains a bit more info than what you get from the go-docker client -func (c *DockerCommand) SetContainerDetails(containers []*Container) { - wg := sync.WaitGroup{} - for _, ctr := range containers { - ctr := ctr - wg.Add(1) - go func() { - details, err := c.Client.ContainerInspect(context.Background(), ctr.ID) - if err != nil { - c.Log.Error(err) - } else { - ctr.Details = details - } - wg.Done() - }() - } - wg.Wait() -} - -// ViewAllLogs attaches to a subprocess viewing all the logs from docker-compose -func (c *DockerCommand) ViewAllLogs() (*exec.Cmd, error) { - cmd := c.OSCommand.ExecutableFromString( - utils.ApplyTemplate( - c.OSCommand.Config.UserConfig.CommandTemplates.ViewAllLogs, - c.NewCommandObject(CommandObject{}), - ), - ) - - c.OSCommand.PrepareForChildren(cmd) - - return cmd, nil -} - -// DockerComposeConfig returns the result of 'docker-compose config' -func (c *DockerCommand) DockerComposeConfig() string { - output, err := c.OSCommand.RunCommandWithOutput( - utils.ApplyTemplate( - c.OSCommand.Config.UserConfig.CommandTemplates.DockerComposeConfig, - c.NewCommandObject(CommandObject{}), - ), - ) - if err != nil { - output = err.Error() - } - return output -} - -// determineDockerHost tries to the determine the docker host that we should connect to -// in the following order of decreasing precedence: -// - value of "DOCKER_HOST" environment variable -// - host retrieved from the current context (specified via DOCKER_CONTEXT) -// - "default docker host" for the host operating system, otherwise -func determineDockerHost() (string, error) { - // If the docker host is explicitly set via the "DOCKER_HOST" environment variable, - // then its a no-brainer :shrug: - if os.Getenv("DOCKER_HOST") != "" { - return os.Getenv("DOCKER_HOST"), nil - } - - currentContext := os.Getenv("DOCKER_CONTEXT") - if currentContext == "" { - cf, err := cliconfig.Load(cliconfig.Dir()) - if err != nil { - return "", err - } - currentContext = cf.CurrentContext - } - - // On some systems (windows) `default` is stored in the docker config as the currentContext. - if currentContext == "" || currentContext == "default" { - // If a docker context is neither specified via the "DOCKER_CONTEXT" environment variable nor via the - // $HOME/.docker/config file, then we fall back to connecting to the "default docker host" meant for - // the host operating system. - return defaultDockerHost, nil - } - - storeConfig := ctxstore.NewConfig( - func() interface{} { return &ddocker.EndpointMeta{} }, - ctxstore.EndpointTypeGetter(ddocker.DockerEndpoint, func() interface{} { return &ddocker.EndpointMeta{} }), - ) - - st := ctxstore.New(cliconfig.ContextStoreDir(), storeConfig) - md, err := st.GetMetadata(currentContext) - if err != nil { - return "", err - } - dockerEP, ok := md.Endpoints[ddocker.DockerEndpoint] - if !ok { - return "", err - } - dockerEPMeta, ok := dockerEP.(ddocker.EndpointMeta) - if !ok { - return "", fmt.Errorf("expected docker.EndpointMeta, got %T", dockerEP) - } - - if dockerEPMeta.Host != "" { - return dockerEPMeta.Host, nil - } - - // We might end up here, if the context was created with the `host` set to an empty value (i.e. ''). - // For example: - // ```sh - // docker context create foo --docker "host=" - // ``` - // In such scenario, we mimic the `docker` cli and try to connect to the "default docker host". - return defaultDockerHost, nil -} diff --git a/pkg/commands/docker_host_unix.go b/pkg/commands/docker_host_unix.go deleted file mode 100644 index ee5fb7af..00000000 --- a/pkg/commands/docker_host_unix.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !windows - -package commands - -const ( - defaultDockerHost = "unix:///var/run/docker.sock" -) diff --git a/pkg/commands/docker_host_windows.go b/pkg/commands/docker_host_windows.go deleted file mode 100644 index 471b55a2..00000000 --- a/pkg/commands/docker_host_windows.go +++ /dev/null @@ -1,5 +0,0 @@ -package commands - -const ( - defaultDockerHost = "npipe:////./pipe/docker_engine" -) diff --git a/pkg/commands/dummies.go b/pkg/commands/dummies.go index 3bd64960..e14476c1 100644 --- a/pkg/commands/dummies.go +++ b/pkg/commands/dummies.go @@ -35,15 +35,15 @@ func NewDummyLog() *logrus.Entry { return log.WithField("test", "test") } -// NewDummyDockerCommand creates a new dummy DockerCommand for testing -func NewDummyDockerCommand() *DockerCommand { - return NewDummyDockerCommandWithOSCommand(NewDummyOSCommand()) +// NewDummyPodmanCommand creates a new dummy PodmanCommand for testing +func NewDummyPodmanCommand() *PodmanCommand { + return NewDummyPodmanCommandWithOSCommand(NewDummyOSCommand()) } -// NewDummyDockerCommandWithOSCommand creates a new dummy DockerCommand for testing -func NewDummyDockerCommandWithOSCommand(osCommand *OSCommand) *DockerCommand { +// NewDummyPodmanCommandWithOSCommand creates a new dummy PodmanCommand for testing +func NewDummyPodmanCommandWithOSCommand(osCommand *OSCommand) *PodmanCommand { newAppConfig := NewDummyAppConfig() - return &DockerCommand{ + return &PodmanCommand{ Log: NewDummyLog(), OSCommand: osCommand, Tr: i18n.NewTranslationSet(NewDummyLog(), newAppConfig.UserConfig.Gui.Language), diff --git a/pkg/commands/errors.go b/pkg/commands/errors.go index f7165fca..02a42555 100644 --- a/pkg/commands/errors.go +++ b/pkg/commands/errors.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "strings" "github.com/go-errors/errors" "golang.org/x/xerrors" @@ -12,6 +13,21 @@ const ( MustStopContainer = iota ) +// IsConnectionFailed checks if the error indicates a connection failure to Podman +func IsConnectionFailed(err error) bool { + if err == nil { + return false + } + errStr := err.Error() + return strings.Contains(errStr, "connection refused") || + strings.Contains(errStr, "no such host") || + strings.Contains(errStr, "socket not found") || + strings.Contains(errStr, "cannot connect") || + strings.Contains(errStr, "unable to connect") || + strings.Contains(errStr, "dial unix") || + strings.Contains(errStr, "permission denied") +} + // WrapError wraps an error for the sake of showing a stack trace at the top level // the go-errors package, for some reason, does not return nil when you try to wrap // a non-error, so we're just doing it here diff --git a/pkg/commands/image.go b/pkg/commands/image.go index 540a0639..a72d63d7 100644 --- a/pkg/commands/image.go +++ b/pkg/commands/image.go @@ -4,37 +4,31 @@ import ( "context" "strings" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/client" "github.com/fatih/color" "github.com/christophe-duc/lazypodman/pkg/utils" "github.com/samber/lo" "github.com/sirupsen/logrus" ) -// Image : A docker Image +// Image represents a Podman image type Image struct { Name string Tag string ID string - Image image.Summary - Client *client.Client + Summary ImageSummary + Runtime ContainerRuntime OSCommand *OSCommand Log *logrus.Entry - DockerCommand LimitedDockerCommand + PodmanCommand LimitedPodmanCommand } // Remove removes the image -func (i *Image) Remove(options image.RemoveOptions) error { - if _, err := i.Client.ImageRemove(context.Background(), i.ID, options); err != nil { - return err - } - - return nil +func (i *Image) Remove(force bool) error { + ctx := context.Background() + return i.Runtime.RemoveImage(ctx, i.ID, force) } -func getHistoryResponseItemDisplayStrings(layer image.HistoryResponseItem) []string { +func getHistoryResponseItemDisplayStrings(layer ImageHistoryEntry) []string { tag := "" if len(layer.Tags) > 0 { tag = layer.Tags[0] @@ -75,12 +69,13 @@ func getHistoryResponseItemDisplayStrings(layer image.HistoryResponseItem) []str // RenderHistory renders the history of the image func (i *Image) RenderHistory() (string, error) { - history, err := i.Client.ImageHistory(context.Background(), i.ID) + ctx := context.Background() + history, err := i.Runtime.ImageHistory(ctx, i.ID) if err != nil { return "", err } - tableBody := lo.Map(history, func(layer image.HistoryResponseItem, _ int) []string { + tableBody := lo.Map(history, func(layer ImageHistoryEntry, _ int) []string { return getHistoryResponseItemDisplayStrings(layer) }) @@ -89,55 +84,3 @@ func (i *Image) RenderHistory() (string, error) { return utils.RenderTable(table) } - -// RefreshImages returns a slice of docker images -func (c *DockerCommand) RefreshImages() ([]*Image, error) { - images, err := c.Client.ImageList(context.Background(), image.ListOptions{}) - if err != nil { - return nil, err - } - - ownImages := make([]*Image, len(images)) - - for i, img := range images { - firstTag := "" - tags := img.RepoTags - if len(tags) > 0 { - firstTag = tags[0] - } - - nameParts := strings.Split(firstTag, ":") - tag := "" - name := "none" - if len(nameParts) > 1 { - tag = nameParts[len(nameParts)-1] - name = strings.Join(nameParts[:len(nameParts)-1], ":") - - for prefix, replacement := range c.Config.UserConfig.Replacements.ImageNamePrefixes { - if strings.HasPrefix(name, prefix) { - name = strings.Replace(name, prefix, replacement, 1) - break - } - } - } - - ownImages[i] = &Image{ - ID: img.ID, - Name: name, - Tag: tag, - Image: img, - Client: c.Client, - OSCommand: c.OSCommand, - Log: c.Log, - DockerCommand: c, - } - } - - return ownImages, nil -} - -// PruneImages prunes images -func (c *DockerCommand) PruneImages() error { - _, err := c.Client.ImagesPrune(context.Background(), filters.Args{}) - return err -} diff --git a/pkg/commands/network.go b/pkg/commands/network.go index 74379ef0..60d817d0 100644 --- a/pkg/commands/network.go +++ b/pkg/commands/network.go @@ -3,52 +3,21 @@ package commands import ( "context" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" "github.com/sirupsen/logrus" ) -// Network : A docker Network +// Network represents a Podman network type Network struct { Name string - Network network.Inspect - Client *client.Client + Summary NetworkSummary + Runtime ContainerRuntime OSCommand *OSCommand Log *logrus.Entry - DockerCommand LimitedDockerCommand -} - -// RefreshNetworks gets the networks and stores them -func (c *DockerCommand) RefreshNetworks() ([]*Network, error) { - networks, err := c.Client.NetworkList(context.Background(), network.ListOptions{}) - if err != nil { - return nil, err - } - - ownNetworks := make([]*Network, len(networks)) - - for i, nw := range networks { - ownNetworks[i] = &Network{ - Name: nw.Name, - Network: nw, - Client: c.Client, - OSCommand: c.OSCommand, - Log: c.Log, - DockerCommand: c, - } - } - - return ownNetworks, nil -} - -// PruneNetworks prunes networks -func (c *DockerCommand) PruneNetworks() error { - _, err := c.Client.NetworksPrune(context.Background(), filters.Args{}) - return err + PodmanCommand LimitedPodmanCommand } // Remove removes the network func (v *Network) Remove() error { - return v.Client.NetworkRemove(context.Background(), v.Name) + ctx := context.Background() + return v.Runtime.RemoveNetwork(ctx, v.Name) } diff --git a/pkg/commands/podman.go b/pkg/commands/podman.go new file mode 100644 index 00000000..50fd66c6 --- /dev/null +++ b/pkg/commands/podman.go @@ -0,0 +1,650 @@ +package commands + +import ( + "context" + "fmt" + "io" + ogLog "log" + "os" + "os/exec" + "strings" + "sync" + "time" + + "github.com/imdario/mergo" + "github.com/christophe-duc/lazypodman/pkg/commands/ssh" + "github.com/christophe-duc/lazypodman/pkg/config" + "github.com/christophe-duc/lazypodman/pkg/i18n" + "github.com/christophe-duc/lazypodman/pkg/utils" + "github.com/sasha-s/go-deadlock" + "github.com/sirupsen/logrus" +) + +const ( + containerHostEnvKey = "CONTAINER_HOST" +) + +// PodmanCommand is our main podman interface +type PodmanCommand struct { + Log *logrus.Entry + OSCommand *OSCommand + Tr *i18n.TranslationSet + Config *config.AppConfig + Runtime ContainerRuntime + InDockerComposeProject bool + ErrorChan chan error + ContainerMutex deadlock.Mutex + ServiceMutex deadlock.Mutex + + Closers []io.Closer +} + +var _ io.Closer = &PodmanCommand{} + +// LimitedPodmanCommand is a stripped-down PodmanCommand with just the methods the container/service/image might need +type LimitedPodmanCommand interface { + NewCommandObject(CommandObject) CommandObject +} + +// CommandObject is what we pass to our template resolvers when we are running a custom command. +// We do not guarantee that all fields will be populated: just the ones that make sense for the current context +type CommandObject struct { + DockerCompose string + Service *Service + Container *Container + Image *Image + Volume *Volume + Network *Network +} + +// NewCommandObject takes a command object and returns a default command object with the passed command object merged in +func (c *PodmanCommand) NewCommandObject(obj CommandObject) CommandObject { + defaultObj := CommandObject{DockerCompose: c.Config.UserConfig.CommandTemplates.DockerCompose} + _ = mergo.Merge(&defaultObj, obj) + return defaultObj +} + +// NewPodmanCommand creates a new PodmanCommand with the appropriate runtime. +// It tries socket mode first, then falls back to libpod if available. +func NewPodmanCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*PodmanCommand, error) { + var runtime ContainerRuntime + var err error + var closers []io.Closer + + // Determine the socket path + socketPath := detectSocketPath() + + // Handle SSH tunneling for remote hosts + if strings.HasPrefix(socketPath, "ssh://") { + os.Setenv(containerHostEnvKey, socketPath) + tunnelCloser, tunnelErr := ssh.NewSSHHandler(osCommand).HandleSSHDockerHost() + if tunnelErr != nil { + ogLog.Printf("> SSH tunnel setup failed: %v", tunnelErr) + } else { + closers = append(closers, tunnelCloser) + } + // Re-check for updated socket path after SSH setup + if updatedPath := os.Getenv(containerHostEnvKey); updatedPath != "" { + socketPath = updatedPath + } + } + + // Try socket mode first + if socketPath != "" { + runtime, err = NewSocketRuntime(socketPath) + if err != nil { + log.Warnf("Socket connection failed (%s): %v, trying libpod", socketPath, err) + } + } + + // Fall back to libpod if socket mode failed + if runtime == nil { + runtime, err = NewLibpodRuntime() + if err != nil { + return nil, fmt.Errorf("no Podman runtime available: socket error and libpod unavailable: %v", err) + } + log.Infof("Using libpod runtime (socket-less mode)") + } else { + log.Infof("Using socket runtime at %s", socketPath) + } + + podmanCommand := &PodmanCommand{ + Log: log, + OSCommand: osCommand, + Tr: tr, + Config: config, + Runtime: runtime, + ErrorChan: errorChan, + InDockerComposeProject: true, + Closers: closers, + } + + podmanCommand.setComposeCommand(config) + + err = osCommand.RunCommand( + utils.ApplyTemplate( + config.UserConfig.CommandTemplates.CheckDockerComposeConfig, + podmanCommand.NewCommandObject(CommandObject{}), + ), + ) + if err != nil { + podmanCommand.InDockerComposeProject = false + log.Warn(err.Error()) + } + + return podmanCommand, nil +} + +// setComposeCommand detects and sets the appropriate compose command +func (c *PodmanCommand) setComposeCommand(config *config.AppConfig) { + // If user has explicitly set a compose command, respect it + if config.UserConfig.CommandTemplates.DockerCompose != "docker compose" && + config.UserConfig.CommandTemplates.DockerCompose != "" { + return + } + + // Try podman-compose first + if err := c.OSCommand.RunCommand("podman-compose version"); err == nil { + config.UserConfig.CommandTemplates.DockerCompose = "podman-compose" + return + } + + // Try podman compose (built-in, if available) + if err := c.OSCommand.RunCommand("podman compose version"); err == nil { + config.UserConfig.CommandTemplates.DockerCompose = "podman compose" + return + } + + // Fall back to docker-compose for compatibility + if err := c.OSCommand.RunCommand("docker-compose version"); err == nil { + config.UserConfig.CommandTemplates.DockerCompose = "docker-compose" + return + } + + // Default to podman-compose + config.UserConfig.CommandTemplates.DockerCompose = "podman-compose" +} + +func (c *PodmanCommand) Close() error { + var errs []error + if c.Runtime != nil { + if err := c.Runtime.Close(); err != nil { + errs = append(errs, err) + } + } + if err := utils.CloseMany(c.Closers); err != nil { + errs = append(errs, err) + } + if len(errs) > 0 { + return errs[0] + } + return nil +} + +// CreateClientStatMonitor starts monitoring stats for a container +func (c *PodmanCommand) CreateClientStatMonitor(container *Container) { + container.MonitoringStats = true + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + statsChan, errChan := c.Runtime.ContainerStats(ctx, container.ID, true) + + go func() { + for err := range errChan { + if err != nil { + c.Log.Error(err) + } + } + }() + + for stats := range statsChan { + recordedStats := &RecordedStats{ + ClientStats: convertStatsEntryToContainerStats(stats), + DerivedStats: DerivedStats{ + CPUPercentage: calculateCPUPercentageFromEntry(stats), + MemoryPercentage: calculateMemoryPercentageFromEntry(stats), + }, + RecordedAt: time.Now(), + } + + container.appendStats(recordedStats, c.Config.UserConfig.Stats.MaxDuration) + } + + container.MonitoringStats = false +} + +// convertStatsEntryToContainerStats converts the new stats format to the legacy format +func convertStatsEntryToContainerStats(entry ContainerStatsEntry) ContainerStats { + return ContainerStats{ + Read: entry.Read, + Preread: entry.PreRead, + PidsStats: struct { + Current int `json:"current"` + }{ + Current: entry.PidsStats.Current, + }, + CPUStats: struct { + CPUUsage struct { + TotalUsage int64 `json:"total_usage"` + PercpuUsage []int64 `json:"percpu_usage"` + UsageInKernelmode int64 `json:"usage_in_kernelmode"` + UsageInUsermode int64 `json:"usage_in_usermode"` + } `json:"cpu_usage"` + SystemCPUUsage int64 `json:"system_cpu_usage"` + OnlineCpus int `json:"online_cpus"` + ThrottlingData struct { + Periods int `json:"periods"` + ThrottledPeriods int `json:"throttled_periods"` + ThrottledTime int `json:"throttled_time"` + } `json:"throttling_data"` + }{ + CPUUsage: struct { + TotalUsage int64 `json:"total_usage"` + PercpuUsage []int64 `json:"percpu_usage"` + UsageInKernelmode int64 `json:"usage_in_kernelmode"` + UsageInUsermode int64 `json:"usage_in_usermode"` + }{ + TotalUsage: entry.CPUStats.CPUUsage.TotalUsage, + PercpuUsage: entry.CPUStats.CPUUsage.PercpuUsage, + UsageInKernelmode: entry.CPUStats.CPUUsage.UsageInKernelmode, + UsageInUsermode: entry.CPUStats.CPUUsage.UsageInUsermode, + }, + SystemCPUUsage: entry.CPUStats.SystemCPUUsage, + OnlineCpus: entry.CPUStats.OnlineCpus, + }, + PrecpuStats: struct { + CPUUsage struct { + TotalUsage int64 `json:"total_usage"` + PercpuUsage []int64 `json:"percpu_usage"` + UsageInKernelmode int64 `json:"usage_in_kernelmode"` + UsageInUsermode int64 `json:"usage_in_usermode"` + } `json:"cpu_usage"` + SystemCPUUsage int64 `json:"system_cpu_usage"` + OnlineCpus int `json:"online_cpus"` + ThrottlingData struct { + Periods int `json:"periods"` + ThrottledPeriods int `json:"throttled_periods"` + ThrottledTime int `json:"throttled_time"` + } `json:"throttling_data"` + }{ + CPUUsage: struct { + TotalUsage int64 `json:"total_usage"` + PercpuUsage []int64 `json:"percpu_usage"` + UsageInKernelmode int64 `json:"usage_in_kernelmode"` + UsageInUsermode int64 `json:"usage_in_usermode"` + }{ + TotalUsage: entry.PreCPUStats.CPUUsage.TotalUsage, + PercpuUsage: entry.PreCPUStats.CPUUsage.PercpuUsage, + UsageInKernelmode: entry.PreCPUStats.CPUUsage.UsageInKernelmode, + UsageInUsermode: entry.PreCPUStats.CPUUsage.UsageInUsermode, + }, + SystemCPUUsage: entry.PreCPUStats.SystemCPUUsage, + OnlineCpus: entry.PreCPUStats.OnlineCpus, + }, + MemoryStats: struct { + Usage int `json:"usage"` + MaxUsage int `json:"max_usage"` + Stats struct { + ActiveAnon int `json:"active_anon"` + ActiveFile int `json:"active_file"` + Cache int `json:"cache"` + Dirty int `json:"dirty"` + HierarchicalMemoryLimit int64 `json:"hierarchical_memory_limit"` + HierarchicalMemswLimit int64 `json:"hierarchical_memsw_limit"` + InactiveAnon int `json:"inactive_anon"` + InactiveFile int `json:"inactive_file"` + MappedFile int `json:"mapped_file"` + Pgfault int `json:"pgfault"` + Pgmajfault int `json:"pgmajfault"` + Pgpgin int `json:"pgpgin"` + Pgpgout int `json:"pgpgout"` + Rss int `json:"rss"` + RssHuge int `json:"rss_huge"` + TotalActiveAnon int `json:"total_active_anon"` + TotalActiveFile int `json:"total_active_file"` + TotalCache int `json:"total_cache"` + TotalDirty int `json:"total_dirty"` + TotalInactiveAnon int `json:"total_inactive_anon"` + TotalInactiveFile int `json:"total_inactive_file"` + TotalMappedFile int `json:"total_mapped_file"` + TotalPgfault int `json:"total_pgfault"` + TotalPgmajfault int `json:"total_pgmajfault"` + TotalPgpgin int `json:"total_pgpgin"` + TotalPgpgout int `json:"total_pgpgout"` + TotalRss int `json:"total_rss"` + TotalRssHuge int `json:"total_rss_huge"` + TotalUnevictable int `json:"total_unevictable"` + TotalWriteback int `json:"total_writeback"` + Unevictable int `json:"unevictable"` + Writeback int `json:"writeback"` + } `json:"stats"` + Limit int64 `json:"limit"` + }{ + Usage: int(entry.MemoryStats.Usage), + MaxUsage: int(entry.MemoryStats.MaxUsage), + Limit: entry.MemoryStats.Limit, + }, + Name: entry.Name, + ID: entry.ID, + } +} + +func calculateCPUPercentageFromEntry(stats ContainerStatsEntry) float64 { + cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage) + systemDelta := float64(stats.CPUStats.SystemCPUUsage - stats.PreCPUStats.SystemCPUUsage) + + if systemDelta > 0 && cpuDelta > 0 { + return (cpuDelta / systemDelta) * 100.0 + } + return 0.0 +} + +func calculateMemoryPercentageFromEntry(stats ContainerStatsEntry) float64 { + if stats.MemoryStats.Limit > 0 { + return float64(stats.MemoryStats.Usage) / float64(stats.MemoryStats.Limit) * 100.0 + } + return 0.0 +} + +func (c *PodmanCommand) RefreshContainersAndServices(currentServices []*Service, currentContainers []*Container) ([]*Container, []*Service, error) { + c.ServiceMutex.Lock() + defer c.ServiceMutex.Unlock() + + containers, err := c.GetContainers(currentContainers) + if err != nil { + return nil, nil, err + } + + var services []*Service + // we only need to get these services once because they won't change in the runtime of the program + if currentServices != nil { + services = currentServices + } else { + services, err = c.GetServices() + if err != nil { + return nil, nil, err + } + } + + c.assignContainersToServices(containers, services) + + return containers, services, nil +} + +func (c *PodmanCommand) assignContainersToServices(containers []*Container, services []*Service) { +L: + for _, service := range services { + for _, ctr := range containers { + if !ctr.OneOff && ctr.ServiceName == service.Name { + service.Container = ctr + continue L + } + } + service.Container = nil + } +} + +// GetContainers gets the podman containers +func (c *PodmanCommand) GetContainers(existingContainers []*Container) ([]*Container, error) { + c.ContainerMutex.Lock() + defer c.ContainerMutex.Unlock() + + ctx := context.Background() + containers, err := c.Runtime.ListContainers(ctx) + if err != nil { + return nil, err + } + + ownContainers := make([]*Container, len(containers)) + + for i, ctr := range containers { + var newContainer *Container + + // check if we already have data stored against the container + for _, existingContainer := range existingContainers { + if existingContainer.ID == ctr.ID { + newContainer = existingContainer + break + } + } + + // initialise the container if it's completely new + if newContainer == nil { + newContainer = &Container{ + ID: ctr.ID, + Runtime: c.Runtime, + OSCommand: c.OSCommand, + Log: c.Log, + PodmanCommand: c, + Tr: c.Tr, + } + } + + newContainer.Summary = ctr + // if the container is made with a name label we will use that + if name, ok := ctr.Labels["name"]; ok { + newContainer.Name = name + } else { + if len(ctr.Names) > 0 { + newContainer.Name = strings.TrimLeft(ctr.Names[0], "/") + } else { + newContainer.Name = ctr.ID + } + } + // Compose labels work the same way with podman-compose + newContainer.ServiceName = ctr.Labels["com.docker.compose.service"] + newContainer.ProjectName = ctr.Labels["com.docker.compose.project"] + newContainer.ContainerNumber = ctr.Labels["com.docker.compose.container"] + newContainer.OneOff = ctr.Labels["com.docker.compose.oneoff"] == "True" + + ownContainers[i] = newContainer + } + + c.SetContainerDetails(ownContainers) + + return ownContainers, nil +} + +// GetServices gets services +func (c *PodmanCommand) GetServices() ([]*Service, error) { + if !c.InDockerComposeProject { + return nil, nil + } + + composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose + output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --services", composeCommand)) + if err != nil { + return nil, err + } + + // output looks like: + // service1 + // service2 + + lines := utils.SplitLines(output) + services := make([]*Service, len(lines)) + for i, str := range lines { + services[i] = &Service{ + Name: str, + ID: str, + OSCommand: c.OSCommand, + Log: c.Log, + PodmanCommand: c, + } + } + + return services, nil +} + +func (c *PodmanCommand) RefreshContainerDetails(containers []*Container) error { + c.ContainerMutex.Lock() + defer c.ContainerMutex.Unlock() + + c.SetContainerDetails(containers) + + return nil +} + +// SetContainerDetails attaches the details returned from container inspect to each of the containers +func (c *PodmanCommand) SetContainerDetails(containers []*Container) { + ctx := context.Background() + wg := sync.WaitGroup{} + for _, ctr := range containers { + ctr := ctr + wg.Add(1) + go func() { + defer wg.Done() + details, err := c.Runtime.InspectContainer(ctx, ctr.ID) + if err != nil { + c.Log.Error(err) + } else { + ctr.Details = details + } + }() + } + wg.Wait() +} + +// ViewAllLogs attaches to a subprocess viewing all the logs from compose +func (c *PodmanCommand) ViewAllLogs() (*exec.Cmd, error) { + cmd := c.OSCommand.ExecutableFromString( + utils.ApplyTemplate( + c.OSCommand.Config.UserConfig.CommandTemplates.ViewAllLogs, + c.NewCommandObject(CommandObject{}), + ), + ) + + c.OSCommand.PrepareForChildren(cmd) + + return cmd, nil +} + +// DockerComposeConfig returns the result of 'compose config' +func (c *PodmanCommand) DockerComposeConfig() string { + output, err := c.OSCommand.RunCommandWithOutput( + utils.ApplyTemplate( + c.OSCommand.Config.UserConfig.CommandTemplates.DockerComposeConfig, + c.NewCommandObject(CommandObject{}), + ), + ) + if err != nil { + output = err.Error() + } + return output +} + +// PruneContainers prunes containers using the runtime +func (c *PodmanCommand) PruneContainers() error { + return c.Runtime.PruneContainers(context.Background()) +} + +// PruneImages prunes images using the runtime +func (c *PodmanCommand) PruneImages() error { + return c.Runtime.PruneImages(context.Background()) +} + +// PruneVolumes prunes volumes using the runtime +func (c *PodmanCommand) PruneVolumes() error { + return c.Runtime.PruneVolumes(context.Background()) +} + +// PruneNetworks prunes networks using the runtime +func (c *PodmanCommand) PruneNetworks() error { + return c.Runtime.PruneNetworks(context.Background()) +} + +// RefreshImages returns a slice of podman images +func (c *PodmanCommand) RefreshImages() ([]*Image, error) { + ctx := context.Background() + images, err := c.Runtime.ListImages(ctx) + if err != nil { + return nil, err + } + + ownImages := make([]*Image, len(images)) + + for i, img := range images { + firstTag := "" + tags := img.RepoTags + if len(tags) > 0 { + firstTag = tags[0] + } + + nameParts := strings.Split(firstTag, ":") + tag := "" + name := "none" + if len(nameParts) > 1 { + tag = nameParts[len(nameParts)-1] + name = strings.Join(nameParts[:len(nameParts)-1], ":") + + for prefix, replacement := range c.Config.UserConfig.Replacements.ImageNamePrefixes { + if strings.HasPrefix(name, prefix) { + name = strings.Replace(name, prefix, replacement, 1) + break + } + } + } + + ownImages[i] = &Image{ + ID: img.ID, + Name: name, + Tag: tag, + Summary: img, + Runtime: c.Runtime, + OSCommand: c.OSCommand, + Log: c.Log, + PodmanCommand: c, + } + } + + return ownImages, nil +} + +// RefreshVolumes gets the volumes and stores them +func (c *PodmanCommand) RefreshVolumes() ([]*Volume, error) { + ctx := context.Background() + volumes, err := c.Runtime.ListVolumes(ctx) + if err != nil { + return nil, err + } + + ownVolumes := make([]*Volume, len(volumes)) + + for i, vol := range volumes { + ownVolumes[i] = &Volume{ + Name: vol.Name, + Summary: vol, + Runtime: c.Runtime, + OSCommand: c.OSCommand, + Log: c.Log, + PodmanCommand: c, + } + } + + return ownVolumes, nil +} + +// RefreshNetworks gets the networks and stores them +func (c *PodmanCommand) RefreshNetworks() ([]*Network, error) { + ctx := context.Background() + networks, err := c.Runtime.ListNetworks(ctx) + if err != nil { + return nil, err + } + + ownNetworks := make([]*Network, len(networks)) + + for i, nw := range networks { + ownNetworks[i] = &Network{ + Name: nw.Name, + Summary: nw, + Runtime: c.Runtime, + OSCommand: c.OSCommand, + Log: c.Log, + PodmanCommand: c, + } + } + + return ownNetworks, nil +} diff --git a/pkg/commands/podman_host_unix.go b/pkg/commands/podman_host_unix.go new file mode 100644 index 00000000..2fbdb44b --- /dev/null +++ b/pkg/commands/podman_host_unix.go @@ -0,0 +1,48 @@ +//go:build !windows + +package commands + +import ( + "fmt" + "os" +) + +// detectSocketPath determines the Podman socket path to use. +// It checks in the following order: +// 1. CONTAINER_HOST environment variable (Podman standard) +// 2. Rootless socket: /run/user/{uid}/podman/podman.sock +// 3. Rootful socket: /run/podman/podman.sock +// 4. Legacy Docker socket: /var/run/docker.sock (for compatibility) +func detectSocketPath() string { + // 1. Check CONTAINER_HOST environment variable (Podman standard) + if host := os.Getenv("CONTAINER_HOST"); host != "" { + return host + } + + // Also check DOCKER_HOST for backward compatibility + if host := os.Getenv("DOCKER_HOST"); host != "" { + return host + } + + // 2. Check for rootless socket + uid := os.Getuid() + rootlessPath := fmt.Sprintf("/run/user/%d/podman/podman.sock", uid) + if _, err := os.Stat(rootlessPath); err == nil { + return "unix://" + rootlessPath + } + + // 3. Check for rootful socket + rootfulPath := "/run/podman/podman.sock" + if _, err := os.Stat(rootfulPath); err == nil { + return "unix://" + rootfulPath + } + + // 4. Legacy Docker socket path (for compatibility) + dockerPath := "/var/run/docker.sock" + if _, err := os.Stat(dockerPath); err == nil { + return "unix://" + dockerPath + } + + // Return empty string - will fall back to libpod if available + return "" +} diff --git a/pkg/commands/podman_host_windows.go b/pkg/commands/podman_host_windows.go new file mode 100644 index 00000000..04177a6a --- /dev/null +++ b/pkg/commands/podman_host_windows.go @@ -0,0 +1,25 @@ +//go:build windows + +package commands + +import "os" + +// detectSocketPath determines the Podman socket path to use on Windows. +// It checks in the following order: +// 1. CONTAINER_HOST environment variable (Podman standard) +// 2. Default Podman machine named pipe +func detectSocketPath() string { + // Check CONTAINER_HOST environment variable + if host := os.Getenv("CONTAINER_HOST"); host != "" { + return host + } + + // Also check DOCKER_HOST for backward compatibility + if host := os.Getenv("DOCKER_HOST"); host != "" { + return host + } + + // Default to Podman machine socket + // Podman machine on Windows uses named pipes + return "npipe:////./pipe/podman-machine-default" +} diff --git a/pkg/commands/runtime.go b/pkg/commands/runtime.go new file mode 100644 index 00000000..9a6f530b --- /dev/null +++ b/pkg/commands/runtime.go @@ -0,0 +1,48 @@ +package commands + +import "context" + +// ContainerRuntime abstracts Podman operations for both socket and socket-less modes. +// This interface allows the application to work with either: +// - Socket mode: using pkg/bindings (stable REST API) +// - Socket-less mode: using libpod directly (requires Linux + CGO) +type ContainerRuntime interface { + // Container operations + ListContainers(ctx context.Context) ([]ContainerSummary, error) + InspectContainer(ctx context.Context, id string) (*ContainerDetails, error) + StartContainer(ctx context.Context, id string) error + StopContainer(ctx context.Context, id string, timeout *int) error + PauseContainer(ctx context.Context, id string) error + UnpauseContainer(ctx context.Context, id string) error + RestartContainer(ctx context.Context, id string, timeout *int) error + RemoveContainer(ctx context.Context, id string, force bool, volumes bool) error + ContainerTop(ctx context.Context, id string) ([]string, [][]string, error) + PruneContainers(ctx context.Context) error + ContainerStats(ctx context.Context, id string, stream bool) (<-chan ContainerStatsEntry, <-chan error) + + // Image operations + ListImages(ctx context.Context) ([]ImageSummary, error) + InspectImage(ctx context.Context, id string) (*ImageDetails, error) + ImageHistory(ctx context.Context, id string) ([]ImageHistoryEntry, error) + RemoveImage(ctx context.Context, id string, force bool) error + PruneImages(ctx context.Context) error + + // Volume operations + ListVolumes(ctx context.Context) ([]VolumeSummary, error) + RemoveVolume(ctx context.Context, name string, force bool) error + PruneVolumes(ctx context.Context) error + + // Network operations + ListNetworks(ctx context.Context) ([]NetworkSummary, error) + RemoveNetwork(ctx context.Context, name string) error + PruneNetworks(ctx context.Context) error + + // Events streams container/image/volume/network events + Events(ctx context.Context) (<-chan Event, <-chan error) + + // Lifecycle + Close() error + + // Mode returns "socket" or "libpod" to indicate which runtime mode is active + Mode() string +} diff --git a/pkg/commands/runtime_libpod.go b/pkg/commands/runtime_libpod.go new file mode 100644 index 00000000..477e1941 --- /dev/null +++ b/pkg/commands/runtime_libpod.go @@ -0,0 +1,517 @@ +//go:build linux && cgo + +package commands + +import ( + "context" + "time" + + "github.com/containers/podman/v5/libpod" + "github.com/containers/podman/v5/libpod/define" +) + +// LibpodRuntime implements ContainerRuntime using libpod directly. +// This mode does not require a Podman socket and works directly with the container runtime. +// It requires CGO and is only available on Linux. +type LibpodRuntime struct { + runtime *libpod.Runtime +} + +// NewLibpodRuntime creates a new LibpodRuntime. +func NewLibpodRuntime() (*LibpodRuntime, error) { + runtime, err := libpod.NewRuntime(context.Background()) + if err != nil { + return nil, err + } + return &LibpodRuntime{runtime: runtime}, nil +} + +// Mode returns "libpod" to indicate this is the direct libpod runtime. +func (r *LibpodRuntime) Mode() string { + return "libpod" +} + +// Close shuts down the libpod runtime. +func (r *LibpodRuntime) Close() error { + if r.runtime != nil { + _, err := r.runtime.Shutdown(false) + return err + } + return nil +} + +// Container operations + +// ListContainers returns all containers. +func (r *LibpodRuntime) ListContainers(ctx context.Context) ([]ContainerSummary, error) { + ctrs, err := r.runtime.GetAllContainers() + if err != nil { + return nil, err + } + return convertLibpodContainerList(ctrs) +} + +// InspectContainer returns detailed information about a container. +func (r *LibpodRuntime) InspectContainer(ctx context.Context, id string) (*ContainerDetails, error) { + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + return nil, err + } + data, err := ctr.Inspect(false) + if err != nil { + return nil, err + } + return convertLibpodContainerInspect(data), nil +} + +// StartContainer starts a stopped container. +func (r *LibpodRuntime) StartContainer(ctx context.Context, id string) error { + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + return err + } + return ctr.Start(ctx, false) +} + +// StopContainer stops a running container. +func (r *LibpodRuntime) StopContainer(ctx context.Context, id string, timeout *int) error { + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + return err + } + t := uint(10) // default timeout + if timeout != nil { + t = uint(*timeout) + } + return ctr.StopWithTimeout(t) +} + +// PauseContainer pauses a running container. +func (r *LibpodRuntime) PauseContainer(ctx context.Context, id string) error { + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + return err + } + return ctr.Pause() +} + +// UnpauseContainer unpauses a paused container. +func (r *LibpodRuntime) UnpauseContainer(ctx context.Context, id string) error { + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + return err + } + return ctr.Unpause() +} + +// RestartContainer restarts a container. +func (r *LibpodRuntime) RestartContainer(ctx context.Context, id string, timeout *int) error { + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + return err + } + t := uint(10) // default timeout + if timeout != nil { + t = uint(*timeout) + } + return ctr.RestartWithTimeout(ctx, t) +} + +// RemoveContainer removes a container. +func (r *LibpodRuntime) RemoveContainer(ctx context.Context, id string, force bool, removeVolumes bool) error { + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + return err + } + var timeout uint = 10 + _, _, err = r.runtime.RemoveContainerAndDependencies(ctx, ctr, force, removeVolumes, &timeout) + return err +} + +// ContainerTop returns process information from a container. +func (r *LibpodRuntime) ContainerTop(ctx context.Context, id string) ([]string, [][]string, error) { + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + return nil, nil, err + } + result, err := ctr.Top([]string{}) + if err != nil { + return nil, nil, err + } + if len(result) == 0 { + return nil, nil, nil + } + return result[0], result[1:], nil +} + +// PruneContainers removes all stopped containers. +func (r *LibpodRuntime) PruneContainers(ctx context.Context) error { + _, err := r.runtime.PruneContainers(ctx, nil) + return err +} + +// ContainerStats streams container statistics. +func (r *LibpodRuntime) ContainerStats(ctx context.Context, id string, stream bool) (<-chan ContainerStatsEntry, <-chan error) { + statsChan := make(chan ContainerStatsEntry) + errChan := make(chan error, 1) + + go func() { + defer close(statsChan) + defer close(errChan) + + ctr, err := r.runtime.LookupContainer(id) + if err != nil { + errChan <- err + return + } + + for { + stats, err := ctr.GetContainerStats(nil) + if err != nil { + errChan <- err + return + } + + entry := convertLibpodContainerStats(stats) + select { + case statsChan <- entry: + case <-ctx.Done(): + return + } + + if !stream { + return + } + time.Sleep(time.Second) + } + }() + + return statsChan, errChan +} + +// Image operations + +// ListImages returns all images. +func (r *LibpodRuntime) ListImages(ctx context.Context) ([]ImageSummary, error) { + imgs, err := r.runtime.ImageRuntime().GetImages() + if err != nil { + return nil, err + } + return convertLibpodImageList(imgs), nil +} + +// InspectImage returns detailed information about an image. +func (r *LibpodRuntime) InspectImage(ctx context.Context, id string) (*ImageDetails, error) { + img, _, err := r.runtime.ImageRuntime().LookupImage(id, nil) + if err != nil { + return nil, err + } + return convertLibpodImageInspect(img), nil +} + +// ImageHistory returns the history of an image. +func (r *LibpodRuntime) ImageHistory(ctx context.Context, id string) ([]ImageHistoryEntry, error) { + img, _, err := r.runtime.ImageRuntime().LookupImage(id, nil) + if err != nil { + return nil, err + } + history, err := img.History(ctx) + if err != nil { + return nil, err + } + return convertLibpodImageHistory(history), nil +} + +// RemoveImage removes an image. +func (r *LibpodRuntime) RemoveImage(ctx context.Context, id string, force bool) error { + img, _, err := r.runtime.ImageRuntime().LookupImage(id, nil) + if err != nil { + return err + } + _, err = r.runtime.ImageRuntime().RemoveImages(ctx, []string{img.ID()}, &libimage.RemoveImagesOptions{Force: force}) + return err +} + +// PruneImages removes unused images. +func (r *LibpodRuntime) PruneImages(ctx context.Context) error { + _, err := r.runtime.ImageRuntime().PruneImages(ctx, nil) + return err +} + +// Volume operations + +// ListVolumes returns all volumes. +func (r *LibpodRuntime) ListVolumes(ctx context.Context) ([]VolumeSummary, error) { + vols, err := r.runtime.GetAllVolumes() + if err != nil { + return nil, err + } + return convertLibpodVolumeList(vols), nil +} + +// RemoveVolume removes a volume. +func (r *LibpodRuntime) RemoveVolume(ctx context.Context, name string, force bool) error { + vol, err := r.runtime.LookupVolume(name) + if err != nil { + return err + } + var timeout uint = 10 + return r.runtime.RemoveVolume(ctx, vol, force, &timeout) +} + +// PruneVolumes removes unused volumes. +func (r *LibpodRuntime) PruneVolumes(ctx context.Context) error { + _, err := r.runtime.PruneVolumes(ctx, nil) + return err +} + +// Network operations + +// ListNetworks returns all networks. +func (r *LibpodRuntime) ListNetworks(ctx context.Context) ([]NetworkSummary, error) { + nets, err := r.runtime.Network().NetworkList() + if err != nil { + return nil, err + } + return convertLibpodNetworkList(nets), nil +} + +// RemoveNetwork removes a network. +func (r *LibpodRuntime) RemoveNetwork(ctx context.Context, name string) error { + _, err := r.runtime.Network().NetworkRemove(name) + return err +} + +// PruneNetworks removes unused networks. +func (r *LibpodRuntime) PruneNetworks(ctx context.Context) error { + _, err := r.runtime.Network().NetworkPrune(nil) + return err +} + +// Events streams container runtime events. +// For libpod, we use a polling approach since direct event streaming requires more setup. +func (r *LibpodRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) { + eventChan := make(chan Event) + errChan := make(chan error, 1) + + go func() { + defer close(eventChan) + defer close(errChan) + + // Libpod events require more complex setup; for now use periodic polling + // by sending empty events that trigger refreshes + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + // Send a synthetic event to trigger refresh + event := Event{ + Type: "refresh", + Action: "poll", + Time: time.Now().Unix(), + } + select { + case eventChan <- event: + case <-ctx.Done(): + return + } + } + } + }() + + return eventChan, errChan +} + +// Conversion functions + +func convertLibpodContainerList(ctrs []*libpod.Container) ([]ContainerSummary, error) { + result := make([]ContainerSummary, len(ctrs)) + for i, ctr := range ctrs { + state, err := ctr.State() + if err != nil { + return nil, err + } + config := ctr.Config() + command := "" + if len(config.Command) > 0 { + command = config.Command[0] + } + result[i] = ContainerSummary{ + ID: ctr.ID(), + Names: []string{ctr.Name()}, + Image: config.RootfsImageName, + ImageID: config.RootfsImageID, + Command: command, + Created: config.CreatedTime.Unix(), + State: state.String(), + Status: state.String(), + Labels: config.Labels, + Pod: ctr.PodID(), + } + } + return result, nil +} + +func convertLibpodContainerInspect(data *define.InspectContainerData) *ContainerDetails { + if data == nil { + return nil + } + + details := &ContainerDetails{ + ID: data.ID, + Name: data.Name, + Created: data.Created, + Path: data.Path, + Args: data.Args, + Image: data.Image, + ImageID: data.ImageName, + ResolvConfPath: data.ResolvConfPath, + HostnamePath: data.HostnamePath, + HostsPath: data.HostsPath, + LogPath: data.LogPath, + RestartCount: int(data.RestartCount), + Driver: data.Driver, + MountLabel: data.MountLabel, + ProcessLabel: data.ProcessLabel, + AppArmorProfile: data.AppArmorProfile, + } + + if data.State != nil { + details.State = &ContainerState{ + Status: data.State.Status, + Running: data.State.Running, + Paused: data.State.Paused, + Restarting: data.State.Restarting, + OOMKilled: data.State.OOMKilled, + Dead: data.State.Dead, + Pid: data.State.Pid, + ExitCode: int(data.State.ExitCode), + Error: data.State.Error, + StartedAt: data.State.StartedAt, + FinishedAt: data.State.FinishedAt, + } + } + + if data.Config != nil { + details.Config = &ContainerConfig{ + Hostname: data.Config.Hostname, + Domainname: data.Config.Domainname, + User: data.Config.User, + AttachStdin: data.Config.AttachStdin, + AttachStdout: data.Config.AttachStdout, + AttachStderr: data.Config.AttachStderr, + Tty: data.Config.Tty, + OpenStdin: data.Config.OpenStdin, + StdinOnce: data.Config.StdinOnce, + Env: data.Config.Env, + Cmd: data.Config.Cmd, + Image: data.Config.Image, + WorkingDir: data.Config.WorkingDir, + Entrypoint: data.Config.Entrypoint, + OnBuild: data.Config.OnBuild, + Labels: data.Config.Labels, + StopSignal: data.Config.StopSignal, + } + } + + return details +} + +func convertLibpodContainerStats(stats *define.ContainerStats) ContainerStatsEntry { + return ContainerStatsEntry{ + Read: time.Now(), + PreRead: time.Now().Add(-time.Second), + CPUStats: CPUStats{ + CPUUsage: CPUUsage{ + TotalUsage: int64(stats.CPU), + }, + SystemCPUUsage: int64(stats.SystemNano), + }, + MemoryStats: MemoryStats{ + Usage: int64(stats.MemUsage), + Limit: int64(stats.MemLimit), + }, + PidsStats: PidsStats{ + Current: int(stats.PIDs), + }, + Name: stats.Name, + ID: stats.ContainerID, + } +} + +func convertLibpodImageList(imgs []*libimage.Image) []ImageSummary { + result := make([]ImageSummary, len(imgs)) + for i, img := range imgs { + result[i] = ImageSummary{ + ID: img.ID(), + RepoTags: img.Names(), + Created: img.Created().Unix(), + Size: img.Size(), + Labels: img.Labels(), + } + } + return result +} + +func convertLibpodImageInspect(img *libimage.Image) *ImageDetails { + if img == nil { + return nil + } + return &ImageDetails{ + ID: img.ID(), + RepoTags: img.Names(), + Created: img.Created(), + Size: img.Size(), + Architecture: img.Architecture(), + Os: img.OS(), + } +} + +func convertLibpodImageHistory(history []*libimage.ImageHistoryLayer) []ImageHistoryEntry { + result := make([]ImageHistoryEntry, len(history)) + for i, layer := range history { + result[i] = ImageHistoryEntry{ + ID: layer.ID, + Created: layer.Created.Unix(), + CreatedBy: layer.CreatedBy, + Tags: layer.Tags, + Size: layer.Size, + Comment: layer.Comment, + } + } + return result +} + +func convertLibpodVolumeList(vols []*libpod.Volume) []VolumeSummary { + result := make([]VolumeSummary, len(vols)) + for i, vol := range vols { + result[i] = VolumeSummary{ + Name: vol.Name(), + Driver: vol.Driver(), + Mountpoint: vol.MountPoint(), + CreatedAt: vol.CreatedTime(), + Labels: vol.Labels(), + Scope: vol.Scope(), + Options: vol.Options(), + } + } + return result +} + +func convertLibpodNetworkList(nets []types.Network) []NetworkSummary { + result := make([]NetworkSummary, len(nets)) + for i, nw := range nets { + result[i] = NetworkSummary{ + Name: nw.Name, + ID: nw.ID, + Created: nw.Created, + Driver: nw.Driver, + Labels: nw.Labels, + } + } + return result +} diff --git a/pkg/commands/runtime_libpod_stub.go b/pkg/commands/runtime_libpod_stub.go new file mode 100644 index 00000000..dec54024 --- /dev/null +++ b/pkg/commands/runtime_libpod_stub.go @@ -0,0 +1,137 @@ +//go:build !linux || !cgo + +package commands + +import ( + "context" + "errors" +) + +// LibpodRuntime is a stub for non-Linux platforms. +// The real implementation requires CGO and is only available on Linux. +type LibpodRuntime struct{} + +// ErrLibpodNotAvailable is returned when libpod is not available on the current platform. +var ErrLibpodNotAvailable = errors.New("libpod runtime not available on this platform (requires Linux with CGO)") + +// NewLibpodRuntime returns an error on non-Linux platforms. +func NewLibpodRuntime() (*LibpodRuntime, error) { + return nil, ErrLibpodNotAvailable +} + +// Mode returns "libpod". +func (r *LibpodRuntime) Mode() string { + return "libpod" +} + +// Close is a no-op on non-Linux platforms. +func (r *LibpodRuntime) Close() error { + return nil +} + +// Container operations - all return ErrLibpodNotAvailable + +func (r *LibpodRuntime) ListContainers(ctx context.Context) ([]ContainerSummary, error) { + return nil, ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) InspectContainer(ctx context.Context, id string) (*ContainerDetails, error) { + return nil, ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) StartContainer(ctx context.Context, id string) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) StopContainer(ctx context.Context, id string, timeout *int) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) PauseContainer(ctx context.Context, id string) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) UnpauseContainer(ctx context.Context, id string) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) RestartContainer(ctx context.Context, id string, timeout *int) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) RemoveContainer(ctx context.Context, id string, force bool, volumes bool) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) ContainerTop(ctx context.Context, id string) ([]string, [][]string, error) { + return nil, nil, ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) PruneContainers(ctx context.Context) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) ContainerStats(ctx context.Context, id string, stream bool) (<-chan ContainerStatsEntry, <-chan error) { + errChan := make(chan error, 1) + errChan <- ErrLibpodNotAvailable + close(errChan) + return nil, errChan +} + +// Image operations - all return ErrLibpodNotAvailable + +func (r *LibpodRuntime) ListImages(ctx context.Context) ([]ImageSummary, error) { + return nil, ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) InspectImage(ctx context.Context, id string) (*ImageDetails, error) { + return nil, ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) ImageHistory(ctx context.Context, id string) ([]ImageHistoryEntry, error) { + return nil, ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) RemoveImage(ctx context.Context, id string, force bool) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) PruneImages(ctx context.Context) error { + return ErrLibpodNotAvailable +} + +// Volume operations - all return ErrLibpodNotAvailable + +func (r *LibpodRuntime) ListVolumes(ctx context.Context) ([]VolumeSummary, error) { + return nil, ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) RemoveVolume(ctx context.Context, name string, force bool) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) PruneVolumes(ctx context.Context) error { + return ErrLibpodNotAvailable +} + +// Network operations - all return ErrLibpodNotAvailable + +func (r *LibpodRuntime) ListNetworks(ctx context.Context) ([]NetworkSummary, error) { + return nil, ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) RemoveNetwork(ctx context.Context, name string) error { + return ErrLibpodNotAvailable +} + +func (r *LibpodRuntime) PruneNetworks(ctx context.Context) error { + return ErrLibpodNotAvailable +} + +// Events returns an error channel on non-Linux platforms. +func (r *LibpodRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) { + errChan := make(chan error, 1) + errChan <- ErrLibpodNotAvailable + close(errChan) + return nil, errChan +} diff --git a/pkg/commands/runtime_socket.go b/pkg/commands/runtime_socket.go new file mode 100644 index 00000000..d9e38f7e --- /dev/null +++ b/pkg/commands/runtime_socket.go @@ -0,0 +1,535 @@ +package commands + +import ( + "context" + "encoding/json" + "time" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/bindings/containers" + "github.com/containers/podman/v5/pkg/bindings/images" + "github.com/containers/podman/v5/pkg/bindings/network" + "github.com/containers/podman/v5/pkg/bindings/system" + "github.com/containers/podman/v5/pkg/bindings/volumes" + "github.com/containers/podman/v5/pkg/domain/entities" +) + +// SocketRuntime implements ContainerRuntime using Podman's REST API bindings. +// This is the preferred mode when a Podman socket is available. +type SocketRuntime struct { + conn context.Context +} + +// NewSocketRuntime creates a new SocketRuntime connected to the given socket path. +// The socketPath should be in the format "unix:///path/to/podman.sock". +func NewSocketRuntime(socketPath string) (*SocketRuntime, error) { + conn, err := bindings.NewConnection(context.Background(), socketPath) + if err != nil { + return nil, err + } + return &SocketRuntime{conn: conn}, nil +} + +// Mode returns "socket" to indicate this is the socket-based runtime. +func (r *SocketRuntime) Mode() string { + return "socket" +} + +// Close cleans up any resources. For socket mode, this is a no-op. +func (r *SocketRuntime) Close() error { + return nil +} + +// Container operations + +// ListContainers returns all containers. +func (r *SocketRuntime) ListContainers(ctx context.Context) ([]ContainerSummary, error) { + all := true + opts := &containers.ListOptions{ + All: &all, + } + podmanContainers, err := containers.List(r.conn, opts) + if err != nil { + return nil, err + } + return convertPodmanContainerList(podmanContainers), nil +} + +// InspectContainer returns detailed information about a container. +func (r *SocketRuntime) InspectContainer(ctx context.Context, id string) (*ContainerDetails, error) { + data, err := containers.Inspect(r.conn, id, nil) + if err != nil { + return nil, err + } + return convertPodmanContainerInspect(data), nil +} + +// StartContainer starts a stopped container. +func (r *SocketRuntime) StartContainer(ctx context.Context, id string) error { + return containers.Start(r.conn, id, nil) +} + +// StopContainer stops a running container. +func (r *SocketRuntime) StopContainer(ctx context.Context, id string, timeout *int) error { + opts := &containers.StopOptions{} + if timeout != nil { + t := uint(*timeout) + opts.Timeout = &t + } + return containers.Stop(r.conn, id, opts) +} + +// PauseContainer pauses a running container. +func (r *SocketRuntime) PauseContainer(ctx context.Context, id string) error { + return containers.Pause(r.conn, id, nil) +} + +// UnpauseContainer unpauses a paused container. +func (r *SocketRuntime) UnpauseContainer(ctx context.Context, id string) error { + return containers.Unpause(r.conn, id, nil) +} + +// RestartContainer restarts a container. +func (r *SocketRuntime) RestartContainer(ctx context.Context, id string, timeout *int) error { + opts := &containers.RestartOptions{} + if timeout != nil { + t := *timeout + opts.Timeout = &t + } + return containers.Restart(r.conn, id, opts) +} + +// RemoveContainer removes a container. +func (r *SocketRuntime) RemoveContainer(ctx context.Context, id string, force bool, removeVolumes bool) error { + opts := &containers.RemoveOptions{ + Force: &force, + Volumes: &removeVolumes, + } + _, err := containers.Remove(r.conn, id, opts) + return err +} + +// ContainerTop returns process information from a container. +func (r *SocketRuntime) ContainerTop(ctx context.Context, id string) ([]string, [][]string, error) { + result, err := containers.Top(r.conn, id, nil) + if err != nil { + return nil, nil, err + } + return result.Titles, result.Processes, nil +} + +// PruneContainers removes all stopped containers. +func (r *SocketRuntime) PruneContainers(ctx context.Context) error { + _, err := containers.Prune(r.conn, nil) + return err +} + +// ContainerStats streams container statistics. +func (r *SocketRuntime) ContainerStats(ctx context.Context, id string, stream bool) (<-chan ContainerStatsEntry, <-chan error) { + statsChan := make(chan ContainerStatsEntry) + errChan := make(chan error, 1) + + go func() { + defer close(statsChan) + defer close(errChan) + + opts := &containers.StatsOptions{ + Stream: &stream, + } + + reportChan, err := containers.Stats(r.conn, []string{id}, opts) + if err != nil { + errChan <- err + return + } + + for report := range reportChan { + if report.Error != nil { + errChan <- report.Error + return + } + + for _, stat := range report.Stats { + entry := convertPodmanContainerStats(stat) + select { + case statsChan <- entry: + case <-ctx.Done(): + return + } + } + } + }() + + return statsChan, errChan +} + +// Image operations + +// ListImages returns all images. +func (r *SocketRuntime) ListImages(ctx context.Context) ([]ImageSummary, error) { + opts := &images.ListOptions{} + podmanImages, err := images.List(r.conn, opts) + if err != nil { + return nil, err + } + return convertPodmanImageList(podmanImages), nil +} + +// InspectImage returns detailed information about an image. +func (r *SocketRuntime) InspectImage(ctx context.Context, id string) (*ImageDetails, error) { + data, err := images.GetImage(r.conn, id, nil) + if err != nil { + return nil, err + } + return convertPodmanImageInspect(data), nil +} + +// ImageHistory returns the history of an image. +func (r *SocketRuntime) ImageHistory(ctx context.Context, id string) ([]ImageHistoryEntry, error) { + history, err := images.History(r.conn, id, nil) + if err != nil { + return nil, err + } + return convertPodmanImageHistory(history), nil +} + +// RemoveImage removes an image. +func (r *SocketRuntime) RemoveImage(ctx context.Context, id string, force bool) error { + opts := &images.RemoveOptions{ + Force: &force, + } + _, errs := images.Remove(r.conn, []string{id}, opts) + if len(errs) > 0 { + return errs[0] + } + return nil +} + +// PruneImages removes unused images. +func (r *SocketRuntime) PruneImages(ctx context.Context) error { + _, err := images.Prune(r.conn, nil) + return err +} + +// Volume operations + +// ListVolumes returns all volumes. +func (r *SocketRuntime) ListVolumes(ctx context.Context) ([]VolumeSummary, error) { + opts := &volumes.ListOptions{} + podmanVolumes, err := volumes.List(r.conn, opts) + if err != nil { + return nil, err + } + return convertPodmanVolumeList(podmanVolumes), nil +} + +// RemoveVolume removes a volume. +func (r *SocketRuntime) RemoveVolume(ctx context.Context, name string, force bool) error { + opts := &volumes.RemoveOptions{ + Force: &force, + } + return volumes.Remove(r.conn, name, opts) +} + +// PruneVolumes removes unused volumes. +func (r *SocketRuntime) PruneVolumes(ctx context.Context) error { + _, err := volumes.Prune(r.conn, nil) + return err +} + +// Network operations + +// ListNetworks returns all network. +func (r *SocketRuntime) ListNetworks(ctx context.Context) ([]NetworkSummary, error) { + opts := &network.ListOptions{} + podmanNetworks, err := network.List(r.conn, opts) + if err != nil { + return nil, err + } + return convertPodmanNetworkList(podmanNetworks), nil +} + +// RemoveNetwork removes a network. +func (r *SocketRuntime) RemoveNetwork(ctx context.Context, name string) error { + _, err := network.Remove(r.conn, name, nil) + return err +} + +// PruneNetworks removes unused network. +func (r *SocketRuntime) PruneNetworks(ctx context.Context) error { + _, err := network.Prune(r.conn, nil) + return err +} + +// Events streams container runtime events. +func (r *SocketRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) { + eventChan := make(chan Event) + errChan := make(chan error, 1) + + go func() { + defer close(eventChan) + defer close(errChan) + + opts := &system.EventsOptions{} + podmanEventChan, err := system.Events(r.conn, nil, opts) + if err != nil { + errChan <- err + return + } + + for podmanEvent := range podmanEventChan { + event := Event{ + Type: string(podmanEvent.Type), + Action: string(podmanEvent.Action), + Actor: EventActor{ + ID: podmanEvent.Actor.ID, + Attributes: podmanEvent.Actor.Attributes, + }, + Time: podmanEvent.Time, + } + select { + case eventChan <- event: + case <-ctx.Done(): + return + } + } + }() + + return eventChan, errChan +} + +// Conversion functions + +func convertPodmanContainerList(podmanContainers []entities.ListContainer) []ContainerSummary { + result := make([]ContainerSummary, len(podmanContainers)) + for i, c := range podmanContainers { + result[i] = ContainerSummary{ + ID: c.ID, + Names: c.Names, + Image: c.Image, + ImageID: c.ImageID, + Command: commandToString(c.Command), + Created: c.Created.Unix(), + State: c.State, + Status: c.Status, + Ports: convertPodmanPorts(c.Ports), + Labels: c.Labels, + SizeRw: c.Size.RwSize, + SizeRootFs: c.Size.RootFsSize, + Pod: c.Pod, + PodName: c.PodName, + } + } + return result +} + +func commandToString(cmd []string) string { + if len(cmd) == 0 { + return "" + } + // Return the command as JSON for consistency with Docker + data, _ := json.Marshal(cmd) + return string(data) +} + +func convertPodmanPorts(ports []entities.PortMapping) []PortMapping { + result := make([]PortMapping, len(ports)) + for i, p := range ports { + result[i] = PortMapping{ + IP: p.HostIP, + PrivatePort: p.ContainerPort, + PublicPort: p.HostPort, + Type: p.Protocol, + } + } + return result +} + +func convertPodmanContainerInspect(data *entities.InspectContainerData) *ContainerDetails { + if data == nil { + return nil + } + + details := &ContainerDetails{ + ID: data.ID, + Name: data.Name, + Created: data.Created, + Path: data.Path, + Args: data.Args, + Image: data.Image, + ImageID: data.ImageName, + ResolvConfPath: data.ResolvConfPath, + HostnamePath: data.HostnamePath, + HostsPath: data.HostsPath, + LogPath: data.LogPath, + RestartCount: int(data.RestartCount), + Driver: data.Driver, + MountLabel: data.MountLabel, + ProcessLabel: data.ProcessLabel, + AppArmorProfile: data.AppArmorProfile, + } + + if data.State != nil { + details.State = &ContainerState{ + Status: data.State.Status, + Running: data.State.Running, + Paused: data.State.Paused, + Restarting: data.State.Restarting, + OOMKilled: data.State.OOMKilled, + Dead: data.State.Dead, + Pid: data.State.Pid, + ExitCode: int(data.State.ExitCode), + Error: data.State.Error, + StartedAt: data.State.StartedAt, + FinishedAt: data.State.FinishedAt, + } + + if data.State.Health != nil { + details.State.Health = &HealthState{ + Status: data.State.Health.Status, + FailingStreak: data.State.Health.FailingStreak, + } + } + } + + if data.Config != nil { + details.Config = &ContainerConfig{ + Hostname: data.Config.Hostname, + Domainname: data.Config.Domainname, + User: data.Config.User, + AttachStdin: data.Config.AttachStdin, + AttachStdout: data.Config.AttachStdout, + AttachStderr: data.Config.AttachStderr, + Tty: data.Config.Tty, + OpenStdin: data.Config.OpenStdin, + StdinOnce: data.Config.StdinOnce, + Env: data.Config.Env, + Cmd: data.Config.Cmd, + Image: data.Config.Image, + WorkingDir: data.Config.WorkingDir, + Entrypoint: data.Config.Entrypoint, + OnBuild: data.Config.OnBuild, + Labels: data.Config.Labels, + StopSignal: data.Config.StopSignal, + } + } + + return details +} + +func convertPodmanContainerStats(stat entities.ContainerStats) ContainerStatsEntry { + return ContainerStatsEntry{ + Read: time.Now(), + PreRead: time.Now().Add(-time.Second), + CPUStats: CPUStats{ + CPUUsage: CPUUsage{ + TotalUsage: int64(stat.CPU), + }, + SystemCPUUsage: int64(stat.CPUSystemNano), + OnlineCpus: int(stat.CPUNano), + }, + PreCPUStats: CPUStats{}, + MemoryStats: MemoryStats{ + Usage: int64(stat.MemUsage), + Limit: int64(stat.MemLimit), + }, + PidsStats: PidsStats{ + Current: int(stat.PIDs), + }, + Name: stat.Name, + ID: stat.ContainerID, + } +} + +func convertPodmanImageList(podmanImages []*entities.ImageSummary) []ImageSummary { + result := make([]ImageSummary, len(podmanImages)) + for i, img := range podmanImages { + result[i] = ImageSummary{ + ID: img.ID, + ParentID: img.ParentId, + RepoTags: img.RepoTags, + RepoDigests: img.RepoDigests, + Created: img.Created, + Size: img.Size, + SharedSize: img.SharedSize, + VirtualSize: img.VirtualSize, + Labels: img.Labels, + Containers: img.Containers, + } + } + return result +} + +func convertPodmanImageInspect(data *entities.ImageInspectReport) *ImageDetails { + if data == nil { + return nil + } + + details := &ImageDetails{ + ID: data.ID, + RepoTags: data.RepoTags, + RepoDigests: data.RepoDigests, + Parent: data.Parent, + Comment: data.Comment, + Created: *data.Created, + Author: data.Author, + Architecture: data.Architecture, + Os: data.Os, + Size: data.Size, + VirtualSize: data.VirtualSize, + } + + if data.RootFS != nil { + details.RootFS = RootFS{ + Type: data.RootFS.Type, + Layers: data.RootFS.Layers, + } + } + + return details +} + +func convertPodmanImageHistory(history []*entities.ImageHistoryLayer) []ImageHistoryEntry { + result := make([]ImageHistoryEntry, len(history)) + for i, layer := range history { + result[i] = ImageHistoryEntry{ + ID: layer.ID, + Created: layer.Created.Unix(), + CreatedBy: layer.CreatedBy, + Tags: layer.Tags, + Size: layer.Size, + Comment: layer.Comment, + } + } + return result +} + +func convertPodmanVolumeList(podmanVolumes []*entities.VolumeListReport) []VolumeSummary { + result := make([]VolumeSummary, len(podmanVolumes)) + for i, vol := range podmanVolumes { + result[i] = VolumeSummary{ + Name: vol.Name, + Driver: vol.Driver, + Mountpoint: vol.Mountpoint, + CreatedAt: vol.CreatedAt, + Labels: vol.Labels, + Scope: vol.Scope, + Options: vol.Options, + } + } + return result +} + +func convertPodmanNetworkList(podmanNetworks []entities.NetworkListReport) []NetworkSummary { + result := make([]NetworkSummary, len(podmanNetworks)) + for i, nw := range podmanNetworks { + result[i] = NetworkSummary{ + Name: nw.Name, + ID: nw.ID, + Created: nw.Created, + Driver: nw.Driver, + Labels: nw.Labels, + } + } + return result +} diff --git a/pkg/commands/runtime_types.go b/pkg/commands/runtime_types.go new file mode 100644 index 00000000..890cd189 --- /dev/null +++ b/pkg/commands/runtime_types.go @@ -0,0 +1,426 @@ +package commands + +import "time" + +// ContainerSummary provides runtime-agnostic container information. +// This replaces Docker's container.Summary type. +type ContainerSummary struct { + ID string + Names []string + Image string + ImageID string + Command string + Created int64 + State string + Status string + Ports []PortMapping + Labels map[string]string + SizeRw int64 + SizeRootFs int64 + // Podman-specific fields + Pod string + PodName string +} + +// PortMapping represents a container port mapping. +type PortMapping struct { + IP string + PrivatePort uint16 + PublicPort uint16 + Type string +} + +// ContainerDetails provides full container inspection data. +// This replaces Docker's container.InspectResponse type. +type ContainerDetails struct { + ID string + Name string + Created time.Time + Path string + Args []string + State *ContainerState + Image string + ImageID string + ResolvConfPath string + HostnamePath string + HostsPath string + LogPath string + RestartCount int + Driver string + Platform string + MountLabel string + ProcessLabel string + AppArmorProfile string + Config *ContainerConfig + NetworkSettings *NetworkSettings + Mounts []Mount +} + +// ContainerState represents the state of a container. +type ContainerState struct { + Status string + Running bool + Paused bool + Restarting bool + OOMKilled bool + Dead bool + Pid int + ExitCode int + Error string + StartedAt time.Time + FinishedAt time.Time + Health *HealthState +} + +// HealthState represents the health check state of a container. +type HealthState struct { + Status string + FailingStreak int + Log []HealthLog +} + +// HealthLog represents a health check log entry. +type HealthLog struct { + Start time.Time + End time.Time + ExitCode int + Output string +} + +// ContainerConfig represents container configuration. +type ContainerConfig struct { + Hostname string + Domainname string + User string + AttachStdin bool + AttachStdout bool + AttachStderr bool + ExposedPorts map[string]struct{} + Tty bool + OpenStdin bool + StdinOnce bool + Env []string + Cmd []string + Image string + Volumes map[string]struct{} + WorkingDir string + Entrypoint []string + OnBuild []string + Labels map[string]string + StopSignal string + StopTimeout *int + Shell []string +} + +// NetworkSettings represents container network settings. +type NetworkSettings struct { + Bridge string + SandboxID string + HairpinMode bool + LinkLocalIPv6Address string + LinkLocalIPv6PrefixLen int + Ports map[string][]PortBinding + SandboxKey string + Networks map[string]*EndpointSettings +} + +// PortBinding represents a port binding. +type PortBinding struct { + HostIP string + HostPort string +} + +// EndpointSettings represents network endpoint settings. +type EndpointSettings struct { + IPAMConfig *EndpointIPAMConfig + Links []string + Aliases []string + NetworkID string + EndpointID string + Gateway string + IPAddress string + IPPrefixLen int + IPv6Gateway string + GlobalIPv6Address string + GlobalIPv6PrefixLen int + MacAddress string + DriverOpts map[string]string +} + +// EndpointIPAMConfig represents IPAM configuration for an endpoint. +type EndpointIPAMConfig struct { + IPv4Address string + IPv6Address string + LinkLocalIPs []string +} + +// Mount represents a container mount. +type Mount struct { + Type string + Name string + Source string + Destination string + Driver string + Mode string + RW bool + Propagation string +} + +// ContainerStatsEntry provides runtime-agnostic container stats. +// This is used for streaming stats from the runtime. +type ContainerStatsEntry struct { + Read time.Time + PreRead time.Time + CPUStats CPUStats + PreCPUStats CPUStats + MemoryStats MemoryStats + PidsStats PidsStats + Networks map[string]NetworkStats + BlkioStats BlkioStats + Name string + ID string +} + +// CPUStats represents CPU usage statistics. +type CPUStats struct { + CPUUsage CPUUsage + SystemCPUUsage int64 + OnlineCpus int + ThrottlingData ThrottlingData +} + +// CPUUsage represents CPU usage breakdown. +type CPUUsage struct { + TotalUsage int64 + PercpuUsage []int64 + UsageInKernelmode int64 + UsageInUsermode int64 +} + +// ThrottlingData represents CPU throttling data. +type ThrottlingData struct { + Periods int + ThrottledPeriods int + ThrottledTime int64 +} + +// MemoryStats represents memory usage statistics. +type MemoryStats struct { + Usage int64 + MaxUsage int64 + Limit int64 + Stats MemoryStatsDetails +} + +// MemoryStatsDetails provides detailed memory statistics. +type MemoryStatsDetails struct { + ActiveAnon int64 + ActiveFile int64 + Cache int64 + Dirty int64 + HierarchicalMemoryLimit int64 + HierarchicalMemswLimit int64 + InactiveAnon int64 + InactiveFile int64 + MappedFile int64 + Pgfault int64 + Pgmajfault int64 + Pgpgin int64 + Pgpgout int64 + Rss int64 + RssHuge int64 + TotalActiveAnon int64 + TotalActiveFile int64 + TotalCache int64 + TotalDirty int64 + TotalInactiveAnon int64 + TotalInactiveFile int64 + TotalMappedFile int64 + TotalPgfault int64 + TotalPgmajfault int64 + TotalPgpgin int64 + TotalPgpgout int64 + TotalRss int64 + TotalRssHuge int64 + TotalUnevictable int64 + TotalWriteback int64 + Unevictable int64 + Writeback int64 +} + +// PidsStats represents PID statistics. +type PidsStats struct { + Current int + Limit int64 +} + +// NetworkStats represents network I/O statistics. +type NetworkStats struct { + RxBytes int64 + RxPackets int64 + RxErrors int64 + RxDropped int64 + TxBytes int64 + TxPackets int64 + TxErrors int64 + TxDropped int64 +} + +// BlkioStats represents block I/O statistics. +type BlkioStats struct { + IoServiceBytesRecursive []BlkioStatEntry + IoServicedRecursive []BlkioStatEntry + IoQueueRecursive []BlkioStatEntry + IoServiceTimeRecursive []BlkioStatEntry + IoWaitTimeRecursive []BlkioStatEntry + IoMergedRecursive []BlkioStatEntry + IoTimeRecursive []BlkioStatEntry + SectorsRecursive []BlkioStatEntry +} + +// BlkioStatEntry represents a single block I/O stat entry. +type BlkioStatEntry struct { + Major int64 + Minor int64 + Op string + Value int64 +} + +// ImageSummary provides runtime-agnostic image information. +// This replaces Docker's image.Summary type. +type ImageSummary struct { + ID string + ParentID string + RepoTags []string + RepoDigests []string + Created int64 + Size int64 + SharedSize int64 + VirtualSize int64 + Labels map[string]string + Containers int64 +} + +// ImageDetails provides full image inspection data. +type ImageDetails struct { + ID string + RepoTags []string + RepoDigests []string + Parent string + Comment string + Created time.Time + Container string + DockerVersion string + Author string + Config *ContainerConfig + Architecture string + Os string + OsVersion string + Size int64 + VirtualSize int64 + RootFS RootFS + Metadata ImageMetadata +} + +// RootFS represents the root filesystem of an image. +type RootFS struct { + Type string + Layers []string +} + +// ImageMetadata represents image metadata. +type ImageMetadata struct { + LastTagTime time.Time +} + +// ImageHistoryEntry represents a layer in image history. +type ImageHistoryEntry struct { + ID string + Created int64 + CreatedBy string + Tags []string + Size int64 + Comment string +} + +// VolumeSummary provides runtime-agnostic volume information. +// This replaces Docker's volume.Volume type. +type VolumeSummary struct { + Name string + Driver string + Mountpoint string + CreatedAt time.Time + Status map[string]interface{} + Labels map[string]string + Scope string + Options map[string]string + UsageData *VolumeUsageData +} + +// VolumeUsageData represents volume usage statistics. +type VolumeUsageData struct { + Size int64 + RefCount int64 +} + +// NetworkSummary provides runtime-agnostic network information. +// This replaces Docker's network.Inspect type. +type NetworkSummary struct { + Name string + ID string + Created time.Time + Scope string + Driver string + EnableIPv6 bool + IPAM IPAM + Internal bool + Attachable bool + Ingress bool + Containers map[string]EndpointResource + Options map[string]string + Labels map[string]string +} + +// IPAM represents IP Address Management configuration. +type IPAM struct { + Driver string + Options map[string]string + Config []IPAMConfig +} + +// IPAMConfig represents IPAM configuration for a network. +type IPAMConfig struct { + Subnet string + IPRange string + Gateway string + AuxAddress map[string]string +} + +// EndpointResource represents a container endpoint on a network. +type EndpointResource struct { + Name string + EndpointID string + MacAddress string + IPv4Address string + IPv6Address string +} + +// TopResponse represents the response from ContainerTop. +type TopResponse struct { + Titles []string + Processes [][]string +} + +// Event represents a container runtime event (container start/stop, image pull, etc.) +type Event struct { + Type string // "container", "image", "volume", "network" + Action string // "start", "stop", "create", "delete", etc. + Actor EventActor + Time int64 +} + +// EventActor represents the actor (container, image, etc.) that triggered an event. +type EventActor struct { + ID string + Attributes map[string]string +} diff --git a/pkg/commands/service.go b/pkg/commands/service.go index bdeab34a..f5de7b4c 100644 --- a/pkg/commands/service.go +++ b/pkg/commands/service.go @@ -4,24 +4,23 @@ import ( "context" "os/exec" - "github.com/docker/docker/api/types/container" "github.com/christophe-duc/lazypodman/pkg/utils" "github.com/sirupsen/logrus" ) -// Service : A docker Service +// Service represents a compose service type Service struct { Name string ID string OSCommand *OSCommand Log *logrus.Entry Container *Container - DockerCommand LimitedDockerCommand + PodmanCommand LimitedPodmanCommand } // Remove removes the service's containers -func (s *Service) Remove(options container.RemoveOptions) error { - return s.Container.Remove(options) +func (s *Service) Remove(force bool, removeVolumes bool) error { + return s.Container.Remove(force, removeVolumes) } // Stop stops the service's containers @@ -47,7 +46,7 @@ func (s *Service) Start() error { func (s *Service) runCommand(templateCmdStr string) error { command := utils.ApplyTemplate( templateCmdStr, - s.DockerCommand.NewCommandObject(CommandObject{Service: s}), + s.PodmanCommand.NewCommandObject(CommandObject{Service: s}), ) return s.OSCommand.RunCommand(command) } @@ -62,7 +61,7 @@ func (s *Service) ViewLogs() (*exec.Cmd, error) { templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ViewServiceLogs command := utils.ApplyTemplate( templateString, - s.DockerCommand.NewCommandObject(CommandObject{Service: s}), + s.PodmanCommand.NewCommandObject(CommandObject{Service: s}), ) cmd := s.OSCommand.ExecutableFromString(command) @@ -76,7 +75,7 @@ func (s *Service) RenderTop(ctx context.Context) (string, error) { templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ServiceTop command := utils.ApplyTemplate( templateString, - s.DockerCommand.NewCommandObject(CommandObject{Service: s}), + s.PodmanCommand.NewCommandObject(CommandObject{Service: s}), ) return s.OSCommand.RunCommandWithOutputContext(ctx, command) diff --git a/pkg/commands/volume.go b/pkg/commands/volume.go index 169ba8d5..0a91c329 100644 --- a/pkg/commands/volume.go +++ b/pkg/commands/volume.go @@ -3,54 +3,21 @@ package commands import ( "context" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/volume" - "github.com/docker/docker/client" "github.com/sirupsen/logrus" ) -// Volume : A docker Volume +// Volume represents a Podman volume type Volume struct { Name string - Volume *volume.Volume - Client *client.Client + Summary VolumeSummary + Runtime ContainerRuntime OSCommand *OSCommand Log *logrus.Entry - DockerCommand LimitedDockerCommand -} - -// RefreshVolumes gets the volumes and stores them -func (c *DockerCommand) RefreshVolumes() ([]*Volume, error) { - result, err := c.Client.VolumeList(context.Background(), volume.ListOptions{}) - if err != nil { - return nil, err - } - - volumes := result.Volumes - - ownVolumes := make([]*Volume, len(volumes)) - - for i, vol := range volumes { - ownVolumes[i] = &Volume{ - Name: vol.Name, - Volume: vol, - Client: c.Client, - OSCommand: c.OSCommand, - Log: c.Log, - DockerCommand: c, - } - } - - return ownVolumes, nil -} - -// PruneVolumes prunes volumes -func (c *DockerCommand) PruneVolumes() error { - _, err := c.Client.VolumesPrune(context.Background(), filters.Args{}) - return err + PodmanCommand LimitedPodmanCommand } // Remove removes the volume func (v *Volume) Remove(force bool) error { - return v.Client.VolumeRemove(context.Background(), v.Name, force) + ctx := context.Background() + return v.Runtime.RemoveVolume(ctx, v.Name, force) } diff --git a/pkg/gui/container_logs.go b/pkg/gui/container_logs.go index 3635244b..1f9c9c6d 100644 --- a/pkg/gui/container_logs.go +++ b/pkg/gui/container_logs.go @@ -8,8 +8,6 @@ import ( "os/signal" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/pkg/stdcopy" "github.com/fatih/color" "github.com/christophe-duc/lazypodman/pkg/commands" "github.com/christophe-duc/lazypodman/pkg/tasks" @@ -105,48 +103,42 @@ func (gui *Gui) promptToReturn() { } func (gui *Gui) writeContainerLogs(ctr *commands.Container, ctx context.Context, writer io.Writer) error { - readCloser, err := gui.DockerCommand.Client.ContainerLogs(ctx, ctr.ID, container.LogsOptions{ - ShowStdout: true, - ShowStderr: true, - Timestamps: gui.Config.UserConfig.Logs.Timestamps, - Since: gui.Config.UserConfig.Logs.Since, - Tail: gui.Config.UserConfig.Logs.Tail, - Follow: true, - }) - if err != nil { + // Build podman logs command + args := []string{"logs", "--follow"} + + if gui.Config.UserConfig.Logs.Timestamps { + args = append(args, "--timestamps") + } + if gui.Config.UserConfig.Logs.Since != "" { + args = append(args, "--since", gui.Config.UserConfig.Logs.Since) + } + if gui.Config.UserConfig.Logs.Tail != "" { + args = append(args, "--tail", gui.Config.UserConfig.Logs.Tail) + } + args = append(args, ctr.ID) + + cmd := ctr.OSCommand.NewCmd("podman", args...) + cmd.Stdout = writer + cmd.Stderr = writer + + if err := cmd.Start(); err != nil { gui.Log.Error(err) return err } - defer readCloser.Close() - if !ctr.DetailsLoaded() { - // loop until the details load or context is cancelled, using timer - ticker := time.NewTicker(time.Millisecond * 100) - defer ticker.Stop() - outer: - for { - select { - case <-ctx.Done(): - return nil - case <-ticker.C: - if ctr.DetailsLoaded() { - break outer - } - } + // Wait for context cancellation or command completion + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + + select { + case <-ctx.Done(): + if err := ctr.OSCommand.Kill(cmd); err != nil { + gui.Log.Warn(err) } + return nil + case err := <-done: + return err } - - if ctr.Details.Config.Tty { - _, err = io.Copy(writer, readCloser) - if err != nil { - return err - } - } else { - _, err = stdcopy.StdCopy(writer, writer, readCloser) - if err != nil { - return err - } - } - - return nil } diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index ff98bf94..494a9c7e 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -6,7 +6,6 @@ import ( "strings" "time" - "github.com/docker/docker/api/types/container" "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/christophe-duc/lazypodman/pkg/commands" @@ -255,7 +254,7 @@ func (gui *Gui) refreshContainersAndServices() error { originalSelectedLineIdx := gui.Panels.Services.SelectedIdx selectedService, isServiceSelected := gui.Panels.Services.List.TryGet(originalSelectedLineIdx) - containers, services, err := gui.DockerCommand.RefreshContainersAndServices( + containers, services, err := gui.PodmanCommand.RefreshContainersAndServices( gui.Panels.Services.List.GetAllItems(), gui.Panels.Containers.List.GetAllItems(), ) @@ -283,7 +282,7 @@ func (gui *Gui) refreshContainersAndServices() error { } func (gui *Gui) renderContainersAndServices() error { - if gui.DockerCommand.InDockerComposeProject { + if gui.PodmanCommand.InDockerComposeProject { if err := gui.Panels.Services.RerenderList(); err != nil { return err } @@ -308,14 +307,13 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error { return nil } - handleMenuPress := func(configOptions container.RemoveOptions) error { + handleMenuPress := func(force bool, removeVolumes bool) error { return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error { - if err := ctr.Remove(configOptions); err != nil { + if err := ctr.Remove(force, removeVolumes); err != nil { if commands.HasErrorCode(err, commands.MustStopContainer) { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.MustForceToRemoveContainer, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error { - configOptions.Force = true - return ctr.Remove(configOptions) + return ctr.Remove(true, removeVolumes) }) }, nil) } @@ -327,12 +325,12 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error { menuItems := []*types.MenuItem{ { - LabelColumns: []string{gui.Tr.Remove, "docker rm " + ctr.ID[1:10]}, - OnPress: func() error { return handleMenuPress(container.RemoveOptions{}) }, + LabelColumns: []string{gui.Tr.Remove, "podman rm " + ctr.ID[1:10]}, + OnPress: func() error { return handleMenuPress(false, false) }, }, { - LabelColumns: []string{gui.Tr.RemoveWithVolumes, "docker rm --volumes " + ctr.ID[1:10]}, - OnPress: func() error { return handleMenuPress(container.RemoveOptions{RemoveVolumes: true}) }, + LabelColumns: []string{gui.Tr.RemoveWithVolumes, "podman rm --volumes " + ctr.ID[1:10]}, + OnPress: func() error { return handleMenuPress(false, true) }, }, } @@ -416,7 +414,7 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handlePruneContainers() error { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmPruneContainers, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { - err := gui.DockerCommand.PruneContainers() + err := gui.PodmanCommand.PruneContainers() if err != nil { return gui.createErrorPanel(err.Error()) } @@ -446,7 +444,7 @@ func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) containerExecShell(container *commands.Container) error { - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{ + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{ Container: container, }) @@ -463,7 +461,7 @@ func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error return nil } - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{ + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{ Container: ctr, }) @@ -490,7 +488,7 @@ func (gui *Gui) handleRemoveContainers() error { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmRemoveContainers, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error { for _, ctr := range gui.Panels.Containers.List.GetAllItems() { - if err := ctr.Remove(container.RemoveOptions{Force: true}); err != nil { + if err := ctr.Remove(true, false); err != nil { gui.Log.Error(err) } } @@ -517,7 +515,7 @@ func (gui *Gui) handleContainersBulkCommand(g *gocui.Gui, v *gocui.View) error { } bulkCommands := append(baseBulkCommands, gui.Config.UserConfig.BulkCommands.Containers...) - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{}) + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{}) return gui.createBulkCommandMenu(bulkCommands, commandObject) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 0df9d9fa..c94db648 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -6,8 +6,6 @@ import ( "strings" "time" - "github.com/docker/docker/api/types/events" - "github.com/go-errors/errors" throttle "github.com/boz/go-throttle" @@ -27,7 +25,7 @@ import ( type Gui struct { g *gocui.Gui Log *logrus.Entry - DockerCommand *commands.DockerCommand + PodmanCommand *commands.PodmanCommand OSCommand *commands.OSCommand State guiState Config *config.AppConfig @@ -125,7 +123,7 @@ func getScreenMode(config *config.AppConfig) WindowMaximisation { } // NewGui builds a new gui handler -func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand *commands.OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*Gui, error) { +func NewGui(log *logrus.Entry, podmanCommand *commands.PodmanCommand, oSCommand *commands.OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*Gui, error) { initialState := guiState{ Platform: *oSCommand.Platform, Panels: &panelStates{ @@ -141,7 +139,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand gui := &Gui{ Log: log, - DockerCommand: dockerCommand, + PodmanCommand: podmanCommand, OSCommand: oSCommand, State: initialState, Config: config, @@ -292,7 +290,7 @@ func (gui *Gui) setPanels() { } func (gui *Gui) updateContainerDetails() error { - return gui.DockerCommand.RefreshContainerDetails(gui.Panels.Containers.List.GetAllItems()) + return gui.PodmanCommand.RefreshContainerDetails(gui.Panels.Containers.List.GetAllItems()) } func (gui *Gui) refresh() { @@ -328,50 +326,38 @@ func (gui *Gui) listenForEvents(ctx context.Context, refresh func()) { onError := func(err error) { if err != nil { - gui.ErrorChan <- errors.Errorf("Docker event stream returned error: %s\nRetry count: %d", err.Error(), errorCount) + gui.ErrorChan <- errors.Errorf("Podman event stream returned error: %s\nRetry count: %d", err.Error(), errorCount) } errorCount++ time.Sleep(time.Second * 2) } -outer: + // Try to use the runtime's event stream if available + eventChan, errChan := gui.PodmanCommand.Runtime.Events(ctx) + for { - messageChan, errChan := gui.DockerCommand.Client.Events(context.Background(), events.ListOptions{}) - - if errorCount > 0 { - select { - case <-ctx.Done(): - return - case err := <-errChan: - onError(err) - continue outer - default: - // If we're here then we lost connection to docker and we just got it back. - // The reason we do this refresh explicitly is because successfully - // reconnecting with docker does not mean it's going to send us a new - // event any time soon. - - // Assuming the confirmation prompt currently holds the given error - _ = gui.closeConfirmationPrompt() - refresh() - errorCount = 0 - } - } - - for { - select { - case <-ctx.Done(): - return - case message := <-messageChan: + select { + case <-ctx.Done(): + return + case event := <-eventChan: + if event.Type != "" { // We could be more granular about what events should trigger which refreshes. // At the moment it's pretty efficient though, and it might not be worth // the maintenance burden of mapping specific events to specific refreshes refresh() - - gui.Log.Infof("received event of type: %s", message.Type) - case err := <-errChan: + gui.Log.Infof("received event of type: %s", event.Type) + } + case err := <-errChan: + if err != nil { onError(err) - continue outer + // Attempt to reconnect + if errorCount > 0 { + _ = gui.closeConfirmationPrompt() + refresh() + errorCount = 0 + } + // Re-establish event stream + eventChan, errChan = gui.PodmanCommand.Runtime.Events(ctx) } } } @@ -459,7 +445,7 @@ func (gui *Gui) ShouldRefresh(key string) bool { } func (gui *Gui) initiallyFocusedViewName() string { - if gui.DockerCommand.InDockerComposeProject { + if gui.PodmanCommand.InDockerComposeProject { return "services" } return "containers" @@ -485,7 +471,7 @@ func (gui *Gui) monitorContainerStats(ctx context.Context) { case <-ticker.C: for _, container := range gui.Panels.Containers.List.GetAllItems() { if !container.MonitoringStats { - go gui.DockerCommand.CreateClientStatMonitor(container) + go gui.PodmanCommand.CreateClientStatMonitor(container) } } } diff --git a/pkg/gui/images_panel.go b/pkg/gui/images_panel.go index b80b52e1..22323efb 100644 --- a/pkg/gui/images_panel.go +++ b/pkg/gui/images_panel.go @@ -5,7 +5,6 @@ import ( "strings" "time" - "github.com/docker/docker/api/types/image" "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/christophe-duc/lazypodman/pkg/commands" @@ -101,7 +100,7 @@ func (gui *Gui) reloadImages() error { } func (gui *Gui) refreshStateImages() error { - images, err := gui.DockerCommand.RefreshImages() + images, err := gui.PodmanCommand.RefreshImages() if err != nil { return err } @@ -121,9 +120,9 @@ func (gui *Gui) FilterString(view *gocui.View) string { func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { type removeImageOption struct { - description string - command string - configOptions image.RemoveOptions + description string + command string + force bool } img, err := gui.Panels.Images.GetSelectedItem() @@ -133,27 +132,17 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { shortSha := img.ID[7:17] - // TODO: have a way of toggling in a menu instead of showing each permutation as a separate menu item + // Simplified menu - Podman's rmi handles pruning automatically options := []*removeImageOption{ { - description: gui.Tr.Remove, - command: "docker image rm " + shortSha, - configOptions: image.RemoveOptions{PruneChildren: true, Force: false}, + description: gui.Tr.Remove, + command: "podman image rm " + shortSha, + force: false, }, { - description: gui.Tr.RemoveWithoutPrune, - command: "docker image rm --no-prune " + shortSha, - configOptions: image.RemoveOptions{PruneChildren: false, Force: false}, - }, - { - description: gui.Tr.RemoveWithForce, - command: "docker image rm --force " + shortSha, - configOptions: image.RemoveOptions{PruneChildren: true, Force: true}, - }, - { - description: gui.Tr.RemoveWithoutPruneWithForce, - command: "docker image rm --no-prune --force " + shortSha, - configOptions: image.RemoveOptions{PruneChildren: false, Force: true}, + description: gui.Tr.RemoveWithForce, + command: "podman image rm --force " + shortSha, + force: true, }, } @@ -164,7 +153,7 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { color.New(color.FgRed).Sprint(option.command), }, OnPress: func() error { - if err := img.Remove(option.configOptions); err != nil { + if err := img.Remove(option.force); err != nil { return gui.createErrorPanel(err.Error()) } @@ -182,7 +171,7 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handlePruneImages() error { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmPruneImages, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { - err := gui.DockerCommand.PruneImages() + err := gui.PodmanCommand.PruneImages() if err != nil { return gui.createErrorPanel(err.Error()) } @@ -197,7 +186,7 @@ func (gui *Gui) handleImagesCustomCommand(g *gocui.Gui, v *gocui.View) error { return nil } - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{ + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{ Image: img, }) @@ -215,7 +204,7 @@ func (gui *Gui) handleImagesBulkCommand(g *gocui.Gui, v *gocui.View) error { } bulkCommands := append(baseBulkCommands, gui.Config.UserConfig.BulkCommands.Images...) - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{}) + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{}) return gui.createBulkCommandMenu(bulkCommands, commandObject) } diff --git a/pkg/gui/networks_panel.go b/pkg/gui/networks_panel.go index b758dcee..750b15cf 100644 --- a/pkg/gui/networks_panel.go +++ b/pkg/gui/networks_panel.go @@ -89,7 +89,7 @@ func (gui *Gui) reloadNetworks() error { } func (gui *Gui) refreshStateNetworks() error { - networks, err := gui.DockerCommand.RefreshNetworks() + networks, err := gui.PodmanCommand.RefreshNetworks() if err != nil { return err } @@ -140,7 +140,7 @@ func (gui *Gui) handleNetworksRemoveMenu(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handlePruneNetworks() error { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmPruneNetworks, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { - err := gui.DockerCommand.PruneNetworks() + err := gui.PodmanCommand.PruneNetworks() if err != nil { return gui.createErrorPanel(err.Error()) } @@ -155,7 +155,7 @@ func (gui *Gui) handleNetworksCustomCommand(g *gocui.Gui, v *gocui.View) error { return nil } - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{ + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{ Network: network, }) @@ -173,7 +173,7 @@ func (gui *Gui) handleNetworksBulkCommand(g *gocui.Gui, v *gocui.View) error { } bulkCommands := append(baseBulkCommands, gui.Config.UserConfig.BulkCommands.Networks...) - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{}) + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{}) return gui.createBulkCommandMenu(bulkCommands, commandObject) } diff --git a/pkg/gui/project_panel.go b/pkg/gui/project_panel.go index d9cba823..b3705ab5 100644 --- a/pkg/gui/project_panel.go +++ b/pkg/gui/project_panel.go @@ -23,7 +23,7 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] { return &panels.SideListPanel[*commands.Project]{ ContextState: &panels.ContextState[*commands.Project]{ GetMainTabs: func() []panels.MainTab[*commands.Project] { - if gui.DockerCommand.InDockerComposeProject { + if gui.PodmanCommand.InDockerComposeProject { return []panels.MainTab[*commands.Project]{ { Key: "logs", @@ -79,7 +79,7 @@ func (gui *Gui) refreshProject() error { func (gui *Gui) getProjectName() string { projectName := path.Base(gui.Config.ProjectDir) - if gui.DockerCommand.InDockerComposeProject { + if gui.PodmanCommand.InDockerComposeProject { for _, service := range gui.Panels.Services.List.GetAllItems() { container := service.Container if container != nil && container.DetailsLoaded() { @@ -122,7 +122,7 @@ func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc { cmd := gui.OSCommand.RunCustomCommand( utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.AllLogs, - gui.DockerCommand.NewCommandObject(commands.CommandObject{}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{}), ), ) @@ -146,7 +146,7 @@ func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc { func (gui *Gui) renderDockerComposeConfig(_project *commands.Project) tasks.TaskFunc { return gui.NewSimpleRenderStringTask(func() string { - return utils.ColoredYamlString(gui.DockerCommand.DockerComposeConfig()) + return utils.ColoredYamlString(gui.PodmanCommand.DockerComposeConfig()) }) } @@ -173,7 +173,7 @@ func lazypodmanTitle() string { // handleViewAllLogs switches to a subprocess viewing all the logs from docker-compose func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error { - c, err := gui.DockerCommand.ViewAllLogs() + c, err := gui.PodmanCommand.ViewAllLogs() if err != nil { return gui.createErrorPanel(err.Error()) } diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index ba8cf8f7..5613b69f 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -78,7 +78,7 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] { return presentation.GetServiceDisplayStrings(&gui.Config.UserConfig.Gui, service) }, Hide: func() bool { - return !gui.DockerCommand.InDockerComposeProject + return !gui.PodmanCommand.InDockerComposeProject }, } } @@ -292,7 +292,7 @@ func (gui *Gui) handleProjectUp(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmUpProject, func(g *gocui.Gui, v *gocui.View) error { cmdStr := utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.Up, - gui.DockerCommand.NewCommandObject(commands.CommandObject{}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{}), ) return gui.WithWaitingStatus(gui.Tr.UppingProjectStatus, func() error { @@ -307,12 +307,12 @@ func (gui *Gui) handleProjectUp(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleProjectDown(g *gocui.Gui, v *gocui.View) error { downCommand := utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.Down, - gui.DockerCommand.NewCommandObject(commands.CommandObject{}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{}), ) downWithVolumesCommand := utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.DownWithVolumes, - gui.DockerCommand.NewCommandObject(commands.CommandObject{}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{}), ) options := []*commandOption{ @@ -363,12 +363,12 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error { rebuildCommand := utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.RebuildService, - gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{Service: service}), ) recreateCommand := utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.RecreateService, - gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{Service: service}), ) options := []*commandOption{ @@ -376,7 +376,7 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error { description: gui.Tr.Restart, command: utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.RestartService, - gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{Service: service}), ), onPress: func() error { return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error { @@ -391,7 +391,7 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error { description: gui.Tr.Recreate, command: utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.RecreateService, - gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{Service: service}), ), onPress: func() error { return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error { @@ -406,7 +406,7 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error { description: gui.Tr.Rebuild, command: utils.ApplyTemplate( gui.Config.UserConfig.CommandTemplates.RebuildService, - gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}), + gui.PodmanCommand.NewCommandObject(commands.CommandObject{Service: service}), ), onPress: func() error { return gui.runSubprocess(gui.OSCommand.RunCustomCommand(rebuildCommand)) @@ -433,7 +433,7 @@ func (gui *Gui) handleServicesCustomCommand(g *gocui.Gui, v *gocui.View) error { return nil } - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{ + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{ Service: service, Container: service.Container, }) @@ -466,7 +466,7 @@ L: func (gui *Gui) handleServicesBulkCommand(g *gocui.Gui, v *gocui.View) error { bulkCommands := gui.Config.UserConfig.BulkCommands.Services - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{}) + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{}) return gui.createBulkCommandMenu(bulkCommands, commandObject) } diff --git a/pkg/gui/views.go b/pkg/gui/views.go index 3de6d61a..49ef6607 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -128,7 +128,7 @@ func (gui *Gui) createAllViews() error { gui.Views.Containers.Highlight = true gui.Views.Containers.SelBgColor = selectedLineBgColor - if gui.Config.UserConfig.Gui.ShowAllContainers || !gui.DockerCommand.InDockerComposeProject { + if gui.Config.UserConfig.Gui.ShowAllContainers || !gui.PodmanCommand.InDockerComposeProject { gui.Views.Containers.Title = gui.Tr.ContainersTitle } else { gui.Views.Containers.Title = gui.Tr.StandaloneContainersTitle diff --git a/pkg/gui/volumes_panel.go b/pkg/gui/volumes_panel.go index 01988ca1..46929602 100644 --- a/pkg/gui/volumes_panel.go +++ b/pkg/gui/volumes_panel.go @@ -94,7 +94,7 @@ func (gui *Gui) reloadVolumes() error { } func (gui *Gui) refreshStateVolumes() error { - volumes, err := gui.DockerCommand.RefreshVolumes() + volumes, err := gui.PodmanCommand.RefreshVolumes() if err != nil { return err } @@ -152,7 +152,7 @@ func (gui *Gui) handleVolumesRemoveMenu(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handlePruneVolumes() error { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmPruneVolumes, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { - err := gui.DockerCommand.PruneVolumes() + err := gui.PodmanCommand.PruneVolumes() if err != nil { return gui.createErrorPanel(err.Error()) } @@ -167,7 +167,7 @@ func (gui *Gui) handleVolumesCustomCommand(g *gocui.Gui, v *gocui.View) error { return nil } - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{ + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{ Volume: volume, }) @@ -185,7 +185,7 @@ func (gui *Gui) handleVolumesBulkCommand(g *gocui.Gui, v *gocui.View) error { } bulkCommands := append(baseBulkCommands, gui.Config.UserConfig.BulkCommands.Volumes...) - commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{}) + commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{}) return gui.createBulkCommandMenu(bulkCommands, commandObject) }