Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ For running in airgapped environments (partially or fully disconnected), see [di
| `HEAP_DUMP_TIMEOUT` | `600` | Timeout for heap dump collection in seconds |
| `HEAP_DUMP_BUFFER_SIZE` | `16777216` | WebSocket buffer size in bytes (16MB) for inspector method |
| `HEAP_DUMP_REMOTE_DIR` | `/tmp` | Directory in container for heap dumps (SIGUSR2 method) |
| `RHDH_OBFUSCATE_DOMAINS` | - | Extra comma-separated domain names to obfuscate when discovery misses them |

### Command-line options

Expand Down Expand Up @@ -145,6 +146,11 @@ Usage: ./must_gather [params...]
When disabled, secret resources are excluded from all collectors
When enabled, secrets are collected but automatically sanitized

--no-obfuscate Skip IP, MAC, and domain obfuscation
Secret sanitization still runs. By default, collected output is
obfuscated before the command exits. See
docs/secret-collection-and-sanitization.md.

> Diagnostic and Troubleshooting Options:
--with-heap-dumps Collect heap dumps from running backstage-backend processes (opt-in, disabled by default)
Heap dumps are collected immediately after pod logs for each deployment/CR
Expand Down Expand Up @@ -227,6 +233,7 @@ Usage: ./must_gather [params...]
| `--cluster-info` | Collect cluster-wide diagnostic information | For comprehensive cluster analysis |
| `--with-secrets` | Include Kubernetes Secrets (sanitized) | For detailed troubleshooting requiring secret metadata |
| `--with-heap-dumps` | Collect heap dumps from backstage-backend containers | For memory leak investigation and performance analysis |
| `--no-obfuscate` | Skip IP, MAC, and domain obfuscation | When you need the original addresses for local debugging |

**Examples:**
- `--with-heap-dumps` - Collect heap dumps for all backstage-backend pods
Expand All @@ -243,6 +250,7 @@ Usage: ./must_gather [params...]
/must-gather/
├── version # Tool version information (e.g., "rhdh-must-gather x.y.z-sha")
├── sanitization-report.txt # Data sanitization summary and details
├── watermark.txt # Records that IP, MAC, and domain obfuscation ran
├── all-routes.txt # All OpenShift routes cluster-wide
├── all-ingresses.txt # All Kubernetes ingresses cluster-wide
├── must-gather.log # Must-gather container logs (if running in pod)
Expand Down
18 changes: 17 additions & 1 deletion docs/secret-collection-and-sanitization.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,20 @@ When secrets are collected (`--with-secrets`), the tool includes automatic sanit
- **Comprehensive coverage** - Processes all YAML, JSON, and text files in the collected data
- **Detailed reporting** - Provides sanitization summary with file and item counts

**Important**: While automatic sanitization catches common sensitive patterns, always review the sanitization report and manually check for any domain-specific sensitive information before sharing externally.
### Automatic obfuscation

After secret sanitization, the gather runs [must-gather-clean](https://github.com/openshift/must-gather-clean) on the collected tree. This step is on by default. It rewrites:

- IP addresses, in file contents and in file paths, using one consistent placeholder per address (`127.0.0.1`, `0.0.0.0`, and `::1` are left as-is)
- MAC addresses, the same way
- Cluster domain names, when they can be discovered. The name in front of the domain is kept (`console.apps.example.com` becomes `console.apps.domain0000000001`) so the gather is still readable

On OpenShift, domain discovery reads the DNS `cluster` base domain, the default ingress controller domain, and the API server hostname. On Kubernetes, and whenever those OpenShift domains cannot be read, discovery uses host names from Ingress resources and OpenShift Routes in the namespaces being collected, plus the API server hostname. In-cluster names such as `cluster.local` and `kubernetes.default.svc` are not treated as customer domains. When none of those names can be read, IP and MAC obfuscation still run, and other hostnames are left unchanged. Set `RHDH_OBFUSCATE_DOMAINS` to a comma-separated list to add domains discovery missed.

ConfigMaps and Secrets are not removed by this step. Secret values are still redacted by the sanitizer above, and secret names stay in the gather when `--with-secrets` was used.

The reversible `report.yaml` map produced by must-gather-clean is not included in the output. Do not copy it into a gather you share. A `watermark.txt` file in the output records that obfuscation ran.

Skip this step with `--no-obfuscate` when you need the original addresses to debug the cluster yourself. Secret sanitization still runs. Heap dumps collected with `--with-heap-dumps` are included in this pass, so use `--no-obfuscate` when the snapshot must keep raw addresses.

**Important**: While automatic sanitization and obfuscation catch common sensitive patterns and cover discovered domains, IPs, and MAC addresses, always review the must-gather output and check for any domain-specific sensitive information before sharing externally.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.26.0

require (
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
github.com/openshift/must-gather-clean v0.0.5
github.com/openshift/oc v0.0.0-alpha.0.0.20260902120315-e0e4c04430bf
github.com/spf13/cobra v1.10.2
go.yaml.in/yaml/v3 v3.0.5
Expand Down Expand Up @@ -114,6 +115,7 @@ require (
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.37.0 // indirect
k8s.io/apiserver v0.37.0 // indirect
k8s.io/component-base v0.37.1 // indirect
Expand Down
18 changes: 2 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/openshift/api v0.0.0-20260805215214-cfb63858e9d7 h1:Z6p+yoWjFXbfnN2zPdQ8SRXPKDs9QtT3P8hN3SP2zuw=
github.com/openshift/api v0.0.0-20260805215214-cfb63858e9d7/go.mod h1:k6qH5QOVa5GDln2VVm8Jz4NV3Z7R2SATHFLwGS6Wh3M=
github.com/openshift/must-gather-clean v0.0.5 h1:C7Hv/0fqaVSnW3IK95+/q2JhU6Mf4am2gVNUlIYYyKk=
github.com/openshift/must-gather-clean v0.0.5/go.mod h1:vD9n9B4iX/AkZ+n0FSNY1Wmc7kpGMfNsQ291i7J/RV0=
github.com/openshift/oc v0.0.0-alpha.0.0.20260902120315-e0e4c04430bf h1:fmzW3O1TIDHfNMVPZa9j+0uH6GG/CVi2FQ6qPCyN35s=
github.com/openshift/oc v0.0.0-alpha.0.0.20260902120315-e0e4c04430bf/go.mod h1:Te41zokhpcNHiXgDW4/lENy47Q7ERVD7ASJPoXcRy68=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
Expand Down Expand Up @@ -400,44 +402,28 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
helm.sh/helm/v4 v4.3.0 h1:wLRTNXzy96ro7waurWMsymlUPaCQOj5nokpJZJNen/w=
helm.sh/helm/v4 v4.3.0/go.mod h1:p6SMo6BMyg+4H52fJXvRNg1To0a0KGfiPxjVEcHHSk0=
k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4=
k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk=
k8s.io/api v0.37.1 h1:l6N77U7tjwB5L056bgrBTJIEdevac/naBZ3iSvDNfpM=
k8s.io/api v0.37.1/go.mod h1:zSlbB1YpJ1YQlFVQy20UYll81UJSJJUMLhkhvg6Z78M=
k8s.io/apiextensions-apiserver v0.37.0 h1:zRMQ3+/LIE5oZ0tVvXwYHC+dIkSP5cjNWju7AZU1LOI=
k8s.io/apiextensions-apiserver v0.37.0/go.mod h1:HU0PfSBwchHL5iDau6jjt9zU6ryWkDDlaVUiq91NK80=
k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0=
k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE=
k8s.io/apimachinery v0.37.1 h1:hGCYyvKHCwtwMitj2vU4vYx0Z16N9GyZk9BBnz0wDAE=
k8s.io/apimachinery v0.37.1/go.mod h1:jF84AyUi/IRIXRot5f+lm6MpxoWI+F1XgjaMmwCdTFw=
k8s.io/apiserver v0.37.0 h1:TXg7OxsOWrAH8J4Zi/gBAZuMw1Dfdd+6cca2h4qjRqo=
k8s.io/apiserver v0.37.0/go.mod h1:OddHDF4gy9qyIb8o/3+qaeP6S0vEObWLgOygVqXksv0=
k8s.io/cli-runtime v0.37.0 h1:U3XakUeirBQJMz5688r04z74SIHSE7V5SIZ6Ho5JyBM=
k8s.io/cli-runtime v0.37.0/go.mod h1:qiQMFkKwFFuPH6zy953On+nc3qfpEHAIDrJmAuRz5Vg=
k8s.io/cli-runtime v0.37.1 h1:3mir5bM4XjMJHW3SMRk++3Ylf4YQne0XFFsW+IGT85U=
k8s.io/cli-runtime v0.37.1/go.mod h1:g3VQOm71f//aNbp79g0az9jBRTrvPjAHz7WxVysbZ4M=
k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ=
k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0=
k8s.io/client-go v0.37.1 h1:QTv/5ha4jAHtW9qxxVBkQVFBRDb4jHfFopQqqMdc+wM=
k8s.io/client-go v0.37.1/go.mod h1:dnAPtTnCNY38Ho04D2KdY1F4IKausa9UbqaAZKl60SY=
k8s.io/component-base v0.37.0 h1:3SdSa4+itMdFTDFTeR8CxKGmSTSMXFlKL4ky8OqjguM=
k8s.io/component-base v0.37.0/go.mod h1:LjOebp4R9y6LODWZQv102ZQxGheLcDO2ZJLAw6bbh4I=
k8s.io/component-base v0.37.1 h1:93DMmlENnK7gNLkL4pMqLO5M/HVf3p3sM4q/GasLveY=
k8s.io/component-base v0.37.1/go.mod h1:bBrdziT4dreQG5lzTaPVxBwsZe1oxphG+ZVdD56dQWQ=
k8s.io/component-helpers v0.37.0 h1:tPz4goLftoiUBfYWZgPlP4Srkd/PciNz9q+0ReOfyxY=
k8s.io/component-helpers v0.37.0/go.mod h1:wDAmi8hduKu3YGqKg5a0wxCpoql39DkTmdQ60bpLpkc=
k8s.io/component-helpers v0.37.1 h1:V6ueiH2SBqpZil1X9+uMK8GAwsGYPVfoRN9eA5vY2G8=
k8s.io/component-helpers v0.37.1/go.mod h1:Ib5+txJA7lG9/xCdRrPshX8GnRRTXPElxtxo+bFQ0Y8=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A=
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I=
k8s.io/kubectl v0.37.0 h1:cici6hiofx93ASldmprDmZF55SfhVt4o3HniltVLjTc=
k8s.io/kubectl v0.37.0/go.mod h1:RSeEl8e/yqDx6srG8Azr0uAtVPNIZljA0PNh9HCBcdg=
k8s.io/kubectl v0.37.1 h1:n1VCIntOJiX943uByXCThSZQmE+4v9jGS8S9u94/aHU=
k8s.io/kubectl v0.37.1/go.mod h1:66cc4Bz8PxBTxlrHzBuzXEzGIytAnNasKQPeEcpuaV8=
k8s.io/streaming v0.37.0 h1:iPBUZLZiKt5bV+lxJurASMOV07VuBhNpiwJt2//AWrM=
k8s.io/streaming v0.37.0/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU=
k8s.io/streaming v0.37.1 h1:TpzVfQeFuVndn2g9mFqxy1UcUYPwDzqjUmwR/IzJCWc=
k8s.io/streaming v0.37.1/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU=
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
Expand Down
26 changes: 23 additions & 3 deletions internal/cli/gather.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"errors"
"fmt"
"io"
"os"
Expand All @@ -19,10 +20,11 @@ import (
"github.com/redhat-developer/rhdh-must-gather/internal/kube"
"github.com/redhat-developer/rhdh-must-gather/internal/log"
"github.com/redhat-developer/rhdh-must-gather/internal/namespace"
"github.com/redhat-developer/rhdh-must-gather/internal/obfuscate"
"github.com/redhat-developer/rhdh-must-gather/internal/sanitize"
)

func runGather(cmd *cobra.Command, opts *gatherOptions) error {
func runGather(cmd *cobra.Command, opts *gatherOptions) (err error) {
log.Init()

basePath := os.Getenv("BASE_COLLECTION_PATH")
Expand All @@ -34,7 +36,7 @@ func runGather(cmd *cobra.Command, opts *gatherOptions) error {
logLevel = "info"
}

if err := os.MkdirAll(basePath, 0o755); err != nil {
if err = os.MkdirAll(basePath, 0o755); err != nil {
return fmt.Errorf("creating output directory: %w", err)
}

Expand All @@ -43,10 +45,28 @@ func runGather(cmd *cobra.Command, opts *gatherOptions) error {

var interrupted atomic.Bool
sanitizeStop := make(chan struct{})
var kubeClient *kube.Client

defer func() {
log.Info("done with data collection. Now sanitizing data...")
sanitize.Run(basePath, sanitizeStop)
if opts.noObfuscate {
log.Info("Obfuscation disabled; collected output keeps IP addresses, MAC addresses, and domain names")
return
}
select {
case <-sanitizeStop:
log.Error("Obfuscation aborted. Do not share this output.")
err = errors.Join(err, fmt.Errorf("obfuscation aborted"))
return
default:
}
log.Info("Obfuscating IP addresses, MAC addresses, and cluster domain names...")
if oerr := obfuscate.Run(context.Background(), kubeClient, basePath, resolveNamespaces(opts), runCleanSubprocess); oerr != nil {
log.Error("Obfuscation failed: %v", oerr)
log.Error("Collected output was not obfuscated. Do not share it.")
err = errors.Join(err, fmt.Errorf("obfuscating must-gather output: %w", oerr))
}
}()

sigCh := make(chan os.Signal, 1)
Expand All @@ -73,7 +93,7 @@ func runGather(cmd *cobra.Command, opts *gatherOptions) error {
return fmt.Errorf("writing version file: %w", err)
}

kubeClient, err := kube.NewClient()
kubeClient, err = kube.NewClient()
if err != nil {
return fmt.Errorf("failed to create Kubernetes client: %w", err)
}
Expand Down
70 changes: 70 additions & 0 deletions internal/cli/obfuscate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cli

import (
"fmt"
"os"
"os/exec"
"runtime"
"strconv"

"github.com/spf13/cobra"

"github.com/redhat-developer/rhdh-must-gather/internal/obfuscate"
)

func newObfuscateCmd() *cobra.Command {
var input, output, reportDir, config string
var workers int

cmd := &cobra.Command{
Use: "obfuscate",
Short: "Obfuscate an existing must-gather directory",
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
return obfuscate.Clean(config, input, output, reportDir, workers)
},
SilenceUsage: true,
SilenceErrors: true,
}
flags := cmd.Flags()
flags.StringVar(&config, "config", "", "Path to the must-gather-clean config file")
flags.StringVar(&input, "input", "", "Directory of the collected must-gather")
flags.StringVar(&output, "output", "", "Directory for the obfuscated output")
flags.StringVar(&reportDir, "report-dir", "", "Directory for report.yaml, which must stay out of the published gather")
flags.IntVar(&workers, "workers", runtime.GOMAXPROCS(0), "Number of must-gather-clean workers")
for _, name := range []string{"config", "input", "output", "report-dir"} {
if err := cmd.MarkFlagRequired(name); err != nil {
panic(err)
}
}
return cmd
}

// runCleanSubprocess re-executes this binary so must-gather-clean's klog.Exitf
// cannot terminate the collector. That Exitf runs on the library's own error
// goroutine, so a BehaviorOnFatal hook in this process does not catch it.
// The parent keeps the original tree when the child fails.
func runCleanSubprocess(configPath, inputPath, outputPath, reportDir string) error {
exe, err := os.Executable()
if err != nil {
return fmt.Errorf("finding gather executable: %w", err)
}
cmd := exec.Command(exe, obfuscateCommandArgs(configPath, inputPath, outputPath, reportDir, runtime.GOMAXPROCS(0))...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("must-gather-clean failed: %w", err)
}
return nil
}

func obfuscateCommandArgs(configPath, inputPath, outputPath, reportDir string, workers int) []string {
return []string{
"obfuscate",
"--config", configPath,
"--input", inputPath,
"--output", outputPath,
"--report-dir", reportDir,
"--workers", strconv.Itoa(workers),
}
}
6 changes: 6 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type gatherOptions struct {
clusterInfo bool
since string
sinceTime string
noObfuscate bool
}

func newRootCmd() *cobra.Command {
Expand Down Expand Up @@ -81,6 +82,7 @@ from both Helm-based and Operator-managed RHDH instances.`,
flags.StringVar(&opts.heapDumpMethod, "heap-dump-method", "inspector", "Heap dump collection method: inspector or sigusr2")
flags.StringVar(&opts.heapDumpInstances, "heap-dump-instances", "", "Comma-separated list of instance names to collect heap dumps from")
flags.BoolVar(&opts.clusterInfo, "cluster-info", false, "Collect cluster-wide diagnostic information")
flags.BoolVar(&opts.noObfuscate, "no-obfuscate", false, "Skip IP, MAC, and domain obfuscation (secret sanitization still runs)")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest not exposing this flag for now. In my understanding, the consistent replacements (x-ipv4-..., domain0000000001) should preserve the structure we need for debugging, so I guess most analysis should still work on obfuscated output.
If the need or complaints come later, we could consider adding it, but for now, I think it should just be the opinionated behavior to obfuscate (similar to the automatic sanitization which is done with no option to skip). WDYT?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to keep the flag. Obfuscation is already the default, the same way sanitization always runs. The consistent tokens are enough for most debugging. Heap dumps collected with --with-heap-dumps go through this pass, and looking at a memory snapshot needs the real addresses. The flag is also the way out when the clean step fails and the command refuses to publish the result. The docs say to pass --no-obfuscate for heap snapshots and for local debugging.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flag is also the way out when the clean step fails and the command refuses to publish the result.

One of the general rules for must-gathers is to make it straightforward for customers to run when they are already facing issues with the main application like RHDH. So if our must-gather fails because of an issue in must-gather-clean, it should just fall back gracefully and and return whatever it collected. It is usually advised not to require users to re-run with another flag, as this would add friction here.
So IMO our must-gather must always ensure to fall back gracefully and not block publishing even if there are failures in the middle and return whatever it was able to capture.

The docs say to pass --no-obfuscate for heap snapshots and for local debugging.

So this means disabling all obfuscation entirely for collecting heap snapshots, which sounds a bit confusing IMO. Maybe a more targeted solution would be to exclude .heapsnapshot files from obfuscation via the must-gather-clean config instead?
As for local debugging, I don't think that's an issue. If you're debugging locally, you already have access to the real data; the must-gather ourput is for customers to share with support, where obfuscation matters.

looking at a memory snapshot needs the real addresses.

Why would this be needed? Node heap snapshot files are just regular text files, so it should be okay if any IP, MAC addresses are obfuscated. Obfuscation returns consistent strings that will show up in the heapsnapshot files, which is fine IMO. I've just checked and the structural integrity of the JSON is preserved, and only strings held in JS memory that happen to match IP/MAC/domain patterns would be replaced. This should be the default behavior IMO even for heap snapshots.

So all this means to me that, similar to the secret sanitization, obfuscation should always be run as part of the must-gather, not opt-out.

flags.StringVar(&opts.since, "since", "", "Only collect logs newer than a relative duration (e.g. 5s, 2m, 3h)")
flags.StringVar(&opts.sinceTime, "since-time", "", "Only collect logs after a specific date (RFC3339, e.g. 2006-01-02T15:04:05Z)")

Expand All @@ -89,6 +91,10 @@ from both Helm-based and Operator-managed RHDH instances.`,
}

cmd.SetVersionTemplate("rhdh-must-gather {{.Version}}\n")
// The hidden obfuscate command makes this a parent command. Without an
// explicit Args func, Cobra then rejects positional tokens as unknown commands.
cmd.Args = cobra.ArbitraryArgs
cmd.AddCommand(newObfuscateCmd())
Comment thread
Fortune-Ndlovu marked this conversation as resolved.
Comment thread
rm3l marked this conversation as resolved.

return cmd
}
Expand Down
78 changes: 78 additions & 0 deletions internal/cli/root_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package cli

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -120,6 +123,81 @@ func TestGetVersion_Compiled(t *testing.T) {
}
}

func TestNoObfuscateFlag(t *testing.T) {
cmd := newRootCmd()
cmd.RunE = func(cmd *cobra.Command, args []string) error { return nil }
cmd.SetArgs([]string{"--no-obfuscate"})
if err := cmd.Execute(); err != nil {
t.Fatalf("Execute: %v", err)
}
got, err := cmd.Flags().GetBool("no-obfuscate")
if err != nil {
t.Fatal(err)
}
if !got {
t.Fatal("no-obfuscate = false, want true")
}
}

func TestObfuscateSubcommandCleansTree(t *testing.T) {
input := t.TempDir()
output := t.TempDir()
report := t.TempDir()
if err := os.WriteFile(filepath.Join(input, "kubelet.log"), []byte("node 10.9.8.7\n"), 0o644); err != nil {
t.Fatal(err)
}
config := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(config, []byte(obfuscateConfig), 0o600); err != nil {
t.Fatal(err)
}

args := obfuscateCommandArgs(config, input, output, report, 2)
cmd := newRootCmd()
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Fatalf("Execute obfuscate: %v", err)
}
body, err := os.ReadFile(filepath.Join(output, "kubelet.log"))
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(body), "10.9.8.7") {
t.Fatalf("IP was not obfuscated: %s", body)
}
if _, err := os.Stat(filepath.Join(output, "report.yaml")); !os.IsNotExist(err) {
t.Fatalf("report.yaml published in output: %v", err)
}
if _, err := os.Stat(filepath.Join(report, "report.yaml")); err != nil {
t.Fatalf("report.yaml missing from report dir: %v", err)
}
}

const obfuscateConfig = `config:
obfuscate:
- type: IP
replacementType: Consistent
target: All
- type: MAC
replacementType: Consistent
target: All
`

func TestPositionalArgsAllowed(t *testing.T) {
cmd := newRootCmd()
var got []string
cmd.RunE = func(cmd *cobra.Command, args []string) error {
got = args
return nil
}
cmd.SetArgs([]string{"some-collector-token"})
if err := cmd.Execute(); err != nil {
t.Fatalf("positional arg should be allowed, got: %v", err)
}
if len(got) != 1 || got[0] != "some-collector-token" {
t.Fatalf("args = %#v", got)
}
}

func TestUnknownFlagsAllowed(t *testing.T) {
cmd := newRootCmd()
cmd.RunE = func(cmd *cobra.Command, args []string) error { return nil }
Expand Down
Loading
Loading