diff --git a/internal/app/cli.go b/internal/app/cli.go index edc8746c..23c8bc1b 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -301,7 +301,7 @@ func (app *App) CliSwitch(switchFrom, switchTo string, waitTimeout time.Duration switchover.From = fromHost switchover.To = toHost - switchover.InitiatedBy = app.config.Hostname + switchover.InitiatedBy = util.GuessWhoRunning() + "@" + app.config.Hostname switchover.InitiatedAt = time.Now() switchover.Cause = CauseManual @@ -360,7 +360,7 @@ func (app *App) CliEnableMaintenance(waitTimeout time.Duration, reason string) i app.dcs.Initialize() maintenance := &Maintenance{ - InitiatedBy: app.config.Hostname, + InitiatedBy: util.GuessWhoRunning() + "@" + app.config.Hostname, InitiatedAt: time.Now(), Reason: reason, } diff --git a/internal/util/user.go b/internal/util/user.go new file mode 100644 index 00000000..70c1b5e7 --- /dev/null +++ b/internal/util/user.go @@ -0,0 +1,41 @@ +package util + +import ( + "os" + "slices" + + "github.com/shirou/gopsutil/v3/process" +) + +var notInformativeUsernames = []string{"root", "mysql"} + +func GuessWhoRunning() string { + pid := os.Getppid() + + p, err := process.NewProcess(int32(pid)) + if err != nil { + return "" + } + + for i := 0; i < 50; i++ { + if p == nil { + return "unknown_dolphin" + } + + p, err = p.Parent() + if err != nil { + return "unknown_sakila" + } + + // Known issue: cross-compiled builds by default uses CGO_ENABLED="0" (aka static builds) + // this may break user.LookupId() for LDAP/NIS users (user.UnknownUserError returned) + username, err := p.Username() + if err != nil { + return "" + } + if !slices.Contains(notInformativeUsernames, username) { + return username + } + } + return "unknown" +} diff --git a/tests/features/async_setting.feature b/tests/features/async_setting.feature index 5423e3a2..673b0610 100644 --- a/tests/features/async_setting.feature +++ b/tests/features/async_setting.feature @@ -284,7 +284,7 @@ Feature: mysync async mode tests "from": "mysql1", "to": "", "cause": "manual", - "initiated_by": "mysql1", + "initiated_by": "REGEXP:.*@mysql1", "result": { "ok": true } diff --git a/tests/features/maintenance.feature b/tests/features/maintenance.feature index 1d49427a..2a03ed71 100644 --- a/tests/features/maintenance.feature +++ b/tests/features/maintenance.feature @@ -214,7 +214,7 @@ Feature: maintenance mode And zookeeper node "/test/maintenance" should match json """ { - "initiated_by": "mysql1" + "initiated_by": "REGEXP:.*@mysql1" } """ When I run command on host "mysql1" diff --git a/tests/features/switchover_from.feature b/tests/features/switchover_from.feature index 12a49d95..e1c3d6e0 100644 --- a/tests/features/switchover_from.feature +++ b/tests/features/switchover_from.feature @@ -29,7 +29,7 @@ Feature: manual switchover from old master "from": "mysql1", "to": "", "cause": "manual", - "initiated_by": "mysql1", + "initiated_by": "REGEXP:.*@mysql1", "result": { "ok": false, "error": "no quorum, have 0 replicas while 2 is required" diff --git a/tests/features/zk_maintenance.feature b/tests/features/zk_maintenance.feature index 05d107f9..e104f107 100644 --- a/tests/features/zk_maintenance.feature +++ b/tests/features/zk_maintenance.feature @@ -62,7 +62,7 @@ Feature: maintenance during dead zookeeper Then zookeeper node "/test/maintenance" should match json within "90" seconds """ { - "initiated_by": "mysql1" + "initiated_by": "REGEXP:.*@mysql1" } """ When I run command on host "mysql1" with timeout "30" seconds diff --git a/tests/testutil/matchers/matchers.go b/tests/testutil/matchers/matchers.go index ab97a57e..a159b3b3 100644 --- a/tests/testutil/matchers/matchers.go +++ b/tests/testutil/matchers/matchers.go @@ -121,7 +121,15 @@ func jsonContains(a, e interface{}, path []string) []string { return path } case reflect.String: - if a.(string) != e.(string) { + _a := a.(string) + _e := e.(string) + prefix := "REGEXP:" + if strings.HasPrefix(_e, prefix) { + eReg := regexp.MustCompile(_e[len(prefix):]) + if !eReg.MatchString(_a) { + return path + } + } else if _a != _e { return path } default: