brillo chroot: Add ability to move workspace chroots.
Add "brillo chroot --move" to specify a new location for a workspace's
chroot. Does not remove the old chroot (if it exists), or move it's
contents.
BUG=brillo:613
TEST=lint + unittests
Change-Id: I7f3b5f89c6fa7dcdb888bd3ee155bf82de2ac8b5
Reviewed-on: https://chromium-review.googlesource.com/263297
Reviewed-by: Don Garrett <dgarrett@chromium.org>
Commit-Queue: Don Garrett <dgarrett@chromium.org>
Tested-by: Don Garrett <dgarrett@chromium.org>
diff --git a/cli/cros/cros_chroot.py b/cli/cros/cros_chroot.py
index 6d17de4..e696d87 100644
--- a/cli/cros/cros_chroot.py
+++ b/cli/cros/cros_chroot.py
@@ -11,6 +11,7 @@
from chromite.cli import command
from chromite.lib import commandline
from chromite.lib import cros_build_lib
+from chromite.lib import workspace_lib
@command.CommandDecorator('chroot')
@@ -20,26 +21,32 @@
# Override base class property to enable stats upload.
upload_stats = True
- @classmethod
- def AddParser(cls, parser):
- """Adds a parser."""
- super(cls, ChrootCommand).AddParser(parser)
- parser.add_argument(
- 'command', nargs=argparse.REMAINDER,
- help='(optional) Command to execute inside the chroot.')
+ def _SpecifyNewChrootLocation(self, chroot_dir):
+ """Specify a new location for a workspace's chroot.
- def Run(self):
- """Runs `cros chroot`."""
- self.options.Freeze()
+ Args:
+ chroot_dir: Directory in which to specify the new chroot.
+ """
+ workspace_path = workspace_lib.WorkspacePath()
+ if not workspace_path:
+ cros_build_lib.Die('You must be in a workspace, to move its chroot.')
+
+ # TODO(dgarrett): Validate chroot_dir, somehow.
+ workspace_lib.SetChrootDir(workspace_path, chroot_dir)
+
+ def _RunChrootCommand(self, cmd):
+ """Run the specified command inside the chroot.
+
+ Args:
+ cmd: A list or tuple of strings to use as a command and its arguments.
+ If empty, run 'bash'.
+
+ Returns:
+ The commands result code.
+ """
commandline.RunInsideChroot(self, auto_detect_brick=False)
- cmd = self.options.command
-
- # If -- was used to separate out the command from arguments, ignore it.
- if cmd and cmd[0] == '--':
- cmd = cmd[1:]
-
# If there is no command, run bash.
if not cmd:
cmd = ['bash']
@@ -47,3 +54,35 @@
result = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
mute_output=False)
return result.returncode
+
+ @classmethod
+ def AddParser(cls, parser):
+ """Adds a parser."""
+ super(cls, ChrootCommand).AddParser(parser)
+ parser.add_argument(
+ '--move', help='Specify new directory for workspace chroot.')
+ parser.add_argument(
+ 'command', nargs=argparse.REMAINDER,
+ help='(optional) Command to execute inside the chroot.')
+
+ def Run(self):
+ """Runs `cros chroot`."""
+ self.options.Freeze()
+
+ # Handle the special case of moving the chroot.
+ if self.options.move:
+ if self.options.command:
+ cros_build_lib.Die(
+ "You can't move a chroot, and use it at the same time.")
+
+ self._SpecifyNewChrootLocation(self.options.move)
+ return 0
+
+ # Handle the standard case.
+ cmd = self.options.command
+
+ # If -- was used to separate out the command from arguments, ignore it.
+ if cmd and cmd[0] == '--':
+ cmd = cmd[1:]
+
+ return self._RunChrootCommand(cmd)
diff --git a/cli/cros/cros_chroot_unittest.py b/cli/cros/cros_chroot_unittest.py
index bfd6dfa..e207a57 100644
--- a/cli/cros/cros_chroot_unittest.py
+++ b/cli/cros/cros_chroot_unittest.py
@@ -6,10 +6,14 @@
from __future__ import print_function
+import os
+
from chromite.cli import command_unittest
from chromite.cli.cros import cros_chroot
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
+from chromite.lib import osutils
+from chromite.lib import workspace_lib
class MockChrootCommand(command_unittest.MockCommand):
@@ -89,3 +93,32 @@
# Ensure we pass along "--help" instead of processing it directly.
self.cmd_mock.rc_mock.assertCommandContains(['--help'])
+
+
+class ChrootMoveTest(cros_test_lib.MockTempDirTestCase):
+ """Test the ChrootCommand move functionality."""
+
+ def SetupCommandMock(self, cmd_args):
+ """Sets up the `cros chroot` command mock."""
+ self.cmd_mock = MockChrootCommand(cmd_args)
+ self.StartPatcher(self.cmd_mock)
+
+ def setUp(self):
+ """Patches objects."""
+ self.cmd_mock = None
+
+ self.work_dir = os.path.join(self.tempdir, 'work')
+ osutils.SafeMakedirs(self.work_dir)
+
+ # Force us to be inside the workspace.
+ self.PatchObject(workspace_lib, 'WorkspacePath', return_value=self.work_dir)
+
+ def testMove(self):
+ """Tests a command name that matches a valid argument, after '--'."""
+ # Technically, this should try to run the command "--help".
+ self.SetupCommandMock(['--move', '/foo'])
+ self.cmd_mock.inst.Run()
+
+ # Verify that it took effect.
+ self.assertEqual('/foo', workspace_lib.ChrootPath(self.work_dir))
+
diff --git a/lib/workspace_lib.py b/lib/workspace_lib.py
index 4736628..0d62151 100644
--- a/lib/workspace_lib.py
+++ b/lib/workspace_lib.py
@@ -82,10 +82,50 @@
Returns:
Path to where the chroot is, or where it should be created.
"""
- # TODO(dgarrett): Check local config for alternate locations.
+ config_value = GetChrootDir(workspace_path)
+
+ if config_value:
+ return config_value
+
+ # Return the default value.
return os.path.join(workspace_path, WORKSPACE_CHROOT_DIR)
+def SetChrootDir(workspace_path, chroot_dir):
+ """Set which chroot directory a workspace uses.
+
+ This value will overwrite the default value, if set. This is normally only
+ used if the user overwrites the default value. This method is NOT atomic.
+
+ Args:
+ workspace_path: Root directory of the workspace (WorkspacePath()).
+ chroot_dir: Directory in which this workspaces chroot should be created.
+ """
+ # Read the config, update its chroot_dir, and write it.
+ config = _ReadLocalConfig(workspace_path)
+ config['chroot_dir'] = chroot_dir
+ _WriteLocalConfig(workspace_path, config)
+
+
+def GetChrootDir(workspace_path):
+ """Get override of chroot directory for a workspace.
+
+ You should normally call ChrootPath so that the default value will be
+ found if no explicit value has been set.
+
+ Args:
+ workspace_path: Root directory of the workspace (WorkspacePath()).
+
+ Returns:
+ version string or None.
+ """
+ # Config should always return a dictionary.
+ config = _ReadLocalConfig(workspace_path)
+
+ # If version is present, use it, else return None.
+ return config.get('chroot_dir')
+
+
def GetActiveSdkVersion(workspace_path):
"""Find which SDK version a workspace is associated with.
diff --git a/lib/workspace_lib_unittest.py b/lib/workspace_lib_unittest.py
index d2b4d88..5aa4ef6 100644
--- a/lib/workspace_lib_unittest.py
+++ b/lib/workspace_lib_unittest.py
@@ -98,7 +98,16 @@
constants.CHROOT_WORKSPACE_ROOT = orig_root
def testChrootPath(self):
- self.assertEqual('/work/.chroot', workspace_lib.ChrootPath('/work'))
+ # Check the default value.
+ self.assertEqual(os.path.join(self.workspace_dir, '.chroot'),
+ workspace_lib.ChrootPath(self.workspace_dir))
+
+ # Set a new value.
+ workspace_lib.SetChrootDir(self.workspace_dir, self.bogus_dir)
+
+ # Check we get it back.
+ self.assertEqual(self.bogus_dir,
+ workspace_lib.ChrootPath(self.workspace_dir))
def testReadWriteLocalConfig(self):
# Non-existent config should read as an empty dictionary.