diff --git a/cmd/lima-guestagent/daemon_linux.go b/cmd/lima-guestagent/daemon_linux.go index 919edb19a53..577f80c2b76 100644 --- a/cmd/lima-guestagent/daemon_linux.go +++ b/cmd/lima-guestagent/daemon_linux.go @@ -28,15 +28,24 @@ func newDaemonCommand() *cobra.Command { daemonCommand.Flags().Duration("tick", 3*time.Second, "Tick for polling events") daemonCommand.Flags().Int("vsock-port", 0, "Use vsock server instead a UNIX socket") daemonCommand.Flags().String("virtio-port", "", "Use virtio server instead a UNIX socket") + daemonCommand.Flags().StringSlice("docker-sockets", []string{}, "Paths to Docker socket files to monitor for exposed ports") + daemonCommand.Flags().StringSlice("containerd-sockets", []string{}, "Paths to Containerd socket files to monitor for exposed ports") + daemonCommand.Flags().StringSlice("kubernetes-configs", []string{}, "Path to Kubernetes config files to monitor for ports") return daemonCommand } func daemonAction(cmd *cobra.Command, _ []string) error { + if os.Geteuid() != 0 { + return errors.New("must run as the root user") + } socket := "/run/lima-guestagent.sock" tick, err := cmd.Flags().GetDuration("tick") if err != nil { return err } + if tick == 0 { + return errors.New("tick must be specified") + } vSockPort, err := cmd.Flags().GetInt("vsock-port") if err != nil { return err @@ -45,12 +54,19 @@ func daemonAction(cmd *cobra.Command, _ []string) error { if err != nil { return err } - if tick == 0 { - return errors.New("tick must be specified") + dockerSockets, err := cmd.Flags().GetStringSlice("docker-sockets") + if err != nil { + return err } - if os.Geteuid() != 0 { - return errors.New("must run as the root user") + containerdSockets, err := cmd.Flags().GetStringSlice("containerd-sockets") + if err != nil { + return err } + kubernetesConfigs, err := cmd.Flags().GetStringSlice("kubernetes-configs") + if err != nil { + return err + } + logrus.Infof("event tick: %v", tick) newTicker := func() (<-chan time.Time, func()) { @@ -61,7 +77,13 @@ func daemonAction(cmd *cobra.Command, _ []string) error { return ticker.C, ticker.Stop } - agent, err := guestagent.New(newTicker, tick*20) + agent, err := guestagent.New(&guestagent.Config{ + Ticker: newTicker, + IptablesIdle: tick * 20, + DockerSockets: dockerSockets, + ContainerdSockets: containerdSockets, + KubernetesConfigs: kubernetesConfigs, + }) if err != nil { return err } diff --git a/cmd/lima-guestagent/install_systemd_linux.go b/cmd/lima-guestagent/install_systemd_linux.go index bc654a56246..194903df3a3 100644 --- a/cmd/lima-guestagent/install_systemd_linux.go +++ b/cmd/lima-guestagent/install_systemd_linux.go @@ -26,6 +26,9 @@ func newInstallSystemdCommand() *cobra.Command { } installSystemdCommand.Flags().Int("vsock-port", 0, "Use vsock server on specified port") installSystemdCommand.Flags().String("virtio-port", "", "Use virtio server instead a UNIX socket") + installSystemdCommand.Flags().StringSlice("docker-sockets", []string{}, "Paths to Docker socket files to monitor for exposed ports") + installSystemdCommand.Flags().StringSlice("containerd-sockets", []string{}, "Paths to Containerd socket files to monitor for exposed ports") + installSystemdCommand.Flags().StringSlice("kubernetes-configs", []string{}, "Path to Kubernetes config files to monitor for ports") return installSystemdCommand } @@ -38,7 +41,24 @@ func installSystemdAction(cmd *cobra.Command, _ []string) error { if err != nil { return err } - unit, err := generateSystemdUnit(vsockPort, virtioPort) + dockerSockets, err := cmd.Flags().GetStringSlice("docker-sockets") + if err != nil { + return err + } + containerdSockets, err := cmd.Flags().GetStringSlice("containerd-sockets") + if err != nil { + return err + } + kubernetesConfigs, err := cmd.Flags().GetStringSlice("kubernetes-configs") + if err != nil { + return err + } + unit, err := generateSystemdUnit( + vsockPort, + virtioPort, + dockerSockets, + containerdSockets, + kubernetesConfigs) if err != nil { return err } @@ -77,7 +97,7 @@ func installSystemdAction(cmd *cobra.Command, _ []string) error { //go:embed lima-guestagent.TEMPLATE.service var systemdUnitTemplate string -func generateSystemdUnit(vsockPort int, virtioPort string) ([]byte, error) { +func generateSystemdUnit(vsockPort int, virtioPort string, dockerSockets, containerdSockets, kubeConfigs []string) ([]byte, error) { selfExeAbs, err := os.Executable() if err != nil { return nil, err @@ -90,6 +110,15 @@ func generateSystemdUnit(vsockPort int, virtioPort string) ([]byte, error) { if virtioPort != "" { args = append(args, fmt.Sprintf("--virtio-port %s", virtioPort)) } + if len(dockerSockets) > 0 { + args = append(args, fmt.Sprintf("--docker-sockets %s", strings.Join(dockerSockets, ","))) + } + if len(containerdSockets) > 0 { + args = append(args, fmt.Sprintf("--containerd-sockets %s", strings.Join(containerdSockets, ","))) + } + if len(kubeConfigs) > 0 { + args = append(args, fmt.Sprintf("--kubernetes-configs %s", strings.Join(kubeConfigs, ","))) + } m := map[string]string{ "Binary": selfExeAbs, diff --git a/go.mod b/go.mod index 0b05d9545a1..1fdd637f218 100644 --- a/go.mod +++ b/go.mod @@ -56,29 +56,40 @@ require ( require ( github.com/Code-Hex/go-infinity-channel v1.0.0 // indirect + github.com/Microsoft/hcsshim v0.11.7 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/a8m/envsubst v1.4.2 // indirect github.com/alecthomas/participle/v2 v2.1.4 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/containerd/cgroups v1.1.0 // indirect + github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/ttrpc v1.2.5 // indirect + github.com/creack/pty v1.1.18 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/digitalocean/go-libvirt v0.0.0-20220804181439-8648fbde413e // indirect github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/djherbis/times v1.6.0 // indirect + github.com/docker/docker v27.3.1+incompatible + github.com/docker/go-connections v0.5.0 + github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/elliotchance/orderedmap v1.8.0 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fatih/color v1.18.0 // indirect - // gomodjail:unconfined + github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/gogo/protobuf v1.3.2 // indirect + github.com/gogo/protobuf v1.3.2 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -92,6 +103,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/fs v0.1.0 // indirect github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 // indirect github.com/magiconair/properties v1.8.10 // indirect @@ -101,13 +113,23 @@ require ( github.com/mdlayher/socket v0.5.1 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/moby/sys/signal v0.7.0 // indirect + github.com/moby/term v0.5.0 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runtime-spec v1.1.0 // indirect + github.com/opencontainers/selinux v1.11.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.9 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect @@ -115,13 +137,18 @@ require ( github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect golang.org/x/crypto v0.40.0 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/oauth2 v0.28.0 // indirect golang.org/x/term v0.33.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.34.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect @@ -135,3 +162,15 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +require ( + github.com/containerd/containerd v1.6.38 + github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/typeurl v1.0.2 // indirect + github.com/gogo/googleapis v1.4.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect +) + +require go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/go.sum b/go.sum index b79170c3772..37761fda97e 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,19 @@ al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +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= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Code-Hex/go-infinity-channel v1.0.0 h1:M8BWlfDOxq9or9yvF9+YkceoTkDI1pFAqvnP87Zh0Nw= github.com/Code-Hex/go-infinity-channel v1.0.0/go.mod h1:5yUVg/Fqao9dAjcpzoQ33WwfdMWmISOrQloDRn3bsvY= github.com/Code-Hex/vz/v3 v3.7.0 h1:VEkfq5TVKnv85M81gQVPzLH9JzHrUJN/QQMpDZ+odPA= github.com/Code-Hex/vz/v3 v3.7.0/go.mod h1:1LsW0jqW0r0cQ+IeR4hHbjdqOtSidNCVMWhStMHGho8= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= +github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= @@ -30,12 +36,30 @@ github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0 github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/containerd v1.6.38 h1:AgSP9hVZT8JHzIAUjA7/wSmkTCuhzJCsaJ8QJ+zP84g= +github.com/containerd/containerd v1.6.38/go.mod h1:MtQjP1WJnC0DoVVzDWj5V1i0m0evpOlSmDPOV7w7zJY= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= 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/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= +github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containers/gvisor-tap-vsock v0.8.6 h1:9SeAXK+K2o36CtrgYk6zRXbU3zrayjvkrI8b7/O6u5A= github.com/containers/gvisor-tap-vsock v0.8.6/go.mod h1:+0mtKmm4STeSDnZe+DGnIwN4EH2f7AcWir7PwT28Ti0= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= @@ -43,8 +67,9 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -57,10 +82,18 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/diskfs/go-diskfs v1.6.0 h1:YmK5+vLSfkwC6kKKRTRPGaDGNF+Xh8FXeiNHwryDfu4= github.com/diskfs/go-diskfs v1.6.0/go.mod h1:bRFumZeGFCO8C2KNswrQeuj2m1WCVr4Ms5IjWMczMDk= +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/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 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/elastic/go-libaudit/v2 v2.6.2 h1:1PM6wVBTJHJQYsKl8jfA9/Aw9pFty5uUezPiUfKtOI4= @@ -73,8 +106,14 @@ github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1Ugj github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -82,8 +121,9 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -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.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -100,14 +140,37 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -121,10 +184,14 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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.1.2/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/google/yamlfmt v0.17.2 h1:TkXxhmj7dnpmOnlWGOXog92Gs6MWcTZqnf3kuyp8yFQ= github.com/google/yamlfmt v0.17.2/go.mod h1:gs0UEklJOYkUJ+OOCG0hg9n+DzucKDPlJElTUasVNK8= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= @@ -145,6 +212,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= @@ -190,11 +258,25 @@ github.com/mikefarah/yq/v4 v4.45.1 h1:EW+HjKEVa55pUYFJseEHEHdQ0+ulunY+q42zF3M7Za github.com/mikefarah/yq/v4 v4.45.1/go.mod h1:djgN2vD749hpjVNGYTShr5Kmv5LYljhCG3lUTuEe3LM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= +github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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/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/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= @@ -207,6 +289,12 @@ github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= 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/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= +github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -224,13 +312,16 @@ github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6kt 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= @@ -247,11 +338,13 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/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.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= @@ -268,18 +361,28 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 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/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= 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= @@ -292,6 +395,10 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -305,10 +412,15 @@ 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.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20201110031124-69a78807bb2b/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -321,8 +433,11 @@ 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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -337,6 +452,7 @@ 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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -346,6 +462,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/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-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -390,6 +507,11 @@ golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -406,10 +528,32 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -430,6 +574,8 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= gvisor.dev/gvisor v0.0.0-20240916094835-a174eb65023f h1:O2w2DymsOlM/nv2pLNWCMCYOldgBBMkD7H0/prN5W2k= gvisor.dev/gvisor v0.0.0-20240916094835-a174eb65023f/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.32.6 h1:UiBAMRzTP24Tz9UT1uhhmAv1auGTT9PT/npywSk9JrU= k8s.io/api v0.32.6/go.mod h1:+iFCyQN34v2rsL53iQEN9lYE03mFdgPvgSXvATIDteg= k8s.io/apimachinery v0.32.6 h1:odtEUjg7OT3132sBFsFn4Arj4Gd+BplYekmLQP8L3ak= diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh b/pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh index f5089144a65..8fc6ca0d078 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh +++ b/pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh @@ -20,6 +20,12 @@ fi # Install or update the guestagent binary install -m 755 "${LIMA_CIDATA_MNT}"/lima-guestagent "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent +strip_array() { + val="${1#[}" # remove leading [ + val="${val%]}" # remove trailing ] + printf '%s\n' "$val" | xargs # trim spaces +} + # Launch the guestagent service if [ -f /sbin/openrc-run ]; then # Convert .env to conf.d by wrapping values in double quotes. @@ -41,9 +47,13 @@ name="lima-guestagent" description="Forward ports to the lima-hostagent" command=${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent -command_args="daemon --debug=${LIMA_CIDATA_DEBUG} --vsock-port \"${LIMA_CIDATA_VSOCK_PORT}\" --virtio-port \"${LIMA_CIDATA_VIRTIO_PORT}\"" -command_background=true -pidfile="/run/lima-guestagent.pid" +command_args="daemon --debug=${LIMA_CIDATA_DEBUG} \ +--docker-sockets \"${LIMA_CIDATA_DOCKER_PORT_MONITOR_SOCKETS}" \ +--containerd-sockets \"{LIMA_CIDATA_CONTAINERD_PORT_MONITOR_SOCKETS}" \ +--kubernetes-configs \"{LIMA_CIDATA_KUBERNETES_SERVICE_WATCHER_CONFIGS}" \ +--vsock-port \"${LIMA_CIDATA_VSOCK_PORT}\" \ +--virtio-port \"${LIMA_CIDATA_VIRTIO_PORT}\"" +command_background=true pidfile="/run/lima-guestagent.pid" EOF chmod 755 /etc/init.d/lima-guestagent @@ -53,11 +63,40 @@ else # Remove legacy systemd service rm -f "${LIMA_CIDATA_HOME}/.config/systemd/user/lima-guestagent.service" + docker_args="" + docker_items="$(strip_array "${LIMA_CIDATA_DOCKER_PORT_MONITOR_SOCKETS}")" + if [ -n "$docker_items" ]; then + docker_args="--docker-sockets=${LIMA_CIDATA_DOCKER_PORT_MONITOR_SOCKETS}" + fi + + containerd_args="" + containerd_items="$(strip_array "${LIMA_CIDATA_CONTAINERD_PORT_MONITOR_SOCKETS}")" + if [ -n "$containerd_items" ]; then + containerd_args="--containerd-sockets=${LIMA_CIDATA_CONTAINERD_PORT_MONITOR_SOCKETS}" + fi + + kubernetes_args="" + kubernetes_items="$(strip_array "${LIMA_CIDATA_KUBERNETES_SERVICE_WATCHER_CONFIGS}")" + if [ -n "$kubernetes_items" ]; then + kubernetes_args="--kubernetes-configs=${LIMA_CIDATA_KUBERNETES_SERVICE_WATCHER_CONFIGS}" + fi + if [ "${LIMA_CIDATA_VSOCK_PORT}" != "0" ]; then - sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --vsock-port "${LIMA_CIDATA_VSOCK_PORT}" - elif [ "${LIMA_CIDATA_VIRTIO_PORT}" != "" ]; then - sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --virtio-port "${LIMA_CIDATA_VIRTIO_PORT}" + sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent" install-systemd \ + --vsock-port "${LIMA_CIDATA_VSOCK_PORT}" \ + ${docker_args:+${docker_args}} \ + ${containerd_args:+${containerd_args}} \ + ${kubernetes_args:+${kubernetes_args}} + elif [ -n "${LIMA_CIDATA_VIRTIO_PORT}" ]; then + sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent" install-systemd \ + --virtio-port "${LIMA_CIDATA_VIRTIO_PORT}" \ + ${docker_args:+${docker_args}} \ + ${containerd_args:+${containerd_args}} \ + ${kubernetes_args:+${kubernetes_args}} else - sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd + sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent" install-systemd \ + ${docker_args:+${docker_args}} \ + ${containerd_args:+${containerd_args}} \ + ${kubernetes_args:+${kubernetes_args}} fi fi diff --git a/pkg/cidata/cidata.TEMPLATE.d/lima.env b/pkg/cidata/cidata.TEMPLATE.d/lima.env index 89d2d34f108..36bf6fd8a02 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/lima.env +++ b/pkg/cidata/cidata.TEMPLATE.d/lima.env @@ -54,6 +54,9 @@ LIMA_CIDATA_SKIP_DEFAULT_DEPENDENCY_RESOLUTION= LIMA_CIDATA_VMTYPE={{ .VMType }} LIMA_CIDATA_VSOCK_PORT={{ .VSockPort }} LIMA_CIDATA_VIRTIO_PORT={{ .VirtioPort}} +LIMA_CIDATA_DOCKER_PORT_MONITOR_SOCKETS={{ .PortMonitor.Docker }} +LIMA_CIDATA_CONTAINERD_PORT_MONITOR_SOCKETS={{ .PortMonitor.Containerd }} +LIMA_CIDATA_KUBERNETES_SERVICE_WATCHER_CONFIGS={{ .PortMonitor.Kubernetes }} {{- if .Plain}} LIMA_CIDATA_PLAIN=1 {{- else}} diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go index 8439ca022f9..452cb94bd59 100644 --- a/pkg/cidata/cidata.go +++ b/pkg/cidata/cidata.go @@ -143,6 +143,11 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L Plain: *instConfig.Plain, TimeZone: *instConfig.TimeZone, Param: instConfig.Param, + PortMonitor: PortMonitor{ + Docker: strings.Join(instConfig.PortMonitors.Docker.Sockets, ","), + Containerd: strings.Join(instConfig.PortMonitors.Containerd.Sockets, ","), + Kubernetes: strings.Join(instConfig.PortMonitors.Kubernetes.Configs, ","), + }, } firstUsernetIndex := limayaml.FirstUsernetIndex(instConfig) diff --git a/pkg/cidata/template.go b/pkg/cidata/template.go index 80622def553..3b89b1ce7cf 100644 --- a/pkg/cidata/template.go +++ b/pkg/cidata/template.go @@ -65,6 +65,13 @@ type Disk struct { FSType string FSArgs []string } + +type PortMonitor struct { + Docker string + Containerd string + Kubernetes string +} + type TemplateArgs struct { Debug bool Name string // instance name @@ -105,6 +112,7 @@ type TemplateArgs struct { VirtioPort string Plain bool TimeZone string + PortMonitor PortMonitor } func ValidateTemplateArgs(args *TemplateArgs) error { diff --git a/pkg/guestagent/events/containerd_linux.go b/pkg/guestagent/events/containerd_linux.go new file mode 100644 index 00000000000..473802b8c64 --- /dev/null +++ b/pkg/guestagent/events/containerd_linux.go @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + "time" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/api/events" + containerdNamespace "github.com/containerd/containerd/namespaces" + "github.com/gogo/protobuf/proto" + "github.com/sirupsen/logrus" + + "github.com/lima-vm/lima/pkg/guestagent/api" +) + +const ( + portsKey = "nerdctl/ports" + namespaceKey = "nerdctl/namespace" + defaultSocketTimeout = 5 * time.Second +) + +type ContainerdEventMonitor struct { + clients []*containerd.Client + runningContainers map[string][]*api.IPPort +} + +func NewContainerdEventMonitor(socketPaths []string) (*ContainerdEventMonitor, error) { + const ( + maxRetries = 5 + retryDelay = 2 * time.Second + ) + + var clients []*containerd.Client + + for _, socket := range socketPaths { + logrus.Debugf("reading containerd socket %s", socket) + + info, err := os.Stat(socket) + if os.IsNotExist(err) { + logrus.Debugf("containerd socket %s does not exist", socket) + continue + } else if err != nil { + return nil, fmt.Errorf("error checking containerd socket %s: %w", socket, err) + } else if info.IsDir() { + logrus.Warnf("containerd socket %s is a directory, skipping", socket) + continue + } + + var cli *containerd.Client + var lastErr error + + for attempt := 1; attempt <= maxRetries; attempt++ { + cli, err = containerd.New(socket, containerd.WithDefaultNamespace(containerdNamespace.Default)) + if err != nil { + logrus.Warnf("attempt %d/%d: failed to create client for socket %s: %v", attempt, maxRetries, socket, err) + lastErr = err + } else { + ctx, cancel := context.WithTimeout(context.Background(), defaultSocketTimeout) + serving, serveErr := cli.IsServing(ctx) + cancel() + + if serveErr == nil && serving { + logrus.Infof("successfully connected to containerd daemon at %s (attempt %d)", socket, attempt) + clients = append(clients, cli) + break + } + + logrus.Warnf("attempt %d/%d: containerd client at %s not serving: %v", attempt, maxRetries, socket, serveErr) + lastErr = serveErr + cli.Close() + } + + select { + case <-time.After(retryDelay): + continue + case <-context.Background().Done(): + logrus.Warn("retry canceled, context done") + return nil, context.Canceled + } + } + + if cli == nil { + logrus.Errorf("failed to connect to containerd at %s after %d attempts: %v", socket, maxRetries, lastErr) + } + } + + if len(clients) == 0 { + logrus.Warn("no valid Containerd clients created from provided sockets") + return nil, nil + } + + return &ContainerdEventMonitor{ + clients: clients, + runningContainers: make(map[string][]*api.IPPort), + }, nil +} + +func (c *ContainerdEventMonitor) MonitorPorts(ctx context.Context, ch chan *api.Event) error { + errCh := make(chan error, len(c.clients)) + + for _, cli := range c.clients { + go func(client *containerd.Client) { + defer client.Close() + if err := c.monitorClient(ctx, client, ch); err != nil { + errCh <- fmt.Errorf("monitoring ports failed: %w", err) + } + }(cli) + } + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + return err + } +} + +func (c *ContainerdEventMonitor) monitorClient(ctx context.Context, cli *containerd.Client, ch chan *api.Event) error { + subscribeFilters := []string{ + `topic=="/tasks/start"`, + `topic=="/containers/update"`, + `topic=="/tasks/exit"`, + } + msgCh, errCh := cli.Subscribe(ctx, subscribeFilters...) + + if err := c.initializeRunningContainers(ctx, cli, ch); err != nil { + logrus.Errorf("failed to initialize existing containers published ports: %v", err) + } + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("context cancellation: %w", ctx.Err()) + + case err := <-errCh: + return fmt.Errorf("receiving container event failed: %w", err) + + case envelope := <-msgCh: + logrus.Debugf("received an event: %+v", envelope.Topic) + switch envelope.Topic { + case "/tasks/start": + startTask := &events.TaskStart{} + + err := proto.Unmarshal(envelope.Event.GetValue(), startTask) + if err != nil { + logrus.Errorf("failed to unmarshal container's start task: %v", err) + continue + } + + ipPorts, err := c.createIPPort(ctx, cli, envelope.Namespace, startTask.ContainerID) + if err != nil { + logrus.Errorf("creating IPPorts, for the following start task: %v failed: %s", startTask, err) + continue + } + + logrus.Debugf("received the following startTask: %v for: %v", startTask, ipPorts) + + if len(ipPorts) != 0 { + sendHostAgentEvent(false, ipPorts, ch) + c.runningContainers[startTask.ContainerID] = ipPorts + } + + case "/containers/update": + cuEvent := &events.ContainerUpdate{} + err := proto.Unmarshal(envelope.Event.GetValue(), cuEvent) + if err != nil { + logrus.Errorf("failed to unmarshal container update event: %v", err) + continue + } + + ipPorts, err := c.createIPPort(ctx, cli, envelope.Namespace, cuEvent.ID) + if err != nil { + logrus.Errorf("creating IPPorts, for the following exit task: %v failed: %s", cuEvent, err) + continue + } + + logrus.Debugf("received the following updateTask: %v for: %v", cuEvent, ipPorts) + + if exsitingipPorts, ok := c.runningContainers[cuEvent.ID]; ok { + if !ipPortsEqual(ipPorts, exsitingipPorts) { + // first remove the existing entry + sendHostAgentEvent(true, exsitingipPorts, ch) + // then update with the new entry + sendHostAgentEvent(false, ipPorts, ch) + c.runningContainers[cuEvent.ID] = ipPorts + } + } + case "/tasks/exit": + exitTask := &events.TaskExit{} + err := proto.Unmarshal(envelope.Event.GetValue(), exitTask) + if err != nil { + logrus.Errorf("failed to unmarshal container's exit task: %v", err) + continue + } + + ipPorts, err := c.createIPPort(ctx, cli, envelope.Namespace, exitTask.ContainerID) + if err != nil { + logrus.Errorf("creating IPPorts, for the following exit task: %v failed: %s", exitTask, err) + continue + } + + logrus.Debugf("received the following exitTask: %v for: %v", exitTask, ipPorts) + + if len(ipPorts) != 0 { + sendHostAgentEvent(true, ipPorts, ch) + delete(c.runningContainers, exitTask.ContainerID) + } + } + } + } +} + +func (c *ContainerdEventMonitor) initializeRunningContainers(ctx context.Context, cli *containerd.Client, ch chan *api.Event) error { + containers, err := cli.Containers(ctx) + if err != nil { + return err + } + + for _, container := range containers { + task, err := container.Task(ctx, nil) + if err != nil || task == nil { + logrus.Errorf("failed getting container %s task: %s", container.ID(), err) + continue + } + + status, err := task.Status(ctx) + if err != nil || status.Status != containerd.Running { + logrus.Errorf("failed getting container %s task status: %s", container.ID(), err) + continue + } + + labels, err := container.Labels(ctx) + if err != nil { + logrus.Errorf("failed getting container %s labels: %s", container.ID(), err) + continue + } + + ipPorts, err := c.createIPPort(ctx, cli, labels[namespaceKey], container.ID()) + if err != nil { + logrus.Errorf("creating IPPorts, while initializing containers the following: %v failed: %s", container.ID(), err) + } + + sendHostAgentEvent(false, ipPorts, ch) + c.runningContainers[container.ID()] = ipPorts + } + + return nil +} + +func (c *ContainerdEventMonitor) createIPPort(ctx context.Context, cli *containerd.Client, namespace, containerID string) ([]*api.IPPort, error) { + container, err := cli.ContainerService().Get( + containerdNamespace.WithNamespace(ctx, namespace), containerID) + if err != nil { + return nil, err + } + + var ipPorts []*api.IPPort + + containerPorts := container.Labels[portsKey] + if containerPorts == "" { + return ipPorts, nil + } + + var ports []Port + err = json.Unmarshal([]byte(containerPorts), &ports) + if err != nil { + return nil, err + } + + for _, port := range ports { + ipPorts = append(ipPorts, &api.IPPort{ + Protocol: strings.ToLower(port.Protocol), + Ip: port.HostIP, + Port: int32(port.HostPort), + }) + } + + return ipPorts, nil +} + +func ipPortsEqual(a, b []*api.IPPort) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i].Protocol != b[i].Protocol || a[i].Ip != b[i].Ip || a[i].Port != b[i].Port { + return false + } + } + return true +} + +// Port is representing nerdctl/ports entry in the +// event envelope's labels. +type Port struct { + HostPort int + ContainerPort int + Protocol string + HostIP string +} diff --git a/pkg/guestagent/events/containerd_stub.go b/pkg/guestagent/events/containerd_stub.go new file mode 100644 index 00000000000..1a67044a08b --- /dev/null +++ b/pkg/guestagent/events/containerd_stub.go @@ -0,0 +1,13 @@ +//go:build !linux +// +build !linux + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package events + +type ContainerdEventMonitor struct{} + +func NewContainerdEventMonitor(_ []string) (*ContainerdEventMonitor, error) { + panic("Containerd event monitoring is not implemented on this platform") +} diff --git a/pkg/guestagent/events/docker_linux.go b/pkg/guestagent/events/docker_linux.go new file mode 100644 index 00000000000..e6d43825286 --- /dev/null +++ b/pkg/guestagent/events/docker_linux.go @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/sirupsen/logrus" + + "github.com/lima-vm/lima/pkg/guestagent/api" +) + +type DockerEventMonitor struct { + dockerClients []*client.Client + // We maintain a record of all active containers because neither the stop nor + // die events provide the port mapping. As API consumers, it's our responsibility + // to track this information. The map uses the container ID as the key and + // stores all published ports associated with that container as the value. + runningContainers map[string][]*api.IPPort +} + +func NewDockerEventMonitor(dockerSocketPaths []string) (*DockerEventMonitor, error) { + const ( + maxRetries = 5 + retryDelay = 2 * time.Second + ) + + var dockerClients []*client.Client + + for _, socket := range dockerSocketPaths { + logrus.Debugf("attempting to read Docker socket %s", socket) + + info, statErr := os.Stat(socket) + if os.IsNotExist(statErr) { + logrus.Debugf("Docker socket %s does not exist", socket) + continue + } else if statErr != nil { + return nil, fmt.Errorf("error checking Docker socket %s: %w", socket, statErr) + } else if info.IsDir() { + logrus.Debugf("Docker socket %s is a directory, skipping", socket) + continue + } + + var cli *client.Client + var err error + + for attempt := 1; attempt <= maxRetries; attempt++ { + cli, err = client.NewClientWithOpts(client.WithHost(socket), client.WithAPIVersionNegotiation()) + if err == nil { + if _, err = cli.Ping(context.Background()); err == nil { + logrus.Infof("successfully connected to Docker daemon at %s", socket) + dockerClients = append(dockerClients, cli) + break + } + } + + logrus.Warnf("attempt %d/%d: failed to connect to Docker at %s: %s", attempt, maxRetries, socket, err) + + if attempt < maxRetries { + select { + case <-time.After(retryDelay): + case <-context.Background().Done(): + logrus.Warn("retry canceled, context done") + return nil, context.Canceled + } + } else { + logrus.Errorf("failed to connect to Docker at %s after %d attempts: %v", socket, maxRetries, err) + } + } + } + + if len(dockerClients) == 0 { + logrus.Warn("no valid Docker clients created from provided sockets, please check the socket paths") + return nil, nil + } + + return &DockerEventMonitor{ + dockerClients: dockerClients, + runningContainers: make(map[string][]*api.IPPort), + }, nil +} + +func (d *DockerEventMonitor) MonitorPorts(ctx context.Context, ch chan *api.Event) error { + errCh := make(chan error, len(d.dockerClients)) + for _, cli := range d.dockerClients { + go func(c *client.Client) { + if err := d.monitorClient(ctx, c, ch); err != nil { + errCh <- fmt.Errorf("monitoring ports failed: %w", err) + } + }(cli) + } + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + return err + } +} + +func (d *DockerEventMonitor) monitorClient(ctx context.Context, cli *client.Client, ch chan *api.Event) error { + defer cli.Close() + + if err := d.initializeRunningContainers(ctx, cli, ch); err != nil { + logrus.Errorf("failed to initialize existing docker container published ports: %s", err) + } + + msgCh, errCh := cli.Events(ctx, events.ListOptions{ + Filters: filters.NewArgs( + filters.Arg("type", string(types.ContainerObject)), + filters.Arg("event", string(events.ActionStart)), + filters.Arg("event", string(events.ActionStop)), + filters.Arg("event", string(events.ActionDie))), + }) + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("context cancellation: %w", ctx.Err()) + + case event := <-msgCh: + container, err := cli.ContainerInspect(ctx, event.ID) + if err != nil { + logrus.Errorf("inspecting container [%v] failed: %v", event.ID, err) + continue + } + portMap := container.NetworkSettings.NetworkSettingsBase.Ports + logrus.Debugf("received an event: {Status: %+v ContainerID: %+v Ports: %+v}", + event.Action, + event.ID, + portMap) + + switch event.Action { + case events.ActionStart: + if len(portMap) != 0 { + validatePortMapping(portMap) + ipPorts, err := convertToIPPort(portMap) + if err != nil { + logrus.Errorf("converting docker's portMapping: %+v to api.IPPort: %v failed: %s", portMap, ipPorts, err) + continue + } + logrus.Infof("successfully converted PortMapping:%+v to IPPorts: %+v", portMap, ipPorts) + d.runningContainers[event.ID] = ipPorts + sendHostAgentEvent(false, ipPorts, ch) + } + case events.ActionStop, events.ActionDie: + ipPorts, ok := d.runningContainers[event.ID] + if !ok { + continue + } + delete(d.runningContainers, event.ID) + sendHostAgentEvent(true, ipPorts, ch) + } + case err := <-errCh: + return fmt.Errorf("receiving container event failed: %w", err) + } + } +} + +func (d *DockerEventMonitor) initializeRunningContainers(ctx context.Context, cli *client.Client, ch chan *api.Event) error { + containers, err := cli.ContainerList(ctx, container.ListOptions{ + Filters: filters.NewArgs(filters.Arg("status", "running")), + }) + if err != nil { + return err + } + + for _, container := range containers { + if len(container.Ports) != 0 { + var ipPorts []*api.IPPort + for _, port := range container.Ports { + if port.IP == "" || port.PublicPort == 0 { + continue + } + + ipPorts = append(ipPorts, &api.IPPort{ + Protocol: strings.ToLower(port.Type), + Ip: port.IP, + Port: int32(port.PublicPort), + }) + } + sendHostAgentEvent(false, ipPorts, ch) + d.runningContainers[container.ID] = ipPorts + } + } + return nil +} + +func convertToIPPort(portMap nat.PortMap) ([]*api.IPPort, error) { + var ipPorts []*api.IPPort + for key, portBindings := range portMap { + for _, portBinding := range portBindings { + hostPort, err := strconv.ParseInt(portBinding.HostPort, 10, 32) + if err != nil { + return ipPorts, err + } + if portBinding.HostIP == "" || hostPort == 0 { + continue + } + + logrus.Debugf("converted the following PortMapping to IPPort, containerPort:%v HostPort:%v IP:%v Protocol:%v", + key.Port(), portBinding.HostPort, portBinding.HostIP, key.Proto()) + + ipPorts = append(ipPorts, &api.IPPort{ + Protocol: strings.ToLower(key.Proto()), + Ip: portBinding.HostIP, + Port: int32(hostPort), + }) + } + } + return ipPorts, nil +} + +// Removes entries in port mapping that do not hold any values +// for IP and Port e.g 9000/tcp:[]. +func validatePortMapping(portMap nat.PortMap) { + for k, v := range portMap { + if len(v) == 0 { + logrus.Debugf("removing entry: %v from the portmappings: %v", k, portMap) + delete(portMap, k) + } + } +} diff --git a/pkg/guestagent/events/docker_stub.go b/pkg/guestagent/events/docker_stub.go new file mode 100644 index 00000000000..164e892e8b0 --- /dev/null +++ b/pkg/guestagent/events/docker_stub.go @@ -0,0 +1,13 @@ +//go:build !linux +// +build !linux + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package events + +type DockerEventMonitor struct{} + +func NewDockerEventMonitor(_ []string) (*DockerEventMonitor, error) { + panic("Dockert event monitoring is not implemented on this platform") +} diff --git a/pkg/guestagent/events/eventutils.go b/pkg/guestagent/events/eventutils.go new file mode 100644 index 00000000000..2ce9da15c8b --- /dev/null +++ b/pkg/guestagent/events/eventutils.go @@ -0,0 +1,27 @@ +//go:build linux +// +build linux + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "github.com/sirupsen/logrus" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/lima-vm/lima/pkg/guestagent/api" +) + +func sendHostAgentEvent(remove bool, ipPorts []*api.IPPort, ch chan *api.Event) { + ev := &api.Event{ + Time: timestamppb.Now(), + } + if remove { + ev.LocalPortsRemoved = ipPorts + } else { + ev.LocalPortsAdded = ipPorts + } + ch <- ev + logrus.Infof("sent the following event to hostAgent: %+v", ev) +} diff --git a/pkg/guestagent/events/kubernetes_linux.go b/pkg/guestagent/events/kubernetes_linux.go new file mode 100644 index 00000000000..2caa554f604 --- /dev/null +++ b/pkg/guestagent/events/kubernetes_linux.go @@ -0,0 +1,290 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "context" + "errors" + "fmt" + "net/url" + "os" + "strings" + "time" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" + + "github.com/lima-vm/lima/pkg/guestagent/api" +) + +type event struct { + UID types.UID + namespace string + name string + portMapping map[int32]corev1.Protocol + deleted bool +} + +type KubeServiceWatcher struct { + kubeConfigPaths []string + kubeClient kubernetes.Interface + eventCh chan event + errorCh chan error +} + +func NewKubeServiceWatcher(cfgPaths []string) *KubeServiceWatcher { + return &KubeServiceWatcher{ + kubeConfigPaths: cfgPaths, + eventCh: make(chan event), + errorCh: make(chan error), + } +} + +func (k *KubeServiceWatcher) createAndVerifyClient(ctx context.Context) (bool, error) { + kubeClient, err := tryGetKubeClient(k.kubeConfigPaths) + if err != nil { + logrus.Tracef("failed to get kube client: %s", err) + return false, nil + } + + informerFactory := informers.NewSharedInformerFactory(kubeClient, time.Hour) + serviceInformer := informerFactory.Core().V1().Services().Informer() + + _, err = serviceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj any) { + logrus.Tracef("Service Informer: Add func called with: %+v", obj) + handleUpdate(nil, obj, k.eventCh) + }, + DeleteFunc: func(obj any) { + logrus.Tracef("Service Informer: Del func called with: %+v", obj) + handleUpdate(obj, nil, k.eventCh) + }, + UpdateFunc: func(oldObj, newObj any) { + logrus.Tracef("Service Informer: Update func called with old object %+v and new Object: %+v", oldObj, newObj) + handleUpdate(oldObj, newObj, k.eventCh) + }, + }) + if err != nil { + // this error can not be ignored and must be returned + return false, fmt.Errorf("error setting eventHandler: %w", err) + } + err = serviceInformer.SetWatchErrorHandler(func(_ *cache.Reflector, err error) { + k.errorCh <- fmt.Errorf("kubernetes: error watching service: %w", err) + }) + if err != nil { + // this error can not be ignored and must be returned + return false, fmt.Errorf("error setting errorHandler: %w", err) + } + informerFactory.WaitForCacheSync(ctx.Done()) + informerFactory.Start(ctx.Done()) + k.kubeClient = kubeClient + return true, nil +} + +func (k *KubeServiceWatcher) initializeServices(ctx context.Context) error { + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + services, err := k.kubeClient.CoreV1().Services(corev1.NamespaceAll).List(ctx, v1.ListOptions{}) + if err != nil { + logrus.Errorf("Listing services failed: %s", err) + switch { + default: + return err + case isTimeout(err): + case errors.Is(err, unix.ENETUNREACH): + case errors.Is(err, unix.ECONNREFUSED): + case isAPINotReady(err): + } + continue + } + + // List the initial set of services asynchronously, so that we don't have to + // worry about the channel blocking. + go func() { + for _, svc := range services.Items { + handleUpdate(nil, svc, k.eventCh) + } + }() + return nil + + case <-ctx.Done(): + return fmt.Errorf("context cancelled during initialization: %w", ctx.Err()) + } + } +} + +func (k *KubeServiceWatcher) MonitorServices(ctx context.Context, ch chan *api.Event) error { + if err := tryGetClient(ctx, k.createAndVerifyClient); err != nil { + return fmt.Errorf("failed getting kube client: %w", err) + } + + if err := k.initializeServices(ctx); err != nil { + return fmt.Errorf("failed initializing services: %w", err) + } + for { + select { + case <-ctx.Done(): + return fmt.Errorf("context cancellation: %w", ctx.Err()) + case err := <-k.errorCh: + logrus.Errorf("received an error from kube API: %s", err) + case event := <-k.eventCh: + logrus.Debugf("received an event from kube API: %+v", event) + ipPorts := createIPPortFromPortMapping(event.portMapping) + sendHostAgentEvent(event.deleted, ipPorts, ch) + } + } +} + +func createIPPortFromPortMapping(portMapping map[int32]corev1.Protocol) (ipPorts []*api.IPPort) { + for port, proto := range portMapping { + ipPorts = append(ipPorts, &api.IPPort{ + Ip: "0.0.0.0", + Protocol: strings.ToLower(string(proto)), + Port: port, + }) + } + return ipPorts +} + +func handleUpdate(oldObj, newObj any, eventCh chan<- event) { + deleted := make(map[int32]corev1.Protocol) + added := make(map[int32]corev1.Protocol) + oldSvc, _ := oldObj.(*corev1.Service) + newSvc, _ := newObj.(*corev1.Service) + namespace := "" + name := "" + + if oldSvc != nil { + namespace = oldSvc.Namespace + name = oldSvc.Name + + if oldSvc.Spec.Type == corev1.ServiceTypeNodePort { + for _, port := range oldSvc.Spec.Ports { + deleted[port.NodePort] = port.Protocol + } + } + + if oldSvc.Spec.Type == corev1.ServiceTypeLoadBalancer { + for _, port := range oldSvc.Spec.Ports { + deleted[port.Port] = port.Protocol + } + } + } + + if newSvc != nil { + namespace = newSvc.Namespace + name = newSvc.Name + + if newSvc.Spec.Type == corev1.ServiceTypeNodePort { + for _, port := range newSvc.Spec.Ports { + delete(deleted, port.NodePort) + added[port.NodePort] = port.Protocol + } + } + + if newSvc.Spec.Type == corev1.ServiceTypeLoadBalancer { + for _, port := range newSvc.Spec.Ports { + delete(deleted, port.Port) + added[port.Port] = port.Protocol + } + } + } + + if len(deleted) > 0 { + sendEvents(deleted, oldSvc, true, eventCh) + } + + if len(added) > 0 { + sendEvents(added, newSvc, false, eventCh) + } + + logrus.Debugf("kubernetes service update: %s/%s has -%d +%d service port", + namespace, name, len(deleted), len(added)) +} + +func sendEvents(mapping map[int32]corev1.Protocol, svc *corev1.Service, deleted bool, eventCh chan<- event) { + if svc != nil { + eventCh <- event{ + UID: svc.UID, + namespace: svc.Namespace, + name: svc.Name, + portMapping: mapping, + deleted: deleted, + } + } +} + +func tryGetKubeClient(candidateKubeConfigs []string) (kubernetes.Interface, error) { + for _, kubeconfig := range candidateKubeConfigs { + _, err := os.Stat(kubeconfig) + if err != nil { + if os.IsNotExist(err) { + continue + } + + return nil, fmt.Errorf("stat kubeconfig %s failed: %w", kubeconfig, err) + } + + restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return nil, fmt.Errorf("build kubeconfig from %s failed: %w", kubeconfig, err) + } + u, err := url.Parse(restConfig.Host) + if err != nil { + return nil, fmt.Errorf("parse kubeconfig host %s failed: %w", restConfig.Host, err) + } + if u.Hostname() != "127.0.0.1" { // might need to support IPv6 + // ensures the kubeconfig points to local k8s + continue + } + + kubeClient, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return nil, err + } + + return kubeClient, nil + } + + return nil, errors.New("no valid kubeconfig found") +} + +func isTimeout(err error) bool { + type timeout interface { + Timeout() bool + } + + var timeoutError timeout + + if !errors.As(err, &timeoutError) { + return timeoutError != nil && timeoutError.Timeout() + } + + return false +} + +// This is a k3s error that is received over +// the HTTP, Also, it is worth noting that this +// error is wrapped. This is why we are not testing +// against the real error object using errors.Is(). +func isAPINotReady(err error) bool { + return strings.Contains(err.Error(), "apiserver not ready") || strings.Contains(err.Error(), "starting") +} + +func tryGetClient(ctx context.Context, tryConnect func(context.Context) (bool, error)) error { + const retryInterval = 10 * time.Second + const pollImmediately = true + return wait.PollUntilContextCancel(ctx, retryInterval, pollImmediately, tryConnect) +} diff --git a/pkg/guestagent/events/kubernetes_stub.go b/pkg/guestagent/events/kubernetes_stub.go new file mode 100644 index 00000000000..d7c0d4ac990 --- /dev/null +++ b/pkg/guestagent/events/kubernetes_stub.go @@ -0,0 +1,13 @@ +//go:build !linux +// +build !linux + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package events + +type KubeServiceWatcher struct{} + +func NewKubeServiceWatcher() *KubeServiceWatcher { + panic("NewKubeServiceWatcher is not implemented on this platform") +} diff --git a/pkg/guestagent/guestagent_linux.go b/pkg/guestagent/guestagent_linux.go index 0bcd49efa49..bfb136e85e4 100644 --- a/pkg/guestagent/guestagent_linux.go +++ b/pkg/guestagent/guestagent_linux.go @@ -18,16 +18,38 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/lima-vm/lima/pkg/guestagent/api" + "github.com/lima-vm/lima/pkg/guestagent/events" "github.com/lima-vm/lima/pkg/guestagent/iptables" - "github.com/lima-vm/lima/pkg/guestagent/kubernetesservice" "github.com/lima-vm/lima/pkg/guestagent/procnettcp" "github.com/lima-vm/lima/pkg/guestagent/timesync" ) -func New(newTicker func() (<-chan time.Time, func()), iptablesIdle time.Duration) (Agent, error) { +type Config struct { + Ticker func() (<-chan time.Time, func()) + IptablesIdle time.Duration + DockerSockets []string + ContainerdSockets []string + KubernetesConfigs []string +} + +func New(cfg *Config) (Agent, error) { + dockerEventMonitor, err := events.NewDockerEventMonitor(cfg.DockerSockets) + if err != nil { + return nil, err + } + + containerdEventMonitor, err := events.NewContainerdEventMonitor(cfg.ContainerdSockets) + if err != nil { + return nil, err + } + + kubeServiceWatcher := events.NewKubeServiceWatcher(cfg.KubernetesConfigs) + a := &agent{ - newTicker: newTicker, - kubernetesServiceWatcher: kubernetesservice.NewServiceWatcher(), + newTicker: cfg.Ticker, + dockerEventMonitor: dockerEventMonitor, + containerdEventMonitor: containerdEventMonitor, + kubeServiceWatcher: kubeServiceWatcher, } auditClient, err := libaudit.NewMulticastAuditClient(nil) @@ -68,7 +90,7 @@ func New(newTicker func() (<-chan time.Time, func()), iptablesIdle time.Duration } } - go a.setWorthCheckingIPTablesRoutine(auditClient, iptablesIdle) + go a.setWorthCheckingIPTablesRoutine(auditClient, cfg.IptablesIdle) } else { a.worthCheckingIPTables = true } @@ -85,7 +107,6 @@ func startGuestAgentRoutines(a *agent, supportsAuditing bool) *agent { if !supportsAuditing { a.worthCheckingIPTables = true } - go a.kubernetesServiceWatcher.Start() go a.fixSystemTimeSkew() return a @@ -97,11 +118,13 @@ type agent struct { // reload /proc/net/tcp. newTicker func() (<-chan time.Time, func()) - worthCheckingIPTables bool - worthCheckingIPTablesMu sync.RWMutex - latestIPTables []iptables.Entry - latestIPTablesMu sync.RWMutex - kubernetesServiceWatcher *kubernetesservice.ServiceWatcher + worthCheckingIPTables bool + worthCheckingIPTablesMu sync.RWMutex + latestIPTables []iptables.Entry + latestIPTablesMu sync.RWMutex + dockerEventMonitor *events.DockerEventMonitor + containerdEventMonitor *events.ContainerdEventMonitor + kubeServiceWatcher *events.KubeServiceWatcher } // setWorthCheckingIPTablesRoutine sets worthCheckingIPTables to be true @@ -197,6 +220,25 @@ func isEventEmpty(ev *api.Event) bool { func (a *agent) Events(ctx context.Context, ch chan *api.Event) { defer close(ch) + + errorCh := make(chan error) + go func() { + if err := a.kubeServiceWatcher.MonitorServices(ctx, ch); err != nil { + errorCh <- err + } + }() + + go func() { + if err := a.containerdEventMonitor.MonitorPorts(ctx, ch); err != nil { + errorCh <- err + } + }() + go func() { + if err := a.dockerEventMonitor.MonitorPorts(ctx, ch); err != nil { + errorCh <- err + } + }() + tickerCh, tickerClose := a.newTicker() defer tickerClose() var st eventState @@ -209,6 +251,8 @@ func (a *agent) Events(ctx context.Context, ch chan *api.Event) { select { case <-ctx.Done(): return + case err := <-errorCh: + logrus.Errorf("event monitoring failed: %s", err) case _, ok := <-tickerCh: if !ok { return @@ -290,25 +334,6 @@ func (a *agent) LocalPorts(_ context.Context) ([]*api.IPPort, error) { } } - kubernetesEntries := a.kubernetesServiceWatcher.GetPorts() - for _, entry := range kubernetesEntries { - found := false - for _, re := range res { - if re.Port == int32(entry.Port) { - found = true - } - } - - if !found { - res = append(res, - &api.IPPort{ - Ip: entry.IP.String(), - Port: int32(entry.Port), - Protocol: string(entry.Protocol), - }) - } - } - return res, nil } diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go index 629cbcc3f97..6df02e1331a 100644 --- a/pkg/limayaml/defaults.go +++ b/pkg/limayaml/defaults.go @@ -593,6 +593,55 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { // After defaults processing the singular HostPort and GuestPort values should not be used again. } + // Manage PortMonitors sockets for Docker + y.PortMonitors.Docker.Sockets = slices.Concat( + o.PortMonitors.Docker.Sockets, + y.PortMonitors.Docker.Sockets, + d.PortMonitors.Docker.Sockets) + + for i := range y.PortMonitors.Docker.Sockets { + if out, err := executeGuestTemplate(y.PortMonitors.Docker.Sockets[i], instDir, y.User, y.Param); err == nil { + y.PortMonitors.Docker.Sockets[i] = out.String() + } else { + logrus.WithError(err).Warnf("Couldn't process Docker socket %q as a template", y.PortMonitors.Docker.Sockets[i]) + } + } + + // Manage PortMonitors sockets for Containerd + y.PortMonitors.Containerd.Sockets = slices.Concat( + o.PortMonitors.Containerd.Sockets, + y.PortMonitors.Containerd.Sockets, + d.PortMonitors.Containerd.Sockets) + + for i := range y.PortMonitors.Containerd.Sockets { + if out, err := executeGuestTemplate(y.PortMonitors.Containerd.Sockets[i], instDir, y.User, y.Param); err == nil { + y.PortMonitors.Containerd.Sockets[i] = out.String() + } else { + logrus.WithError(err).Warnf("Couldn't process Containerd socket %q as a template", y.PortMonitors.Docker.Sockets[i]) + } + } + + if y.Containerd.System != nil && *y.Containerd.System { + if out, err := executeGuestTemplate("/run/containerd/containerd.sock", instDir, y.User, y.Param); err == nil { + y.PortMonitors.Containerd.Sockets = unique(append(y.PortMonitors.Containerd.Sockets, out.String())) + } else { + logrus.WithError(err).Warnf("Couldn't process Containerd system socket") + } + } + if y.Containerd.User != nil && *y.Containerd.User { + if out, err := executeGuestTemplate("/run/user/{{.UID}}/containerd/containerd.sock", instDir, y.User, y.Param); err == nil { + y.PortMonitors.Containerd.Sockets = unique(append(y.PortMonitors.Containerd.Sockets, out.String())) + } else { + logrus.WithError(err).Warnf("Couldn't process Containerd user socket") + } + } + + // Manage PortMonitors config for kubernetes + y.PortMonitors.Kubernetes.Configs = unique(slices.Concat( + o.PortMonitors.Kubernetes.Configs, + y.PortMonitors.Kubernetes.Configs, + d.PortMonitors.Kubernetes.Configs)) + y.CopyToHost = slices.Concat(o.CopyToHost, y.CopyToHost, d.CopyToHost) for i := range y.CopyToHost { FillCopyToHostDefaults(&y.CopyToHost[i], instDir, y.User, y.Param) diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go index 70f6810c855..616e800a8fd 100644 --- a/pkg/limayaml/defaults_test.go +++ b/pkg/limayaml/defaults_test.go @@ -143,6 +143,9 @@ func TestFillDefault(t *testing.T) { // All these slices and maps are empty in "builtin". Add minimal entries here to see that // their values are retained and defaults for their fields are applied correctly. + + t.Log("Builtin defaults are set when y is (mostly) empty") + y = LimaYAML{ HostResolver: HostResolver{ Hosts: map[string]string{ @@ -279,9 +282,6 @@ func TestFillDefault(t *testing.T) { defaultPortForward, defaultPortForward, } - expect.CopyToHost = []CopyToHost{ - {}, - } // Setting GuestPort and HostPort for DeepEqual(), but they are not supposed to be used // after FillDefault() has been called and the ...PortRange fields have been set. @@ -297,6 +297,14 @@ func TestFillDefault(t *testing.T) { expect.PortForwards[3].GuestSocket = fmt.Sprintf("%s | %s | %s | %s", user.HomeDir, user.Uid, user.Username, y.Param["ONE"]) expect.PortForwards[3].HostSocket = fmt.Sprintf("%s | %s | %s | %s | %s | %s", hostHome, instDir, instName, currentUser.Uid, currentUser.Username, y.Param["ONE"]) + expect.PortMonitors.Containerd.Sockets = []string{ + fmt.Sprintf("/run/user/%s/containerd/containerd.sock", user.Uid), + } + + expect.CopyToHost = []CopyToHost{ + {}, + } + expect.CopyToHost[0].GuestFile = fmt.Sprintf("%s | %s | %s | %s", user.HomeDir, user.Uid, user.Username, y.Param["ONE"]) expect.CopyToHost[0].HostFile = fmt.Sprintf("%s | %s | %s | %s | %s | %s", hostHome, instDir, instName, currentUser.Uid, currentUser.Username, y.Param["ONE"]) @@ -334,7 +342,11 @@ func TestFillDefault(t *testing.T) { // Choose values that are different from the "builtin" defaults // Calling filepath.Abs() to add a drive letter on Windows + + t.Log("User-provided defaults should override builtin defaults") + varLog, _ := filepath.Abs("/var/log") + defaultUID := 8080 d = LimaYAML{ VMType: ptr.Of("vz"), OS: ptr.Of("unknown"), @@ -462,7 +474,7 @@ func TestFillDefault(t *testing.T) { Comment: ptr.Of("Foo Bar"), Home: ptr.Of("/tmp"), Shell: ptr.Of("/bin/tcsh"), - UID: ptr.Of(uint32(8080)), + UID: ptr.Of(uint32(defaultUID)), }, } @@ -508,6 +520,9 @@ func TestFillDefault(t *testing.T) { } } expect.Plain = ptr.Of(false) + expect.PortMonitors.Containerd.Sockets = []string{ + "/run/containerd/containerd.sock", + } y = LimaYAML{} FillDefault(&y, &d, &LimaYAML{}, filePath, false) @@ -518,6 +533,8 @@ func TestFillDefault(t *testing.T) { // ------------------------------------------------------------------------------------ // User-provided defaults should not override user-provided config values + t.Log("User-provided defaults should not override user-provided config values") + y = filledDefaults y.DNS = []net.IP{net.ParseIP("8.8.8.8")} y.AdditionalDisks = []Disk{{Name: "overridden"}} @@ -555,6 +572,9 @@ func TestFillDefault(t *testing.T) { // ------------------------------------------------------------------------------------ // User-provided overrides should override user-provided config settings + t.Log("User-provided overrides should override user-provided config settings") + + overrideUID := 1122 o = LimaYAML{ VMType: ptr.Of("qemu"), OS: ptr.Of(LINUX), @@ -694,7 +714,7 @@ func TestFillDefault(t *testing.T) { Comment: ptr.Of("foo bar baz"), Home: ptr.Of("/override"), Shell: ptr.Of("/bin/sh"), - UID: ptr.Of(uint32(1122)), + UID: ptr.Of(uint32(overrideUID)), }, } @@ -705,6 +725,10 @@ func TestFillDefault(t *testing.T) { expect.Provision = slices.Concat(o.Provision, y.Provision, dExpect.Provision) expect.Probes = slices.Concat(o.Probes, y.Probes, dExpect.Probes) expect.PortForwards = slices.Concat(o.PortForwards, y.PortForwards, dExpect.PortForwards) + expect.PortMonitors.Containerd.Sockets = []string{ + fmt.Sprintf("/run/user/%s/containerd/containerd.sock", user.Uid), + "/run/containerd/containerd.sock", + } expect.CopyToHost = slices.Concat(o.CopyToHost, y.CopyToHost, dExpect.CopyToHost) expect.Containerd.Archives = slices.Concat(o.Containerd.Archives, y.Containerd.Archives, dExpect.Containerd.Archives) expect.Containerd.Archives[3].Arch = *expect.Arch diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go index d72e5b602c8..922c827a6d5 100644 --- a/pkg/limayaml/limayaml.go +++ b/pkg/limayaml/limayaml.go @@ -36,6 +36,7 @@ type LimaYAML struct { GuestInstallPrefix *string `yaml:"guestInstallPrefix,omitempty" json:"guestInstallPrefix,omitempty" jsonschema:"nullable"` Probes []Probe `yaml:"probes,omitempty" json:"probes,omitempty"` PortForwards []PortForward `yaml:"portForwards,omitempty" json:"portForwards,omitempty"` + PortMonitors PortMonitor `yaml:"portMonitors,omitempty" json:"portMonitors,omitempty" jsonschema:"nullable"` CopyToHost []CopyToHost `yaml:"copyToHost,omitempty" json:"copyToHost,omitempty"` Message string `yaml:"message,omitempty" json:"message,omitempty"` Networks []Network `yaml:"networks,omitempty" json:"networks,omitempty" jsonschema:"nullable"` @@ -317,3 +318,18 @@ type CACertificates struct { Files []string `yaml:"files,omitempty" json:"files,omitempty" jsonschema:"nullable"` Certs []string `yaml:"certs,omitempty" json:"certs,omitempty" jsonschema:"nullable"` } + +// Engine is the name of the container engine, e.g. "docker", "containerd". +type Engine struct { + Sockets []string `yaml:"sockets,omitempty" json:"sockets,omitempty"` +} + +type Kubernetes struct { + Configs []string `yaml:"configs,omitempty" json:"configs,omitempty"` // Paths to Kubernetes config files, e.g. "/etc/rancher/k3s/k3s.yaml" +} + +type PortMonitor struct { + Docker Engine `yaml:"docker,omitempty" json:"docker,omitempty"` + Containerd Engine `yaml:"containerd,omitempty" json:"containerd,omitempty"` + Kubernetes Kubernetes `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty"` +} diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go index 9b959f25d1a..e8bd4300e6a 100644 --- a/pkg/limayaml/validate.go +++ b/pkg/limayaml/validate.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net" + "net/url" "os" "path" "path/filepath" @@ -375,6 +376,19 @@ func Validate(y *LimaYAML, warn bool) error { // Not validating that the various GuestPortRanges and HostPortRanges are not overlapping. Rules will be // processed sequentially and the first matching rule for a guest port determines forwarding behavior. } + + for _, socket := range y.PortMonitors.Containerd.Sockets { + if err := validateSocket("containerd", socket); err != nil { + return err + } + } + + for _, socket := range y.PortMonitors.Docker.Sockets { + if err := validateSocket("docker", socket); err != nil { + return err + } + } + for i, rule := range y.CopyToHost { field := fmt.Sprintf("CopyToHost[%d]", i) if rule.GuestFile != "" { @@ -647,3 +661,39 @@ func ValidateAgainstLatestConfig(yNew, yLatest []byte) error { return nil } + +func validateSocket(engine, socket string) error { + if socket == "" { + return fmt.Errorf("%s socket path must not be empty", engine) + } + + if strings.Contains(socket, "://") { + u, err := url.Parse(socket) + if err != nil { + return fmt.Errorf("%s socket path %q is not a valid URL: %w", engine, socket, err) + } + switch u.Scheme { + case "unix", "file": + if u.Path == "" { + return fmt.Errorf("%s socket path %q is not a valid URL: missing path", engine, socket) + } + return validateUnixSocket(engine, u.Path) + case "tcp": + if u.Host == "" { + return fmt.Errorf("%s socket path %q is not a valid URL: missing host", engine, socket) + } + return nil + default: + return fmt.Errorf("%s socket path %q is not a valid URL: unsupported scheme %q", engine, socket, u.Scheme) + } + } + + return validateUnixSocket(engine, socket) +} + +func validateUnixSocket(engine, path string) error { + if !filepath.IsAbs(path) { + return fmt.Errorf("%s socket path must be absolute, got %s", engine, path) + } + return nil +} diff --git a/pkg/portfwd/listener.go b/pkg/portfwd/listener.go index e4b250dd882..2d5980ab34c 100644 --- a/pkg/portfwd/listener.go +++ b/pkg/portfwd/listener.go @@ -71,13 +71,13 @@ func (p *ClosableListeners) Remove(_ context.Context, protocol, hostAddress, gue func (p *ClosableListeners) forwardTCP(ctx context.Context, client *guestagentclient.GuestAgentClient, hostAddress, guestAddress string) { key := key("tcp", hostAddress, guestAddress) - p.listenersRW.Lock() _, ok := p.listeners[key] if ok { p.listenersRW.Unlock() return } + defer p.Remove(ctx, "tcp", hostAddress, guestAddress) tcpLis, err := Listen(ctx, p.listenConfig, hostAddress) if err != nil { logrus.Errorf("failed to listen to TCP connection: %v", err) diff --git a/templates/docker-rootful.yaml b/templates/docker-rootful.yaml index 037b6b839f4..d7ab60edf25 100644 --- a/templates/docker-rootful.yaml +++ b/templates/docker-rootful.yaml @@ -68,3 +68,7 @@ message: | docker context use lima-{{.Name}} docker run hello-world ------ +portMonitors: + docker: + sockets: + - "/var/run/docker.sock" diff --git a/templates/docker.yaml b/templates/docker.yaml index da1148095c4..296d3bf67d3 100644 --- a/templates/docker.yaml +++ b/templates/docker.yaml @@ -70,3 +70,7 @@ message: | docker context use lima-{{.Name}} docker run hello-world ------ +portMonitors: + docker: + sockets: + - "/run/user/{{.UID}}/docker.sock" diff --git a/templates/k3s.yaml b/templates/k3s.yaml index 9b84d591114..98d8a38783d 100644 --- a/templates/k3s.yaml +++ b/templates/k3s.yaml @@ -49,3 +49,7 @@ message: | export KUBECONFIG="{{.Dir}}/copied-from-guest/kubeconfig.yaml" kubectl ... ------ +portMonitors: + kubernetes: + configs: + - "/etc/rancher/k3s/k3s.yaml" diff --git a/templates/k8s.yaml b/templates/k8s.yaml index 3bcc6c9afc6..9e0309144cf 100644 --- a/templates/k8s.yaml +++ b/templates/k8s.yaml @@ -176,3 +176,7 @@ message: | export KUBECONFIG="{{.Dir}}/copied-from-guest/kubeconfig.yaml" kubectl ... ------ +portMonitors: + kubernetes: + configs: + - "/etc/kubernetes/admin.conf"