From 022673f37620bffd53df26bd88b0d443778a315a Mon Sep 17 00:00:00 2001 From: Matt Long Date: Fri, 23 Jan 2026 11:58:38 +0100 Subject: [PATCH 1/5] Update to bazel and add tests --- .bazelignore | 14 + .bazelrc | 21 + .bazelversion | 1 + .gitignore | 25 + BUILD.bazel | 81 ++ MODULE.bazel | 38 + cmd/BUILD.bazel | 13 + .../omnicate-ksniff/pkg/cmd/index.html | 98 ++ .../pkg/cmd/sniff.go.gcov.html | 563 +++++++++ .../omnicate-ksniff/utils/index.html | 98 ++ .../omnicate-ksniff/utils/utils.go.gcov.html | 128 ++ coverage_html/amber.png | Bin 0 -> 141 bytes coverage_html/cmd_line | 1 + coverage_html/emerald.png | Bin 0 -> 141 bytes coverage_html/gcov.css | 1125 +++++++++++++++++ coverage_html/glass.png | Bin 0 -> 167 bytes coverage_html/index-sort-f.html | 116 ++ coverage_html/index-sort-l.html | 116 ++ coverage_html/index.html | 116 ++ coverage_html/ruby.png | Bin 0 -> 141 bytes coverage_html/runtime/containerd.go.gcov.html | 145 +++ coverage_html/runtime/crio.go.gcov.html | 169 +++ coverage_html/runtime/docker.go.gcov.html | 135 ++ coverage_html/runtime/index-sort-f.html | 125 ++ coverage_html/runtime/index-sort-l.html | 125 ++ coverage_html/runtime/index.html | 125 ++ coverage_html/runtime/runtime.go.gcov.html | 109 ++ coverage_html/snow.png | Bin 0 -> 141 bytes coverage_html/updown.png | Bin 0 -> 117 bytes go.mod | 71 +- kube/BUILD.bazel | 26 + kube/kubernetes_api_service.go | 4 +- kube/ops.go | 2 +- pkg/cmd/BUILD.bazel | 46 + pkg/cmd/sniff.go | 28 +- pkg/cmd/sniff_test.go | 454 ++++++- pkg/config/BUILD.bazel | 9 + pkg/service/sniffer/BUILD.bazel | 20 + pkg/service/sniffer/runtime/BUILD.bazel | 29 + pkg/service/sniffer/runtime/runtime_test.go | 263 ++++ tcpdump.BUILD | 49 + utils/BUILD.bazel | 15 + 42 files changed, 4491 insertions(+), 12 deletions(-) create mode 100644 .bazelignore create mode 100644 .bazelrc create mode 100644 .bazelversion create mode 100644 .gitignore create mode 100644 BUILD.bazel create mode 100644 MODULE.bazel create mode 100644 cmd/BUILD.bazel create mode 100644 coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/index.html create mode 100644 coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go.gcov.html create mode 100644 coverage_html/Users/mtl/Development/omnicate-ksniff/utils/index.html create mode 100644 coverage_html/Users/mtl/Development/omnicate-ksniff/utils/utils.go.gcov.html create mode 100644 coverage_html/amber.png create mode 100644 coverage_html/cmd_line create mode 100644 coverage_html/emerald.png create mode 100644 coverage_html/gcov.css create mode 100644 coverage_html/glass.png create mode 100644 coverage_html/index-sort-f.html create mode 100644 coverage_html/index-sort-l.html create mode 100644 coverage_html/index.html create mode 100644 coverage_html/ruby.png create mode 100644 coverage_html/runtime/containerd.go.gcov.html create mode 100644 coverage_html/runtime/crio.go.gcov.html create mode 100644 coverage_html/runtime/docker.go.gcov.html create mode 100644 coverage_html/runtime/index-sort-f.html create mode 100644 coverage_html/runtime/index-sort-l.html create mode 100644 coverage_html/runtime/index.html create mode 100644 coverage_html/runtime/runtime.go.gcov.html create mode 100644 coverage_html/snow.png create mode 100644 coverage_html/updown.png create mode 100644 kube/BUILD.bazel create mode 100644 pkg/cmd/BUILD.bazel create mode 100644 pkg/config/BUILD.bazel create mode 100644 pkg/service/sniffer/BUILD.bazel create mode 100644 pkg/service/sniffer/runtime/BUILD.bazel create mode 100644 tcpdump.BUILD create mode 100644 utils/BUILD.bazel diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000..2f79d01 --- /dev/null +++ b/.bazelignore @@ -0,0 +1,14 @@ +# Ignore bazel symlink directories +bazel-bin +bazel-out +bazel-testlogs +bazel-ksniff + +# Ignore existing build artifacts +kubectl-sniff +kubectl-sniff-windows +kubectl-sniff-darwin +kubectl-sniff-darwin-arm64 +static-tcpdump +ksniff.zip +tcpdump-* diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..996dd9c --- /dev/null +++ b/.bazelrc @@ -0,0 +1,21 @@ +# Enable Bzlmod +common --enable_bzlmod + +# Common build options +build --incompatible_enable_cc_toolchain_resolution +build --enable_platform_specific_config + +# Go specific options +build --@rules_go//go/config:pure + +# Platform-specific configurations +build:linux --platforms=@rules_go//go/toolchain:linux_amd64 +build:darwin --platforms=@rules_go//go/toolchain:darwin_amd64 +build:darwin_arm64 --platforms=@rules_go//go/toolchain:darwin_arm64 +build:windows --platforms=@rules_go//go/toolchain:windows_amd64 + +# Test output +test --test_output=errors + +# Performance +build --jobs=auto diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..e7fdef7 --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +8.4.2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54162a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*~ +.#* + +# OS X Desktop Service Store +.DS_Store + +.generated + +# Build and test process +.coverage + +# bzlmod lockfile which is useless: +# "Importantly, it only includes dependencies that are included in the current invocation of the build" +/MODULE.bazel.lock + +/bazel-* + +# Local bazel overrides +.bazelrc-local + +# Go +go.mod +go.sum +!/go.mod +!/go.sum diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..0b987a0 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,81 @@ +load("@gazelle//:def.bzl", "gazelle") +load("@rules_go//go:def.bzl", "go_binary") + +# gazelle:prefix ksniff +gazelle(name = "gazelle") + +# Linux binary +go_binary( + name = "kubectl-sniff-linux", + embed = ["//cmd:kubectl-sniff_lib"], + goarch = "amd64", + goos = "linux", + pure = "on", + visibility = ["//visibility:public"], +) + +# Darwin (macOS) binary - amd64 +go_binary( + name = "kubectl-sniff-darwin", + embed = ["//cmd:kubectl-sniff_lib"], + goarch = "amd64", + goos = "darwin", + pure = "on", + visibility = ["//visibility:public"], +) + +# Darwin (macOS) binary - arm64 +go_binary( + name = "kubectl-sniff-darwin-arm64", + embed = ["//cmd:kubectl-sniff_lib"], + goarch = "arm64", + goos = "darwin", + pure = "on", + visibility = ["//visibility:public"], +) + +# Windows binary +go_binary( + name = "kubectl-sniff-windows", + embed = ["//cmd:kubectl-sniff_lib"], + goarch = "amd64", + goos = "windows", + pure = "on", + visibility = ["//visibility:public"], +) + +# Default binary for current platform +go_binary( + name = "kubectl-sniff", + embed = ["//cmd:kubectl-sniff_lib"], + pure = "on", + visibility = ["//visibility:public"], +) + +# Static tcpdump binary +# This target builds a statically-linked tcpdump binary from source. +# On Linux: Builds a fully static tcpdump binary (equivalent to Makefile's static-tcpdump target) +# On macOS/other: Creates a placeholder script explaining that this must be built on Linux +# +# Usage: +# bazel build //:static-tcpdump +# +# The binary will be available at: +# bazel-bin/external/+_repo_rules+tcpdump/tcpdump_bin +alias( + name = "static-tcpdump", + actual = "@tcpdump//:tcpdump_bin", + visibility = ["//visibility:public"], +) + +# Filegroup for all binaries +filegroup( + name = "all-binaries", + srcs = [ + ":kubectl-sniff-darwin", + ":kubectl-sniff-darwin-arm64", + ":kubectl-sniff-linux", + ":kubectl-sniff-windows", + ], + visibility = ["//visibility:public"], +) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..033a230 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,38 @@ +module( + name = "ksniff", + version = "0.0.0", +) + +bazel_dep(name = "rules_go", version = "0.59.0") +bazel_dep(name = "gazelle", version = "0.47.0") +bazel_dep(name = "platforms", version = "1.0.0") + +go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") +go_sdk.download(version = "1.23.4") + +go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") +go_deps.from_file(go_mod = "//:go.mod") + +# Use all Go dependencies +use_repo(go_deps, "com_github_mitchellh_go_homedir") +use_repo(go_deps, "com_github_pkg_errors") +use_repo(go_deps, "com_github_sirupsen_logrus") +use_repo(go_deps, "com_github_spf13_cobra") +use_repo(go_deps, "com_github_spf13_pflag") +use_repo(go_deps, "com_github_spf13_viper") +use_repo(go_deps, "com_github_stretchr_testify") +use_repo(go_deps, "io_k8s_api") +use_repo(go_deps, "io_k8s_apimachinery") +use_repo(go_deps, "io_k8s_cli_runtime") +use_repo(go_deps, "io_k8s_client_go") + +# HTTP archive for tcpdump +http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "tcpdump", + build_file = "//:tcpdump.BUILD", + sha256 = "798b3536a29832ce0cbb07fafb1ce5097c95e308a6f592d14052e1ef1505fe79", + strip_prefix = "tcpdump-4.9.2", + urls = ["https://www.tcpdump.org/release/tcpdump-4.9.2.tar.gz"], +) diff --git a/cmd/BUILD.bazel b/cmd/BUILD.bazel new file mode 100644 index 0000000..c9ad7cb --- /dev/null +++ b/cmd/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "kubectl-sniff_lib", + srcs = ["kubectl-sniff.go"], + importpath = "ksniff/cmd", + visibility = ["//visibility:public"], + deps = [ + "//pkg/cmd", + "@com_github_spf13_pflag//:pflag", + "@io_k8s_cli_runtime//pkg/genericclioptions", + ], +) diff --git a/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/index.html b/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/index.html new file mode 100644 index 0000000..de097d2 --- /dev/null +++ b/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/index.html @@ -0,0 +1,98 @@ + + + + + + + LCOV - _coverage_report.dat - Users/mtl/Development/omnicate-ksniff/pkg/cmd + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - /Users/mtl/Development/omnicate-ksniff/pkg/cmdCoverageTotalHit
Test:_coverage_report.datLines:52.4 %349183
Test Date:2026-01-23 11:51:52Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
sniff.go +
52.4%52.4%
+
52.4 %349183
Note: 'Function Coverage' columns elided as function owner is not identified.
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go.gcov.html b/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go.gcov.html new file mode 100644 index 0000000..52058a8 --- /dev/null +++ b/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go.gcov.html @@ -0,0 +1,563 @@ + + + + + + + LCOV - _coverage_report.dat - Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - /Users/mtl/Development/omnicate-ksniff/pkg/cmd - sniff.goCoverageTotalHit
Test:_coverage_report.datLines:52.4 %349183
Test Date:2026-01-23 11:51:52Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : package cmd
+       2              : 
+       3              : import (
+       4              :         "context"
+       5              :         "fmt"
+       6              :         "io"
+       7              :         "os"
+       8              :         "os/exec"
+       9              :         "os/signal"
+      10              :         "path/filepath"
+      11              :         "strings"
+      12              :         "syscall"
+      13              :         "time"
+      14              : 
+      15              :         "ksniff/kube"
+      16              :         "ksniff/pkg/config"
+      17              :         "ksniff/pkg/service/sniffer"
+      18              :         "ksniff/pkg/service/sniffer/runtime"
+      19              : 
+      20              :         "github.com/mitchellh/go-homedir"
+      21              :         "github.com/pkg/errors"
+      22              :         log "github.com/sirupsen/logrus"
+      23              :         "github.com/spf13/cobra"
+      24              :         "github.com/spf13/viper"
+      25              :         corev1 "k8s.io/api/core/v1"
+      26              :         v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+      27              :         "k8s.io/cli-runtime/pkg/genericclioptions"
+      28              :         "k8s.io/client-go/kubernetes"
+      29              :         "k8s.io/client-go/rest"
+      30              :         "k8s.io/client-go/tools/clientcmd"
+      31              :         "k8s.io/client-go/tools/clientcmd/api"
+      32              : 
+      33              :         _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
+      34              :         _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
+      35              :         _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+      36              : )
+      37              : 
+      38              : var (
+      39              :         ksniffExample = "kubectl sniff hello-minikube-7c77b68cff-qbvsd -c hello-minikube"
+      40              : )
+      41              : 
+      42              : const minimumNumberOfArguments = 1
+      43              : const tcpdumpBinaryName = "static-tcpdump"
+      44              : const tcpdumpRemotePath = "/tmp/static-tcpdump"
+      45              : 
+      46              : var tcpdumpLocalBinaryPathLookupList []string
+      47              : 
+      48              : type Ksniff struct {
+      49              :         configFlags      *genericclioptions.ConfigFlags
+      50              :         resultingContext *api.Context
+      51              :         clientset        kubernetes.Interface
+      52              :         restConfig       *rest.Config
+      53              :         rawConfig        api.Config
+      54              :         settings         *config.KsniffSettings
+      55              :         snifferService   sniffer.SnifferService
+      56              :         wireshark        *exec.Cmd
+      57              : }
+      58              : 
+      59           28 : func NewKsniff(settings *config.KsniffSettings) *Ksniff {
+      60           28 :         return &Ksniff{settings: settings, configFlags: genericclioptions.NewConfigFlags(true)}
+      61           28 : }
+      62              : 
+      63           10 : func NewCmdSniff(streams genericclioptions.IOStreams) *cobra.Command {
+      64           10 :         ksniffSettings := config.NewKsniffSettings(streams)
+      65           10 : 
+      66           10 :         ksniff := NewKsniff(ksniffSettings)
+      67           10 : 
+      68           10 :         cmd := &cobra.Command{
+      69           10 :                 Use:          "sniff pod [-n namespace] [-c container] [-f filter] [-o output-file] [-l local-tcpdump-path] [-r remote-tcpdump-path]",
+      70           10 :                 Short:        "Perform network sniffing on a container running in a kubernetes cluster.",
+      71           10 :                 Example:      ksniffExample,
+      72           10 :                 SilenceUsage: true,
+      73           10 :                 RunE: func(c *cobra.Command, args []string) error {
+      74            0 :                         if err := ksniff.Complete(c, args); err != nil {
+      75            0 :                                 return err
+      76            0 :                         }
+      77            0 :                         if err := ksniff.Validate(); err != nil {
+      78            0 :                                 return err
+      79            0 :                         }
+      80            0 :                         if err := ksniff.Run(); err != nil {
+      81            0 :                                 return err
+      82            0 :                         }
+      83              : 
+      84            0 :                         return nil
+      85              :                 },
+      86              :         }
+      87              : 
+      88           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedNamespace, "namespace", "n", "", "namespace (optional)")
+      89           10 :         _ = viper.BindEnv("namespace", "KUBECTL_PLUGINS_CURRENT_NAMESPACE")
+      90           10 :         _ = viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace"))
+      91           10 : 
+      92           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedInterface, "interface", "i", "any", "pod interface to packet capture (optional)")
+      93           10 :         _ = viper.BindEnv("interface", "KUBECTL_PLUGINS_LOCAL_FLAG_INTERFACE")
+      94           10 :         _ = viper.BindPFlag("interface", cmd.Flags().Lookup("interface"))
+      95           10 : 
+      96           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedContainer, "container", "c", "", "container (optional)")
+      97           10 :         _ = viper.BindEnv("container", "KUBECTL_PLUGINS_LOCAL_FLAG_CONTAINER")
+      98           10 :         _ = viper.BindPFlag("container", cmd.Flags().Lookup("container"))
+      99           10 : 
+     100           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedFilter, "filter", "f", "", "tcpdump filter (optional)")
+     101           10 :         _ = viper.BindEnv("filter", "KUBECTL_PLUGINS_LOCAL_FLAG_FILTER")
+     102           10 :         _ = viper.BindPFlag("filter", cmd.Flags().Lookup("filter"))
+     103           10 : 
+     104           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedOutputFile, "output-file", "o", "",
+     105           10 :                 "output file path, tcpdump output will be redirect to this file instead of wireshark (optional) ('-' stdout)")
+     106           10 :         _ = viper.BindEnv("output-file", "KUBECTL_PLUGINS_LOCAL_FLAG_OUTPUT_FILE")
+     107           10 :         _ = viper.BindPFlag("output-file", cmd.Flags().Lookup("output-file"))
+     108           10 : 
+     109           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedLocalTcpdumpPath, "local-tcpdump-path", "l", "",
+     110           10 :                 "local static tcpdump binary path (optional)")
+     111           10 :         _ = viper.BindEnv("local-tcpdump-path", "KUBECTL_PLUGINS_LOCAL_FLAG_LOCAL_TCPDUMP_PATH")
+     112           10 :         _ = viper.BindPFlag("local-tcpdump-path", cmd.Flags().Lookup("local-tcpdump-path"))
+     113           10 : 
+     114           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedRemoteTcpdumpPath, "remote-tcpdump-path", "r", tcpdumpRemotePath,
+     115           10 :                 "remote static tcpdump binary path (optional)")
+     116           10 :         _ = viper.BindEnv("remote-tcpdump-path", "KUBECTL_PLUGINS_LOCAL_FLAG_REMOTE_TCPDUMP_PATH")
+     117           10 :         _ = viper.BindPFlag("remote-tcpdump-path", cmd.Flags().Lookup("remote-tcpdump-path"))
+     118           10 : 
+     119           10 :         cmd.Flags().BoolVarP(&ksniffSettings.UserSpecifiedVerboseMode, "verbose", "v", false,
+     120           10 :                 "if specified, ksniff output will include debug information (optional)")
+     121           10 :         _ = viper.BindEnv("verbose", "KUBECTL_PLUGINS_LOCAL_FLAG_VERBOSE")
+     122           10 :         _ = viper.BindPFlag("verbose", cmd.Flags().Lookup("verbose"))
+     123           10 : 
+     124           10 :         cmd.Flags().BoolVarP(&ksniffSettings.UserSpecifiedPrivilegedMode, "privileged", "p", false,
+     125           10 :                 "if specified, ksniff will deploy another pod that have privileges to attach target pod network namespace")
+     126           10 :         _ = viper.BindEnv("privileged", "KUBECTL_PLUGINS_LOCAL_FLAG_PRIVILEGED")
+     127           10 :         _ = viper.BindPFlag("privileged", cmd.Flags().Lookup("privileged"))
+     128           10 : 
+     129           10 :         cmd.Flags().DurationVarP(&ksniffSettings.UserSpecifiedPodCreateTimeout, "pod-creation-timeout", "",
+     130           10 :                 1*time.Minute, "the length of time to wait for privileged pod to be created (e.g. 20s, 2m, 1h). "+
+     131           10 :                         "A value of zero means the creation never times out.")
+     132           10 : 
+     133           10 :         cmd.Flags().StringVarP(&ksniffSettings.Image, "image", "", "",
+     134           10 :                 "the privileged container image (optional)")
+     135           10 :         _ = viper.BindEnv("image", "KUBECTL_PLUGINS_LOCAL_FLAG_IMAGE")
+     136           10 :         _ = viper.BindPFlag("image", cmd.Flags().Lookup("image"))
+     137           10 : 
+     138           10 :         cmd.Flags().StringVarP(&ksniffSettings.TCPDumpImage, "tcpdump-image", "", "",
+     139           10 :                 "the tcpdump container image (optional)")
+     140           10 :         _ = viper.BindEnv("tcpdump-image", "KUBECTL_PLUGINS_LOCAL_FLAG_TCPDUMP_IMAGE")
+     141           10 :         _ = viper.BindPFlag("tcpdump-image", cmd.Flags().Lookup("tcpdump-image"))
+     142           10 : 
+     143           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedKubeContext, "context", "x", "",
+     144           10 :                 "kubectl context to work on (optional)")
+     145           10 :         _ = viper.BindEnv("context", "KUBECTL_PLUGINS_CURRENT_CONTEXT")
+     146           10 :         _ = viper.BindPFlag("context", cmd.Flags().Lookup("context"))
+     147           10 : 
+     148           10 :         cmd.Flags().StringVarP(&ksniffSettings.SocketPath, "socket", "", "",
+     149           10 :                 "the container runtime socket path (optional)")
+     150           10 :         _ = viper.BindEnv("socket", "KUBECTL_PLUGINS_SOCKET_PATH")
+     151           10 :         _ = viper.BindPFlag("socket", cmd.Flags().Lookup("socket"))
+     152           10 : 
+     153           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedServiceAccount, "serviceaccount", "s", "",
+     154           10 :                 "the privileged container service account (optional)")
+     155           10 :         _ = viper.BindEnv("serviceaccount", "KUBECTL_PLUGINS_LOCAL_FLAG_SERVICE_ACCOUNT")
+     156           10 :         _ = viper.BindPFlag("serviceaccount", cmd.Flags().Lookup("serviceaccount"))
+     157           10 : 
+     158           10 :         return cmd
+     159              : }
+     160              : 
+     161            2 : func (o *Ksniff) Complete(cmd *cobra.Command, args []string) error {
+     162            2 :         // Validate and populate basic settings
+     163            2 :         if err := o.completeBasicSettings(cmd, args); err != nil {
+     164            2 :                 return err
+     165            2 :         }
+     166              : 
+     167              :         // Load Kubernetes configuration
+     168            0 :         if err := o.loadKubeConfig(); err != nil {
+     169            0 :                 return err
+     170            0 :         }
+     171              : 
+     172            0 :         return nil
+     173              : }
+     174              : 
+     175              : // completeBasicSettings validates arguments and populates settings from viper
+     176              : // This method doesn't require Kubernetes configuration and can be tested independently
+     177           12 : func (o *Ksniff) completeBasicSettings(cmd *cobra.Command, args []string) error {
+     178           12 :         if len(args) < minimumNumberOfArguments {
+     179            1 :                 _ = cmd.Usage()
+     180            1 :                 return errors.New("not enough arguments")
+     181            1 :         }
+     182              : 
+     183           11 :         o.settings.UserSpecifiedPodName = args[0]
+     184           11 :         if o.settings.UserSpecifiedPodName == "" {
+     185            1 :                 return errors.New("pod name is empty")
+     186            1 :         }
+     187              : 
+     188           10 :         o.settings.UserSpecifiedNamespace = viper.GetString("namespace")
+     189           10 :         o.settings.UserSpecifiedContainer = viper.GetString("container")
+     190           10 :         o.settings.UserSpecifiedInterface = viper.GetString("interface")
+     191           10 :         o.settings.UserSpecifiedFilter = viper.GetString("filter")
+     192           10 :         o.settings.UserSpecifiedOutputFile = viper.GetString("output-file")
+     193           10 :         o.settings.UserSpecifiedLocalTcpdumpPath = viper.GetString("local-tcpdump-path")
+     194           10 :         o.settings.UserSpecifiedRemoteTcpdumpPath = viper.GetString("remote-tcpdump-path")
+     195           10 :         o.settings.UserSpecifiedVerboseMode = viper.GetBool("verbose")
+     196           10 :         o.settings.UserSpecifiedPrivilegedMode = viper.GetBool("privileged")
+     197           10 :         o.settings.UserSpecifiedKubeContext = viper.GetString("context")
+     198           10 :         o.settings.Image = viper.GetString("image")
+     199           10 :         o.settings.TCPDumpImage = viper.GetString("tcpdump-image")
+     200           10 :         o.settings.SocketPath = viper.GetString("socket")
+     201           10 :         o.settings.UseDefaultImage = !viper.IsSet("image")
+     202           10 :         o.settings.UseDefaultTCPDumpImage = !viper.IsSet("tcpdump-image")
+     203           10 :         o.settings.UseDefaultSocketPath = !viper.IsSet("socket")
+     204           10 :         o.settings.UserSpecifiedServiceAccount = viper.GetString("serviceaccount")
+     205           10 : 
+     206           10 :         if o.settings.UserSpecifiedVerboseMode {
+     207            1 :                 log.Info("running in verbose mode")
+     208            1 :                 log.SetLevel(log.DebugLevel)
+     209            1 :         }
+     210              : 
+     211           10 :         var err error
+     212           10 :         tcpdumpLocalBinaryPathLookupList, err = o.buildTcpdumpBinaryPathLookupList()
+     213           10 :         if err != nil {
+     214            0 :                 return err
+     215            0 :         }
+     216              : 
+     217           10 :         return nil
+     218              : }
+     219              : 
+     220              : // loadKubeConfig loads the Kubernetes configuration and initializes the clientset
+     221              : // This method is separated to allow for easier testing and mocking
+     222            0 : func (o *Ksniff) loadKubeConfig() error {
+     223            0 :         var err error
+     224            0 : 
+     225            0 :         o.rawConfig, err = o.configFlags.ToRawKubeConfigLoader().RawConfig()
+     226            0 :         if err != nil {
+     227            0 :                 return err
+     228            0 :         }
+     229              : 
+     230            0 :         var currentContext *api.Context
+     231            0 :         var exists bool
+     232            0 : 
+     233            0 :         if o.settings.UserSpecifiedKubeContext != "" {
+     234            0 :                 currentContext, exists = o.rawConfig.Contexts[o.settings.UserSpecifiedKubeContext]
+     235            0 :         } else {
+     236            0 :                 currentContext, exists = o.rawConfig.Contexts[o.rawConfig.CurrentContext]
+     237            0 :         }
+     238              : 
+     239            0 :         if !exists {
+     240            0 :                 return errors.New("context doesn't exist")
+     241            0 :         }
+     242              : 
+     243            0 :         loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
+     244            0 :         configOverrides := &clientcmd.ConfigOverrides{
+     245            0 :                 CurrentContext: o.settings.UserSpecifiedKubeContext,
+     246            0 :         }
+     247            0 :         kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
+     248            0 :         o.restConfig, err = kubeConfig.ClientConfig()
+     249            0 :         if err != nil {
+     250            0 :                 return err
+     251            0 :         }
+     252              : 
+     253            0 :         o.restConfig.Timeout = 30 * time.Second
+     254            0 : 
+     255            0 :         o.clientset, err = kubernetes.NewForConfig(o.restConfig)
+     256            0 :         if err != nil {
+     257            0 :                 return err
+     258            0 :         }
+     259              : 
+     260            0 :         o.resultingContext = currentContext.DeepCopy()
+     261            0 :         if o.settings.UserSpecifiedNamespace != "" {
+     262            0 :                 o.resultingContext.Namespace = o.settings.UserSpecifiedNamespace
+     263            0 :         }
+     264              : 
+     265            0 :         return nil
+     266              : }
+     267              : 
+     268           10 : func (o *Ksniff) buildTcpdumpBinaryPathLookupList() ([]string, error) {
+     269           10 :         userHomeDir, err := homedir.Dir()
+     270           10 :         if err != nil {
+     271            0 :                 return nil, err
+     272            0 :         }
+     273              : 
+     274           10 :         ksniffBinaryPath, err := filepath.EvalSymlinks(os.Args[0])
+     275           10 :         if err != nil {
+     276            0 :                 return nil, err
+     277            0 :         }
+     278              : 
+     279           10 :         ksniffBinaryDir := filepath.Dir(ksniffBinaryPath)
+     280           10 :         ksniffBinaryPath = filepath.Join(ksniffBinaryDir, tcpdumpBinaryName)
+     281           10 : 
+     282           10 :         kubeKsniffPluginFolder := filepath.Join(userHomeDir, filepath.FromSlash("/.kube/plugin/sniff/"), tcpdumpBinaryName)
+     283           10 : 
+     284           10 :         return append([]string{o.settings.UserSpecifiedLocalTcpdumpPath, ksniffBinaryPath},
+     285           10 :                 filepath.Join("/usr/local/bin/", tcpdumpBinaryName), kubeKsniffPluginFolder), nil
+     286              : }
+     287              : 
+     288            6 : func (o *Ksniff) Validate() error {
+     289            6 :         if len(o.rawConfig.CurrentContext) == 0 {
+     290            0 :                 return errors.New("context doesn't exist")
+     291            0 :         }
+     292              : 
+     293            6 :         if o.resultingContext.Namespace == "" {
+     294            0 :                 return errors.New("namespace value is empty should be custom or default")
+     295            0 :         }
+     296              : 
+     297            6 :         var err error
+     298            6 : 
+     299            6 :         if !o.settings.UserSpecifiedPrivilegedMode {
+     300            0 :                 o.settings.UserSpecifiedLocalTcpdumpPath, err = findLocalTcpdumpBinaryPath()
+     301            0 :                 if err != nil {
+     302            0 :                         return err
+     303            0 :                 }
+     304              : 
+     305            0 :                 log.Infof("using tcpdump path at: '%s'", o.settings.UserSpecifiedLocalTcpdumpPath)
+     306            6 :         } else if o.settings.UserSpecifiedServiceAccount != "" {
+     307            0 :                 _, err := o.clientset.CoreV1().ServiceAccounts(o.resultingContext.Namespace).Get(context.TODO(), o.settings.UserSpecifiedServiceAccount, v1.GetOptions{})
+     308            0 :                 if err != nil {
+     309            0 :                         return err
+     310            0 :                 }
+     311              :         }
+     312              : 
+     313            6 :         pod, err := o.clientset.CoreV1().Pods(o.resultingContext.Namespace).Get(context.TODO(), o.settings.UserSpecifiedPodName, v1.GetOptions{})
+     314            6 :         if err != nil {
+     315            1 :                 return err
+     316            1 :         }
+     317              : 
+     318            5 :         if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
+     319            1 :                 return errors.Errorf("cannot sniff on a container in a completed pod; current phase is %s", pod.Status.Phase)
+     320            1 :         }
+     321              : 
+     322            4 :         o.settings.DetectedPodNodeName = pod.Spec.NodeName
+     323            4 : 
+     324            4 :         log.Debugf("pod '%s' status: '%s'", o.settings.UserSpecifiedPodName, pod.Status.Phase)
+     325            4 : 
+     326            4 :         if len(pod.Spec.Containers) < 1 {
+     327            1 :                 return errors.New("no containers in specified pod")
+     328            1 :         }
+     329              : 
+     330            3 :         if o.settings.UserSpecifiedContainer == "" {
+     331            1 :                 log.Info("no container specified, taking first container we found in pod.")
+     332            1 :                 o.settings.UserSpecifiedContainer = pod.Spec.Containers[0].Name
+     333            1 :                 log.Infof("selected container: '%s'", o.settings.UserSpecifiedContainer)
+     334            1 :         }
+     335              : 
+     336            3 :         if err := o.findContainerId(pod); err != nil {
+     337            1 :                 return err
+     338            1 :         }
+     339              : 
+     340            2 :         kubernetesApiService := kube.NewKubernetesApiService(o.clientset, o.restConfig, o.resultingContext.Namespace)
+     341            2 : 
+     342            2 :         if o.settings.UserSpecifiedPrivilegedMode {
+     343            2 :                 log.Info("sniffing method: privileged pod")
+     344            2 :                 bridge := runtime.NewContainerRuntimeBridge(o.settings.DetectedContainerRuntime)
+     345            2 :                 o.snifferService = sniffer.NewPrivilegedPodRemoteSniffingService(o.settings, kubernetesApiService, bridge)
+     346            2 :         } else {
+     347            0 :                 log.Info("sniffing method: upload static tcpdump")
+     348            0 :                 o.snifferService = sniffer.NewUploadTcpdumpRemoteSniffingService(o.settings, kubernetesApiService)
+     349            0 :         }
+     350              : 
+     351            2 :         return nil
+     352              : }
+     353              : 
+     354            3 : func (o *Ksniff) findContainerId(pod *corev1.Pod) error {
+     355            3 :         for _, containerStatus := range pod.Status.ContainerStatuses {
+     356            3 :                 if o.settings.UserSpecifiedContainer == containerStatus.Name {
+     357            2 :                         result := strings.Split(containerStatus.ContainerID, "://")
+     358            2 :                         if len(result) != 2 {
+     359            0 :                                 break
+     360              :                         }
+     361            2 :                         o.settings.DetectedContainerRuntime = result[0]
+     362            2 :                         o.settings.DetectedContainerId = result[1]
+     363            2 :                         return nil
+     364              :                 }
+     365              :         }
+     366              : 
+     367            1 :         return errors.Errorf("couldn't find container: '%s' in pod: '%s'", o.settings.UserSpecifiedContainer, o.settings.UserSpecifiedPodName)
+     368              : }
+     369              : 
+     370            0 : func findLocalTcpdumpBinaryPath() (string, error) {
+     371            0 :         log.Debugf("searching for tcpdump binary using lookup list: '%v'", tcpdumpLocalBinaryPathLookupList)
+     372            0 : 
+     373            0 :         for _, possibleTcpdumpPath := range tcpdumpLocalBinaryPathLookupList {
+     374            0 :                 if _, err := os.Stat(possibleTcpdumpPath); err == nil {
+     375            0 :                         log.Debugf("tcpdump binary found at: '%s'", possibleTcpdumpPath)
+     376            0 : 
+     377            0 :                         return possibleTcpdumpPath, nil
+     378            0 :                 }
+     379              : 
+     380            0 :                 log.Debugf("tcpdump binary was not found at: '%s'", possibleTcpdumpPath)
+     381              :         }
+     382              : 
+     383            0 :         return "", errors.Errorf("couldn't find static tcpdump binary on any of: '%v'", tcpdumpLocalBinaryPathLookupList)
+     384              : }
+     385              : 
+     386            0 : func (o *Ksniff) setupSignalHandler() chan interface{} {
+     387            0 :         signals := make(chan os.Signal, 1)
+     388            0 :         exit := make(chan interface{})
+     389            0 : 
+     390            0 :         signal.Notify(signals, syscall.SIGINT)
+     391            0 :         go func() {
+     392            0 :                 for {
+     393            0 :                         select {
+     394            0 :                         case sig := <-signals:
+     395            0 :                                 if sig == syscall.SIGINT || sig == syscall.SIGTERM {
+     396            0 :                                         log.Info("starting sniffer cleanup")
+     397            0 :                                         err := o.snifferService.Cleanup()
+     398            0 :                                         if err != nil {
+     399            0 :                                                 log.WithError(err).Error("failed to teardown sniffer, a manual teardown is required.")
+     400            0 :                                         }
+     401            0 :                                         log.Info("sniffer cleanup completed successfully")
+     402            0 : 
+     403            0 :                                         // Kill wireshark if used
+     404            0 :                                         if o.wireshark != nil {
+     405            0 :                                                 if o.wireshark.Process != nil {
+     406            0 :                                                         err = o.wireshark.Process.Kill()
+     407            0 :                                                         if err != nil && err != os.ErrProcessDone {
+     408            0 :                                                                 log.WithError(err).Error("failed to kill wireshark process")
+     409            0 :                                                         } else {
+     410            0 :                                                                 log.Debug("wireshark process killed")
+     411            0 :                                                         }
+     412              :                                                 }
+     413              :                                         }
+     414              : 
+     415            0 :                                         close(signals)
+     416              :                                 }
+     417            0 :                         case <-exit:
+     418            0 :                                 return
+     419              :                         }
+     420              : 
+     421              :                 }
+     422              :         }()
+     423            0 :         return exit
+     424              : }
+     425              : 
+     426            0 : func (o *Ksniff) Run() error {
+     427            0 :         log.Infof("sniffing on pod: '%s' [namespace: '%s', container: '%s', filter: '%s', interface: '%s']",
+     428            0 :                 o.settings.UserSpecifiedPodName, o.resultingContext.Namespace, o.settings.UserSpecifiedContainer, o.settings.UserSpecifiedFilter, o.settings.UserSpecifiedInterface)
+     429            0 : 
+     430            0 :         err := o.snifferService.Setup()
+     431            0 :         if err != nil {
+     432            0 :                 return err
+     433            0 :         }
+     434              : 
+     435              :         // Ensure sniffer is clean on interrupt
+     436            0 :         closeHandler := o.setupSignalHandler()
+     437            0 : 
+     438            0 :         // Ensure sniffer is clean on complete
+     439            0 :         defer func() {
+     440            0 :                 closeHandler <- true
+     441            0 :         }()
+     442              : 
+     443            0 :         if o.settings.UserSpecifiedOutputFile != "" {
+     444            0 :                 log.Infof("output file option specified, storing output in: '%s'", o.settings.UserSpecifiedOutputFile)
+     445            0 : 
+     446            0 :                 var err error
+     447            0 :                 var fileWriter io.Writer
+     448            0 : 
+     449            0 :                 if o.settings.UserSpecifiedOutputFile == "-" {
+     450            0 :                         fileWriter = os.Stdout
+     451            0 :                 } else {
+     452            0 :                         fileWriter, err = os.Create(o.settings.UserSpecifiedOutputFile)
+     453            0 :                         if err != nil {
+     454            0 :                                 return err
+     455            0 :                         }
+     456              :                 }
+     457              : 
+     458            0 :                 err = o.snifferService.Start(fileWriter)
+     459            0 :                 if err != nil {
+     460            0 :                         return err
+     461            0 :                 }
+     462              : 
+     463            0 :         } else {
+     464            0 :                 log.Info("spawning wireshark!")
+     465            0 : 
+     466            0 :                 title := fmt.Sprintf("gui.window_title:%s/%s/%s", o.resultingContext.Namespace, o.settings.UserSpecifiedPodName, o.settings.UserSpecifiedContainer)
+     467            0 :                 o.wireshark = exec.Command("wireshark", "-k", "-i", "-", "-o", title)
+     468            0 : 
+     469            0 :                 stdinWriter, err := o.wireshark.StdinPipe()
+     470            0 :                 if err != nil {
+     471            0 :                         return err
+     472            0 :                 }
+     473              : 
+     474            0 :                 go func() {
+     475            0 :                         err := o.snifferService.Start(stdinWriter)
+     476            0 :                         if err != nil {
+     477            0 :                                 log.WithError(err).Errorf("failed to start remote sniffing, stopping wireshark")
+     478            0 :                                 _ = o.wireshark.Process.Kill()
+     479            0 :                         }
+     480              :                 }()
+     481              : 
+     482            0 :                 err = o.wireshark.Run()
+     483            0 :                 return err
+     484              :         }
+     485              : 
+     486            0 :         return nil
+     487              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/index.html b/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/index.html new file mode 100644 index 0000000..0f934d0 --- /dev/null +++ b/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/index.html @@ -0,0 +1,98 @@ + + + + + + + LCOV - _coverage_report.dat - Users/mtl/Development/omnicate-ksniff/utils + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - /Users/mtl/Development/omnicate-ksniff/utilsCoverageTotalHit
Test:_coverage_report.datLines:100.0 %3636
Test Date:2026-01-23 11:51:52Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
utils.go +
100.0%
+
100.0 %3636
Note: 'Function Coverage' columns elided as function owner is not identified.
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/utils.go.gcov.html b/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/utils.go.gcov.html new file mode 100644 index 0000000..c59ac96 --- /dev/null +++ b/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/utils.go.gcov.html @@ -0,0 +1,128 @@ + + + + + + + LCOV - _coverage_report.dat - Users/mtl/Development/omnicate-ksniff/utils/utils.go + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - /Users/mtl/Development/omnicate-ksniff/utils - utils.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %3636
Test Date:2026-01-23 11:51:52Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : package utils
+       2              : 
+       3              : import (
+       4              :         "context"
+       5              :         "math/rand"
+       6              :         "time"
+       7              : )
+       8              : 
+       9            4 : func RunWhileFalse(fn func() bool, timeout time.Duration, delay time.Duration) bool {
+      10            4 :         var ctx context.Context
+      11            4 :         var cancel context.CancelFunc
+      12            4 :         if fn() {
+      13            1 :                 return true
+      14            1 :         }
+      15              : 
+      16              :         // Timeout 0 is infinite timeout
+      17            3 :         if (timeout == 0) {
+      18            1 :                 ctx, cancel = context.WithCancel(context.Background())
+      19            2 :         } else {
+      20            2 :                 ctx, cancel = context.WithTimeout(context.Background(), timeout)
+      21            2 :         }
+      22            3 :         delayTick := time.NewTicker(delay)
+      23            3 : 
+      24            3 :         defer delayTick.Stop()
+      25            3 :         defer cancel()
+      26            3 : 
+      27            8 :         for {
+      28            8 :                 select {
+      29            1 :                 case <-ctx.Done():
+      30            1 :                         return false
+      31            6 :                 case <-delayTick.C:
+      32            6 :                         if fn() {
+      33            1 :                                 cancel()
+      34            1 :                                 return true
+      35            1 :                         }
+      36              :                 }
+      37              :         }
+      38              : }
+      39              : 
+      40            6 : func GenerateRandomString(length int) string {
+      41            6 : 
+      42            6 :         rand.Seed(time.Now().UnixNano())
+      43            6 : 
+      44            6 :         var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+      45            6 : 
+      46            6 :         b := make([]rune, length)
+      47           48 :         for i := range b {
+      48           48 :                 b[i] = letterRunes[rand.Intn(len(letterRunes))]
+      49           48 :         }
+      50              : 
+      51            6 :         return string(b)
+      52              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/amber.png b/coverage_html/amber.png new file mode 100644 index 0000000000000000000000000000000000000000..2cab170d8359081983a4e343848dfe06bc490f12 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^G2tW}LqE04T&+ z;1OBOz`!j8!i<;h*8KqrvZOouIx;Y9?C1WI$O`1M1^9%x{(levWG?NMQuI!iC1^Jb!lvI6;R0X`wF(yt=9xVZRt1vCRixIA4P dLn>}1Cji+@42)0J?}79&c)I$ztaD0e0sy@GAL0N2 literal 0 HcmV?d00001 diff --git a/coverage_html/gcov.css b/coverage_html/gcov.css new file mode 100644 index 0000000..1cacc83 --- /dev/null +++ b/coverage_html/gcov.css @@ -0,0 +1,1125 @@ +/* All views: initial background and text color */ +body +{ + color: #000000; + background-color: #ffffff; +} + +/* All views: standard link format*/ +a:link +{ + color: #284fa8; + text-decoration: underline; +} + +/* All views: standard link - visited format */ +a:visited +{ + color: #00cb40; + text-decoration: underline; +} + +/* All views: standard link - activated format */ +a:active +{ + color: #ff0040; + text-decoration: underline; +} + +/* All views: main title format */ +td.title +{ + text-align: center; + padding-bottom: 10px; + font-family: sans-serif; + font-size: 20pt; + font-style: italic; + font-weight: bold; +} +/* table footnote */ +td.footnote +{ + text-align: left; + padding-left: 100px; + padding-right: 10px; + background-color: #dae7fe; /* light blue table background color */ + /* dark blue table header color + background-color: #6688d4; */ + white-space: nowrap; + font-family: sans-serif; + font-style: italic; + font-size:70%; +} +/* "Line coverage date bins" leader */ +td.subTableHeader +{ + text-align: center; + padding-bottom: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: center; +} + +/* All views: header item format */ +td.headerItem +{ + text-align: right; + padding-right: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: top; + white-space: nowrap; +} + +/* All views: header item value format */ +td.headerValue +{ + text-align: left; + color: #284fa8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; +} + +/* All views: header item coverage table heading */ +td.headerCovTableHead +{ + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; +} + +/* All views: header item coverage table entry */ +td.headerCovTableEntry +{ + text-align: right; + color: #284fa8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #dae7fe; +} + +/* All views: header item coverage table entry for high coverage rate */ +td.headerCovTableEntryHi +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #a7fc9d; +} + +/* All views: header item coverage table entry for medium coverage rate */ +td.headerCovTableEntryMed +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #ffea20; +} + +/* All views: header item coverage table entry for ow coverage rate */ +td.headerCovTableEntryLo +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #ff0000; +} + +/* All views: header legend value for legend entry */ +td.headerValueLeg +{ + text-align: left; + color: #000000; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; + padding-top: 4px; +} + +/* All views: color of horizontal ruler */ +td.ruler +{ + background-color: #6688d4; +} + +/* All views: version string format */ +td.versionInfo +{ + text-align: center; + padding-top: 2px; + font-family: sans-serif; + font-style: italic; +} + +/* Directory view/File view (all)/Test case descriptions: + table headline format */ +td.tableHead +{ + text-align: center; + color: #ffffff; + background-color: #6688d4; + font-family: sans-serif; + font-size: 120%; + font-weight: bold; + white-space: nowrap; + padding-left: 4px; + padding-right: 4px; +} + +span.tableHeadSort +{ + padding-right: 4px; +} + +/* Directory view/File view (all): filename entry format */ +td.coverFile +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #dae7fe; + font-family: monospace; +} + +/* Directory view/File view (all): directory name entry format */ +td.coverDirectory +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #b8d0ff; + font-family: monospace; +} + +/* Directory view/File view (all): filename entry format */ +td.overallOwner +{ + text-align: center; + font-weight: bold; + font-family: sans-serif; + background-color: #dae7fe; + padding-right: 10px; + padding-left: 10px; +} + +/* Directory view/File view (all): filename entry format */ +td.ownerName +{ + text-align: right; + font-style: italic; + font-family: sans-serif; + background-color: #E5DBDB; + padding-right: 10px; + padding-left: 20px; +} + +/* Directory view/File view (all): bar-graph entry format*/ +td.coverBar +{ + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; +} + +/* Directory view/File view (all): bar-graph entry format*/ +td.owner_coverBar +{ + padding-left: 10px; + padding-right: 10px; + background-color: #E5DBDB; +} + +/* Directory view/File view (all): bar-graph outline color */ +td.coverBarOutline +{ + background-color: #000000; +} + +/* Directory view/File view (all): percentage entry for files with + high coverage rate */ +td.coverPerHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #a7fc9d; + font-weight: bold; + font-family: sans-serif; +} + +/* 'owner' entry: slightly lighter color than 'coverPerHi' */ +td.owner_coverPerHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #82E0AA; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry */ +td.coverNumDflt +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + white-space: nowrap; + font-family: sans-serif; +} + +/* td background color and font for the 'owner' section of the table */ +td.ownerTla +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #E5DBDB; + white-space: nowrap; + font-family: sans-serif; + font-style: italic; +} + +/* Directory view/File view (all): line count entry for files with + high coverage rate */ +td.coverNumHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #a7fc9d; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #82E0AA; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + medium coverage rate */ +td.coverPerMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ffea20; + font-weight: bold; + font-family: sans-serif; +} + +td.owner_coverPerMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #F9E79F; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + medium coverage rate */ +td.coverNumMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ffea20; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #F9E79F; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + low coverage rate */ +td.coverPerLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + font-weight: bold; + font-family: sans-serif; +} + +td.owner_coverPerLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + low coverage rate */ +td.coverNumLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; + white-space: nowrap; + font-family: sans-serif; +} + +/* File view (all): "show/hide details" link format */ +a.detail:link +{ + color: #b8d0ff; + font-size:80%; +} + +/* File view (all): "show/hide details" link - visited format */ +a.detail:visited +{ + color: #b8d0ff; + font-size:80%; +} + +/* File view (all): "show/hide details" link - activated format */ +a.detail:active +{ + color: #ffffff; + font-size:80%; +} + +/* File view (detail): test name entry */ +td.testName +{ + text-align: right; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* File view (detail): test percentage entry */ +td.testPer +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* File view (detail): test lines count entry */ +td.testNum +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* Test case descriptions: test name format*/ +dt +{ + font-family: sans-serif; + font-weight: bold; +} + +/* Test case descriptions: description table body */ +td.testDescription +{ + padding-top: 10px; + padding-left: 30px; + padding-bottom: 10px; + padding-right: 30px; + background-color: #dae7fe; +} + +/* Source code view: function entry */ +td.coverFn +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #dae7fe; + font-family: monospace; +} + +/* Source code view: function entry zero count*/ +td.coverFnLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: function entry nonzero count*/ +td.coverFnHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-weight: bold; + font-family: sans-serif; +} + +td.coverFnAlias +{ + text-align: right; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + /* make this a slightly different color than the leader - otherwise, + otherwise the alias is hard to distinguish in the table */ + background-color: #E5DBDB; /* very light pale grey/blue */ + font-family: monospace; +} + +/* Source code view: function entry zero count*/ +td.coverFnAliasLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; /* lighter red */ + font-family: sans-serif; +} + +/* Source code view: function entry nonzero count*/ +td.coverFnAliasHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: source code format */ +pre.source +{ + font-family: monospace; + white-space: pre; + margin-top: 2px; +} + +/* elided/removed code */ +span.elidedSource +{ + font-family: sans-serif; + /*font-size: 8pt; */ + font-style: italic; + background-color: lightgrey; +} + +/* Source code view: line number format */ +span.lineNum +{ + background-color: #efe383; +} + +/* Source code view: line number format when there are deleted + lines in the corresponding location */ +span.lineNumWithDelete +{ + foreground-color: #efe383; + background-color: lightgrey; +} + +/* Source code view: format for Cov legend */ +span.coverLegendCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #cad7fe; +} + +/* Source code view: format for NoCov legend */ +span.coverLegendNoCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #ff6230; +} + +/* Source code view: format for the source code heading line */ +pre.sourceHeading +{ + white-space: pre; + font-family: monospace; + font-weight: bold; + margin: 0px; +} + +/* All views: header legend value for low rate */ +td.headerValueLegL +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 4px; + padding-right: 2px; + background-color: #ff0000; + font-size: 80%; +} + +/* All views: header legend value for med rate */ +td.headerValueLegM +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 2px; + background-color: #ffea20; + font-size: 80%; +} + +/* All views: header legend value for hi rate */ +td.headerValueLegH +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 4px; + background-color: #a7fc9d; + font-size: 80%; +} + +/* All views except source code view: legend format for low coverage */ +span.coverLegendCovLo +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #ff0000; +} + +/* All views except source code view: legend format for med coverage */ +span.coverLegendCovMed +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #ffea20; +} + +/* All views except source code view: legend format for hi coverage */ +span.coverLegendCovHi +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #a7fc9d; +} + +a.branchTla:link +{ + color: #000000; +} + +a.branchTla:visited +{ + color: #000000; +} + +a.mcdcTla:link +{ + color: #000000; +} + +a.mcdcTla:visited +{ + color: #000000; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): +Newly added code is not tested" */ +td.tlaUNC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUNC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): +Newly added code is not tested" */ +span.tlaUNC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUNC { + background-color: #FF6230; +} +a.tlaBgUNC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUNC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): +Unchanged code is no longer tested" */ +td.tlaLBC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgLBC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): +Unchanged code is no longer tested" */ +span.tlaLBC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgLBC { + background-color: #FF6230; +} +a.tlaBgLBC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadLBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): +Previously unused code is untested" */ +td.tlaUIC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUIC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): +Previously unused code is untested" */ +span.tlaUIC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUIC { + background-color: #FF6230; +} +a.tlaBgUIC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUIC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): +Unchanged code was untested before, is untested now" */ +td.tlaUBC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUBC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): +Unchanged code was untested before, is untested now" */ +span.tlaUBC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUBC { + background-color: #FF6230; +} +a.tlaBgUBC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): +Unchanged code is tested now" */ +td.tlaGBC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGBC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): +Unchanged code is tested now" */ +span.tlaGBC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGBC { + background-color: #CAD7FE; +} +a.tlaBgGBC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): +Previously unused code is tested now" */ +td.tlaGIC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGIC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): +Previously unused code is tested now" */ +span.tlaGIC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGIC { + background-color: #CAD7FE; +} +a.tlaBgGIC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGIC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): +Newly added code is tested" */ +td.tlaGNC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGNC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): +Newly added code is tested" */ +span.tlaGNC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGNC { + background-color: #CAD7FE; +} +a.tlaBgGNC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGNC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): +Unchanged code was tested before and is still tested" */ +td.tlaCBC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgCBC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): +Unchanged code was tested before and is still tested" */ +span.tlaCBC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgCBC { + background-color: #CAD7FE; +} +a.tlaBgCBC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadCBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): +Previously untested code is unused now" */ +td.tlaEUB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgEUB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): +Previously untested code is unused now" */ +span.tlaEUB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgEUB { + background-color: #FFFFFF; +} +a.tlaBgEUB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadEUB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): +Previously tested code is unused now" */ +td.tlaECB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgECB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): +Previously tested code is unused now" */ +span.tlaECB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgECB { + background-color: #FFFFFF; +} +a.tlaBgECB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadECB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): +Previously untested code has been deleted" */ +td.tlaDUB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgDUB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): +Previously untested code has been deleted" */ +span.tlaDUB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgDUB { + background-color: #FFFFFF; +} +a.tlaBgDUB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadDUB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): +Previously tested code has been deleted" */ +td.tlaDCB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgDCB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): +Previously tested code has been deleted" */ +span.tlaDCB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgDCB { + background-color: #FFFFFF; +} +a.tlaBgDCB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadDCB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view: format for date/owner bin that is not hit */ +span.missBins +{ + background-color: #ff0000 /* red */ +} diff --git a/coverage_html/glass.png b/coverage_html/glass.png new file mode 100644 index 0000000000000000000000000000000000000000..e1abc00680a3093c49fdb775ae6bdb6764c95af2 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtmSN`?>!lvI6;R0X`wF z|Ns97GD8ntt^-nxB|(0{3=Yq3q=7g|-tI089jvk*Kn`btM`SSr1Gf+eGhVt|_XjA* zUgGKN%6^Gmn4d%Ph(nkFP>9RZ#WAE}PI3Z}&BVayv3^M*kj3EX>gTe~DWM4f=_Dpv literal 0 HcmV?d00001 diff --git a/coverage_html/index-sort-f.html b/coverage_html/index-sort-f.html new file mode 100644 index 0000000..050a98e --- /dev/null +++ b/coverage_html/index-sort-f.html @@ -0,0 +1,116 @@ + + + + + + + LCOV - _coverage_report.dat + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:_coverage_report.datLines:69.3 %541375
Test Date:2026-01-23 11:51:52Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
/Users/mtl/Development/omnicate-ksniff/pkg/cmd/ +
52.4%52.4%
+
52.4 %349183
/Users/mtl/Development/omnicate-ksniff/utils/ +
100.0%
+
100.0 %3636
runtime/ +
100.0%
+
100.0 %156156
Note: 'Function Coverage' columns elided as function owner is not identified.
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/index-sort-l.html b/coverage_html/index-sort-l.html new file mode 100644 index 0000000..3924dac --- /dev/null +++ b/coverage_html/index-sort-l.html @@ -0,0 +1,116 @@ + + + + + + + LCOV - _coverage_report.dat + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:_coverage_report.datLines:69.3 %541375
Test Date:2026-01-23 11:51:52Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
/Users/mtl/Development/omnicate-ksniff/pkg/cmd/ +
52.4%52.4%
+
52.4 %349183
/Users/mtl/Development/omnicate-ksniff/utils/ +
100.0%
+
100.0 %3636
runtime/ +
100.0%
+
100.0 %156156
Note: 'Function Coverage' columns elided as function owner is not identified.
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/index.html b/coverage_html/index.html new file mode 100644 index 0000000..6aeb064 --- /dev/null +++ b/coverage_html/index.html @@ -0,0 +1,116 @@ + + + + + + + LCOV - _coverage_report.dat + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:_coverage_report.datLines:69.3 %541375
Test Date:2026-01-23 11:51:52Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
/Users/mtl/Development/omnicate-ksniff/pkg/cmd/ +
52.4%52.4%
+
52.4 %349183
/Users/mtl/Development/omnicate-ksniff/utils/ +
100.0%
+
100.0 %3636
runtime/ +
100.0%
+
100.0 %156156
Note: 'Function Coverage' columns elided as function owner is not identified.
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/ruby.png b/coverage_html/ruby.png new file mode 100644 index 0000000000000000000000000000000000000000..991b6d4ec9e78be165e3ef757eed1aada287364d GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^FceV#7`HfI^%F z9+AZi4BSE>%y{W;-5;PJOS+@4BLl<6e(pbstUx|nfKQ0)e^Y%R^MdiLxj>4`)5S5Q b;#P73kj=!v_*DHKNFRfztDnm{r-UW|iOwIS literal 0 HcmV?d00001 diff --git a/coverage_html/runtime/containerd.go.gcov.html b/coverage_html/runtime/containerd.go.gcov.html new file mode 100644 index 0000000..c48ad7c --- /dev/null +++ b/coverage_html/runtime/containerd.go.gcov.html @@ -0,0 +1,145 @@ + + + + + + + LCOV - _coverage_report.dat - runtime/containerd.go + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - runtime - containerd.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %4747
Test Date:2026-01-23 11:51:52Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : package runtime
+       2              : 
+       3              : import (
+       4              :         "fmt"
+       5              :         "ksniff/utils"
+       6              : )
+       7              : 
+       8              : type ContainerdBridge struct {
+       9              :         tcpdumpContainerName string
+      10              :         socketPath string
+      11              : }
+      12              : 
+      13           10 : func NewContainerdBridge() *ContainerdBridge {
+      14           10 :         return &ContainerdBridge{}
+      15           10 : }
+      16              : 
+      17            1 : func (d ContainerdBridge) NeedsPid() bool {
+      18            1 :         return false
+      19            1 : }
+      20              : 
+      21            1 : func (d ContainerdBridge) BuildInspectCommand(string) []string {
+      22            1 :         panic("Containerd doesn't need this implemented")
+      23              : }
+      24              : 
+      25            1 : func (d ContainerdBridge) ExtractPid(inspection string) (*string, error) {
+      26            1 :         panic("Containerd doesn't need this implemented")
+      27              : }
+      28              : 
+      29            1 : func (d ContainerdBridge) GetDefaultSocketPath() string {
+      30            1 :         return "/run/containerd/containerd.sock"
+      31            1 : }
+      32              : 
+      33            2 : func (d *ContainerdBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
+      34            2 :         d.tcpdumpContainerName = "ksniff-container-" + utils.GenerateRandomString(8)
+      35            2 :         d.socketPath = socketPath
+      36            2 :         tcpdumpCommand := fmt.Sprintf("tcpdump -i %s -U -w - %s", netInterface, filter)
+      37            2 :         shellScript := fmt.Sprintf(`
+      38            2 :     set -ex
+      39            2 :     export CONTAINERD_SOCKET="%s"
+      40            2 :     export CONTAINERD_NAMESPACE="k8s.io"
+      41            2 :     export CONTAINER_RUNTIME_ENDPOINT="unix:///host${CONTAINERD_SOCKET}"
+      42            2 :     export IMAGE_SERVICE_ENDPOINT=${CONTAINER_RUNTIME_ENDPOINT}
+      43            2 :     crictl pull %s >/dev/null
+      44            2 :     netns=$(crictl inspect %s | jq '.info.runtimeSpec.linux.namespaces[] | select(.type == "network") | .path' | tr -d '"')
+      45            2 :     exec chroot /host ctr -a ${CONTAINERD_SOCKET} run --rm --with-ns "network:${netns}" %s %s %s 
+      46            2 :     `, d.socketPath, tcpdumpImage, *containerId, tcpdumpImage, d.tcpdumpContainerName, tcpdumpCommand)
+      47            2 :         command := []string{"/bin/sh", "-c", shellScript}
+      48            2 :         return command
+      49            2 : }
+      50              : 
+      51            1 : func (d *ContainerdBridge) BuildCleanupCommand() []string {
+      52            1 :         shellScript := fmt.Sprintf(`
+      53            1 :     set -ex
+      54            1 :     export CONTAINERD_SOCKET="%s"
+      55            1 :     export CONTAINERD_NAMESPACE="k8s.io"
+      56            1 :     export CONTAINER_ID="%s"
+      57            1 :     chroot /host ctr -a ${CONTAINERD_SOCKET} task kill -s SIGKILL ${CONTAINER_ID}
+      58            1 :     `, d.socketPath, d.tcpdumpContainerName)
+      59            1 :         command := []string{"/bin/sh", "-c", shellScript}
+      60            1 :         return command
+      61            1 : }
+      62              : 
+      63            1 : func (d ContainerdBridge) GetDefaultImage() string {
+      64            1 :         return "docker.io/hamravesh/ksniff-helper:v3"
+      65            1 : }
+      66              : 
+      67            1 : func (d *ContainerdBridge) GetDefaultTCPImage() string {
+      68            1 :         return "docker.io/maintained/tcpdump:latest"
+      69            1 : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/runtime/crio.go.gcov.html b/coverage_html/runtime/crio.go.gcov.html new file mode 100644 index 0000000..ff3a3bf --- /dev/null +++ b/coverage_html/runtime/crio.go.gcov.html @@ -0,0 +1,169 @@ + + + + + + + LCOV - _coverage_report.dat - runtime/crio.go + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - runtime - crio.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %6363
Test Date:2026-01-23 11:51:52Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : package runtime
+       2              : 
+       3              : import (
+       4              :         "encoding/json"
+       5              :         "fmt"
+       6              : 
+       7              :         "github.com/pkg/errors"
+       8              : )
+       9              : 
+      10              : type CrioBridge struct {
+      11              : }
+      12              : 
+      13           20 : func NewCrioBridge() *CrioBridge {
+      14           20 :         return &CrioBridge{}
+      15           20 : }
+      16              : 
+      17            1 : func (c *CrioBridge) NeedsPid() bool {
+      18            1 :         return true
+      19            1 : }
+      20              : 
+      21            1 : func (c *CrioBridge) BuildInspectCommand(containerId string) []string {
+      22            1 :         return []string{"chroot", "/host", "crictl", "inspect",
+      23            1 :                 "--output", "json", containerId}
+      24            1 : }
+      25              : 
+      26           12 : func (c *CrioBridge) ExtractPid(inspection string) (*string, error) {
+      27           12 :         var result map[string]json.RawMessage
+      28           12 :         var pid float64
+      29           12 :         var err error
+      30           12 : 
+      31           12 :         err = json.Unmarshal([]byte(inspection), &result)
+      32           12 :         if err != nil {
+      33            3 :                 return nil, err
+      34            3 :         }
+      35              : 
+      36              :         // CRI-O changes the way it reports PID so we have to by dynamic here
+      37            9 :         if result["pid"] != nil {
+      38            3 :                 pid, err = extractPidCrio117(result)
+      39            3 :                 if err != nil {
+      40            1 :                         return nil, errors.Wrap(err, "error getting container PID from CRI-O")
+      41            1 :                 }
+      42            6 :         } else if result["info"] != nil {
+      43            3 :                 pid, err = extractPidCrio118(result)
+      44            3 :                 if err != nil {
+      45            1 :                         return nil, errors.Wrap(err, "error getting container PID from CRI-O")
+      46            1 :                 }
+      47            3 :         } else {
+      48            3 :                 return nil, errors.New("unable to identify CRI-O version")
+      49            3 :         }
+      50              : 
+      51            4 :         ret := fmt.Sprintf("%.0f", pid)
+      52            4 :         return &ret, nil
+      53              : }
+      54              : 
+      55            1 : func (c *CrioBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
+      56            1 :         return []string{"nsenter", "-n", "-t", *pid, "--", "tcpdump", "-i", netInterface, "-U", "-w", "-", filter}
+      57            1 : }
+      58              : 
+      59            1 : func (c *CrioBridge) BuildCleanupCommand() []string {
+      60            1 :         return nil // No cleanup needed
+      61            1 : }
+      62              : 
+      63            1 : func (c *CrioBridge) GetDefaultImage() string {
+      64            1 :         return "maintained/tcpdump"
+      65            1 : }
+      66              : 
+      67            1 : func (c *CrioBridge) GetDefaultSocketPath() string {
+      68            1 :         return "/var/run/crio/crio.sock"
+      69            1 : }
+      70              : 
+      71              : // CRI-O 1.17 and older have pid as first-level attribute
+      72            3 : func extractPidCrio117(partial map[string]json.RawMessage) (float64, error) {
+      73            3 :         var result float64
+      74            3 :         err := json.Unmarshal(partial["pid"], &result)
+      75            3 :         if err != nil {
+      76            1 :                 return -1, err
+      77            1 :         }
+      78            2 :         return result, nil
+      79              : }
+      80              : 
+      81              : // CRI-O 1.18 and later nest pid under info attribute
+      82            3 : func extractPidCrio118(partial map[string]json.RawMessage) (float64, error) {
+      83            3 :         var result map[string]interface{}
+      84            3 :         err := json.Unmarshal(partial["info"], &result)
+      85            3 :         if err != nil {
+      86            1 :                 return -1, err
+      87            1 :         }
+      88            2 :         return result["pid"].(float64), nil
+      89              : }
+      90              : 
+      91            1 : func (d *CrioBridge) GetDefaultTCPImage() string {
+      92            1 :         return ""
+      93            1 : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/runtime/docker.go.gcov.html b/coverage_html/runtime/docker.go.gcov.html new file mode 100644 index 0000000..e57834d --- /dev/null +++ b/coverage_html/runtime/docker.go.gcov.html @@ -0,0 +1,135 @@ + + + + + + + LCOV - _coverage_report.dat - runtime/docker.go + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - runtime - docker.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %3636
Test Date:2026-01-23 11:51:52Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : package runtime
+       2              : 
+       3              : import (
+       4              :         "fmt"
+       5              : 
+       6              :         "ksniff/utils"
+       7              : )
+       8              : 
+       9              : type DockerBridge struct {
+      10              :         tcpdumpContainerName string
+      11              :         cleanupCommand       []string
+      12              : }
+      13              : 
+      14           14 : func NewDockerBridge() *DockerBridge {
+      15           14 :         return &DockerBridge{}
+      16           14 : }
+      17              : 
+      18            1 : func (d *DockerBridge) NeedsPid() bool {
+      19            1 :         return false
+      20            1 : }
+      21              : 
+      22            2 : func (d *DockerBridge) BuildInspectCommand(string) []string {
+      23            2 :         panic("Docker doesn't need this implemented")
+      24              : }
+      25              : 
+      26            2 : func (d *DockerBridge) ExtractPid(inspection string) (*string, error) {
+      27            2 :         panic("Docker doesn't need this implemented")
+      28              : }
+      29              : 
+      30            4 : func (d *DockerBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
+      31            4 :         d.tcpdumpContainerName = "ksniff-container-" + utils.GenerateRandomString(8)
+      32            4 :         containerNameFlag := fmt.Sprintf("--name=%s", d.tcpdumpContainerName)
+      33            4 : 
+      34            4 :         command := []string{"docker", "--host", "unix://" + socketPath,
+      35            4 :                 "run", "--rm", "--log-driver", "none", containerNameFlag,
+      36            4 :                 fmt.Sprintf("--net=container:%s", *containerId), tcpdumpImage, "-i",
+      37            4 :                 netInterface, "-U", "-w", "-", filter}
+      38            4 : 
+      39            4 :         d.cleanupCommand = []string{"docker", "--host", "unix://" + socketPath,
+      40            4 :                 "rm", "-f", d.tcpdumpContainerName}
+      41            4 : 
+      42            4 :         return command
+      43            4 : }
+      44              : 
+      45            2 : func (d *DockerBridge) BuildCleanupCommand() []string {
+      46            2 :         return d.cleanupCommand
+      47            2 : }
+      48              : 
+      49            1 : func (d *DockerBridge) GetDefaultImage() string {
+      50            1 :         return "docker"
+      51            1 : }
+      52              : 
+      53            3 : func (d *DockerBridge) GetDefaultTCPImage() string {
+      54            3 :         return "maintained/tcpdump"
+      55            3 : }
+      56              : 
+      57            1 : func (d *DockerBridge) GetDefaultSocketPath() string {
+      58            1 :         return "/var/run/docker.sock"
+      59            1 : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/runtime/index-sort-f.html b/coverage_html/runtime/index-sort-f.html new file mode 100644 index 0000000..63f8155 --- /dev/null +++ b/coverage_html/runtime/index-sort-f.html @@ -0,0 +1,125 @@ + + + + + + + LCOV - _coverage_report.dat - runtime + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - runtimeCoverageTotalHit
Test:_coverage_report.datLines:100.0 %156156
Test Date:2026-01-23 11:51:52Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
containerd.go +
100.0%
+
100.0 %4747
crio.go +
100.0%
+
100.0 %6363
docker.go +
100.0%
+
100.0 %3636
runtime.go +
100.0%
+
100.0 %1010
Note: 'Function Coverage' columns elided as function owner is not identified.
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/runtime/index-sort-l.html b/coverage_html/runtime/index-sort-l.html new file mode 100644 index 0000000..c5a57a3 --- /dev/null +++ b/coverage_html/runtime/index-sort-l.html @@ -0,0 +1,125 @@ + + + + + + + LCOV - _coverage_report.dat - runtime + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - runtimeCoverageTotalHit
Test:_coverage_report.datLines:100.0 %156156
Test Date:2026-01-23 11:51:52Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
runtime.go +
100.0%
+
100.0 %1010
docker.go +
100.0%
+
100.0 %3636
containerd.go +
100.0%
+
100.0 %4747
crio.go +
100.0%
+
100.0 %6363
Note: 'Function Coverage' columns elided as function owner is not identified.
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/runtime/index.html b/coverage_html/runtime/index.html new file mode 100644 index 0000000..fe1e59e --- /dev/null +++ b/coverage_html/runtime/index.html @@ -0,0 +1,125 @@ + + + + + + + LCOV - _coverage_report.dat - runtime + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - runtimeCoverageTotalHit
Test:_coverage_report.datLines:100.0 %156156
Test Date:2026-01-23 11:51:52Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
containerd.go +
100.0%
+
100.0 %4747
crio.go +
100.0%
+
100.0 %6363
docker.go +
100.0%
+
100.0 %3636
runtime.go +
100.0%
+
100.0 %1010
Note: 'Function Coverage' columns elided as function owner is not identified.
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/runtime/runtime.go.gcov.html b/coverage_html/runtime/runtime.go.gcov.html new file mode 100644 index 0000000..c07db6b --- /dev/null +++ b/coverage_html/runtime/runtime.go.gcov.html @@ -0,0 +1,109 @@ + + + + + + + LCOV - _coverage_report.dat - runtime/runtime.go + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - runtime - runtime.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %1010
Test Date:2026-01-23 11:51:52Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : package runtime
+       2              : 
+       3              : import "fmt"
+       4              : 
+       5              : var SupportedContainerRuntimes = []string{
+       6              :         "docker",
+       7              :         "cri-o",
+       8              :         "containerd",
+       9              : }
+      10              : 
+      11              : type ContainerRuntimeBridge interface {
+      12              :         NeedsPid() bool
+      13              :         BuildInspectCommand(containerId string) []string
+      14              :         ExtractPid(inspection string) (*string, error)
+      15              :         BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string
+      16              :         BuildCleanupCommand() []string
+      17              :         GetDefaultImage() string
+      18              :         GetDefaultTCPImage() string
+      19              :         GetDefaultSocketPath() string
+      20              : }
+      21              : 
+      22            6 : func NewContainerRuntimeBridge(runtimeName string) ContainerRuntimeBridge {
+      23            6 :         switch runtimeName {
+      24            2 :         case "docker":
+      25            2 :                 return NewDockerBridge()
+      26            1 :         case "cri-o":
+      27            1 :                 return NewCrioBridge()
+      28            2 :         case "containerd":
+      29            2 :                 return NewContainerdBridge()
+      30            1 :         default:
+      31            1 :                 panic(fmt.Sprintf("Unable to build bridge to %s", runtimeName))
+      32              :         }
+      33              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.4-0
+
+ + + diff --git a/coverage_html/snow.png b/coverage_html/snow.png new file mode 100644 index 0000000000000000000000000000000000000000..2cdae107fceec6e7f02ac7acb4a34a82a540caa5 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^MM!lvI6;R0X`wF|Ns97GD8ntt^-nBo-U3d c6}OTTfNUlP#;5A{K>8RwUHx3vIVCg!071?oo&W#< literal 0 HcmV?d00001 diff --git a/coverage_html/updown.png b/coverage_html/updown.png new file mode 100644 index 0000000000000000000000000000000000000000..aa56a238b3e6c435265250f9266cd1b8caba0f20 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^AT}Qd8;}%R+`Ae`*?77*hG?8mPH5^{)z4*}Q$iB}huR`+ literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 48353a2..be432c0 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,10 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/imdario/mergo v0.3.8 // indirect github.com/mitchellh/go-homedir v1.1.0 - github.com/pelletier/go-toml v1.6.0 // indirect github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.3 github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.1.3 - github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.7.0 @@ -25,4 +23,71 @@ require ( k8s.io/client-go v0.20.6 ) -go 1.13 +require ( + cloud.google.com/go v0.54.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.1 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.5 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.0 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect + github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5 // indirect + github.com/emicklei/go-restful v2.11.1+incompatible // indirect + github.com/evanphx/json-patch v4.9.0+incompatible // indirect + github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-logr/logr v0.2.0 // indirect + github.com/go-openapi/jsonpointer v0.19.3 // indirect + github.com/go-openapi/jsonreference v0.19.3 // indirect + github.com/go-openapi/spec v0.19.5 // indirect + github.com/go-openapi/swag v0.19.6 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.4.3 // indirect + github.com/google/btree v1.0.0 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/googleapis/gnostic v0.4.1 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.8 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/json-iterator/go v1.1.10 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/magiconair/properties v1.8.1 // indirect + github.com/mailru/easyjson v0.7.0 // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pelletier/go-toml v1.6.0 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect + golang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect + golang.org/x/text v0.3.4 // indirect + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect + google.golang.org/appengine v1.6.5 // indirect + google.golang.org/protobuf v1.25.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.51.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + k8s.io/klog/v2 v2.4.0 // indirect + k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd // indirect + k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect + sigs.k8s.io/kustomize v2.0.3+incompatible // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.0.3 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) + +go 1.17 diff --git a/kube/BUILD.bazel b/kube/BUILD.bazel new file mode 100644 index 0000000..702c8da --- /dev/null +++ b/kube/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "kube", + srcs = [ + "kubernetes_api_service.go", + "ops.go", + "tar.go", + ], + importpath = "ksniff/kube", + visibility = ["//visibility:public"], + deps = [ + "//pkg/service/sniffer/runtime", + "//utils", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + "@io_k8s_api//core/v1:core", + "@io_k8s_apimachinery//pkg/api/resource", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_client_go//kubernetes", + "@io_k8s_client_go//kubernetes/scheme", + "@io_k8s_client_go//rest", + "@io_k8s_client_go//tools/remotecommand", + "@io_k8s_client_go//util/exec", + ], +) diff --git a/kube/kubernetes_api_service.go b/kube/kubernetes_api_service.go index 1ed14a6..8e7ed51 100644 --- a/kube/kubernetes_api_service.go +++ b/kube/kubernetes_api_service.go @@ -31,12 +31,12 @@ type KubernetesApiService interface { } type KubernetesApiServiceImpl struct { - clientset *kubernetes.Clientset + clientset kubernetes.Interface restConfig *rest.Config targetNamespace string } -func NewKubernetesApiService(clientset *kubernetes.Clientset, +func NewKubernetesApiService(clientset kubernetes.Interface, restConfig *rest.Config, targetNamespace string) KubernetesApiService { return &KubernetesApiServiceImpl{clientset: clientset, diff --git a/kube/ops.go b/kube/ops.go index 27326fa..dc4bb5c 100644 --- a/kube/ops.go +++ b/kube/ops.go @@ -16,7 +16,7 @@ import ( ) type KubeRequest struct { - Clientset *kubernetes.Clientset + Clientset kubernetes.Interface RestConfig *rest.Config Namespace string Pod string diff --git a/pkg/cmd/BUILD.bazel b/pkg/cmd/BUILD.bazel new file mode 100644 index 0000000..9067d10 --- /dev/null +++ b/pkg/cmd/BUILD.bazel @@ -0,0 +1,46 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "cmd", + srcs = ["sniff.go"], + importpath = "ksniff/pkg/cmd", + visibility = ["//visibility:public"], + deps = [ + "//kube", + "//pkg/config", + "//pkg/service/sniffer", + "//pkg/service/sniffer/runtime", + "@com_github_mitchellh_go_homedir//:go-homedir", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_viper//:viper", + "@io_k8s_api//core/v1:core", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_cli_runtime//pkg/genericclioptions", + "@io_k8s_client_go//kubernetes", + "@io_k8s_client_go//plugin/pkg/client/auth/azure", + "@io_k8s_client_go//plugin/pkg/client/auth/gcp", + "@io_k8s_client_go//plugin/pkg/client/auth/oidc", + "@io_k8s_client_go//rest", + "@io_k8s_client_go//tools/clientcmd", + "@io_k8s_client_go//tools/clientcmd/api", + ], +) + +go_test( + name = "cmd_test", + srcs = ["sniff_test.go"], + embed = [":cmd"], + deps = [ + "//pkg/config", + "@com_github_spf13_cobra//:cobra", + "@com_github_stretchr_testify//assert", + "@io_k8s_api//core/v1:core", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_cli_runtime//pkg/genericclioptions", + "@io_k8s_client_go//kubernetes/fake", + "@io_k8s_client_go//rest", + "@io_k8s_client_go//tools/clientcmd/api", + ], +) diff --git a/pkg/cmd/sniff.go b/pkg/cmd/sniff.go index c79db3e..8b9fcce 100644 --- a/pkg/cmd/sniff.go +++ b/pkg/cmd/sniff.go @@ -48,7 +48,7 @@ var tcpdumpLocalBinaryPathLookupList []string type Ksniff struct { configFlags *genericclioptions.ConfigFlags resultingContext *api.Context - clientset *kubernetes.Clientset + clientset kubernetes.Interface restConfig *rest.Config rawConfig api.Config settings *config.KsniffSettings @@ -159,7 +159,22 @@ func NewCmdSniff(streams genericclioptions.IOStreams) *cobra.Command { } func (o *Ksniff) Complete(cmd *cobra.Command, args []string) error { + // Validate and populate basic settings + if err := o.completeBasicSettings(cmd, args); err != nil { + return err + } + + // Load Kubernetes configuration + if err := o.loadKubeConfig(); err != nil { + return err + } + + return nil +} +// completeBasicSettings validates arguments and populates settings from viper +// This method doesn't require Kubernetes configuration and can be tested independently +func (o *Ksniff) completeBasicSettings(cmd *cobra.Command, args []string) error { if len(args) < minimumNumberOfArguments { _ = cmd.Usage() return errors.New("not enough arguments") @@ -188,18 +203,25 @@ func (o *Ksniff) Complete(cmd *cobra.Command, args []string) error { o.settings.UseDefaultSocketPath = !viper.IsSet("socket") o.settings.UserSpecifiedServiceAccount = viper.GetString("serviceaccount") - var err error - if o.settings.UserSpecifiedVerboseMode { log.Info("running in verbose mode") log.SetLevel(log.DebugLevel) } + var err error tcpdumpLocalBinaryPathLookupList, err = o.buildTcpdumpBinaryPathLookupList() if err != nil { return err } + return nil +} + +// loadKubeConfig loads the Kubernetes configuration and initializes the clientset +// This method is separated to allow for easier testing and mocking +func (o *Ksniff) loadKubeConfig() error { + var err error + o.rawConfig, err = o.configFlags.ToRawKubeConfigLoader().RawConfig() if err != nil { return err diff --git a/pkg/cmd/sniff_test.go b/pkg/cmd/sniff_test.go index 2872c4b..f7172ec 100644 --- a/pkg/cmd/sniff_test.go +++ b/pkg/cmd/sniff_test.go @@ -1,14 +1,19 @@ package cmd import ( + "context" "ksniff/pkg/config" "strings" + "testing" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" - - "testing" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd/api" ) func TestComplete_NotEnoughArguments(t *testing.T) { @@ -49,9 +54,452 @@ func TestComplete_PodNameSpecified(t *testing.T) { var commands []string // when - err := sniff.Complete(cmd, append(commands, "pod-name")) + // Only test the basic settings completion, not the kubeconfig loading + err := sniff.completeBasicSettings(cmd, append(commands, "pod-name")) // then assert.Nil(t, err) assert.Equal(t, "pod-name", settings.UserSpecifiedPodName) } + +func TestCompleteBasicSettings_WithMultipleFlags(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set some flags + _ = cmd.Flags().Set("namespace", "test-namespace") + _ = cmd.Flags().Set("container", "test-container") + _ = cmd.Flags().Set("interface", "eth0") + _ = cmd.Flags().Set("filter", "port 80") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.Equal(t, "test-pod", settings.UserSpecifiedPodName) + assert.Equal(t, "test-namespace", settings.UserSpecifiedNamespace) + assert.Equal(t, "test-container", settings.UserSpecifiedContainer) + assert.Equal(t, "eth0", settings.UserSpecifiedInterface) + assert.Equal(t, "port 80", settings.UserSpecifiedFilter) +} + +func TestCompleteBasicSettings_WithPrivilegedMode(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set privileged mode flag + _ = cmd.Flags().Set("privileged", "true") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.True(t, settings.UserSpecifiedPrivilegedMode) +} + +func TestCompleteBasicSettings_WithOutputFile(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set output file flag + _ = cmd.Flags().Set("output-file", "/tmp/capture.pcap") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.Equal(t, "/tmp/capture.pcap", settings.UserSpecifiedOutputFile) +} + +func TestCompleteBasicSettings_WithVerboseMode(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set verbose mode flag + _ = cmd.Flags().Set("verbose", "true") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.True(t, settings.UserSpecifiedVerboseMode) +} + +func TestCompleteBasicSettings_WithImage(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set image flag + _ = cmd.Flags().Set("image", "custom/tcpdump:latest") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.Equal(t, "custom/tcpdump:latest", settings.Image) + assert.False(t, settings.UseDefaultImage) +} + +func TestCompleteBasicSettings_WithTcpdumpImage(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set tcpdump-image flag + _ = cmd.Flags().Set("tcpdump-image", "custom/tcpdump:v2") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.Equal(t, "custom/tcpdump:v2", settings.TCPDumpImage) + assert.False(t, settings.UseDefaultTCPDumpImage) +} + +func TestCompleteBasicSettings_WithSocket(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set socket flag + _ = cmd.Flags().Set("socket", "/custom/path/docker.sock") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.Equal(t, "/custom/path/docker.sock", settings.SocketPath) + assert.False(t, settings.UseDefaultSocketPath) +} + +func TestCompleteBasicSettings_WithServiceAccount(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set serviceaccount flag + _ = cmd.Flags().Set("serviceaccount", "custom-sa") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.Equal(t, "custom-sa", settings.UserSpecifiedServiceAccount) +} + +func TestCompleteBasicSettings_WithLocalTcpdump(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set local-tcpdump-path flag + _ = cmd.Flags().Set("local-tcpdump-path", "/usr/bin/tcpdump") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.Equal(t, "/usr/bin/tcpdump", settings.UserSpecifiedLocalTcpdumpPath) +} + +// Tests with mocked Kubernetes client + +func TestValidate_WithRunningPod(t *testing.T) { + // Create fake clientset + fakeClientset := fake.NewSimpleClientset() + + // Create test pod + testPod := &corev1.Pod{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + NodeName: "test-node", + Containers: []corev1.Container{ + {Name: "test-container"}, + }, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + ContainerStatuses: []corev1.ContainerStatus{ + { + Name: "test-container", + ContainerID: "docker://abc123", + }, + }, + }, + } + + // Add pod to fake clientset + _, err := fakeClientset.CoreV1().Pods("default").Create(context.TODO(), testPod, v1.CreateOptions{}) + assert.Nil(t, err) + + // Create Ksniff instance with mocked clientset + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + settings.UserSpecifiedPodName = "test-pod" + settings.UserSpecifiedContainer = "test-container" + settings.UserSpecifiedPrivilegedMode = true // Use privileged mode to avoid tcpdump binary check + + ksniff := NewKsniff(settings) + ksniff.clientset = fakeClientset + ksniff.rawConfig = api.Config{CurrentContext: "test-context"} + ksniff.resultingContext = &api.Context{Namespace: "default"} + ksniff.restConfig = &rest.Config{} + + // Test Validate + err = ksniff.Validate() + assert.Nil(t, err) + assert.Equal(t, "test-node", settings.DetectedPodNodeName) + assert.Equal(t, "docker", settings.DetectedContainerRuntime) + assert.Equal(t, "abc123", settings.DetectedContainerId) +} + +func TestValidate_PodNotFound(t *testing.T) { + // Create fake clientset with no pods + fakeClientset := fake.NewSimpleClientset() + + // Create Ksniff instance with mocked clientset + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + settings.UserSpecifiedPodName = "nonexistent-pod" + settings.UserSpecifiedPrivilegedMode = true // Use privileged mode to avoid tcpdump binary check + + ksniff := NewKsniff(settings) + ksniff.clientset = fakeClientset + ksniff.rawConfig = api.Config{CurrentContext: "test-context"} + ksniff.resultingContext = &api.Context{Namespace: "default"} + ksniff.restConfig = &rest.Config{} + + // Test Validate + err := ksniff.Validate() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "not found") +} + +func TestValidate_PodInFailedState(t *testing.T) { + // Create fake clientset + fakeClientset := fake.NewSimpleClientset() + + // Create test pod in failed state + testPod := &corev1.Pod{ + ObjectMeta: v1.ObjectMeta{ + Name: "failed-pod", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + NodeName: "test-node", + Containers: []corev1.Container{ + {Name: "test-container"}, + }, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodFailed, + }, + } + + // Add pod to fake clientset + _, err := fakeClientset.CoreV1().Pods("default").Create(context.TODO(), testPod, v1.CreateOptions{}) + assert.Nil(t, err) + + // Create Ksniff instance with mocked clientset + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + settings.UserSpecifiedPodName = "failed-pod" + settings.UserSpecifiedPrivilegedMode = true // Use privileged mode to avoid tcpdump binary check + + ksniff := NewKsniff(settings) + ksniff.clientset = fakeClientset + ksniff.rawConfig = api.Config{CurrentContext: "test-context"} + ksniff.resultingContext = &api.Context{Namespace: "default"} + ksniff.restConfig = &rest.Config{} + + // Test Validate + err = ksniff.Validate() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "cannot sniff on a container in a completed pod") +} + +func TestValidate_PodWithNoContainers(t *testing.T) { + // Create fake clientset + fakeClientset := fake.NewSimpleClientset() + + // Create test pod with no containers + testPod := &corev1.Pod{ + ObjectMeta: v1.ObjectMeta{ + Name: "empty-pod", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + NodeName: "test-node", + Containers: []corev1.Container{}, // No containers + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + } + + // Add pod to fake clientset + _, err := fakeClientset.CoreV1().Pods("default").Create(context.TODO(), testPod, v1.CreateOptions{}) + assert.Nil(t, err) + + // Create Ksniff instance with mocked clientset + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + settings.UserSpecifiedPodName = "empty-pod" + settings.UserSpecifiedPrivilegedMode = true + + ksniff := NewKsniff(settings) + ksniff.clientset = fakeClientset + ksniff.rawConfig = api.Config{CurrentContext: "test-context"} + ksniff.resultingContext = &api.Context{Namespace: "default"} + ksniff.restConfig = &rest.Config{} + + // Test Validate + err = ksniff.Validate() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "no containers in specified pod") +} + +func TestValidate_AutoSelectFirstContainer(t *testing.T) { + // Create fake clientset + fakeClientset := fake.NewSimpleClientset() + + // Create test pod with multiple containers + testPod := &corev1.Pod{ + ObjectMeta: v1.ObjectMeta{ + Name: "multi-container-pod", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + NodeName: "test-node", + Containers: []corev1.Container{ + {Name: "first-container"}, + {Name: "second-container"}, + }, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + ContainerStatuses: []corev1.ContainerStatus{ + { + Name: "first-container", + ContainerID: "containerd://xyz789", + }, + { + Name: "second-container", + ContainerID: "containerd://abc123", + }, + }, + }, + } + + // Add pod to fake clientset + _, err := fakeClientset.CoreV1().Pods("default").Create(context.TODO(), testPod, v1.CreateOptions{}) + assert.Nil(t, err) + + // Create Ksniff instance with mocked clientset (no container specified) + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + settings.UserSpecifiedPodName = "multi-container-pod" + settings.UserSpecifiedPrivilegedMode = true + // Don't specify container - should auto-select first one + + ksniff := NewKsniff(settings) + ksniff.clientset = fakeClientset + ksniff.rawConfig = api.Config{CurrentContext: "test-context"} + ksniff.resultingContext = &api.Context{Namespace: "default"} + ksniff.restConfig = &rest.Config{} + + // Test Validate + err = ksniff.Validate() + assert.Nil(t, err) + assert.Equal(t, "first-container", settings.UserSpecifiedContainer) + assert.Equal(t, "containerd", settings.DetectedContainerRuntime) + assert.Equal(t, "xyz789", settings.DetectedContainerId) +} + +func TestValidate_ContainerNotFound(t *testing.T) { + // Create fake clientset + fakeClientset := fake.NewSimpleClientset() + + // Create test pod + testPod := &corev1.Pod{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + NodeName: "test-node", + Containers: []corev1.Container{ + {Name: "existing-container"}, + }, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + ContainerStatuses: []corev1.ContainerStatus{ + { + Name: "existing-container", + ContainerID: "docker://abc123", + }, + }, + }, + } + + // Add pod to fake clientset + _, err := fakeClientset.CoreV1().Pods("default").Create(context.TODO(), testPod, v1.CreateOptions{}) + assert.Nil(t, err) + + // Create Ksniff instance with mocked clientset + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + settings.UserSpecifiedPodName = "test-pod" + settings.UserSpecifiedContainer = "nonexistent-container" // Container doesn't exist + settings.UserSpecifiedPrivilegedMode = true + + ksniff := NewKsniff(settings) + ksniff.clientset = fakeClientset + ksniff.rawConfig = api.Config{CurrentContext: "test-context"} + ksniff.resultingContext = &api.Context{Namespace: "default"} + ksniff.restConfig = &rest.Config{} + + // Test Validate + err = ksniff.Validate() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "couldn't find container") +} diff --git a/pkg/config/BUILD.bazel b/pkg/config/BUILD.bazel new file mode 100644 index 0000000..7b8fd3a --- /dev/null +++ b/pkg/config/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "config", + srcs = ["settings.go"], + importpath = "ksniff/pkg/config", + visibility = ["//visibility:public"], + deps = ["@io_k8s_cli_runtime//pkg/genericclioptions"], +) diff --git a/pkg/service/sniffer/BUILD.bazel b/pkg/service/sniffer/BUILD.bazel new file mode 100644 index 0000000..6800c25 --- /dev/null +++ b/pkg/service/sniffer/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "sniffer", + srcs = [ + "privileged_pod_sniffer_service.go", + "sniffer_service.go", + "static_tcpdump_sniffer_service.go", + ], + importpath = "ksniff/pkg/service/sniffer", + visibility = ["//visibility:public"], + deps = [ + "//kube", + "//pkg/config", + "//pkg/service/sniffer/runtime", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + "@io_k8s_api//core/v1:core", + ], +) diff --git a/pkg/service/sniffer/runtime/BUILD.bazel b/pkg/service/sniffer/runtime/BUILD.bazel new file mode 100644 index 0000000..7be08fc --- /dev/null +++ b/pkg/service/sniffer/runtime/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "runtime", + srcs = [ + "containerd.go", + "crio.go", + "docker.go", + "runtime.go", + ], + importpath = "ksniff/pkg/service/sniffer/runtime", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + ], +) + +go_test( + name = "runtime_test", + srcs = [ + "crio_test.go", + "docker_test.go", + "runtime_test.go", + ], + embed = [":runtime"], + deps = ["@com_github_stretchr_testify//assert"], +) diff --git a/pkg/service/sniffer/runtime/runtime_test.go b/pkg/service/sniffer/runtime/runtime_test.go index e3cd75b..c8b1269 100644 --- a/pkg/service/sniffer/runtime/runtime_test.go +++ b/pkg/service/sniffer/runtime/runtime_test.go @@ -16,6 +16,269 @@ func TestNewContainerRuntimeBridge_Crio(t *testing.T) { assert.IsType(t, &CrioBridge{}, bridge) } +func TestNewContainerRuntimeBridge_Containerd(t *testing.T) { + bridge := NewContainerRuntimeBridge("containerd") + assert.IsType(t, &ContainerdBridge{}, bridge) +} + func TestNewContainerRuntimeBridge_Invalid(t *testing.T) { assert.Panics(t, func() { NewContainerRuntimeBridge("i-do-not-exist") }) } + +// Docker Bridge Tests +func TestDockerBridge_NeedsPid(t *testing.T) { + bridge := NewDockerBridge() + assert.False(t, bridge.NeedsPid()) +} + +func TestDockerBridge_GetDefaultImage(t *testing.T) { + bridge := NewDockerBridge() + assert.Equal(t, "docker", bridge.GetDefaultImage()) +} + +func TestDockerBridge_GetDefaultTCPImage(t *testing.T) { + bridge := NewDockerBridge() + assert.Equal(t, "maintained/tcpdump", bridge.GetDefaultTCPImage()) +} + +func TestDockerBridge_GetDefaultSocketPath(t *testing.T) { + bridge := NewDockerBridge() + assert.Equal(t, "/var/run/docker.sock", bridge.GetDefaultSocketPath()) +} + +func TestDockerBridge_BuildInspectCommand_Panics(t *testing.T) { + bridge := NewDockerBridge() + assert.Panics(t, func() { bridge.BuildInspectCommand("test-container") }) +} + +func TestDockerBridge_ExtractPid_Panics(t *testing.T) { + bridge := NewDockerBridge() + assert.Panics(t, func() { bridge.ExtractPid("test-inspection") }) +} + +func TestDockerBridge_BuildTcpdumpCommand(t *testing.T) { + bridge := NewDockerBridge() + containerId := "test-container-id" + netInterface := "eth0" + filter := "port 80" + socketPath := "/var/run/docker.sock" + tcpdumpImage := "maintained/tcpdump" + + command := bridge.BuildTcpdumpCommand(&containerId, netInterface, filter, nil, socketPath, tcpdumpImage) + + assert.NotNil(t, command) + assert.Contains(t, command, "docker") + assert.Contains(t, command, "--host") + assert.Contains(t, command, "unix:///var/run/docker.sock") + assert.Contains(t, command, "run") + assert.Contains(t, command, "--rm") + assert.Contains(t, command, "--net=container:test-container-id") + assert.Contains(t, command, tcpdumpImage) + assert.Contains(t, command, "-i") + assert.Contains(t, command, netInterface) + assert.Contains(t, command, filter) +} + +func TestDockerBridge_BuildCleanupCommand(t *testing.T) { + bridge := NewDockerBridge() + containerId := "test-container-id" + socketPath := "/var/run/docker.sock" + + // First build tcpdump command to initialize cleanup command + bridge.BuildTcpdumpCommand(&containerId, "eth0", "port 80", nil, socketPath, "maintained/tcpdump") + + cleanupCommand := bridge.BuildCleanupCommand() + + assert.NotNil(t, cleanupCommand) + assert.Contains(t, cleanupCommand, "docker") + assert.Contains(t, cleanupCommand, "--host") + assert.Contains(t, cleanupCommand, "unix:///var/run/docker.sock") + assert.Contains(t, cleanupCommand, "rm") + assert.Contains(t, cleanupCommand, "-f") +} + +// CRI-O Bridge Tests +func TestCrioBridge_NeedsPid(t *testing.T) { + bridge := NewCrioBridge() + assert.True(t, bridge.NeedsPid()) +} + +func TestCrioBridge_GetDefaultImage(t *testing.T) { + bridge := NewCrioBridge() + assert.Equal(t, "maintained/tcpdump", bridge.GetDefaultImage()) +} + +func TestCrioBridge_GetDefaultTCPImage(t *testing.T) { + bridge := NewCrioBridge() + assert.Equal(t, "", bridge.GetDefaultTCPImage()) +} + +func TestCrioBridge_GetDefaultSocketPath(t *testing.T) { + bridge := NewCrioBridge() + assert.Equal(t, "/var/run/crio/crio.sock", bridge.GetDefaultSocketPath()) +} + +func TestCrioBridge_BuildInspectCommand(t *testing.T) { + bridge := NewCrioBridge() + containerId := "test-container-id" + + command := bridge.BuildInspectCommand(containerId) + + assert.NotNil(t, command) + assert.Equal(t, []string{"chroot", "/host", "crictl", "inspect", "--output", "json", containerId}, command) +} + +func TestCrioBridge_ExtractPid_Crio117(t *testing.T) { + bridge := NewCrioBridge() + inspection := `{"pid": 12345}` + + pid, err := bridge.ExtractPid(inspection) + + assert.Nil(t, err) + assert.NotNil(t, pid) + assert.Equal(t, "12345", *pid) +} + +func TestCrioBridge_ExtractPid_Crio118(t *testing.T) { + bridge := NewCrioBridge() + inspection := `{"info": {"pid": 67890}}` + + pid, err := bridge.ExtractPid(inspection) + + assert.Nil(t, err) + assert.NotNil(t, pid) + assert.Equal(t, "67890", *pid) +} + +func TestCrioBridge_ExtractPid_InvalidJSON(t *testing.T) { + bridge := NewCrioBridge() + inspection := `invalid json` + + pid, err := bridge.ExtractPid(inspection) + + assert.NotNil(t, err) + assert.Nil(t, pid) +} + +func TestCrioBridge_ExtractPid_UnknownVersion(t *testing.T) { + bridge := NewCrioBridge() + inspection := `{"unknown": "field"}` + + pid, err := bridge.ExtractPid(inspection) + + assert.NotNil(t, err) + assert.Nil(t, pid) + assert.Contains(t, err.Error(), "unable to identify CRI-O version") +} + +func TestCrioBridge_ExtractPid_Crio117_InvalidPid(t *testing.T) { + bridge := NewCrioBridge() + inspection := `{"pid": "not-a-number"}` + + pid, err := bridge.ExtractPid(inspection) + + assert.NotNil(t, err) + assert.Nil(t, pid) +} + +func TestCrioBridge_ExtractPid_Crio118_InvalidInfo(t *testing.T) { + bridge := NewCrioBridge() + inspection := `{"info": "not-an-object"}` + + pid, err := bridge.ExtractPid(inspection) + + assert.NotNil(t, err) + assert.Nil(t, pid) +} + +func TestCrioBridge_BuildTcpdumpCommand(t *testing.T) { + bridge := NewCrioBridge() + containerId := "test-container-id" + netInterface := "eth0" + filter := "port 80" + pid := "12345" + + command := bridge.BuildTcpdumpCommand(&containerId, netInterface, filter, &pid, "", "") + + assert.NotNil(t, command) + assert.Equal(t, []string{"nsenter", "-n", "-t", "12345", "--", "tcpdump", "-i", "eth0", "-U", "-w", "-", "port 80"}, command) +} + +func TestCrioBridge_BuildCleanupCommand(t *testing.T) { + bridge := NewCrioBridge() + + cleanupCommand := bridge.BuildCleanupCommand() + + assert.Nil(t, cleanupCommand) +} + +// Containerd Bridge Tests +func TestContainerdBridge_NeedsPid(t *testing.T) { + bridge := NewContainerdBridge() + assert.False(t, bridge.NeedsPid()) +} + +func TestContainerdBridge_GetDefaultImage(t *testing.T) { + bridge := NewContainerdBridge() + assert.Equal(t, "docker.io/hamravesh/ksniff-helper:v3", bridge.GetDefaultImage()) +} + +func TestContainerdBridge_GetDefaultTCPImage(t *testing.T) { + bridge := NewContainerdBridge() + assert.Equal(t, "docker.io/maintained/tcpdump:latest", bridge.GetDefaultTCPImage()) +} + +func TestContainerdBridge_GetDefaultSocketPath(t *testing.T) { + bridge := NewContainerdBridge() + assert.Equal(t, "/run/containerd/containerd.sock", bridge.GetDefaultSocketPath()) +} + +func TestContainerdBridge_BuildInspectCommand_Panics(t *testing.T) { + bridge := NewContainerdBridge() + assert.Panics(t, func() { bridge.BuildInspectCommand("test-container") }) +} + +func TestContainerdBridge_ExtractPid_Panics(t *testing.T) { + bridge := NewContainerdBridge() + assert.Panics(t, func() { bridge.ExtractPid("test-inspection") }) +} + +func TestContainerdBridge_BuildTcpdumpCommand(t *testing.T) { + bridge := NewContainerdBridge() + containerId := "test-container-id" + netInterface := "eth0" + filter := "port 80" + socketPath := "/run/containerd/containerd.sock" + tcpdumpImage := "docker.io/maintained/tcpdump:latest" + + command := bridge.BuildTcpdumpCommand(&containerId, netInterface, filter, nil, socketPath, tcpdumpImage) + + assert.NotNil(t, command) + assert.Equal(t, 3, len(command)) + assert.Equal(t, "/bin/sh", command[0]) + assert.Equal(t, "-c", command[1]) + assert.Contains(t, command[2], "crictl pull") + assert.Contains(t, command[2], tcpdumpImage) + assert.Contains(t, command[2], containerId) + assert.Contains(t, command[2], socketPath) + assert.Contains(t, command[2], "tcpdump -i eth0 -U -w - port 80") +} + +func TestContainerdBridge_BuildCleanupCommand(t *testing.T) { + bridge := NewContainerdBridge() + containerId := "test-container-id" + socketPath := "/run/containerd/containerd.sock" + + // First build tcpdump command to initialize internal state + bridge.BuildTcpdumpCommand(&containerId, "eth0", "port 80", nil, socketPath, "docker.io/maintained/tcpdump:latest") + + cleanupCommand := bridge.BuildCleanupCommand() + + assert.NotNil(t, cleanupCommand) + assert.Equal(t, 3, len(cleanupCommand)) + assert.Equal(t, "/bin/sh", cleanupCommand[0]) + assert.Equal(t, "-c", cleanupCommand[1]) + assert.Contains(t, cleanupCommand[2], "ctr -a") + assert.Contains(t, cleanupCommand[2], socketPath) + assert.Contains(t, cleanupCommand[2], "task kill -s SIGKILL") +} diff --git a/tcpdump.BUILD b/tcpdump.BUILD new file mode 100644 index 0000000..7d6cb73 --- /dev/null +++ b/tcpdump.BUILD @@ -0,0 +1,49 @@ +filegroup( + name = "all_srcs", + srcs = glob(["**"]), + visibility = ["//visibility:public"], +) + +# Build tcpdump using a genrule +# Note: This is designed to build a static binary on Linux. +# On macOS, this may not work due to limitations with static linking and configure script compatibility. +# For production use on Linux, this will create a statically-linked tcpdump binary. +genrule( + name = "tcpdump", + srcs = [":all_srcs"], + outs = ["tcpdump_bin"], + cmd = select({ + "@platforms//os:linux": """ + set -e + # Create a temporary build directory + BUILD_DIR=$$(mktemp -d) + trap "rm -rf $$BUILD_DIR" EXIT + + # Copy all sources to build directory + SRC_DIR=$$(dirname $$(echo $(SRCS) | cut -d' ' -f1)) + cp -r $$SRC_DIR/* $$BUILD_DIR/ + cd $$BUILD_DIR + + # Configure and build with static linking + CFLAGS=-static ./configure --without-crypto + make + + # Copy the binary to the output + cp tcpdump $(location tcpdump_bin) + """, + "//conditions:default": """ + # On non-Linux platforms, create a placeholder script + # that explains how to build static-tcpdump + cat > $(location tcpdump_bin) << 'EOF' +#!/bin/bash +echo "Error: static-tcpdump must be built on Linux" +echo "To build static-tcpdump:" +echo " 1. Run this build on a Linux machine or in a Linux container" +echo " 2. Or use the Makefile target: make static-tcpdump" +exit 1 +EOF + chmod +x $(location tcpdump_bin) + """, + }), + visibility = ["//visibility:public"], +) diff --git a/utils/BUILD.bazel b/utils/BUILD.bazel new file mode 100644 index 0000000..59c7140 --- /dev/null +++ b/utils/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "utils", + srcs = ["utils.go"], + importpath = "ksniff/utils", + visibility = ["//visibility:public"], +) + +go_test( + name = "utils_test", + srcs = ["utils_test.go"], + embed = [":utils"], + deps = ["@com_github_stretchr_testify//assert"], +) From 764840529fdfc5191227ee5066dfa313071aadc9 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Fri, 23 Jan 2026 11:59:17 +0100 Subject: [PATCH 2/5] Update to bazel and add tests --- .gitignore | 1 + .../omnicate-ksniff/pkg/cmd/index.html | 98 -- .../pkg/cmd/sniff.go.gcov.html | 563 --------- .../omnicate-ksniff/utils/index.html | 98 -- .../omnicate-ksniff/utils/utils.go.gcov.html | 128 -- coverage_html/amber.png | Bin 141 -> 0 bytes coverage_html/cmd_line | 1 - coverage_html/emerald.png | Bin 141 -> 0 bytes coverage_html/gcov.css | 1125 ----------------- coverage_html/glass.png | Bin 167 -> 0 bytes coverage_html/index-sort-f.html | 116 -- coverage_html/index-sort-l.html | 116 -- coverage_html/index.html | 116 -- coverage_html/ruby.png | Bin 141 -> 0 bytes coverage_html/runtime/containerd.go.gcov.html | 145 --- coverage_html/runtime/crio.go.gcov.html | 169 --- coverage_html/runtime/docker.go.gcov.html | 135 -- coverage_html/runtime/index-sort-f.html | 125 -- coverage_html/runtime/index-sort-l.html | 125 -- coverage_html/runtime/index.html | 125 -- coverage_html/runtime/runtime.go.gcov.html | 109 -- coverage_html/snow.png | Bin 141 -> 0 bytes coverage_html/updown.png | Bin 117 -> 0 bytes 23 files changed, 1 insertion(+), 3294 deletions(-) delete mode 100644 coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/index.html delete mode 100644 coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go.gcov.html delete mode 100644 coverage_html/Users/mtl/Development/omnicate-ksniff/utils/index.html delete mode 100644 coverage_html/Users/mtl/Development/omnicate-ksniff/utils/utils.go.gcov.html delete mode 100644 coverage_html/amber.png delete mode 100644 coverage_html/cmd_line delete mode 100644 coverage_html/emerald.png delete mode 100644 coverage_html/gcov.css delete mode 100644 coverage_html/glass.png delete mode 100644 coverage_html/index-sort-f.html delete mode 100644 coverage_html/index-sort-l.html delete mode 100644 coverage_html/index.html delete mode 100644 coverage_html/ruby.png delete mode 100644 coverage_html/runtime/containerd.go.gcov.html delete mode 100644 coverage_html/runtime/crio.go.gcov.html delete mode 100644 coverage_html/runtime/docker.go.gcov.html delete mode 100644 coverage_html/runtime/index-sort-f.html delete mode 100644 coverage_html/runtime/index-sort-l.html delete mode 100644 coverage_html/runtime/index.html delete mode 100644 coverage_html/runtime/runtime.go.gcov.html delete mode 100644 coverage_html/snow.png delete mode 100644 coverage_html/updown.png diff --git a/.gitignore b/.gitignore index 54162a8..3b8743e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ # Build and test process .coverage +coverage_html # bzlmod lockfile which is useless: # "Importantly, it only includes dependencies that are included in the current invocation of the build" diff --git a/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/index.html b/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/index.html deleted file mode 100644 index de097d2..0000000 --- a/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/index.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - Users/mtl/Development/omnicate-ksniff/pkg/cmd - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - /Users/mtl/Development/omnicate-ksniff/pkg/cmdCoverageTotalHit
Test:_coverage_report.datLines:52.4 %349183
Test Date:2026-01-23 11:51:52Functions:-00
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
sniff.go -
52.4%52.4%
-
52.4 %349183
Note: 'Function Coverage' columns elided as function owner is not identified.
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go.gcov.html b/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go.gcov.html deleted file mode 100644 index 52058a8..0000000 --- a/coverage_html/Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go.gcov.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - Users/mtl/Development/omnicate-ksniff/pkg/cmd/sniff.go - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - /Users/mtl/Development/omnicate-ksniff/pkg/cmd - sniff.goCoverageTotalHit
Test:_coverage_report.datLines:52.4 %349183
Test Date:2026-01-23 11:51:52Functions:-00
-
- - - - - - - - -

-
            Line data    Source code
-
-       1              : package cmd
-       2              : 
-       3              : import (
-       4              :         "context"
-       5              :         "fmt"
-       6              :         "io"
-       7              :         "os"
-       8              :         "os/exec"
-       9              :         "os/signal"
-      10              :         "path/filepath"
-      11              :         "strings"
-      12              :         "syscall"
-      13              :         "time"
-      14              : 
-      15              :         "ksniff/kube"
-      16              :         "ksniff/pkg/config"
-      17              :         "ksniff/pkg/service/sniffer"
-      18              :         "ksniff/pkg/service/sniffer/runtime"
-      19              : 
-      20              :         "github.com/mitchellh/go-homedir"
-      21              :         "github.com/pkg/errors"
-      22              :         log "github.com/sirupsen/logrus"
-      23              :         "github.com/spf13/cobra"
-      24              :         "github.com/spf13/viper"
-      25              :         corev1 "k8s.io/api/core/v1"
-      26              :         v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-      27              :         "k8s.io/cli-runtime/pkg/genericclioptions"
-      28              :         "k8s.io/client-go/kubernetes"
-      29              :         "k8s.io/client-go/rest"
-      30              :         "k8s.io/client-go/tools/clientcmd"
-      31              :         "k8s.io/client-go/tools/clientcmd/api"
-      32              : 
-      33              :         _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
-      34              :         _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
-      35              :         _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
-      36              : )
-      37              : 
-      38              : var (
-      39              :         ksniffExample = "kubectl sniff hello-minikube-7c77b68cff-qbvsd -c hello-minikube"
-      40              : )
-      41              : 
-      42              : const minimumNumberOfArguments = 1
-      43              : const tcpdumpBinaryName = "static-tcpdump"
-      44              : const tcpdumpRemotePath = "/tmp/static-tcpdump"
-      45              : 
-      46              : var tcpdumpLocalBinaryPathLookupList []string
-      47              : 
-      48              : type Ksniff struct {
-      49              :         configFlags      *genericclioptions.ConfigFlags
-      50              :         resultingContext *api.Context
-      51              :         clientset        kubernetes.Interface
-      52              :         restConfig       *rest.Config
-      53              :         rawConfig        api.Config
-      54              :         settings         *config.KsniffSettings
-      55              :         snifferService   sniffer.SnifferService
-      56              :         wireshark        *exec.Cmd
-      57              : }
-      58              : 
-      59           28 : func NewKsniff(settings *config.KsniffSettings) *Ksniff {
-      60           28 :         return &Ksniff{settings: settings, configFlags: genericclioptions.NewConfigFlags(true)}
-      61           28 : }
-      62              : 
-      63           10 : func NewCmdSniff(streams genericclioptions.IOStreams) *cobra.Command {
-      64           10 :         ksniffSettings := config.NewKsniffSettings(streams)
-      65           10 : 
-      66           10 :         ksniff := NewKsniff(ksniffSettings)
-      67           10 : 
-      68           10 :         cmd := &cobra.Command{
-      69           10 :                 Use:          "sniff pod [-n namespace] [-c container] [-f filter] [-o output-file] [-l local-tcpdump-path] [-r remote-tcpdump-path]",
-      70           10 :                 Short:        "Perform network sniffing on a container running in a kubernetes cluster.",
-      71           10 :                 Example:      ksniffExample,
-      72           10 :                 SilenceUsage: true,
-      73           10 :                 RunE: func(c *cobra.Command, args []string) error {
-      74            0 :                         if err := ksniff.Complete(c, args); err != nil {
-      75            0 :                                 return err
-      76            0 :                         }
-      77            0 :                         if err := ksniff.Validate(); err != nil {
-      78            0 :                                 return err
-      79            0 :                         }
-      80            0 :                         if err := ksniff.Run(); err != nil {
-      81            0 :                                 return err
-      82            0 :                         }
-      83              : 
-      84            0 :                         return nil
-      85              :                 },
-      86              :         }
-      87              : 
-      88           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedNamespace, "namespace", "n", "", "namespace (optional)")
-      89           10 :         _ = viper.BindEnv("namespace", "KUBECTL_PLUGINS_CURRENT_NAMESPACE")
-      90           10 :         _ = viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace"))
-      91           10 : 
-      92           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedInterface, "interface", "i", "any", "pod interface to packet capture (optional)")
-      93           10 :         _ = viper.BindEnv("interface", "KUBECTL_PLUGINS_LOCAL_FLAG_INTERFACE")
-      94           10 :         _ = viper.BindPFlag("interface", cmd.Flags().Lookup("interface"))
-      95           10 : 
-      96           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedContainer, "container", "c", "", "container (optional)")
-      97           10 :         _ = viper.BindEnv("container", "KUBECTL_PLUGINS_LOCAL_FLAG_CONTAINER")
-      98           10 :         _ = viper.BindPFlag("container", cmd.Flags().Lookup("container"))
-      99           10 : 
-     100           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedFilter, "filter", "f", "", "tcpdump filter (optional)")
-     101           10 :         _ = viper.BindEnv("filter", "KUBECTL_PLUGINS_LOCAL_FLAG_FILTER")
-     102           10 :         _ = viper.BindPFlag("filter", cmd.Flags().Lookup("filter"))
-     103           10 : 
-     104           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedOutputFile, "output-file", "o", "",
-     105           10 :                 "output file path, tcpdump output will be redirect to this file instead of wireshark (optional) ('-' stdout)")
-     106           10 :         _ = viper.BindEnv("output-file", "KUBECTL_PLUGINS_LOCAL_FLAG_OUTPUT_FILE")
-     107           10 :         _ = viper.BindPFlag("output-file", cmd.Flags().Lookup("output-file"))
-     108           10 : 
-     109           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedLocalTcpdumpPath, "local-tcpdump-path", "l", "",
-     110           10 :                 "local static tcpdump binary path (optional)")
-     111           10 :         _ = viper.BindEnv("local-tcpdump-path", "KUBECTL_PLUGINS_LOCAL_FLAG_LOCAL_TCPDUMP_PATH")
-     112           10 :         _ = viper.BindPFlag("local-tcpdump-path", cmd.Flags().Lookup("local-tcpdump-path"))
-     113           10 : 
-     114           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedRemoteTcpdumpPath, "remote-tcpdump-path", "r", tcpdumpRemotePath,
-     115           10 :                 "remote static tcpdump binary path (optional)")
-     116           10 :         _ = viper.BindEnv("remote-tcpdump-path", "KUBECTL_PLUGINS_LOCAL_FLAG_REMOTE_TCPDUMP_PATH")
-     117           10 :         _ = viper.BindPFlag("remote-tcpdump-path", cmd.Flags().Lookup("remote-tcpdump-path"))
-     118           10 : 
-     119           10 :         cmd.Flags().BoolVarP(&ksniffSettings.UserSpecifiedVerboseMode, "verbose", "v", false,
-     120           10 :                 "if specified, ksniff output will include debug information (optional)")
-     121           10 :         _ = viper.BindEnv("verbose", "KUBECTL_PLUGINS_LOCAL_FLAG_VERBOSE")
-     122           10 :         _ = viper.BindPFlag("verbose", cmd.Flags().Lookup("verbose"))
-     123           10 : 
-     124           10 :         cmd.Flags().BoolVarP(&ksniffSettings.UserSpecifiedPrivilegedMode, "privileged", "p", false,
-     125           10 :                 "if specified, ksniff will deploy another pod that have privileges to attach target pod network namespace")
-     126           10 :         _ = viper.BindEnv("privileged", "KUBECTL_PLUGINS_LOCAL_FLAG_PRIVILEGED")
-     127           10 :         _ = viper.BindPFlag("privileged", cmd.Flags().Lookup("privileged"))
-     128           10 : 
-     129           10 :         cmd.Flags().DurationVarP(&ksniffSettings.UserSpecifiedPodCreateTimeout, "pod-creation-timeout", "",
-     130           10 :                 1*time.Minute, "the length of time to wait for privileged pod to be created (e.g. 20s, 2m, 1h). "+
-     131           10 :                         "A value of zero means the creation never times out.")
-     132           10 : 
-     133           10 :         cmd.Flags().StringVarP(&ksniffSettings.Image, "image", "", "",
-     134           10 :                 "the privileged container image (optional)")
-     135           10 :         _ = viper.BindEnv("image", "KUBECTL_PLUGINS_LOCAL_FLAG_IMAGE")
-     136           10 :         _ = viper.BindPFlag("image", cmd.Flags().Lookup("image"))
-     137           10 : 
-     138           10 :         cmd.Flags().StringVarP(&ksniffSettings.TCPDumpImage, "tcpdump-image", "", "",
-     139           10 :                 "the tcpdump container image (optional)")
-     140           10 :         _ = viper.BindEnv("tcpdump-image", "KUBECTL_PLUGINS_LOCAL_FLAG_TCPDUMP_IMAGE")
-     141           10 :         _ = viper.BindPFlag("tcpdump-image", cmd.Flags().Lookup("tcpdump-image"))
-     142           10 : 
-     143           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedKubeContext, "context", "x", "",
-     144           10 :                 "kubectl context to work on (optional)")
-     145           10 :         _ = viper.BindEnv("context", "KUBECTL_PLUGINS_CURRENT_CONTEXT")
-     146           10 :         _ = viper.BindPFlag("context", cmd.Flags().Lookup("context"))
-     147           10 : 
-     148           10 :         cmd.Flags().StringVarP(&ksniffSettings.SocketPath, "socket", "", "",
-     149           10 :                 "the container runtime socket path (optional)")
-     150           10 :         _ = viper.BindEnv("socket", "KUBECTL_PLUGINS_SOCKET_PATH")
-     151           10 :         _ = viper.BindPFlag("socket", cmd.Flags().Lookup("socket"))
-     152           10 : 
-     153           10 :         cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedServiceAccount, "serviceaccount", "s", "",
-     154           10 :                 "the privileged container service account (optional)")
-     155           10 :         _ = viper.BindEnv("serviceaccount", "KUBECTL_PLUGINS_LOCAL_FLAG_SERVICE_ACCOUNT")
-     156           10 :         _ = viper.BindPFlag("serviceaccount", cmd.Flags().Lookup("serviceaccount"))
-     157           10 : 
-     158           10 :         return cmd
-     159              : }
-     160              : 
-     161            2 : func (o *Ksniff) Complete(cmd *cobra.Command, args []string) error {
-     162            2 :         // Validate and populate basic settings
-     163            2 :         if err := o.completeBasicSettings(cmd, args); err != nil {
-     164            2 :                 return err
-     165            2 :         }
-     166              : 
-     167              :         // Load Kubernetes configuration
-     168            0 :         if err := o.loadKubeConfig(); err != nil {
-     169            0 :                 return err
-     170            0 :         }
-     171              : 
-     172            0 :         return nil
-     173              : }
-     174              : 
-     175              : // completeBasicSettings validates arguments and populates settings from viper
-     176              : // This method doesn't require Kubernetes configuration and can be tested independently
-     177           12 : func (o *Ksniff) completeBasicSettings(cmd *cobra.Command, args []string) error {
-     178           12 :         if len(args) < minimumNumberOfArguments {
-     179            1 :                 _ = cmd.Usage()
-     180            1 :                 return errors.New("not enough arguments")
-     181            1 :         }
-     182              : 
-     183           11 :         o.settings.UserSpecifiedPodName = args[0]
-     184           11 :         if o.settings.UserSpecifiedPodName == "" {
-     185            1 :                 return errors.New("pod name is empty")
-     186            1 :         }
-     187              : 
-     188           10 :         o.settings.UserSpecifiedNamespace = viper.GetString("namespace")
-     189           10 :         o.settings.UserSpecifiedContainer = viper.GetString("container")
-     190           10 :         o.settings.UserSpecifiedInterface = viper.GetString("interface")
-     191           10 :         o.settings.UserSpecifiedFilter = viper.GetString("filter")
-     192           10 :         o.settings.UserSpecifiedOutputFile = viper.GetString("output-file")
-     193           10 :         o.settings.UserSpecifiedLocalTcpdumpPath = viper.GetString("local-tcpdump-path")
-     194           10 :         o.settings.UserSpecifiedRemoteTcpdumpPath = viper.GetString("remote-tcpdump-path")
-     195           10 :         o.settings.UserSpecifiedVerboseMode = viper.GetBool("verbose")
-     196           10 :         o.settings.UserSpecifiedPrivilegedMode = viper.GetBool("privileged")
-     197           10 :         o.settings.UserSpecifiedKubeContext = viper.GetString("context")
-     198           10 :         o.settings.Image = viper.GetString("image")
-     199           10 :         o.settings.TCPDumpImage = viper.GetString("tcpdump-image")
-     200           10 :         o.settings.SocketPath = viper.GetString("socket")
-     201           10 :         o.settings.UseDefaultImage = !viper.IsSet("image")
-     202           10 :         o.settings.UseDefaultTCPDumpImage = !viper.IsSet("tcpdump-image")
-     203           10 :         o.settings.UseDefaultSocketPath = !viper.IsSet("socket")
-     204           10 :         o.settings.UserSpecifiedServiceAccount = viper.GetString("serviceaccount")
-     205           10 : 
-     206           10 :         if o.settings.UserSpecifiedVerboseMode {
-     207            1 :                 log.Info("running in verbose mode")
-     208            1 :                 log.SetLevel(log.DebugLevel)
-     209            1 :         }
-     210              : 
-     211           10 :         var err error
-     212           10 :         tcpdumpLocalBinaryPathLookupList, err = o.buildTcpdumpBinaryPathLookupList()
-     213           10 :         if err != nil {
-     214            0 :                 return err
-     215            0 :         }
-     216              : 
-     217           10 :         return nil
-     218              : }
-     219              : 
-     220              : // loadKubeConfig loads the Kubernetes configuration and initializes the clientset
-     221              : // This method is separated to allow for easier testing and mocking
-     222            0 : func (o *Ksniff) loadKubeConfig() error {
-     223            0 :         var err error
-     224            0 : 
-     225            0 :         o.rawConfig, err = o.configFlags.ToRawKubeConfigLoader().RawConfig()
-     226            0 :         if err != nil {
-     227            0 :                 return err
-     228            0 :         }
-     229              : 
-     230            0 :         var currentContext *api.Context
-     231            0 :         var exists bool
-     232            0 : 
-     233            0 :         if o.settings.UserSpecifiedKubeContext != "" {
-     234            0 :                 currentContext, exists = o.rawConfig.Contexts[o.settings.UserSpecifiedKubeContext]
-     235            0 :         } else {
-     236            0 :                 currentContext, exists = o.rawConfig.Contexts[o.rawConfig.CurrentContext]
-     237            0 :         }
-     238              : 
-     239            0 :         if !exists {
-     240            0 :                 return errors.New("context doesn't exist")
-     241            0 :         }
-     242              : 
-     243            0 :         loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
-     244            0 :         configOverrides := &clientcmd.ConfigOverrides{
-     245            0 :                 CurrentContext: o.settings.UserSpecifiedKubeContext,
-     246            0 :         }
-     247            0 :         kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
-     248            0 :         o.restConfig, err = kubeConfig.ClientConfig()
-     249            0 :         if err != nil {
-     250            0 :                 return err
-     251            0 :         }
-     252              : 
-     253            0 :         o.restConfig.Timeout = 30 * time.Second
-     254            0 : 
-     255            0 :         o.clientset, err = kubernetes.NewForConfig(o.restConfig)
-     256            0 :         if err != nil {
-     257            0 :                 return err
-     258            0 :         }
-     259              : 
-     260            0 :         o.resultingContext = currentContext.DeepCopy()
-     261            0 :         if o.settings.UserSpecifiedNamespace != "" {
-     262            0 :                 o.resultingContext.Namespace = o.settings.UserSpecifiedNamespace
-     263            0 :         }
-     264              : 
-     265            0 :         return nil
-     266              : }
-     267              : 
-     268           10 : func (o *Ksniff) buildTcpdumpBinaryPathLookupList() ([]string, error) {
-     269           10 :         userHomeDir, err := homedir.Dir()
-     270           10 :         if err != nil {
-     271            0 :                 return nil, err
-     272            0 :         }
-     273              : 
-     274           10 :         ksniffBinaryPath, err := filepath.EvalSymlinks(os.Args[0])
-     275           10 :         if err != nil {
-     276            0 :                 return nil, err
-     277            0 :         }
-     278              : 
-     279           10 :         ksniffBinaryDir := filepath.Dir(ksniffBinaryPath)
-     280           10 :         ksniffBinaryPath = filepath.Join(ksniffBinaryDir, tcpdumpBinaryName)
-     281           10 : 
-     282           10 :         kubeKsniffPluginFolder := filepath.Join(userHomeDir, filepath.FromSlash("/.kube/plugin/sniff/"), tcpdumpBinaryName)
-     283           10 : 
-     284           10 :         return append([]string{o.settings.UserSpecifiedLocalTcpdumpPath, ksniffBinaryPath},
-     285           10 :                 filepath.Join("/usr/local/bin/", tcpdumpBinaryName), kubeKsniffPluginFolder), nil
-     286              : }
-     287              : 
-     288            6 : func (o *Ksniff) Validate() error {
-     289            6 :         if len(o.rawConfig.CurrentContext) == 0 {
-     290            0 :                 return errors.New("context doesn't exist")
-     291            0 :         }
-     292              : 
-     293            6 :         if o.resultingContext.Namespace == "" {
-     294            0 :                 return errors.New("namespace value is empty should be custom or default")
-     295            0 :         }
-     296              : 
-     297            6 :         var err error
-     298            6 : 
-     299            6 :         if !o.settings.UserSpecifiedPrivilegedMode {
-     300            0 :                 o.settings.UserSpecifiedLocalTcpdumpPath, err = findLocalTcpdumpBinaryPath()
-     301            0 :                 if err != nil {
-     302            0 :                         return err
-     303            0 :                 }
-     304              : 
-     305            0 :                 log.Infof("using tcpdump path at: '%s'", o.settings.UserSpecifiedLocalTcpdumpPath)
-     306            6 :         } else if o.settings.UserSpecifiedServiceAccount != "" {
-     307            0 :                 _, err := o.clientset.CoreV1().ServiceAccounts(o.resultingContext.Namespace).Get(context.TODO(), o.settings.UserSpecifiedServiceAccount, v1.GetOptions{})
-     308            0 :                 if err != nil {
-     309            0 :                         return err
-     310            0 :                 }
-     311              :         }
-     312              : 
-     313            6 :         pod, err := o.clientset.CoreV1().Pods(o.resultingContext.Namespace).Get(context.TODO(), o.settings.UserSpecifiedPodName, v1.GetOptions{})
-     314            6 :         if err != nil {
-     315            1 :                 return err
-     316            1 :         }
-     317              : 
-     318            5 :         if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
-     319            1 :                 return errors.Errorf("cannot sniff on a container in a completed pod; current phase is %s", pod.Status.Phase)
-     320            1 :         }
-     321              : 
-     322            4 :         o.settings.DetectedPodNodeName = pod.Spec.NodeName
-     323            4 : 
-     324            4 :         log.Debugf("pod '%s' status: '%s'", o.settings.UserSpecifiedPodName, pod.Status.Phase)
-     325            4 : 
-     326            4 :         if len(pod.Spec.Containers) < 1 {
-     327            1 :                 return errors.New("no containers in specified pod")
-     328            1 :         }
-     329              : 
-     330            3 :         if o.settings.UserSpecifiedContainer == "" {
-     331            1 :                 log.Info("no container specified, taking first container we found in pod.")
-     332            1 :                 o.settings.UserSpecifiedContainer = pod.Spec.Containers[0].Name
-     333            1 :                 log.Infof("selected container: '%s'", o.settings.UserSpecifiedContainer)
-     334            1 :         }
-     335              : 
-     336            3 :         if err := o.findContainerId(pod); err != nil {
-     337            1 :                 return err
-     338            1 :         }
-     339              : 
-     340            2 :         kubernetesApiService := kube.NewKubernetesApiService(o.clientset, o.restConfig, o.resultingContext.Namespace)
-     341            2 : 
-     342            2 :         if o.settings.UserSpecifiedPrivilegedMode {
-     343            2 :                 log.Info("sniffing method: privileged pod")
-     344            2 :                 bridge := runtime.NewContainerRuntimeBridge(o.settings.DetectedContainerRuntime)
-     345            2 :                 o.snifferService = sniffer.NewPrivilegedPodRemoteSniffingService(o.settings, kubernetesApiService, bridge)
-     346            2 :         } else {
-     347            0 :                 log.Info("sniffing method: upload static tcpdump")
-     348            0 :                 o.snifferService = sniffer.NewUploadTcpdumpRemoteSniffingService(o.settings, kubernetesApiService)
-     349            0 :         }
-     350              : 
-     351            2 :         return nil
-     352              : }
-     353              : 
-     354            3 : func (o *Ksniff) findContainerId(pod *corev1.Pod) error {
-     355            3 :         for _, containerStatus := range pod.Status.ContainerStatuses {
-     356            3 :                 if o.settings.UserSpecifiedContainer == containerStatus.Name {
-     357            2 :                         result := strings.Split(containerStatus.ContainerID, "://")
-     358            2 :                         if len(result) != 2 {
-     359            0 :                                 break
-     360              :                         }
-     361            2 :                         o.settings.DetectedContainerRuntime = result[0]
-     362            2 :                         o.settings.DetectedContainerId = result[1]
-     363            2 :                         return nil
-     364              :                 }
-     365              :         }
-     366              : 
-     367            1 :         return errors.Errorf("couldn't find container: '%s' in pod: '%s'", o.settings.UserSpecifiedContainer, o.settings.UserSpecifiedPodName)
-     368              : }
-     369              : 
-     370            0 : func findLocalTcpdumpBinaryPath() (string, error) {
-     371            0 :         log.Debugf("searching for tcpdump binary using lookup list: '%v'", tcpdumpLocalBinaryPathLookupList)
-     372            0 : 
-     373            0 :         for _, possibleTcpdumpPath := range tcpdumpLocalBinaryPathLookupList {
-     374            0 :                 if _, err := os.Stat(possibleTcpdumpPath); err == nil {
-     375            0 :                         log.Debugf("tcpdump binary found at: '%s'", possibleTcpdumpPath)
-     376            0 : 
-     377            0 :                         return possibleTcpdumpPath, nil
-     378            0 :                 }
-     379              : 
-     380            0 :                 log.Debugf("tcpdump binary was not found at: '%s'", possibleTcpdumpPath)
-     381              :         }
-     382              : 
-     383            0 :         return "", errors.Errorf("couldn't find static tcpdump binary on any of: '%v'", tcpdumpLocalBinaryPathLookupList)
-     384              : }
-     385              : 
-     386            0 : func (o *Ksniff) setupSignalHandler() chan interface{} {
-     387            0 :         signals := make(chan os.Signal, 1)
-     388            0 :         exit := make(chan interface{})
-     389            0 : 
-     390            0 :         signal.Notify(signals, syscall.SIGINT)
-     391            0 :         go func() {
-     392            0 :                 for {
-     393            0 :                         select {
-     394            0 :                         case sig := <-signals:
-     395            0 :                                 if sig == syscall.SIGINT || sig == syscall.SIGTERM {
-     396            0 :                                         log.Info("starting sniffer cleanup")
-     397            0 :                                         err := o.snifferService.Cleanup()
-     398            0 :                                         if err != nil {
-     399            0 :                                                 log.WithError(err).Error("failed to teardown sniffer, a manual teardown is required.")
-     400            0 :                                         }
-     401            0 :                                         log.Info("sniffer cleanup completed successfully")
-     402            0 : 
-     403            0 :                                         // Kill wireshark if used
-     404            0 :                                         if o.wireshark != nil {
-     405            0 :                                                 if o.wireshark.Process != nil {
-     406            0 :                                                         err = o.wireshark.Process.Kill()
-     407            0 :                                                         if err != nil && err != os.ErrProcessDone {
-     408            0 :                                                                 log.WithError(err).Error("failed to kill wireshark process")
-     409            0 :                                                         } else {
-     410            0 :                                                                 log.Debug("wireshark process killed")
-     411            0 :                                                         }
-     412              :                                                 }
-     413              :                                         }
-     414              : 
-     415            0 :                                         close(signals)
-     416              :                                 }
-     417            0 :                         case <-exit:
-     418            0 :                                 return
-     419              :                         }
-     420              : 
-     421              :                 }
-     422              :         }()
-     423            0 :         return exit
-     424              : }
-     425              : 
-     426            0 : func (o *Ksniff) Run() error {
-     427            0 :         log.Infof("sniffing on pod: '%s' [namespace: '%s', container: '%s', filter: '%s', interface: '%s']",
-     428            0 :                 o.settings.UserSpecifiedPodName, o.resultingContext.Namespace, o.settings.UserSpecifiedContainer, o.settings.UserSpecifiedFilter, o.settings.UserSpecifiedInterface)
-     429            0 : 
-     430            0 :         err := o.snifferService.Setup()
-     431            0 :         if err != nil {
-     432            0 :                 return err
-     433            0 :         }
-     434              : 
-     435              :         // Ensure sniffer is clean on interrupt
-     436            0 :         closeHandler := o.setupSignalHandler()
-     437            0 : 
-     438            0 :         // Ensure sniffer is clean on complete
-     439            0 :         defer func() {
-     440            0 :                 closeHandler <- true
-     441            0 :         }()
-     442              : 
-     443            0 :         if o.settings.UserSpecifiedOutputFile != "" {
-     444            0 :                 log.Infof("output file option specified, storing output in: '%s'", o.settings.UserSpecifiedOutputFile)
-     445            0 : 
-     446            0 :                 var err error
-     447            0 :                 var fileWriter io.Writer
-     448            0 : 
-     449            0 :                 if o.settings.UserSpecifiedOutputFile == "-" {
-     450            0 :                         fileWriter = os.Stdout
-     451            0 :                 } else {
-     452            0 :                         fileWriter, err = os.Create(o.settings.UserSpecifiedOutputFile)
-     453            0 :                         if err != nil {
-     454            0 :                                 return err
-     455            0 :                         }
-     456              :                 }
-     457              : 
-     458            0 :                 err = o.snifferService.Start(fileWriter)
-     459            0 :                 if err != nil {
-     460            0 :                         return err
-     461            0 :                 }
-     462              : 
-     463            0 :         } else {
-     464            0 :                 log.Info("spawning wireshark!")
-     465            0 : 
-     466            0 :                 title := fmt.Sprintf("gui.window_title:%s/%s/%s", o.resultingContext.Namespace, o.settings.UserSpecifiedPodName, o.settings.UserSpecifiedContainer)
-     467            0 :                 o.wireshark = exec.Command("wireshark", "-k", "-i", "-", "-o", title)
-     468            0 : 
-     469            0 :                 stdinWriter, err := o.wireshark.StdinPipe()
-     470            0 :                 if err != nil {
-     471            0 :                         return err
-     472            0 :                 }
-     473              : 
-     474            0 :                 go func() {
-     475            0 :                         err := o.snifferService.Start(stdinWriter)
-     476            0 :                         if err != nil {
-     477            0 :                                 log.WithError(err).Errorf("failed to start remote sniffing, stopping wireshark")
-     478            0 :                                 _ = o.wireshark.Process.Kill()
-     479            0 :                         }
-     480              :                 }()
-     481              : 
-     482            0 :                 err = o.wireshark.Run()
-     483            0 :                 return err
-     484              :         }
-     485              : 
-     486            0 :         return nil
-     487              : }
-        
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/index.html b/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/index.html deleted file mode 100644 index 0f934d0..0000000 --- a/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/index.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - Users/mtl/Development/omnicate-ksniff/utils - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - /Users/mtl/Development/omnicate-ksniff/utilsCoverageTotalHit
Test:_coverage_report.datLines:100.0 %3636
Test Date:2026-01-23 11:51:52Functions:-00
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
utils.go -
100.0%
-
100.0 %3636
Note: 'Function Coverage' columns elided as function owner is not identified.
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/utils.go.gcov.html b/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/utils.go.gcov.html deleted file mode 100644 index c59ac96..0000000 --- a/coverage_html/Users/mtl/Development/omnicate-ksniff/utils/utils.go.gcov.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - Users/mtl/Development/omnicate-ksniff/utils/utils.go - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - /Users/mtl/Development/omnicate-ksniff/utils - utils.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %3636
Test Date:2026-01-23 11:51:52Functions:-00
-
- - - - - - - - -

