grpcheck: new extension to check if the user is in given groups

Summary:
We have seen issues that users have outdated groups when running hg commands,
which will probably always cause issues:

- Authentication issue, unable to ssh
- Filesystem permission issue, unable to write hgcache
- Even worse with chg server since the long-running server process will keep
  the wrong groups information

This extension is to address the above issues. It allows us to print a message
to let the user know they have group issues. Besides, it allows us to override
configs like `chgserver.idletimeout` so chg servers with wrong groups can have
a much smaller TTL and won't be long-running and causing issues.

Test Plan: Run the newly added test

Reviewers: #sourcecontrol, simonfar

Reviewed By: simonfar

Subscribers: simonfar, mjpieters

Differential Revision: https://phabricator.intern.facebook.com/D3896628

Signature: t1:3896628:1474454162:22785ff23e3ada75013ce5f1eead3407068ba172
This commit is contained in:
Jun Wu 2016-09-20 23:15:09 +01:00
parent 71c96fe8ea
commit 73eac46994
2 changed files with 145 additions and 0 deletions

72
hgext3rd/grpcheck.py Normal file
View File

@ -0,0 +1,72 @@
# grpcheck.py - check if the user is in specified groups
#
# Copyright 2015 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""check if the user is in specified groups
If the user is not a member of specified groups, optionally show a warning
message and override some other config items.
::
[grpcheck]
# the user is expected to be a member of devs and users group
groups = devs, users
# warning to show if the user is not a member of any of those groups
warning = You are not a member of %s group. Consult IT department for help.
# if the user is not a member of any of those groups, override chgserver
# config to make chgserver exit earlier
overrides.chgserver.idletimeout = 2
"""
import os
import grp
testedwith = '3.9'
_missinggroup = None
def _grpname2gid(name):
try:
return grp.getgrnam(name).gr_gid
except KeyError:
return None
def _firstmissinggroup(groupnames):
usergids = set(os.getgroups())
for name in groupnames:
expectedgid = _grpname2gid(name)
# ignore unknown groups
if expectedgid is not None and expectedgid not in usergids:
return name
def _overrideconfigs(ui):
for k, v in ui.configitems('grpcheck'):
if not k.startswith('overrides.'):
continue
section, name = k[len('overrides.'):].split('.', 1)
ui.setconfig(section, name, v)
def extsetup(ui):
groupnames = ui.configlist('grpcheck', 'groups')
if not groupnames:
return
missing = _firstmissinggroup(groupnames)
if not missing:
return
message = ui.config('grpcheck', 'warning')
if message and not ui.plain():
if '%s' in message:
message = message % missing
ui.warn(message + '\n')
_overrideconfigs(ui)
# re-used by reposetup. groups information is immutable for a process,
# so we can re-use the "missing" calculation result safely.
global _missinggroup
_missinggroup = missing
def reposetup(ui, repo):
if _missinggroup:
_overrideconfigs(ui)

73
tests/test-grpcheck.t Normal file
View File

@ -0,0 +1,73 @@
$ cat >> $HGRCPATH << EOF
> [extensions]
> grpcheck = $TESTDIR/../hgext3rd/grpcheck.py
> EOF
$ hg init repo
$ cd repo
mock os.getgroups and grp.getgrnam
$ cat >> $TESTTMP/mockgrp.py << EOF
> import os, grp
> def _getgroups():
> return map(int, os.environ.get('HGMOCKGRPS', '1000').split())
> class _grp(object):
> def __init__(self, gid):
> self.gr_gid = gid
> def _getgrnam(name):
> if name == 'users':
> gid = 1000
> elif name == 'devs':
> gid = 2000
> else:
> raise KeyError()
> return _grp(gid)
> os.getgroups = _getgroups
> grp.getgrnam = _getgrnam
> EOF
$ cat >> $HGRCPATH << EOF
> mockgrp = $TESTTMP/mockgrp.py
> [grpcheck]
> groups = users, devs
> warning = You should be in %s group.
> overrides.chgserver.idletimeout = 3
> overrides.ui.foo = bar
> EOF
when the user is not in those groups, warnings are printed
$ hg log
You should be in devs group.
$ HGMOCKGRPS=100 hg log
You should be in users group.
$ HGMOCKGRPS='100 1000' hg log
You should be in devs group.
customized warning message
$ hg --config grpcheck.warning=foo log
foo
no warning if HGPLAIN or grpcheck.warning is empty
$ hg --config grpcheck.warning= log
$ HGPLAIN=1 hg log
warning does not affect write action (commit)
$ touch a
$ hg commit -A a -m a
You should be in devs group.
$ hg log -T '{rev} {node}\n'
You should be in devs group.
0 3903775176ed42b1458a6281db4a0ccf4d9f287a
config overrides
$ HGPLAIN=1 hg config --untrusted chgserver.idletimeout
3
$ cd .. # use repo ui
$ HGPLAIN=1 hg config --untrusted ui.foo
bar