summaryrefslogtreecommitdiff
path: root/lib/cipd.py
blob: 427e02755ca965de029fd37efc0a066d942d6087 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# 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.

"""Module to download and run the CIPD client.

CIPD is the Chrome Infra Package Deployer, a simple method of resolving
package/architecture/version tuples into GStorage links and installing them.

"""
from __future__ import print_function

import hashlib
import json
import os
import pprint
import urllib
import urlparse

import third_party.httplib2 as httplib2

import chromite.lib.cros_logging as log
from chromite.lib import cache
from chromite.lib import osutils
from chromite.lib import path_util


# The version of CIPD to download.
# TODO(phobbs) we could make a call to the 'resolveVersion' endpoint
#   to resolve 'latest' into an instance_id for us.
CIPD_INSTANCE_ID = '03f354ad7a6031c7924d9b69a85f83269cc3c2e0'
CIPD_PACKAGE = 'infra/tools/cipd/linux-amd64'

CHROME_INFRA_PACKAGES_API_BASE = (
    'https://chrome-infra-packages.appspot.com/_ah/api/repo/v1/')


def _ChromeInfraRequest(endpoint, request_args=None):
  """Makes a request to the Chrome Infra Packages API with httplib2.

  Args:
    endpoint: The endpoint to make a request to.
    request_args: Keyword arguments to put into the request string.

  Returns:
    A tuple of (headers, content) returned by the server. The body content is
    assumed to be JSON.
  """
  uri = ''.join([CHROME_INFRA_PACKAGES_API_BASE,
                 endpoint,
                 '?',
                 urllib.urlencode(request_args or {})])
  result = httplib2.Http().request(uri=uri)
  try:
    return result[0], json.loads(result[1])
  except Exception as e:
    e.message = 'Encountered exception requesting "%s":\n' + e.message
    raise


def _DownloadCIPD(instance_id):
  """Finds the CIPD download link and requests the binary.

  The 'client' endpoit of the chrome infra packages API responds with a sha1 and
  a Google Storage link. After downloading the binary, we validate that the sha1
  of the response and return it.

  Args:
    instance_id: The version of CIPD to download.

  Returns:
    the CIPD binary as a string.
  """
  args = {'instance_id': instance_id, 'package_name': CIPD_PACKAGE}
  _, body = _ChromeInfraRequest('client', request_args=args)
  if not 'client_binary' in body:
    log.error(
        'Error requesting the link to download CIPD from. Got:\n%s',
        pprint.pformat(body))

  http = httplib2.Http(cache='.cache')
  response, binary = http.request(uri=body['client_binary']['fetch_url'])
  assert response['status'] == '200', (
      'Got a %s response from Google Storage.' % response['status'])
  digest = unicode(hashlib.sha1(binary).hexdigest())
  assert digest == body['client_binary']['sha1'], (
      'The binary downloaded does not match the expected SHA1.')
  return binary


class CipdCache(cache.RemoteCache):
  """Supports caching of the CIPD download."""
  def _Fetch(self, key, path):
    instance_id = urlparse.urlparse(key).netloc
    binary = _DownloadCIPD(instance_id)
    log.info('Fetched CIPD package %s:%s', CIPD_PACKAGE, instance_id)
    osutils.WriteFile(path, binary)
    os.chmod(path, 0755)


def GetCIPDFromCache(instance_id=CIPD_INSTANCE_ID):
  """Checks the cache, downloading CIPD if it is missing.

  Args:
    instance_id: The version of CIPD to download. Default CIPD_INSTANCE_ID

  Returns:
    Path to the CIPD binary.
  """
  cache_dir = os.path.join(path_util.GetCacheDir(), 'cipd')
  bin_cache = CipdCache(cache_dir)
  key = (instance_id,)
  ref = bin_cache.Lookup(key)
  ref.SetDefault('cipd://' + instance_id)
  return ref.path