display-accts: run through `cros format`
BUG=b:239567381
TEST=`./display-accts.py` output is the same
Change-Id: I963aed4b0c17617b6cc9cbc80a2ebf8fa21faf95
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/overlays/eclass-overlay/+/4112143
Reviewed-by: Allen Webb <allenwebb@google.com>
Tested-by: Mike Frysinger <vapier@chromium.org>
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
index 450b7b9..4c723dd 100644
--- a/PRESUBMIT.cfg
+++ b/PRESUBMIT.cfg
@@ -4,3 +4,4 @@
[Hook Scripts]
account_checks = profiles/base/accounts/display-accts.py --lint
+cros format = cros format --check ${PRESUBMIT_FILES}
diff --git a/profiles/base/accounts/display-accts.py b/profiles/base/accounts/display-accts.py
index 4f9cd21..3ef5544 100755
--- a/profiles/base/accounts/display-accts.py
+++ b/profiles/base/accounts/display-accts.py
@@ -14,290 +14,309 @@
from typing import Dict, List, NamedTuple
-assert sys.version_info >= (3, 6), (
- f'Python 3.6+ required, but found {sys.version_info}')
+assert sys.version_info >= (
+ 3,
+ 6,
+), f"Python 3.6+ required, but found {sys.version_info}"
# Regex to match valid account names.
-VALID_ACCT_NAME_RE = re.compile(r'^[a-z][a-z0-9_-]*[a-z0-9]$')
+VALID_ACCT_NAME_RE = re.compile(r"^[a-z][a-z0-9_-]*[a-z0-9]$")
class Group(NamedTuple):
- """A group account."""
- # NB: Order is not the same as /etc/group. Don't rely on it.
- group: str
- gid: str
- password: str = '!'
- users: str = ''
- defunct: str = ''
+ """A group account."""
+
+ # NB: Order is not the same as /etc/group. Don't rely on it.
+ group: str
+ gid: str
+ password: str = "!"
+ users: str = ""
+ defunct: str = ""
class User(NamedTuple):
- """A user account."""
- # NB: Order is not the same as /etc/passwd. Don't rely on it.
- user: str
- uid: str
- gid: str
- password: str = '!'
- gecos: str = ''
- home: str = '/dev/null'
- shell: str = '/bin/false'
- defunct: str = ''
+ """A user account."""
+
+ # NB: Order is not the same as /etc/passwd. Don't rely on it.
+ user: str
+ uid: str
+ gid: str
+ password: str = "!"
+ gecos: str = ""
+ home: str = "/dev/null"
+ shell: str = "/bin/false"
+ defunct: str = ""
def _ParseAccount(name, name_key, content, obj):
- """Parse the raw data in |content| and return a new |obj|"""
- # Make sure files all have a trailing newline.
- if not content.endswith('\n'):
- raise ValueError('File needs a trailing newline')
+ """Parse the raw data in |content| and return a new |obj|."""
+ # Make sure files all have a trailing newline.
+ if not content.endswith("\n"):
+ raise ValueError("File needs a trailing newline")
- # Disallow leading & trailing blank lines.
- if content.startswith('\n'):
- raise ValueError('Delete leading blank lines')
- if content.endswith('\n\n'):
- raise ValueError('Delete trailing blank lines')
+ # Disallow leading & trailing blank lines.
+ if content.startswith("\n"):
+ raise ValueError("Delete leading blank lines")
+ if content.endswith("\n\n"):
+ raise ValueError("Delete trailing blank lines")
- d = {}
- for line in content.splitlines():
- if not line or line.startswith('#'):
- continue
+ d = {}
+ for line in content.splitlines():
+ if not line or line.startswith("#"):
+ continue
- # Disallow leading & trailing whitespace.
- if line != line.strip():
- raise ValueError(f'Trim leading/trailing whitespace: "{line}"')
+ # Disallow leading & trailing whitespace.
+ if line != line.strip():
+ raise ValueError(f'Trim leading/trailing whitespace: "{line}"')
- key, val = line.split(':')
- if key not in obj._fields:
- raise ValueError(f'unknown key: {key}')
- d[key] = val
+ key, val = line.split(":")
+ if key not in obj._fields:
+ raise ValueError(f"unknown key: {key}")
+ d[key] = val
- unknown_keys = set(d.keys()) - set(obj._fields)
- if unknown_keys:
- raise ValueError(f'unknown keys: {" ".join(unknown_keys)}')
+ unknown_keys = set(d.keys()) - set(obj._fields)
+ if unknown_keys:
+ raise ValueError(f'unknown keys: {" ".join(unknown_keys)}')
- if d[name_key] != name:
- raise ValueError(
- f'account "{name}" has "{name_key}" field set to "{d[name_key]}"')
+ if d[name_key] != name:
+ raise ValueError(
+ f'account "{name}" has "{name_key}" field set to "{d[name_key]}"'
+ )
- return obj(**d)
+ return obj(**d)
def ParseGroup(name, content):
- """Parse |content| as a Group object"""
- return _ParseAccount(name, 'group', content, Group)
+ """Parse |content| as a Group object."""
+ return _ParseAccount(name, "group", content, Group)
def ParseUser(name, content):
- """Parse |content| as a User object"""
- return _ParseAccount(name, 'user', content, User)
+ """Parse |content| as a User object."""
+ return _ParseAccount(name, "user", content, User)
def AlignWidths(arr: List[NamedTuple]) -> Dict:
- """Calculate a set of widths for alignment
+ """Calculate a set of widths for alignment.
- Args:
- arr: An array of accounts.
+ Args:
+ arr: An array of accounts.
- Returns:
- A dict whose fields have the max length
- """
- d = {}
- for f in arr[0]._fields:
- d[f] = 0
+ Returns:
+ A dict whose fields have the max length.
+ """
+ d = {}
+ for f in arr[0]._fields:
+ d[f] = 0
- for a in arr:
- for f in a._fields:
- d[f] = max(d[f], len(getattr(a, f)))
+ for a in arr:
+ for f in a._fields:
+ d[f] = max(d[f], len(getattr(a, f)))
- return d
+ return d
def DisplayAccounts(accts: List[NamedTuple], order):
- """Display |accts| as a table using |order| for field ordering
+ """Display |accts| as a table using |order| for field ordering.
- Args:
- accts: An array of accounts.
- order: The order in which to display the members
- """
- obj = type(accts[0])
- header_obj = obj(**dict([(k, (v if v else k).upper()) for k, v in order]))
- keys = [k for k, _ in order]
- sorter = lambda x: int(getattr(x, keys[0]))
+ Args:
+ accts: An array of accounts.
+ order: The order in which to display the members.
+ """
+ obj = type(accts[0])
+ header_obj = obj(**dict([(k, (v if v else k).upper()) for k, v in order]))
+ keys = [k for k, _ in order]
+ sorter = lambda x: int(getattr(x, keys[0]))
- widths = AlignWidths([header_obj] + accts)
- def p(obj):
- for k in keys:
- print(f'{getattr(obj, k):<{widths[k] + 1}}', end='')
- print()
+ widths = AlignWidths([header_obj] + accts)
- for a in [header_obj] + sorted(accts, key=sorter):
- p(a)
+ def p(obj):
+ for k in keys:
+ print(f"{getattr(obj, k):<{widths[k] + 1}}", end="")
+ print()
+
+ for a in [header_obj] + sorted(accts, key=sorter):
+ p(a)
def CheckConsistency(groups, users):
- """Run various consistency/sanity checks on the lists of groups/users.
+ """Run various consistency checks on the lists of groups/users.
- This does not check for syntax/etc... errors on a per-account basis as the
- main _ParseAccount function above took care of that.
+ This does not check for syntax/etc... errors on a per-account basis as the
+ main _ParseAccount function above took care of that.
- Args:
- groups: A list of Group objects.
- users: A list of User objects.
+ Args:
+ groups: A list of Group objects.
+ users: A list of User objects.
- Returns:
- True if everything is consistent.
- """
- ret = True
+ Returns:
+ True if everything is consistent.
+ """
+ ret = True
- gid_counts = collections.Counter(x.gid for x in groups)
- for gid in [k for k, v in gid_counts.items() if v > 1]:
- ret = False
- dupes = ', '.join(x.group for x in groups if x.gid == gid)
- print(f'error: duplicate gid found: {gid}: {dupes}', file=sys.stderr)
+ gid_counts = collections.Counter(x.gid for x in groups)
+ for gid in [k for k, v in gid_counts.items() if v > 1]:
+ ret = False
+ dupes = ", ".join(x.group for x in groups if x.gid == gid)
+ print(f"error: duplicate gid found: {gid}: {dupes}", file=sys.stderr)
- uid_counts = collections.Counter(x.uid for x in users)
- for uid in [k for k, v in uid_counts.items() if v > 1]:
- ret = False
- dupes = ', '.join(x.user for x in users if x.uid == uid)
- print(f'error: duplicate uid found: {uid}: {dupes}', file=sys.stderr)
+ uid_counts = collections.Counter(x.uid for x in users)
+ for uid in [k for k, v in uid_counts.items() if v > 1]:
+ ret = False
+ dupes = ", ".join(x.user for x in users if x.uid == uid)
+ print(f"error: duplicate uid found: {uid}: {dupes}", file=sys.stderr)
- for group in groups:
- if not VALID_ACCT_NAME_RE.match(group.group):
- print(f'error: invalid group account name: {group.group}')
- for user in users:
- if not VALID_ACCT_NAME_RE.match(user.user):
- print(f'error: invalid user account name: {user.user}')
-
- found_users = set(x.user for x in users)
- want_users = set()
- for group in groups:
- if group.users:
- want_users.update(group.users.split(','))
-
- missing_users = want_users - found_users
- if missing_users:
- ret = False
- print('error: group lists unknown users', file=sys.stderr)
for group in groups:
- for user in missing_users:
- if user in group.users.split(','):
- print(f'error: group "{group.group}" wants missing user "{user}"',
- file=sys.stderr)
+ if not VALID_ACCT_NAME_RE.match(group.group):
+ print(f"error: invalid group account name: {group.group}")
+ for user in users:
+ if not VALID_ACCT_NAME_RE.match(user.user):
+ print(f"error: invalid user account name: {user.user}")
- return ret
+ found_users = set(x.user for x in users)
+ want_users = set()
+ for group in groups:
+ if group.users:
+ want_users.update(group.users.split(","))
+
+ missing_users = want_users - found_users
+ if missing_users:
+ ret = False
+ print("error: group lists unknown users", file=sys.stderr)
+ for group in groups:
+ for user in missing_users:
+ if user in group.users.split(","):
+ print(
+ f'error: group "{group.group}" wants missing user '
+ f'"{user}"',
+ file=sys.stderr,
+ )
+
+ return ret
def _FindFreeIds(accts, key, low_id, high_id):
- """Find all free ids in |accts| between |low_id| and |high_id| (inclusive).
+ """Find all free ids in |accts| between |low_id| and |high_id| (inclusive).
- Args:
- accts: An iterable of account objects.
- key: The member of the account object holding the id.
- low_id: The first id to look for.
- high_id: The last id to look for.
+ Args:
+ accts: An iterable of account objects.
+ key: The member of the account object holding the id.
+ low_id: The first id to look for.
+ high_id: The last id to look for.
- Returns:
- A sorted list of free ids.
- """
- free_accts = set(range(low_id, high_id + 1))
- used_accts = set(int(getattr(x, key)) for x in accts)
- return sorted(free_accts - used_accts)
+ Returns:
+ A sorted list of free ids.
+ """
+ free_accts = set(range(low_id, high_id + 1))
+ used_accts = set(int(getattr(x, key)) for x in accts)
+ return sorted(free_accts - used_accts)
def ShowNextFree(groups, users):
- """Display next set of free groups/users."""
- RANGES = (
- ('CrOS daemons', 20100, 29999),
- ('FUSE daemons', 300, 399),
- ('Standalone', 400, 499),
- ('Namespaces', 600, 699),
- )
- for name, low_id, high_id in RANGES:
- print(f'{name}:')
- for accts, key in ((groups, 'gid'), (users, 'uid')):
- if accts:
- free_accts = _FindFreeIds(accts, key, low_id, high_id)
- if len(free_accts) > 10:
- free_accts = free_accts[0:10] + ['...']
- print(f' {key}: {free_accts}')
- print()
+ """Display next set of free groups/users."""
+ RANGES = (
+ ("CrOS daemons", 20100, 29999),
+ ("FUSE daemons", 300, 399),
+ ("Standalone", 400, 499),
+ ("Namespaces", 600, 699),
+ )
+ for name, low_id, high_id in RANGES:
+ print(f"{name}:")
+ for accts, key in ((groups, "gid"), (users, "uid")):
+ if accts:
+ free_accts = _FindFreeIds(accts, key, low_id, high_id)
+ if len(free_accts) > 10:
+ free_accts = free_accts[0:10] + ["..."]
+ print(f" {key}: {free_accts}")
+ print()
def GetParser():
- """Creates the argparse parser."""
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--show-free', default=False, action='store_true',
- help='Find next available UID/GID')
- parser.add_argument('--lint', default=False, action='store_true',
- help='Validate all the user accounts')
- parser.add_argument('account', nargs='*', type=Path,
- help='Display these account files only')
- return parser
+ """Creates the argparse parser."""
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument(
+ "--show-free",
+ default=False,
+ action="store_true",
+ help="Find next available UID/GID",
+ )
+ parser.add_argument(
+ "--lint",
+ default=False,
+ action="store_true",
+ help="Validate all the user accounts",
+ )
+ parser.add_argument(
+ "account", nargs="*", type=Path, help="Display these account files only"
+ )
+ return parser
def main(argv):
- parser = GetParser()
- opts = parser.parse_args(argv)
+ parser = GetParser()
+ opts = parser.parse_args(argv)
- accounts = opts.account
- consistency_check = False
- if not accounts:
- accounts_dir = Path(__file__).resolve().parent
- accounts = (list((accounts_dir / 'group').glob('*')) +
- list((accounts_dir / 'user').glob('*')))
- consistency_check = True
+ accounts = opts.account
+ consistency_check = False
+ if not accounts:
+ accounts_dir = Path(__file__).resolve().parent
+ accounts = list((accounts_dir / "group").glob("*")) + list(
+ (accounts_dir / "user").glob("*")
+ )
+ consistency_check = True
- groups = []
- users = []
- for f in accounts:
- try:
- content = f.read_text(encoding='utf-8')
- if not content:
- raise ValueError('empty file')
- if content[-1] != '\n':
- raise ValueError('missing trailing newline')
+ groups = []
+ users = []
+ for f in accounts:
+ try:
+ content = f.read_text(encoding="utf-8")
+ if not content:
+ raise ValueError("empty file")
+ if content[-1] != "\n":
+ raise ValueError("missing trailing newline")
- if 'group:' in content:
- groups.append(ParseGroup(f.name, content))
- else:
- users.append(ParseUser(f.name, content))
- except ValueError as e:
- print(f'error: {f}: {e}', file=sys.stderr)
- return os.EX_DATAERR
+ if "group:" in content:
+ groups.append(ParseGroup(f.name, content))
+ else:
+ users.append(ParseUser(f.name, content))
+ except ValueError as e:
+ print(f"error: {f}: {e}", file=sys.stderr)
+ return os.EX_DATAERR
- if opts.show_free:
- ShowNextFree(groups, users)
- return
+ if opts.show_free:
+ ShowNextFree(groups, users)
+ return
- if not opts.lint:
- if groups:
- order = (
- ('gid', ''),
- ('group', ''),
- ('password', 'pass'),
- ('users', ''),
- ('defunct', ''),
- )
- DisplayAccounts(groups, order)
+ if not opts.lint:
+ if groups:
+ order = (
+ ("gid", ""),
+ ("group", ""),
+ ("password", "pass"),
+ ("users", ""),
+ ("defunct", ""),
+ )
+ DisplayAccounts(groups, order)
- if users:
- if groups:
- print()
- order = (
- ('uid', ''),
- ('gid', ''),
- ('user', ''),
- ('shell', ''),
- ('home', ''),
- ('password', 'pass'),
- ('gecos', ''),
- ('defunct', ''),
- )
- DisplayAccounts(users, order)
+ if users:
+ if groups:
+ print()
+ order = (
+ ("uid", ""),
+ ("gid", ""),
+ ("user", ""),
+ ("shell", ""),
+ ("home", ""),
+ ("password", "pass"),
+ ("gecos", ""),
+ ("defunct", ""),
+ )
+ DisplayAccounts(users, order)
- if consistency_check and not CheckConsistency(groups, users):
- return os.EX_DATAERR
+ if consistency_check and not CheckConsistency(groups, users):
+ return os.EX_DATAERR
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
+if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))