Beispiel #1
0
def get_builds_for_patchset_async(project, issue_id, patchset_id):
  """Queries BuildBucket for builds associated with the patchset.

  Requests for max 500 builds and does not check "next_cursor". Currently if
  more than 100 builds are requested, only 100 are returned. Presumably there
  will be no patchsets with >100 builds.

  Returns:
    A list of buildbucket build dicts.
  """
  # See tag conventions http://cr-buildbucket.appspot.com/#docs/conventions .
  hostname = common.get_preferred_domain(project, default_to_appid=False)
  if not hostname:
    logging.error(
        'Preferred domain name for this app is not set. '
        'See PREFERRED_DOMAIN_NAMES in settings.py: %r', hostname)
    raise ndb.Return([])

  buildset_tag = BUILDSET_TAG_FORMAT.format(
      hostname=hostname,
      issue=issue_id,
      patch=patchset_id,
  )
  params = {
      'max_builds': 500,
      'tag': 'buildset:%s' % buildset_tag,
  }

  url = '%s/search' % BUILDBUCKET_API_ROOT
  logging.info(
      'Fetching builds for patchset %s/%s. Buildset: %s',
      issue_id, patchset_id, buildset_tag)
  try:
    resp = yield net.json_request_async(
        url, params=params,
        scopes='https://www.googleapis.com/auth/userinfo.email')
  except net.NotFoundError as ex:
    logging.error(
        'Buildbucket returned 404 unexpectedly. Body: %s', ex.response)
    raise
  if 'error' in resp:
    bb_error = resp.get('error', {})
    raise BuildBucketError(
        'BuildBucket responded with error (reason %s): %s' % (
            bb_error.get('reason', 'no-reason'),
            bb_error.get('message', 'no-message')))
  raise ndb.Return(resp.get('builds') or [])
Beispiel #2
0
def get_builds_for_patchset_async(project, issue_id, patchset_id):
    """Queries BuildBucket for builds associated with the patchset.

  Requests for max 500 builds and does not check "next_cursor". Currently if
  more than 100 builds are requested, only 100 are returned. Presumably there
  will be no patchsets with >100 builds.

  Returns:
    A list of buildbucket build dicts.
  """
    # See tag conventions http://cr-buildbucket.appspot.com/#docs/conventions .
    hostname = common.get_preferred_domain(project, default_to_appid=False)
    if not hostname:
        logging.error(
            'Preferred domain name for this app is not set. '
            'See PREFERRED_DOMAIN_NAMES in settings.py: %r', hostname)
        raise ndb.Return([])

    buildset_tag = BUILDSET_TAG_FORMAT.format(
        hostname=hostname,
        issue=issue_id,
        patch=patchset_id,
    )
    params = {
        'max_builds': 500,
        'tag': 'buildset:%s' % buildset_tag,
    }

    url = '%s/search' % BUILDBUCKET_API_ROOT
    logging.info('Fetching builds for patchset %s/%s. Buildset: %s', issue_id,
                 patchset_id, buildset_tag)
    try:
        resp = yield net.json_request_async(
            url,
            params=params,
            scopes='https://www.googleapis.com/auth/userinfo.email')
    except net.NotFoundError as ex:
        logging.error('Buildbucket returned 404 unexpectedly. Body: %s',
                      ex.response)
        raise
    if 'error' in resp:
        bb_error = resp.get('error', {})
        raise BuildBucketError(
            'BuildBucket responded with error (reason %s): %s' % (bb_error.get(
                'reason', 'no-reason'), bb_error.get('message', 'no-message')))
    raise ndb.Return(resp.get('builds') or [])
Beispiel #3
0
def _mint_delegation_token_async():
  """Generates an access token to impersonate the current user, if any.

  Memcaches the token.
  """
  account = models.Account.current_user_account
  if account is None:
    raise ndb.Return(None)

  ctx = ndb.get_context()
  # Get from cache.
  cache_key = IMPERSONATION_TOKEN_CACHE_KEY_FORMAT % account.email
  token = yield ctx.memcache_get(cache_key)
  if token:
    raise ndb.Return(token)

  # Request a new one.
  logging.debug('Minting a delegation token for %s', account.email)
  req = {
    'audience': ['user:%s' % app_identity.get_service_account_name()],
    'services': ['service:%s' % BUILDBUCKET_APP_ID],
    'impersonate': 'user:%s' % account.email,
  }
  resp = yield net.json_request_async(
      IMPERSONATION_TOKEN_MINT_URL,
      method='POST',
      payload=req,
      scopes=net.EMAIL_SCOPE)
  token = resp.get('delegation_token')
  if not token:
    raise BuildBucketError(
        'Could not mint a delegation token. Response: %s' % resp)

  # Put to cache.
  validity_duration_sec = resp.get('validity_duration')
  assert isinstance(validity_duration_sec, int)
  if validity_duration_sec >= 10:
    validity_duration_sec -= 10  # Refresh the token 10 sec in advance.
    yield ctx.memcache_add(cache_key, token, time=validity_duration_sec)

  raise ndb.Return(token)
