blob: 9eade415c5b8e42efea16fe4459b6f1482c5e65a [file] [log] [blame]
Amin Hassani2aa34282020-11-18 01:18:191# -*- coding: utf-8 -*-
2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Devserver module for handling update client requests."""
7
8from __future__ import print_function
9
10import os
11
12from six.moves import urllib
13
14import cherrypy # pylint: disable=import-error
15
16# TODO(crbug.com/872441): We try to import nebraska from different places
17# because when we install the devserver, we copy the nebraska.py into the main
18# directory. Once this bug is resolved, we can always import from nebraska
19# directory.
20try:
21 from nebraska import nebraska
22except ImportError:
23 import nebraska
24
25import setup_chromite # pylint: disable=unused-import
26from chromite.lib.xbuddy import cherrypy_log_util
Amin Hassani2aa34282020-11-18 01:18:1927
28
29# Module-local log function.
30def _Log(message, *args):
31 return cherrypy_log_util.LogWithTag('UPDATE', message, *args)
32
33class AutoupdateError(Exception):
34 """Exception classes used by this module."""
Amin Hassanibf9e0402021-02-24 03:41:0035 # pylint: disable=unnecessary-pass
Amin Hassani2aa34282020-11-18 01:18:1936 pass
37
38
39def _ChangeUrlPort(url, new_port):
40 """Return the URL passed in with a different port"""
41 scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
42 host_port = netloc.split(':')
43
44 if len(host_port) == 1:
45 host_port.append(new_port)
46 else:
47 host_port[1] = new_port
48
49 print(host_port)
50 netloc = '%s:%s' % tuple(host_port)
51
52 # pylint: disable=too-many-function-args
53 return urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))
54
55def _NonePathJoin(*args):
56 """os.path.join that filters None's from the argument list."""
57 return os.path.join(*[x for x in args if x is not None])
58
59
60class Autoupdate(object):
61 """Class that contains functionality that handles Chrome OS update pings."""
62
63 def __init__(self, xbuddy, static_dir=None):
64 """Initializes the class.
65
66 Args:
67 xbuddy: The xbuddy path.
68 static_dir: The path to the devserver static directory.
69 """
70 self.xbuddy = xbuddy
71 self.static_dir = static_dir
72
Amin Hassani2aa34282020-11-18 01:18:1973 def GetDevserverUrl(self):
74 """Returns the devserver url base."""
75 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
76 if x_forwarded_host:
77 # Select the left most <ip>:<port> value so that the request is
78 # forwarded correctly.
79 x_forwarded_host = [x.strip() for x in x_forwarded_host.split(',')][0]
80 hostname = 'http://' + x_forwarded_host
81 else:
82 hostname = cherrypy.request.base
83
84 return hostname
85
86 def GetStaticUrl(self):
87 """Returns the static url base that should prefix all payload responses."""
88 hostname = self.GetDevserverUrl()
89 _Log('Handling update ping as %s', hostname)
90
91 static_urlbase = '%s/static' % hostname
92 _Log('Using static url base %s', static_urlbase)
93 return static_urlbase
94
Amin Hassani624b7ae2020-12-04 18:55:4995 def GetBuildID(self, label, board):
96 """Find the build id of the given lable and board.
Amin Hassani2aa34282020-11-18 01:18:1997
98 Args:
99 label: from update request
100 board: from update request
101
102 Returns:
Amin Hassani624b7ae2020-12-04 18:55:49103 The build id of given label and board. e.g. reef-release/R88-13591.0.0
Amin Hassani2aa34282020-11-18 01:18:19104
105 Raises:
106 AutoupdateError: If the update could not be found.
107 """
108 label = label or ''
109 label_list = label.split('/')
Amin Hassani8b503582020-12-03 04:47:01110 if label_list[0] == 'xbuddy':
111 # If path explicitly calls xbuddy, pop off the tag.
112 label_list.pop()
113 x_label, _ = self.xbuddy.Translate(label_list, board=board)
Amin Hassani624b7ae2020-12-04 18:55:49114 return x_label
Amin Hassani2aa34282020-11-18 01:18:19115
116 def HandleUpdatePing(self, data, label='', **kwargs):
117 """Handles an update ping from an update client.
118
119 Args:
120 data: XML blob from client.
121 label: optional label for the update.
122 kwargs: The map of query strings passed to the /update API.
123
124 Returns:
125 Update payload message for client.
126 """
Amin Hassani2aa34282020-11-18 01:18:19127 # Change the URL's string query dictionary provided by cherrypy to a valid
128 # dictionary that has proper values for its keys. e.g. True instead of
129 # 'True'.
130 kwargs = nebraska.QueryDictToDict(kwargs)
131
132 # Process attributes of the update check.
133 request = nebraska.Request(data)
134 if request.request_type == nebraska.Request.RequestType.EVENT:
135 _Log('A non-update event notification received. Returning an ack.')
Amin Hassanibf9e0402021-02-24 03:41:00136 n = nebraska.Nebraska()
137 n.UpdateConfig(**kwargs)
138 return n.GetResponseToRequest(request)
Amin Hassani2aa34282020-11-18 01:18:19139
140 _Log('Update Check Received.')
141
142 try:
Amin Hassani624b7ae2020-12-04 18:55:49143 build_id = self.GetBuildID(label, request.board)
144 base_url = '/'.join((self.GetStaticUrl(), build_id))
145 local_payload_dir = _NonePathJoin(self.static_dir, build_id)
Amin Hassani2aa34282020-11-18 01:18:19146 except AutoupdateError as e:
147 # Raised if we fail to generate an update payload.
148 _Log('Failed to process an update request, but we will defer to '
149 'nebraska to respond with no-update. The error was %s', e)
150
151 _Log('Responding to client to use url %s to get image', base_url)
Amin Hassanibf9e0402021-02-24 03:41:00152 n = nebraska.Nebraska()
153 n.UpdateConfig(update_payloads_address=base_url,
154 update_app_index=nebraska.AppIndex(local_payload_dir))
155 return n.GetResponseToRequest(request)