Files
wehub-resource-sync 36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:06 +08:00

827 lines
24 KiB
Go

//go:build linux || darwin || freebsd || windows
package app
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"runtime"
"strconv"
"strings"
"github.com/pranshuparmar/witr/internal/output"
"github.com/pranshuparmar/witr/internal/pipeline"
procpkg "github.com/pranshuparmar/witr/internal/proc"
"github.com/pranshuparmar/witr/internal/source"
"github.com/pranshuparmar/witr/internal/target"
"github.com/pranshuparmar/witr/internal/tui"
"github.com/pranshuparmar/witr/pkg/model"
"github.com/spf13/cobra"
)
var (
version = "v0.0.0-dev"
commit = "unknown"
buildDate = "unknown"
)
var rootCmd = &cobra.Command{
Use: "witr [process name...]",
Short: "Why is this running?",
Long: "witr explains why a process or port is running by tracing its ancestry.",
Args: cobra.ArbitraryArgs,
CompletionOptions: cobra.CompletionOptions{
HiddenDefaultCmd: false,
DisableDefaultCmd: false,
DisableNoDescFlag: false,
},
Example: _genExamples(),
RunE: runApp,
}
func _genExamples() string {
return `
# Inspect a running process by name
witr nginx
# Look up a process by PID
witr --pid 1234
# Find the process listening on a specific port
witr --port 5432
# Find the process holding a file open
witr --file /var/lib/dpkg/lock
# Inspect a container by name
witr --container redis
# Inspect a process by name with exact matching (no fuzzy search)
witr bun --exact
# Show the full process ancestry (who started whom)
witr postgres --tree
# Show only warnings (suspicious env, arguments, parents)
witr docker --warnings
# Display only environment variables of the process
witr node --env
# Short, single-line output (useful for scripts)
witr sshd --short
# Disable colorized output (CI or piping)
witr redis --no-color
# Output machine-readable JSON
witr chrome --json
# Show extended process information (memory, I/O, file descriptors)
witr mysql --verbose
# Combine flags: inspect port, show environment variables, output JSON
witr --port 8080 --env --json
# Multiple inputs
witr nginx node
witr --port 8080 --port 3000
witr --pid 1234 --pid 5678
# Mixed inputs
witr nginx --pid 1234 --port 8080
`
}
// Exit codes
const (
ExitOK = 0
ExitWarnings = 1
ExitNotFound = 2
ExitPermission = 3
ExitInvalidInput = 4
// ExitInternalError is distinct from ExitWarnings so scripts can tell an
// unexpected witr failure apart from "process found, has warnings".
ExitInternalError = 5
)
// exitCodeError wraps an error with a specific exit code.
type exitCodeError struct {
code int
err error
}
func (e *exitCodeError) Error() string { return e.err.Error() }
func (e *exitCodeError) Unwrap() error { return e.err }
func withExitCode(code int, err error) error {
return &exitCodeError{code: code, err: err}
}
func Execute() {
err := rootCmd.Execute()
if err == nil {
return
}
var ece *exitCodeError
if errors.As(err, &ece) {
os.Exit(ece.code)
}
os.Exit(1)
}
func init() {
rootCmd.InitDefaultCompletionCmd()
rootCmd.Version = version
rootCmd.SetVersionTemplate(fmt.Sprintf("witr {{.Version}} (commit %s, built %s)\n", commit, buildDate))
rootCmd.SetErr(output.NewSafeTerminalWriter(os.Stderr))
rootCmd.Flags().StringSliceP("pid", "p", nil, "pid(s) to look up (repeatable)")
rootCmd.Flags().StringSliceP("port", "o", nil, "port(s) to look up (repeatable)")
rootCmd.Flags().StringSliceP("file", "f", nil, "file(s) held open by a process (repeatable)")
rootCmd.Flags().StringSliceP("container", "c", nil, "container(s) to look up (repeatable)")
rootCmd.Flags().BoolP("short", "s", false, "show only ancestry")
rootCmd.Flags().BoolP("tree", "t", false, "show only ancestry as a tree")
rootCmd.Flags().Bool("json", false, "show result as JSON")
rootCmd.Flags().Bool("warnings", false, "show only warnings")
rootCmd.Flags().Bool("no-color", false, "disable colorized output")
rootCmd.Flags().Bool("env", false, "show environment variables for the process")
rootCmd.Flags().Bool("verbose", false, "show extended process information")
rootCmd.Flags().BoolP("exact", "x", false, "use exact name matching (no substring search)")
rootCmd.Flags().BoolP("interactive", "i", false, "interactive mode (TUI)")
}
// appFlags holds all parsed CLI flags for convenience.
type appFlags struct {
short bool
tree bool
json bool
warn bool
noColor bool
verbose bool
exact bool
env bool
}
func runApp(cmd *cobra.Command, args []string) error {
interactiveFlag, _ := cmd.Flags().GetBool("interactive")
if interactiveFlag {
return runInteractive()
}
envFlag, _ := cmd.Flags().GetBool("env")
pidFlags, _ := cmd.Flags().GetStringSlice("pid")
portFlags, _ := cmd.Flags().GetStringSlice("port")
fileFlags, _ := cmd.Flags().GetStringSlice("file")
containerFlags, _ := cmd.Flags().GetStringSlice("container")
if !envFlag && len(pidFlags) == 0 && len(portFlags) == 0 && len(fileFlags) == 0 && len(containerFlags) == 0 && len(args) == 0 {
return runInteractive()
}
flags := appFlags{
env: envFlag,
exact: boolFlag(cmd, "exact"),
short: boolFlag(cmd, "short"),
tree: boolFlag(cmd, "tree"),
json: boolFlag(cmd, "json"),
warn: boolFlag(cmd, "warnings"),
noColor: boolFlag(cmd, "no-color"),
verbose: boolFlag(cmd, "verbose"),
}
// Collect all targets preserving command-line order
targets := collectTargetsInOrder(os.Args[1:], args, flagTakesValue(cmd))
if len(targets) == 0 {
return withExitCode(ExitInvalidInput, fmt.Errorf("must specify --pid, --port, --file, --container, or a process name"))
}
outw := cmd.OutOrStdout()
outp := output.NewPrinter(outw)
multiMode := len(targets) > 1
colorEnabled := useColor(flags, outw)
// For JSON multi-output, collect all JSON strings and wrap in array
var jsonResults []string
highestExit := ExitOK
for i, t := range targets {
if multiMode && !flags.json {
printDivider(outp, t, colorEnabled, i > 0)
}
exitCode := processTarget(cmd, outw, outp, t, flags, multiMode, &jsonResults)
if exitCode > highestExit {
highestExit = exitCode
}
}
// Emit JSON array for multi-target
if flags.json && multiMode {
indented := make([]string, len(jsonResults))
for i, r := range jsonResults {
lines := strings.Split(r, "\n")
for j := range lines {
if j > 0 {
lines[j] = " " + lines[j]
}
}
indented[i] = " " + strings.Join(lines, "\n")
}
fmt.Fprintf(outw, "[\n%s\n]\n", strings.Join(indented, ",\n"))
}
if highestExit > ExitOK {
cmd.SilenceErrors = true
return withExitCode(highestExit, fmt.Errorf("completed with exit code %d", highestExit))
}
return nil
}
func boolFlag(cmd *cobra.Command, name string) bool {
v, _ := cmd.Flags().GetBool(name)
return v
}
// flagTakesValue reports whether a raw argv token names a non-boolean flag
// whose value is the following token (e.g. "--config foo"). It lets the
// order-preserving parser take flag-arity from cobra's flag set instead of
// assuming every non-target flag is boolean — so a future string-valued flag
// won't have its value mistaken for a target.
func flagTakesValue(cmd *cobra.Command) func(string) bool {
return func(arg string) bool {
if strings.Contains(arg, "=") {
return false // value is attached: --flag=value
}
if name, ok := strings.CutPrefix(arg, "--"); ok {
if f := cmd.Flags().Lookup(name); f != nil {
return f.NoOptDefVal == ""
}
} else if sh, ok := strings.CutPrefix(arg, "-"); ok && len(sh) == 1 {
if f := cmd.Flags().ShorthandLookup(sh); f != nil {
return f.NoOptDefVal == ""
}
}
return false
}
}
// collectTargetsInOrder walks the raw command-line arguments to build a target
// list that preserves the order the user typed them in. takesValue reports
// whether a non-target flag consumes the following token as its value.
func collectTargetsInOrder(rawArgs []string, positionalArgs []string, takesValue func(string) bool) []model.Target {
var targets []model.Target
positionalIdx := 0
// Map flag names to target types
flagType := map[string]model.TargetType{
"-p": model.TargetPID, "--pid": model.TargetPID,
"-o": model.TargetPort, "--port": model.TargetPort,
"-f": model.TargetFile, "--file": model.TargetFile,
"-c": model.TargetContainer, "--container": model.TargetContainer,
}
// Track which positional args we've placed so we can insert them in order
// between flag-based targets
i := 0
for i < len(rawArgs) {
arg := rawArgs[i]
// "--" ends option parsing (POSIX): everything after it is positional,
// matching how cobra fills positionalArgs.
if arg == "--" {
break
}
// Check for --flag=value form
if strings.HasPrefix(arg, "--") {
if eqIdx := strings.Index(arg, "="); eqIdx >= 0 {
flagName := arg[:eqIdx]
flagVal := arg[eqIdx+1:]
if tt, ok := flagType[flagName]; ok {
for _, v := range strings.Split(flagVal, ",") {
v = strings.TrimSpace(v)
if v != "" {
targets = append(targets, model.Target{Type: tt, Value: v})
}
}
}
i++
continue
}
}
// Check for -f value or --flag value form
if tt, ok := flagType[arg]; ok {
if i+1 < len(rawArgs) {
i++
for _, v := range strings.Split(rawArgs[i], ",") {
v = strings.TrimSpace(v)
if v != "" {
targets = append(targets, model.Target{Type: tt, Value: v})
}
}
}
i++
continue
}
// Non-target flag. If it's a value-taking flag in space form
// (--flag value, not --flag=value), skip its value too so the value
// isn't mistaken for a positional target.
if strings.HasPrefix(arg, "-") {
if takesValue(arg) && i+1 < len(rawArgs) {
i++ // consume the flag's value
}
i++
continue
}
// Positional argument — use it as a name target
if positionalIdx < len(positionalArgs) {
targets = append(targets, model.Target{Type: model.TargetName, Value: positionalArgs[positionalIdx]})
positionalIdx++
}
i++
}
// Append any remaining positional args that weren't matched
for positionalIdx < len(positionalArgs) {
targets = append(targets, model.Target{Type: model.TargetName, Value: positionalArgs[positionalIdx]})
positionalIdx++
}
return targets
}
// targetLabel returns a human-readable label for the divider.
func targetLabel(t model.Target) string {
switch t.Type {
case model.TargetPID:
return fmt.Sprintf("pid: %s", t.Value)
case model.TargetPort:
return fmt.Sprintf("port: %s", t.Value)
case model.TargetFile:
return fmt.Sprintf("file: %s", t.Value)
case model.TargetContainer:
return fmt.Sprintf("container: %s", t.Value)
default:
return fmt.Sprintf("name: %s", t.Value)
}
}
func printDivider(outp output.Printer, t model.Target, colorEnabled bool, needsNewline bool) {
label := targetLabel(t)
if needsNewline {
outp.Println()
}
if colorEnabled {
outp.Printf("%s----- [%s] -----%s\n", output.ColorCyan, label, output.ColorReset)
} else {
outp.Printf("----- [%s] -----\n", label)
}
}
// jsonErrorEntry returns a JSON string representing a failed target lookup.
func jsonErrorEntry(t model.Target, errMsg string) string {
type errorEntry struct {
Target model.Target
Error string
}
data, _ := json.MarshalIndent(errorEntry{Error: errMsg, Target: t}, "", " ")
return string(data)
}
// processTarget handles resolving and rendering a single target.
// Returns the exit code for this target.
func processTarget(cmd *cobra.Command, outw io.Writer, outp output.Printer, t model.Target, flags appFlags, multiMode bool, jsonResults *[]string) int {
colorEnabled := useColor(flags, outw)
if flags.env {
return processEnvTarget(outw, outp, t, flags, multiMode, jsonResults)
}
if t.Type == model.TargetContainer {
return processContainerTarget(cmd, outw, outp, t, flags, multiMode, jsonResults)
}
pids, err := target.Resolve(t, flags.exact)
if err == nil && len(pids) == 0 {
err = fmt.Errorf("no matching process found")
}
if err != nil {
return handleResolveError(cmd, outw, outp, t, err, flags, multiMode, jsonResults)
}
if len(pids) > 1 {
if multiMode && flags.json {
*jsonResults = append(*jsonResults, jsonErrorEntry(t, fmt.Sprintf("multiple processes matched (%d results)", len(pids))))
} else {
hint := "witr --pid <pid>"
if flags.env {
hint = "witr --pid <pid> --env"
}
printMultiMatch(outp, pids, colorEnabled, hint)
}
return ExitInvalidInput
}
pid := pids[0]
var systemdService string
if t.Type == model.TargetPort && pid == 1 && source.IsSystemdRunning() {
if portNum, err := strconv.Atoi(t.Value); err == nil {
if svc, err := procpkg.ResolveSystemdService(portNum); err == nil && svc != "" {
systemdService = svc
}
}
}
res, err := pipeline.AnalyzePID(pipeline.AnalyzeConfig{
PID: pid,
Verbose: flags.verbose,
Tree: flags.tree,
Target: t,
})
if err != nil {
if multiMode {
if flags.json {
*jsonResults = append(*jsonResults, jsonErrorEntry(t, err.Error()))
} else {
outp.Printf("Error: %v\n", err)
}
return classifyError(err)
}
errStr := err.Error()
errorMsg := fmt.Sprintf("%s\n\nNo matching process or service found. Please check your query or try a different name/port/PID.\nFor usage and options, run: witr --help", errStr)
cmd.PrintErrln(errorMsg)
return classifyError(err)
}
if systemdService != "" {
res.ResolvedTarget = strings.TrimSuffix(systemdService, ".service")
}
if t.Type == model.TargetPort {
portNum := 0
fmt.Sscanf(t.Value, "%d", &portNum)
if portNum > 0 {
res.SocketInfo = procpkg.GetSocketStateForPort(portNum)
source.EnrichSocketInfo(res.SocketInfo)
}
}
renderResult(outw, res, flags, multiMode, jsonResults)
if len(res.Warnings) > 0 {
return ExitWarnings
}
return ExitOK
}
// processEnvTarget handles the --env flag for a single target.
func processEnvTarget(outw io.Writer, outp output.Printer, t model.Target, flags appFlags, multiMode bool, jsonResults *[]string) int {
colorEnabled := useColor(flags, outw)
pids, err := target.Resolve(t, flags.exact)
if err != nil {
if multiMode {
if flags.json {
*jsonResults = append(*jsonResults, jsonErrorEntry(t, err.Error()))
} else {
outp.Printf("Error: %v\n", err)
}
return classifyError(err)
}
outp.Printf("error: %v\n", err)
return classifyError(err)
}
if len(pids) == 0 {
if multiMode && flags.json {
*jsonResults = append(*jsonResults, jsonErrorEntry(t, "no matching process found"))
return ExitNotFound
}
outp.Println("No matching process found.")
return ExitNotFound
}
if len(pids) > 1 {
printMultiMatch(outp, pids, colorEnabled, "witr --pid <pid> --env")
return ExitInvalidInput
}
pid := pids[0]
procInfo, err := procpkg.ReadProcess(pid)
if err != nil {
outp.Printf("error: %v\n", err)
return ExitInternalError
}
resEnv := model.Result{
Process: procInfo,
Ancestry: []model.Process{procInfo},
}
if flags.json {
jsonStr, err := output.ToEnvJSON(resEnv)
if err != nil {
outp.Printf("failed to generate json output: %v\n", err)
return ExitInternalError
}
if multiMode {
*jsonResults = append(*jsonResults, jsonStr)
} else {
fmt.Fprintln(outw, jsonStr)
}
} else {
output.RenderEnvOnly(outw, resEnv, colorEnabled)
}
return ExitOK
}
// handleResolveError handles target resolution errors, including Docker fallback.
func handleResolveError(cmd *cobra.Command, outw io.Writer, outp output.Printer, t model.Target, err error, flags appFlags, multiMode bool, jsonResults *[]string) int {
errStr := err.Error()
colorEnabled := useColor(flags, outw)
// Platform-unsupported target (e.g. -f on Windows). Don't tack on the
// generic "try a different name/port/PID" suffix — the operation isn't a
// failed lookup, it's unavailable on this OS.
if errors.Is(err, target.ErrUnsupported) || strings.Contains(errStr, "not supported on") {
if multiMode {
if flags.json {
*jsonResults = append(*jsonResults, jsonErrorEntry(t, errStr))
} else {
outp.Printf("Error: %v\n", err)
}
} else {
cmd.PrintErrln(errStr)
}
return ExitInvalidInput
}
if errors.Is(err, target.ErrSocketOwnerUnknown) || strings.Contains(errStr, "socket found but owning process not detected") {
if t.Type == model.TargetPort {
if portNum, convErr := strconv.Atoi(t.Value); convErr == nil {
if match := procpkg.ResolveContainerByPort(portNum); match != nil {
label := "port " + t.Value
if flags.json {
jsonStr, jsonErr := output.ContainerFallbackToJSON(label, match)
if jsonErr != nil {
outp.Printf("failed to generate json output: %v\n", jsonErr)
return ExitInternalError
}
if multiMode {
*jsonResults = append(*jsonResults, jsonStr)
} else {
fmt.Fprintln(outw, jsonStr)
}
} else if flags.short {
output.RenderContainerFallbackShort(outw, label, match, colorEnabled)
} else {
output.RenderContainerFallback(outw, label, match, colorEnabled, flags.verbose)
}
return ExitOK
}
}
}
if multiMode {
if flags.json {
*jsonResults = append(*jsonResults, jsonErrorEntry(t, "socket found but owning process not detected (try sudo)"))
} else {
outp.Printf("Error: socket found but owning process not detected (try sudo)\n")
}
return ExitPermission
}
errorMsg := fmt.Sprintf("%s\n\nA socket was found for the port, but the owning process could not be detected.\nThis may be due to insufficient permissions. Try running with sudo:\n sudo %s", errStr, strings.Join(os.Args, " "))
cmd.PrintErrln(errorMsg)
return ExitPermission
}
if multiMode {
if flags.json {
*jsonResults = append(*jsonResults, jsonErrorEntry(t, errStr))
} else {
outp.Printf("Error: %v\n", err)
}
return classifyError(err)
}
errorMsg := fmt.Sprintf("%s\n\nNo matching process or service found. Please check your query or try a different name/port/PID.\nFor usage and options, run: witr --help", errStr)
if t.Type == model.TargetFile && runtime.GOOS != "windows" && os.Geteuid() != 0 {
errorMsg += "\n\nIf the file is held by another user's process, retry with sudo:\n sudo " + strings.Join(os.Args, " ")
}
cmd.PrintErrln(errorMsg)
return classifyError(err)
}
// renderResult renders a single result in the appropriate output mode.
func renderResult(outw io.Writer, res model.Result, flags appFlags, multiMode bool, jsonResults *[]string) {
colorEnabled := useColor(flags, outw)
if flags.json {
var jsonStr string
var err error
if flags.short {
jsonStr, err = output.ToShortJSON(res)
} else if flags.tree {
jsonStr, err = output.ToTreeJSON(res)
} else if flags.warn {
jsonStr, err = output.ToWarningsJSON(res)
} else {
jsonStr, err = output.ToJSON(res)
}
if err != nil {
fmt.Fprintf(outw, "failed to generate json output: %v\n", err)
return
}
if multiMode {
*jsonResults = append(*jsonResults, jsonStr)
} else {
fmt.Fprintln(outw, jsonStr)
}
} else if flags.warn {
output.RenderWarnings(outw, res, colorEnabled)
} else if flags.tree {
output.PrintTree(outw, res.Ancestry, res.Children, colorEnabled)
} else if flags.short {
output.RenderShort(outw, res, colorEnabled)
} else {
output.RenderStandard(outw, res, colorEnabled, flags.verbose)
}
}
func Root() *cobra.Command { return rootCmd }
func runInteractive() error {
v := version
if v == "v0.0.0-dev" {
v = ""
}
return tui.Start(v)
}
func printMultiMatch(outp output.Printer, pids []int, colorEnabled bool, hint string) {
outp.Printf("Multiple matching processes found:\n\n")
for i, pid := range pids {
proc, err := procpkg.ReadProcess(pid)
var command, cmdline string
if err != nil {
command = "unknown"
cmdline = procpkg.GetCmdline(pid)
} else {
command = proc.Command
cmdline = proc.Cmdline
}
if colorEnabled {
outp.Printf("[%d] %s%s%s (%spid %d%s)\n %s\n",
i+1, output.ColorGreen, command, output.ColorReset,
output.ColorDim, pid, output.ColorReset,
cmdline)
} else {
outp.Printf("[%d] %s (pid %d)\n %s\n", i+1, command, pid, cmdline)
}
}
outp.Printf("\nRe-run with:\n")
outp.Printf(" %s\n", hint)
}
func printContainerMultiMatch(outp output.Printer, matches []*model.ContainerMatch, colorEnabled bool) {
outp.Printf("Multiple matching containers found:\n\n")
for i, m := range matches {
name := output.SanitizeTerminal(m.Name)
image := output.SanitizeTerminal(m.Image)
status := output.SanitizeTerminal(m.Status)
ports := output.SanitizeTerminal(m.Ports)
runtime := output.SanitizeTerminal(m.Runtime)
if colorEnabled {
outp.Printf("[%d] %s%s%s (%s%s%s)\n",
i+1, output.ColorGreen, name, output.ColorReset,
output.ColorDim, runtime, output.ColorReset)
} else {
outp.Printf("[%d] %s (%s)\n", i+1, name, runtime)
}
detail := "image: " + image
if status != "" {
detail += ", status: " + status
}
if ports != "" {
detail += ", ports: " + ports
}
outp.Printf(" %s\n", detail)
}
outp.Printf("\nRe-run with the exact container name to disambiguate:\n")
outp.Println(" witr -c <container-name> --exact")
}
// classifyError maps common error strings to exit codes.
func classifyError(err error) int {
msg := strings.ToLower(err.Error())
switch {
case strings.Contains(msg, "permission denied") ||
strings.Contains(msg, "operation not permitted") ||
strings.Contains(msg, "insufficient permissions"):
return ExitPermission
case strings.Contains(msg, "no matching") ||
strings.Contains(msg, "no running process") ||
strings.Contains(msg, "not found") ||
strings.Contains(msg, "no process"):
return ExitNotFound
case strings.Contains(msg, "invalid") ||
strings.Contains(msg, "must specify"):
return ExitInvalidInput
default:
return ExitInternalError
}
}
// processContainerTarget handles `-c/--container` lookups. Resolves against
// every available container runtime, dispatches to the normal pipeline if
// the container's main process is host-visible, otherwise renders the
// runtime-side metadata via the container fallback view.
func processContainerTarget(cmd *cobra.Command, outw io.Writer, outp output.Printer, t model.Target, flags appFlags, multiMode bool, jsonResults *[]string) int {
colorEnabled := useColor(flags, outw)
matches := procpkg.ResolveContainer(t.Value, flags.exact)
if len(matches) == 0 {
err := fmt.Errorf("no container found matching %q", t.Value)
return handleResolveError(cmd, outw, outp, t, err, flags, multiMode, jsonResults)
}
if len(matches) > 1 {
if multiMode && flags.json {
*jsonResults = append(*jsonResults, jsonErrorEntry(t, fmt.Sprintf("multiple containers matched (%d results)", len(matches))))
} else {
printContainerMultiMatch(outp, matches, colorEnabled)
}
return ExitInvalidInput
}
match := matches[0]
procpkg.EnrichContainer(match)
pid := procpkg.ResolveContainerHostPID(match.Runtime, match.ID)
if pid > 0 && procpkg.PIDBelongsToContainer(pid, match.ID) {
res, err := pipeline.AnalyzePID(pipeline.AnalyzeConfig{
PID: pid,
Verbose: flags.verbose,
Tree: flags.tree,
Target: t,
})
if err != nil {
outp.Printf("Error: %v\n", err)
return classifyError(err)
}
res.Process.Container = output.FormatContainerLine(match)
if len(res.Ancestry) > 0 {
res.Ancestry[len(res.Ancestry)-1].Container = res.Process.Container
}
renderResult(outw, res, flags, multiMode, jsonResults)
if len(res.Warnings) > 0 {
return ExitWarnings
}
return ExitOK
}
label := "container " + match.Name
switch {
case flags.json:
jsonStr, err := output.ContainerFallbackToJSON(label, match)
if err != nil {
outp.Printf("failed to generate json output: %v\n", err)
return ExitInternalError
}
if multiMode {
*jsonResults = append(*jsonResults, jsonStr)
} else {
fmt.Fprintln(outw, jsonStr)
}
case flags.short:
output.RenderContainerFallbackShort(outw, label, match, colorEnabled)
case flags.tree:
output.RenderContainerFallbackTree(outw, match, colorEnabled)
case flags.warn:
output.RenderContainerFallbackWarnings(outw, match, colorEnabled)
default:
output.RenderContainerFallback(outw, label, match, colorEnabled, flags.verbose)
}
return ExitOK
}
func SetVersion(v string, c string, bd string) {
version = v
commit = c
buildDate = bd
rootCmd.Version = version
rootCmd.SetVersionTemplate(fmt.Sprintf("witr {{.Version}} (commit %s, built %s)\n", commit, buildDate))
rootCmd.SilenceUsage = true
}