blob: 5e74422eefe23ba9116a05ff914f60e828f1bb9f [file] [log] [blame]
Amin Hassani2aa34282020-11-18 01:18:19 +00001# -*- 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
Amin Hassani3587fb32021-04-28 10:10:01 -070026from chromite.lib import cros_logging as logging
Amin Hassani2aa34282020-11-18 01:18:19 +000027
28
29# Module-local log function.
30def _Log(message, *args):
Amin Hassani3587fb32021-04-28 10:10:01 -070031 return logging.info(message, *args)
32
Amin Hassani2aa34282020-11-18 01:18:19 +000033
34class AutoupdateError(Exception):
35 """Exception classes used by this module."""
Amin Hassanibf9e0402021-02-23 19:41:00 -080036 # pylint: disable=unnecessary-pass
Amin Hassani2aa34282020-11-18 01:18:19 +000037 pass
38
39
40def _ChangeUrlPort(url, new_port):
41 """Return the URL passed in with a different port"""
42 scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
43 host_port = netloc.split(':')
44
45 if len(host_port) == 1:
46 host_port.append(new_port)
47 else:
48 host_port[1] = new_port
49
50 print(host_port)
51 netloc = '%s:%s' % tuple(host_port)
52
53 # pylint: disable=too-many-function-args
54 return urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))
55
56def _NonePathJoin(*args):
57 """os.path.join that filters None's from the argument list."""
58 return os.path.join(*[x for x in args if x is not None])
59
60
61class Autoupdate(object):
62 """Class that contains functionality that handles Chrome OS update pings."""
63
64 def __init__(self, xbuddy, static_dir=None):
65 """Initializes the class.
66
67 Args:
68 xbuddy: The xbuddy path.
69 static_dir: The path to the devserver static directory.
70 """
71 self.xbuddy = xbuddy
72 self.static_dir = static_dir
73
Amin Hassani2aa34282020-11-18 01:18:19 +000074 def GetDevserverUrl(self):
75 """Returns the devserver url base."""
76 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
77 if x_forwarded_host:
78 # Select the left most <ip>:<port> value so that the request is
79 # forwarded correctly.
80 x_forwarded_host = [x.strip() for x in x_forwarded_host.split(',')][0]
81 hostname = 'http://' + x_forwarded_host
82 else:
83 hostname = cherrypy.request.base
84
85 return hostname
86
87 def GetStaticUrl(self):
88 """Returns the static url base that should prefix all payload responses."""
89 hostname = self.GetDevserverUrl()
90 _Log('Handling update ping as %s', hostname)
91
92 static_urlbase = '%s/static' % hostname
93 _Log('Using static url base %s', static_urlbase)
94 return static_urlbase
95
Amin Hassani624b7ae2020-12-04 10:55:49 -080096 def GetBuildID(self, label, board):
97 """Find the build id of the given lable and board.
Amin Hassani2aa34282020-11-18 01:18:19 +000098
99 Args:
100 label: from update request
101 board: from update request
102
103 Returns:
Amin Hassani624b7ae2020-12-04 10:55:49 -0800104 The build id of given label and board. e.g. reef-release/R88-13591.0.0
Amin Hassani2aa34282020-11-18 01:18:19 +0000105
106 Raises:
107 AutoupdateError: If the update could not be found.
108 """
109 label = label or ''
110 label_list = label.split('/')
Amin Hassani8b503582020-12-02 20:47:01 -0800111 if label_list[0] == 'xbuddy':
112 # If path explicitly calls xbuddy, pop off the tag.
113 label_list.pop()
114 x_label, _ = self.xbuddy.Translate(label_list, board=board)
Amin Hassani624b7ae2020-12-04 10:55:49 -0800115 return x_label
Amin Hassani2aa34282020-11-18 01:18:19 +0000116
117 def HandleUpdatePing(self, data, label='', **kwargs):
118 """Handles an update ping from an update client.
119
120 Args:
121 data: XML blob from client.
122 label: optional label for the update.
123 kwargs: The map of query strings passed to the /update API.
124
125 Returns:
126 Update payload message for client.
127 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000128 # Change the URL's string query dictionary provided by cherrypy to a valid
129 # dictionary that has proper values for its keys. e.g. True instead of
130 # 'True'.
131 kwargs = nebraska.QueryDictToDict(kwargs)
132
133 # Process attributes of the update check.
134 request = nebraska.Request(data)
135 if request.request_type == nebraska.Request.RequestType.EVENT:
136 _Log('A non-update event notification received. Returning an ack.')
Amin Hassanibf9e0402021-02-23 19:41:00 -0800137 n = nebraska.Nebraska()
138 n.UpdateConfig(**kwargs)
139 return n.GetResponseToRequest(request)
Amin Hassani2aa34282020-11-18 01:18:19 +0000140
141 _Log('Update Check Received.')
142
143 try:
Amin Hassani624b7ae2020-12-04 10:55:49 -0800144 build_id = self.GetBuildID(label, request.board)
145 base_url = '/'.join((self.GetStaticUrl(), build_id))
146 local_payload_dir = _NonePathJoin(self.static_dir, build_id)
Amin Hassani2aa34282020-11-18 01:18:19 +0000147 except AutoupdateError as e:
148 # Raised if we fail to generate an update payload.
149 _Log('Failed to process an update request, but we will defer to '
150 'nebraska to respond with no-update. The error was %s', e)
151
152 _Log('Responding to client to use url %s to get image', base_url)
Amin Hassanibf9e0402021-02-23 19:41:00 -0800153 n = nebraska.Nebraska()
154 n.UpdateConfig(update_payloads_address=base_url,
155 update_app_index=nebraska.AppIndex(local_payload_dir))
156 return n.GetResponseToRequest(request)