Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(671)

Unified Diff: infra/tools/cros_pin/pinfile.py

Issue 1403313002: Added `cros_pin` CrOS pin-bump tool. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Added pinfile.py tests, cleanup. Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « infra/tools/cros_pin/logger.py ('k') | infra/tools/cros_pin/test/__init__.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: infra/tools/cros_pin/pinfile.py
diff --git a/infra/tools/cros_pin/pinfile.py b/infra/tools/cros_pin/pinfile.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc6251a243110869787eb48674e1d0e860c964ae
--- /dev/null
+++ b/infra/tools/cros_pin/pinfile.py
@@ -0,0 +1,132 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import collections
+import contextlib
+import json
+import os
+import re
+
+from infra.libs.gitiles import gitiles
+from infra.tools.cros_pin.logger import LOGGER
+
+class InvalidPinError(Exception):
+ pass
+
+class ReadOnlyError(Exception):
+ pass
+
+
+# Named Chromite pins to their checkout-relative paths.
+Config = collections.namedtuple('Config',
+ ('name', 'base', 'json_subpath', 'masters'))
+
+
+EXTERNAL = Config(
+ 'external',
+ ('build',),
+ ('scripts', 'common', 'cros_chromite_pins.json'),
+ ('chromiumos',))
+
+
+INTERNAL = Config(
+ 'internal',
+ ('build_internal',),
+ ('scripts', 'common_internal', 'cros_chromite_internal_pins.json'),
+ ('chromeos', 'chromeos_release'))
+
+
+PinUpdate = collections.namedtuple('PinUpdate',
+ ('name', 'fr', 'to'))
+
+
+class Editor(object):
+
+ # Regular expression to match a Git commit (SHA1)
+ RE_COMMIT_SHA1 = re.compile(r'^[a-fA-F0-9]{40}$')
+
+ def __init__(self, checkout_path, gitiles_repo, validate=True):
+ self._checkout_path = checkout_path
+ self._gitiles = None
+ self._validate = validate
+ self._gitiles = gitiles_repo
+
+ def load(self, pin):
+ return self.File(self, pin)
+
+ def get_commit(self, branch):
+ try:
+ return self._gitiles.ref_info(branch)['commit']
+ except gitiles.GitilesError:
+ raise InvalidPinError("Pin ref [%s] does not exist." % (branch,))
+
+ def validate_pin(self, ref):
+ if not self.RE_COMMIT_SHA1.match(ref):
+ raise InvalidPinError("Not a valid SHA1 hash")
+ self.get_commit(ref)
+
+ class File(object):
+ def __init__(self, editor, pin):
+ self._editor = editor
+ self._pin = pin
+ self._path = os.path.join(editor._checkout_path,
+ *(pin.base + pin.json_subpath))
+
+ @contextlib.contextmanager
+ def edit(self):
+ d = self.load()
+ orig = d.copy()
+ try:
+ yield d
+ finally:
+ if d != orig:
+ self.save(**d)
+
+ def load(self):
+ with open(self._path, 'r') as fd:
+ return json.load(fd)
+
+ def save(self, **pins):
+ for k, v in pins.iteritems():
+ assert isinstance(k, basestring)
+ assert isinstance(v, basestring)
+ LOGGER.debug('Writing pin file [%s]: %s', self._path, pins)
+ with open(self._path, 'w') as fd:
+ json.dump(pins, fd, indent=2, sort_keys=True)
+
+
+ def update(self, pin_name, create=False, version=None):
+ """Updates a single pin value."""
+ if not version:
+ LOGGER.debug('Resolving version for pin [%s]', pin_name)
+ version = self._editor.get_commit(pin_name)
+ elif self._editor._validate:
+ LOGGER.debug('Validating pin [%s]', pin_name)
+ self._editor.validate_pin(version)
+
+ with self.edit() as pins:
+ current = pins.get(pin_name)
+ if current == version:
+ LOGGER.warning('Pin [%s.%s] is already at version [%s]',
+ self._pin.name, pin_name, current)
+ return None
+
+ LOGGER.info('Updating pin [%s.%s]: [%s] => [%s]',
+ self._pin.name, pin_name, current, version)
+ if not (current or create):
+ raise ReadOnlyError("Pin does not exist [%s]" % (pin_name,))
+ pins[pin_name] = version
+ return PinUpdate(pin_name, current, version)
+
+ def remove(self, pin_name):
+ """Removes a single pin from the pin list."""
+ with self.edit() as pins:
+ cur = pins.pop(pin_name, None)
+ if cur is None:
+ return None
+ return PinUpdate(pin_name, cur, None)
+
+ def iterpins(self):
+ """Returns a list of pins."""
+ return self.load().iteritems()
« no previous file with comments | « infra/tools/cros_pin/logger.py ('k') | infra/tools/cros_pin/test/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698