-
            Line data    Source code
-
-       1              : package utils
-       2              : 
-       3              : import (
-       4              :         "context"
-       5              :         "math/rand"
-       6              :         "time"
-       7              : )
-       8              : 
-       9            4 : func RunWhileFalse(fn func() bool, timeout time.Duration, delay time.Duration) bool {
-      10            4 :         var ctx context.Context
-      11            4 :         var cancel context.CancelFunc
-      12            4 :         if fn() {
-      13            1 :                 return true
-      14            1 :         }
-      15              : 
-      16              :         // Timeout 0 is infinite timeout
-      17            3 :         if (timeout == 0) {
-      18            1 :                 ctx, cancel = context.WithCancel(context.Background())
-      19            2 :         } else {
-      20            2 :                 ctx, cancel = context.WithTimeout(context.Background(), timeout)
-      21            2 :         }
-      22            3 :         delayTick := time.NewTicker(delay)
-      23            3 : 
-      24            3 :         defer delayTick.Stop()
-      25            3 :         defer cancel()
-      26            3 : 
-      27            8 :         for {
-      28            8 :                 select {
-      29            1 :                 case <-ctx.Done():
-      30            1 :                         return false
-      31            6 :                 case <-delayTick.C:
-      32            6 :                         if fn() {
-      33            1 :                                 cancel()
-      34            1 :                                 return true
-      35            1 :                         }
-      36              :                 }
-      37              :         }
-      38              : }
-      39              : 
-      40            6 : func GenerateRandomString(length int) string {
-      41            6 : 
-      42            6 :         rand.Seed(time.Now().UnixNano())
-      43            6 : 
-      44            6 :         var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
-      45            6 : 
-      46            6 :         b := make([]rune, length)
-      47           48 :         for i := range b {
-      48           48 :                 b[i] = letterRunes[rand.Intn(len(letterRunes))]
-      49           48 :         }
-      50              : 
-      51            6 :         return string(b)
-      52              : }
-        
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/amber.png b/coverage_html/amber.png deleted file mode 100644 index 2cab170d8359081983a4e343848dfe06bc490f12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^G2tW}LqE04T&+ z;1OBOz`!j8!i<;h*8KqrvZOouIx;Y9?C1WI$O`1M1^9%x{(levWG?NMQuI!iC1^Jb!lvI6;R0X`wF(yt=9xVZRt1vCRixIA4P dLn>}1Cji+@42)0J?}79&c)I$ztaD0e0sy@GAL0N2 diff --git a/coverage_html/gcov.css b/coverage_html/gcov.css deleted file mode 100644 index 1cacc83..0000000 --- a/coverage_html/gcov.css +++ /dev/null @@ -1,1125 +0,0 @@ -/* All views: initial background and text color */ -body -{ - color: #000000; - background-color: #ffffff; -} - -/* All views: standard link format*/ -a:link -{ - color: #284fa8; - text-decoration: underline; -} - -/* All views: standard link - visited format */ -a:visited -{ - color: #00cb40; - text-decoration: underline; -} - -/* All views: standard link - activated format */ -a:active -{ - color: #ff0040; - text-decoration: underline; -} - -/* All views: main title format */ -td.title -{ - text-align: center; - padding-bottom: 10px; - font-family: sans-serif; - font-size: 20pt; - font-style: italic; - font-weight: bold; -} -/* table footnote */ -td.footnote -{ - text-align: left; - padding-left: 100px; - padding-right: 10px; - background-color: #dae7fe; /* light blue table background color */ - /* dark blue table header color - background-color: #6688d4; */ - white-space: nowrap; - font-family: sans-serif; - font-style: italic; - font-size:70%; -} -/* "Line coverage date bins" leader */ -td.subTableHeader -{ - text-align: center; - padding-bottom: 6px; - font-family: sans-serif; - font-weight: bold; - vertical-align: center; -} - -/* All views: header item format */ -td.headerItem -{ - text-align: right; - padding-right: 6px; - font-family: sans-serif; - font-weight: bold; - vertical-align: top; - white-space: nowrap; -} - -/* All views: header item value format */ -td.headerValue -{ - text-align: left; - color: #284fa8; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; -} - -/* All views: header item coverage table heading */ -td.headerCovTableHead -{ - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; -} - -/* All views: header item coverage table entry */ -td.headerCovTableEntry -{ - text-align: right; - color: #284fa8; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #dae7fe; -} - -/* All views: header item coverage table entry for high coverage rate */ -td.headerCovTableEntryHi -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #a7fc9d; -} - -/* All views: header item coverage table entry for medium coverage rate */ -td.headerCovTableEntryMed -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #ffea20; -} - -/* All views: header item coverage table entry for ow coverage rate */ -td.headerCovTableEntryLo -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #ff0000; -} - -/* All views: header legend value for legend entry */ -td.headerValueLeg -{ - text-align: left; - color: #000000; - font-family: sans-serif; - font-size: 80%; - white-space: nowrap; - padding-top: 4px; -} - -/* All views: color of horizontal ruler */ -td.ruler -{ - background-color: #6688d4; -} - -/* All views: version string format */ -td.versionInfo -{ - text-align: center; - padding-top: 2px; - font-family: sans-serif; - font-style: italic; -} - -/* Directory view/File view (all)/Test case descriptions: - table headline format */ -td.tableHead -{ - text-align: center; - color: #ffffff; - background-color: #6688d4; - font-family: sans-serif; - font-size: 120%; - font-weight: bold; - white-space: nowrap; - padding-left: 4px; - padding-right: 4px; -} - -span.tableHeadSort -{ - padding-right: 4px; -} - -/* Directory view/File view (all): filename entry format */ -td.coverFile -{ - text-align: left; - padding-left: 10px; - padding-right: 20px; - color: #284fa8; - background-color: #dae7fe; - font-family: monospace; -} - -/* Directory view/File view (all): directory name entry format */ -td.coverDirectory -{ - text-align: left; - padding-left: 10px; - padding-right: 20px; - color: #284fa8; - background-color: #b8d0ff; - font-family: monospace; -} - -/* Directory view/File view (all): filename entry format */ -td.overallOwner -{ - text-align: center; - font-weight: bold; - font-family: sans-serif; - background-color: #dae7fe; - padding-right: 10px; - padding-left: 10px; -} - -/* Directory view/File view (all): filename entry format */ -td.ownerName -{ - text-align: right; - font-style: italic; - font-family: sans-serif; - background-color: #E5DBDB; - padding-right: 10px; - padding-left: 20px; -} - -/* Directory view/File view (all): bar-graph entry format*/ -td.coverBar -{ - padding-left: 10px; - padding-right: 10px; - background-color: #dae7fe; -} - -/* Directory view/File view (all): bar-graph entry format*/ -td.owner_coverBar -{ - padding-left: 10px; - padding-right: 10px; - background-color: #E5DBDB; -} - -/* Directory view/File view (all): bar-graph outline color */ -td.coverBarOutline -{ - background-color: #000000; -} - -/* Directory view/File view (all): percentage entry for files with - high coverage rate */ -td.coverPerHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #a7fc9d; - font-weight: bold; - font-family: sans-serif; -} - -/* 'owner' entry: slightly lighter color than 'coverPerHi' */ -td.owner_coverPerHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #82E0AA; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry */ -td.coverNumDflt -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #dae7fe; - white-space: nowrap; - font-family: sans-serif; -} - -/* td background color and font for the 'owner' section of the table */ -td.ownerTla -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #E5DBDB; - white-space: nowrap; - font-family: sans-serif; - font-style: italic; -} - -/* Directory view/File view (all): line count entry for files with - high coverage rate */ -td.coverNumHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #a7fc9d; - white-space: nowrap; - font-family: sans-serif; -} - -td.owner_coverNumHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #82E0AA; - white-space: nowrap; - font-family: sans-serif; -} - -/* Directory view/File view (all): percentage entry for files with - medium coverage rate */ -td.coverPerMed -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #ffea20; - font-weight: bold; - font-family: sans-serif; -} - -td.owner_coverPerMed -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #F9E79F; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry for files with - medium coverage rate */ -td.coverNumMed -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #ffea20; - white-space: nowrap; - font-family: sans-serif; -} - -td.owner_coverNumMed -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #F9E79F; - white-space: nowrap; - font-family: sans-serif; -} - -/* Directory view/File view (all): percentage entry for files with - low coverage rate */ -td.coverPerLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #ff0000; - font-weight: bold; - font-family: sans-serif; -} - -td.owner_coverPerLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #EC7063; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry for files with - low coverage rate */ -td.coverNumLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #ff0000; - white-space: nowrap; - font-family: sans-serif; -} - -td.owner_coverNumLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #EC7063; - white-space: nowrap; - font-family: sans-serif; -} - -/* File view (all): "show/hide details" link format */ -a.detail:link -{ - color: #b8d0ff; - font-size:80%; -} - -/* File view (all): "show/hide details" link - visited format */ -a.detail:visited -{ - color: #b8d0ff; - font-size:80%; -} - -/* File view (all): "show/hide details" link - activated format */ -a.detail:active -{ - color: #ffffff; - font-size:80%; -} - -/* File view (detail): test name entry */ -td.testName -{ - text-align: right; - padding-right: 10px; - background-color: #dae7fe; - font-family: sans-serif; -} - -/* File view (detail): test percentage entry */ -td.testPer -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #dae7fe; - font-family: sans-serif; -} - -/* File view (detail): test lines count entry */ -td.testNum -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #dae7fe; - font-family: sans-serif; -} - -/* Test case descriptions: test name format*/ -dt -{ - font-family: sans-serif; - font-weight: bold; -} - -/* Test case descriptions: description table body */ -td.testDescription -{ - padding-top: 10px; - padding-left: 30px; - padding-bottom: 10px; - padding-right: 30px; - background-color: #dae7fe; -} - -/* Source code view: function entry */ -td.coverFn -{ - text-align: left; - padding-left: 10px; - padding-right: 20px; - color: #284fa8; - background-color: #dae7fe; - font-family: monospace; -} - -/* Source code view: function entry zero count*/ -td.coverFnLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #ff0000; - font-weight: bold; - font-family: sans-serif; -} - -/* Source code view: function entry nonzero count*/ -td.coverFnHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #dae7fe; - font-weight: bold; - font-family: sans-serif; -} - -td.coverFnAlias -{ - text-align: right; - padding-left: 10px; - padding-right: 20px; - color: #284fa8; - /* make this a slightly different color than the leader - otherwise, - otherwise the alias is hard to distinguish in the table */ - background-color: #E5DBDB; /* very light pale grey/blue */ - font-family: monospace; -} - -/* Source code view: function entry zero count*/ -td.coverFnAliasLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #EC7063; /* lighter red */ - font-family: sans-serif; -} - -/* Source code view: function entry nonzero count*/ -td.coverFnAliasHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #dae7fe; - font-weight: bold; - font-family: sans-serif; -} - -/* Source code view: source code format */ -pre.source -{ - font-family: monospace; - white-space: pre; - margin-top: 2px; -} - -/* elided/removed code */ -span.elidedSource -{ - font-family: sans-serif; - /*font-size: 8pt; */ - font-style: italic; - background-color: lightgrey; -} - -/* Source code view: line number format */ -span.lineNum -{ - background-color: #efe383; -} - -/* Source code view: line number format when there are deleted - lines in the corresponding location */ -span.lineNumWithDelete -{ - foreground-color: #efe383; - background-color: lightgrey; -} - -/* Source code view: format for Cov legend */ -span.coverLegendCov -{ - padding-left: 10px; - padding-right: 10px; - padding-bottom: 2px; - background-color: #cad7fe; -} - -/* Source code view: format for NoCov legend */ -span.coverLegendNoCov -{ - padding-left: 10px; - padding-right: 10px; - padding-bottom: 2px; - background-color: #ff6230; -} - -/* Source code view: format for the source code heading line */ -pre.sourceHeading -{ - white-space: pre; - font-family: monospace; - font-weight: bold; - margin: 0px; -} - -/* All views: header legend value for low rate */ -td.headerValueLegL -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 4px; - padding-right: 2px; - background-color: #ff0000; - font-size: 80%; -} - -/* All views: header legend value for med rate */ -td.headerValueLegM -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 2px; - padding-right: 2px; - background-color: #ffea20; - font-size: 80%; -} - -/* All views: header legend value for hi rate */ -td.headerValueLegH -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 2px; - padding-right: 4px; - background-color: #a7fc9d; - font-size: 80%; -} - -/* All views except source code view: legend format for low coverage */ -span.coverLegendCovLo -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #ff0000; -} - -/* All views except source code view: legend format for med coverage */ -span.coverLegendCovMed -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #ffea20; -} - -/* All views except source code view: legend format for hi coverage */ -span.coverLegendCovHi -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #a7fc9d; -} - -a.branchTla:link -{ - color: #000000; -} - -a.branchTla:visited -{ - color: #000000; -} - -a.mcdcTla:link -{ - color: #000000; -} - -a.mcdcTla:visited -{ - color: #000000; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): -Newly added code is not tested" */ -td.tlaUNC -{ - text-align: right; - background-color: #FF6230; -} -td.tlaBgUNC { - background-color: #FF6230; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): -Newly added code is not tested" */ -span.tlaUNC -{ - text-align: left; - background-color: #FF6230; -} -span.tlaBgUNC { - background-color: #FF6230; -} -a.tlaBgUNC { - background-color: #FF6230; - color: #000000; -} - -td.headerCovTableHeadUNC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FF6230; -} - -/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): -Unchanged code is no longer tested" */ -td.tlaLBC -{ - text-align: right; - background-color: #FF6230; -} -td.tlaBgLBC { - background-color: #FF6230; -} - -/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): -Unchanged code is no longer tested" */ -span.tlaLBC -{ - text-align: left; - background-color: #FF6230; -} -span.tlaBgLBC { - background-color: #FF6230; -} -a.tlaBgLBC { - background-color: #FF6230; - color: #000000; -} - -td.headerCovTableHeadLBC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FF6230; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): -Previously unused code is untested" */ -td.tlaUIC -{ - text-align: right; - background-color: #FF6230; -} -td.tlaBgUIC { - background-color: #FF6230; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): -Previously unused code is untested" */ -span.tlaUIC -{ - text-align: left; - background-color: #FF6230; -} -span.tlaBgUIC { - background-color: #FF6230; -} -a.tlaBgUIC { - background-color: #FF6230; - color: #000000; -} - -td.headerCovTableHeadUIC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FF6230; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): -Unchanged code was untested before, is untested now" */ -td.tlaUBC -{ - text-align: right; - background-color: #FF6230; -} -td.tlaBgUBC { - background-color: #FF6230; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): -Unchanged code was untested before, is untested now" */ -span.tlaUBC -{ - text-align: left; - background-color: #FF6230; -} -span.tlaBgUBC { - background-color: #FF6230; -} -a.tlaBgUBC { - background-color: #FF6230; - color: #000000; -} - -td.headerCovTableHeadUBC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FF6230; -} - -/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): -Unchanged code is tested now" */ -td.tlaGBC -{ - text-align: right; - background-color: #CAD7FE; -} -td.tlaBgGBC { - background-color: #CAD7FE; -} - -/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): -Unchanged code is tested now" */ -span.tlaGBC -{ - text-align: left; - background-color: #CAD7FE; -} -span.tlaBgGBC { - background-color: #CAD7FE; -} -a.tlaBgGBC { - background-color: #CAD7FE; - color: #000000; -} - -td.headerCovTableHeadGBC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #CAD7FE; -} - -/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): -Previously unused code is tested now" */ -td.tlaGIC -{ - text-align: right; - background-color: #CAD7FE; -} -td.tlaBgGIC { - background-color: #CAD7FE; -} - -/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): -Previously unused code is tested now" */ -span.tlaGIC -{ - text-align: left; - background-color: #CAD7FE; -} -span.tlaBgGIC { - background-color: #CAD7FE; -} -a.tlaBgGIC { - background-color: #CAD7FE; - color: #000000; -} - -td.headerCovTableHeadGIC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #CAD7FE; -} - -/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): -Newly added code is tested" */ -td.tlaGNC -{ - text-align: right; - background-color: #CAD7FE; -} -td.tlaBgGNC { - background-color: #CAD7FE; -} - -/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): -Newly added code is tested" */ -span.tlaGNC -{ - text-align: left; - background-color: #CAD7FE; -} -span.tlaBgGNC { - background-color: #CAD7FE; -} -a.tlaBgGNC { - background-color: #CAD7FE; - color: #000000; -} - -td.headerCovTableHeadGNC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #CAD7FE; -} - -/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): -Unchanged code was tested before and is still tested" */ -td.tlaCBC -{ - text-align: right; - background-color: #CAD7FE; -} -td.tlaBgCBC { - background-color: #CAD7FE; -} - -/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): -Unchanged code was tested before and is still tested" */ -span.tlaCBC -{ - text-align: left; - background-color: #CAD7FE; -} -span.tlaBgCBC { - background-color: #CAD7FE; -} -a.tlaBgCBC { - background-color: #CAD7FE; - color: #000000; -} - -td.headerCovTableHeadCBC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #CAD7FE; -} - -/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): -Previously untested code is unused now" */ -td.tlaEUB -{ - text-align: right; - background-color: #FFFFFF; -} -td.tlaBgEUB { - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): -Previously untested code is unused now" */ -span.tlaEUB -{ - text-align: left; - background-color: #FFFFFF; -} -span.tlaBgEUB { - background-color: #FFFFFF; -} -a.tlaBgEUB { - background-color: #FFFFFF; - color: #000000; -} - -td.headerCovTableHeadEUB { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): -Previously tested code is unused now" */ -td.tlaECB -{ - text-align: right; - background-color: #FFFFFF; -} -td.tlaBgECB { - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): -Previously tested code is unused now" */ -span.tlaECB -{ - text-align: left; - background-color: #FFFFFF; -} -span.tlaBgECB { - background-color: #FFFFFF; -} -a.tlaBgECB { - background-color: #FFFFFF; - color: #000000; -} - -td.headerCovTableHeadECB { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): -Previously untested code has been deleted" */ -td.tlaDUB -{ - text-align: right; - background-color: #FFFFFF; -} -td.tlaBgDUB { - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): -Previously untested code has been deleted" */ -span.tlaDUB -{ - text-align: left; - background-color: #FFFFFF; -} -span.tlaBgDUB { - background-color: #FFFFFF; -} -a.tlaBgDUB { - background-color: #FFFFFF; - color: #000000; -} - -td.headerCovTableHeadDUB { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): -Previously tested code has been deleted" */ -td.tlaDCB -{ - text-align: right; - background-color: #FFFFFF; -} -td.tlaBgDCB { - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): -Previously tested code has been deleted" */ -span.tlaDCB -{ - text-align: left; - background-color: #FFFFFF; -} -span.tlaBgDCB { - background-color: #FFFFFF; -} -a.tlaBgDCB { - background-color: #FFFFFF; - color: #000000; -} - -td.headerCovTableHeadDCB { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FFFFFF; -} - -/* Source code view: format for date/owner bin that is not hit */ -span.missBins -{ - background-color: #ff0000 /* red */ -} diff --git a/coverage_html/glass.png b/coverage_html/glass.png deleted file mode 100644 index e1abc00680a3093c49fdb775ae6bdb6764c95af2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtmSN`?>!lvI6;R0X`wF z|Ns97GD8ntt^-nxB|(0{3=Yq3q=7g|-tI089jvk*Kn`btM`SSr1Gf+eGhVt|_XjA* zUgGKN%6^Gmn4d%Ph(nkFP>9RZ#WAE}PI3Z}&BVayv3^M*kj3EX>gTe~DWM4f=_Dpv diff --git a/coverage_html/index-sort-f.html b/coverage_html/index-sort-f.html deleted file mode 100644 index 050a98e..0000000 --- a/coverage_html/index-sort-f.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelCoverageTotalHit
Test:_coverage_report.datLines:69.3 %541375
Test Date:2026-01-23 11:51:52Functions:-00
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
/Users/mtl/Development/omnicate-ksniff/pkg/cmd/ -
52.4%52.4%
-
52.4 %349183
/Users/mtl/Development/omnicate-ksniff/utils/ -
100.0%
-
100.0 %3636
runtime/ -
100.0%
-
100.0 %156156
Note: 'Function Coverage' columns elided as function owner is not identified.
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/index-sort-l.html b/coverage_html/index-sort-l.html deleted file mode 100644 index 3924dac..0000000 --- a/coverage_html/index-sort-l.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelCoverageTotalHit
Test:_coverage_report.datLines:69.3 %541375
Test Date:2026-01-23 11:51:52Functions:-00
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
/Users/mtl/Development/omnicate-ksniff/pkg/cmd/ -
52.4%52.4%
-
52.4 %349183
/Users/mtl/Development/omnicate-ksniff/utils/ -
100.0%
-
100.0 %3636
runtime/ -
100.0%
-
100.0 %156156
Note: 'Function Coverage' columns elided as function owner is not identified.
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/index.html b/coverage_html/index.html deleted file mode 100644 index 6aeb064..0000000 --- a/coverage_html/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelCoverageTotalHit
Test:_coverage_report.datLines:69.3 %541375
Test Date:2026-01-23 11:51:52Functions:-00
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
/Users/mtl/Development/omnicate-ksniff/pkg/cmd/ -
52.4%52.4%
-
52.4 %349183
/Users/mtl/Development/omnicate-ksniff/utils/ -
100.0%
-
100.0 %3636
runtime/ -
100.0%
-
100.0 %156156
Note: 'Function Coverage' columns elided as function owner is not identified.
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/ruby.png b/coverage_html/ruby.png deleted file mode 100644 index 991b6d4ec9e78be165e3ef757eed1aada287364d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^FceV#7`HfI^%F z9+AZi4BSE>%y{W;-5;PJOS+@4BLl<6e(pbstUx|nfKQ0)e^Y%R^MdiLxj>4`)5S5Q b;#P73kj=!v_*DHKNFRfztDnm{r-UW|iOwIS diff --git a/coverage_html/runtime/containerd.go.gcov.html b/coverage_html/runtime/containerd.go.gcov.html deleted file mode 100644 index c48ad7c..0000000 --- a/coverage_html/runtime/containerd.go.gcov.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - runtime/containerd.go - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - runtime - containerd.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %4747
Test Date:2026-01-23 11:51:52Functions:-00
-
- - - - - - - - -

-
            Line data    Source code
-
-       1              : package runtime
-       2              : 
-       3              : import (
-       4              :         "fmt"
-       5              :         "ksniff/utils"
-       6              : )
-       7              : 
-       8              : type ContainerdBridge struct {
-       9              :         tcpdumpContainerName string
-      10              :         socketPath string
-      11              : }
-      12              : 
-      13           10 : func NewContainerdBridge() *ContainerdBridge {
-      14           10 :         return &ContainerdBridge{}
-      15           10 : }
-      16              : 
-      17            1 : func (d ContainerdBridge) NeedsPid() bool {
-      18            1 :         return false
-      19            1 : }
-      20              : 
-      21            1 : func (d ContainerdBridge) BuildInspectCommand(string) []string {
-      22            1 :         panic("Containerd doesn't need this implemented")
-      23              : }
-      24              : 
-      25            1 : func (d ContainerdBridge) ExtractPid(inspection string) (*string, error) {
-      26            1 :         panic("Containerd doesn't need this implemented")
-      27              : }
-      28              : 
-      29            1 : func (d ContainerdBridge) GetDefaultSocketPath() string {
-      30            1 :         return "/run/containerd/containerd.sock"
-      31            1 : }
-      32              : 
-      33            2 : func (d *ContainerdBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
-      34            2 :         d.tcpdumpContainerName = "ksniff-container-" + utils.GenerateRandomString(8)
-      35            2 :         d.socketPath = socketPath
-      36            2 :         tcpdumpCommand := fmt.Sprintf("tcpdump -i %s -U -w - %s", netInterface, filter)
-      37            2 :         shellScript := fmt.Sprintf(`
-      38            2 :     set -ex
-      39            2 :     export CONTAINERD_SOCKET="%s"
-      40            2 :     export CONTAINERD_NAMESPACE="k8s.io"
-      41            2 :     export CONTAINER_RUNTIME_ENDPOINT="unix:///host${CONTAINERD_SOCKET}"
-      42            2 :     export IMAGE_SERVICE_ENDPOINT=${CONTAINER_RUNTIME_ENDPOINT}
-      43            2 :     crictl pull %s >/dev/null
-      44            2 :     netns=$(crictl inspect %s | jq '.info.runtimeSpec.linux.namespaces[] | select(.type == "network") | .path' | tr -d '"')
-      45            2 :     exec chroot /host ctr -a ${CONTAINERD_SOCKET} run --rm --with-ns "network:${netns}" %s %s %s 
-      46            2 :     `, d.socketPath, tcpdumpImage, *containerId, tcpdumpImage, d.tcpdumpContainerName, tcpdumpCommand)
-      47            2 :         command := []string{"/bin/sh", "-c", shellScript}
-      48            2 :         return command
-      49            2 : }
-      50              : 
-      51            1 : func (d *ContainerdBridge) BuildCleanupCommand() []string {
-      52            1 :         shellScript := fmt.Sprintf(`
-      53            1 :     set -ex
-      54            1 :     export CONTAINERD_SOCKET="%s"
-      55            1 :     export CONTAINERD_NAMESPACE="k8s.io"
-      56            1 :     export CONTAINER_ID="%s"
-      57            1 :     chroot /host ctr -a ${CONTAINERD_SOCKET} task kill -s SIGKILL ${CONTAINER_ID}
-      58            1 :     `, d.socketPath, d.tcpdumpContainerName)
-      59            1 :         command := []string{"/bin/sh", "-c", shellScript}
-      60            1 :         return command
-      61            1 : }
-      62              : 
-      63            1 : func (d ContainerdBridge) GetDefaultImage() string {
-      64            1 :         return "docker.io/hamravesh/ksniff-helper:v3"
-      65            1 : }
-      66              : 
-      67            1 : func (d *ContainerdBridge) GetDefaultTCPImage() string {
-      68            1 :         return "docker.io/maintained/tcpdump:latest"
-      69            1 : }
-        
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/runtime/crio.go.gcov.html b/coverage_html/runtime/crio.go.gcov.html deleted file mode 100644 index ff3a3bf..0000000 --- a/coverage_html/runtime/crio.go.gcov.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - runtime/crio.go - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - runtime - crio.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %6363
Test Date:2026-01-23 11:51:52Functions:-00
-
- - - - - - - - -

-
            Line data    Source code
-
-       1              : package runtime
-       2              : 
-       3              : import (
-       4              :         "encoding/json"
-       5              :         "fmt"
-       6              : 
-       7              :         "github.com/pkg/errors"
-       8              : )
-       9              : 
-      10              : type CrioBridge struct {
-      11              : }
-      12              : 
-      13           20 : func NewCrioBridge() *CrioBridge {
-      14           20 :         return &CrioBridge{}
-      15           20 : }
-      16              : 
-      17            1 : func (c *CrioBridge) NeedsPid() bool {
-      18            1 :         return true
-      19            1 : }
-      20              : 
-      21            1 : func (c *CrioBridge) BuildInspectCommand(containerId string) []string {
-      22            1 :         return []string{"chroot", "/host", "crictl", "inspect",
-      23            1 :                 "--output", "json", containerId}
-      24            1 : }
-      25              : 
-      26           12 : func (c *CrioBridge) ExtractPid(inspection string) (*string, error) {
-      27           12 :         var result map[string]json.RawMessage
-      28           12 :         var pid float64
-      29           12 :         var err error
-      30           12 : 
-      31           12 :         err = json.Unmarshal([]byte(inspection), &result)
-      32           12 :         if err != nil {
-      33            3 :                 return nil, err
-      34            3 :         }
-      35              : 
-      36              :         // CRI-O changes the way it reports PID so we have to by dynamic here
-      37            9 :         if result["pid"] != nil {
-      38            3 :                 pid, err = extractPidCrio117(result)
-      39            3 :                 if err != nil {
-      40            1 :                         return nil, errors.Wrap(err, "error getting container PID from CRI-O")
-      41            1 :                 }
-      42            6 :         } else if result["info"] != nil {
-      43            3 :                 pid, err = extractPidCrio118(result)
-      44            3 :                 if err != nil {
-      45            1 :                         return nil, errors.Wrap(err, "error getting container PID from CRI-O")
-      46            1 :                 }
-      47            3 :         } else {
-      48            3 :                 return nil, errors.New("unable to identify CRI-O version")
-      49            3 :         }
-      50              : 
-      51            4 :         ret := fmt.Sprintf("%.0f", pid)
-      52            4 :         return &ret, nil
-      53              : }
-      54              : 
-      55            1 : func (c *CrioBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
-      56            1 :         return []string{"nsenter", "-n", "-t", *pid, "--", "tcpdump", "-i", netInterface, "-U", "-w", "-", filter}
-      57            1 : }
-      58              : 
-      59            1 : func (c *CrioBridge) BuildCleanupCommand() []string {
-      60            1 :         return nil // No cleanup needed
-      61            1 : }
-      62              : 
-      63            1 : func (c *CrioBridge) GetDefaultImage() string {
-      64            1 :         return "maintained/tcpdump"
-      65            1 : }
-      66              : 
-      67            1 : func (c *CrioBridge) GetDefaultSocketPath() string {
-      68            1 :         return "/var/run/crio/crio.sock"
-      69            1 : }
-      70              : 
-      71              : // CRI-O 1.17 and older have pid as first-level attribute
-      72            3 : func extractPidCrio117(partial map[string]json.RawMessage) (float64, error) {
-      73            3 :         var result float64
-      74            3 :         err := json.Unmarshal(partial["pid"], &result)
-      75            3 :         if err != nil {
-      76            1 :                 return -1, err
-      77            1 :         }
-      78            2 :         return result, nil
-      79              : }
-      80              : 
-      81              : // CRI-O 1.18 and later nest pid under info attribute
-      82            3 : func extractPidCrio118(partial map[string]json.RawMessage) (float64, error) {
-      83            3 :         var result map[string]interface{}
-      84            3 :         err := json.Unmarshal(partial["info"], &result)
-      85            3 :         if err != nil {
-      86            1 :                 return -1, err
-      87            1 :         }
-      88            2 :         return result["pid"].(float64), nil
-      89              : }
-      90              : 
-      91            1 : func (d *CrioBridge) GetDefaultTCPImage() string {
-      92            1 :         return ""
-      93            1 : }
-        
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/runtime/docker.go.gcov.html b/coverage_html/runtime/docker.go.gcov.html deleted file mode 100644 index e57834d..0000000 --- a/coverage_html/runtime/docker.go.gcov.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - runtime/docker.go - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - runtime - docker.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %3636
Test Date:2026-01-23 11:51:52Functions:-00
-
- - - - - - - - -

-
            Line data    Source code
-
-       1              : package runtime
-       2              : 
-       3              : import (
-       4              :         "fmt"
-       5              : 
-       6              :         "ksniff/utils"
-       7              : )
-       8              : 
-       9              : type DockerBridge struct {
-      10              :         tcpdumpContainerName string
-      11              :         cleanupCommand       []string
-      12              : }
-      13              : 
-      14           14 : func NewDockerBridge() *DockerBridge {
-      15           14 :         return &DockerBridge{}
-      16           14 : }
-      17              : 
-      18            1 : func (d *DockerBridge) NeedsPid() bool {
-      19            1 :         return false
-      20            1 : }
-      21              : 
-      22            2 : func (d *DockerBridge) BuildInspectCommand(string) []string {
-      23            2 :         panic("Docker doesn't need this implemented")
-      24              : }
-      25              : 
-      26            2 : func (d *DockerBridge) ExtractPid(inspection string) (*string, error) {
-      27            2 :         panic("Docker doesn't need this implemented")
-      28              : }
-      29              : 
-      30            4 : func (d *DockerBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
-      31            4 :         d.tcpdumpContainerName = "ksniff-container-" + utils.GenerateRandomString(8)
-      32            4 :         containerNameFlag := fmt.Sprintf("--name=%s", d.tcpdumpContainerName)
-      33            4 : 
-      34            4 :         command := []string{"docker", "--host", "unix://" + socketPath,
-      35            4 :                 "run", "--rm", "--log-driver", "none", containerNameFlag,
-      36            4 :                 fmt.Sprintf("--net=container:%s", *containerId), tcpdumpImage, "-i",
-      37            4 :                 netInterface, "-U", "-w", "-", filter}
-      38            4 : 
-      39            4 :         d.cleanupCommand = []string{"docker", "--host", "unix://" + socketPath,
-      40            4 :                 "rm", "-f", d.tcpdumpContainerName}
-      41            4 : 
-      42            4 :         return command
-      43            4 : }
-      44              : 
-      45            2 : func (d *DockerBridge) BuildCleanupCommand() []string {
-      46            2 :         return d.cleanupCommand
-      47            2 : }
-      48              : 
-      49            1 : func (d *DockerBridge) GetDefaultImage() string {
-      50            1 :         return "docker"
-      51            1 : }
-      52              : 
-      53            3 : func (d *DockerBridge) GetDefaultTCPImage() string {
-      54            3 :         return "maintained/tcpdump"
-      55            3 : }
-      56              : 
-      57            1 : func (d *DockerBridge) GetDefaultSocketPath() string {
-      58            1 :         return "/var/run/docker.sock"
-      59            1 : }
-        
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/runtime/index-sort-f.html b/coverage_html/runtime/index-sort-f.html deleted file mode 100644 index 63f8155..0000000 --- a/coverage_html/runtime/index-sort-f.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - runtime - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - runtimeCoverageTotalHit
Test:_coverage_report.datLines:100.0 %156156
Test Date:2026-01-23 11:51:52Functions:-00
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
containerd.go -
100.0%
-
100.0 %4747
crio.go -
100.0%
-
100.0 %6363
docker.go -
100.0%
-
100.0 %3636
runtime.go -
100.0%
-
100.0 %1010
Note: 'Function Coverage' columns elided as function owner is not identified.
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/runtime/index-sort-l.html b/coverage_html/runtime/index-sort-l.html deleted file mode 100644 index c5a57a3..0000000 --- a/coverage_html/runtime/index-sort-l.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - runtime - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - runtimeCoverageTotalHit
Test:_coverage_report.datLines:100.0 %156156
Test Date:2026-01-23 11:51:52Functions:-00
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
runtime.go -
100.0%
-
100.0 %1010
docker.go -
100.0%
-
100.0 %3636
containerd.go -
100.0%
-
100.0 %4747
crio.go -
100.0%
-
100.0 %6363
Note: 'Function Coverage' columns elided as function owner is not identified.
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/runtime/index.html b/coverage_html/runtime/index.html deleted file mode 100644 index fe1e59e..0000000 --- a/coverage_html/runtime/index.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - runtime - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - runtimeCoverageTotalHit
Test:_coverage_report.datLines:100.0 %156156
Test Date:2026-01-23 11:51:52Functions:-00
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

File Sort by file nameLine Coverage Sort by line coverage
Rate Total Hit
containerd.go -
100.0%
-
100.0 %4747
crio.go -
100.0%
-
100.0 %6363
docker.go -
100.0%
-
100.0 %3636
runtime.go -
100.0%
-
100.0 %1010
Note: 'Function Coverage' columns elided as function owner is not identified.
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/runtime/runtime.go.gcov.html b/coverage_html/runtime/runtime.go.gcov.html deleted file mode 100644 index c07db6b..0000000 --- a/coverage_html/runtime/runtime.go.gcov.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - LCOV - _coverage_report.dat - runtime/runtime.go - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - runtime - runtime.goCoverageTotalHit
Test:_coverage_report.datLines:100.0 %1010
Test Date:2026-01-23 11:51:52Functions:-00
-
- - - - - - - - -

-
            Line data    Source code
-
-       1              : package runtime
-       2              : 
-       3              : import "fmt"
-       4              : 
-       5              : var SupportedContainerRuntimes = []string{
-       6              :         "docker",
-       7              :         "cri-o",
-       8              :         "containerd",
-       9              : }
-      10              : 
-      11              : type ContainerRuntimeBridge interface {
-      12              :         NeedsPid() bool
-      13              :         BuildInspectCommand(containerId string) []string
-      14              :         ExtractPid(inspection string) (*string, error)
-      15              :         BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string
-      16              :         BuildCleanupCommand() []string
-      17              :         GetDefaultImage() string
-      18              :         GetDefaultTCPImage() string
-      19              :         GetDefaultSocketPath() string
-      20              : }
-      21              : 
-      22            6 : func NewContainerRuntimeBridge(runtimeName string) ContainerRuntimeBridge {
-      23            6 :         switch runtimeName {
-      24            2 :         case "docker":
-      25            2 :                 return NewDockerBridge()
-      26            1 :         case "cri-o":
-      27            1 :                 return NewCrioBridge()
-      28            2 :         case "containerd":
-      29            2 :                 return NewContainerdBridge()
-      30            1 :         default:
-      31            1 :                 panic(fmt.Sprintf("Unable to build bridge to %s", runtimeName))
-      32              :         }
-      33              : }
-        
-
-
- - - - -
Generated by: LCOV version 2.4-0
-
- - - diff --git a/coverage_html/snow.png b/coverage_html/snow.png deleted file mode 100644 index 2cdae107fceec6e7f02ac7acb4a34a82a540caa5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^MM!lvI6;R0X`wF|Ns97GD8ntt^-nBo-U3d c6}OTTfNUlP#;5A{K>8RwUHx3vIVCg!071?oo&W#< diff --git a/coverage_html/updown.png b/coverage_html/updown.png deleted file mode 100644 index aa56a238b3e6c435265250f9266cd1b8caba0f20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^AT}Qd8;}%R+`Ae`*?77*hG?8mPH5^{)z4*}Q$iB}huR`+ From 4c15f6ccca7d92d2ef6602f1e98fced9126bea63 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Mon, 26 Jan 2026 10:02:00 +0100 Subject: [PATCH 3/5] Sync up with go dependency updates --- README_golang.md | 14 +++++++++++++ go.mod | 22 +++++++------------- go.sum | 54 ++---------------------------------------------- 3 files changed, 23 insertions(+), 67 deletions(-) create mode 100644 README_golang.md diff --git a/README_golang.md b/README_golang.md new file mode 100644 index 0000000..79665b3 --- /dev/null +++ b/README_golang.md @@ -0,0 +1,14 @@ +# Go dependencies + +For the following commands its recommended to use the Bazel target `rules_go//go`, ensuring a consistent Go version. + +In order to add a dependency, you need to: +1. add it to the global `go.mod` using `bazel run @rules_go//go -- get name@version`. To view the latest version, run `go list -m -versions name`. +2. run `bazel run @rules_go//go -- mod tidy`. + +Since this will update dependencies for _all_ Go programs in the +monorepo, you should retest all the Go dependencies afterwards: + +``` +bazel test $(bazel query 'kind(go_.*, //...) except attr("tags", "manual", //...)') +``` diff --git a/go.mod b/go.mod index be432c0..38b69f9 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module ksniff +go 1.23 + require ( github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect github.com/elazarl/goproxy v1.2.1 // indirect @@ -11,12 +13,10 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.3 - github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.1.3 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.7.0 - gopkg.in/ini.v1 v1.51.1 // indirect k8s.io/api v0.20.6 k8s.io/apimachinery v0.20.6 k8s.io/cli-runtime v0.20.6 @@ -34,9 +34,6 @@ require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect - github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5 // indirect - github.com/emicklei/go-restful v2.11.1+incompatible // indirect github.com/evanphx/json-patch v4.9.0+incompatible // indirect github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect @@ -44,19 +41,14 @@ require ( github.com/go-logr/logr v0.2.0 // indirect github.com/go-openapi/jsonpointer v0.19.3 // indirect github.com/go-openapi/jsonreference v0.19.3 // indirect - github.com/go-openapi/spec v0.19.5 // indirect - github.com/go-openapi/swag v0.19.6 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.4.3 // indirect github.com/google/btree v1.0.0 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/googleapis/gnostic v0.4.1 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/imdario/mergo v0.3.8 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/json-iterator/go v1.1.10 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.1 // indirect github.com/mailru/easyjson v0.7.0 // indirect @@ -70,11 +62,11 @@ require ( github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect - golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect - golang.org/x/text v0.3.4 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect google.golang.org/appengine v1.6.5 // indirect google.golang.org/protobuf v1.25.0 // indirect @@ -90,4 +82,4 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) -go 1.17 +require golang.org/x/term v0.27.0 // indirect diff --git a/go.sum b/go.sum index d5bfb4f..f3ff6a0 100644 --- a/go.sum +++ b/go.sum @@ -157,9 +157,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw 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.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -345,7 +344,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -363,10 +361,6 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -399,11 +393,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -431,13 +420,6 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -453,12 +435,6 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -488,26 +464,9 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -516,12 +475,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -565,13 +518,10 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= From a345d4000f0299c14344a6366283e5bf3e7620a3 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Mon, 26 Jan 2026 11:17:40 +0100 Subject: [PATCH 4/5] [sniff] Update sniff with better exit handling and explicit image pull secret flag --- .pre-commit-config.yaml | 88 +++++++++++++++++++ README.md | 29 +++--- go.mod | 49 ++++++----- go.sum | 87 ++++++++++-------- kube/kubernetes_api_service.go | 12 ++- pkg/cmd/sniff.go | 58 +++++++----- pkg/cmd/sniff_test.go | 19 ++++ pkg/config/settings.go | 1 + .../sniffer/privileged_pod_sniffer_service.go | 1 + 9 files changed, 250 insertions(+), 94 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..2fc4045 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,88 @@ +# Copyright (C) 2021 Cisco Systems, Inc. + +# See CONTRIBUTING.md for instructions. +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +# +# Hook naming convention - prefixes by category (run order): +# Format: - Code formatters that modify files +# Deps: - Dependency/lockfile hygiene (go mod tidy, pip-compile, tfupdate) +# Lint: - Static analysis/linting (read-only checks) +# Files: - File hygiene checks (whitespace, symlinks, text normalization) +# License: - Copyright/license checks +# SAST: - Security static analysis (secrets, config scanning, code analysis) +# Vuln: - Dependency vulnerability scanning + +repos: + # ============================================================================ + # FORMAT: Code formatters (modify files) + # ============================================================================ + - repo: https://github.com/keith/pre-commit-buildifier + rev: 8.2.1 + hooks: + - id: buildifier + name: "Format: buildifier" + args: + # Keep this argument in sync with .bazelci/presubmit.yml and the buildifier-lint hook in LINT section + - --warnings=-bzl-visibility,-function-docstring-args,-function-docstring-return,-print,-unnamed-macro,-provider-params,-function-docstring-header,-no-effect,-uninitialized,-rule-impl-return + + # ============================================================================ + # LINT: Static analysis (read-only checks) + # ============================================================================ + - repo: https://github.com/keith/pre-commit-buildifier + rev: 8.2.1 + hooks: + - id: buildifier-lint + name: "Lint: buildifier-lint" + args: + # Keep this argument in sync with the buildifier hook in FORMAT section + - --warnings=-bzl-visibility,-function-docstring-args,-function-docstring-return,-print,-unnamed-macro,-provider-params,-function-docstring-header,-no-effect,-uninitialized,-rule-impl-return + + - repo: https://github.com/syntaqx/git-hooks + rev: v0.0.18 + hooks: + - id: shellcheck + name: "Lint: shellcheck" + exclude: "aws_launch_template_.*" + + - repo: git@github.com:omnicate/pre-commit-policy-bot.git + rev: v0.0.2 + hooks: + - id: validate + name: "Lint: policy-bot-validate" + + # ============================================================================ + # FILES: File hygiene checks + # ============================================================================ + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + name: "Files: trailing-whitespace" + args: [--markdown-linebreak-ext=md] + # provisioning-tools has test fixtures with significant formatting + exclude: "connectivity/provisioning-tools/src/test/kotlin/io/omnicate/connectivity/tools/(ImportSimCardsTest|mvno/MappingTest).kt" + - id: check-merge-conflict + name: "Files: check-merge-conflict" + - id: end-of-file-fixer + name: "Files: end-of-file-fixer" + exclude: ".*\\.svg$|voice/li/test/data/.*\\.bin$|copyright.txt|vendor-images/README.md" + - id: check-executables-have-shebangs + name: "Files: check-executables-have-shebangs" + - id: check-shebang-scripts-are-executable + name: "Files: check-shebang-scripts-are-executable" + exclude: "aws_launch_template_.*" + - id: check-symlinks + name: "Files: check-symlinks" + - id: destroyed-symlinks + name: "Files: destroyed-symlinks" + + - repo: https://github.com/sirosen/texthooks + rev: 0.7.1 + hooks: + - id: fix-smartquotes + name: "Files: fix-smartquotes" + exclude: ".+\\.vue$|.+\\.vm|.+\\.java|.+\\.kt|.+\\.xml|.+\\.json|.+\\.sql|.*.gen.go$" + - id: fix-ligatures + name: "Files: fix-ligatures" + exclude: ".+\\.vue$|.+\\.vm|.+\\.java|.+\\.kt|.+\\.xml|.+\\.json|.+\\.sql|.*.gen.go$" diff --git a/README.md b/README.md index 3cf50c5..07e01ba 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ Ksniff [isn't production ready yet](https://github.com/eldadru/ksniff/issues/96# Installation via krew (https://github.com/GoogleContainerTools/krew) kubectl krew install sniff - -For manual installation, download the latest release package, unzip it and use the attached makefile: + +For manual installation, download the latest release package, unzip it and use the attached makefile: unzip ksniff.zip make install @@ -42,11 +42,11 @@ Requirements: 2. go 1.11 or newer Compiling: - + linux: make linux windows: make windows mac: make darwin - + To compile a static tcpdump binary: @@ -56,10 +56,10 @@ To compile a static tcpdump binary: kubectl < 1.12: kubectl plugin sniff [-n ] [-c ] [-i ] [-f ] [-o OUTPUT_FILE] [-l LOCAL_TCPDUMP_FILE] [-r REMOTE_TCPDUMP_FILE] - + kubectl >= 1.12: kubectl sniff [-n ] [-c ] [-i ] [-f ] [-o OUTPUT_FILE] [-l LOCAL_TCPDUMP_FILE] [-r REMOTE_TCPDUMP_FILE] - + POD_NAME: Required. the name of the kubernetes pod to start capture it's traffic. NAMESPACE_NAME: Optional. Namespace name. used to specify the target namespace to operate on. CONTAINER_NAME: Optional. If omitted, the first container in the pod will be chosen. @@ -71,9 +71,16 @@ To compile a static tcpdump binary: #### Air gapped environments Use `--image` and `--tcpdump-image` flags (or KUBECTL_PLUGINS_LOCAL_FLAG_IMAGE and KUBECTL_PLUGINS_LOCAL_FLAG_TCPDUMP_IMAGE environment variables) to override the default container images and use your own e.g (docker): - + kubectl plugin sniff [-n ] [-c ] --image /docker --tcpdump-image /tcpdump - + +#### Private registry with imagePullSecret +If your custom images are stored in a private registry that requires authentication, you can specify an imagePullSecret using the `--image-pull-secret` flag (or KUBECTL_PLUGINS_LOCAL_FLAG_IMAGE_PULL_SECRET environment variable): + + kubectl plugin sniff [-n ] [-c ] --image /docker --tcpdump-image /tcpdump --image-pull-secret + +The secret must exist in the same namespace where the ksniff privileged pod will be created. + #### Non-Privileged and Scratch Pods To reduce attack surface and have small and lean containers, many production-ready containers runs as non-privileged user @@ -82,7 +89,7 @@ or even as a scratch container. To support those containers as well, ksniff now ships with the "-p" (privileged) mode. When executed with the -p flag, ksniff will create a new pod on the remote kubernetes cluster that will have access to the node docker daemon. -ksniff will than use that pod to execute a container attached to the target container network namespace +ksniff will than use that pod to execute a container attached to the target container network namespace and perform the actual network capture. #### Piping output to stdout @@ -94,12 +101,12 @@ Example using `tshark`: kubectl sniff pod-name -f "port 80" -o - | tshark -r - ### Contribution -More than welcome! please don't hesitate to open bugs, questions, pull requests +More than welcome! please don't hesitate to open bugs, questions, pull requests ### Future Work 1. Instead of uploading static tcpdump, use the future support of "kubectl debug" feature (https://github.com/kubernetes/community/pull/649) which should be a much cleaner solution. - + ### Known Issues #### Wireshark and TShark cannot read pcap diff --git a/go.mod b/go.mod index 38b69f9..7468dc2 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,22 @@ module ksniff -go 1.23 +go 1.23.0 + +toolchain go1.23.4 require ( github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect - github.com/elazarl/goproxy v1.2.1 // indirect - github.com/emicklei/go-restful v2.16.0+incompatible // indirect github.com/go-openapi/spec v0.19.5 // indirect github.com/go-openapi/swag v0.19.6 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/imdario/mergo v0.3.8 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.8.3 - github.com/spf13/cobra v1.1.3 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.7.0 - github.com/stretchr/testify v1.7.0 + github.com/sirupsen/logrus v1.9.4 + github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 + github.com/spf13/viper v1.21.0 + github.com/stretchr/testify v1.11.1 k8s.io/api v0.20.6 k8s.io/apimachinery v0.20.6 k8s.io/cli-runtime v0.20.6 @@ -36,44 +36,43 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v4.9.0+incompatible // indirect github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-logr/logr v0.2.0 // indirect github.com/go-openapi/jsonpointer v0.19.3 // indirect github.com/go-openapi/jsonreference v0.19.3 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.4.3 // indirect github.com/google/btree v1.0.0 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/googleapis/gnostic v0.4.1 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.10 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/magiconair/properties v1.8.1 // indirect github.com/mailru/easyjson v0.7.0 // indirect - github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/pelletier/go-toml v1.6.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/afero v1.2.2 // indirect - github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/subosito/gotenv v1.2.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect google.golang.org/appengine v1.6.5 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.51.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.4.0 // indirect k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd // indirect k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect @@ -82,4 +81,8 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) -require golang.org/x/term v0.27.0 // indirect +require ( + github.com/elazarl/goproxy v1.2.1 // indirect + github.com/emicklei/go-restful v2.16.0+incompatible // indirect + golang.org/x/term v0.27.0 // indirect +) diff --git a/go.sum b/go.sum index f3ff6a0..d5d44fa 100644 --- a/go.sum +++ b/go.sum @@ -39,7 +39,6 @@ github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8 github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -70,6 +69,7 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -93,9 +93,12 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -122,6 +125,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.6 h1:JSUbVWlaTLMhXeOMyArSUNCdroxZu2j1TcrsOV8Mj7Q= github.com/go-openapi/swag v0.19.6/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -157,8 +162,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw 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.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -175,7 +181,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -199,7 +204,6 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -211,15 +215,15 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -228,15 +232,16 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -254,7 +259,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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= @@ -275,8 +279,8 @@ github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= -github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -298,37 +302,44 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.8.3 h1:DBBfY8eMYazKEJHb3JKpSPfpgd2mBCoNFlQx6C5fftU= -github.com/sirupsen/logrus v1.8.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -336,10 +347,11 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -352,6 +364,8 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -464,9 +478,8 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -475,8 +488,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -521,7 +534,6 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -586,8 +598,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -598,8 +608,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/kube/kubernetes_api_service.go b/kube/kubernetes_api_service.go index 8e7ed51..233a787 100644 --- a/kube/kubernetes_api_service.go +++ b/kube/kubernetes_api_service.go @@ -25,7 +25,7 @@ type KubernetesApiService interface { DeletePod(podName string) error - CreatePrivilegedPod(nodeName string, containerName string, image string, socketPath string, timeout time.Duration, serviceaccount string) (*corev1.Pod, error) + CreatePrivilegedPod(nodeName string, containerName string, image string, socketPath string, timeout time.Duration, serviceaccount string, imagePullSecret string) (*corev1.Pod, error) UploadFile(localPath string, remotePath string, podName string, containerName string) error } @@ -102,7 +102,7 @@ func (k *KubernetesApiServiceImpl) DeletePod(podName string) error { return err } -func (k *KubernetesApiServiceImpl) CreatePrivilegedPod(nodeName string, containerName string, image string, socketPath string, timeout time.Duration, serviceaccount string) (*corev1.Pod, error) { +func (k *KubernetesApiServiceImpl) CreatePrivilegedPod(nodeName string, containerName string, image string, socketPath string, timeout time.Duration, serviceaccount string, imagePullSecret string) (*corev1.Pod, error) { log.Debugf("creating privileged pod on remote node") isSupported, err := k.IsSupportedContainerRuntime(nodeName) @@ -198,6 +198,14 @@ func (k *KubernetesApiServiceImpl) CreatePrivilegedPod(nodeName string, containe podSpecs.ServiceAccountName = serviceaccount } + if imagePullSecret != "" { + podSpecs.ImagePullSecrets = []corev1.LocalObjectReference{ + { + Name: imagePullSecret, + }, + } + } + pod := corev1.Pod{ TypeMeta: typeMetadata, ObjectMeta: objectMetadata, diff --git a/pkg/cmd/sniff.go b/pkg/cmd/sniff.go index 8b9fcce..866ddb6 100644 --- a/pkg/cmd/sniff.go +++ b/pkg/cmd/sniff.go @@ -9,6 +9,7 @@ import ( "os/signal" "path/filepath" "strings" + "sync" "syscall" "time" @@ -54,6 +55,7 @@ type Ksniff struct { settings *config.KsniffSettings snifferService sniffer.SnifferService wireshark *exec.Cmd + cleanupOnce sync.Once } func NewKsniff(settings *config.KsniffSettings) *Ksniff { @@ -155,6 +157,11 @@ func NewCmdSniff(streams genericclioptions.IOStreams) *cobra.Command { _ = viper.BindEnv("serviceaccount", "KUBECTL_PLUGINS_LOCAL_FLAG_SERVICE_ACCOUNT") _ = viper.BindPFlag("serviceaccount", cmd.Flags().Lookup("serviceaccount")) + cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedImagePullSecret, "image-pull-secret", "", "", + "the image pull secret name for private registries (optional)") + _ = viper.BindEnv("image-pull-secret", "KUBECTL_PLUGINS_LOCAL_FLAG_IMAGE_PULL_SECRET") + _ = viper.BindPFlag("image-pull-secret", cmd.Flags().Lookup("image-pull-secret")) + return cmd } @@ -202,6 +209,7 @@ func (o *Ksniff) completeBasicSettings(cmd *cobra.Command, args []string) error o.settings.UseDefaultTCPDumpImage = !viper.IsSet("tcpdump-image") o.settings.UseDefaultSocketPath = !viper.IsSet("socket") o.settings.UserSpecifiedServiceAccount = viper.GetString("serviceaccount") + o.settings.UserSpecifiedImagePullSecret = viper.GetString("image-pull-secret") if o.settings.UserSpecifiedVerboseMode { log.Info("running in verbose mode") @@ -383,6 +391,32 @@ func findLocalTcpdumpBinaryPath() (string, error) { return "", errors.Errorf("couldn't find static tcpdump binary on any of: '%v'", tcpdumpLocalBinaryPathLookupList) } +func (o *Ksniff) performCleanup() { + o.cleanupOnce.Do(func() { + log.Info("starting sniffer cleanup") + + // Kill wireshark if used + if o.wireshark != nil { + if o.wireshark.Process != nil { + err := o.wireshark.Process.Kill() + if err != nil && err != os.ErrProcessDone { + log.WithError(err).Error("failed to kill wireshark process") + } else { + log.Debug("wireshark process killed") + } + } + } + + // Cleanup sniffer service (removes privileged pods, etc.) + err := o.snifferService.Cleanup() + if err != nil { + log.WithError(err).Error("failed to cleanup sniffer, manual cleanup may be required") + } else { + log.Info("sniffer cleanup completed successfully") + } + }) +} + func (o *Ksniff) setupSignalHandler() chan interface{} { signals := make(chan os.Signal, 1) exit := make(chan interface{}) @@ -393,26 +427,9 @@ func (o *Ksniff) setupSignalHandler() chan interface{} { select { case sig := <-signals: if sig == syscall.SIGINT || sig == syscall.SIGTERM { - log.Info("starting sniffer cleanup") - err := o.snifferService.Cleanup() - if err != nil { - log.WithError(err).Error("failed to teardown sniffer, a manual teardown is required.") - } - log.Info("sniffer cleanup completed successfully") - - // Kill wireshark if used - if o.wireshark != nil { - if o.wireshark.Process != nil { - err = o.wireshark.Process.Kill() - if err != nil && err != os.ErrProcessDone { - log.WithError(err).Error("failed to kill wireshark process") - } else { - log.Debug("wireshark process killed") - } - } - } - + o.performCleanup() close(signals) + os.Exit(0) } case <-exit: return @@ -435,8 +452,9 @@ func (o *Ksniff) Run() error { // Ensure sniffer is clean on interrupt closeHandler := o.setupSignalHandler() - // Ensure sniffer is clean on complete + // Ensure sniffer is cleaned up on exit (both normal and error cases) defer func() { + o.performCleanup() closeHandler <- true }() diff --git a/pkg/cmd/sniff_test.go b/pkg/cmd/sniff_test.go index f7172ec..92cd76a 100644 --- a/pkg/cmd/sniff_test.go +++ b/pkg/cmd/sniff_test.go @@ -185,6 +185,25 @@ func TestCompleteBasicSettings_WithTcpdumpImage(t *testing.T) { assert.False(t, settings.UseDefaultTCPDumpImage) } +func TestCompleteBasicSettings_WithImagePullSecret(t *testing.T) { + // given + settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) + sniff := NewKsniff(settings) + cmd := NewCmdSniff(genericclioptions.IOStreams{}) + + // Set image-pull-secret flag + _ = cmd.Flags().Set("image-pull-secret", "my-registry-secret") + + var commands []string + + // when + err := sniff.completeBasicSettings(cmd, append(commands, "test-pod")) + + // then + assert.Nil(t, err) + assert.Equal(t, "my-registry-secret", settings.UserSpecifiedImagePullSecret) +} + func TestCompleteBasicSettings_WithSocket(t *testing.T) { // given settings := config.NewKsniffSettings(genericclioptions.IOStreams{}) diff --git a/pkg/config/settings.go b/pkg/config/settings.go index f8b1e8a..738fbf4 100644 --- a/pkg/config/settings.go +++ b/pkg/config/settings.go @@ -30,6 +30,7 @@ type KsniffSettings struct { SocketPath string UseDefaultSocketPath bool UserSpecifiedServiceAccount string + UserSpecifiedImagePullSecret string } func NewKsniffSettings(streams genericclioptions.IOStreams) *KsniffSettings { diff --git a/pkg/service/sniffer/privileged_pod_sniffer_service.go b/pkg/service/sniffer/privileged_pod_sniffer_service.go index 9c101a5..830b742 100644 --- a/pkg/service/sniffer/privileged_pod_sniffer_service.go +++ b/pkg/service/sniffer/privileged_pod_sniffer_service.go @@ -49,6 +49,7 @@ func (p *PrivilegedPodSnifferService) Setup() error { p.settings.SocketPath, p.settings.UserSpecifiedPodCreateTimeout, p.settings.UserSpecifiedServiceAccount, + p.settings.UserSpecifiedImagePullSecret, ) if err != nil { log.WithError(err).Errorf("failed to create privileged pod on node: '%s'", p.settings.DetectedPodNodeName) From d32d6b9f0c61df77867acce0c443706b5b9a1d21 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Tue, 10 Feb 2026 12:19:54 +0100 Subject: [PATCH 5/5] [ksniff] update go version --- MODULE.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index 033a230..9d91cdd 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -8,7 +8,7 @@ bazel_dep(name = "gazelle", version = "0.47.0") bazel_dep(name = "platforms", version = "1.0.0") go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") -go_sdk.download(version = "1.23.4") +go_sdk.download(version = "1.25.7") go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//:go.mod")