account_tool: Add script to install accounts to sysroot

BUG=brillo:1112
TEST=Hand testing
CQ-DEPEND=CL:275684

Change-Id: I24c556a43510ef1b007b7f4db42eb27f25fc1596
Reviewed-on: https://chromium-review.googlesource.com/275353
Tested-by: Christopher Wiley <wiley@chromium.org>
Reviewed-by: Bertrand Simonnet <bsimonnet@chromium.org>
Commit-Queue: Christopher Wiley <wiley@chromium.org>
diff --git a/scripts/account_tool.py b/scripts/account_tool.py
new file mode 100644
index 0000000..3769b0e
--- /dev/null
+++ b/scripts/account_tool.py
@@ -0,0 +1,117 @@
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This script installs users and groups into sysroots."""
+
+from __future__ import print_function
+
+import os
+
+from chromite.lib import accounts_lib
+from chromite.lib import commandline
+from chromite.lib import cros_build_lib
+from chromite.lib import osutils
+from chromite.lib import sysroot_lib
+from chromite.lib import user_db
+
+
+ACCOUNT_DB_FILENAME = 'accounts.json'
+
+ACTION_GET_ENTRY = 'get_entry'
+ACTION_INSTALL_USER = 'install_user'
+ACTION_INSTALL_GROUP = 'install_group'
+
+USER_DB = 'passwd'
+GROUP_DB = 'group'
+
+
+def GetOptions(argv):
+  """Returns the parsed command line arguments in |argv|."""
+  parser = commandline.ArgumentParser(description=__doc__)
+  command_parsers = parser.add_subparsers(dest='action')
+
+  get_ent_parser = command_parsers.add_parser(
+      ACTION_GET_ENTRY, help='Get an entry from an account database.')
+  get_ent_parser.add_argument(
+      '--nolock', action='store_true', default=False,
+      help='Skip locking the database before reading it.')
+  get_ent_parser.add_argument('sysroot', type='path',
+                              help='Path to sysroot containing the database')
+  get_ent_parser.add_argument('database', choices=(USER_DB, GROUP_DB),
+                              help='Name of database to get')
+  get_ent_parser.add_argument('name', type=str, help='Name of account to get')
+
+  user_parser = command_parsers.add_parser(
+      ACTION_INSTALL_USER, help='Install a user to a sysroot')
+  user_parser.add_argument('name', type=str,
+                           help='Name of user to install')
+  user_parser.add_argument('--uid', type=int,
+                           help='UID of the user')
+  user_parser.add_argument('--shell', type='path',
+                           help='Shell of user')
+  user_parser.add_argument('--home', type='path',
+                           help='Home directory of user')
+  user_parser.add_argument('--primary_group', type=str,
+                           help='Name of primary group for user')
+
+  group_parser = command_parsers.add_parser(
+      ACTION_INSTALL_GROUP, help='Install a group to a sysroot')
+  group_parser.add_argument('name', type=str,
+                            help='Name of group to install.')
+  group_parser.add_argument('--gid', type=int, help='GID of the group')
+
+  # Both group and user parsers need to understand the target sysroot.
+  for sub_parser in (user_parser, group_parser):
+    sub_parser.add_argument(
+        'sysroot', type='path', help='The sysroot to install the user into')
+
+  options = parser.parse_args(argv)
+  options.Freeze()
+  return options
+
+
+def main(argv):
+  cros_build_lib.AssertInsideChroot()
+  options = GetOptions(argv)
+
+  if options.action == ACTION_GET_ENTRY:
+    db = user_db.UserDB(options.sysroot)
+    if options.database == USER_DB:
+      print(db.GetUserEntry(options.name, skip_lock=options.nolock))
+    else:
+      print(db.GetGroupEntry(options.name, skip_lock=options.nolock))
+    return 0
+
+  overlays = sysroot_lib.Sysroot(options.sysroot).GetStandardField(
+      sysroot_lib.STANDARD_FIELD_PORTDIR_OVERLAY).split()
+
+  # TODO(wiley) This process could be optimized to avoid reparsing these
+  #             overlay databases each time.
+  account_db = accounts_lib.AccountDatabase()
+  for overlay_path in overlays:
+    database_path = os.path.join(overlay_path, ACCOUNT_DB_FILENAME)
+    if os.path.exists(database_path):
+      account_db.AddAccountsFromDatabase(database_path)
+
+  installed_users = user_db.UserDB(options.sysroot)
+
+  if options.action == ACTION_INSTALL_USER:
+    account_db.InstallUser(options.name, installed_users,
+                           uid=options.uid, shell=options.shell,
+                           homedir=options.home,
+                           primary_group=options.primary_group)
+
+    homedir = account_db.users[options.name].home
+    homedir_path = os.path.join(options.sysroot, homedir)
+
+    if homedir != '/dev/null' and not os.path.exists(homedir_path):
+      osutils.SafeMakedirs(homedir_path, sudo=True)
+      uid = account_db.users[options.name].uid
+      cros_build_lib.SudoRunCommand(
+          ['chown', '%d:%d' % (uid, uid), homedir_path], print_cmd=False)
+
+  elif options.action == ACTION_INSTALL_GROUP:
+    account_db.InstallGroup(options.name, installed_users, gid=options.gid)
+  else:
+    cros_build_lib.Die('Unsupported account type: %s' % options.account_type)