Beispiel #4
0
def rpc_async(method, path, **kwargs):
  """Makes an authenticated request to buildbucket.

  Impersonates the current user if he/she is logged in.
  Otherwise sends an anonymous request.
  """
  assert 'scopes' not in kwargs
  assert 'headers' not in kwargs
  url = '%s/%s' % (BUILDBUCKET_API_ROOT, path)
  delegation_token = yield _mint_delegation_token_async()
  headers = {}
  scopes = None
  if delegation_token:
    headers['X-Delegation-Token-V1'] = delegation_token
    scopes = net.EMAIL_SCOPE
  res = yield net.json_request_async(
      url, method=method,
      headers=headers,
      scopes=scopes,
      **kwargs)
  raise ndb.Return(res)
Beispiel #5
0
def rpc_async(method, path, **kwargs):
    """Makes an authenticated request to buildbucket.

  Impersonates the current user if he/she is logged in.
  Otherwise sends an anonymous request.
  """
    assert 'scopes' not in kwargs
    assert 'headers' not in kwargs
    url = '%s/%s' % (BUILDBUCKET_API_ROOT, path)
    delegation_token = yield _mint_delegation_token_async()
    headers = {}
    scopes = None
    if delegation_token:
        headers['X-Delegation-Token-V1'] = delegation_token
        scopes = net.EMAIL_SCOPE
    res = yield net.json_request_async(url,
                                       method=method,
                                       headers=headers,
                                       scopes=scopes,
                                       **kwargs)
    raise ndb.Return(res)
Beispiel #6
0
def _mint_delegation_token_async():
    """Generates an access token to impersonate the current user, if any.

  Memcaches the token.
  """
    account = models.Account.current_user_account
    if account is None:
        raise ndb.Return(None)

    ctx = ndb.get_context()
    # Get from cache.
    cache_key = IMPERSONATION_TOKEN_CACHE_KEY_FORMAT % account.email
    token_envelope = yield ctx.memcache_get(cache_key)
    if token_envelope:
        # Randomize token expiration time to workaround the case when multiple
        # concurrent requests start to refresh the token at the same time.
        token, exp_ts, lifetime_sec = token_envelope
        if time.time() < exp_ts - lifetime_sec * 0.05 * random.random():
            logging.info('Fetched cached delegation token: fingerprint=%s',
                         _get_token_fingerprint(token))
            raise ndb.Return(token)

    # Request a new one.
    logging.debug('Minting a delegation token for %s', account.email)
    req = {
        'delegatedIdentity': 'user:%s' % account.email,
        'audience': ['REQUESTOR'],
        'services': ['service:%s' % BUILDBUCKET_APP_ID],
        'validityDuration': 5 * 3600,
    }
    resp = yield net.json_request_async(
        IMPERSONATION_TOKEN_MINT_URL,
        method='POST',
        payload=req,
        scopes=net.EMAIL_SCOPE,
        headers={'Accept': 'application/json; charset=utf-8'})

    signed_token = resp.get('token')
    if not signed_token:
        raise BuildBucketError(
            'Could not mint a delegation token. Response: %s' % resp)

    token_struct = resp.get('delegationSubtoken')
    if not token_struct or not isinstance(token_struct, dict):
        logging.error('Bad delegation token response: %s', resp)
        raise BuildBucketError('Could not mint a delegation token')

    logging.info(
        'Token server "%s" generated token (subtoken_id=%s, fingerprint=%s):\n%s',
        resp.get('serviceVersion'), token_struct.get('subtokenId'),
        _get_token_fingerprint(signed_token),
        json.dumps(token_struct,
                   sort_keys=True,
                   indent=2,
                   separators=(',', ': ')))

    # Put to cache.
    validity_duration_sec = token_struct.get('validityDuration')
    assert isinstance(validity_duration_sec, (int, float))
    if validity_duration_sec >= 10:
        validity_duration_sec -= 10  # Refresh the token 10 sec in advance.
        exp_ts = int(time.time() + validity_duration_sec)
        yield ctx.memcache_set(key=cache_key,
                               value=(signed_token, exp_ts,
                                      validity_duration_sec),
                               time=exp_ts)

    raise ndb.Return(signed_token)