diff --git a/.promu.yml b/.promu.yml index 2a7d977..0a39a48 100644 --- a/.promu.yml +++ b/.promu.yml @@ -1,6 +1,6 @@ go: version: 1.20 - cgo: true + cgo: false repository: path: github.com/treydock/cgroup_exporter build: diff --git a/collector/cgroupv1.go b/collector/cgroupv1.go index 260dd7e..4440202 100644 --- a/collector/cgroupv1.go +++ b/collector/cgroupv1.go @@ -15,7 +15,6 @@ package collector import ( "fmt" - "os/user" "path/filepath" "regexp" "strings" @@ -45,11 +44,11 @@ func getInfov1(name string, metric *CgroupMetric, logger log.Logger) { if len(userSliceMatch) == 2 { metric.userslice = true metric.uid = userSliceMatch[1] - user, err := user.LookupId(metric.uid) + user, err := getentPasswd(metric.uid) if err != nil { level.Error(logger).Log("msg", "Error looking up user slice uid", "uid", metric.uid, "err", err) } else { - metric.username = user.Username + metric.username = user } return } @@ -59,11 +58,11 @@ func getInfov1(name string, metric *CgroupMetric, logger log.Logger) { metric.job = true metric.uid = slurmMatch[1] metric.jobid = slurmMatch[2] - user, err := user.LookupId(metric.uid) + user, err := getentPasswd(metric.uid) if err != nil { level.Error(logger).Log("msg", "Error looking up slurm uid", "uid", metric.uid, "err", err) } else { - metric.username = user.Username + metric.username = user } return } diff --git a/collector/cgroupv2.go b/collector/cgroupv2.go index 140ba58..09b2311 100644 --- a/collector/cgroupv2.go +++ b/collector/cgroupv2.go @@ -88,12 +88,12 @@ func getInfov2(name string, pids []int, metric *CgroupMetric, logger log.Logger) // effective UID uid := procStat.UIDs[1] metric.uid = uid - user, err := user.LookupId(metric.uid) + user, err := getentPasswd(metric.uid) if err != nil { level.Error(logger).Log("msg", "Error looking up slurm uid", "uid", metric.uid, "err", err) return } - metric.username = user.Username + metric.username = user return } } diff --git a/collector/collector.go b/collector/collector.go index 4b24641..60922d6 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -14,8 +14,11 @@ package collector import ( + "bytes" + "context" "fmt" "os" + "os/exec" "reflect" "strconv" "strings" @@ -33,7 +36,9 @@ var ( CgroupRoot = kingpin.Flag("path.cgroup.root", "Root path to cgroup fs").Default(defCgroupRoot).String() collectProcMaxExec = kingpin.Flag("collect.proc.max-exec", "Max length of process executable to record").Default("100").Int() ProcRoot = kingpin.Flag("path.proc.root", "Root path to proc fs").Default(defProcRoot).String() + userLookupTimeout = kingpin.Flag("exec.getent.timeout", "Timeout for running 'getent passwd' command").Default("5s").Duration() metricLock = sync.RWMutex{} + execCommand = exec.CommandContext ) const ( @@ -311,3 +316,20 @@ func sliceContains(s interface{}, v interface{}) bool { } return false } + +func getentPasswd(uid string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), *userLookupTimeout) + defer cancel() + cmd := execCommand(ctx, "getent", "passwd", uid) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("Timeout executing: getent passwd %s", uid) + } else if err != nil { + return "", fmt.Errorf("Error executing 'getent passwd %s': %s %s", uid, stderr.String(), err.Error()) + } + username := strings.Split(stdout.String(), ":")[0] + return username, nil +} diff --git a/collector/collector_test.go b/collector/collector_test.go index 0638691..84639ba 100644 --- a/collector/collector_test.go +++ b/collector/collector_test.go @@ -14,15 +14,25 @@ package collector import ( + "context" "os" + "os/exec" "path/filepath" "reflect" "runtime" + "strconv" "testing" + "time" "github.com/go-kit/log" ) +var ( + mockedExitStatus = 0 + mockedStdout string + _, cancel = context.WithTimeout(context.Background(), 5*time.Second) +) + func TestMain(m *testing.M) { _, filename, _, _ := runtime.Caller(0) dir := filepath.Dir(filename) @@ -84,3 +94,33 @@ func TestGetProcInfo(t *testing.T) { } } } + +func fakeExecCommand(ctx context.Context, command string, args ...string) *exec.Cmd { + cs := []string{"-test.run=TestExecCommandHelper", "--", command} + cs = append(cs, args...) + defer cancel() + cmd := exec.CommandContext(ctx, os.Args[0], cs...) + es := strconv.Itoa(mockedExitStatus) + tmp, _ := os.MkdirTemp("", "fake") + cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1", + "GOCOVERDIR=" + tmp, + "STDOUT=" + mockedStdout, + "EXIT_STATUS=" + es} + return cmd +} + +func TestGetentPasswd(t *testing.T) { + timeout := 5 * time.Second + userLookupTimeout = &timeout + execCommand = fakeExecCommand + mockedExitStatus = 0 + mockedStdout = "adm:x:3:4:adm:/var/adm:/sbin/nologin" + defer func() { execCommand = exec.CommandContext }() + user, err := getentPasswd("3") + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + } + if user != "adm" { + t.Errorf("Unexpected user: %s", user) + } +}