Index: client/cipd.py |
diff --git a/client/cipd.py b/client/cipd.py |
index 927ec594354790a93fbc22c418c1af17fda29daf..a6ebd3043cd2b9a565f790e9d73467951b469f76 100644 |
--- a/client/cipd.py |
+++ b/client/cipd.py |
@@ -11,6 +11,8 @@ import logging |
import optparse |
import os |
import platform |
+import re |
+import shutil |
import sys |
import tempfile |
import time |
@@ -21,6 +23,7 @@ from utils import fs |
from utils import net |
from utils import subprocess42 |
from utils import tools |
+import isolate |
kjlubick
2017/05/03 18:28:33
This introduces a dependency cycle that I have no
|
import isolated_format |
import isolateserver |
@@ -140,12 +143,109 @@ class CipdClient(object): |
self.instance_id = instance_id |
self.service_url = service_url |
+ def _ensure_from_isolate(self, root, subdir, isolated_cipd, isolate_cache): |
+ if not isolate_cache: |
+ logging.info('Not ensuring cipd from isolate cache isolate_cache is not' |
+ 'defined: %s', isolate_cache) |
+ return False |
+ try: |
+ with open(isolated_cipd , 'r') as f: |
+ digest = str(f.read()) |
+ try: |
+ content = isolate_cache.getfileobj(digest).read() |
+ except Exception as e: |
+ logging.warning('Could not find isolated file in cache with digest ' |
+ '%s: %s', digest, e) |
+ return False |
+ |
+ #FIXME(kjlubick): Don't assume sha1 |
+ sha_1 = isolated_format.SUPPORTED_ALGOS['sha-1'] |
+ ifile = isolated_format.IsolatedFile(digest, sha_1) |
+ ifile.load(content) |
+ |
+ subdir = os.path.join(root, subdir) |
+ file_path.ensure_tree(subdir) |
+ files = ifile.data.get(u'files', {}) |
+ for f in files.keys(): |
+ props = files.get(f, None) |
+ if not props: |
+ logging.warning('Problem getting info for %s', f) |
+ return False |
+ file_mode = props.get('m', None) |
+ if file_mode: |
+ # Ignore all bits apart from the user |
+ file_mode &= 0700 |
+ |
+ dstpath = os.path.join(subdir, f) |
+ file_path.ensure_tree(os.path.dirname(dstpath)) |
+ digest = props.get('h', None) |
+ if not digest: |
+ logging.warning('Hash can\'t be empty %s', f) |
+ return False |
+ srcpath = isolate_cache.getfileobj(digest).name |
+ |
+ file_path.link_file(unicode(dstpath), unicode(srcpath), |
+ file_path.HARDLINK_WITH_FALLBACK) |
+ |
+ if file_mode is not None: |
+ fs.chmod(dstpath, file_mode) |
+ except Exception as e: |
+ logging.warning('Could not ensure cipd package from isolate %s', e) |
+ |
+ return True |
+ |
+ |
+ def _isolate_cipd(self, root, packages, isolate_cache, cipd_cache): |
+ logging.debug('_isolate_cipd(%s, %s, %s, %s)', root, packages, |
+ isolate_cache, cipd_cache) |
+ if not isolate_cache or not os.path.isdir(cipd_cache): |
+ logging.info('Not putting cipd into isolate cache because one of the' |
+ 'caches is empty: %s, %s', isolate_cache, cipd_cache) |
+ return |
+ for subdir, shafile in packages.iteritems(): |
+ isolated_file = os.path.join(cipd_cache, shafile[:-5]) |
+ complete_state = isolate.CompleteState.load_files(isolated_file) |
+ |
+ subdir = os.path.join(root, subdir) |
+ |
+ infiles = isolated_format.expand_directories_and_symlinks( |
+ subdir, ['./'], [], False, True) |
+ |
+ complete_state.saved_state.update_isolated(None, infiles, True, None) |
+ complete_state.saved_state.root_dir = subdir |
+ # Collapse symlinks to remove CIPD symlinking AND to simplify other code. |
+ complete_state.files_to_metadata('', True) |
+ complete_state.save_files() |
+ |
+ for infile in infiles: |
+ digest = complete_state.saved_state.files[infile].get('h', '') |
+ if not digest: |
+ logging.warning('No digest found in saved state %s:%s', infile, |
+ complete_state.saved_state.files[infile]) |
+ continue |
+ with open(os.path.join(subdir, infile) , 'r') as f: |
+ isolate_cache.write(digest, f) |
+ |
+ with open(isolated_file , 'r') as f: |
+ content = f.read() |
+ digest = complete_state.saved_state.algo(content).hexdigest() |
+ isolate_cache.write(digest, content) |
+ |
+ with open(os.path.join(cipd_cache, shafile), 'w') as sf: |
+ sf.write(digest) |
+ |
+ |
+ |
def ensure( |
- self, site_root, packages, cache_dir=None, tmp_dir=None, timeout=None): |
+ self, site_root, packages, cache_dir=None, tmp_dir=None, timeout=None, |
+ isolate_cache=None): |
"""Ensures that packages installed in |site_root| equals |packages| set. |
Blocking call. |
+ Attempts to use the isolate cache to store the unzipped cipd files, keeping |
+ a .isolated file in the cipd cache_dir |
+ |
Args: |
site_root (str): where to install packages. |
packages: dict of subdir -> list of (package_template, version) tuples. |
@@ -163,26 +263,49 @@ class CipdClient(object): |
""" |
timeoutfn = tools.sliding_timeout(timeout) |
logging.info('Installing packages %r into %s', packages, site_root) |
+ logging.info('Cache dir %s', cache_dir) |
ensure_file_handle, ensure_file_path = tempfile.mkstemp( |
dir=tmp_dir, prefix=u'cipd-ensure-file-', suffix='.txt') |
json_out_file_handle, json_file_path = tempfile.mkstemp( |
dir=tmp_dir, prefix=u'cipd-ensure-result-', suffix='.json') |
os.close(json_out_file_handle) |
- |
+ if cache_dir: |
+ file_path.ensure_tree(unicode(cache_dir)) |
+ to_isolate = {} |
+ from_isolate = {} |
try: |
try: |
for subdir, pkgs in sorted(packages.iteritems()): |
if '\n' in subdir: |
raise Error( |
'Could not install packages; subdir %r contains newline' % subdir) |
+ |
+ versions = [p[1] for p in pkgs] |
+ isolated_cipd = '%s.%s.isolated.sha1' % (subdir, |
+ '_'.join(versions)) |
+ abs_isolated_cipd = os.path.join(cache_dir, isolated_cipd) |
+ if (os.path.isfile(abs_isolated_cipd) and |
+ self._ensure_from_isolate(site_root, subdir, abs_isolated_cipd, |
+ isolate_cache)): |
+ from_isolate[unicode(subdir)] = pkgs |
+ continue |
+ to_isolate[subdir] = isolated_cipd |
os.write(ensure_file_handle, '@Subdir %s\n' % (subdir,)) |
for pkg, version in pkgs: |
pkg = render_package_name_template(pkg) |
os.write(ensure_file_handle, '%s %s\n' % (pkg, version)) |
+ |
finally: |
os.close(ensure_file_handle) |
+ # to_isolate is the packages that we need to ensure from CIPD and then |
+ # isolate. Thus, if this is empty, we don't need to get anything from |
+ # CIPD because they were successfully pulled from isolate. Thus return |
+ # from_isolate, the pinned packages that we pulled from_isolate |
+ if not to_isolate: |
+ return from_isolate |
+ |
cmd = [ |
self.binary_path, 'ensure', |
'-root', site_root, |
@@ -219,12 +342,16 @@ class CipdClient(object): |
raise Error( |
'Could not install packages; exit code %d\noutput:%s' % ( |
exit_code, '\n'.join(output))) |
+ |
+ self._isolate_cipd(site_root, to_isolate, isolate_cache, cache_dir) |
+ |
with open(json_file_path) as jfile: |
result_json = json.load(jfile) |
- return { |
+ from_isolate.update({ |
subdir: [(x['package'], x['instance_id']) for x in pins] |
for subdir, pins in result_json['result'].iteritems() |
- } |
+ }) |
+ return from_isolate |
finally: |
fs.remove(ensure_file_path) |
fs.remove(json_file_path) |