service: Implement SetBinhost.
From my inspection, it seems we only use binhost
conf files to set: BINHOST_KEY=URI. The old code
allowed for multiple configuration lines, but this
seems overly complicated.
As a safety measure, this implementation performs
sanity checks to make sure no unexpected config
is clobbered.
TEST=./run_tests service/binhost_unittest
BUG=chromium:920418,b:127691580
Change-Id: I5ee06bf3043002e1de6a0645dc546f8a892ad1d2
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1534547
Reviewed-by: Jason Clinton <jclinton@chromium.org>
Commit-Queue: Evan Hernandez <evanhernandez@chromium.org>
Tested-by: Evan Hernandez <evanhernandez@chromium.org>
Trybot-Ready: Evan Hernandez <evanhernandez@chromium.org>
diff --git a/service/binhost.py b/service/binhost.py
new file mode 100644
index 0000000..377491b
--- /dev/null
+++ b/service/binhost.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019 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.
+
+"""The Binhost API interacts with Portage binhosts and Packages files."""
+
+from __future__ import print_function
+
+import os
+
+from chromite.lib import constants
+from chromite.lib import cros_build_lib
+from chromite.lib import osutils
+
+
+def _ValidateBinhostConf(path, key):
+ """Validates the binhost conf file defines only one environment variable.
+
+ This function is effectively a sanity check that ensures unexpected
+ configuration is not clobbered by conf overwrites.
+
+ Args:
+ path: Path to the file to validate.
+ key: Expected binhost key.
+
+ Raises:
+ ValueError: If file defines != 1 environment variable.
+ """
+ if not os.path.exists(path):
+ # If the conf file does not exist, e.g. with new targets, then whatever.
+ return
+
+ kvs = cros_build_lib.LoadKeyValueFile(path)
+
+ if not kvs:
+ raise ValueError(
+ 'Found empty .conf file %s when a non-empty one was expected.' % path)
+ elif len(kvs) > 1:
+ raise ValueError(
+ 'Conf file %s must define exactly 1 variable. '
+ 'Instead found: %r' % (path, kvs))
+ elif key not in kvs:
+ raise KeyError('Did not find key %s in %s' % (key, path))
+
+
+def SetBinhost(target, key, uri, private=True):
+ """Set binhost configuration for the given build target.
+
+ A binhost is effectively a key (Portage env variable) pointing to a URL
+ that contains binaries. The configuration is set in .conf files at static
+ directories on a build target by build target (and host by host) basis.
+
+ This function updates the .conf file by completely rewriting it.
+
+ Args:
+ target: The build target to set configuration for.
+ key: The binhost key to set, e.g. POSTSUBMIT_BINHOST.
+ uri: The new value for the binhost key, e.g. gs://chromeos-prebuilt/foo/bar.
+ private: Whether or not the build target is private.
+
+ Returns:
+ Path to the updated .conf file.
+ """
+ conf_root = os.path.join(
+ constants.SOURCE_ROOT,
+ constants.PRIVATE_BINHOST_CONF_DIR if private else
+ constants.PUBLIC_BINHOST_CONF_DIR, 'target')
+ conf_file = '%s-%s.conf' % (target, key)
+ conf_path = os.path.join(conf_root, conf_file)
+ _ValidateBinhostConf(conf_path, key)
+ osutils.WriteFile(conf_path, '%s="%s"' % (key, uri))
+ return conf_path
diff --git a/service/binhost_unittest b/service/binhost_unittest
new file mode 120000
index 0000000..72196ce
--- /dev/null
+++ b/service/binhost_unittest
@@ -0,0 +1 @@
+../scripts/wrapper.py
\ No newline at end of file
diff --git a/service/binhost_unittest.py b/service/binhost_unittest.py
new file mode 100644
index 0000000..d1c8fae
--- /dev/null
+++ b/service/binhost_unittest.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-path + os.sep)
+# Copyright 2019 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.
+
+"""Unittests for the binhost.py service."""
+
+from __future__ import print_function
+
+import os
+
+from chromite.lib import constants
+from chromite.lib import cros_test_lib
+from chromite.lib import osutils
+from chromite.service import binhost
+
+class BinhostTest(cros_test_lib.MockTempDirTestCase):
+ """Unit test definitions."""
+
+ def setUp(self):
+ self.PatchObject(constants, 'SOURCE_ROOT', new=self.tempdir)
+
+ self.public_conf_dir = os.path.join(
+ self.tempdir, constants.PUBLIC_BINHOST_CONF_DIR, 'target')
+ osutils.SafeMakedirs(self.public_conf_dir)
+
+ self.private_conf_dir = os.path.join(
+ self.tempdir, constants.PRIVATE_BINHOST_CONF_DIR, 'target')
+ osutils.SafeMakedirs(self.private_conf_dir)
+
+ def tearDown(self):
+ osutils.EmptyDir(self.tempdir)
+
+ def testSetBinhostPublic(self):
+ """SetBinhost returns correct public path and updates conf file."""
+ actual = binhost.SetBinhost(
+ 'coral', 'BINHOST_KEY', 'gs://prebuilts', private=False)
+ expected = os.path.join(self.public_conf_dir, 'coral-BINHOST_KEY.conf')
+ self.assertEqual(actual, expected)
+ self.assertEqual(osutils.ReadFile(actual), 'BINHOST_KEY="gs://prebuilts"')
+
+ def testSetBinhostPrivate(self):
+ """SetBinhost returns correct private path and updates conf file."""
+ actual = binhost.SetBinhost('coral', 'BINHOST_KEY', 'gs://prebuilts')
+ expected = os.path.join(self.private_conf_dir, 'coral-BINHOST_KEY.conf')
+ self.assertEqual(actual, expected)
+ self.assertEqual(osutils.ReadFile(actual), 'BINHOST_KEY="gs://prebuilts"')
+
+ def testSetBinhostEmptyConf(self):
+ """SetBinhost rejects existing but empty conf files."""
+ conf_path = os.path.join(self.private_conf_dir, 'multi-BINHOST_KEY.conf')
+ osutils.WriteFile(conf_path, ' ')
+ with self.assertRaises(ValueError):
+ binhost.SetBinhost('multi', 'BINHOST_KEY', 'gs://blah')
+
+ def testSetBinhostMultilineConf(self):
+ """SetBinhost rejects existing multiline conf files."""
+ conf_path = os.path.join(self.private_conf_dir, 'multi-BINHOST_KEY.conf')
+ osutils.WriteFile(conf_path, '\n'.join(['A="foo"', 'B="bar"']))
+ with self.assertRaises(ValueError):
+ binhost.SetBinhost('multi', 'BINHOST_KEY', 'gs://blah')
+
+ def testSetBinhhostBadConfLine(self):
+ """SetBinhost rejects existing conf files with malformed lines."""
+ conf_path = os.path.join(self.private_conf_dir, 'bad-BINHOST_KEY.conf')
+ osutils.WriteFile(conf_path, 'bad line')
+ with self.assertRaises(ValueError):
+ binhost.SetBinhost('bad', 'BINHOST_KEY', 'gs://blah')
+
+ def testSetBinhostMismatchedKey(self):
+ """SetBinhost rejects existing conf files with a mismatched key."""
+ conf_path = os.path.join(self.private_conf_dir, 'bad-key-GOOD_KEY.conf')
+ osutils.WriteFile(conf_path, 'BAD_KEY="https://foo.bar"')
+ with self.assertRaises(KeyError):
+ binhost.SetBinhost('bad-key', 'GOOD_KEY', 'gs://blah')